Found while smoothing the client, and flagged in pull #11 as pre-existing rather than fixed there.
A connection that gets no seat — no session cookie, or a second client from an address that already holds one — is sent the spectator snapshot: score, round, timer, feed, and no me and no players, which is the whole point of it (it is what closed the both-teams vision hole).
The client does not expect that. static/val.js reaches into snap.me in several places with no guard — predicting() reads snap.me.alive, the HUD reads snap.me.haste, look() compares p.id === snap.me.id — so a spectator's first frame throws and the page stops drawing. Whoever opens a second tab, or clicks in with cookies blocked, gets a dead screen rather than the watching view we deliberately built for them.
What it should do: draw the spectator view — the scoreboard, the round, the clock, the kill feed — and say plainly that this connection is watching because somebody at this address is already playing. No map, no bodies: it has no positions to draw and must not invent any.
Worth a test in client.test.ts that feeds the bundled client a spectator snapshot and asserts it renders without throwing, since this is exactly the shape no existing test covers.
Found while smoothing the client, and flagged in pull #11 as pre-existing rather than fixed there.
A connection that gets no seat — no session cookie, or a second client from an address that already holds one — is sent the spectator snapshot: score, round, timer, feed, and no
meand no players, which is the whole point of it (it is what closed the both-teams vision hole).The client does not expect that.
static/val.jsreaches intosnap.mein several places with no guard —predicting()readssnap.me.alive, the HUD readssnap.me.haste,look()comparesp.id === snap.me.id— so a spectator's first frame throws and the page stops drawing. Whoever opens a second tab, or clicks in with cookies blocked, gets a dead screen rather than the watching view we deliberately built for them.What it should do: draw the spectator view — the scoreboard, the round, the clock, the kill feed — and say plainly that this connection is watching because somebody at this address is already playing. No map, no bodies: it has no positions to draw and must not invent any.
Worth a test in
client.test.tsthat feeds the bundled client a spectator snapshot and asserts it renders without throwing, since this is exactly the shape no existing test covers.