diff --git a/Makefile b/Makefile index db3fc5f..27a7e09 100644 --- a/Makefile +++ b/Makefile @@ -68,9 +68,6 @@ rebuild: clean all run: $(TARGET) ./$(TARGET) -# Run manual integration test with simulation -test-manual: $(TARGET) - ./$(TARGET) configs/test_simple.toml --headless --readable --days 365 # Build automated test suite test-build: @@ -101,4 +98,4 @@ test-build: test: test-build ./$(TEST_TARGET) -.PHONY: all clean clean-all rebuild run test test-build test-manual raylib +.PHONY: all clean clean-all rebuild run test test-build raylib diff --git a/README.md b/README.md index 63fb26d..fa09497 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,22 @@ make clean-all # Clean everything including raylib - **[Technical Reference](docs/implementation_plan.md)** - Data structures and module overview - **[Detailed Architecture](docs/verbose_project_overview.md)** - Complete implementation details and data flow -- **[Testing Guide](docs/test_verification.md)** - Verification commands and expected behavior - **[Known Issues](docs/config_assumptions.md)** - Configuration bugs and future improvements +## Testing + +```bash +make test # Run full automated test suite +``` + +The test suite validates: +- Orbital period accuracy (Earth, Mars) +- Energy conservation (RK4 integration) +- SOI transitions and multi-body interactions +- Eccentric orbit behavior (comets, parabolic, hyperbolic) + +All tests use Catch2 framework with configurable time steps and durations. + ## License This project is provided as-is for educational and research purposes. diff --git a/docs/test_verification.md b/docs/test_verification.md deleted file mode 100644 index 9a53f67..0000000 --- a/docs/test_verification.md +++ /dev/null @@ -1,61 +0,0 @@ -# Test Verification Guide - -Quick reference for testing orbital mechanics using `configs/test_simple.txt`. - -## Test Command - -```bash -make -./orbit_sim configs/test_simple.txt --headless --readable --days 365 -``` - -## Test Bodies - -- **Sun**: Origin (0, 0, 0) -- **Earth**: 1.0 AU, circular orbit (e=0), period ~365 days -- **Mars**: 1.5 AU, circular orbit (e=0), period ~687 days -- **Comet**: 2.5 AU semi-major axis, eccentric orbit (e=0.7), period ~1444 days - - Starts at perihelion (0.75 AU) - - Aphelion at 4.25 AU - -All orbit counter-clockwise (viewed from +Z). - -## Expected Behavior - -**Circular orbits (Earth, Mars):** -- Distance from Sun remains constant -- Velocity magnitude remains constant - - Earth: ~29.8 km/s - - Mars: ~24.3 km/s -- Return to starting position after full period - -**Eccentric orbit (Comet):** -- Distance varies: 0.75 AU (perihelion) to 4.25 AU (aphelion) -- Velocity varies: faster at perihelion, slower at aphelion -- Period ~1444 days - -## Quick Tests - -```bash -# Earth full orbit -./orbit_sim configs/test_simple.txt --headless --readable --days 365 - -# Mars full orbit -./orbit_sim configs/test_simple.txt --headless --readable --days 687 - -# Comet full orbit (eccentric) -./orbit_sim configs/test_simple.txt --headless --readable --days 1444 -``` - -## Acceptable Tolerances - -Due to Euler integration with dt=60s: -- Distance variation: ±0.001 AU -- Velocity variation: ±0.1 km/s -- Position after full orbit: Within 0.01 AU of start - -## Output Flags - -- `--headless`: Terminal output only (no GUI) -- `--readable`: Human-friendly units (AU, km/s) instead of SI (m, m/s) -- `--days N`: Simulation duration in days diff --git a/src/main.cpp b/src/main.cpp index 0c1b17d..7071925 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,64 +9,16 @@ // Configuration defaults - edit to change default run mode #define DEFAULT_CONFIG_FILE "configs/test_simple.toml" -#define DEFAULT_HEADLESS 0 // 0 = GUI mode, 1 = headless mode -#define DEFAULT_READABLE 1 // 0 = scientific notation, 1 = human-readable (AU, km/s) -#define DEFAULT_SIM_DAYS 365 // Default simulation duration for headless mode - -// Unit conversion constants -const double AU = 1.496e11; // 1 AU in meters -const double KM = 1000.0; // 1 km in meters - -// Helper functions for human-readable output -void print_position_readable(const char* name, Vec3 pos) { - double dist_au = vec3_magnitude(pos) / AU; - double x_au = pos.x / AU; - double y_au = pos.y / AU; - double z_au = pos.z / AU; - - // Calculate polar coordinates (angle in XY plane) - double angle_rad = atan2(pos.y, pos.x); - double angle_deg = angle_rad * 180.0 / M_PI; - if (angle_deg < 0.0) angle_deg += 360.0; // Convert to 0-360 range - - printf("%s:\n", name); - printf(" Position: (%.6f, %.6f, %.6f) AU\n", x_au, y_au, z_au); - printf(" Polar (XY plane): r=%.6f AU, θ=%.2f°\n", dist_au, angle_deg); -} - -void print_velocity_readable(const char* name, Vec3 vel) { - double speed_km_s = vec3_magnitude(vel) / KM; - printf(" Velocity magnitude: %.3f km/s\n", speed_km_s); - printf(" Velocity: (%.3f, %.3f, %.3f) km/s\n", - vel.x / KM, vel.y / KM, vel.z / KM); -} - -void print_body_readable(CelestialBody* body) { - print_position_readable(body->name, body->position); - print_velocity_readable(body->name, body->velocity); -} struct ProgramArgs { const char* config_file; - bool headless; - bool readable; - int sim_duration_days; }; void parse_command_line_args(int argc, char** argv, ProgramArgs* args) { args->config_file = DEFAULT_CONFIG_FILE; - args->headless = DEFAULT_HEADLESS; - args->readable = DEFAULT_READABLE; - args->sim_duration_days = DEFAULT_SIM_DAYS; for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--headless") == 0 || strcmp(argv[i], "-h") == 0) { - args->headless = true; - } else if (strcmp(argv[i], "--readable") == 0 || strcmp(argv[i], "-r") == 0) { - args->readable = true; - } else if (strcmp(argv[i], "--days") == 0 && i + 1 < argc) { - args->sim_duration_days = atoi(argv[++i]); - } else if (argv[i][0] != '-') { + if (argv[i][0] != '-') { args->config_file = argv[i]; } } @@ -75,110 +27,6 @@ void parse_command_line_args(int argc, char** argv, ProgramArgs* args) { void print_startup_info(const ProgramArgs* args) { printf("=== Orbital Mechanics Simulation ===\n"); printf("Loading configuration: %s\n", args->config_file); - if (args->headless) { - printf("Mode: Headless (terminal output only)\n"); - printf("Duration: %d days\n", args->sim_duration_days); - if (args->readable) { - printf("Output: Human-readable units (AU, km/s)\n"); - } - } -} - -void run_headless_simulation(SimulationState* sim, const ProgramArgs* args) { - const double SECONDS_PER_DAY = 86400.0; - const double total_time = args->sim_duration_days * SECONDS_PER_DAY; - const double output_interval = SECONDS_PER_DAY; - double next_output_time = 0.0; - - CelestialBody* initial_state = (CelestialBody*)malloc(sim->body_count * sizeof(CelestialBody)); - int* previous_parents = (int*)malloc(sim->body_count * sizeof(int)); - for (int i = 0; i < sim->body_count; i++) { - initial_state[i] = sim->bodies[i]; - previous_parents[i] = sim->bodies[i].parent_index; - } - - printf("\n=== Initial State ===\n"); - for (int i = 0; i < sim->body_count; i++) { - CelestialBody* body = &sim->bodies[i]; - if (args->readable) { - print_body_readable(body); - } else { - printf("%s:\n", body->name); - printf(" Position: (%.3e, %.3e, %.3e) m\n", - body->position.x, body->position.y, body->position.z); - printf(" Velocity: (%.3e, %.3e, %.3e) m/s\n", - body->velocity.x, body->velocity.y, body->velocity.z); - } - } - - printf("\n=== Running Simulation ===\n"); - while (sim->time < total_time) { - update_simulation(sim); - - for (int i = 0; i < sim->body_count; i++) { - int current_parent = sim->bodies[i].parent_index; - if (current_parent != previous_parents[i]) { - printf("\n*** PARENT CHANGE at Day %.1f ***\n", sim->time / SECONDS_PER_DAY); - printf(" Body: %s\n", sim->bodies[i].name); - printf(" Old parent: %d", previous_parents[i]); - if (previous_parents[i] >= 0) printf(" (%s)", sim->bodies[previous_parents[i]].name); - printf("\n"); - printf(" New parent: %d", current_parent); - if (current_parent >= 0) printf(" (%s)", sim->bodies[current_parent].name); - printf("\n\n"); - previous_parents[i] = current_parent; - } - } - - if (sim->time >= next_output_time) { - printf("Day %.1f: ", sim->time / SECONDS_PER_DAY); - for (int i = 0; i < sim->body_count && i < 3; i++) { - Vec3 pos = sim->bodies[i].position; - double dist = vec3_magnitude(pos); - if (args->readable) { - double angle_rad = atan2(pos.y, pos.x); - double angle_deg = angle_rad * 180.0 / M_PI; - if (angle_deg < 0.0) angle_deg += 360.0; - printf("%s(r=%.4f AU, θ=%.1f°) ", sim->bodies[i].name, dist / AU, angle_deg); - } else { - printf("%s=%.3e m ", sim->bodies[i].name, dist); - } - } - printf("\n"); - next_output_time += output_interval; - } - } - - printf("\n=== Initial State (Day 0.0) ===\n"); - for (int i = 0; i < sim->body_count; i++) { - CelestialBody* body = &initial_state[i]; - if (args->readable) { - print_body_readable(body); - } else { - printf("%s:\n", body->name); - printf(" Position: (%.3e, %.3e, %.3e) m\n", - body->position.x, body->position.y, body->position.z); - printf(" Velocity: (%.3e, %.3e, %.3e) m/s\n", - body->velocity.x, body->velocity.y, body->velocity.z); - } - } - - printf("\n=== Final State (Day %.1f) ===\n", sim->time / SECONDS_PER_DAY); - for (int i = 0; i < sim->body_count; i++) { - CelestialBody* body = &sim->bodies[i]; - if (args->readable) { - print_body_readable(body); - } else { - printf("%s:\n", body->name); - printf(" Position: (%.3e, %.3e, %.3e) m\n", - body->position.x, body->position.y, body->position.z); - printf(" Velocity: (%.3e, %.3e, %.3e) m/s\n", - body->velocity.x, body->velocity.y, body->velocity.z); - } - } - - free(initial_state); - free(previous_parents); } void run_gui_simulation(SimulationState* sim) { @@ -249,11 +97,7 @@ int main(int argc, char** argv) { return 1; } - if (args.headless) { - run_headless_simulation(sim, &args); - } else { - run_gui_simulation(sim); - } + run_gui_simulation(sim); destroy_simulation(sim); return 0;