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 },
"craftSections": {
"name": "Aurora",
"sections": [ {
"name": "Spaceframe",
"designKey": "chassis.scenario-aurora",
"components": [
{ "key": "engine.fusion-drive-dfd" },
{ "key": "tank.deuterium-120t", "state": { "fillKg": 120000 } } ] } ]
},
"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. |
audience |
"user" for a curated showcase mission or "integration" (the default) for a solver-coverage fixture. The debugger's scenario picker splits its dropdowns on this, so the handful of missions worth demonstrating aren't buried in the regression catalog. |
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. |
craftSections |
The ship, built from the component catalog: installed hulls by designKey and components by key, with per-installation state (tank fill, engine mode). The summed masses and the main engine's thrust/Isp pair 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. Two optional fields tune how a phase is flown: throttlePercent runs the phase's burns at a throttle step of the engine (on a constant-power drive like a fusion torch, lower throttle means more Isp and a longer burn — "depart hot, cruise cold" is a real trade), and windowAuto asks the launch-window finder to locate the classical departure window inside the phase's horizon instead of scanning from day zero. Phase kinds include:
- Launch — surface to circular parking orbit (needs an
onSurfacestart) - ChangeOrbit — raise/lower to a new orbit around the current central. A burn too long for the orbit it is flown on is split into kicks at successive apsides, in either direction: a lowering's kicks never pass below the target orbit, and a phase solved after a capture aims its burns in the capture body's frame
- 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 - Brake — the mid-cruise flip-and-burn: a retrograde burn on the crossing, authored strictly between a Transfer and its Capture, shedding arrival speed so the single capture insertion fits its pass window. Usually authored bare (the solver derives a flyable target from the craft's own thrust, with margin);
arrivalSpeedMsis the explicit override - Capture — arrive and circularize at
circularKm - Deorbit — a retro trim that commits the trajectory to an atmospheric entry corridor (
entryPerigeeKm) - Aerobrake — dip the periapsis into the atmosphere (
aerobrakePeriapsisKm) and shed apoapsis with free drag passes — the atmosphere pays the Δv a burn would have; too fast or too deep still destroys the craft - Land — deorbit plus a powered descent to a survivable
touchdown; the mission ends with the craft on the surface, intact - Boost — the Launch ascent without the insertion: a booster throws a stack onto an arc whose
apoapsisKmits payload will circularize at - Separate — stage separation; sheds the stacked payload and records the state its own mission starts from
- Boostback — returns a spent booster to its launch site, solved on where the ballistic arc actually comes down (
withinKmis the site tolerance) - Insert — the Launch insertion standing alone, for a craft whose flight begins mid-ascent on a separated stack
Staging: Two Craft, One Launch
A rocket can carry another rocket. One field on the manifest — stackedOn — pairs a
payload craft to its carrier, and everything else follows from it: the booster's ascent is priced
carrying the payload's full mass, a separate phase sheds it, and the payload's own
mission is compiled starting from the state the booster actually flew it to. Two independent
trajectories come out, and from there the pair are simply two craft.
The fleet flies one: Atlas, a reusable LH2/LOX aerospike booster rated at 500 tonnes to orbit, lifts Aurora — a 36-seat shuttle — off the pad, separates on the ascent arc, flips, and lands back where it launched. Aurora circularizes at the arc's apoapsis, phases up to 6,000 km, and docks with the Kepler Outpost station.
Terminal conditions map one-to-one onto JSON keys: circularKm, apoapsisKm, withinKm, aimAtBody, freeReturnPerigeeKm, entryPerigeeKm, aerobrakePeriapsisKm, touchdown, arrivalSpeedMs (a Brake's target arrival excess), and dock (whose posToleranceKm is the standard the rendezvous solve is held to). Atmospheres themselves are data — data/atmospheres.json authors each body's bands (altitude, density, composition) and the gamified coefficients behind launch drag losses, aerobrake pass Δv, heat load, and the survival gates. An unrecognized phase kind fails the compile loudly rather than silently dropping the phase.
A phase can also carry a profile, and the words are per kind. A Transfer, Flyby or GravityAssist reads Efficient (the minimum-energy arc, and what an absent profile means), Fastest (propellant traded for an earlier arrival, gated so the single capture burn still fits its pass) or Torch (the partial brachistochrone: a heavy injection, a coast, a required turnover burn late in the cruise, then capture). A ChangeOrbit reads one word, BiElliptic — the raise flown out past the target and back in three burns, cheaper than the Hohmann only past a radius ratio of 11.94 and declined, with the reason in the phase detail, below it. A word on any other kind fails validation in the editor rather than being dropped.
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