Nothing in the map covers the failure a 20 Hz game actually has. One throwing tick kills the interval,
the process keeps serving, / answers 200 in 7 ms, the page renders a roster and a clock — and the
sea is frozen. Everything looks healthy except the only thing that matters.
Done looks like
[ ] A tick that throws does not end the loop: the error is caught at the tick boundary, logged once
with the tick number, and the loop continues. A tick that throws repeatedly ends the round
cleanly — scores written, round.ended emitted, a new round started — rather than ending the
world.
[ ] The loop knows whether it is alive: the round exports the last tick's number and wall time, and a
staleness rule (say, no tick for two seconds while a round is running) that is one function and
is tested by moving the clock, not by sleeping.
[ ] GET /health answers JSON a machine can read and a person can skim: ticking or not, the tick
number, how long since the last one, the round id and its clock, the roster size, the socket
count, and the schema version. It is public, it names no player, and it carries the floor.
[ ] HEAD / stays the cheap liveness check for the forge; /health is the one that knows about the
world, and the README says which is which.
[ ] The page does not lie: when the world is not ticking the arena says so plainly instead of drawing
a still frame that looks like calm water. One line, server-rendered, and live over the SSE bus.
[ ] A deliberately throwing tick in a test proves all of it: the loop survives, /health reports the
stall, the page's region changes, and the next round starts.
Risks The temptation is to swallow the error and carry on silently, which is how a game ends up
frozen for an hour before anyone notices; log it once per round, not once per tick, or a bad tick
becomes a log flood. Do not put a player's name in /health — it is a public door. Keep the staleness
rule out of the route: it is a function of the round's numbers and belongs where it can be tested.
Nothing in the map covers the failure a 20 Hz game actually has. One throwing tick kills the interval, the process keeps serving,
/answers 200 in 7 ms, the page renders a roster and a clock — and the sea is frozen. Everything looks healthy except the only thing that matters.Done looks like
with the tick number, and the loop continues. A tick that throws repeatedly ends the round cleanly — scores written,
round.endedemitted, a new round started — rather than ending the world.staleness rule (say, no tick for two seconds while a round is running) that is one function and is tested by moving the clock, not by sleeping.
GET /healthanswers JSON a machine can read and a person can skim: ticking or not, the ticknumber, how long since the last one, the round id and its clock, the roster size, the socket count, and the schema version. It is public, it names no player, and it carries the floor.
HEAD /stays the cheap liveness check for the forge;/healthis the one that knows about theworld, and the README says which is which.
a still frame that looks like calm water. One line, server-rendered, and live over the SSE bus.
/healthreports thestall, the page's region changes, and the next round starts.
Files
src/round.ts,src/routes.ts,src/page.ts,src/round.test.ts,README.md.Risks The temptation is to swallow the error and carry on silently, which is how a game ends up frozen for an hour before anyone notices; log it once per round, not once per tick, or a bad tick becomes a log flood. Do not put a player's name in
/health— it is a public door. Keep the staleness rule out of the route: it is a function of the round's numbers and belongs where it can be tested.Waits on #8. Fits Vesper's brief.