@ -8,12 +8,42 @@
# include "../src/config_loader.h"
# include "../src/config_loader.h"
# include "../src/test_utilities.h"
# include "../src/test_utilities.h"
# include <cmath>
# include <cmath>
# include <cstring>
const double POSITION_TOLERANCE = 1e-3 ;
const double POSITION_TOLERANCE = 1e-3 ;
const double VELOCITY_TOLERANCE = 1e-3 ;
const double VELOCITY_TOLERANCE = 1e-3 ;
const double ELEMENT_TOLERANCE = 1e-6 ;
const double ELEMENT_TOLERANCE = 1e-6 ;
const double ENERGY_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] " ) {
TEST_CASE ( " Config loading for hybrid impulse burns " , " [hybrid][impulse][config] " ) {
const double TIME_STEP = 60.0 ;
const double TIME_STEP = 60.0 ;
@ -63,13 +93,16 @@ SCENARIO("Hohmann transfer with two burns", "[hybrid][impulse][hohmann]") {
Vec3 initial_pos ;
Vec3 initial_pos ;
Vec3 initial_vel ;
Vec3 initial_vel ;
orbital_elements_to_cartesian ( craft - > orbit , earth - > mass , & initial_pos , & 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 ) ;
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 ) ;
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 ) ;
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 ) ;
destroy_simulation ( sim ) ;
}
}
@ -105,6 +172,8 @@ SCENARIO("Large burns (Δv > orbital velocity)", "[hybrid][impulse][large_delta_
Vec3 initial_pos ;
Vec3 initial_pos ;
Vec3 initial_vel ;
Vec3 initial_vel ;
orbital_elements_to_cartesian ( craft - > orbit , earth - > mass , & initial_pos , & 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 ) ;
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 ( " Initial velocity: " < < initial_velocity_mag < < " m/s " ) ;
INFO ( " Escape velocity: " < < escape_velocity < < " m/s " ) ;
INFO ( " Escape velocity: " < < escape_velocity < < " m/s " ) ;
double delta_v = 12000.0 ;
// Execute large burn via maneuver system
apply_impulsive_burn ( craft , BURN_PROGRADE , delta_v ) ;
execute_maneuver_by_name ( sim , " large_burn " , craft ) ;
double final_velocity_mag = vec3_magnitude ( craft - > local_velocity ) ;
double final_velocity_mag = vec3_magnitude ( craft - > local_velocity ) ;
INFO ( " Final velocity: " < < final_velocity_mag < < " m/s " ) ;
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 " ) {
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 ) ;
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 ;
double delta_v = 1000.0 ;
Vec3 v_initial = craft - > local_velocity ;
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 v_final = craft - > local_velocity ;
Vec3 dv = vec3_sub ( v_final , v_initial ) ;
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 ;
double delta_v = 1000.0 ;
Vec3 v_initial = craft - > local_velocity ;
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 v_final = craft - > local_velocity ;
Vec3 dv = vec3_sub ( v_final , v_initial ) ;
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 position_from_elements ;
Vec3 velocity_from_elements ;
Vec3 velocity_from_elements ;
orbital_elements_to_cartesian ( original_elements , earth - > mass , & position_from_elements , & 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 semi_major_axis: " < < original_elements . semi_major_axis ) ;
INFO ( " Original eccentricity: " < < original_elements . eccentricity ) ;
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 . 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 ) ) ;
REQUIRE_THAT ( recovered_elements . eccentricity , Catch : : Matchers : : WithinAbs ( original_elements . eccentricity , ELEMENT_TOLERANCE ) ) ;
Vec3 burn_velocity = calculate_prograde_dir ( velocity_from_elements ) ;
// Execute maneuver via system
Vec3 new_velocity = velocity_from_elements ;
execute_maneuver_by_name ( sim , " hohmann_burn_1 " , craft ) ;
new_velocity . x + = burn_velocity . x * 1000.0 ;
new_velocity . y + = burn_velocity . y * 1000.0 ;
new_velocity . z + = burn_velocity . z * 1000.0 ;
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 semi_major_axis: " < < post_burn_elements . semi_major_axis ) ;
INFO ( " Post-burn eccentricity: " < < post_burn_elements . eccentricity ) ;
INFO ( " Post-burn eccentricity: " < < post_burn_elements . eccentricity ) ;
@ -280,6 +365,8 @@ SCENARIO("Round-trip conversion with burns", "[hybrid][impulse][roundtrip]") {
Vec3 position ;
Vec3 position ;
Vec3 velocity ;
Vec3 velocity ;
orbital_elements_to_cartesian ( original_elements , earth - > mass , & position , & 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 + + ) {
for ( int i = 0 ; i < 5 ; i + + ) {
OrbitalElements elements = cartesian_to_orbital_elements ( position , velocity , earth - > mass ) ;
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 a: " < < initial_elements . semi_major_axis ) ;
INFO ( " Initial e: " < < initial_elements . eccentricity ) ;
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 ) ;
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 ) ;
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 ) ;
REQUIRE ( after_first_burn . semi_major_axis > initial_elements . semi_major_axis ) ;
// Propagate to apogee for second burn
OrbitalElements apogee_elements = after_first_burn ;
OrbitalElements apogee_elements = after_first_burn ;
apogee_elements . true_anomaly = M_PI ;
apogee_elements . true_anomaly = M_PI ;
@ -344,7 +434,9 @@ SCENARIO("Multiple burn sequences", "[hybrid][impulse][sequence]") {
craft - > local_position = apogee_pos ;
craft - > local_position = apogee_pos ;
craft - > local_velocity = apogee_vel ;
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 ) ;
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 ) ;
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 " ) {
SECTION ( " Three-burn sequence with plane change " ) {
OrbitalElements initial_elements = cartesian_to_orbital_elements ( craft - > local_position , craft - > local_velocity , earth - > mass ) ;
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 ) ;
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 ) ;
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 ) ;
OrbitalElements after_burn3 = cartesian_to_orbital_elements ( craft - > local_position , craft - > local_velocity , earth - > mass ) ;
INFO ( " Initial a: " < < initial_elements . semi_major_axis ) ;
INFO ( " Initial a: " < < initial_elements . semi_major_axis ) ;