Skip to content

Adding a new mission — runbook

The single prescribed path for bringing a new mission's content (text · links · flight · images · translations) into Orrery. Analogous to scripts/IMAGE-PIPELINE.md "Adding a new gallery image" and the AGENTS.md "Adding a new flyby body" checklist. Read this before hand-authoring mission JSON.

Worked example throughout: Artemis 4 (artemis4, a planned NASA Moon mission), added 2026-07-10.

The model — mostly hand-authored JSON, gated by validate-data

There is no add-mission script — a mission is static JSON validated by fail-closed gates. npm run validate-data is your checklist: it names every missing/asymmetric touchpoint (often with the exact fix command). Author → validate-data → fix what it flags → repeat until green. Multi-device is automatic: static/data is bundled into the iOS/Android apps and renders on mobile + TV through the same components — no per-device step.

Design intent — derive integrity + source images, but never fabricate content

The process should do more of the mechanical work for you, along one clear line — derive/source where the answer is unambiguous; block-and-prompt where it's real content that needs authoring + approval:

  • Integrity is auto-derived (do it, don't ask). Fleet-ref symmetry (linked_missions) is pure derivation from fleet_refs — the add flow should run migrate-fleet-linked-missions.ts for you, not make you chase a validate-data failure. Same for any manifest that's a function of disk (gallery counts).
  • Images are auto-sourced to staging (attempt, then human-approve). The add flow should kick off the agency-first fetch for the hero + gallery into _staging/ for review at /dev/staging — "to some extent where possible" (rich for flown missions, agency concept art for future ones like Artemis 4). It never auto-ships: per-image approval stands (AGENTS.md), and the WebP/masters derive + provenance run only after promotion.
  • New fleet records are NOT auto-created — they're detected + blocked. When a mission references a fleet asset that doesn't exist (Artemis 4 wants sls-block-1b / gateway / i-hab / starship-hls), the process must say "author these first" — not stub them. An auto-generated fleet entry is a bare stub (specs/agency/images/provenance all empty), the exact rot AGENTS.md's five-gate rule exists to stop. Adding a mission that needs a new launcher is legitimately two authoring tasks.

Net: validate-data flagging a missing fleet asset is correct — but the fix for symmetry should be automatic, while the fix for a missing fleet record should be an explicit prompt to author it.

Touchpoints (in order)

#TouchpointFileRequiredFix / script
1Base recordstatic/data/missions/<dest_lower>/<id>.jsonschema mission.schema.json
2Index entrystatic/data/missions/index.jsonschema mission-index.schema.json
3en-US overlayi18n-src/en-US/missions/<dest>/<id>.jsonschema mission-overlay.schema.json
4LEARN linkslinks[] in the base record✅ (≥1)npm run check-learn-links -- --update (ADR-051)
5Fleet cross-refsfleet_refs[] in base recordoptionalnpx tsx scripts/migrate-fleet-linked-missions.ts (derives the reverse linked_missions[] — symmetry is fail-closed)
6Flight + trajectoryflight.* + generated waypoints_km / waypoints_helio_auoptional (needed for /fly)Moon: generate-hybrid-waypoints.ts; Mars/outer: generate-helio-hybrid-waypoints.ts
7Hero + gallery imagesstatic/images/missions/<id>/…✅ hero (zero-gap policy)the image pipeline — see below
8Translations (all 14 locales)i18n-src/<locale>/missions/<dest>/<id>.jsonrequired (core content)scripts/translate-i18n-gaps.mjs

1–3 · Core data

Base record required fields (mission.schema.json): id, agency, agency_full, sector, dest, color, year, status, departure_date, arrival_date, transit_days, vehicle, payload, delta_v, data_quality, credit, links. Index required: id, agency, dest, status, year, sector, color (+ crewed). Overlay required: name, type, first, description. id is ^[a-z0-9-]+$; dest must match the file-path destination. Copy the shape from the nearest analog (Artemis 4 was authored from artemis3.json).

5 · Fleet refs — author new fleet entries FIRST

fleet_refs[] must point at existing fleet entries; a new launcher/spacecraft (Artemis 4 really wants sls-block-1b, gateway, i-hab, starship-hls — none exist yet) must be authored via the fleet-addition runbook before you can reference it (an agent can fan out one sub-agent per missing asset). Never stub them. Then run migrate-fleet-linked-missions.ts to populate the reverse pointers (symmetry is fail-closed). In the Artemis 4 dogfood we reused sls-block-1 + orion + lc-39b; the Block 1B / Gateway / I-HAB fleet entries are a follow-up.

6 · Flight data is OPTIONAL — but nothing warns you /fly is empty

A mission validates with no flight block; it just renders on /missions and not on /fly. If you want the /fly trajectory, add flight.* (launch/cruise/arrival/totals/events + cislunar_profile for Moon or interplanetary_profile for Mars/outer) and generate the waypoints — the geometry won't render without them:

bash
npx tsx scripts/cislunar/generate-hybrid-waypoints.ts static/data/missions/moon/<id>.json      # Moon
npx tsx scripts/cislunar/generate-helio-hybrid-waypoints.ts static/data/missions/<dest>/<id>.json  # Mars / outer

Flight-internal consistency (validate-data fails-closed). The flight block must be self-consistent — most notably cruise.tcm_count must equal the number of events[] with type: "tcm" (caught in the Hera dogfood: ✗ tcm_count=2 but events.tcm.length=0). Add the matching tcm events, or set the count to match. Event met_days must be ordered + within the transit; events[].type must be in the schema enum. Re-run the waypoint generator after editing events (it re-pins event anchors). The generator does a parametric Lambert transfer — a mission with a real gravity assist (Hera's 2025 Mars flyby) is approximated, hence flight_data_quality: sparse + source_tier: tier_1_5_hybrid.

Trajectory thumbnail — REQUIRED once flight data exists (#390, validate-data fails-closed). static/images/missions/thumbnails/<id>.png — a 240×120 node-canvas render of the flight path, shown in the mission panel. Rendered by the thumbnail step in scripts/fetch-assets.ts (NOT part of npm run build), so run it explicitly after adding/editing flight data:

bash
npx tsx scripts/fetch-assets.ts --thumbnails-only   # renders all mission thumbnails; commit only <id>.png

Every flight-block mission is renderable — MOON → cislunar; a mission with generated waypoints → Sun-centric path; a planetary porkchop body → transfer arc; and generic SUN/COMET/ASTEROID destinations → a per-mission analytic heliocentric distance (ANALYTIC_HELIO_AU in fetch-assets.ts, real astronomical figures — add an entry for a new analytic destination or the gate lists it). The validate-data gate fails closed if a flight mission has no thumbnails/<id>.png — parity with the hero zero-gap + i18n gates. (Re-rendering is byte-noisy — git checkout -- the churn and commit only your mission's new PNG.)

Optional /fly polish (not gated):

  • /fly camera static/data/fly-camera-audit.json — a per-mission camera-framing entry. Absent → /fly uses a sensible default (not broken, just untuned). Add one only if the default framing is poor.

7 · Images — missions carry ZERO hero gaps

validate-hero-coverage fails-closed if static/images/missions/<id>/01.webp is absent (MISSIONS_KNOWN_GAPS is intentionally empty since #342). Source the hero + gallery through the image pipeline runbook: source (agency-first) → masters/ (git-LFS) → WebP ladder + 1x1 → provenance → gallery counts. For a future mission with no photos (Artemis 4), the hero is agency concept art — same pipeline, and image changes need per-image approval (see AGENTS.md).

8 · Translations — CORE content, not optional

Localization is content work — the same tier as sourcing the hero image, and it cannot be skipped. All 14 locales must be authored before the mission is done. validate-data currently only enforces the en-US overlay (the other 13 fall back to English at runtime so nothing hard-breaks), but a mission that renders English into 13 locales is not shipped — it's half-authored. Fill them in the same PR:

bash
set -a; source .env; set +a
node scripts/translate-i18n-gaps.mjs   # scans for missing locale overlays + fills them (Claude API, ADR-033)
npm run i18n:compile

The en-US-only floor in validate-data is a fallback safety net, not the done-bar. A green validate-data with missing translations is as unfinished as a missing hero image.

Validation = the done-signal

bash
npm run validate-data     # schema + overlay-completeness + fleet symmetry + hero coverage + links
npm run preflight         # full pre-push (typecheck → lint → test → validate → build)
npm run preview           # eyeball /missions card + /fly (if flight data) at localhost

Each validate-data failure names the touchpoint and usually the fix command — treat it as the interactive checklist.

Gotchas (learned in the Artemis 4 dogfood)

  • New fleet deps cascade. An accurate mission often needs new fleet entries authored first (Block 1B / Gateway / I-HAB). Reuse existing entries only when honest.
  • fleet_refs are one-directional until you run the migrate script — symmetry is fail-closed; the error even prints the command.
  • flight optional + no /fly warning — a mission can ship looking complete while /fly?mission=<id> is empty because waypoints were never generated.
  • Hero image is mandatory (zero-gap policy) — you cannot ship a mission without one; for future missions that means sourcing concept art through the approval-gated image pipeline.
  • en-US passing CI is a floor, not done. Localization is core content (same tier as the hero image) — a mission rendering English into 13 locales is unfinished; run translate-i18n-gaps.mjs in the same PR.

Orrery — architecture documentation · MIT · No tracking