Missions: From Intent to Trajectory
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:
- 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)
- Swing through a Venus gravity assist aimed onward at Ceres
- Cross the asteroid belt for a 1,000 km flyby of the dwarf planet Ceres
- 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:
- Launch — surface to circular parking orbit (needs an
onSurfacestart) - ChangeOrbit — raise/lower to a new orbit around the current central
- Transfer — cross to another body (cislunar, escape, or heliocentric regime is picked automatically)
- GravityAssist — a powered slingshot;
aimAtBodymakes the post-flyby heading point at the next destination - Flyby — pass a body within
withinKm; addfreeReturnPerigeeKmfor an Apollo-style free return - Capture — arrive and circularize at
circularKm - Deorbit — a retro trim that commits the trajectory to an atmospheric entry corridor (
entryPerigeeKm)
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:
- Pin the clock. The scenario's
startUtcoverrides the host time and every planet is Kepler-solved to its position at that instant. - Build the craft and anchor its orbit. Mass and thrust come from the
craftblock; theorbitspec 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. - 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.
- 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.
- 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.
- 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) andNodes(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:
- Before the first burn the craft loops its parking orbit (the compiled initial-orbit leg, wrapped by period).
- Along the mission it interpolates the integrated transfer-arc samples — burns, coasts, flybys, all of it.
- Past the end it loops the final orbit forever; there is no world-frame freeze.
- A collision-truncated compilation freezes the craft at the impact state.
Two hosts drive this same code:
- The maneuver debugger replays it on a transport bar (play/pause, rewind, ×1/×4/×16 speed) — the full four-year mission plays in about 90 seconds.
- The game compiles each craft's flight plan from
data/CRAFT.MISSION.jsonon a background task at boot (ships appear parked immediately; the solve lands when ready). Each frame,MissionExecutorcomputes the mission time from the simulation clock, asksMissionPlaybackfor the central-relative state, and re-anchors it to the central body's live position — so as Earth moves around the Sun, the whole compiled trajectory rides along with it.
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
Heliocentric View
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
- Astrial API & Physics — the Kepler solver and N-body integration underneath the compiler
- Home — architecture overview and how the domain stack, rendering, and UI fit together