# Exact Position Burn Execution - Implementation Plan **Date:** 2026-02-21 ## Status: COMPLETED ✅ All 143 tests passing. ## Problem Statement True anomaly triggers correctly calculate when a crossing will occur, but burns execute at the **current** position, not the **trigger** position. ### Original Behavior ``` Frame N: spacecraft at nu=6.22 rad Trigger: dt_needed = 52.95s (< 60s dt) → fires Burn executes at nu=6.22 rad ❌ (should be at nu=0.0) ``` ### New Behavior ``` Frame N: spacecraft at nu=6.22 rad Trigger: dt_needed = 52.95s → fires, sets maneuver->scheduled_dt = 52.95 execute_pending_maneuvers(): - Propagates spacecraft by 52.95s → spacecraft now at nu=0.0 rad - Executes burn at exact periapsis ✓ - Propagates by (60 - 52.95) = 7.05s for remaining frame time ``` ### Test Failures | Test | Failure | Root Cause | |------|---------|------------| | Prograde burn at periapsis | Maneuver never fires | Config has `true_anomaly=0.1`, spacecraft moves AWAY from target | | Two periapsis burns | Radius mismatch | Burn fires 3.5° before periapsis | | Periapsis burn crossing | Radius off by 1,785m | Burn fires 3.5° before periapsis | | Burn location = new periapsis | Position mismatch | Test records position BEFORE burn | ## Solution Design Add a `scheduled_dt` field to `Maneuver` struct to communicate the exact propagation time needed from trigger check to execution. ### Modified Flow ``` Frame N: spacecraft at nu=6.22 rad Trigger: dt_needed = 52.95s → fires, sets maneuver->scheduled_dt = 52.95 execute_pending_maneuvers(): - Propagates spacecraft by 52.95s → spacecraft now at nu=0.0 rad - Executes burn at exact periapsis ✓ - Propagates by (60 - 52.95) = 7.05s for remaining frame time ``` ## Files to Modify ### 1. src/maneuver.h - Add field to Maneuver struct ```cpp struct Maneuver { char name[64]; int craft_index; BurnDirection direction; double delta_v; TriggerType trigger_type; double trigger_value; double scheduled_dt; // NEW: Time to propagate before executing burn bool executed; double executed_time; }; ``` ### 2. src/maneuver.cpp - Modify check_maneuver_trigger() - When immediate trigger (`current_diff < 0.01`): set `scheduled_dt = 0.0` - When crossing detected with `dt_needed < sim->dt`: set `scheduled_dt = dt_needed` - Return value unchanged ### 3. src/simulation.cpp - Modify execute_pending_maneuvers() ```cpp void execute_pending_maneuvers(SimulationState* sim) { for (int i = 0; i < sim->maneuver_count; i++) { Maneuver* maneuver = &sim->maneuvers[i]; if (maneuver->executed) continue; if (maneuver->craft_index < 0 || maneuver->craft_index >= sim->craft_count) continue; Spacecraft* craft = &sim->spacecraft[maneuver->craft_index]; if (check_maneuver_trigger(maneuver, craft, sim)) { CelestialBody* parent = &sim->bodies[craft->parent_index]; // Propagate to exact trigger position if (maneuver->scheduled_dt > 0.0) { craft->orbit = propagate_orbital_elements(craft->orbit, maneuver->scheduled_dt, parent->mass); orbital_elements_to_cartesian(craft->orbit, parent->mass, &craft->local_position, &craft->local_velocity); } execute_maneuver(maneuver, craft, sim, sim->time + maneuver->scheduled_dt); // Propagate remaining frame time double remaining_dt = sim->dt - maneuver->scheduled_dt; if (remaining_dt > 0.0) { craft->orbit = propagate_orbital_elements(craft->orbit, remaining_dt, parent->mass); orbital_elements_to_cartesian(craft->orbit, parent->mass, &craft->local_position, &craft->local_velocity); } maneuver->scheduled_dt = 0.0; // Reset for safety } } } ``` ### 4. src/maneuver.cpp - create_maneuver() Initialize `scheduled_dt = 0.0` ### 5. src/config_loader.cpp Initialize `scheduled_dt = 0.0` when loading maneuvers from TOML. ## Design Decisions ### Propagation Strategy (Option 3) All propagation for maneuvering spacecraft happens in `execute_pending_maneuvers()`. The `update_spacecraft_physics()` will still run but the velocity deviation check will reconstruct elements if needed. This keeps maneuver logic contained in one place. ### Edge Cases Handled 1. **scheduled_dt > sim->dt**: Defensive check added (should never happen per trigger logic) 2. **Two burns in same frame**: Both can fire if both scheduled_dt values fit within dt 3. **scheduled_dt = 0**: No pre-propagation, burn executes at current position (immediate trigger case) ## Cleanup After tests pass, remove DEBUG INFO printf statements from `src/maneuver.cpp` (lines ~153-212). ## Expected Test Results - Burns execute at exact periapsis (within floating-point precision) - All 4 failing tests should pass with current 1000m tolerance - "Prograde burn at periapsis" may need test config adjustment (starts at true_anomaly=0.1)