WARBIRDS.IO Dev Log

← All posts

The ghost captain

We spent the day teaching the browser test suite to grade the war itself — not just "does my tank answer the wheel" but "are the bots' tanks going anywhere, is the flak out-shooting the pilots, do the convoys actually arrive". On its very first full run it caught something better than any of that: a captain spawning at a helm that would never answer, for a reason that had nothing to do with ships.

Grading the war, not the diff

The e2e suite already flies, sails and drives each theater as a real player in a real headless browser. What it never did was look past its own cockpit. The standing complaints it couldn't see are the emergent ones: bots camp, the flak hammers you out of the sky, nothing ever happens on the supply road. So the harness now keeps two ledgers off the wire every session already produces:

A hull tracker — one entry per AI-hull life, accumulating how far it actually traveled and how long it spent aground. The land probe computes this server-side (its moved-per-60s census); now the client-side suite measures the same thing from exactly what a player's browser receives, and warns when half the platoon never covers a hull-length. And a kill ledger — every kill broadcast classified by the killer's id space, so "is the AAA too strong" becomes a number: what share of the sky's deaths came up from the ground.

On top of those ride the doctrine canaries — platoon leaders must call their own fire missions, towns must change hands, destroyers must sail as a division, the mercy wing's greenhorns must actually be on the roster — and a tower watch: 25 seconds on the spectator wire, which skips fog-of-war entirely, grading the map-wide machinery no cockpit sees whole. Convoys must make headway truck by truck. Freighters must be under way. Downed pilots must hit the silk. Almost all of it reads numbers the sessions already produced; the whole expansion costs the suite under half a minute.

The first catch

First full validation run: land theater, all green — two bot fire missions, towns flipping, zero stuck platoon hulls, the strike wing overhead. Naval: three failures. The helm probe held W for 45 seconds and made good zero metres. The conning panel never came up. X refused to swap shell natures. The screenshots showed a destroyer's-eye view of a cove wall, speed pegged at 0 km/h, throttle rung up to flank.

The wrong theory, held with confidence

Everything about the picture said wedged spawn: hull beached in an island pocket, the auto-astern and the held throttle fighting to a standstill. It even rhymed with a real find from the same run's new census — a naval bot logged at 0 m moved in 84 seconds. So we did the obvious: booted a naval server and joined it eight times in a row, reading the spawn's position, the aground flag, and twelve seconds of way on the engines. Eight for eight: clean water, panel up, 62 m made good. The spawn picker validates a 280 m clear disc before it berths anyone. The terrain was innocent.

The real culprit: a one-tick race

The clue that finally pointed away from the ocean was in the DOM, not the water: the client's whole helm — drive wire, conning panel, shell swap — hangs off one variable, and that variable is set by exactly one thing: the manveh event, the server's "you are now crewing hull so-and-so" broadcast. Miss that one event and you are a ghost captain: the server thinks you're driving, your browser never got the memo, and every key that needs a hull is politely dead for the rest of the session.

And you can miss it. Joining runs in one goroutine: spawn the hull, append manveh to the event queue, compose the welcome, and only then — a lock handoff later — mark the socket as joined. The tick loop runs in another: step the world, drain the event queue, broadcast to every joined client. If the tick fires inside that handoff window, it drains your manveh and delivers it to everyone on the server except you. The 522 ms join in the failing run — the fastest of any session that day — threaded exactly that needle.

The fix: the welcome carries your seat

The welcome handshake is composed under the same world lock as the spawn, so it can never race and never lie. It now carries the seat the join put you in — hull, gun emplacement, or bomber turret — and the client replays the seat event from it through the normal pipeline, deduping the broadcast copy when it (usually) arrives. A regression test pins the contract, and the full five-theater suite runs green on top of it. The window was microseconds wide and the outcome was a whole session lost; players who hit it would have reported "spawned, ship ignored me, quit" — and no server metric would ever have shown a thing.

Confessions

Two, in the tradition of this dev log. First: we probed spawn points for an hour because the screenshot looked like terrain's fault, when the disproof — eight clean joins — was ten minutes of measurement. The habit that broke the case was the same one this suite exists to enforce: don't trust the picture, read the wire. Second: one of the brand-new doctrine checks cried wolf on its very first run — it demanded bot torpedo salvos inside a two-minute session, a window in which an honest destroyer is still steaming for torpedo water. It got demoted from a warning to a tracked metric the same day. A tripwire that trips on a healthy war is just a different kind of silence.