Skip to content

RFC-007 — Multi-destination porkchop

Status · Decided · closed by ADR-026 (2026-04-29) TA anchor · §components/lambert-worker · §components/plan-screen Related PRD · PRD-002 (Mission Configurator) Related UXS · UXS-002 (Mission Configurator) Related ADRs · ADR-008 (Lambert worker), ADR-022 (Lambert protocol), ADR-023 (mobile magnifier), ADR-026 (this RFC's closure) Why this is an RFC · The current /plan screen hardcodes Earth → Mars trajectories. Extending to "Earth → any planet" with optional landing/flyby semantics is a genuine open question with three interlocking sub-decisions: (1) which destinations to support and how to handle gas giants where landing isn't physical; (2) how the landing-vs-flyby toggle changes the porkchop's ∆v computation and the FLY-this-mission semantics; (3) how the date ranges scale per destination since outer-planet transfers are years long. Each has multiple defensible options.

The question

The current porkchop plot answers one question: "if I want to go from Earth to Mars between 2026 and 2030, when's the cheapest launch window?" The user feedback: "can we extend porkchop to support Earth to any planet, and one selects what planet they want to go and is it a landing or flyby?"

This expansion changes three things simultaneously: the data model (per-destination Lambert grids), the UI (destination + mission-type selectors), and the physics (landing arrival ∆v vs flyby arrival ∆v differ substantially).

Use cases

  • STEM student wants to see why a Voyager-class flyby of Jupiter is dramatically cheaper than a Galileo-style orbiter (no orbit-insertion burn).
  • Curious learner wants to understand why the next Saturn launch window is "in 12 years" and the Mars window is "in 26 months" — by seeing both porkchops side by side or sequentially.
  • STEM student wants to verify that the C3 (departure energy) for a Mars Hohmann ≈ 8.6 km²/s² matches what they computed from textbook values.
  • All audiences: trying to plan a mission to Pluto reveals why New Horizons used a Jupiter gravity assist (no direct trajectory in the porkchop's transfer-time range).

Goals

  • Each major destination gets its own porkchop computed at build time or on demand.
  • Mission-type toggle (LANDING vs FLYBY) changes the ∆v calculation transparently — same Lambert solver, different arrival-leg cost.
  • The educational moment from PRD-002 (delta-v as felt reality) extends to outer-planet missions where the realisation is "this is unreachable with current launchers."
  • No regression for the existing Earth→Mars experience.

Constraints

  • ADR-008 locks the Lambert solver in a Web Worker. Adding destinations means more grids, not a different solver.
  • ADR-019 locks ajv schema validation on PR. New per-destination data files must validate.
  • Lambert solver runtime — Earth→Mars grid is ~200 ms on fast hardware, 1–3 s on slow. Gas-giant transfer-time ranges are 5–10× wider; naive expansion would make the grid intolerable on mobile.
  • Mobile magnifier (ADR-023) assumes a 112×100 grid. Per-destination grid resolutions need to keep this assumption valid or the magnifier needs adjusting.

Options

What destinations to support

Option D-A — All eight planets. Mercury, Venus, Mars, Jupiter, Saturn, Uranus, Neptune (skip Earth as origin). Full coverage at the cost of 8× the data and 8× compute. Mercury and Venus need careful handling: their porkchops are narrow because of the inner-planet phasing.

Option D-B — Inner + Jupiter + Saturn (5 destinations). Skip Uranus and Neptune as v1 of multi-destination — their transfer times (8–12 years) make porkchops practically unreadable at mobile-friendly grid resolution, and they're rarely targeted (Voyager 2 was the only mission). Keeps the data and compute scope manageable.

Option D-C — Mars + Venus + Jupiter (3 destinations). Most-targeted destinations only. Smallest scope; least educational coverage.

Landing vs flyby semantics

Option L-A — Toggle switches the displayed grid. Two grids per destination: landing.json (includes orbit-insertion ∆v) and flyby.json (Lambert solution only). Toggle swaps the active grid. Storage doubles per destination but compute is the same as today (each grid is one Lambert run with a different arrival-cost convention).

Option L-B — Toggle adds a delta to the displayed grid. One Lambert grid per destination (the flyby cost). A separately-stored per-destination "orbit insertion delta-v" gets added on the client when LANDING is toggled. Smaller storage; arithmetic on the client. Less precise — orbit insertion ∆v varies with arrival velocity.

Option L-C — Per-destination decision based on physical possibility. Mars/Venus/Mercury support both LANDING and FLYBY. Gas giants support only FLYBY (landing on Jupiter is undefined; orbit-insertion is its own ∆v). Jupiter LANDING resolves to "Europa orbit insertion" or similar (out of scope). Cleaner conceptually but more rules to encode.

Transfer-time ranges per destination

Option T-A — Fixed 80–520 day range (current). Works for Mars and inner planets. Fails for outer planets: Jupiter Hohmann takes ~860 days, Saturn ~6 years. Not viable.

Option T-B — Per-destination range, bigger for outer planets. Mars: 80–520 days. Jupiter: 400–1500 days. Saturn: 800–3000 days. Each per-destination JSON declares its range. Grid resolution stays 112×100 so the magnifier still works.

Option T-C — Auto-scaled range based on Hohmann transit. dep_range_days = [Hohmann × 0.5, Hohmann × 2.5]. Calculated from the destination's semi-major axis. Less hand-tuning; could miss interesting non-Hohmann transfers (gravity-assist windows).

Proposed approach

Combine D-B + L-C + T-B.

  • 5 destinations: Mercury, Venus, Mars, Jupiter, Saturn. Defers Uranus/Neptune to a v0.3 follow-up where the porkchop UX has been validated against multi-decade transfer windows.
  • LANDING + FLYBY for inner planets (Mercury, Venus, Mars). FLYBY only for Jupiter + Saturn — the toggle on those destinations is still rendered but disabled with an explanatory tooltip ("Landing on a gas giant is not a defined manoeuvre — see Galileo orbit insertion in /missions").
  • Per-destination transfer-time range hardcoded in each destination's JSON. Mars stays 80–520 days (no regression); outer planets get ranges chosen to span the Hohmann transfer ± 60% so launch-window structure is visible.

Data model:

static/data/porkchop/
  earth-to-mars.json       # existing, unchanged
  earth-to-mercury.json    # NEW
  earth-to-venus.json      # NEW
  earth-to-jupiter.json    # NEW
  earth-to-saturn.json     # NEW

Each JSON contains: destination (planet id), dep_range_days, arr_range_days (= dep + tof_range), tof_range_days, steps ([dep_steps, tof_steps]), mission_types (["LANDING", "FLYBY"] or ["FLYBY"]), orbit_insertion_dv_km_s (per type, used when LANDING is selected).

Computation: Lambert worker takes a destination id alongside the existing range params and looks up the destination's mu, a, T, L0 from static/data/planets.json. No new physics — same solver, parameterised over the destination.

UI: A <select> for destination above the porkchop, a radiogroup for LANDING / FLYBY beside it (mirroring /missions filter pills). Both filters write to the URL (?dest=jupiter&type=flyby) so deep-links carry the configuration.

Alternatives considered

  • Compute on demand from a single solver instance — same solver code; the worker is already async-friendly. Already proposed.
  • Per-destination separate Worker instances — overkill; one worker handles all destinations in series.
  • Skip per-destination data and compute fresh on every visit — porkchop computation is the most expensive operation in the app; pre-computing is consistent with ADR-016 (assets resolved at build time). Build-time pre-compute is the right call.
  • Drop the FLYBY toggle entirely — simpler UI but loses the educational moment about Jupiter-class missions where a flyby is the only currently-affordable option.

Trade-offs

  • 5 grids ≈ 5× data on disk. ~110 KB current → ~550 KB total. Acceptable; below the existing 5.6 MB image bundle.
  • Per-destination calibration required for LANDING ∆v values; needs sourcing from public NASA technical reports per destination. Some hand-tuning expected.
  • Mobile magnifier (ADR-023) keeps working since per-destination grids stay at 112×100 cells. The transfer-time axis scales differently per destination but the magnifier is in cell-space, not date-space.
  • Onboarding complexity — UXS-002's "blue cells are cheap" mental model still works, but users need to understand why Saturn's porkchop is mostly orange (because everything to Saturn is expensive). The porkchop educational overlay (added v0.1.2) needs an "outer planets" note.

Open questions — resolved 2026-04-29

  1. FLY button for outer-planet flybys — RESOLVED: render an outbound-only arc using the v0.1.2 per-mission arc helper (the path already supports Mars one-way missions). The /fly HUD reflects the destination + mission type. Free-return is reserved for the ORRERY DEMO scenario.
  2. Build-time vs runtime grids — RESOLVED: pre-computed at build time per ADR-016. Five JSONs live in static/data/porkchop/. Trade-off accepted: ~550 KB on-disk for instant first paint and full offline capability. Runtime computation would re-do work on every visit and would block the porkchop's first frame on slow devices.
  3. Y-axis units — RESOLVED: auto-switch to years when the destination's tof_range_days[1] > 730 (≥2 years). Mercury, Venus, Mars stay in days; Jupiter and Saturn show years. The threshold sits per-destination in the JSON file so tests can fix it. Tick labels render as integers ("1y", "2y", "3y") for legibility.
  4. C3-vs-tof axes — DROPPED: kept ∆v / dep-date axes for v0.2. C3 (characteristic energy = v_∞²) is the mission-planner convention but adds an axis the user has to learn before reading the plot. PRD-002's promise is "intuitive before explanation" (blue cheap, orange expensive). C3 axes belong in a v0.3+ "advanced view" toggle if the educational case emerges.
  5. Pluto + dwarf planets — DEFERRED to v0.3 alongside Uranus + Neptune. All four share the same problem (multi-decade transfer-time axes that overwhelm the 112×100 grid resolution + Lambert convergence concerns at extreme distances). The same RFC frame applies; resolution will pull in the v0.2 lessons learned.

Closing evidence

This RFC closes when:

  • 5 per-destination porkchop grids are pre-computed and committed
  • The destination + mission-type selectors are visible on /plan
  • Cell selection + FLY routing work for Jupiter and Saturn (the outer-planet edge cases)
  • A passing e2e covers the Mars regression + at least one outer-planet flyby
  • ADR-NNN locks the data model contract and selector UX

How this closes

One ADR locking:

  • The 5-destination scope decision (D-B)
  • The LANDING/FLYBY semantics (L-C)
  • The per-destination transfer-time range pattern (T-B)
  • The URL parameter contract (?dest=...&type=...)
  • The data file location + schema

Then PRD-002 + UXS-002 get a v0.2 extension section once the ADR lands, mirroring the slice-3 closure pattern.

Orrery — architecture documentation · MIT · No tracking