Physics Engine — Integrator Policy (#1084)

Purpose

Single source of truth for which integrator runs where in Galaxy. Decided in #1084. Code that adds or replaces a physics integration loop must conform to this policy or update it via the spec-first workflow.

Why this matters

Galaxy has multiple distinct physics paths — the live game, the trajectory planner, the planner’s safety check, the visualization waypoint generator, the landing simulator, and several test fixtures. Different paths have used different integrators, sometimes accidentally:

  • The live game ship integrator uses Encke’s method (analytical Kepler reference + Velocity Verlet of the perturbation Δr) at sub-step ≤ 0.5 s, with adaptive sub-stepping to 0.02 s under high perturbation.
  • The body N-body integrator uses Velocity Verlet at sub-step ≤ 0.5 s.
  • The trajectory planner’s simulate_trajectory and the game-engine’s simulate_trajectory_waypoints use raw Velocity Verlet of the full ship motion (no Kepler reference) at internal sub-step ≤ 10 s, with stationary reference body and stationary perturbers.
  • The planner’s Python _SimState cost-evaluation loop uses raw Velocity Verlet in Python.
  • The landing/descent simulators use Velocity Verlet for short timescales.
  • The web client has dead Verlet code paths — comments imply it propagates, but the code snaps to server positions.

This produces concrete bugs:

  • Plans the live game cannot follow accurately (#1083 root cause was burn execution; #1085 root cause is the planner sim’s stationary perturbers and raw leapfrog producing different orbits than the live Encke ship loop)
  • Visualization waypoints that show false collisions (#1085 — viz periapsis −328 km vs live periapsis +626 km on the same schedule)
  • Confusion about whether “we use leapfrog” — Encke is a leapfrog of Δr, but only the perturbation, with the central force handled analytically.

Terminology

For this spec:

  • Velocity Verlet / Leapfrog / Symplectic Euler (kick-drift-kick) are used interchangeably. They are all second-order symplectic integrators with bounded energy error. Galaxy uses the kick-drift-kick form throughout.
  • Encke’s method = analytical Kepler propagation of a reference orbit
    • numerical integration of the perturbation Δr from that reference. The numerical integration of Δr uses Velocity Verlet. The benefit of Encke is not “no leapfrog” — it is “leapfrog only the small perturbation, get the large central-body force exactly via Kepler”.
  • Raw leapfrog = Velocity Verlet of the full ship state (no Kepler reference). Acceptable for body-on-body N-body where the central force is diffuse, problematic for ship-around-body where the central force dominates.
  • Body-relative coordinates = ship state expressed as a delta from a reference body’s position. Avoids precision loss from ICRF cancellation at ~10¹¹ m scales (#1014).

Policy

KEEP Velocity Verlet — these are correct as-is

Path File Why
Body N-body integrator services/game-engine/src/physics/nbody.py Symplectic, energy-bounded, validated for years. Raised to 0.5 s sub-step in #1061. Replacing it would risk regressions with no accuracy benefit at this timescale.
Station / jumpgate gravity-only step services/game-engine/src/physics/nbody.py, simulation.py::_update_orbital_object_encke Same as bodies. Stable.
Ship Encke perturbation step services/game-engine/physics_core/src/lib.rs::encke_substep_loop This is Encke’s method: the inner Velocity Verlet integrates only the small Δr perturbation around an interpolated reference body. The central force is exact (analytical via the reference body’s pre/post tick interpolation). Sub-step ≤ 0.5 s, adaptive down to 0.02 s under high perturbation.
Landing / descent sims services/game-engine/src/tick_engine/maneuver_landing.py Sub-minute timescales under high thrust. Drift over many orbits is irrelevant; Velocity Verlet is appropriate. Document the choice in a comment at the top of the file.
Test physics fixtures services/game-engine/tests/tick_engine/simulation/physics.py Tests should match what the live game does. They do.

REPLACE — these must move to Encke

Path File Replacement
Trajectory planner cost simulator services/game-engine/physics_core/src/lib.rs::simulate_trajectory Encke around the reference body, with linearly-interpolated body motion. Use the same encke_substep_loop building block.
Trajectory viz waypoint generator services/game-engine/physics_core/src/lib.rs::simulate_trajectory_waypoints Same — drop in the new Encke loop, then sample positions every waypoint_interval seconds.
Planner Python _SimState services/trajectory-planner/src/planner.py Replace with calls to the new Rust Encke fast path.
Cross-SOI planner Python sim services/game-engine/src/tick_engine/trajectory_planner.py::_SimState Same.

The replacement is not “use a higher-order integrator”. It is “use the same physics path the live game uses for ship integration”. The integrator is still Velocity Verlet of Δr — we just stop integrating the central force numerically and start propagating the bodies during the trajectory.

DELETE — dead code paths

Path File Action
Web client Verlet integration paths services/web-client/src/cockpitExtrapolation.js, mapStateUpdate.js, flightOverlays.js, tracers.js, orbital.js, mapView.js Multiple files contain // snap _clientPos to server position (Verlet disabled) comments next to code that previously did Verlet propagation but now just copies server values. Remove the dead branches and update the comments. The web client is always server-authoritative; it never extrapolates.

DO NOT introduce — out of scope

Integrator Why we are not adopting it
Runge-Kutta 4 / 5 / Dormand-Prince Higher truncation accuracy per step, but not symplectic — energy drifts unboundedly over many orbits. For multi-day game-time integration of stable orbits this is worse than Velocity Verlet, not better. We have no use case where the per-step accuracy gain outweighs the long-term energy drift cost.
Yoshida / higher-order symplectic Energy-conserving like Velocity Verlet but multiple force evaluations per step. Marginal accuracy gain at significant CPU cost. Not justified by current accuracy requirements.
Adaptive RK45 Mostly useful for stiff problems with rapidly changing forces. Not our case.

If a future feature genuinely needs sub-meter accuracy over many orbits and the existing path can’t deliver, revisit this section and ratify the addition through the spec-first workflow.

Architecture diagram

┌─────────────────────────────────────────────────────────────────────┐
│  LIVE GAME (game-engine tick loop)                                  │
│                                                                     │
│  Bodies ─────► Velocity Verlet (nbody.py)         sub_dt ≤ 0.5 s    │
│                                                                     │
│  Stations ──► Velocity Verlet (gravity-only)      sub_dt ≤ 0.5 s    │
│                                                                     │
│  Ships  ────► Encke (simulation.py)                                 │
│                  ├── Kepler reference body (analytical, exact)      │
│                  └── Velocity Verlet of Δr                          │
│                          (encke_substep_loop in Rust)               │
│                          sub_dt ≤ 0.5 s, adaptive to 0.02 s         │
└─────────────────────────────────────────────────────────────────────┘
                              │
                              │  same physics path
                              ▼
┌─────────────────────────────────────────────────────────────────────┐
│  PLANNER (trajectory-planner pods)                                  │
│                                                                     │
│  Cost evaluation, safety checks, viz waypoints all use:             │
│      Encke loop (Rust) over linearly-interpolated body positions    │
│      sampled at coarse intervals (e.g. 60 s body steps + finer      │
│      Encke sub-stepping for the ship).                              │
│                                                                     │
│  No Python _SimState. No raw leapfrog. No stationary perturbers.    │
└─────────────────────────────────────────────────────────────────────┘
                              │
                              │  identical schedule produces identical
                              │  trajectory in both paths
                              ▼
┌─────────────────────────────────────────────────────────────────────┐
│  CLIENT (web client)                                                │
│                                                                     │
│  No physics. Snap to server state. Render.                          │
└─────────────────────────────────────────────────────────────────────┘

Migration plan

This spec defers implementation to follow-up issues. Order of operations:

  1. #1085 — Replace simulate_trajectory_waypoints with the new Encke loop. Verify the viz waypoints agree with the planner’s safety check on the same schedule (same min_pe to within ~100 m).
  2. #1085 — Replace simulate_trajectory with the same Encke loop. Verify cost evaluations don’t regress on the existing rendezvous test matrix.
  3. #1085 — Replace planner _SimState (both files) with calls to the new Rust path.
  4. Cleanup PR — Remove dead Verlet branches in the web client. Update stale comments.
  5. #1086 — Implement the Deviation Monitor on top of the now-consistent physics paths. The monitor compares actual ship state to planner-side reference positions; both sides must use identical physics.

Open questions

None. This policy is ratified. Re-open via a new issue if a use case appears that requires deviation.

  • #1014 — Encke ICRF round-trip precision loss (motivated body-relative coords)
  • #1061 — Raised MAX_PHYSICS_DT to 0.5 s
  • #1066 — Stations / jumpgates moved to Encke
  • #1067 — Trajectory planner service
  • #1083 — Burn execution fix (closed)
  • #1085 — Viz waypoints disagree with safety check (depends on this spec)
  • #1086 — Deviation Monitor (depends on this spec + #1085)

Back to top

Galaxy — Kubernetes-based multiplayer space game

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