diff --git a/tests/test_hybrid_impulse_burns.cpp b/tests/test_hybrid_impulse_burns.cpp index 2b614bb..e7e0d5e 100644 --- a/tests/test_hybrid_impulse_burns.cpp +++ b/tests/test_hybrid_impulse_burns.cpp @@ -8,12 +8,42 @@ #include "../src/config_loader.h" #include "../src/test_utilities.h" #include +#include const double POSITION_TOLERANCE = 1e-3; const double VELOCITY_TOLERANCE = 1e-3; const double ELEMENT_TOLERANCE = 1e-6; const double ENERGY_TOLERANCE = 1e-6; +int find_maneuver_by_name(SimulationState* sim, const char* name) { + for (int i = 0; i < sim->maneuver_count; i++) { + if (strcmp(sim->maneuvers[i].name, name) == 0) { + return i; + } + } + return -1; +} + +void execute_maneuver_by_name(SimulationState* sim, const char* maneuver_name, Spacecraft* craft) { + int maneuver_index = find_maneuver_by_name(sim, maneuver_name); + REQUIRE(maneuver_index >= 0); + + Maneuver* maneuver = &sim->maneuvers[maneuver_index]; + REQUIRE(!maneuver->executed); + + // Set simulation time to trigger (for time-based triggers) + if (maneuver->trigger_type == TRIGGER_TIME) { + sim->time = maneuver->trigger_value; + } + + // Execute maneuver + execute_maneuver(maneuver, craft, sim->time); + + // Verify execution + REQUIRE(maneuver->executed); + REQUIRE(maneuver->executed_time == sim->time); +} + TEST_CASE("Config loading for hybrid impulse burns", "[hybrid][impulse][config]") { const double TIME_STEP = 60.0; @@ -63,13 +93,16 @@ SCENARIO("Hohmann transfer with two burns", "[hybrid][impulse][hohmann]") { Vec3 initial_pos; Vec3 initial_vel; orbital_elements_to_cartesian(craft->orbit, earth->mass, &initial_pos, &initial_vel); + craft->local_position = initial_pos; + craft->local_velocity = initial_vel; - SECTION("First burn at perigee raises apogee") { - OrbitalElements initial_elements = craft->orbit; + OrbitalElements initial_elements = craft->orbit; + SECTION("First burn at perigee raises apogee") { double initial_velocity_mag = vec3_magnitude(initial_vel); - apply_impulsive_burn(craft, BURN_PROGRADE, 2440.0); + // Execute first maneuver via maneuver system + execute_maneuver_by_name(sim, "hohmann_burn_1", craft); double new_velocity_mag = vec3_magnitude(craft->local_velocity); @@ -89,6 +122,40 @@ SCENARIO("Hohmann transfer with two burns", "[hybrid][impulse][hohmann]") { REQUIRE(new_elements.eccentricity > initial_elements.eccentricity); } + SECTION("Second burn at apogee circularizes orbit") { + // Execute first burn + execute_maneuver_by_name(sim, "hohmann_burn_1", craft); + + OrbitalElements after_first_burn = cartesian_to_orbital_elements(craft->local_position, craft->local_velocity, earth->mass); + + // Set up position at apogee (true_anomaly = PI) + OrbitalElements apogee_elements = after_first_burn; + apogee_elements.true_anomaly = M_PI; + + Vec3 apogee_pos; + Vec3 apogee_vel; + orbital_elements_to_cartesian(apogee_elements, earth->mass, &apogee_pos, &apogee_vel); + craft->local_position = apogee_pos; + craft->local_velocity = apogee_vel; + + // Execute second maneuver via maneuver system + execute_maneuver_by_name(sim, "hohmann_burn_2", craft); + + Vec3 final_pos = craft->local_position; + Vec3 final_vel = craft->local_velocity; + + OrbitalElements final_elements = cartesian_to_orbital_elements(final_pos, final_vel, earth->mass); + + INFO("After first burn a: " << after_first_burn.semi_major_axis); + INFO("After first burn e: " << after_first_burn.eccentricity); + INFO("Final a: " << final_elements.semi_major_axis); + INFO("Final e: " << final_elements.eccentricity); + + REQUIRE(final_elements.semi_major_axis > after_first_burn.semi_major_axis); + REQUIRE(final_elements.eccentricity < after_first_burn.eccentricity); + REQUIRE(final_elements.eccentricity < 0.1); + } + destroy_simulation(sim); } @@ -105,6 +172,8 @@ SCENARIO("Large burns (Δv > orbital velocity)", "[hybrid][impulse][large_delta_ Vec3 initial_pos; Vec3 initial_vel; orbital_elements_to_cartesian(craft->orbit, earth->mass, &initial_pos, &initial_vel); + craft->local_position = initial_pos; + craft->local_velocity = initial_vel; OrbitalElements initial_elements = cartesian_to_orbital_elements(initial_pos, initial_vel, earth->mass); @@ -115,8 +184,8 @@ SCENARIO("Large burns (Δv > orbital velocity)", "[hybrid][impulse][large_delta_ INFO("Initial velocity: " << initial_velocity_mag << " m/s"); INFO("Escape velocity: " << escape_velocity << " m/s"); - double delta_v = 12000.0; - apply_impulsive_burn(craft, BURN_PROGRADE, delta_v); + // Execute large burn via maneuver system + execute_maneuver_by_name(sim, "large_burn", craft); double final_velocity_mag = vec3_magnitude(craft->local_velocity); INFO("Final velocity: " << final_velocity_mag << " m/s"); @@ -133,7 +202,8 @@ SCENARIO("Large burns (Δv > orbital velocity)", "[hybrid][impulse][large_delta_ } SECTION("Large burn produces correct hyperbolic trajectory") { - apply_impulsive_burn(craft, BURN_PROGRADE, 12000.0); + // Execute large burn via maneuver system + execute_maneuver_by_name(sim, "large_burn", craft); OrbitalElements new_elements = cartesian_to_orbital_elements(craft->local_position, craft->local_velocity, earth->mass); @@ -176,7 +246,14 @@ SCENARIO("Energy conservation during burns", "[hybrid][impulse][energy]") { double delta_v = 1000.0; Vec3 v_initial = craft->local_velocity; - apply_impulsive_burn(craft, BURN_PROGRADE, delta_v); + // Get maneuver delta_v from config + int maneuver_index = find_maneuver_by_name(sim, "hohmann_burn_1"); + REQUIRE(maneuver_index >= 0); + Maneuver* maneuver = &sim->maneuvers[maneuver_index]; + delta_v = maneuver->delta_v; + + // Execute burn via maneuver system + execute_maneuver_by_name(sim, "hohmann_burn_1", craft); Vec3 v_final = craft->local_velocity; Vec3 dv = vec3_sub(v_final, v_initial); @@ -204,7 +281,16 @@ SCENARIO("Energy conservation during burns", "[hybrid][impulse][energy]") { double delta_v = 1000.0; Vec3 v_initial = craft->local_velocity; - apply_impulsive_burn(craft, BURN_RETROGRADE, delta_v); + // Reset spacecraft for second test + craft->local_position = initial_pos; + craft->local_velocity = initial_vel; + sim->time = 0.0; + sim->maneuvers[find_maneuver_by_name(sim, "hohmann_burn_1")].executed = false; + + // Create a retrograde maneuver for this test + Vec3 retrograde_dir = calculate_retrograde_dir(v_initial); + Vec3 dv_vec = vec3_scale(retrograde_dir, delta_v); + apply_custom_burn(craft, dv_vec); Vec3 v_final = craft->local_velocity; Vec3 dv = vec3_sub(v_final, v_initial); @@ -247,6 +333,8 @@ SCENARIO("Round-trip conversion with burns", "[hybrid][impulse][roundtrip]") { Vec3 position_from_elements; Vec3 velocity_from_elements; orbital_elements_to_cartesian(original_elements, earth->mass, &position_from_elements, &velocity_from_elements); + craft->local_position = position_from_elements; + craft->local_velocity = velocity_from_elements; INFO("Original semi_major_axis: " << original_elements.semi_major_axis); INFO("Original eccentricity: " << original_elements.eccentricity); @@ -259,13 +347,10 @@ SCENARIO("Round-trip conversion with burns", "[hybrid][impulse][roundtrip]") { REQUIRE_THAT(recovered_elements.semi_major_axis, Catch::Matchers::WithinAbs(original_elements.semi_major_axis, ELEMENT_TOLERANCE)); REQUIRE_THAT(recovered_elements.eccentricity, Catch::Matchers::WithinAbs(original_elements.eccentricity, ELEMENT_TOLERANCE)); - Vec3 burn_velocity = calculate_prograde_dir(velocity_from_elements); - Vec3 new_velocity = velocity_from_elements; - new_velocity.x += burn_velocity.x * 1000.0; - new_velocity.y += burn_velocity.y * 1000.0; - new_velocity.z += burn_velocity.z * 1000.0; + // Execute maneuver via system + execute_maneuver_by_name(sim, "hohmann_burn_1", craft); - OrbitalElements post_burn_elements = cartesian_to_orbital_elements(position_from_elements, new_velocity, earth->mass); + OrbitalElements post_burn_elements = cartesian_to_orbital_elements(craft->local_position, craft->local_velocity, earth->mass); INFO("Post-burn semi_major_axis: " << post_burn_elements.semi_major_axis); INFO("Post-burn eccentricity: " << post_burn_elements.eccentricity); @@ -280,6 +365,8 @@ SCENARIO("Round-trip conversion with burns", "[hybrid][impulse][roundtrip]") { Vec3 position; Vec3 velocity; orbital_elements_to_cartesian(original_elements, earth->mass, &position, &velocity); + craft->local_position = position; + craft->local_velocity = velocity; for (int i = 0; i < 5; i++) { OrbitalElements elements = cartesian_to_orbital_elements(position, velocity, earth->mass); @@ -327,7 +414,9 @@ SCENARIO("Multiple burn sequences", "[hybrid][impulse][sequence]") { INFO("Initial a: " << initial_elements.semi_major_axis); INFO("Initial e: " << initial_elements.eccentricity); - apply_impulsive_burn(craft, BURN_PROGRADE, 500.0); + // Execute first burn via maneuver system + execute_maneuver_by_name(sim, "hohmann_burn_1", craft); + OrbitalElements after_first_burn = cartesian_to_orbital_elements(craft->local_position, craft->local_velocity, earth->mass); INFO("After first burn a: " << after_first_burn.semi_major_axis); @@ -335,6 +424,7 @@ SCENARIO("Multiple burn sequences", "[hybrid][impulse][sequence]") { REQUIRE(after_first_burn.semi_major_axis > initial_elements.semi_major_axis); + // Propagate to apogee for second burn OrbitalElements apogee_elements = after_first_burn; apogee_elements.true_anomaly = M_PI; @@ -344,7 +434,9 @@ SCENARIO("Multiple burn sequences", "[hybrid][impulse][sequence]") { craft->local_position = apogee_pos; craft->local_velocity = apogee_vel; - apply_impulsive_burn(craft, BURN_PROGRADE, 300.0); + // Execute second burn via maneuver system + execute_maneuver_by_name(sim, "hohmann_burn_2", craft); + OrbitalElements after_second_burn = cartesian_to_orbital_elements(craft->local_position, craft->local_velocity, earth->mass); INFO("After second burn a: " << after_second_burn.semi_major_axis); @@ -357,13 +449,33 @@ SCENARIO("Multiple burn sequences", "[hybrid][impulse][sequence]") { SECTION("Three-burn sequence with plane change") { OrbitalElements initial_elements = cartesian_to_orbital_elements(craft->local_position, craft->local_velocity, earth->mass); - apply_impulsive_burn(craft, BURN_PROGRADE, 500.0); + // Reset spacecraft + craft->local_position = initial_pos; + craft->local_velocity = initial_vel; + sim->time = 0.0; + for (int i = 0; i < sim->maneuver_count; i++) { + sim->maneuvers[i].executed = false; + } + + // Execute prograde burn manually (no config maneuver for this sequence) + Vec3 prograde_dir = calculate_prograde_dir(craft->local_velocity); + Vec3 dv1 = vec3_scale(prograde_dir, 500.0); + apply_custom_burn(craft, dv1); + OrbitalElements after_burn1 = cartesian_to_orbital_elements(craft->local_position, craft->local_velocity, earth->mass); - apply_impulsive_burn(craft, BURN_NORMAL, 300.0); + // Execute normal burn + Vec3 normal_dir = calculate_normal_dir(craft->local_position, craft->local_velocity); + Vec3 dv2 = vec3_scale(normal_dir, 300.0); + apply_custom_burn(craft, dv2); + OrbitalElements after_burn2 = cartesian_to_orbital_elements(craft->local_position, craft->local_velocity, earth->mass); - apply_impulsive_burn(craft, BURN_PROGRADE, 200.0); + // Execute second prograde burn + prograde_dir = calculate_prograde_dir(craft->local_velocity); + Vec3 dv3 = vec3_scale(prograde_dir, 200.0); + apply_custom_burn(craft, dv3); + OrbitalElements after_burn3 = cartesian_to_orbital_elements(craft->local_position, craft->local_velocity, earth->mass); INFO("Initial a: " << initial_elements.semi_major_axis);