Browse Source

Add test cases capturing periapsis burn bug

Added test cases that reveal the bug where periapsis burns execute
at apoapsis instead of periapsis after the first burn.

Changes:
- Modified tests/test_periapsis_burn.toml to include second spacecraft
  (TestSatelliteCrossing) starting at true_anomaly = 1.57
- Replaced test "Prograde burn at periapsis raises apoapsis" with
  "Two periapsis burns execute at same location" to verify sequential
  periapsis burns one orbit apart both fire at correct location
- Added test "Periapsis burn fires when crossing periapsis" to verify burn
  triggers when spacecraft crosses periapsis from 90 degrees

Both new tests fail as expected, confirming the bug exists in
cartesian_to_orbital_elements() where argument_of_periapsis is
calculated as π instead of 0 after a burn.

Test status: 4 periapsis tests fail (capturing bug)
main
cinnaboot 5 months ago
parent
commit
49c748f4fe
  1. 156
      tests/test_periapsis_burn.cpp
  2. 29
      tests/test_periapsis_burn.toml

156
tests/test_periapsis_burn.cpp

@ -61,33 +61,169 @@ TEST_CASE("Prograde burn at periapsis preserves periapsis distance", "[maneuver]
destroy_simulation(sim);
}
TEST_CASE("Prograde burn at periapsis raises apoapsis not periapsis", "[maneuver][periapsis][apoapsis]") {
TEST_CASE("Two periapsis burns execute at same location", "[maneuver][periapsis][sequential]") {
const double TIME_STEP = 60.0;
const int ORBIT_STEPS = 175;
SimulationState* sim = create_simulation(10, 10, 100, TIME_STEP);
REQUIRE(load_system_config(sim, "tests/test_periapsis_burn.toml"));
Spacecraft* craft = &sim->spacecraft[0];
CelestialBody* parent = &sim->bodies[craft->parent_index];
int maneuver_indices[2];
int maneuver_count_for_craft = 0;
for (int i = 0; i < sim->maneuver_count; i++) {
if (sim->maneuvers[i].craft_index == 0) {
maneuver_indices[maneuver_count_for_craft++] = i;
}
}
REQUIRE(maneuver_count_for_craft == 2);
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);
INFO("Initial orbit:");
INFO(" Periapsis: " << initial_periapsis);
INFO(" Apoapsis: " << initial_apoapsis);
INFO(" Eccentricity: " << craft->orbit.eccentricity);
double burn1_time = -1.0;
double burn1_radius = -1.0;
double burn1_true_anomaly = -10.0;
double burn2_time = -1.0;
double burn2_radius = -1.0;
double burn2_true_anomaly = -10.0;
for (int i = 0; i < ORBIT_STEPS; i++) {
update_simulation(sim);
if (sim->maneuvers[maneuver_indices[0]].executed && burn1_time < 0) {
burn1_time = sim->time;
burn1_radius = vec3_magnitude(craft->local_position);
Vec3 r = craft->local_position;
Vec3 v = craft->local_velocity;
Vec3 h = vec3_cross(r, v);
Vec3 e_vec = calculate_eccentricity_vector(r, v, h, G * parent->mass);
double e_mag = vec3_magnitude(e_vec);
burn1_true_anomaly = calculate_true_anomaly(r, v, e_vec, e_mag, burn1_radius);
burn1_true_anomaly = normalize_angle(burn1_true_anomaly);
INFO("First burn executed at step " << i);
INFO(" Time: " << burn1_time);
INFO(" Radius: " << burn1_radius);
INFO(" True anomaly: " << burn1_true_anomaly << " rad (" << burn1_true_anomaly * 180.0 / M_PI << "°)");
INFO(" Periapsis: " << initial_periapsis);
INFO(" Apoapsis: " << initial_apoapsis);
}
if (sim->maneuvers[maneuver_indices[1]].executed && burn2_time < 0) {
burn2_time = sim->time;
burn2_radius = vec3_magnitude(craft->local_position);
Vec3 r = craft->local_position;
Vec3 v = craft->local_velocity;
Vec3 h = vec3_cross(r, v);
Vec3 e_vec = calculate_eccentricity_vector(r, v, h, G * parent->mass);
double e_mag = vec3_magnitude(e_vec);
burn2_true_anomaly = calculate_true_anomaly(r, v, e_vec, e_mag, burn2_radius);
burn2_true_anomaly = normalize_angle(burn2_true_anomaly);
INFO("Second burn executed at step " << i);
INFO(" Time: " << burn2_time);
INFO(" Radius: " << burn2_radius);
INFO(" True anomaly: " << burn2_true_anomaly << " rad (" << burn2_true_anomaly * 180.0 / M_PI << "°)");
}
}
REQUIRE(sim->maneuvers[maneuver_indices[0]].executed);
REQUIRE(sim->maneuvers[maneuver_indices[1]].executed);
INFO("Burn comparison:");
INFO(" Burn 1: time=" << burn1_time << ", radius=" << burn1_radius << ", true_anomaly=" << burn1_true_anomaly);
INFO(" Burn 2: time=" << burn2_time << ", radius=" << burn2_radius << ", true_anomaly=" << burn2_true_anomaly);
REQUIRE_THAT(burn1_radius, Catch::Matchers::WithinAbs(initial_periapsis, 1000.0));
REQUIRE_THAT(burn2_radius, Catch::Matchers::WithinAbs(initial_periapsis, 1000.0));
REQUIRE(fabs(burn1_true_anomaly) < 0.5);
REQUIRE(fabs(burn2_true_anomaly) < 0.5);
double period = 2.0 * M_PI * sqrt(pow(craft->orbit.semi_major_axis, 3.0) / (G * parent->mass));
INFO("Expected orbital period: " << period << " seconds");
INFO("Actual time between burns: " << (burn2_time - burn1_time) << " seconds");
REQUIRE_THAT(burn2_time - burn1_time, Catch::Matchers::WithinAbs(period, TIME_STEP * 2.0));
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);
destroy_simulation(sim);
}
INFO("Initial: peri=" << initial_periapsis << " apo=" << initial_apoapsis);
INFO("Final: peri=" << final_periapsis << " apo=" << final_apoapsis);
TEST_CASE("Periapsis burn fires when crossing periapsis", "[maneuver][periapsis][crossing]") {
const double TIME_STEP = 60.0;
SimulationState* sim = create_simulation(10, 10, 100, TIME_STEP);
REQUIRE(final_apoapsis > initial_apoapsis);
REQUIRE_THAT(final_periapsis, Catch::Matchers::WithinAbs(initial_periapsis, 1.0));
REQUIRE(load_system_config(sim, "tests/test_periapsis_burn.toml"));
Spacecraft* craft = &sim->spacecraft[1]; // TestSatelliteCrossing
CelestialBody* parent = &sim->bodies[craft->parent_index];
int maneuver_index = -1;
for (int i = 0; i < sim->maneuver_count; i++) {
if (sim->maneuvers[i].craft_index == 1) {
maneuver_index = i;
break;
}
}
REQUIRE(maneuver_index >= 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);
INFO("Initial orbit:");
INFO(" Periapsis: " << initial_periapsis);
INFO(" Apoapsis: " << initial_apoapsis);
INFO(" Initial true anomaly: " << craft->orbit.true_anomaly << " rad");
double burn_time = -1.0;
double burn_radius = -1.0;
double burn_true_anomaly = -10.0;
int max_steps = 1000;
for (int i = 0; i < max_steps && !sim->maneuvers[maneuver_index].executed; i++) {
update_simulation(sim);
if (sim->maneuvers[maneuver_index].executed) {
burn_time = sim->time;
burn_radius = vec3_magnitude(craft->local_position);
Vec3 r = craft->local_position;
Vec3 v = craft->local_velocity;
Vec3 h = vec3_cross(r, v);
Vec3 e_vec = calculate_eccentricity_vector(r, v, h, G * parent->mass);
double e_mag = vec3_magnitude(e_vec);
burn_true_anomaly = calculate_true_anomaly(r, v, e_vec, e_mag, burn_radius);
burn_true_anomaly = normalize_angle(burn_true_anomaly);
INFO("Burn executed at step " << i);
INFO(" Time: " << burn_time);
INFO(" Radius: " << burn_radius);
INFO(" True anomaly: " << burn_true_anomaly << " rad (" << burn_true_anomaly * 180.0 / M_PI << "°)");
INFO(" Periapsis: " << initial_periapsis);
INFO(" Apoapsis: " << initial_apoapsis);
}
}
REQUIRE(sim->maneuvers[maneuver_index].executed);
REQUIRE_THAT(burn_radius, Catch::Matchers::WithinAbs(initial_periapsis, 1000.0));
REQUIRE(fabs(burn_true_anomaly) < 0.5);
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);

29
tests/test_periapsis_burn.toml

@ -35,7 +35,18 @@ parent_index = 1
orbit = {
semi_major_axis = 1.0371e7,
eccentricity = 0.3,
true_anomaly = 0.0
true_anomaly = 0.1 # Start slightly past periapsis so first burn doesn't trigger immediately
}
[[spacecraft]]
name = "TestSatelliteCrossing"
mass = 1000.0
parent_index = 1
# Start at 90 degrees from periapsis for crossing test
orbit = {
semi_major_axis = 1.0371e7,
eccentricity = 0.3,
true_anomaly = 1.57 # 90 degrees (pi/2)
}
[[maneuvers]]
@ -45,3 +56,19 @@ trigger_type = "true_anomaly"
trigger_value = 0.0
direction = "prograde"
delta_v = 500.0
[[maneuvers]]
name = "periapsis_prograde_burn_2"
spacecraft_name = "TestSatellite"
trigger_type = "true_anomaly"
trigger_value = 0.0
direction = "prograde"
delta_v = 500.0
[[maneuvers]]
name = "periapsis_prograde_burn_crossing"
spacecraft_name = "TestSatelliteCrossing"
trigger_type = "true_anomaly"
trigger_value = 0.0
direction = "prograde"
delta_v = 500.0

Loading…
Cancel
Save