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.