Midpoint Body Integration (#958)
Problem
Ships orbiting moons lose orbital energy and spiral inward. A ship at 50 km altitude around Io crashed within 20 minutes. The root cause is asymmetric body positions in the leapfrog integrator’s Strang splitting:
- First kick uses pre-update body positions (time T)
- Second kick uses post-update body positions (time T+dt)
- For fast-moving moons (Io at 17.3 km/s), the ~346m position difference at 50 Hz creates an asymmetric gravitational bias that systematically drains orbital energy
Solution
Replace asymmetric Strang splitting with midpoint body positions for both leapfrog kicks. Compute the average of pre-update (T) and post-update (T+dt) body positions and velocities, then use that midpoint for both the first and second kick.
This eliminates the asymmetry while maintaining O(dt^2) accuracy.
Design
Midpoint computation
In process_tick(), after celestial body N-body integration:
- For each system, iterate over
ref_by_system(T) andbodies_by_system(T+dt) in parallel - Compute midpoint position:
(ref.position + cur.position) * 0.5 - Compute midpoint velocity:
(ref.velocity + cur.velocity) * 0.5 - Store as
RefBodynamedtuples inmid_by_system
Ship integration
- Both leapfrog kicks use
mid_bodiesfor gravity and drag computation - Attitude control still uses
ref_bodies(pre-update) — consistent with ship’s pre-update position for Hill sphere lookups - Surface contact checks still use post-update
bodies— correct for collision detection at end-of-tick positions
Station/jumpgate integration
- Both leapfrog kicks use
mid_bodiesfor gravity and drag - Same symmetric treatment as ships
Backward compatibility
All new parameters default to None. When mid_bodies=None:
- Ship integration falls back to
ref_bodies(first kick) /bodies(second kick) — original Strang behavior - Station integration falls back to same Strang behavior
Files modified
services/game-engine/src/physics/simulation.py— midpoint computation inprocess_tick(),mid_bodiesparameter in_update_ship()services/game-engine/src/physics/nbody.py—mid_bodiesparameter inupdate_station()
Verification
- Orbital energy for ships around moons should oscillate (bounded) rather than monotonically decrease
- Existing physics tests pass (mid_bodies defaults to None)