VS A FRIEND on the mode screen (host a code, join a code), a ~110-line
RFC6455 relay in server.py that holds no game state, and src/net.js: each
phone plays itself near-side and the opponent's court is this one rotated
half a turn about the net. The phone a ball flies toward makes every call
on it; the striker's copy is picture. Six tests pin the rule from both
seats. No reconnect yet — a dropped radio is a card and a reload.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The cold-start complaints — late fonts, zombies missing from the stats
screen — were one header. server.py sent no-cache, must-revalidate for
everything, so a fresh launch revalidated ~230 sprites and three fonts
before the game had a full set of frames. Assets under /assets now cache
for a year when the URL carries ?v= (changing the file changes the URL,
which is what SPRV is for) and a day when it does not; the fonts get a
?v= too. HTML and JS stay no-cache, so a deploy is still visible.
Two client-side halves of the same report: a dropped sprite request used
to be permanent for the session — the sheet stayed undefined and the
zombie drew as some other design — so each one gets one retry. And the
splash now holds its buttons back behind a loading bar until the last
sheet has landed and both faces are ready, instead of letting you open
the stats screen while half of it is still in flight.
The bar is driven by setInterval, not rAF: rAF does not fire in a hidden
tab, and an app that loads while backgrounded would come back to a frozen
bar and a menu with no buttons on it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nothing secret is in it — the .env never enters the image and the client is
public JS regardless — but handing out server.py and the asset-pipeline
scripts to anyone who guesses a filename publishes the exact validation rules
for free.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ranked on KILLS IN A SINGLE RUN. Games survived is the same information at a
tenth the resolution — most players land between 5 and 20, so that board is a
wall of ties broken by who got there first. Games ride along as the tiebreak
and as a column.
The image is python:3.12-alpine running server.py now, not nginx. The board
needs one process that can write, and everything the nginx conf did here — the
no-cache headers and the manifest MIME type — is a few lines of stdlib. /data
must be a persistent volume or every deploy resets the ladder to empty.
A name belongs to whoever took it first: the client mints a random id once and
the row remembers it, so a stranger cannot overwrite your score. The id never
leaves the server. That is the whole security model and it is enough.
The prompt to sign the board appears on exactly one occasion — a run that beat
your own best kill count, on the tap that leaves the death screen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>