Skip to content

ADR-028 — Outer planets + dwarf planets in /plan: scope and scaling

Status · Accepted Date · 2026-05-02 Closes · RFC-008 (Outer planets + dwarf planets) TA anchor · §components/lambert-worker · §components/plan-screen Related ADRs · ADR-008 (Lambert worker), ADR-022 (Lambert protocol), ADR-023 (mobile magnifier), ADR-026 (multi-destination v0.1.6 / 5 destinations)

Context

ADR-026 shipped 5 destinations in v0.1.6: Mercury, Venus, Mars, Jupiter, Saturn. Three outer planets (Uranus, Neptune) and Pluto plus all dwarf planets were explicitly deferred to v0.3.0. RFC-008 raised three open questions: Y-axis scaling for multi-decade transfers, gravity-assist modelling, and dwarf-planet inclusion. This ADR locks every variable so the v0.3.0 implementation has a single contract.

Decision

Destination scope (RFC §dwarf-planets — Option DW-B refined)

Four new destinations ship in v0.3.0: Uranus, Neptune, Pluto, Ceres. Total destination count rises from 5 → 9.

idHohmann transittof_range_daysmission_typesNotes
uranus~16.0 yr[3000, 6500] (8.2–17.8 yr)FLYBYVoyager 2 only flown
neptune~30.6 yr[10000, 20000] (27–55 yr)FLYBYVoyager 2 only flown
pluto~45.5 yr[12000, 22000] (33–60 yr)FLYBYNew Horizons only flown
ceres~1.3 yr (Hohmann-class)[120, 480] d (short-way Lambert window)LANDING, FLYBYDawn (orbit insertion); Y-axis days (tof_range_days[1] ≤ 730). Draft [800, 1800] d produced zero converged cells with the v0.1.6 short-way solver — see scripts/precompute-porkchops.ts.

LANDING availability for Ceres mirrors Mars/Venus/Mercury (per ADR-026's possibility-driven rule). All other v0.3.0 entries are FLYBY-only — surface landings on gas giants / ice giants / Pluto are not defined manoeuvres at this fidelity level.

Eris, Makemake, Haumea explicitly deferred. Their multi-century orbits don't read on the 112×100 grid and they have no flown missions — adding them would be educational noise.

Y-axis scaling (RFC §y-axis — Option YA-A)

Per-destination tof range expressed in years on the axis label when tof_range_days[1] > 730 (the v0.1.6 threshold). Year ticks are integer-valued (5y, 10y, 15y ...). Inner planets, Mars, and Ceres stay in days; gas giants, Uranus, Neptune, and Pluto flip to years.

The educational moment the v0.1.6 work landed for Saturn ("12-year cycle") extends naturally — Neptune now visibly takes "a generation" while a Mars launch window arrives every 26 months.

Logarithmic axis (Option YA-B) rejected — the porkchop's "cheapest cell" insight depends on linear time; log scaling would compress the Hohmann minimum into illegibility.

Gravity-assist scope (RFC §gravity-assist — Option GA-A)

Direct-Hohmann trajectories only. The pre-computed grids show what a direct launch costs; real outer-planet missions use planetary gravity assists which are out of scope.

Each outer-planet panel surfaces a one-line caveat banner (mirroring the v0.1.7 sparse-data caveat pattern):

Direct trajectory shown. Real outer-planet missions use planetary gravity assists — see Voyager 2, Galileo, Cassini, New Horizons in /missions for actual flight paths.

Option GA-C (Voyager/Galileo reference overlays) deferred to a possible v0.4. Its educational power is real but engineering scope (multi-leg Lambert with patched-conic approximations) belongs in its own RFC.

Schema additions

DestinationConstants in src/lib/lambert-grid.constants.ts gains an optional e field (eccentricity). Pluto sets e from small-bodies.json so arrival radius varies with true anomaly. Ceres omits e (circular at a) so the short-way Lambert porkchop grid converges; mission-arc destinationPos matches. All planets use circular a in this map (consistent with marsPos() and historical goldens).

ts
export interface DestinationConstants {
  id: DestinationId;
  a: number;           // semi-major axis, AU
  a0: number;          // mean longitude at epoch, rad
  meanMotionRadPerDay: number;
  e?: number;          // eccentricity (default 0 — circular approx)
}

DestinationId enum extends to include the 4 new ids.

Lambert convergence policy (RFC OQ #2)

For multi-decade transfers, Lambert's iterative solution can fail to converge or produce negative ∆v values. The solver already returns DV_FAILED for such cells; outer-planet grids may have higher failure rates than inner-planet grids.

scripts/precompute-porkchops.ts records the per-destination convergence statistic in the JSON's credit field:

Computed at build time via Lambert solver. Uranus: 84% convergence. Failed cells render as DV_FAILED.

UI behaviour: failed cells render as the existing dark-grey background; magnifier shows "—" for non-convergent cells.

/fly arc geometry for multi-year trajectories

The v0.1.6-introduced single-ellipse outboundArc() already handles outer destinations (positive eccentricity for outer, negative for inner). For Pluto (e=0.249), pass the eccentricity through destinationPos / porkchop arrival so the visible arc reads as the truly-elongated trajectory rather than a misleading circular curve. Ceres uses circular geometry in these paths (see §Schema additions).

Camera framing per cameraDistanceFor() from v0.1.10: Neptune at ~30 AU = ~2400 scene units; Pluto at ~40 AU = ~3200 units. Adjust the camR clamp upper bound from 900 → 4000.

Mission catalogue additions (in scope)

v0.3.0 also adds the 4 missions that justify these destinations:

  • Voyager 2 (1977) — Jupiter / Saturn / Uranus / Neptune flybys. Implemented: dest: NEPTUNE (final giant-planet encounter); overlay copy explains the full tour. Gravity assists are not modelled in porkchops or /fly heliocentric arcs.
  • Galileo (1989) — Jupiter orbiter. dest: JUPITER.
  • New Horizons (2006) — Pluto flyby (+ later Kuiper belt targets). dest: PLUTO; flight_data_quality may stay sparse until a dedicated ADR-027 population pass.
  • Dawn (2007) — Vesta + Ceres orbiter. dest: CERES (final orbital target); overlay mentions Vesta. A future optional itinerary[] (ADR-020 amendment) may split multi-body missions without changing v1 dest semantics.

Total mission catalogue: 32 → 36 (16 Mars + 16 Moon + 4 outer-system entries).

Future expansion: multi-leg missions may add itinerary[] or secondary_dest in a follow-on ADR — not required for the four shipped records.

URL contract (extends ADR-026)

/plan?dest={mercury|venus|mars|jupiter|saturn|uranus|neptune|pluto|ceres}&type={LANDING|FLYBY}
  • Default still dest=mars (no regression).
  • Invalid ?dest=eris (or any deferred dwarf) coerces to mars with a one-time console warning, not a UI error.

Rationale

The decision cluster around three principles already locked elsewhere:

  • PRD-002: "intuitive before explanation" — drives the linear (not log) Y-axis and the direct-trajectory-only choice. A user reading the Neptune porkchop should think "this takes a generation," not "I need to learn what a log axis means."
  • PA §promises — fail honestly — drives the gravity-assist caveat banner. We don't pretend the porkchop shows what real missions do; we tell the user.
  • ADR-016 (build-time assets) — drives the pre-computed grid pattern; same as v0.1.6.

The "drop dwarfs" rule (DW-B refined) is selective: Pluto + Ceres earn inclusion through New Horizons + Dawn. The remaining dwarfs (Eris/Makemake/Haumea) lack flown missions and have orbits that don't read at our format.

Alternatives considered

  • Option DW-C — all four dwarfs rejected because Eris (a=67 AU, e=0.43) doesn't compute a sensible Hohmann porkchop — its perihelion is at 38 AU and aphelion at 97 AU, so transfer parameters depend heavily on which orbital phase you target. Adding it would be visual noise.
  • Option YA-B — log-scale Y-axis rejected because porkchops teach "this is the cheapest date," and log-scale obscures the date axis for the user.
  • Option GA-B — two grids per outer destination (direct + Jupiter-assisted) rejected as scope-doubling for marginal teaching gain. The caveat banner + the v0.4 reference-trajectory option captures the teaching moment cleanly.
  • Skip Pluto entirely rejected — New Horizons is the most-Googled outer-system mission of the last decade; its absence would be glaring.

Consequences

Positive:

  • /plan covers 9 of the major solar-system destinations (8 planets + Ceres). Pluto's inclusion as a dwarf opens the door for an educational thread on the IAU 2006 redefinition.
  • Lambert worker's parameterisation extends naturally — same per-destination JSON contract as v0.1.6.
  • Y-axis scaling continues the pattern from v0.1.6 (Saturn already in years).
  • 4 new missions deepen the catalogue: Voyager 2, Galileo, New Horizons, Dawn — landmark missions that v0.1.x oddly omitted.

Negative:

  • ~880 KB of new pre-computed grid data (4 × ~220 KB; outer-planet grids are slightly larger because Lambert convergence stats compress less). Total now ~1.4 MB for the 9-destination grid set. Within budget but worth noting.
  • Multi-year transfer windows + Lambert convergence at extreme distances need careful testing. The DV_FAILED rate may be 15–20% for Neptune/Pluto vs <5% for inner planets. Document this honestly.
  • /fly camera-distance upper bound lifts from 900 → 4000. Some legacy mission entries may need camR re-tuning.
  • The gravity-assist caveat banner may frustrate STEM students looking for "the real trajectory." The follow-up RFC for reference overlays (Option GA-C) is already framed.

Implementation notes

Implementation lives in v0.3.0 milestone (issue #27 tracker). Slice plan:

  • 3.0a-1 — Schema + types. Extend DestinationConstants with optional e. New DestinationId enum entries. Update lambert-grid.constants.ts to include all 9 destinations.
  • 3.0a-2 — Pre-computed grids. scripts/precompute-porkchops.ts runs over the 4 new destinations. Verify Lambert convergence stats; record per-destination in JSON credit field.
  • 3.0a-3/plan UI. Destination dropdown extends to 9. LANDING/FLYBY toggle correctly disabled for outer planets + Pluto. Y-axis year ticks at 5y intervals.
  • 3.0a-4/fly arc geometry. Pass eccentricity through outboundArc() for Pluto. Adjust cameraDistanceFor() upper bound. Caveat banner under the FLY button.
  • 3.0a-5 — Mission catalogue. Add Voyager 2, Galileo, New Horizons, Dawn per ADR-020 + ADR-027 shapes; extended dest enum; 36 missions shipped. Full measured flight blocks may follow in a data-only pass.
  • 3.0a-6 — Tests + docs roll-up. Extend e2e to cover Neptune porkchop convergence, Pluto camera framing, Voyager 2 mission load. Tag v0.3.0.

The v0.4 deferred work (gravity-assist reference overlays) is tracked separately; this ADR doesn't open that thread.

Orrery — architecture documentation · MIT · No tracking