The 15-Phase System Powering Starship Mission — Managing a Complex Flight Flow
Notes from building a Starship mission simulation with 15 phases. State machine design, P-controller autopilot landing, and Mechazilla Catch detection.
Opening — When Phase Count Hit 15...
I was building a loose reenactment of SpaceX's Starship mission. Initially I thought state === 'flying' was enough, but tracing the actual mission flow, phases kept multiplying.
pre_launch → countdown → launch → ascent → sep_prompt (separation standby)
→ separation → boostback → boost_coast → reentry → landing_burn
→ catch_anim → result
Plus second-stage orbit insertion and descent brought the total to 15 phases. A simple boolean or single enum wasn't enough.
This post documents how I tamed that complexity — especially the trickier parts like autopilot landing control and Mechazilla Catch detection.
State Machine — Transition Rules Per Phase
Instead of a plain enum, I explicitly defined "which phases can transition to which".
type Phase =
| 'pre_launch'
| 'countdown'
| 'launch'
| 'ascent'
| 'separation'
| 'boostback'
| 'boost_coast'
| 'reentry'
| 'landing_burn'
| 'catch_anim'
| 'result'
| 'failed';
// allowed next phases from each phase
const TRANSITIONS: Record<Phase, Phase[]> = {
pre_launch: ['countdown'],
countdown: ['launch', 'pre_launch'],
launch: ['ascent', 'failed'],
ascent: ['separation', 'failed'],
separation: ['boostback'],
boostback: ['boost_coast', 'failed'],
// ...
};
Invalid transitions get rejected at runtime. Catches bugs like "jumping from ascent straight to catch_anim".
Phase Timer
Each phase carries its own frame counter (phaseTimer). On phase transition, it resets to 0. Animations, audio, and logic trigger off "how many frames we've been in this phase".
// every frame
state.phaseTimer += 1;
switch (state.phase) {
case 'landing_burn':
if (state.phaseTimer > 45) {
// after 45 frames, MECO fadeout
state.thrustLevel = Math.max(0, 1 - state.phaseTimer / 45);
}
break;
}
Autopilot Landing — P-Controller
The hardest part. Landing a booster gently requires precise throttle adjustment as altitude decreases. Real SpaceX uses sophisticated controllers, but for the game a P-controller (Proportional controller) was enough.
Target Velocity Curve
Define "the maximum safe velocity" per altitude:
function getTargetVelocity(altM: number): number {
// > 200m: mild descent
// 15m ~ 200m: linear deceleration
// < 15m: near standstill
if (altM > 200) return -10; // -10 m/s OK
if (altM > 15) {
return -(2 + ((altM - 15) / 185) * 8); // -2 to -10 m/s
}
return -2; // below 15m, slow to -2 m/s
}
Proportional Throttle Decision
Adjust throttle proportional to the error between current and target velocity:
const targetVel = getTargetVelocity(state.altM);
const velError = state.velMS - targetVel; // + = too fast, - = too slow
// start from hover throttle, scale by error
const hoverT = 0.93;
let throttle = hoverT + velError * 0.015;
throttle = Math.max(0, Math.min(1, throttle));
state.thrustLevel = throttle;
Hover throttle 0.93 is "exactly balances gravity". A bit above that decelerates; below, you fall. P-gain 0.015 was found through tuning.
Don't Use Static hoverT * 0.93
An early attempt used a fixed state.thrustLevel = hoverT * 0.93, and descents came in too fast or too slow depending on conditions. Only after switching to the dynamic P-controller did landings become smooth and reliable.
Mechazilla Catch Detection
The iconic maneuver — the tower's "chopsticks" arms catching the booster. Implementation required some thought.
Ghost Guide Rendering
A translucent "ideal alignment" ghost at the tower arm position guides the user's aim:
// ghost rendering characteristics
ctx.strokeStyle = 'rgba(255, 255, 255, 0.55)';
ctx.setLineDash([6, 4]);
ctx.lineWidth = 3;
// translucent fill adds a "target" feel
Auto-Catch Conditions
All three conditions must be satisfied for a successful catch:
if (
state.altM <= 15 && // low altitude
catchScore >= 0.8 && // alignment score ≥ 0.8
Math.abs(state.velMS) <= 8 // low descent speed
) {
transitionPhase('catch_anim');
}
catchScore is a normalized combination of horizontal distance and angular alignment between booster and tower arms.
Terminal State
On successful catch, the m2_result phase receives catchProgress. The result phase is designed as terminal state — no auto-transitions — so the user explicitly exits via "Retry" or "Assembly" buttons.
Failure Classification
One more important part — classify failure causes for UX feedback. Not just "failed", but why:
structural_failure— MAX-Q exceeded (above 5km)sep_timeout— separation missed past 80kmfuel_exhausted— fuel depletedimpact— ground impact (excessive velocity)g_force— over 15G (structural limit)
Each failure gets a matching hint text on the result screen, giving the user a clue about what to improve.
Avoiding Chain Side Effects
A common bug in phase-based games is updating only some of the required values on transition. For this flight sim, I kept a "sync 8 places" checklist:
- Types / constants definition
- State fields
createInitialState()- Physics step
- Failure check
- Score calculation
- HUD draw
- Result draw
Every new physics element needs every one of these touched. Miss one, and NaN or undefined pops up somewhere at runtime.
⚠️ Especially: multi-phase physics elements must apply in every relevant phase. For instance, if booster fuel consumption logic lives only in
ascentbut notlaunch, fuel doesn't decrease during launch — bug.
Retrospective
Managing 15 phases felt like overkill at first. "Isn't three states — flying / landing / done — enough?" But accurately expressing the real flight flow meant each step needed its own physics, audio, camera, and UI.
Learning the basics of control theory like P-controller was a bonus. A one-line formula replaced tens of minutes of trial-and-error. "When building a physics-like sim in a game, borrowing a hint from control theory resolves a lot of problems naturally" — this project brought that home.
If you're making a complex sequence game, I'd recommend adopting explicit state machine + phase timer + transition rules from day one. It makes later extensions much easier.
Guestbook
Leave a short note about this post
Loading...