Your keys, your stick
For as long as the game has flown, the keys were ours, not yours. W climbed, A/D rolled, Q/E worked the rudder, and if your hands wanted them anywhere else — a lefty, an AZERTY board, a thumb that lives on the arrows — tough. Now there’s a Settings > Keybindings screen. Bind every desktop flight control to whatever key you like; it saves to your device and takes hold in the air the instant you set it.
The keys were nailed to the floor
Under the hood, every control was a literal. Pitch was
axis(["KeyS","ArrowDown"], ["KeyW","ArrowUp"]); the throttle
was another literal; the toggles were a wall of if (code === "KeyV").
Fine, until someone asks to move one. There was nowhere to move it
to — no indirection, no table, no home. The HOTAS map had a
hidden localStorage override and nothing else did.
So the first job wasn’t UI at all. It was hoisting every one of those literals into a single table — nineteen actions, each with its default physical-key codes — and pointing the input reader at the table instead of at the strings. One source of truth, read live every frame. Rebind an action and input.js is already looking at the new value next tick; no reload, no re-plug.
The rule that made it safe: everything keys off the physical key
(KeyboardEvent.code), exactly as the flight controls always
did. A binding you record on QWERTY lands on the same key of an AZERTY
board. The label on the cap is cosmetics; the code underneath is what
we store.
Click a cap, press a key
The screen itself is the small, obvious thing: a keycap for every binding, grouped Flight / Throttle / Weapons / Systems. Click a cap and it pulses gold — press… — and the next key you hit takes the slot. A control can hold more than one key (the letter and the arrow, both Shifts), so there’s a + to add another and a ↻ to put one back to default.
One quiet rule keeps it honest: a physical key can only do one flight thing at a time. Bind Space to bombs and it stops firing the guns — the screen takes the key from whoever held it and tells you so (“Space → Drop bomb (taken from Fire guns)”), rather than letting one press quietly do two jobs. And the old “prevent the browser from scrolling” list — the hardcoded Space/Arrows/Shift that stopped the page jumping mid-dogfight — now follows your bindings, so throttle rebound onto PageUp gets swallowed too instead of paging you off the plane.
Because the help text should never lie, the join-card control card and the HUD hint bar read from the same live table. Move pitch to K and the card quietly changes to K/S. The three pilot toggles that used to be blind-press secrets — mouse aim, auto-trim, inverted pitch — got checkboxes on the same screen, so you can find them without knowing the magic letter first.
The proof, and the bug it caught
A settings screen that looks right proves nothing; the test is whether a rebound key flies the plane. So the playtest joins a real server, moves climb from W to K, spawns, and holds K: keyboard pitch runs to 1.0, the nose comes up. Then it holds the old W: 0.0, dead key, exactly as it should be. That round trip — new key lives, old key dies — is the whole feature in one assertion.
It also caught a bug that reads fine until you watch it. Re-recording the W cap to K, I removed W and appended K — which left K at the end of pitch’s key list. Harmless for flying, but the join-card hint shows the first key, so it read ↑/S instead of K/S: rebind the key, and the card advertises the wrong one. The fix was to swap in place — the cap you click keeps its slot — and now the card says what you'd expect. A screen that drew perfectly was telling a small lie, and only driving the real thing showed it.
The air game is byte-for-byte what it was; naval and land crews keep their own conning keys for now. But if you fly with a keyboard, the sticks are finally where you put them.