Mission Planning & Execution

Declarative mission JSON, a Lambert-seeded N-body course compiler, and a shared playback engine — walked through with the Aurora DFD Ceres grand tour

Missions: From Intent to Trajectory

The idea: a mission is authored as intent — "gravity-assist at Venus, fly by Ceres at 1,000 km, come home and capture" — in a small JSON file. The mission compiler turns that intent into a fully solved, flyable course: every burn's Δv, timing, and duration, plus the integrated N-body trajectory between them. The game and the standalone maneuver debugger then replay the same compiled result through shared playback code, so what you debug is exactly what the game flies.

Case Study: the Aurora DFD Ceres Grand Tour

The Aurora is a Direct Fusion Drive ship — 12,000 s specific impulse, 40 kN of thrust, and with 120 tonnes of fuel against 60 tonnes dry, a total Δv budget around 129 km/s that no chemical stage could dream of. Her mission, pinned to depart on January 1, 2055:

  1. Depart a 100,000 km Earth assembly orbit (low-thrust ships leave from high orbits, where a multi-hour burn is still a clean impulse against a 4-day orbital period)
  2. Swing through a Venus gravity assist aimed onward at Ceres
  3. Cross the asteroid belt for a 1,000 km flyby of the dwarf planet Ceres
  4. Ride a fast fusion-powered return home and recapture into the same 100,000 km Earth orbit

Four years, roughly 22 km/s of expended Δv, all inside one fusion tank. The whole mission is authored in ~30 lines of JSON.

The Mission JSON Structure

Every mission lives in a scenario.json file that deserializes into MissionDefinition. This is the Aurora's, verbatim:

{
  "name": "Aurora DFD Ceres grand tour",
  "description": "The Aurora, a Direct Fusion Drive ship ... departs its
                  100,000 km assembly orbit in 2055, swings through a Venus
                  gravity assist, crosses the belt for a 1,000 km flyby of
                  the dwarf planet Ceres, and rides a fast return home to
                  recapture into the same high Earth orbit.",
  "startUtc": "2055-01-01T00:00:00Z",
  "central": "Earth",
  "contextBodies": [ "Sun", "Earth", "Moon", "Venus", "Ceres" ],
  "orbit": { "central": "Earth", "altitudeKm": 100000, "meanAnomalyRad": 0.0 },
  "craft": { "dryMassKg": 60000, "fuelMassKg": 120000, "thrustKn": 40, "isp": 12000 },
  "mission": {
    "state": "Accepted",
    "phases": [
      {
        "kind": "GravityAssist",
        "target": { "body": "Venus" },
        "terminal": { "aimAtBody": "Ceres", "withinKm": 3000 },
        "windowLatestDay": 250
      },
      {
        "kind": "Flyby",
        "target": { "body": "Ceres" },
        "terminal": { "withinKm": 1000 },
        "windowLatestDay": 300
      },
      {
        "kind": "Transfer",
        "target": { "body": "Earth" },
        "windowLatestDay": 300
      },
      {
        "kind": "Capture",
        "terminal": { "circularKm": 100000 }
      }
    ]
  }
}

Top-Level Fields

Field Meaning
name / description Display text for the scenario catalog and panels.
startUtc A pinned start instant that overrides the host's simulation clock. Celestial geometry — planetary alignment, the Moon's phase — depends on the absolute date, so pinning it makes every compile reproducible: the debugger, the game, and the test suite all get the identical trajectory.
central The initial reference frame: a planet name ("Earth") or "Sun". Determines planetocentric vs. heliocentric compilation and rendering.
contextBodies Bodies drawn as map context alongside the craft (tracks + sprites). These are also the bodies the trajectory dynamically interacts with — collision scanning and gravity-assist attribution watch them.
orbit The craft's initial state, one of four modes: onSurface (grounded, carrying the body's rotation velocity — pairs with a Launch phase), a raw state vector (position/velocity), altitudeKm (circular at altitude, as the Aurora uses), or explicit Keplerian elements.
craft Propulsion and mass: dryMassKg, fuelMassKg, thrustKn, isp. These drive finite burn durations and fuel accounting via the rocket equation.
mission or course Either ordered intent phases (shown above) that the compiler must solve, or an explicit course of hand-authored burn nodes (prograde/normal/radial Δv + time) that it just flies.

Phases: Declaring Intent

Each phase is a kind, an optional target body, a terminal condition that defines "done", and an optional search window. Phase kinds include:

Terminal conditions map one-to-one onto JSON keys: circularKm, apoapsisKm, withinKm, aimAtBody, freeReturnPerigeeKm, entryPerigeeKm, and dock. An unrecognized phase kind fails the compile loudly rather than silently dropping the phase.

Generating the Course

Compilation is one call — IMissionCompiler.Compile(definition, simStart) — that returns a MissionCompilation. Under the hood it runs a multi-stage pipeline through the production solver stack:

  1. Pin the clock. The scenario's startUtc overrides the host time and every planet is Kepler-solved to its position at that instant.
  2. Build the craft and anchor its orbit. Mass and thrust come from the craft block; the orbit spec becomes a real state vector around the central body. A gravity-source set is assembled — heliocentric craft get a fixed source list, planetocentric craft get a moving-source updater so the Earth, Moon, and Sun all pull realistically during the integration.
  3. Solve each phase in order. The phase compiler walks the intent list, and each leg is solved from the previous leg's flown end state and time — not from an idealized orbit. For an interplanetary leg like Trans-Venus injection, a pure-Kepler Lambert grid over departure/arrival windows picks the launch window and seeds the burn; the injection is then phased on the parking orbit and Newton-refined against the full N-body integration until the flown trajectory actually arrives.
  4. Thread the needle with corrections. Real trajectories drift, so each leg gets a correction sequence — a mid-course burn near 55% of cruise and a trim ~45 days out — and the chain is finished chain-truth: the final trim and the capture insertion are re-solved from the flown chain's own state, so a 1,000 km flyby window still holds after four years of accumulated dynamics.
  5. Scan and attribute. The flown legs are scanned in time order for collisions (the first crossing of any body's encounter envelope truncates the mission there), and every completed SOI passage is measured for gravity-assist attribution — the "free" Δv the flyby contributed, computed energy-consistently in the assist body's primary frame, never mixing in burn Δv.
  6. Package for rendering. The result carries KSP-style step render data: Legs (initial orbit and final orbit as smooth Kepler ellipses, transfer arcs as the integrated trajectory) and Nodes (each burn with its Δv split, duration, fuel spend, and mass before/after).

What the Compiler Produced for the Aurora

The four declared phases solve into ten burns and two free gravity assists, totaling 21,637 m/s:

Phase (intent) Solved steps Δv
GravityAssist (Venus) Trans-Venus injection (1,792 m/s) + two arrival corrections, then the Venus swing-by — −4,338 m/s of free heliocentric braking that bends the path onward toward Ceres 2,026 m/s
Flyby (Ceres, 1,000 km) Trans-Ceres injection (9,676 m/s — the fusion drive brute-forcing across the belt) + corrections down to 19 m/s at the end; the Ceres pass itself adds +97 m/s free 11,198 m/s
Transfer (Earth) Trans-Earth injection (2,471 m/s) + trims of 12 and 3 m/s — km-scale accuracy after a 1.2-year cruise 2,486 m/s
Capture (100,000 km) Orbital insertion at Earth arrival 5,926 m/s

Notice the shape of each leg: one big injection, then corrections that shrink by orders of magnitude (202 → 33 m/s, 1,504 → 19 m/s, 12 → 3 m/s). That's the chain-truth correction sequence doing its job — each trim is solved against the actually-flown trajectory, so the errors collapse instead of compounding.

Executing the Course

The compiled MissionCompilation stores every trajectory sample relative to the central body, timestamped in days from the mission start. Execution is a pure replay through the shared MissionPlayback lookups:

Two hosts drive this same code:

Because both hosts share the same playback math and the same compiler, the debugger is a true window into the game: a mission verified there is byte-for-byte the mission the game flies.

Two Frames, One Trajectory

The debugger's Frame toggle re-projects the compiled course between the Earth frame and the Sun frame. The samples are stored central-relative, so the heliocentric view is computed per-sample as helio(t) = relative(t) + centralPositionAt(t) — the same trajectory, seen from two vantage points.

Earth-Centric View

Aurora DFD Ceres grand tour in the Earth-centric frame: looping relative trajectory with numbered burn nodes, Venus and Ceres assist markers, and the full step list in the right panel
The Earth frame. Because Earth itself is orbiting the Sun, the interplanetary cruise appears as huge sweeping loops relative to home. The numbered nodes mark the ten solved burns; the lavender diamonds mark the Venus (−4,338 m/s) and Ceres (+97 m/s) gravity assists. The right panel lists every phase, step, and Δv of the compiled course.

Heliocentric View

Aurora DFD Ceres grand tour in the heliocentric frame: clean transfer arcs from Earth inward to Venus, out to Ceres, and back to Earth around the Sun
The same course with the Frame toggle set to Sun. Now the mission reads like a textbook diagram: inward from Earth's orbit to the Venus assist, a long fusion-powered arc out across the belt to Ceres, and the fast return leg home to the Trans-Earth injection and final capture. Earth's own orbit is drawn faintly — in this frame the departure point rides it.

Testing the Catalog

Every scenario in the catalog doubles as an integration test: SolarApp.Scenarios.Tests compiles each one through the real solver stack, and scenarios with pinned dates carry numeric assertions on the outcome — total Δv, pass distances, final orbit shape. Adding a mission to the game is just adding a folder with a scenario.json: the catalog discovers it, the debugger lists it, and the test suite compiles it.

Going Deeper