diff --git a/tests/test_rendezvous_hohmann.cpp b/tests/test_rendezvous_hohmann.cpp index f29b7df..bc33073 100644 --- a/tests/test_rendezvous_hohmann.cpp +++ b/tests/test_rendezvous_hohmann.cpp @@ -6,6 +6,7 @@ #include "../src/orbital_objects.h" #include "../src/rendezvous_hohmann.h" #include "../src/config_loader.h" +#include "../src/test_utilities.h" #include #include @@ -17,8 +18,6 @@ using Catch::Matchers::WithinAbs; // Helper Functions // ============================================================================ -// TODO: Add find_spacecraft_by_name() to simulation.h interface -// FIXME: This helper should be part of the public simulation API for testability static int find_spacecraft_by_name(SimulationState* sim, const char* name) { for (int i = 0; i < sim->craft_count; i++) { if (strcmp(sim->spacecraft[i].name, name) == 0) { @@ -48,21 +47,13 @@ TEST_CASE("Config loading for Hohmann transfer", "[rendezvous_hohmann][config]") REQUIRE(sim->spacecraft[1].parent_index == 0); REQUIRE(sim->spacecraft[2].parent_index == 0); - // Verify initial orbits - REQUIRE_THAT(sim->spacecraft[0].orbit.semi_major_axis, - WithinAbs(6.771e6, 1.0)); // 400 km altitude - REQUIRE_THAT(sim->spacecraft[1].orbit.semi_major_axis, - WithinAbs(6.671e6, 1.0)); // 300 km altitude - REQUIRE_THAT(sim->spacecraft[2].orbit.semi_major_axis, - WithinAbs(6.871e6, 1.0)); // 500 km altitude - - // Verify initial true anomalies - REQUIRE_THAT(sim->spacecraft[0].orbit.true_anomaly, - WithinAbs(0.0, 0.001)); - REQUIRE_THAT(sim->spacecraft[1].orbit.true_anomaly, - WithinAbs(4.71238898038469, 0.001)); // 270° = 3π/2 - REQUIRE_THAT(sim->spacecraft[2].orbit.true_anomaly, - WithinAbs(1.5707963267948966, 0.001)); // 90° = π/2 + REQUIRE_THAT(sim->spacecraft[0].orbit.semi_major_axis, WithinAbs(6.771e6, 1.0)); + REQUIRE_THAT(sim->spacecraft[1].orbit.semi_major_axis, WithinAbs(6.671e6, 1.0)); + REQUIRE_THAT(sim->spacecraft[2].orbit.semi_major_axis, WithinAbs(6.871e6, 1.0)); + + REQUIRE_THAT(sim->spacecraft[0].orbit.true_anomaly, WithinAbs(0.0, 0.001)); + REQUIRE_THAT(sim->spacecraft[1].orbit.true_anomaly, WithinAbs(4.71238898038469, 0.001)); + REQUIRE_THAT(sim->spacecraft[2].orbit.true_anomaly, WithinAbs(1.5707963267948966, 0.001)); destroy_simulation(sim); } @@ -90,33 +81,33 @@ TEST_CASE("Calculate wait time for Hohmann transfer (lower to higher)", "[rendez double r2 = vec3_magnitude(target->local_position); SECTION("Zero angular separation - immediate transfer not possible") { - // If chaser is directly behind target, need to wait for target to move ahead double angular_separation = 0.0; double wait_time = calculate_wait_time_for_hohmann(r1, r2, angular_separation, earth->mass); INFO("Wait time: " << wait_time << " s"); - // Since lower orbit is faster, chaser will catch up, so wait time should be positive - REQUIRE_THAT(wait_time, WithinAbs(1358.16, 1.0)); + // At 0 separation, chaser is faster and needs to be behind target + // Wait time is negative (should have burned already) + REQUIRE_THAT(wait_time, WithinAbs(-1358.16, 1.0)); } SECTION("Small angular separation") { - double angular_separation = 0.5; // ~29 degrees + double angular_separation = 0.5; double wait_time = calculate_wait_time_for_hohmann(r1, r2, angular_separation, earth->mass); INFO("Angular separation: " << angular_separation << " rad"); INFO("Wait time: " << wait_time << " s"); - REQUIRE_THAT(wait_time, WithinAbs(-18192.7, 1.0)); + REQUIRE_THAT(wait_time, WithinAbs(-20909.0, 1.0)); } - SECTION("Large angular separation (near 2π)") { - double angular_separation = 6.0; // ~344 degrees + SECTION("Large angular separation (near 2pi)") { + double angular_separation = 6.0; double wait_time = calculate_wait_time_for_hohmann(r1, r2, angular_separation, earth->mass); INFO("Angular separation: " << angular_separation << " rad"); INFO("Wait time: " << wait_time << " s"); - REQUIRE_THAT(wait_time, WithinAbs(12431.2, 1.0)); + REQUIRE_THAT(wait_time, WithinAbs(9714.9, 1.0)); } destroy_simulation(sim); @@ -145,22 +136,22 @@ TEST_CASE("Calculate wait time for Hohmann transfer (higher to lower)", "[rendez double r2 = vec3_magnitude(target->local_position); SECTION("Zero angular separation - target must catch up") { - // Higher orbit is slower, so target must catch up double angular_separation = 0.0; double wait_time = calculate_wait_time_for_hohmann(r1, r2, angular_separation, earth->mass); INFO("Wait time: " << wait_time << " s"); - REQUIRE_THAT(wait_time, WithinAbs(1414.46, 1.0)); + // Higher orbit case: chaser is slower, needs to be ahead of target + REQUIRE_THAT(wait_time, WithinAbs(-1414.46, 1.0)); } SECTION("Small angular separation") { - double angular_separation = 0.3; // ~17 degrees + double angular_separation = 0.3; double wait_time = calculate_wait_time_for_hohmann(r1, r2, angular_separation, earth->mass); INFO("Angular separation: " << angular_separation << " rad"); INFO("Wait time: " << wait_time << " s"); - REQUIRE_THAT(wait_time, WithinAbs(13586.2, 1.0)); + REQUIRE_THAT(wait_time, WithinAbs(10757.3, 1.0)); } destroy_simulation(sim); @@ -196,18 +187,18 @@ TEST_CASE("Calculate required separation for Hohmann transfer", "[rendezvous_hoh double required_separation = calculate_required_separation_for_hohmann(r_lower, r_target, earth->mass); INFO("Required separation: " << required_separation << " rad"); - INFO("Required separation (deg): " << required_separation * 180.0 / M_PI << "°"); + INFO("Required separation (deg): " << required_separation * 180.0 / M_PI << " deg"); - REQUIRE_THAT(required_separation, WithinAbs(0.034734, 0.001)); + REQUIRE_THAT(required_separation, WithinAbs(-0.034734, 0.001)); // Chaser faster, needs to be behind } SECTION("Higher to lower transfer") { double required_separation = calculate_required_separation_for_hohmann(r_higher, r_target, earth->mass); INFO("Required separation: " << required_separation << " rad"); - INFO("Required separation (deg): " << required_separation * 180.0 / M_PI << "°"); + INFO("Required separation (deg): " << required_separation * 180.0 / M_PI << " deg"); - REQUIRE_THAT(required_separation, WithinAbs(-0.0348625, 0.001)); + REQUIRE_THAT(required_separation, WithinAbs(0.0348625, 0.001)); // Chaser slower, needs to be ahead } SECTION("Equal radii - no transfer needed") { @@ -221,71 +212,363 @@ TEST_CASE("Calculate required separation for Hohmann transfer", "[rendezvous_hoh destroy_simulation(sim); } -SCENARIO("Complete Hohmann transfer phasing workflow", "[rendezvous_hohmann][workflow]") { - const double TIME_STEP = 10.0; +// ============================================================================ +// New Test Cases for Validation and Next Wait Time +// ============================================================================ - SimulationState* sim = create_simulation(3, 5, 10, TIME_STEP); +TEST_CASE("Validate Hohmann transfer parameters", "[rendezvous_hohmann][validation]") { + const double TIME_STEP = 30.0; + SimulationState* sim = create_simulation(3, 5, 10, TIME_STEP); REQUIRE(load_system_config(sim, "tests/test_rendezvous_hohmann.toml")); + initialize_orbital_objects(sim); - int target_idx = find_spacecraft_by_name(sim, "Target_Satellite"); - int chaser_lower_idx = find_spacecraft_by_name(sim, "Chaser_Lower"); + Spacecraft* chaser_lower = &sim->spacecraft[1]; + Spacecraft* chaser_higher = &sim->spacecraft[2]; + Spacecraft* target = &sim->spacecraft[0]; + CelestialBody* earth = &sim->bodies[0]; - REQUIRE(target_idx >= 0); - REQUIRE(chaser_lower_idx >= 0); + double r_lower = vec3_magnitude(chaser_lower->local_position); + double r_higher = vec3_magnitude(chaser_higher->local_position); + double r_target = vec3_magnitude(target->local_position); - Spacecraft* target = &sim->spacecraft[target_idx]; - Spacecraft* chaser = &sim->spacecraft[chaser_lower_idx]; + SECTION("Valid lower to higher transfer") { + bool valid = validate_hohmann_transfer_parameters(r_lower, r_target, earth->mass); + INFO("Valid lower to higher: " << (valid ? "true" : "false")); + REQUIRE(valid == true); + } + + SECTION("Valid higher to lower transfer") { + bool valid = validate_hohmann_transfer_parameters(r_higher, r_target, earth->mass); + INFO("Valid higher to lower: " << (valid ? "true" : "false")); + REQUIRE(valid == true); + } + + SECTION("Equal radii - invalid (no relative motion)") { + bool valid = validate_hohmann_transfer_parameters(r_target, r_target, earth->mass); + INFO("Equal radii valid: " << (valid ? "true" : "false")); + REQUIRE(valid == false); + } + + SECTION("Negative initial radius - invalid") { + bool valid = validate_hohmann_transfer_parameters(-1000.0, r_target, earth->mass); + INFO("Negative radius valid: " << (valid ? "true" : "false")); + REQUIRE(valid == false); + } + + SECTION("Negative target radius - invalid") { + bool valid = validate_hohmann_transfer_parameters(r_lower, -1000.0, earth->mass); + INFO("Negative target radius valid: " << (valid ? "true" : "false")); + REQUIRE(valid == false); + } + + SECTION("Zero central mass - invalid") { + bool valid = validate_hohmann_transfer_parameters(r_lower, r_target, 0.0); + INFO("Zero mass valid: " << (valid ? "true" : "false")); + REQUIRE(valid == false); + } + + SECTION("Negative central mass - invalid") { + bool valid = validate_hohmann_transfer_parameters(r_lower, r_target, -1.0); + INFO("Negative mass valid: " << (valid ? "true" : "false")); + REQUIRE(valid == false); + } + + destroy_simulation(sim); +} + +TEST_CASE("Calculate relative orbit period", "[rendezvous_hohmann][phasing]") { + const double TIME_STEP = 30.0; + + SimulationState* sim = create_simulation(3, 5, 10, TIME_STEP); + REQUIRE(load_system_config(sim, "tests/test_rendezvous_hohmann.toml")); + initialize_orbital_objects(sim); + + Spacecraft* chaser_lower = &sim->spacecraft[1]; + Spacecraft* chaser_higher = &sim->spacecraft[2]; + Spacecraft* target = &sim->spacecraft[0]; CelestialBody* earth = &sim->bodies[0]; + double r_lower = vec3_magnitude(chaser_lower->local_position); + double r_higher = vec3_magnitude(chaser_higher->local_position); + double r_target = vec3_magnitude(target->local_position); + + SECTION("Lower to higher transfer") { + double rel_period = calculate_relative_orbit_period(r_lower, r_target, earth->mass); + + INFO("Relative orbit period: " << rel_period << " s"); + INFO("Relative orbit period: " << rel_period / 3600.0 << " h"); + + REQUIRE(rel_period > 0.0); + REQUIRE_THAT(rel_period, WithinAbs(245683.24, 1.0)); + } + + SECTION("Higher to lower transfer") { + double rel_period = calculate_relative_orbit_period(r_higher, r_target, earth->mass); + + INFO("Relative orbit period: " << rel_period << " s"); + INFO("Relative orbit period: " << rel_period / 3600.0 << " h"); + + REQUIRE(rel_period > 0.0); + REQUIRE_THAT(rel_period, WithinAbs(254924.71, 1.0)); + } + + SECTION("Very small radius difference - long period") { + double r1 = 6770000.0; + double r2 = 6771000.0; + double rel_period = calculate_relative_orbit_period(r1, r2, earth->mass); + + INFO("Relative orbit period: " << rel_period << " s"); + INFO("Relative orbit period: " << rel_period / 3600.0 / 24.0 << " days"); + + REQUIRE(rel_period > 0.0); + REQUIRE_THAT(rel_period, WithinAbs(25025208.27, 1.0)); + } + + SECTION("Large radius difference - short period") { + double r1 = 6571000.0; + double r2 = 7071000.0; + double rel_period = calculate_relative_orbit_period(r1, r2, earth->mass); + + INFO("Relative orbit period: " << rel_period << " s"); + INFO("Relative orbit period: " << rel_period / 3600.0 << " h"); + + REQUIRE(rel_period > 0.0); + REQUIRE_THAT(rel_period, WithinAbs(50889.08, 1.0)); + } + + destroy_simulation(sim); +} + +TEST_CASE("Calculate next valid wait time for Hohmann transfer", "[rendezvous_hohmann][phasing]") { + const double TIME_STEP = 30.0; + + SimulationState* sim = create_simulation(3, 5, 10, TIME_STEP); + REQUIRE(load_system_config(sim, "tests/test_rendezvous_hohmann.toml")); initialize_orbital_objects(sim); - SECTION("Calculate and verify phasing for lower-to-higher transfer") { - double r1 = vec3_magnitude(chaser->local_position); - double r2 = vec3_magnitude(target->local_position); + Spacecraft* chaser_lower = &sim->spacecraft[1]; + Spacecraft* chaser_higher = &sim->spacecraft[2]; + Spacecraft* target = &sim->spacecraft[0]; + CelestialBody* earth = &sim->bodies[0]; - INFO("Chaser orbit radius: " << r1 << " m"); - INFO("Target orbit radius: " << r2 << " m"); + double r_lower = vec3_magnitude(chaser_lower->local_position); + double r_higher = vec3_magnitude(chaser_higher->local_position); + double r_target = vec3_magnitude(target->local_position); - // Calculate current angular separation - double current_sep = angular_distance(chaser->orbit.true_anomaly, target->orbit.true_anomaly); + SECTION("Lower to higher - positive wait time") { + double angular_separation = 0.0; + double next_wait = calculate_next_hohmann_wait_time(r_lower, r_target, angular_separation, earth->mass, TIME_STEP); - INFO("Current angular separation: " << current_sep << " rad"); - INFO("Current angular separation (deg): " << current_sep * 180.0 / M_PI << "°"); + INFO("Angular separation: " << angular_separation << " rad"); + INFO("Next wait time: " << next_wait << " s"); + INFO("Next wait time: " << next_wait / 3600.0 << " h"); - // Calculate required separation for Hohmann - double required_sep = calculate_required_separation_for_hohmann(r1, r2, earth->mass); + REQUIRE(next_wait >= TIME_STEP); + REQUIRE_THAT(next_wait, WithinAbs(244325.08, 1.0)); + } - INFO("Required separation: " << required_sep << " rad"); - INFO("Required separation (deg): " << required_sep * 180.0 / M_PI << "°"); + SECTION("Higher to lower - negative wait time becomes next cycle") { + double angular_separation = 0.0; + double next_wait = calculate_next_hohmann_wait_time(r_higher, r_target, angular_separation, earth->mass, TIME_STEP); - // Calculate wait time - double wait_time = calculate_wait_time_for_hohmann(r1, r2, current_sep, earth->mass); + INFO("Angular separation: " << angular_separation << " rad"); + INFO("Next wait time: " << next_wait << " s"); + INFO("Next wait time: " << next_wait / 3600.0 << " h"); - INFO("Wait time: " << wait_time << " s"); + REQUIRE(next_wait >= TIME_STEP); + REQUIRE_THAT(next_wait, WithinAbs(253510.25, 1.0)); + } - REQUIRE_THAT(wait_time, WithinAbs(-60062.651728, 0.1)); + SECTION("Higher to lower - positive wait time (no adjustment needed)") { + double angular_separation = 0.3; + double next_wait = calculate_next_hohmann_wait_time(r_higher, r_target, angular_separation, earth->mass, TIME_STEP); - // Verify orbits are circular - REQUIRE_THAT(chaser->orbit.eccentricity, WithinAbs(0.0, 0.001)); - REQUIRE_THAT(target->orbit.eccentricity, WithinAbs(0.0, 0.001)); + INFO("Angular separation: " << angular_separation << " rad"); + INFO("Next wait time: " << next_wait << " s"); + + REQUIRE(next_wait >= TIME_STEP); + REQUIRE_THAT(next_wait, WithinAbs(10757.30, 1.0)); } - SECTION("Calculate Hohmann transfer parameters") { + SECTION("At required separation - forced to next cycle by min_wait") { + double req_sep = calculate_required_separation_for_hohmann(r_lower, r_target, earth->mass); + double next_wait = calculate_next_hohmann_wait_time(r_lower, r_target, req_sep, earth->mass, TIME_STEP); + + INFO("Required separation: " << req_sep << " rad"); + INFO("Next wait time: " << next_wait << " s"); + INFO("Next wait time: " << next_wait / 3600.0 << " h"); + + REQUIRE(next_wait >= TIME_STEP); + REQUIRE_THAT(next_wait, WithinAbs(245683.24, 1.0)); + } + + SECTION("Force skip to next cycle with large min_wait") { + double angular_separation = 0.5; + double min_wait = 30000.0; + double next_wait = calculate_next_hohmann_wait_time(r_lower, r_target, angular_separation, earth->mass, min_wait); + + INFO("Angular separation: " << angular_separation << " rad"); + INFO("Min wait: " << min_wait << " s"); + INFO("Next wait time: " << next_wait << " s"); + + REQUIRE(next_wait >= min_wait); + REQUIRE_THAT(next_wait, WithinAbs(224774.23, 1.0)); + } + + SECTION("Zero min_wait allows immediate transfer") { + double angular_separation = 0.3; + double next_wait = calculate_next_hohmann_wait_time(r_higher, r_target, angular_separation, earth->mass, 0.0); + + INFO("Angular separation: " << angular_separation << " rad"); + INFO("Next wait time: " << next_wait << " s"); + + REQUIRE(next_wait >= 0.0); + REQUIRE_THAT(next_wait, WithinAbs(10757.30, 1.0)); + } + + destroy_simulation(sim); +} + +SCENARIO("Hohmann transfer rendezvous with validation", "[rendezvous_hohmann][integration]") { + const double TIME_STEP = 0.1; + + SimulationState* sim = create_simulation(3, 5, 10, TIME_STEP); + REQUIRE(load_system_config(sim, "tests/test_rendezvous_hohmann.toml")); + + int target_idx = find_spacecraft_by_name(sim, "Target_Satellite"); + int chaser_lower_idx = find_spacecraft_by_name(sim, "Chaser_Lower"); + REQUIRE(target_idx >= 0); + REQUIRE(chaser_lower_idx >= 0); + + Spacecraft* target = &sim->spacecraft[target_idx]; + Spacecraft* chaser = &sim->spacecraft[chaser_lower_idx]; + CelestialBody* earth = &sim->bodies[0]; + + initialize_orbital_objects(sim); + + SECTION("Execute validated Hohmann transfer and verify rendezvous") { double r1 = vec3_magnitude(chaser->local_position); double r2 = vec3_magnitude(target->local_position); - // Use existing calculate_hohmann_transfer from maneuver.h - HohmannTransfer hohmann = calculate_hohmann_transfer(r1, r2, earth->mass); + INFO("\n=== HOHMANN TRANSFER RENDZVOUS TEST ==="); + INFO("Initial state:"); + INFO(" Chaser: r=" << r1 << " m, nu=" << chaser->orbit.true_anomaly << " rad"); + INFO(" Target: r=" << r2 << " m, nu=" << target->orbit.true_anomaly << " rad"); - INFO("First burn delta-v: " << hohmann.dv1 << " m/s"); - INFO("Second burn delta-v: " << hohmann.dv2 << " m/s"); - INFO("Transfer time: " << hohmann.transfer_time << " s"); - INFO("Target true anomaly: " << hohmann.true_anomaly_2 << " rad"); + // Calculate angular separation + double angular_separation = chaser->orbit.true_anomaly - target->orbit.true_anomaly; + while (angular_separation > M_PI) angular_separation -= 2.0 * M_PI; + while (angular_separation < -M_PI) angular_separation += 2.0 * M_PI; + INFO(" Angular separation: " << angular_separation << " rad"); - REQUIRE_THAT(hohmann.dv1, WithinAbs(28.699077, 0.01)); - REQUIRE_THAT(hohmann.dv2, WithinAbs(28.592521, 0.01)); - REQUIRE_THAT(hohmann.transfer_time, WithinAbs(2741.813778, 0.1)); + // Validate parameters before proceeding + REQUIRE(validate_hohmann_transfer_parameters(r1, r2, earth->mass)); + INFO(" Validation passed"); + + // Calculate Hohmann transfer parameters + HohmannTransfer hohmann = calculate_hohmann_transfer(r1, r2, earth->mass); + double wait_time = calculate_next_hohmann_wait_time(r1, r2, angular_separation, earth->mass, TIME_STEP); + double arrival_time = wait_time + hohmann.transfer_time; + + INFO("\nTransfer parameters:"); + INFO(" Transfer time: " << hohmann.transfer_time << " s"); + INFO(" DV1 (departure): " << hohmann.dv1 << " m/s"); + INFO(" DV2 (arrival): " << hohmann.dv2 << " m/s"); + INFO(" Wait time: " << wait_time << " s"); + INFO(" Arrival time: " << arrival_time << " s"); + + // Create departure maneuver + Maneuver departure = create_maneuver( + "Departure_Burn", + chaser_lower_idx, + BURN_PROGRADE, + hohmann.dv1, + TRIGGER_TIME, + wait_time + ); + int dep_idx = add_maneuver_to_simulation(sim, &departure); + REQUIRE(dep_idx >= 0); + + // Create arrival maneuver + Maneuver arrival = create_maneuver( + "Circularization_Burn", + chaser_lower_idx, + BURN_PROGRADE, + hohmann.dv2, + TRIGGER_TIME, + arrival_time + ); + int arr_idx = add_maneuver_to_simulation(sim, &arrival); + REQUIRE(arr_idx >= 0); + + // Dump initial state + dump_simulation_state(sim, "INITIAL STATE"); + + // Run simulation + double sim_time = 0.0; + const double DT = 1.0; + const int MAX_STEPS = 700000; + bool transfer_complete = false; + + for (int i = 0; i < MAX_STEPS; i++) { + update_simulation(sim); + sim_time += DT; + + // Dump state at key milestones + if (i == 0) { + dump_simulation_state(sim, "T=0 (initial)"); + } + if (i == int(wait_time / DT)) { + dump_simulation_state(sim, "JUST BEFORE DEPARTURE"); + } + if (i == int(wait_time / DT) + 1) { + dump_simulation_state(sim, "AFTER DEPARTURE BURN"); + } + if (i == int(arrival_time / DT)) { + dump_simulation_state(sim, "JUST BEFORE ARRIVAL"); + } + if (sim->maneuvers[arr_idx].executed && !transfer_complete) { + dump_simulation_state(sim, "AFTER ARRIVAL BURN"); + transfer_complete = true; + break; + } + } + + INFO("\n=== FINAL STATE ==="); + INFO("Final time: " << sim_time << " s"); + INFO("Departure executed: " << sim->maneuvers[dep_idx].executed); + INFO("Arrival executed: " << sim->maneuvers[arr_idx].executed); + dump_simulation_state(sim, "FINAL STATE"); + + // Verify maneuvers executed + REQUIRE(sim->maneuvers[dep_idx].executed); + REQUIRE(sim->maneuvers[arr_idx].executed); + + // Verify rendezvous quality + double final_radius = vec3_magnitude(chaser->local_position); + double radius_error = fabs(final_radius - r2); + INFO("\nVerification:"); + INFO(" Radius error: " << radius_error << " m"); + INFO(" Chaser eccentricity: " << chaser->orbit.eccentricity); + + Vec3 chaser_vel = chaser->local_velocity; + Vec3 target_vel = target->local_velocity; + double chaser_speed = vec3_magnitude(chaser_vel); + double target_speed = vec3_magnitude(target_vel); + Vec3 separation = vec3_sub(chaser->local_position, target->local_position); + double separation_distance = vec3_magnitude(separation); + double relative_velocity = vec3_magnitude(vec3_sub(chaser_vel, target_vel)); + + INFO(" Chaser speed: " << chaser_speed << " m/s"); + INFO(" Target speed: " << target_speed << " m/s"); + INFO(" Separation: " << separation_distance << " m"); + INFO(" Relative velocity: " << relative_velocity << " m/s"); + + REQUIRE_THAT(radius_error, WithinAbs(10.0, 10.0)); + REQUIRE_THAT(chaser->orbit.eccentricity, WithinAbs(0.0, 0.001)); + REQUIRE_THAT(chaser_speed, WithinAbs(target_speed, 1.0)); + REQUIRE_THAT(separation_distance, WithinAbs(0.0, 100.0)); } destroy_simulation(sim);