Browse Source

refactor test_maneuver_planning: SCENARIO/SECTION pattern, quantitative assertions, TOML 1.0 config, precalc script

test-refactor
cinnaboot 3 weeks ago
parent
commit
3bbac86ca5
  1. 2
      continue.md
  2. 128
      scripts/precalc_maneuver_planning.py
  3. 81
      tests/test_maneuver_planning.cpp
  4. 41
      tests/test_maneuver_planning.toml

2
continue.md

@ -68,6 +68,7 @@ All constants defined in `src/test_utilities.h` — use those, do not redefine l
- `test_energy` — Energy calculations and conservation tests
- `test_inclined_orbits` — 3D inclined orbit conversions, Molniya orbits, rotation matrices
- `test_maneuvers` — Impulsive burn tests with precalculated values
- `test_maneuver_planning` — Maneuver trigger system (TIME + elliptical TRUE_ANOMALY triggers)
- `test_orbital_period` — Orbital period calculations, SCENARIO/SECTION pattern
- `test_true_anomaly_roundtrip` — True anomaly conversion round-trips, tight tolerances
- `test_physics_utilities` — Vector math, acceleration, matrix ops, rotation matrices, compare_vec3
@ -75,7 +76,6 @@ All constants defined in `src/test_utilities.h` — use those, do not redefine l
- `test_hybrid_burns` — 14 TEST_CASEs → 1 SCENARIO with 22 SECTIONs, impulse + continuous burns, precalculated values; converted 17 qualitative checks to quantitative, replaced hardcoded tolerances with named constants (A_TOL, E_TOL, D_TOL, M_TOL, ANG_TOL, R_TOL)
### Can Refactor Now (sim_engine.py supports all features needed)
- `test_maneuver_planning` — maneuver trigger system (TIME + elliptical TRUE_ANOMALY triggers)
- `test_orbit_rendering` — rendering tests (check if sim_engine needed)
- `test_precision_boundaries` — boundary condition tests
- `test_invalid_parent_assignment` — validation/error handling tests

128
scripts/precalc_maneuver_planning.py

@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""
Precalculate expected values for test_maneuver_planning.cpp refactoring.
Computes velocities and energy after time-based and true anomaly-based
maneuver triggers.
"""
import math
import sys
sys.path.insert(0, "/home/agent/dev/claudes_game")
from scripts.sim_engine import *
def main():
G_const = G
earth_mass = 5.972e24
mu = G_const * earth_mass
# Initial circular orbit at r = 6.771e6 m
r0 = 6.771e6
v_circular = math.sqrt(mu / r0)
print("// Initial circular orbit")
print(f"// r = {r0} m")
print(f"// v_circular = {v_circular:.15e} m/s")
print()
# Run the full simulation with the TOML config
sim = Simulator("tests/test_maneuver_planning.toml", dt=60.0)
# Get initial craft velocity
craft = sim.get_craft("LEO_Satellite")
v_initial = vmag(craft.local_vel)
print(f"// Initial craft velocity from config: {v_initial:.15e} m/s")
print()
# Run to just before first burn (t=3600)
steps_to_first_burn = int(3600.0 / 60.0) # 60 steps
sim.run(steps_to_first_burn)
craft = sim.get_craft("LEO_Satellite")
v_before_burn1 = vmag(craft.local_vel)
t_before = sim.time
# Check if maneuver[0] executed
man = sim.maneuvers[0]
print("// First burn (time trigger at 3600.0 s)")
print(f"// Time at step end: {t_before:.1f} s")
print(f"// Executed: {man.executed}")
print(f"// Executed time: {man.executed_time:.15e} s")
print(f"// Velocity before burn: {v_before_burn1:.15e} m/s")
print()
# Continue a bit more to ensure burn fires
sim.run(1)
man = sim.maneuvers[0]
craft = sim.get_craft("LEO_Satellite")
v_after_burn1 = vmag(craft.local_vel)
a_after_burn1 = craft.orbit.a
e_after_burn1 = craft.orbit.e
r_after_burn1 = vmag(craft.local_pos)
print(f"// After stepping past burn:")
print(f"// Executed: {man.executed}")
print(f"// Executed time: {man.executed_time:.15e} s")
print(f"// Velocity after burn: {v_after_burn1:.15e} m/s")
print(f"// Semi-major axis: {a_after_burn1:.15e} m")
print(f"// Eccentricity: {e_after_burn1:.15e}")
print(f"// Radius: {r_after_burn1:.15e} m")
print(f"// KE after first burn: {0.5 * 1000.0 * v_after_burn1 * v_after_burn1:.15e} J")
print()
# Continue until second burn fires (true anomaly 0.0)
max_additional_steps = 2000 # should be enough
second_burn_fired = False
for step in range(max_additional_steps):
sim.run(1)
if sim.maneuvers[1].executed:
second_burn_fired = True
break
craft = sim.get_craft("LEO_Satellite")
v_after_burn2 = vmag(craft.local_vel)
a_after_burn2 = craft.orbit.a
e_after_burn2 = craft.orbit.e
man2 = sim.maneuvers[1]
print("// Second burn (true anomaly trigger at 0.0)")
print(f"// Fired: {second_burn_fired}")
print(f"// Executed time: {man2.executed_time:.15e} s")
print(f"// Sim time: {sim.time:.1f} s")
print(f"// Velocity after second burn: {v_after_burn2:.15e} m/s")
print(f"// Semi-major axis: {a_after_burn2:.15e} m")
print(f"// Eccentricity: {e_after_burn2:.15e}")
print(f"// KE after second burn: {0.5 * 1000.0 * v_after_burn2 * v_after_burn2:.15e} J")
print()
# Also get the exact burn result for second burn
br = man2.burn_result
print(f"// Pre-burn state at second burn:")
print(f"// pos = {br.position}")
print(f"// vel = {br.velocity}")
print(f"// true_anomaly = {br.true_anomaly:.15e} rad")
print()
# Run beyond well past to verify no extra executions
sim.run(500)
exec_count = sum(1 for m in sim.maneuvers if m.executed)
print("// After extra simulation")
print(f"// Total executed: {exec_count}")
for i, m in enumerate(sim.maneuvers):
print(f"// Maneuver[{i}] '{m.name}': executed={m.executed}, time={m.executed_time:.1f} s")
print()
print("// For WithinAbs assertions:")
print(f"// v_initial := {v_circular:.15e}")
print(f"// v_after_burn1 := {v_after_burn1:.15e}")
print(f"// a_after_burn1 := {a_after_burn1:.15e}")
print(f"// e_after_burn1 := {e_after_burn1:.15e}")
print(f"// v_after_burn2 := {v_after_burn2:.15e}")
print(f"// a_after_burn2 := {a_after_burn2:.15e}")
print(f"// e_after_burn2 := {e_after_burn2:.15e}")
print(f"// executed_time_1 := {man.executed_time:.15e}")
print(f"// executed_time_2 := {man2.executed_time:.15e}")
if __name__ == "__main__":
main()

81
tests/test_maneuver_planning.cpp

@ -0,0 +1,81 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include "../src/physics.h"
#include "../src/simulation.h"
#include "../src/orbital_objects.h"
#include "../src/maneuver.h"
#include "../src/config_loader.h"
#include "../src/test_utilities.h"
using Catch::Matchers::WithinAbs;
SCENARIO("Maneuver planning and execution", "[maneuver][planning][trigger][config]") {
const double TIME_STEP = 60.0;
const double BURN_TIME = 3600.0;
SimulationState* sim = create_simulation(10, 10, 100, TIME_STEP);
REQUIRE(load_system_config(sim, "tests/test_maneuver_planning.toml"));
auto run_until = [&](double target_time) {
while (sim->time < target_time) {
update_simulation(sim);
}
};
SECTION("maneuvers load from config with correct properties") {
REQUIRE(sim->maneuver_count == 2);
REQUIRE(std::string(sim->maneuvers[0].name) == "orbit_raise_1");
REQUIRE(std::string(sim->maneuvers[1].name) == "orbit_raise_2");
REQUIRE(sim->maneuvers[0].trigger_type == TRIGGER_TIME);
REQUIRE(sim->maneuvers[1].trigger_type == TRIGGER_TRUE_ANOMALY);
REQUIRE_THAT(sim->maneuvers[0].delta_v, WithinAbs(500.0, D_TOL));
}
SECTION("time-based trigger executes at 3600 s") {
REQUIRE(!sim->maneuvers[0].executed);
REQUIRE(!sim->maneuvers[1].executed);
run_until(BURN_TIME + TIME_STEP);
REQUIRE(sim->maneuvers[0].executed);
REQUIRE_THAT(sim->maneuvers[0].executed_time, WithinAbs(BURN_TIME, TIME_STEP));
const double after_velocity = vec3_magnitude(sim->spacecraft[0].local_velocity);
REQUIRE_THAT(after_velocity, WithinAbs(8.170251503359999e3, V_TOL));
}
SECTION("true anomaly trigger executes after first burn") {
REQUIRE(!sim->maneuvers[1].executed);
run_until(BURN_TIME + TIME_STEP);
REQUIRE(sim->maneuvers[0].executed);
const double max_sim_time = BURN_TIME + 72000.0;
while (sim->time < max_sim_time) {
update_simulation(sim);
if (sim->maneuvers[1].executed) {
break;
}
}
REQUIRE(sim->maneuvers[1].executed);
REQUIRE_THAT(vec3_magnitude(sim->spacecraft[0].local_velocity),
WithinAbs(8.672299586435140e3, V_TOL));
}
SECTION("maneuvers execute only once") {
run_until(20000.0);
REQUIRE(sim->maneuvers[0].executed);
REQUIRE(sim->maneuvers[1].executed);
int execution_count = 0;
for (int i = 0; i < sim->maneuver_count; i++) {
if (sim->maneuvers[i].executed) {
execution_count++;
}
}
REQUIRE(execution_count == 2);
}
destroy_simulation(sim);
}

41
tests/test_maneuver_planning.toml

@ -0,0 +1,41 @@
# Test Configuration: Maneuver Sequence
# Sun + Earth + LEO Satellite with scheduled burns
# Tests time-based and true anomaly-based maneuver triggers
[[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 = "LEO_Satellite"
mass = 1000.0
parent_index = 1
orbit = {semi_major_axis = 6.771e6, eccentricity = 0.0, true_anomaly = 1.57}
[[maneuvers]]
name = "orbit_raise_1"
spacecraft_name = "LEO_Satellite"
trigger_type = "time"
trigger_value = 3600.0
direction = "prograde"
delta_v = 500.0
[[maneuvers]]
name = "orbit_raise_2"
spacecraft_name = "LEO_Satellite"
trigger_type = "true_anomaly"
trigger_value = 0.0
direction = "prograde"
delta_v = 500.0
Loading…
Cancel
Save