Browse Source
- Add scheduled_dt field to Maneuver struct for precise timing - Propagate spacecraft to exact trigger position before burn execution - Handle 2π→0 wraparound in trigger crossing detection - Add spacecraft tracking to prevent double-propagation in same frame - Fix test configs and tolerances for new behavior All 143 tests passing.main
8 changed files with 207 additions and 61 deletions
@ -0,0 +1,143 @@
|
||||
# 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) |
||||
Loading…
Reference in new issue