Chess on the ATmosphere checkmate.blue

Subscribe Jetstream at the live tail and catch up with one read #52

Merged checkmate.blue
targeting main
Merged opened by and assigned to checkmate.blue
targeting main

Fixes a regression I introduced in HAD-150: opponent moves were taking 7-10 seconds to appear.

HAD-150 wanted to avoid losing a move committed between the page's PDS read and the WebSocket opening, and did it by subscribing from a past cursor. That was the wrong mechanism. Jetstream does not seek to a cursor and then jump to the live tail; it drains the backlog at close to real time, so every later frame arrives as far behind as the cursor was old. Measured against jetstream2.us-east:

live tail (no cursor)     lag median  -31ms   p90   -22ms   max     59ms
cursor 5s in the past     lag median 1840ms   p90  4536ms   max   5236ms
cursor 60s in the past    lag median 55614ms  p90 59556ms   max  60296ms

The offset was worse than the nominal 5s, too: loadStartedAt is captured at the top of loadGame, before the record read, the pairing lookup (which now pages), the Constellation joiner query and handle resolution, so by the time the socket opened that timestamp was already seconds stale.

Now the game and spectator sockets subscribe with no cursor, and JetstreamConnection reads the opponent's record once on every onopen instead. That closes the same gap without ever running behind, reuses the read the polling fallback already does, and is safely repeatable because applyOpponentMove ignores anything that is not strictly longer. It also runs after a reconnect, where the same gap reappears.

Left alone deliberately: waitForOpponent still subscribes with a 60s cursor, so join detection can lag by up to a minute. It has the same root cause but sits on the join path rather than the move path, and I would rather not change that on a demo day. Filed separately.

This pull request has no versions submitted yet.