From 7b9a557662f751ee57bb9aaabff516528bb7d8ca Mon Sep 17 00:00:00 2001 From: cinnaboot Date: Wed, 22 Apr 2026 13:01:24 -0400 Subject: [PATCH] use interpolated maneuvers with time trigger this still has a bug, but we're committing here along with the testing changes before attempt to fix --- src/maneuver.cpp | 30 ++++++++++++++++++++++-- src/simulation.cpp | 3 ++- src/test_utilities.cpp | 48 ++++++++++++++++++++++++++------------- src/test_utilities.h | 7 ++++-- tests/test_rendezvous.cpp | 37 +++++++++++++++++++++++++++--- 5 files changed, 101 insertions(+), 24 deletions(-) diff --git a/src/maneuver.cpp b/src/maneuver.cpp index 20a66ff..9ec426e 100644 --- a/src/maneuver.cpp +++ b/src/maneuver.cpp @@ -115,8 +115,34 @@ OrbitalElements preview_burn_result(const Spacecraft* craft, BurnDirection direc // TODO: add parabolic (Barker's equation) and hyperbolic branches. bool check_maneuver_trigger(Maneuver* maneuver, Spacecraft* craft, SimulationState* sim) { switch (maneuver->trigger_type) { - case TRIGGER_TIME: - return sim->time >= maneuver->trigger_value; + case TRIGGER_TIME: { + // Fire at the step that contains the trigger time. + // The orbit state is at sim->time (start of current step). + // We propagate forward to trigger_value, burn, then propagate + // the remaining time to reach sim->time + sim->dt. + if (sim->time > maneuver->trigger_value) { + // Trigger is before the start of this step — clamp to 0 + // (should have fired in an earlier step; fire immediately) + maneuver->scheduled_dt = 0.0; + return true; + } + if (sim->time + sim->dt <= maneuver->trigger_value) { + return false; + } + + double dt_to_burn = maneuver->trigger_value - sim->time; + + // Clamp to valid range [0, sim->dt] + if (dt_to_burn < 0.0) { + dt_to_burn = 0.0; + } + if (dt_to_burn > sim->dt) { + dt_to_burn = sim->dt; + } + + maneuver->scheduled_dt = dt_to_burn; + return true; + } case TRIGGER_TRUE_ANOMALY: { if (craft->parent_index < 0 || craft->parent_index >= sim->body_count) { diff --git a/src/simulation.cpp b/src/simulation.cpp index 96046b8..146cb96 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -329,7 +329,8 @@ void update_spacecraft_physics(SimulationState* sim) { craft->orbit = propagate_orbital_elements(craft->orbit, burn_dt, parent->mass); orbital_elements_to_cartesian(craft->orbit, parent->mass, &craft->local_position, &craft->local_velocity); - execute_maneuver(fired_maneuver, craft, sim, sim->time + burn_dt); + double burn_time = sim->time + burn_dt; + execute_maneuver(fired_maneuver, craft, sim, burn_time); double remaining_dt = sim->dt - burn_dt; craft->orbit = propagate_orbital_elements(craft->orbit, remaining_dt, parent->mass); diff --git a/src/test_utilities.cpp b/src/test_utilities.cpp index 2df9181..edd73ad 100644 --- a/src/test_utilities.cpp +++ b/src/test_utilities.cpp @@ -166,16 +166,23 @@ bool compare_vec3(Vec3 a, Vec3 b, double tolerance) { fabs(a.z - b.z) <= tolerance; } -void dump_simulation_state(SimulationState* sim, const char* label) { - printf("\n=== %s (t=%.0f s) ===\n", label, sim->time); +int dump_simulation_state(SimulationState* sim, const char* label, + char* buffer, int buffer_size) { + int offset = 0; - printf("Bodies (%d):\n", sim->body_count); + offset += snprintf(buffer + offset, buffer_size - offset, + "\n=== %s (t=%.0f s) ===\n", label, sim->time); + + offset += snprintf(buffer + offset, buffer_size - offset, + "Bodies (%d):\n", sim->body_count); for (int i = 0; i < sim->body_count; i++) { - printf(" [%d] %s: mass=%.2e kg\n", - i, sim->bodies[i].name, sim->bodies[i].mass); + offset += snprintf(buffer + offset, buffer_size - offset, + " [%d] %s: mass=%.2e kg\n", + i, sim->bodies[i].name, sim->bodies[i].mass); } - printf("Spacecraft (%d):\n", sim->craft_count); + offset += snprintf(buffer + offset, buffer_size - offset, + "Spacecraft (%d):\n", sim->craft_count); for (int i = 0; i < sim->craft_count; i++) { Spacecraft* s = &sim->spacecraft[i]; double r = sqrt(s->local_position.x*s->local_position.x + @@ -184,19 +191,28 @@ void dump_simulation_state(SimulationState* sim, const char* label) { double v = sqrt(s->local_velocity.x*s->local_velocity.x + s->local_velocity.y*s->local_velocity.y + s->local_velocity.z*s->local_velocity.z); - printf(" [%d] %s: r=%.1f v=%.1f nu=%.5f a=%.1f e=%.6f\n", - i, s->name, r, v, - s->orbit.true_anomaly, s->orbit.semi_major_axis, s->orbit.eccentricity); - printf(" pos=(%.1f, %.1f, %.1f) vel=(%.1f, %.1f, %.1f)\n", - s->local_position.x, s->local_position.y, s->local_position.z, - s->local_velocity.x, s->local_velocity.y, s->local_velocity.z); + offset += snprintf(buffer + offset, buffer_size - offset, + " [%d] %s: r=%.1f v=%.1f nu=%.5f a=%.1f e=%.6f, omega=%.6f\n", + i, s->name, r, v, + s->orbit.true_anomaly, + s->orbit.semi_major_axis, + s->orbit.eccentricity, + s->orbit.argument_of_periapsis); + offset += snprintf(buffer + offset, buffer_size - offset, + " pos=(%.1f, %.1f, %.1f) vel=(%.1f, %.1f, %.1f)\n", + s->local_position.x, s->local_position.y, s->local_position.z, + s->local_velocity.x, s->local_velocity.y, s->local_velocity.z); } - printf("Maneuvers (%d):\n", sim->maneuver_count); + offset += snprintf(buffer + offset, buffer_size - offset, + "Maneuvers (%d):\n", sim->maneuver_count); for (int i = 0; i < sim->maneuver_count; i++) { Maneuver* m = &sim->maneuvers[i]; - printf(" [%d] %s: craft=%d dir=%d dv=%.4f trigger=%d val=%.2f exec=%d\n", - i, m->name, m->craft_index, m->direction, m->delta_v, - m->trigger_type, m->trigger_value, m->executed); + offset += snprintf(buffer + offset, buffer_size - offset, + " [%d] %s: craft=%d dir=%d dv=%.4f trigger=%d val=%.2f exec=%d\n", + i, m->name, m->craft_index, m->direction, m->delta_v, + m->trigger_type, m->trigger_value, m->executed); } + + return offset; } diff --git a/src/test_utilities.h b/src/test_utilities.h index 71bfcf0..d71087a 100644 --- a/src/test_utilities.h +++ b/src/test_utilities.h @@ -46,7 +46,10 @@ void destroy_orbit_tracker(OrbitTracker* tracker); bool compare_double(double a, double b, double tolerance); bool compare_vec3(Vec3 a, Vec3 b, double tolerance); -// Debug helper: dump simulation state to console -void dump_simulation_state(SimulationState* sim, const char* label); +// Write simulation state to a caller-allocated buffer. +// Returns number of characters written (excluding null terminator). +// Caller must ensure buffer is large enough. +int dump_simulation_state(SimulationState* sim, const char* label, + char* buffer, int buffer_size); #endif diff --git a/tests/test_rendezvous.cpp b/tests/test_rendezvous.cpp index 91e31f7..149c1ab 100644 --- a/tests/test_rendezvous.cpp +++ b/tests/test_rendezvous.cpp @@ -27,7 +27,9 @@ static int find_spacecraft_by_name(SimulationState* sim, const char* name) { return -1; } -// ── Test-only output helper ────────────────────────────────────────────────── +// ============================================================================ +// Test-only output helper +// ============================================================================ struct TestOutput { char buf[32768]; @@ -36,6 +38,34 @@ struct TestOutput { void dump_state(SimulationState* sim, const char* label) { int n = dump_simulation_state(sim, label, buf + offset, sizeof(buf) - offset); if (n > 0) offset += n; + + int target_idx = -1, chaser_idx = -1; + for (int i = 0; i < sim->craft_count; i++) { + if (strcmp(sim->spacecraft[i].name, "Target_Satellite") == 0) + target_idx = i; + if (strcmp(sim->spacecraft[i].name, "Chaser_Lower") == 0) + chaser_idx = i; + } + + if (target_idx >= 0 && chaser_idx >= 0) { + Vec3 target_pos = sim->spacecraft[target_idx].local_position; + Vec3 chaser_pos = sim->spacecraft[chaser_idx].local_position; + + double target_angle = atan2(target_pos.y, target_pos.x); + double chaser_angle = atan2(chaser_pos.y, chaser_pos.x); + double angular_sep = chaser_angle - target_angle; + while (angular_sep > M_PI) angular_sep -= 2.0 * M_PI; + while (angular_sep < -M_PI) angular_sep += 2.0 * M_PI; + + Vec3 diff = vec3_sub(chaser_pos, target_pos); + double sep_mag = vec3_magnitude(diff); + + n = snprintf(buf + offset, sizeof(buf) - offset, + " Angular separation (Chaser-Target): %.6f rad (%.4f deg)\n" + " Separation magnitude: %.2f m\n", + angular_sep, angular_sep * 180.0 / M_PI, sep_mag); + if (n > 0) offset += n; + } } }; @@ -535,7 +565,7 @@ SCENARIO("Hohmann transfer rendezvous with validation", "[rendezvous_hohmann][in if (i == 0) out.dump_state(sim, "T=0 (initial)"); if (i == static_cast(wait_time / sim->dt)) out.dump_state(sim, "JUST BEFORE DEPARTURE"); if (i == static_cast(wait_time / sim->dt) + 1) out.dump_state(sim, "AFTER DEPARTURE BURN"); - if (i == static_cast(arrival_time / sim->dt)) out.dump_state(sim, "JUST BEFORE ARRIVAL"); + if (i == static_cast(arrival_time / sim->dt) - 1) out.dump_state(sim, "JUST BEFORE ARRIVAL BURN"); if (sim->maneuvers[arr_idx].executed && !transfer_complete) { out.dump_state(sim, "AFTER ARRIVAL BURN"); transfer_complete = true; @@ -543,6 +573,8 @@ SCENARIO("Hohmann transfer rendezvous with validation", "[rendezvous_hohmann][in } } + INFO(out.buf); + // Verify rendezvous quality double final_radius = vec3_magnitude(chaser->local_position); double radius_error = fabs(final_radius - r2); @@ -566,7 +598,6 @@ SCENARIO("Hohmann transfer rendezvous with validation", "[rendezvous_hohmann][in INFO(" Target speed: " << target_speed << " m/s"); INFO(" Separation: " << separation_distance << " m"); INFO(" Relative velocity: " << relative_velocity << " m/s"); - INFO(out.buf); // Verify maneuvers executed REQUIRE(sim->maneuvers[dep_idx].executed);