How the flight model works
The physics is arcade, not aerodynamic: there is no angle of attack, no drag polar, and no engine model. Four rules carry the whole thing, and all four live in js/flight-model.js and js/crash.js as plain functions with no Three.js dependency, so they can be unit tested in Node.
Speeds below are given in world units per second, with the HUD reading in knots at two knots per unit.
On this page
The throttle is a setting, not a shove
Shift and Ctrl move a lever between 0 and 100%, at a rate that takes two seconds to sweep the whole travel. The lever picks a target speed - 100% asks for 200 units/s, 50% asks for 100 - and airspeed then converges on that target rather than jumping to it. The engine pulls harder than drag pushes back, so speed builds faster than it bleeds off.
This is why the HUD throttle reads the setting you asked for while the airspeed needle is still catching up, and why a closed throttle is a request for a dead stop rather than a request to coast.
With no engine, the nose sets the speed
Two of the game modes can leave the aircraft with nothing pulling: a dead stick opens that way, and a cargo run that spends the last of its budget arrives there. The lever still moves - there is simply nothing on the end of it - and the throttle readout sits at 0%.
The airspeed then comes off the attitude instead. A level nose settles at 80 units/s, and every radian of nose-down adds 180 to that, so putting the nose down buys speed with the height you had left to spend and holding it up gives the speed back. It is floored at a standstill and capped where the engine's own top speed is, so a dive is fast rather than unbounded. The convergence is slower than the engine's either way, because what is being moved is the aircraft's own momentum rather than a throttle: a nose dropped for speed pays for it over a second or two.
The pair of numbers is chosen so that a settled glide is always a descent. The nose-up angle that bleeds the speed to nothing is about 25 degrees, and short of that the sink the slow wing is already losing outruns the climb the nose is asking for - so there is no attitude a settled glide holds height at, which is the one way a dead stick could quietly stop being one. The suite sweeps the whole attitude range rather than sampling it, because a hole in that guarantee is not something anyone would find by flying.
A settled glide is not where the aircraft spends its time. The nose moves at the control rate and the airspeed follows it far more slowly, so for a second or two after every pull the aircraft is carrying the speed of the attitude it left at the angle of the one it arrived at - and that pair is not on the curve the sweep walks. Flown straight, those pairs climbed: holding the nose up out of a settled glide gained 151 ft on no engine, and pulling up out of a dive gained 2150 ft.
So the nose is counted for no more height than the airspeed can pay for. The attitude it is held at is taken as no higher than the one that airspeed has settled to, and never as pointing down when the pilot has not pointed it down - which only ever bites on a nose held up, and never asks for a faster sink than the level nose at that speed is already losing, so an engine dying at cruise does not drop the aircraft out of the sky on the frame the tank empties. A settled glide is unchanged at every attitude; what changes is that no attitude and airspeed the aircraft can be in comes out climbing, and below cruise speed every one of them is losing height. The suite sweeps that whole plane, which is the form the guarantee has to hold in for the mode to mean anything.
At or above cruise speed a level nose does hold height with the engine dead, for as long as the speed lasts. That is the wing rather than the glide - lift cancels gravity exactly from cruise up, as it does under power - and with nothing pulling, the speed is gone within seconds.
What comes out is a glide worth planning. A level nose reaches about twelve times the height it spends; a little nose-up does better than that, and better again is close enough to the stall to be a knife edge. A dive reaches less, and steeply. Finding the attitude that reaches furthest is the whole of Dead Stick.
Lift is read off airspeed
Lift rises with the square of airspeed and is capped at the weight of the aircraft, so:
- At or above cruise speed (120 units/s) lift cancels gravity exactly, and level flight holds altitude. Extra speed never lifts on its own, so a fast pass stays level instead of ballooning; climbing is done with the nose.
- Below cruise speed lift only covers part of the weight, and the aircraft sinks by the difference.
- At a standstill there is no lift at all and the full weight pulls down.
Climbing is therefore a trade rather than a free gain: pulling the nose up spends airspeed, and spending too much of it costs the lift the climb depends on.
Below the stall speed the wing gives up
Under 40 units/s the wing is stalled and the sink rate is multiplied on top of the lost lift, easing in from no penalty at the stall speed up to double at a dead stop. The penalty eases rather than snapping on, so a stall is a mush and a sag rather than a switch being thrown.
Every flight starts at exactly 40 units/s, which is 80 knots and the stall speed itself: the wing is carrying, but there is nothing in hand. Opening the throttle is still the first thing to do.
The start is a condition, not a set of numbers
The start is data rather than a set of literals scattered through the flight code. It lives in js/config.js as one object, written the way a pilot reads it - 80 knots, 1390 ft, +1260 ft/min, heading 000, 20% throttle, chase camera - and converted into world units by the same factors the instruments read back through, so the HUD shows those numbers on the first frame rather than something near them.
The same file declares what each of those fields is allowed to be: its range, the step it moves by, and the units it is read in. That is what lets the settings panel offer the start without knowing anything about flight, and what a host embedding the Pilot API reads to offer it its own way.
Two of the values are held to the flight model rather than declared at it. The throttle setting is the one that asks for exactly 80 knots, so airspeed is not converging on anything when the flight begins. The pitch is not configured at all: it is the angle that turns enough of 80 knots into height to cover both the 1260 ft/min climb and the sink the wing is losing at that airspeed, worked out from whatever climb and airspeed the start was set to. The result is a flight that holds its opening condition until the pilot changes it, rather than settling out of it over the first second.
R, and the pause menu's RESET FLIGHT, put the aircraft back into that same condition, the camera it opens in included.
The ground is not a floor
Arriving at the terrain is only a crash if it is arrived at hard. Coming down slower than 30 units/s is survived: the aircraft keeps its 5 units of ground clearance and flies on, which means an engine-out settle onto a hillside is not fatal, because the worst the flight model can sink without the nose pointing down is the 24 units/s of a dead-stop stall.
Coming down faster than that - which takes a dive, since it can only be reached by pointing the nose at the ground and adding speed to it - wrecks the aircraft. The controls go dead, the throttle and airspeed drop to zero, the wreck stays where it hit for two and a half seconds behind a CRASHED banner, and then the flight resets to its starting condition. Pressing R skips the wait. Pausing holds the countdown rather than letting it run out behind the paused frame.
A runway changes what an arrival means
A strip does not raise the bar for how hard an arrival may be so much as give it somewhere to be something else. On a runway, an arrival is one of three things:
| Arriving | Is |
|---|---|
| Slower than 18 units/s, wings within 11 degrees of level, nose within 15 degrees of the horizon, heading within 25 degrees of the strip | A landing |
| Firmer than that, but slower than 48 units/s | A rollout - the aircraft is down and flying on, and nothing is counted |
| Faster than 48 units/s | A crash, the same as anywhere else |
Prepared ground takes an arrival a hillside would not, which is why the crash threshold on a strip is 48 rather than 30. The gap between 18 and 48 is what makes a landing something to fly well rather than something that happens to anyone who reaches the runway.
Landing either way down the strip counts: a runway has two thresholds rather than a start and a finish, and the heading is measured to whichever end is nearer. All four limits are read off the aircraft's own nose and wings rather than off the controls behind them, so what is judged is how the aircraft was actually being held.
Only the frame the aircraft arrives on is judged. Everything after it is a rollout, and a rollout is not a second arrival - judging every frame of one would turn a landing into a crash as the airspeed, and with it the lift holding the aircraft up, bled away underneath it. LANDED stays on screen until the aircraft leaves the ground again, or until the rollout ends and the landing breakdown goes up on the objective card to say the same thing at more length.
What does end a run along the ground is leaving the strip it is running on. A takeoff that goes past the end of the runway, or a rollout that swerves off the side of one, is a crash through the same path any other arrival on ground the aircraft cannot use takes: the attempt ends and is recorded like one, rather than the aircraft carrying on over the country as though it were taxiing. The rule is bound to the runway rather than to a distance from where the run began, so an overrun off either end and a swerve off either side are the same event. A flight that never had a strip under it - one that opened on open ground, or settled onto a hillside and flew out of it - is never leaving one.
Holding an altitude
Level flight at cruise speed holds altitude on its own, but trimming a climb out by hand means moving the nose a little at a time and reading the vertical speed until it settles, which is a fiddle in the middle of everything else an approach is asking for. Space does it in one press: the vertical speed goes to zero, the aircraft keeps the altitude it was at, and the nose eases from wherever the pilot left it down to level.
The nose takes 0.6 seconds to settle, on a curve that leaves the old attitude gently and arrives at level gently, so the movement reads as the aeroplane settling rather than as a jump. The altitude is held from the frame the key goes down and the attitude arrives a moment later, which is the order a pilot flies it in - the climb stops, and the aeroplane settles. The interval is LEVEL_OFF_SECONDS in js/flight-model.js, and it is the only copy of it: the artificial horizon reads the model's own pitch, so easing that pitch is the whole of what carries the horizon down with the nose. Nothing eases a second copy, which is how the dial and the view out of the window would come to disagree by a frame. Roll is left alone - a wing-level is a decision of its own, and rolling the aircraft on a keypress nobody pressed for it would be a surprise rather than a convenience.
The hold lasts until the pilot calls for a different vertical state, which is any pitch or throttle input. That hands both halves back at once: the altitude stops being held and the nose stops easing, wherever in the 0.6 seconds it had got to, so a pilot who takes the pitch back mid-settle is flying it themselves from that frame. Roll and yaw are not that call - an altitude held through a turn is what holding one is for - so a circuit can be flown on the altitude the level off set. Crashing and resetting end it too. Ground contact that is not a crash only suspends it - on the ground the altitude is the ground's, and a hold pinning an aircraft to a strip it is trying to leave is not a hold anybody asked for - so an arrival flown out of leaves the hold where the pilot left it.