Every push to this repository redeploys the app, which drops every socket at once. The promise in #1 is
five seconds from opening the page to being in the world; it has to hold for the second visit too — the
one that happens because the server went away for a moment while somebody was mid-round.
Done looks like
[ ] The client reconnects on its own: a closed socket is reopened with backoff and jitter — fast at
first, capped a few seconds out, never a tight loop — and it gives up only when the page is
hidden, resuming when it is visible again.
[ ] Reconnecting rejoins: the same guest cookie or forge identity gets the same player back, and the
pull says what happens to their mote in the gap — held for a grace period and resumed, or dropped
and re-placed. Either is defensible; silently becoming a second player is not.
[ ] The page says what is happening in one word while it is away, and stops saying it the moment
frames resume. No spinner over the arena, no layout shift, no modal.
[ ] Interpolation does not lurch on the first frame back: a gap longer than a few frames snaps rather
than animating a second of catch-up across the arena.
[ ] The SSE bus reconnects too and replays from Last-Event-ID, so a round that started during the
gap is not missed — src/live.ts already keeps the last hundred events for exactly this.
[ ] Measured and stated in the pull: from socket close to a frame drawn again, on a normal
reconnection, in under two seconds.
[ ] Tests cover the pure parts — the backoff schedule is a function, tested for its bounds, its
jitter and that it never returns zero; the gap-detection rule is a function of two frame
numbers.
Risks Reconnect storms are the real danger: every client in the world reconnects at the same
instant after a deploy, so the jitter is not decoration — without it the app knocks itself over on its
own restart. Backoff in a game also fights the five-second promise; favour fast early attempts and let
the jitter spread them. The grace period for a mote is a gameplay decision as much as a technical one
— a mote held too long is a ghost blocking a seat.
Every push to this repository redeploys the app, which drops every socket at once. The promise in #1 is five seconds from opening the page to being in the world; it has to hold for the second visit too — the one that happens because the server went away for a moment while somebody was mid-round.
Done looks like
first, capped a few seconds out, never a tight loop — and it gives up only when the page is hidden, resuming when it is visible again.
pull says what happens to their mote in the gap — held for a grace period and resumed, or dropped and re-placed. Either is defensible; silently becoming a second player is not.
frames resume. No spinner over the arena, no layout shift, no modal.
than animating a second of catch-up across the arena.
Last-Event-ID, so a round that started during thegap is not missed —
src/live.tsalready keeps the last hundred events for exactly this.reconnection, in under two seconds.
jitter and that it never returns zero; the gap-detection rule is a function of two frame numbers.
Files
static/drift.js,static/live.js,src/socket.ts,src/round.ts.Risks Reconnect storms are the real danger: every client in the world reconnects at the same instant after a deploy, so the jitter is not decoration — without it the app knocks itself over on its own restart. Backoff in a game also fights the five-second promise; favour fast early attempts and let the jitter spread them. The grace period for a mote is a gameplay decision as much as a technical one — a mote held too long is a ghost blocking a seat.
Waits on #23 and #10. Fits Vesper's brief.