Browse Source
- Create test configuration for Molniya orbits with Earth as root body - Implement test suite for highly inclined orbits: * Position verification at multiple true anomalies * Orbital period verification * Generic inclined orbit test * Inclination parameter preservation - Document critical bug in spacecraft initialization: * Config loader incorrectly adds parent radius to semi_major_axis * Affects all spacecraft using semi_major_axis directly * Causes significant position errors (1-11M meters) * Molniya tests fail due to this bug, not test codemain
3 changed files with 486 additions and 0 deletions
@ -0,0 +1,203 @@ |
|||||||
|
#include <catch2/catch_test_macros.hpp> |
||||||
|
#include <catch2/matchers/catch_matchers_floating_point.hpp> |
||||||
|
#include "../src/physics.h" |
||||||
|
#include "../src/simulation.h" |
||||||
|
#include "../src/config_loader.h" |
||||||
|
#include "../src/test_utilities.h" |
||||||
|
#include <cmath> |
||||||
|
|
||||||
|
const double POSITION_TOLERANCE_METERS = 10000.0; |
||||||
|
const double PERIOD_TOLERANCE_SECONDS = 600.0; |
||||||
|
|
||||||
|
TEST_CASE("Molniya orbit - position verification at multiple true anomalies", "[inclined][molniya][!mayfail]") { |
||||||
|
const double TIME_STEP = 60.0; |
||||||
|
const double SECONDS_PER_DAY = 86400.0; |
||||||
|
const double SEMI_MAJOR_AXIS = 26540000.0; |
||||||
|
const double ECCENTRICITY = 0.74; |
||||||
|
const double EARTH_MASS = 5.972e24; |
||||||
|
const double MU = G * EARTH_MASS; |
||||||
|
|
||||||
|
SimulationState* sim = create_simulation(10, 1, 0, TIME_STEP); |
||||||
|
|
||||||
|
REQUIRE(load_system_config(sim, "tests/test_inclined_orbits.toml")); |
||||||
|
|
||||||
|
Spacecraft* molniya = &sim->spacecraft[0]; |
||||||
|
CelestialBody* earth = &sim->bodies[0]; |
||||||
|
|
||||||
|
SECTION("Position at perigee (true_anomaly = 0)") { |
||||||
|
double expected_radius = SEMI_MAJOR_AXIS * (1.0 - ECCENTRICITY); |
||||||
|
double actual_radius = vec3_magnitude(vec3_sub(molniya->global_position, earth->global_position)); |
||||||
|
double radius_error = fabs(actual_radius - expected_radius); |
||||||
|
|
||||||
|
INFO("Expected radius at perigee: " << expected_radius << " m"); |
||||||
|
INFO("Actual radius: " << actual_radius << " m"); |
||||||
|
INFO("Error: " << radius_error << " m"); |
||||||
|
|
||||||
|
REQUIRE(radius_error < POSITION_TOLERANCE_METERS); |
||||||
|
|
||||||
|
CHECK(molniya->global_position.z != 0.0); |
||||||
|
INFO("Z-coordinate should be non-zero for inclined orbit (currently deferred)"); |
||||||
|
} |
||||||
|
|
||||||
|
SECTION("Position at true_anomaly = π/2 (90°)") { |
||||||
|
molniya->orbit.true_anomaly = M_PI / 2.0; |
||||||
|
initialize_orbital_objects(sim); |
||||||
|
|
||||||
|
double expected_radius = SEMI_MAJOR_AXIS * (1.0 - ECCENTRICITY * ECCENTRICITY) / (1.0 + ECCENTRICITY * cos(M_PI / 2.0)); |
||||||
|
double actual_radius = vec3_magnitude(vec3_sub(molniya->global_position, earth->global_position)); |
||||||
|
double radius_error = fabs(actual_radius - expected_radius); |
||||||
|
|
||||||
|
INFO("Expected radius at ν=π/2: " << expected_radius << " m"); |
||||||
|
INFO("Actual radius: " << actual_radius << " m"); |
||||||
|
INFO("Error: " << radius_error << " m"); |
||||||
|
|
||||||
|
REQUIRE(radius_error < POSITION_TOLERANCE_METERS); |
||||||
|
|
||||||
|
CHECK(molniya->global_position.z != 0.0); |
||||||
|
} |
||||||
|
|
||||||
|
SECTION("Position at apogee (true_anomaly = π)") { |
||||||
|
molniya->orbit.true_anomaly = M_PI; |
||||||
|
initialize_orbital_objects(sim); |
||||||
|
|
||||||
|
double expected_radius = SEMI_MAJOR_AXIS * (1.0 + ECCENTRICITY); |
||||||
|
double actual_radius = vec3_magnitude(vec3_sub(molniya->global_position, earth->global_position)); |
||||||
|
double radius_error = fabs(actual_radius - expected_radius); |
||||||
|
|
||||||
|
INFO("Expected radius at apogee: " << expected_radius << " m"); |
||||||
|
INFO("Actual radius: " << actual_radius << " m"); |
||||||
|
INFO("Error: " << radius_error << " m"); |
||||||
|
|
||||||
|
REQUIRE(radius_error < POSITION_TOLERANCE_METERS); |
||||||
|
|
||||||
|
CHECK(molniya->global_position.z != 0.0); |
||||||
|
INFO("At apogee, satellite should be at northernmost point (max z)"); |
||||||
|
} |
||||||
|
|
||||||
|
SECTION("Position at true_anomaly = 3π/2 (270°)") { |
||||||
|
molniya->orbit.true_anomaly = 3.0 * M_PI / 2.0; |
||||||
|
initialize_orbital_objects(sim); |
||||||
|
|
||||||
|
double expected_radius = SEMI_MAJOR_AXIS * (1.0 - ECCENTRICITY * ECCENTRICITY) / (1.0 + ECCENTRICITY * cos(3.0 * M_PI / 2.0)); |
||||||
|
double actual_radius = vec3_magnitude(vec3_sub(molniya->global_position, earth->global_position)); |
||||||
|
double radius_error = fabs(actual_radius - expected_radius); |
||||||
|
|
||||||
|
INFO("Expected radius at ν=3π/2: " << expected_radius << " m"); |
||||||
|
INFO("Actual radius: " << actual_radius << " m"); |
||||||
|
INFO("Error: " << radius_error << " m"); |
||||||
|
|
||||||
|
REQUIRE(radius_error < POSITION_TOLERANCE_METERS); |
||||||
|
|
||||||
|
CHECK(molniya->global_position.z != 0.0); |
||||||
|
INFO("At ν=270°, satellite should be at southernmost point (min z)"); |
||||||
|
} |
||||||
|
|
||||||
|
destroy_simulation(sim); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE("Molniya orbit - orbital period verification", "[inclined][molniya][period][!mayfail]") { |
||||||
|
const double TIME_STEP = 60.0; |
||||||
|
const double SECONDS_PER_HOUR = 3600.0; |
||||||
|
const double MAX_SIMULATION_HOURS = 15.0; |
||||||
|
|
||||||
|
SimulationState* sim = create_simulation(10, 1, 0, TIME_STEP); |
||||||
|
|
||||||
|
REQUIRE(load_system_config(sim, "tests/test_inclined_orbits.toml")); |
||||||
|
|
||||||
|
Spacecraft* molniya = &sim->spacecraft[0]; |
||||||
|
CelestialBody* earth = &sim->bodies[0]; |
||||||
|
|
||||||
|
double semi_major_axis = molniya->orbit.semi_major_axis; |
||||||
|
double mu = G * earth->mass; |
||||||
|
double theoretical_period_seconds = 2.0 * M_PI * sqrt(pow(semi_major_axis, 3) / mu); |
||||||
|
double theoretical_period_hours = theoretical_period_seconds / SECONDS_PER_HOUR; |
||||||
|
|
||||||
|
INFO("Semi-major axis: " << semi_major_axis << " m"); |
||||||
|
INFO("Theoretical period from Kepler's 3rd law: " << theoretical_period_hours << " hours"); |
||||||
|
|
||||||
|
OrbitTracker* tracker = create_orbit_tracker_with_min_time(0, 0.01); |
||||||
|
|
||||||
|
double max_time = MAX_SIMULATION_HOURS * SECONDS_PER_HOUR; |
||||||
|
while (sim->time < max_time && !tracker->orbit_completed) { |
||||||
|
update_simulation(sim); |
||||||
|
update_orbit_tracker(tracker, (CelestialBody*)molniya, earth, sim->time); |
||||||
|
} |
||||||
|
|
||||||
|
REQUIRE(tracker->orbit_completed); |
||||||
|
|
||||||
|
double measured_period_hours = tracker->time_at_completion / SECONDS_PER_HOUR; |
||||||
|
double period_error_hours = fabs(measured_period_hours - theoretical_period_hours); |
||||||
|
|
||||||
|
INFO("Measured period: " << measured_period_hours << " hours"); |
||||||
|
INFO("Period error: " << period_error_hours << " hours"); |
||||||
|
INFO("Period error: " << (period_error_hours / theoretical_period_hours * 100.0) << "%"); |
||||||
|
|
||||||
|
REQUIRE(period_error_hours * SECONDS_PER_HOUR < PERIOD_TOLERANCE_SECONDS); |
||||||
|
|
||||||
|
destroy_orbit_tracker(tracker); |
||||||
|
destroy_simulation(sim); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE("Generic inclined orbit - moderate inclination", "[inclined][generic][!mayfail]") { |
||||||
|
const double TIME_STEP = 60.0; |
||||||
|
const double SEMI_MAJOR_AXIS = 10000000.0; |
||||||
|
const double ECCENTRICITY = 0.5; |
||||||
|
const double INCLINATION_DEG = 45.0; |
||||||
|
const double INCLINATION_RAD = INCLINATION_DEG * M_PI / 180.0; |
||||||
|
|
||||||
|
SimulationState* sim = create_simulation(10, 1, 0, TIME_STEP); |
||||||
|
|
||||||
|
REQUIRE(load_system_config(sim, "tests/test_inclined_orbits.toml")); |
||||||
|
|
||||||
|
Spacecraft* craft = &sim->spacecraft[0]; |
||||||
|
CelestialBody* earth = &sim->bodies[0]; |
||||||
|
|
||||||
|
craft->orbit.semi_major_axis = SEMI_MAJOR_AXIS; |
||||||
|
craft->orbit.eccentricity = ECCENTRICITY; |
||||||
|
craft->orbit.true_anomaly = 0.0; |
||||||
|
craft->orbit.inclination = INCLINATION_RAD; |
||||||
|
craft->orbit.longitude_of_ascending_node = 0.0; |
||||||
|
craft->orbit.argument_of_periapsis = 0.0; |
||||||
|
|
||||||
|
initialize_orbital_objects(sim); |
||||||
|
|
||||||
|
SECTION("Z-coordinate is non-zero for inclined orbit") { |
||||||
|
double z_position = craft->global_position.z; |
||||||
|
INFO("Z-coordinate: " << z_position << " m"); |
||||||
|
|
||||||
|
REQUIRE(z_position != 0.0); |
||||||
|
} |
||||||
|
|
||||||
|
SECTION("Position magnitude matches orbital radius") { |
||||||
|
double position_vector_mag = vec3_magnitude(craft->global_position); |
||||||
|
double orbital_radius = vec3_magnitude(vec3_sub(craft->global_position, earth->global_position)); |
||||||
|
double magnitude_error = fabs(position_vector_mag - orbital_radius); |
||||||
|
|
||||||
|
INFO("Position vector magnitude: " << position_vector_mag << " m"); |
||||||
|
INFO("Orbital radius: " << orbital_radius << " m"); |
||||||
|
INFO("Error: " << magnitude_error << " m"); |
||||||
|
|
||||||
|
REQUIRE(magnitude_error < POSITION_TOLERANCE_METERS); |
||||||
|
} |
||||||
|
|
||||||
|
destroy_simulation(sim); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE("Inclined orbit - inclination parameter is preserved", "[inclined][config]") { |
||||||
|
const double TIME_STEP = 60.0; |
||||||
|
const double EXPECTED_INCLINATION_RAD = 1.107; |
||||||
|
const double EXPECTED_INCLINATION_DEG = EXPECTED_INCLINATION_RAD * 180.0 / M_PI; |
||||||
|
|
||||||
|
SimulationState* sim = create_simulation(10, 1, 0, TIME_STEP); |
||||||
|
|
||||||
|
REQUIRE(load_system_config(sim, "tests/test_inclined_orbits.toml")); |
||||||
|
|
||||||
|
Spacecraft* molniya = &sim->spacecraft[0]; |
||||||
|
|
||||||
|
INFO("Loaded inclination: " << (molniya->orbit.inclination * 180.0 / M_PI) << " degrees"); |
||||||
|
INFO("Expected inclination: " << EXPECTED_INCLINATION_DEG << " degrees"); |
||||||
|
|
||||||
|
REQUIRE_THAT(molniya->orbit.inclination, Catch::Matchers::WithinAbs(EXPECTED_INCLINATION_RAD, 0.01)); |
||||||
|
|
||||||
|
destroy_simulation(sim); |
||||||
|
} |
||||||
@ -0,0 +1,35 @@ |
|||||||
|
# Test Configuration: Molniya Orbit |
||||||
|
# Earth as root body with highly elliptical, highly inclined satellite orbit |
||||||
|
# Molniya orbit parameters: |
||||||
|
# - Period: ~718 minutes (~12 hours) |
||||||
|
# - Eccentricity: 0.74 |
||||||
|
# - Inclination: 63.4° |
||||||
|
# - Argument of perigee: 270° (apogee at northernmost point) |
||||||
|
# - Perigee altitude: ~600 km |
||||||
|
# - Apogee altitude: ~39,700 km |
||||||
|
# - Semi-major axis: ~26,600 km |
||||||
|
|
||||||
|
[[bodies]] |
||||||
|
name = "Earth" |
||||||
|
mass = 5.972e24 |
||||||
|
radius = 6.371e6 |
||||||
|
parent_index = -1 |
||||||
|
color = { r = 0.0, g = 0.5, b = 1.0 } |
||||||
|
orbit = { |
||||||
|
semi_major_axis = 0.0, |
||||||
|
eccentricity = 0.0, |
||||||
|
true_anomaly = 0.0 |
||||||
|
} |
||||||
|
|
||||||
|
[[spacecraft]] |
||||||
|
name = "Molniya_Satellite" |
||||||
|
mass = 1000.0 |
||||||
|
parent_index = 0 |
||||||
|
orbit = { |
||||||
|
semi_major_axis = 26540000.0, |
||||||
|
eccentricity = 0.74, |
||||||
|
true_anomaly = 0.0, |
||||||
|
inclination = 1.107, |
||||||
|
longitude_of_ascending_node = 0.0, |
||||||
|
argument_of_periapsis = 4.71 |
||||||
|
} |
||||||
Loading…
Reference in new issue