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:

  1. For each system, iterate over ref_by_system (T) and bodies_by_system (T+dt) in parallel
  2. Compute midpoint position: (ref.position + cur.position) * 0.5
  3. Compute midpoint velocity: (ref.velocity + cur.velocity) * 0.5
  4. Store as RefBody namedtuples in mid_by_system

Ship integration

  • Both leapfrog kicks use mid_bodies for 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_bodies for 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 in process_tick(), mid_bodies parameter in _update_ship()
  • services/game-engine/src/physics/nbody.pymid_bodies parameter in update_station()

Verification

  • Orbital energy for ships around moons should oscillate (bounded) rather than monotonically decrease
  • Existing physics tests pass (mid_bodies defaults to None)

Back to top

Galaxy — Kubernetes-based multiplayer space game

This site uses Just the Docs, a documentation theme for Jekyll.