Nobody could point to the one place where the rules of [PvP connection presence](/posts/interrupted-not-gone) actually lived. The advisory online/offline indicator tracked each player's connection as four loosely coordinated fields, two booleans, a nullable grace timer, and a refcount map, with the externally visible status derived ad hoc from whichever combination they happened to be in. The cost of that showed up directly: on 2026-08-14, the roughly 170-line file behind it took five separate fix commits in a seventy-minute window, each patching a different edge of the same implicit mechanism. One of them, eight minutes after the commit before it, undid a callback that commit had just added. That revert was the clearest signal available that the correct behavior was being discovered live, one edge case at a time, rather than designed.

An architecture review picked up on the pattern rather than any one of the five fixes. Whoever hit the next presence bug would very likely repeat the same trial and error, because there was no single place that defined what a connection-status transition actually was. The fix was to give the module an explicit state machine: a named state type and one pure function deciding, for a given state and event, what comes next and whether the change is worth telling the opponent about.

```typescript
export type ConnectionState = 'unknown' | 'observing' | 'connected' | 'grace' | 'interrupted';

export type ConnectionEvent =
	| 'connected'
	| 'disconnected'
	| 'grace-expired'
	| 'activated'
	| 'deactivated'
	| 'reset';

export function transition(
	state: ConnectionState,
	event: ConnectionEvent
): { next: ConnectionState; crossedInterruptedBoundary: boolean }
```

Writing the transition table against the existing test suite surfaced a real gap in the state model as originally proposed. A four-state version covering unknown, connected, grace, and interrupted would have reported a role that was never observed, but had a first-observation grace timer already running, as connected, when it should still read as unknown to anyone watching. A fifth state, distinguishing that first-observation grace from an ordinary reconnect grace, closed the gap. All thirteen pre-existing behavioral tests for the module passed unmodified against the rewrite, the confirmation that restructuring the internals hadn't quietly changed what players actually see.

## The same shape, client-side

The client side had grown the same shape of problem independently. A counter meant to stop an out-of-order network response from overwriting fresher state had been invented for one specific presence race, generalized once when the same ordering problem turned out to affect turn resync too, then hand-copied a third time inside word submission. It only ever guarded the one field it was first built for. Three other call sites that wrote the same shared state, claiming a timeout and both halves of a rematch request, had no staleness guard at all.

Grilling the proposed fix before writing any code caught that the obvious version of it, one generic mechanism that discards a whole response the moment it looks stale, would itself have been a regression. Tracing the word-submission path showed it could discard a player's own just-submitted word if an unrelated background resync happened to start later and finish first, even though that resync carried nothing newer. Two independent mechanisms shipped instead, each matched to what the data they guard actually has available to judge staleness with: a content-based check on the resync engine, rejecting a fetch whose turn number is already behind what's been seen, mirroring a guard the turn-animation path already had; and a request-order token on the client store for the one field with no content of its own to compare, this time wired into every site that reads a fresh fetch, including the three that had been missing it.

Both halves of the review are done, and the module that took five fix commits in one hour now has a documented pointer for wherever the next presence bug turns out to live. Whether it goes without another emergency commit is the only thing left to find out.