diff --git a/src/maneuver.cpp b/src/maneuver.cpp index fe28b49..325e3da 100644 --- a/src/maneuver.cpp +++ b/src/maneuver.cpp @@ -123,6 +123,7 @@ static bool angle_between(double current, double next, double target) { } } +// FIXME: this function needs helpers for wraparound detection and Kepler's equation bool check_maneuver_trigger(Maneuver* maneuver, Spacecraft* craft, SimulationState* sim) { switch (maneuver->trigger_type) { case TRIGGER_TIME: diff --git a/tests/test_maneuver_planning.cpp b/tests/test_maneuver_planning.cpp index 03bb41a..ddfc503 100644 --- a/tests/test_maneuver_planning.cpp +++ b/tests/test_maneuver_planning.cpp @@ -1,4 +1,5 @@ #include +#include #include "../src/physics.h" #include "../src/simulation.h" #include "../src/orbital_objects.h" @@ -6,6 +7,8 @@ #include "../src/config_loader.h" #include +using Catch::Matchers::WithinAbs; + TEST_CASE("Maneuver loading from config", "[maneuver][config]") { const double TIME_STEP = 60.0; @@ -143,3 +146,131 @@ TEST_CASE("Duplicate maneuver names fail config load", "[maneuver][config][error destroy_simulation(sim); } + +TEST_CASE("Time-triggered burn executes at step boundary", "[maneuver][timing][quantization]") { + const double DT = 10.0; + + SimulationState* sim = create_simulation(10, 10, 100, DT); + REQUIRE(load_system_config(sim, "tests/test_maneuver_planning.toml")); + initialize_orbital_objects(sim); + + // Trigger at t=305.0, steps are at 0, 10, 20, ..., 300, 310 + // Should fire at t=310 (5s late) + const double BURN_TIME = 305.0; + + Maneuver burn = create_maneuver( + "TestBurst", + 0, + BURN_PROGRADE, + 10.0, + TRIGGER_TIME, + BURN_TIME + ); + + int idx = add_maneuver_to_simulation(sim, &burn); + REQUIRE(idx >= 0); + + int steps = 0; + const int MAX_STEPS = 1000; + while (!sim->maneuvers[idx].executed && steps < MAX_STEPS) { + update_simulation(sim); + steps++; + } + + REQUIRE(sim->maneuvers[idx].executed); + INFO("Trigger time: " << BURN_TIME); + INFO("Executed at: " << sim->maneuvers[idx].executed_time); + INFO("Steps taken: " << steps); + INFO("Quantization error: " << (sim->maneuvers[idx].executed_time - BURN_TIME) << " s"); + + double expected_step = std::ceil(BURN_TIME / DT) * DT; + INFO("Expected step: " << expected_step); + REQUIRE(sim->maneuvers[idx].executed_time == expected_step); + REQUIRE_THAT(sim->maneuvers[idx].executed_time - BURN_TIME, WithinAbs(5.0, 0.01)); + + destroy_simulation(sim); +} + +TEST_CASE("Time-triggered burn on exact step boundary", "[maneuver][timing][quantization]") { + const double DT = 10.0; + + SimulationState* sim = create_simulation(10, 10, 100, DT); + REQUIRE(load_system_config(sim, "tests/test_maneuver_planning.toml")); + initialize_orbital_objects(sim); + + // Trigger at exact step boundary + const double BURN_TIME = 300.0; + + Maneuver burn = create_maneuver( + "TestBurst", + 0, + BURN_PROGRADE, + 10.0, + TRIGGER_TIME, + BURN_TIME + ); + + int idx = add_maneuver_to_simulation(sim, &burn); + REQUIRE(idx >= 0); + + int steps = 0; + const int MAX_STEPS = 1000; + while (!sim->maneuvers[idx].executed && steps < MAX_STEPS) { + update_simulation(sim); + steps++; + } + + REQUIRE(sim->maneuvers[idx].executed); + INFO("Trigger time: " << BURN_TIME); + INFO("Executed at: " << sim->maneuvers[idx].executed_time); + INFO("Steps taken: " << steps); + INFO("Quantization error: " << (sim->maneuvers[idx].executed_time - BURN_TIME) << " s"); + + REQUIRE(sim->maneuvers[idx].executed_time == BURN_TIME); + + destroy_simulation(sim); +} + +TEST_CASE("Time-triggered burn quantization error accumulates over long sim", "[maneuver][timing][quantization]") { + const double DT = 10.0; + + SimulationState* sim = create_simulation(10, 10, 100, DT); + REQUIRE(load_system_config(sim, "tests/test_maneuver_planning.toml")); + initialize_orbital_objects(sim); + + // Trigger at a time that's 7s after a step boundary + const double BURN_TIME = 62807.0; + double expected_step = std::ceil(BURN_TIME / DT) * DT; // 62810 + double quantization_error = expected_step - BURN_TIME; // 3s + + Maneuver burn = create_maneuver( + "TestBurst", + 0, + BURN_PROGRADE, + 10.0, + TRIGGER_TIME, + BURN_TIME + ); + + int idx = add_maneuver_to_simulation(sim, &burn); + REQUIRE(idx >= 0); + + int steps = 0; + const int MAX_STEPS = 10000; + while (!sim->maneuvers[idx].executed && steps < MAX_STEPS) { + update_simulation(sim); + steps++; + } + + REQUIRE(sim->maneuvers[idx].executed); + INFO("Trigger time: " << BURN_TIME); + INFO("Executed at: " << sim->maneuvers[idx].executed_time); + INFO("Steps taken: " << steps); + INFO("Quantization error: " << (sim->maneuvers[idx].executed_time - BURN_TIME) << " s"); + INFO("Expected quantization error: " << quantization_error << " s"); + + REQUIRE(sim->maneuvers[idx].executed_time == expected_step); + REQUIRE_THAT(sim->maneuvers[idx].executed_time - BURN_TIME, WithinAbs(quantization_error, 0.01)); + + destroy_simulation(sim); +}