vibe coding an orbital mechanics simulation to try out claude code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

11 KiB

Hohmann Transfer Rendezvous - Burn Timing Quantization Analysis

Session Date

2026-04-19

Problem Statement

The Hohmann transfer rendezvous simulation was failing with ~1.3 km separation despite correct phasing calculations. Investigation revealed two issues:

  1. Burn timing quantization: Time-triggered burns execute at step boundaries, not exact trigger times
  2. cartesian_to_orbital_elements bug: Coplanar orbit omega calculation was incorrect

Root Cause Analysis

1. Burn Timing Quantization

How it works:

  • check_maneuver_trigger() for TRIGGER_TIME uses simple comparison: sim->time >= maneuver->trigger_value
  • When triggered, the burn executes at the first step where sim->time >= trigger_value
  • No sub-step interpolation for time triggers

Verified behavior (from test_maneuver_planning.cpp):

Trigger Time Step Boundary Actual Execution Delay
t=305.0 t=310.0 t=310.0 5.0s
t=300.0 t=300.0 t=300.0 0.0s
t=62807.0 t=62810.0 t=62810.0 3.0s

Impact on Hohmann transfer:

  • Arrival trigger: t=62804.47 (calculated precisely)
  • Step boundaries: ..., 62800, 62810, ...
  • Actual execution: t=62810 (5.53s late)
  • Position drift: ~5.53s × ~7672 m/s ≈ 42 km of orbital travel

2. cartesian_to_orbital_elements Bug

Location: src/orbital_mechanics.cpp, lines 300-320

Bug: For coplanar orbits (inclination < 0.01 rad), the function was setting omega = 0.0 instead of computing the longitude of periapsis.

Fix:

} else if (e > 1e-10) {
    // Coplanar or near-circular: use longitude of periapsis
    omega = atan2(e_vec.y, e_vec.x);
    if (omega < 0.0) {
        omega += 2.0 * M_PI;
    }
} else {
    omega = 0.0;
}

Impact: Without this fix, Hohmann separation goes from 8.75m → 3.22 million meters.

DT Reduction Results

TIME_STEP Separation Test Result
10.0 s 1,324 m Failed (>100m)
5.0 s 458 m Failed (>100m)
2.0 s 228 m Failed (>100m)
1.0 s 55 m Passed
0.5 s 69 m Failed (>100m)
0.1 s 8.75 m Passed

Note: Values measured from test_maneuver_planning.cpp DT sweep test (2026-04-20). The 0.5s value (69m) is higher than the original estimate (40m) due to the specific trigger offset within the step boundary for this scenario. The 2.0s value (228m) is also higher than the original estimate (150m). The 10.0s value (1324m) matches the original measurement exactly.

Key insight: DT reduction dramatically improves accuracy:

  • 24x improvement from 10s→1s
  • 6x more from 1s→0.1s

Test Results Summary

Test Category Before Fix After Fix Status
rendezvous (8 cases) 87 passed 107 passed All pass
maneuver_planning (6 cases) 3 passed 6 passed All pass
omega (2 cases) 1 failed 2 passed All pass
Total 156/160 pass 154/154 pass All pass

Notes:

  • The old rendezvous (CW guidance) module was removed entirely, eliminating 3 pre-existing test failures
  • test_maneuver_timing.cpp was merged into test_maneuver_planning.cpp
  • rendezvous_hohmann was renamed to rendezvous (CW module removed, only Hohmann remains)
  • All 154 remaining test cases pass (240,445 assertions)

Current Code Path (After 2026-04-26 Sub-step Interpolation)

The maneuver trigger check and execution are merged into update_spacecraft_physics(). Both trigger types now use sub-step interpolation:

// In update_spacecraft_physics(), per spacecraft:
check_maneuver_trigger(maneuver, craft, sim);
// → For TRIGGER_TIME: computes dt_to_burn = trigger_value - sim->time
// → For TRIGGER_TRUE_ANOMALY: computes dt_needed from mean anomaly delta
// → Both set scheduled_dt = dt_to_burn (0 to sim->dt)

if (maneuver_fired) {
    craft->orbit = propagate_orbital_elements(craft->orbit, burn_dt, ...);
    execute_maneuver(fired_maneuver, ...);
    craft->orbit = propagate_orbital_elements(craft->orbit, remaining_dt, ...);
} else {
    craft->orbit = propagate_orbital_elements(craft->orbit, sim->dt, ...);
}

For TRIGGER_TIME: burn_dt is the exact sub-step offset from step start to trigger time. The spacecraft propagates to the precise trigger position before the burn, then continues for sim->dt - burn_dt.

For TRIGGER_TRUE_ANOMALY: burn_dt is set to exact seconds-to-target by check_maneuver_trigger(), so the spacecraft propagates to the exact burn position before the burn executes.

Edge case: If sim->time > trigger_value (trigger passed in a previous step), scheduled_dt is clamped to 0 and the burn fires immediately at the current position.

Burn Timing Quantization — RESOLVED (2026-04-26)

Option A (Sub-step Interpolation) is now implemented for both TRIGGER_TIME and TRIGGER_TRUE_ANOMALY.

Implementation:

  • check_maneuver_trigger() computes dt_to_burn = trigger_value - sim->time for time triggers
  • update_spacecraft_physics() propagates to exact burn time, executes burn, propagates remainder
  • Quantization error is eliminated: burns execute at the precise trigger time

Edge case: When sim->time > trigger_value (trigger passed in a previous step), scheduled_dt is clamped to 0 and the burn fires immediately at the current position.

Remaining Options (No Longer Needed)

Option B: Snap Trigger Times to Step Boundaries

Approach: In calculate_next_hohmann_wait_time(), snap the calculated wait time to the nearest step boundary.

Changes needed:

  1. In calculate_next_hohmann_wait_time():
    • After calculating wait time, snap to step boundary: wait_time = ceil(wait_time / DT) * DT
    • This ensures the trigger aligns with a simulation step

Pros: Simple, minimal code changes Cons: Introduces systematic timing error, may affect phasing accuracy

Option C: Accept Quantization Error

Approach: Keep current behavior but set realistic thresholds based on DT.

Changes needed:

  1. Calculate expected quantization error: max_error = DT
  2. Set rendezvous threshold proportional to DT: threshold = 100 * DT (meters)
  3. Document the limitation

Pros: Simplest, no code changes Cons: Less accurate, threshold depends on DT choice

Strategy for Testing with Larger Time Steps

Goal

Understand the accuracy limitations of the simulation at realistic DT values (10s, 30s) to set appropriate rendezvous thresholds.

Test Plan

Phase 1: Baseline at Current DT (0.1s)

  • Already done: 8.75m separation at DT=0.1s

Phase 2: Systematic DT Sweep

Run the same Hohmann transfer test at increasing DT values:

DT Expected Steps Expected Separation
0.1s ~628,000 ~8.75 m
0.5s ~125,600 ~40 m (estimate)
1.0s ~62,800 ~55 m
2.0s ~31,400 ~100-200 m (estimate)
5.0s ~12,560 ~500 m (estimate)
10.0s ~6,280 ~1,324 m
30.0s ~2,093 ~4,000 m (estimate)

Method:

  1. Create a new test file tests/test_hohmann_dt_sweep.cpp
  2. Run the same Hohmann transfer scenario at each DT value
  3. Record: final separation, radius error, relative velocity
  4. Plot separation vs DT to determine the relationship

Phase 3: Quantization Impact Analysis

Test the effect of burn timing quantization specifically:

Scenario Trigger Offset Expected Delay
Exact boundary 0s 0s
5s after boundary 5s 5s
9s after boundary 9s 1s

Method:

  1. For each DT, run the Hohmann transfer multiple times with different trigger offsets
  2. Measure the variation in final separation
  3. Determine if quantization error dominates over integration error

Phase 4: Threshold Recommendation

Based on Phase 2 & 3 results, recommend:

  • Maximum DT for rendezvous operations
  • Separation threshold as a function of DT
  • Whether sub-step interpolation is necessary

Implementation Notes

  • Use calculate_next_hohmann_wait_time() with min_wait_time to control trigger timing
  • Keep all other parameters constant (initial conditions, maneuver DVs, etc.)
  • Use WithinAbs() with increasing margins to find the threshold that passes at each DT

Completed Work

Files Modified

  • src/orbital_mechanics.cpp - Fixed coplanar orbit omega calculation
  • src/rendezvous.cpp (renamed from rendezvous_hohmann.cpp) - Added 3 new functions (validate, relative period, next wait time)
  • src/rendezvous.h (renamed from rendezvous_hohmann.h) - Added function declarations
  • src/test_utilities.cpp - Added dump_simulation_state() helper
  • src/test_utilities.h - Added function declaration
  • tests/test_rendezvous.cpp (renamed from test_rendezvous_hohmann.cpp) - Updated integration test with DT=0.1
  • tests/test_rendezvous.toml (renamed from test_rendezvous_hohmann.toml) - Reverted to original values
  • tests/test_maneuver_planning.cpp - Added 3 burn timing quantization tests (merged from test_maneuver_timing.cpp), plus 3 DT sweep tests (2026-04-20)
  • tests/test_omega_debug.cpp - Updated to accept new coplanar omega behavior
  • Makefile - Updated object file references
  • src/simulation.cpp - Merged execute_pending_maneuvers() into update_spacecraft_physics() (2026-04-20)
  • src/simulation.h - Removed execute_pending_maneuvers() declaration (2026-04-20)

Files Removed

  • src/rendezvous.h (old CW module) - replaced by Hohmann-only rendezvous.h
  • src/rendezvous.cpp (old CW module) - replaced by Hohmann-only rendezvous.cpp
  • tests/test_rendezvous.cpp (old CW tests) - replaced by Hohmann-only test_rendezvous.cpp
  • tests/test_rendezvous.toml (old CW config) - replaced by Hohmann-only test_rendezvous.toml

Remaining Work

DT Sweep Tests (COMPLETED - 2026-04-20)

Measured in test_maneuver_planning.cpp via DT sweep: Hohmann transfer separation vs time step:

DT Expected Steps Measured Separation
0.1s ~628,000 8.75 m
0.5s ~125,600 69 m
1.0s ~62,800 55 m
2.0s ~31,400 228 m
5.0s ~12,560 458 m
10.0s ~6,280 1,324 m

The separation scales roughly linearly with DT for larger values, consistent with quantization error being the dominant factor (position error ≈ timing_error × velocity, where timing_error is uniformly distributed in [0, DT]).

Additional tests:

  • DT sweep: quantization error is bounded by DT — verifies error is always in [0, DT)
  • DT sweep: Hohmann arrival burn timing error — measures exact timing error at each DT

Threshold Recommendation

With sub-step interpolation implemented, burn timing quantization is eliminated. DT sweep results now reflect integration error rather than quantization error. Recommend:

  • Maximum DT for rendezvous operations based on integration accuracy
  • Separation threshold set by orbital dynamics, not quantization bounds
  • Sub-step interpolation is now active for both trigger types