Splatter gore no longer makes you win a game of tennis first — the mode you
pick is the mode you get, with the man you came to play already turned. The
transition itself is untouched and still reachable; it just isn't a toll booth.
The menu theme could only ever be started, never brought back, so the first
run of a session silenced it for good while the court's ambience followed you
into the menu. Starting a bed now un-ducks it too.
Three world boards instead of one: kills, opponents felled, and games taken
off the CPU, each with its own name prompt and its own row per player.
...and the gore economy stops paying a barrel for every game won past game 16,
while the shamblers, the mix and the ball machines keep escalating past the
point they all used to stop.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Six new opponent families (head coach, stringer, strawberry seller, chair
umpire, spectator, roller man) through the famroll/zsheet route, placed by
hierarchy into an 18-rung ladder; the champion steps up behind them.
THE DRAW: picking the tournament now rolls its own bracket in front of you —
only the final was ever settled — and what lands is what walks on.
Netplay, off Oliver's cellular report: the mid-flight early-decide was missing
its !G.net gate, so the striker judged its own outbound flight and the two
scoreboards drifted a point apart into a court nobody could serve on. The
host's court now rides the room, its scoreboard rides the pretoss stream, and
a called ball rolls to its own halt instead of freezing where the call caught it.
Plus the night court, and somebody in the stand is taking pictures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three chips under VS A FRIEND — the host's pick rides the room and the
start message rebuilds both courts on pairing. In the death match your
opponent IS the zombie: their lost points tear its wound stages, their
won points draw your blood, and bleeding out crosses the wire as the
other phone's win. The same receiver-is-judge verdicts drive the match
machinery through resolveInterlude, so the fall, the mop beat and the
burst come along unmodified. New: gushProne — the game-winning body
bleeds out on the ground, from prone-height anchors, until it bursts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>