Skip to content

ADR-086 — Earth→(planetary moon) multi-leg porkchops on /plan

Status · Accepted · 2026-08-28 · Closes #308's deferred "planetocentric moons of the giants" scope · builds on ADR-085 (geocentric Earth→Moon) · ADR-026/028 (the heliocentric porkchop set)

Gating sentence. /plan gains nine planetary moons as destinations — Jupiter's Galileans (Io, Europa, Ganymede, Callisto), Saturn's Titan + Enceladus, Neptune's Triton, and Mars' Phobos + Deimos — each shaded by the total ∆v of a multi-leg patched-conic mission: the heliocentric Earth→host-planet transfer's departure burn plus a moon-orbit insertion, where the host arrival v∞ is carried down the host's gravity well to the moon and captured (µ_host then µ_moon). The nine grids ride their host planet's departure/TOF axes; the heliocentric leg reuses solveLambert (µ_Sun) unchanged, so every existing planet/dwarf grid stays byte-identical.

Context

ADR-085 modelled the Moon as a single geocentric Lambert arc (Earth→Moon, µ_Earth) because the Moon orbits Earth and you launch from Earth. A moon of a giant planet is different: you reach it via its host planet, so the honest model is a chain, not one arc. #308 named "Moon + moons"; the moons were deferred in ADR-085 as "three-body sequencing." This closes that.

Decision

Two-leg patched-conic (src/lib/moon-transfer.ts, interplanetaryMoonDv):

  1. Heliocentric Earth→host — the existing porkchop leg (solveLambert, µ_Sun). Departure ∆v = |v1 − v_Earth|. The spacecraft is not captured at the host; its hyperbolic arrival v∞ = |v_host − v2| is carried into leg 2 (no separate host-capture burn — that would double-count the energy that reaches the moon).
  2. Moon-orbit insertion — fall down the host well from v∞ to the moon's orbital radius (v_at_moon = √(v∞² + 2µ_host/r_moon)), take the excess relative to the moon (v∞_moon = |v_at_moon − v_moon_circ|, tangential approximation), capture into a low circular moon orbit (MOI = √(v∞_moon² + 2µ_moon/r_lo) − √(µ_moon/r_lo)).

Cell ∆v = departure + MOI. The /plan readout breaks it out as DEP x + MOI y, mirroring the Moon's TLI + LOI.

Data. Moon orbital radius is derived from its sidereal period (the value explore-scene.ts already ships for the 3D renderer) via Kepler's third law with µ_host — one source of truth, so this can't drift from the renderer. µ_host / µ_moon / mean radii are JPL/NASA reference constants (src/lib/moon-transfer.constants.ts). Low-orbit radius = mean radius + 100 km.

Type system. A new MoonMissionDestId (the 9 ids) joins PlanDestinationId alongside DestinationId and the geo 'moon'; isMoonMissionDest() gates the split readout. The moons are not heliocentric DestinationIds (no single Lambert arc) and not geocentric (not Earth-orbiting) — a third category.

Honesty bar (validation)

A 2D two-body teaching estimate, the giant-planet analogue of ADR-085 — it ships only if the numbers match reality:

  • Kepler-derived orbital radii match JPL to ≤0.1% for all nine (Europa 670 965 vs 671 100 km).
  • Arrival v∞ at Jupiter ~5.6 km/s and v∞ at Europa ~6.5 km/s match the Galileo / Europa-Clipper flyby band.
  • Direct Europa EOI ~5 km/s matches the "direct moon capture is brutal at the giants" literature — this is the honest upper bound; real missions use gravity-assist tours to slash the moon leg, and the caption + credit say so.
  • The Galilean gradient (Io > Europa > Ganymede > Callisto — deeper well = costlier) and the Mars-moon cheapness (Phobos/Deimos ~4.8 km/s, shallow well) fall out correctly.

Unit tests (moon-transfer.test.ts) assert the Kepler match, the Europa/Jupiter bands, the Galilean gradient, and a feasible positive departure+MOI split for every moon.

Consequences

  • Positive — closes #308 fully; the nine moons reuse the existing porkchop UI (heatmap, magnifier, sandbox) with only a split-readout addition; no change to any heliocentric grid.
  • Neutral / accepted — the moon grids inherit their host planet's feasibility profile exactly (the heliocentric leg is low-branch only, like the existing planet grids), so outer-moon porkchops (Triton 9%, matching Neptune) are mostly the fast side of the TOF band. This is consistent + honest, not a regression; a fuller outer-planet feasibility pass would be a change to all heliocentric grids, out of scope here.
  • Accepted — direct-capture ∆v ignores gravity-assist tours; the caption frames it as the representative upper bound.

Alternatives considered

  • Full 3-leg with an explicit host-orbit-insertion (JOI) then a Hohmann to the moon — rejected for v1: more realistic for a real orbiter but adds a mission-design choice (capture-orbit apoapsis) with little pedagogical gain over the direct-capture upper bound.
  • Planetocentric-only leg (host→moon, ignoring the interplanetary journey) — rejected: it hides that the Earth→host transfer is the dominant cost, which is the whole point.
  • Hard-coded moon orbital radii — rejected: derive from the shipped period so the table can't silently drift from the 3D scene.

0.8.1 physics-audit amendment (B1 / S1 / S2)

An independent physics review (2026-08-28) found three defects in the shared Lambert/porkchop conic that this ADR and ADR-085/026/028 all build on. All are fixed; the affected grids were recomputed. The model shape and honesty bar are unchanged — these are correctness fixes, not scope changes.

  • B1 / S1 — arrival v∞ is a vector, not a scalar. Both the heliocentric computeDv (S1) and this ADR's interplanetaryMoonDv (B1) priced the arrival hyperbolic excess as |v_dest − v2|. That scalar collapses on faster-than-Hohmann transfers — the speeds match while the velocity vectors diverge — under-pricing the fast half of every grid and inverting the TOF gradient (it bites hardest here, where v∞ is squared into the host well-drop). solveLambert now returns the transfer's semi-latus rectum p; callers split the endpoint velocity into tangential √(µp)/r + radial and take the true vector v∞. (Fast Europa arrival v∞ went 0.24 → 13.4 km/s.)
  • S2 — perihelion at ϖ. a0 + n·day is the mean longitude L, but the conic used it directly as the true anomaly, pinning every body's perihelion at ecliptic longitude 0. The ephemerides (planets.json / small-bodies.json) now carry the longitude of perihelion ϖ (varpi); the conic phases by mean anomaly M = L − ϖ, solves Kepler for the eccentric anomaly, and places the body at ν + ϖ with r = a(1 − e·cos E). Circular bodies (e = 0, ϖ inert) stay byte-identical, so only the eccentric-model grids (Pluto, Bennu) changed; Pluto's 2026 heliocentric distance now reads ~35.6 AU, not the mis-phased value. The /fly render conic (mission-arc.destinationPos) shares the same fix, so the porkchop and the flight scene agree on where a body is.

Orrery — architecture documentation · MIT · No tracking