Browse Source
Documents a bug where true anomaly triggers set to 0 (periapsis) don't execute because the spacecraft moves past periapsis before the trigger check happens. The trigger check occurs after physics propagation in the simulation update order. - test_periapsis_burn.cpp: Three test cases demonstrating expected behavior 1. Verify periapsis distance is preserved after prograde burn (fails - shows bug) 2. Verify apoapsis raises and periapsis stays same (skipped - depends on fix) 3. Verify burn location equals new periapsis (skipped - depends on fix) - test_periapsis_burn.toml: Test configuration with elliptical orbit satellite starting at periapsis with a true anomaly trigger at 0main
2 changed files with 170 additions and 0 deletions
@ -0,0 +1,123 @@ |
|||||||
|
#include <catch2/catch_test_macros.hpp> |
||||||
|
#include <catch2/matchers/catch_matchers_floating_point.hpp> |
||||||
|
#include "../src/physics.h" |
||||||
|
#include "../src/simulation.h" |
||||||
|
#include "../src/spacecraft.h" |
||||||
|
#include "../src/maneuver.h" |
||||||
|
#include "../src/config_loader.h" |
||||||
|
#include "../src/orbital_mechanics.h" |
||||||
|
#include <cmath> |
||||||
|
|
||||||
|
// This test documents a bug: when a true anomaly trigger is set to 0 (periapsis),
|
||||||
|
// the maneuver doesn't execute because the spacecraft moves past periapsis before
|
||||||
|
// the trigger check happens (trigger check occurs after physics update).
|
||||||
|
// The expected behavior is that the maneuver should execute when at periapsis.
|
||||||
|
TEST_CASE("Prograde burn at periapsis preserves periapsis distance", "[maneuver][periapsis][bug]") { |
||||||
|
const double TIME_STEP = 60.0; |
||||||
|
SimulationState* sim = create_simulation(10, 10, 100, TIME_STEP); |
||||||
|
|
||||||
|
REQUIRE(load_system_config(sim, "tests/test_periapsis_burn.toml")); |
||||||
|
|
||||||
|
Spacecraft* craft = &sim->spacecraft[0]; |
||||||
|
|
||||||
|
// Record initial state (at periapsis)
|
||||||
|
double initial_radius = vec3_magnitude(craft->local_position); |
||||||
|
double initial_sma = craft->orbit.semi_major_axis; |
||||||
|
double initial_ecc = craft->orbit.eccentricity; |
||||||
|
double initial_periapsis = initial_sma * (1.0 - initial_ecc); |
||||||
|
double initial_velocity = vec3_magnitude(craft->local_velocity); |
||||||
|
|
||||||
|
INFO("Initial state:"); |
||||||
|
INFO(" Radius: " << initial_radius << " (should equal periapsis: " << initial_periapsis << ")"); |
||||||
|
INFO(" True anomaly: " << craft->orbit.true_anomaly); |
||||||
|
INFO(" Velocity: " << initial_velocity); |
||||||
|
INFO(" Maneuver trigger: true_anomaly = " << sim->maneuvers[0].trigger_value); |
||||||
|
|
||||||
|
// Execute one step
|
||||||
|
update_simulation(sim); |
||||||
|
|
||||||
|
// Check if maneuver executed
|
||||||
|
INFO("After 1 step:"); |
||||||
|
INFO(" Maneuver executed: " << sim->maneuvers[0].executed); |
||||||
|
INFO(" Final radius: " << vec3_magnitude(craft->local_position)); |
||||||
|
INFO(" Final velocity: " << vec3_magnitude(craft->local_velocity)); |
||||||
|
|
||||||
|
// The maneuver should have executed since we started at periapsis
|
||||||
|
// This assertion will fail due to the bug - the trigger check happens
|
||||||
|
// after physics moves the spacecraft past periapsis
|
||||||
|
REQUIRE(sim->maneuvers[0].executed); |
||||||
|
|
||||||
|
// If the maneuver executed, verify the physics:
|
||||||
|
double final_sma = craft->orbit.semi_major_axis; |
||||||
|
double final_ecc = craft->orbit.eccentricity; |
||||||
|
double final_periapsis = final_sma * (1.0 - final_ecc); |
||||||
|
double final_velocity = vec3_magnitude(craft->local_velocity); |
||||||
|
|
||||||
|
// Periapsis distance should be preserved
|
||||||
|
REQUIRE_THAT(final_periapsis, Catch::Matchers::WithinAbs(initial_periapsis, 1.0)); |
||||||
|
|
||||||
|
// Semi-major axis and velocity should increase
|
||||||
|
REQUIRE(final_sma > initial_sma); |
||||||
|
REQUIRE(final_velocity > initial_velocity); |
||||||
|
|
||||||
|
destroy_simulation(sim); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE("Prograde burn at periapsis raises apoapsis not periapsis", "[maneuver][periapsis][apoapsis]") { |
||||||
|
const double TIME_STEP = 60.0; |
||||||
|
SimulationState* sim = create_simulation(10, 10, 100, TIME_STEP); |
||||||
|
|
||||||
|
REQUIRE(load_system_config(sim, "tests/test_periapsis_burn.toml")); |
||||||
|
|
||||||
|
Spacecraft* craft = &sim->spacecraft[0]; |
||||||
|
|
||||||
|
double initial_periapsis = craft->orbit.semi_major_axis * (1.0 - craft->orbit.eccentricity); |
||||||
|
double initial_apoapsis = craft->orbit.semi_major_axis * (1.0 + craft->orbit.eccentricity); |
||||||
|
|
||||||
|
update_simulation(sim); |
||||||
|
|
||||||
|
// Skip verification if bug not fixed
|
||||||
|
if (!sim->maneuvers[0].executed) { |
||||||
|
SKIP("Maneuver didn't execute - known bug"); |
||||||
|
} |
||||||
|
|
||||||
|
double final_sma = craft->orbit.semi_major_axis; |
||||||
|
double final_ecc = craft->orbit.eccentricity; |
||||||
|
double final_periapsis = final_sma * (1.0 - final_ecc); |
||||||
|
double final_apoapsis = final_sma * (1.0 + final_ecc); |
||||||
|
|
||||||
|
INFO("Initial: peri=" << initial_periapsis << " apo=" << initial_apoapsis); |
||||||
|
INFO("Final: peri=" << final_periapsis << " apo=" << final_apoapsis); |
||||||
|
|
||||||
|
REQUIRE(final_apoapsis > initial_apoapsis); |
||||||
|
REQUIRE_THAT(final_periapsis, Catch::Matchers::WithinAbs(initial_periapsis, 1.0)); |
||||||
|
|
||||||
|
destroy_simulation(sim); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE("Burn location equals new periapsis after prograde burn", "[maneuver][periapsis][location]") { |
||||||
|
const double TIME_STEP = 60.0; |
||||||
|
SimulationState* sim = create_simulation(10, 10, 100, TIME_STEP); |
||||||
|
|
||||||
|
REQUIRE(load_system_config(sim, "tests/test_periapsis_burn.toml")); |
||||||
|
|
||||||
|
Spacecraft* craft = &sim->spacecraft[0]; |
||||||
|
|
||||||
|
double burn_radius = vec3_magnitude(craft->local_position); |
||||||
|
|
||||||
|
update_simulation(sim); |
||||||
|
|
||||||
|
// Skip verification if bug not fixed
|
||||||
|
if (!sim->maneuvers[0].executed) { |
||||||
|
SKIP("Maneuver didn't execute - known bug"); |
||||||
|
} |
||||||
|
|
||||||
|
double final_periapsis = craft->orbit.semi_major_axis * (1.0 - craft->orbit.eccentricity); |
||||||
|
|
||||||
|
INFO("Burn radius: " << burn_radius); |
||||||
|
INFO("Final periapsis: " << final_periapsis); |
||||||
|
|
||||||
|
REQUIRE_THAT(burn_radius, Catch::Matchers::WithinAbs(final_periapsis, 1.0)); |
||||||
|
|
||||||
|
destroy_simulation(sim); |
||||||
|
} |
||||||
@ -0,0 +1,47 @@ |
|||||||
|
# Test Configuration: Prograde Burn at Periapsis |
||||||
|
# Tests that periapsis distance is preserved during prograde burn |
||||||
|
|
||||||
|
[[bodies]] |
||||||
|
name = "Sun" |
||||||
|
mass = 1.989e30 |
||||||
|
radius = 6.96e8 |
||||||
|
parent_index = -1 |
||||||
|
color = { r = 1.0, g = 1.0, b = 0.0 } |
||||||
|
orbit = { |
||||||
|
semi_major_axis = 0.0, |
||||||
|
eccentricity = 0.0, |
||||||
|
true_anomaly = 0.0 |
||||||
|
} |
||||||
|
|
||||||
|
[[bodies]] |
||||||
|
name = "Earth" |
||||||
|
mass = 5.972e24 |
||||||
|
radius = 6.371e6 |
||||||
|
parent_index = 0 |
||||||
|
color = { r = 0.0, g = 0.5, b = 1.0 } |
||||||
|
orbit = { |
||||||
|
semi_major_axis = 1.496e11, |
||||||
|
eccentricity = 0.0, |
||||||
|
true_anomaly = 0.0 |
||||||
|
} |
||||||
|
|
||||||
|
[[spacecraft]] |
||||||
|
name = "TestSatellite" |
||||||
|
mass = 1000.0 |
||||||
|
parent_index = 1 |
||||||
|
# Start at periapsis of an elliptical orbit |
||||||
|
# a = 10,000 km, e = 0.3 |
||||||
|
# periapsis = 7,000 km, apoapsis = 13,000 km |
||||||
|
orbit = { |
||||||
|
semi_major_axis = 1.0371e7, |
||||||
|
eccentricity = 0.3, |
||||||
|
true_anomaly = 0.0 |
||||||
|
} |
||||||
|
|
||||||
|
[[maneuvers]] |
||||||
|
name = "periapsis_prograde_burn" |
||||||
|
spacecraft_name = "TestSatellite" |
||||||
|
trigger_type = "true_anomaly" |
||||||
|
trigger_value = 0.0 |
||||||
|
direction = "prograde" |
||||||
|
delta_v = 500.0 |
||||||
Loading…
Reference in new issue