From c53c598ffe9a7c3feb743475507f73d6f6b5b138 Mon Sep 17 00:00:00 2001 From: cinnaboot Date: Thu, 8 Jan 2026 18:50:45 -0500 Subject: [PATCH] Add moon orbit test suite and fix solar system config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created tests/test_moon_orbits.cpp with 4 test cases: * Moon orbital stability around Earth * Galilean moons orbital stability around Jupiter * Titan orbital stability around Saturn * Combined solar system parent stability - Enhanced test_utilities.h/cpp: * Added min_time_days field to OrbitTracker * Added create_orbit_tracker_with_min_time() for short-period orbits - Fixed parent indexes in tests/configs/solar_system.toml: * Moon: parent 3 (Mars) → 2 (Earth) * Io/Europa/Ganymede/Callisto: parent 5 (Saturn) → 4 (Jupiter) * Titan: parent 6 (Uranus) → 5 (Saturn) - Updated semi_major_axis for moons to actual orbital distances Tests reveal need for hierarchical orbit physics implementation. Net change: +368 lines --- Makefile | 4 +- src/test_utilities.cpp | 17 +- src/test_utilities.h | 2 + tests/configs/solar_system.toml | 141 +++++++++++++++++ tests/test_moon_orbits.cpp | 264 ++++++++++++++++++++++++++++++++ 5 files changed, 425 insertions(+), 3 deletions(-) create mode 100644 tests/configs/solar_system.toml create mode 100644 tests/test_moon_orbits.cpp diff --git a/Makefile b/Makefile index 9f10a77..e622fd6 100644 --- a/Makefile +++ b/Makefile @@ -81,6 +81,8 @@ test-build: -c $(TEST_DIR)/test_orbital_period.cpp -o build/test_orbital_period.o $(CXX) $(CXXFLAGS) -I/usr/include/catch2 \ -c $(TEST_DIR)/test_comet_orbit.cpp -o build/test_comet_orbit.o + $(CXX) $(CXXFLAGS) -I/usr/include/catch2 \ + -c $(TEST_DIR)/test_moon_orbits.cpp -o build/test_moon_orbits.o $(CXX) $(CXXFLAGS) \ -c $(SRC_DIR)/test_utilities.cpp -o build/test_utilities.o $(CXX) $(CXXFLAGS) \ @@ -91,7 +93,7 @@ test-build: -c $(SRC_DIR)/config_loader.cpp -o build/test_config_loader.o $(CC) -Wall -Wextra -g -ggdb3 -std=c99 -I./src -I./ext/tomlc17/src \ -c $(SRC_DIR)/../ext/tomlc17/src/tomlc17.c -o build/test_tomlc17.o - $(CXX) build/test_main.o build/test_integration.o build/test_energy.o build/test_orbital_period.o build/test_comet_orbit.o build/test_utilities.o build/test_physics.o build/test_simulation.o build/test_config_loader.o build/test_tomlc17.o \ + $(CXX) build/test_main.o build/test_integration.o build/test_energy.o build/test_orbital_period.o build/test_comet_orbit.o build/test_moon_orbits.o build/test_utilities.o build/test_physics.o build/test_simulation.o build/test_config_loader.o build/test_tomlc17.o \ -o $(TEST_TARGET) -lCatch2Main -lCatch2 -lm # Run automated test suite diff --git a/src/test_utilities.cpp b/src/test_utilities.cpp index 7782b37..e53738e 100644 --- a/src/test_utilities.cpp +++ b/src/test_utilities.cpp @@ -53,6 +53,19 @@ OrbitTracker* create_orbit_tracker(int body_index) { tracker->quadrant_transitions = 0; tracker->orbit_completed = false; tracker->time_at_completion = 0.0; + tracker->min_time_days = 100.0; + return tracker; +} + +OrbitTracker* create_orbit_tracker_with_min_time(int body_index, double min_time_days) { + OrbitTracker* tracker = (OrbitTracker*)malloc(sizeof(OrbitTracker)); + tracker->body_index = body_index; + tracker->initial_angle = 0.0; + tracker->previous_angle = 0.0; + tracker->quadrant_transitions = 0; + tracker->orbit_completed = false; + tracker->time_at_completion = 0.0; + tracker->min_time_days = min_time_days; return tracker; } @@ -93,10 +106,10 @@ void update_orbit_tracker(OrbitTracker* tracker, CelestialBody* body, CelestialB if (total_rotation > M_PI) total_rotation -= 2.0 * M_PI; const double SECONDS_PER_DAY = 86400.0; - const double MIN_ORBIT_TIME = 100.0 * SECONDS_PER_DAY; + double min_time_seconds = tracker->min_time_days * SECONDS_PER_DAY; if (tracker->quadrant_transitions >= 2 && - current_time > MIN_ORBIT_TIME && + current_time > min_time_seconds && fabs(total_rotation) < 0.05) { tracker->orbit_completed = true; tracker->time_at_completion = current_time; diff --git a/src/test_utilities.h b/src/test_utilities.h index cd532d5..ab4c36b 100644 --- a/src/test_utilities.h +++ b/src/test_utilities.h @@ -20,6 +20,7 @@ struct OrbitTracker { bool orbit_completed; double time_at_completion; int body_index; + double min_time_days; }; double calculate_kinetic_energy(CelestialBody* body); @@ -28,6 +29,7 @@ double calculate_system_total_energy(SimulationState* sim); OrbitalMetrics calculate_orbital_metrics(CelestialBody* body, CelestialBody* parent); OrbitTracker* create_orbit_tracker(int body_index); +OrbitTracker* create_orbit_tracker_with_min_time(int body_index, double min_time_days); void reset_orbit_tracker(OrbitTracker* tracker); void update_orbit_tracker(OrbitTracker* tracker, CelestialBody* body, CelestialBody* parent, double current_time); void destroy_orbit_tracker(OrbitTracker* tracker); diff --git a/tests/configs/solar_system.toml b/tests/configs/solar_system.toml new file mode 100644 index 0000000..7916a8f --- /dev/null +++ b/tests/configs/solar_system.toml @@ -0,0 +1,141 @@ +# Solar System Configuration + +[[bodies]] +name = "Sun" +mass = 1.989e30 # kg +radius = 6.96e8 # m +position = { x = 0.0, y = 0.0, z = 0.0 } +parent_index = -1 +color = { r = 1.0, g = 1.0, b = 0.0 } +eccentricity = 0.0 +semi_major_axis = 0.0 + +[[bodies]] +name = "Venus" +mass = 4.867e24 +radius = 6.0518e6 +position = { x = 1.082e11, y = 0.0, z = 0.0 } +parent_index = 0 +color = { r = 0.9, g = 0.7, b = 0.3 } +eccentricity = 0.0 +semi_major_axis = 1.082e11 + +[[bodies]] +name = "Earth" +mass = 5.972e24 +radius = 6.371e6 +position = { x = 1.496e11, y = 0.0, z = 0.0 } +parent_index = 0 +color = { r = 0.0, g = 0.5, b = 1.0 } +eccentricity = 0.0 +semi_major_axis = 1.496e11 + +[[bodies]] +name = "Mars" +mass = 6.39e23 +radius = 3.3895e6 +position = { x = 2.279e11, y = 0.0, z = 0.0 } +parent_index = 0 +color = { r = 0.8, g = 0.3, b = 0.1 } +eccentricity = 0.0 +semi_major_axis = 2.279e11 + +[[bodies]] +name = "Jupiter" +mass = 1.898e27 +radius = 6.9911e7 +position = { x = 7.785e11, y = 0.0, z = 0.0 } +parent_index = 0 +color = { r = 0.9, g = 0.7, b = 0.5 } +eccentricity = 0.0 +semi_major_axis = 7.785e11 + +[[bodies]] +name = "Saturn" +mass = 5.683e26 +radius = 5.8232e7 +position = { x = 1.434e12, y = 0.0, z = 0.0 } +parent_index = 0 +color = { r = 0.9, g = 0.8, b = 0.6 } +eccentricity = 0.0 +semi_major_axis = 1.434e12 + +[[bodies]] +name = "Uranus" +mass = 8.681e25 +radius = 2.5362e7 +position = { x = 2.871e12, y = 0.0, z = 0.0 } +parent_index = 0 +color = { r = 0.5, g = 0.8, b = 0.9 } +eccentricity = 0.0 +semi_major_axis = 2.871e12 + +[[bodies]] +name = "Neptune" +mass = 1.024e26 +radius = 2.4622e7 +position = { x = 4.495e12, y = 0.0, z = 0.0 } +parent_index = 0 +color = { r = 0.2, g = 0.4, b = 0.9 } +eccentricity = 0.0 +semi_major_axis = 4.495e12 + + [[bodies]] + name = "Moon" + mass = 7.342e22 + radius = 1.737e6 + position = { x = 1.500e11, y = 0.0, z = 0.0 } + parent_index = 2 + color = { r = 0.7, g = 0.7, b = 0.7 } + eccentricity = 0.0 + semi_major_axis = 3.844e8 + + [[bodies]] + name = "Io" + mass = 8.93e22 + radius = 1.822e6 + position = { x = 8.207e11, y = 0.0, z = 0.0 } + parent_index = 4 + color = { r = 0.9, g = 0.9, b = 0.3 } + eccentricity = 0.0 + semi_major_axis = 4.217e8 + + [[bodies]] + name = "Europa" + mass = 4.80e22 + radius = 1.561e6 + position = { x = 8.456e11, y = 0.0, z = 0.0 } + parent_index = 4 + color = { r = 0.8, g = 0.8, b = 0.7 } + eccentricity = 0.0 + semi_major_axis = 6.709e8 + + [[bodies]] + name = "Ganymede" + mass = 1.48e23 + radius = 2.634e6 + position = { x = 8.853e11, y = 0.0, z = 0.0 } + parent_index = 4 + color = { r = 0.6, g = 0.6, b = 0.5 } + eccentricity = 0.0 + semi_major_axis = 1.070e9 + + [[bodies]] + name = "Callisto" + mass = 1.08e23 + radius = 2.410e6 + position = { x = 9.670e11, y = 0.0, z = 0.0 } + parent_index = 4 + color = { r = 0.5, g = 0.5, b = 0.4 } + eccentricity = 0.0 + semi_major_axis = 1.883e9 + + [[bodies]] + name = "Titan" + mass = 1.345e23 + radius = 2.575e6 + position = { x = 1.556e12, y = 0.0, z = 0.0 } + parent_index = 5 + color = { r = 0.9, g = 0.6, b = 0.3 } + eccentricity = 0.0 + semi_major_axis = 1.222e9 diff --git a/tests/test_moon_orbits.cpp b/tests/test_moon_orbits.cpp new file mode 100644 index 0000000..befca5b --- /dev/null +++ b/tests/test_moon_orbits.cpp @@ -0,0 +1,264 @@ +#include +#include "../src/physics.h" +#include "../src/simulation.h" +#include "../src/config_loader.h" +#include "../src/test_utilities.h" +#include +#include + +TEST_CASE("Moon orbital stability around Earth", "[moon][earth]") { + const double TIME_STEP = 60.0; + const double EXPECTED_PERIOD_DAYS = 27.3; + const double SECONDS_PER_DAY = 86400.0; + const double MAX_SIMULATION_DAYS = 35.0; + const double MOON_DISTANCE_FROM_EARTH = 384400000.0; + + SimulationState* sim = create_simulation(20, TIME_STEP); + + REQUIRE(load_system_config(sim, "tests/configs/solar_system.toml")); + + const int EARTH_INDEX = 2; + const int MOON_INDEX = 8; + + double max_time = MAX_SIMULATION_DAYS * SECONDS_PER_DAY; + OrbitTracker* tracker = create_orbit_tracker_with_min_time(MOON_INDEX, 5.0); + + int initial_parent = sim->bodies[MOON_INDEX].parent_index; + Vec3 initial_pos_relative_to_earth = vec3_sub( + sim->bodies[MOON_INDEX].position, + sim->bodies[EARTH_INDEX].position + ); + double initial_distance = vec3_magnitude(initial_pos_relative_to_earth); + + INFO("Moon initial distance from Earth: " << initial_distance << " m"); + INFO("Expected distance: " << MOON_DISTANCE_FROM_EARTH << " m"); + + while (sim->time < max_time && !tracker->orbit_completed) { + update_simulation(sim); + + int current_parent = sim->bodies[MOON_INDEX].parent_index; + + REQUIRE(current_parent == initial_parent); + + if (current_parent != EARTH_INDEX) { + INFO("Moon parent changed from " << initial_parent << " to " << current_parent); + REQUIRE(current_parent == EARTH_INDEX); + } + + Vec3 current_pos_relative_to_earth = vec3_sub( + sim->bodies[MOON_INDEX].position, + sim->bodies[EARTH_INDEX].position + ); + double current_distance = vec3_magnitude(current_pos_relative_to_earth); + + double distance_drift = fabs(current_distance - initial_distance); + double drift_percentage = (distance_drift / initial_distance) * 100.0; + + REQUIRE(drift_percentage < 20.0); + + update_orbit_tracker(tracker, &sim->bodies[MOON_INDEX], &sim->bodies[EARTH_INDEX], sim->time); + } + + REQUIRE(tracker->orbit_completed); + + double measured_period_days = tracker->time_at_completion / SECONDS_PER_DAY; + double period_error_days = fabs(measured_period_days - EXPECTED_PERIOD_DAYS); + + INFO("Expected Moon period: " << EXPECTED_PERIOD_DAYS << " days"); + INFO("Measured Moon period: " << measured_period_days << " days"); + INFO("Period error: " << period_error_days << " days"); + + REQUIRE(period_error_days < 3.0); + + Vec3 final_pos_relative_to_earth = vec3_sub( + sim->bodies[MOON_INDEX].position, + sim->bodies[EARTH_INDEX].position + ); + double final_distance = vec3_magnitude(final_pos_relative_to_earth); + + double final_drift_percentage = (fabs(final_distance - initial_distance) / initial_distance) * 100.0; + + INFO("Final distance from Earth: " << final_distance << " m"); + INFO("Initial distance from Earth: " << initial_distance << " m"); + INFO("Final drift: " << final_drift_percentage << "%"); + + REQUIRE(final_drift_percentage < 10.0); + + destroy_orbit_tracker(tracker); + destroy_simulation(sim); +} + +TEST_CASE("Galilean moons orbital stability around Jupiter", "[moon][jupiter]") { + const double TIME_STEP = 60.0; + const double SECONDS_PER_DAY = 86400.0; + const double MAX_SIMULATION_DAYS = 20.0; + + const double IO_PERIOD_DAYS = 1.77; + const double EUROPA_PERIOD_DAYS = 3.55; + const double GANYMEDE_PERIOD_DAYS = 7.15; + const double CALLISTO_PERIOD_DAYS = 16.69; + + SimulationState* sim = create_simulation(20, TIME_STEP); + + REQUIRE(load_system_config(sim, "tests/configs/solar_system.toml")); + + const int JUPITER_INDEX = 4; + const int IO_INDEX = 9; + const int EUROPA_INDEX = 10; + const int GANYMEDE_INDEX = 11; + const int CALLISTO_INDEX = 12; + + OrbitTracker* io_tracker = create_orbit_tracker_with_min_time(IO_INDEX, 1.0); + OrbitTracker* europa_tracker = create_orbit_tracker_with_min_time(EUROPA_INDEX, 2.0); + OrbitTracker* ganymede_tracker = create_orbit_tracker_with_min_time(GANYMEDE_INDEX, 5.0); + OrbitTracker* callisto_tracker = create_orbit_tracker_with_min_time(CALLISTO_INDEX, 10.0); + + double max_time = MAX_SIMULATION_DAYS * SECONDS_PER_DAY; + + while (sim->time < max_time) { + update_simulation(sim); + + REQUIRE(sim->bodies[IO_INDEX].parent_index == JUPITER_INDEX); + REQUIRE(sim->bodies[EUROPA_INDEX].parent_index == JUPITER_INDEX); + REQUIRE(sim->bodies[GANYMEDE_INDEX].parent_index == JUPITER_INDEX); + REQUIRE(sim->bodies[CALLISTO_INDEX].parent_index == JUPITER_INDEX); + + update_orbit_tracker(io_tracker, &sim->bodies[IO_INDEX], &sim->bodies[JUPITER_INDEX], sim->time); + update_orbit_tracker(europa_tracker, &sim->bodies[EUROPA_INDEX], &sim->bodies[JUPITER_INDEX], sim->time); + update_orbit_tracker(ganymede_tracker, &sim->bodies[GANYMEDE_INDEX], &sim->bodies[JUPITER_INDEX], sim->time); + update_orbit_tracker(callisto_tracker, &sim->bodies[CALLISTO_INDEX], &sim->bodies[JUPITER_INDEX], sim->time); + } + + REQUIRE(io_tracker->orbit_completed); + REQUIRE(europa_tracker->orbit_completed); + REQUIRE(ganymede_tracker->orbit_completed); + REQUIRE(callisto_tracker->orbit_completed); + + double io_period_days = io_tracker->time_at_completion / SECONDS_PER_DAY; + double europa_period_days = europa_tracker->time_at_completion / SECONDS_PER_DAY; + double ganymede_period_days = ganymede_tracker->time_at_completion / SECONDS_PER_DAY; + double callisto_period_days = callisto_tracker->time_at_completion / SECONDS_PER_DAY; + + INFO("Io period: " << io_period_days << " days (expected: " << IO_PERIOD_DAYS << ")"); + INFO("Europa period: " << europa_period_days << " days (expected: " << EUROPA_PERIOD_DAYS << ")"); + INFO("Ganymede period: " << ganymede_period_days << " days (expected: " << GANYMEDE_PERIOD_DAYS << ")"); + INFO("Callisto period: " << callisto_period_days << " days (expected: " << CALLISTO_PERIOD_DAYS << ")"); + + REQUIRE(fabs(io_period_days - IO_PERIOD_DAYS) < 0.5); + REQUIRE(fabs(europa_period_days - EUROPA_PERIOD_DAYS) < 1.0); + REQUIRE(fabs(ganymede_period_days - GANYMEDE_PERIOD_DAYS) < 2.0); + REQUIRE(fabs(callisto_period_days - CALLISTO_PERIOD_DAYS) < 4.0); + + destroy_orbit_tracker(io_tracker); + destroy_orbit_tracker(europa_tracker); + destroy_orbit_tracker(ganymede_tracker); + destroy_orbit_tracker(callisto_tracker); + destroy_simulation(sim); +} + +TEST_CASE("Titan orbital stability around Saturn", "[moon][saturn]") { + const double TIME_STEP = 60.0; + const double EXPECTED_PERIOD_DAYS = 15.95; + const double SECONDS_PER_DAY = 86400.0; + const double MAX_SIMULATION_DAYS = 25.0; + + SimulationState* sim = create_simulation(20, TIME_STEP); + + REQUIRE(load_system_config(sim, "tests/configs/solar_system.toml")); + + const int SATURN_INDEX = 5; + const int TITAN_INDEX = 13; + + OrbitTracker* tracker = create_orbit_tracker_with_min_time(TITAN_INDEX, 10.0); + + Vec3 initial_pos_relative_to_saturn = vec3_sub( + sim->bodies[TITAN_INDEX].position, + sim->bodies[SATURN_INDEX].position + ); + double initial_distance = vec3_magnitude(initial_pos_relative_to_saturn); + + double max_time = MAX_SIMULATION_DAYS * SECONDS_PER_DAY; + + while (sim->time < max_time && !tracker->orbit_completed) { + update_simulation(sim); + + REQUIRE(sim->bodies[TITAN_INDEX].parent_index == SATURN_INDEX); + + Vec3 current_pos_relative_to_saturn = vec3_sub( + sim->bodies[TITAN_INDEX].position, + sim->bodies[SATURN_INDEX].position + ); + double current_distance = vec3_magnitude(current_pos_relative_to_saturn); + + double drift_percentage = (fabs(current_distance - initial_distance) / initial_distance) * 100.0; + REQUIRE(drift_percentage < 10.0); + + update_orbit_tracker(tracker, &sim->bodies[TITAN_INDEX], &sim->bodies[SATURN_INDEX], sim->time); + } + + REQUIRE(tracker->orbit_completed); + + double measured_period_days = tracker->time_at_completion / SECONDS_PER_DAY; + double period_error_days = fabs(measured_period_days - EXPECTED_PERIOD_DAYS); + + INFO("Expected Titan period: " << EXPECTED_PERIOD_DAYS << " days"); + INFO("Measured Titan period: " << measured_period_days << " days"); + INFO("Period error: " << period_error_days << " days"); + + REQUIRE(period_error_days < 3.0); + + destroy_orbit_tracker(tracker); + destroy_simulation(sim); +} + +TEST_CASE("Combined solar system with all moons - parent stability", "[moon][integration]") { + const double TIME_STEP = 60.0; + const double SECONDS_PER_DAY = 86400.0; + const double MAX_SIMULATION_DAYS = 60.0; + + SimulationState* sim = create_simulation(20, TIME_STEP); + + REQUIRE(load_system_config(sim, "tests/configs/solar_system.toml")); + + struct ParentChange { + double time_days; + int body_index; + int old_parent; + int new_parent; + }; + + std::vector parent_changes; + + int initial_parents[14]; + for (int i = 0; i < 14; i++) { + initial_parents[i] = sim->bodies[i].parent_index; + } + + double max_time = MAX_SIMULATION_DAYS * SECONDS_PER_DAY; + + while (sim->time < max_time) { + update_simulation(sim); + + for (int i = 0; i < sim->body_count; i++) { + if (sim->bodies[i].parent_index != initial_parents[i]) { + ParentChange change; + change.time_days = sim->time / SECONDS_PER_DAY; + change.body_index = i; + change.old_parent = initial_parents[i]; + change.new_parent = sim->bodies[i].parent_index; + parent_changes.push_back(change); + initial_parents[i] = sim->bodies[i].parent_index; + } + } + } + + INFO("Total parent changes detected: " << parent_changes.size()); + for (const auto& change : parent_changes) { + INFO("Body " << sim->bodies[change.body_index].name + << " (index " << change.body_index << ") changed parent " + << "from " << change.old_parent << " to " << change.new_parent + << " at day " << change.time_days); + } + + destroy_simulation(sim); +}