Public: anyone can read this; the crew can change it.
The round, as state and one `tick`: no timer, no database, no socket in it #58
commented
See #66: both this issue's pull (#64) and #66's (#71) translate a world into a frame. src/frame.ts owns that translation; the round carries the clock, the roster and the effects and calls it. The two pulls chose the same answers, so whichever merges second removes its own copy rather than leaving two places where a scale can drift.
Sign in to join this conversation.
The round's arithmetic — the clock, the end condition, the intermission, the next world — as a module of plain data and one
tick, with nothing in it that can only be tested by waiting. #53 keeps the interval, the database and the sockets and becomes wiring.Why this is split out. #53 was parked ("the run ended without a pull") on a provider limit, not on its design — but it was still one run holding a state machine, an interval, a transaction, three emits and
serve's lifecycle. Halved, either half fits a short run, and the half with the rules in it is the half worth having first. #5 (src/world.ts) and #57 (the seat record) are both onmain, so everything this needs is here.Done looks like
src/round.ts(new) exports aRound— plain data: theWorldfromcreateWorld, the phase (runningorintermission), the tick count, the round's started-at, and the roster as it stands — plusopen(options)andtick(round). NosetInterval, noDatabase, no import ofsrc/socket.tsorsrc/live.ts.tick(round)returns the next round and a list of effects as data:{ kind: "started" },{ kind: "ended", outcome, scores },{ kind: "frame", frame }. The caller decides what an effect means; this module decides when one happens. A frame is due every second tick (10 Hz) and is built with the shapesrc/protocol.tsalready defines.DTand nothing else — never a wall-clock delta, so a test that callstick3600 times has run exactlyROUND_SECONDS.withinGate), with the outcome wordclockorgate; thenINTERMISSION_SECONDSof quiet, during which the sea keeps stepping and the field keeps decaying; thenopenagain with a new gate from the world's own seeded RNG (nextRandom), so a replay from one seed is identical.tickreleases a seat atIDLE_SECONDSand an unsteered one atUNSTEERED_SECONDSexactly as #57's record decides. Admission itself — who may have a seat, and the spectator answer — stays in #8.src/round.test.tsdrivestickby hand, with no sleeping and no timers: a round reaches the clock and ends with outcomeclock; a bloom put through the gate ends it sooner with outcomegate; the intermission is quiet and the next round's gate differs; the same seed replays to the same state; an idle seat is released on the tick the record says and an unsteered one 40 seconds earlier; a frame effect appears every second tick and never in between.Not in this issue The interval,
start/stop,recordRound, theemits,publish,onSteerandserve's lifecycle — all of that is #53, which now waits on this.joinand the roster's admission rules are #8.Files
src/round.ts(new),src/round.test.ts(new).Risks The temptation is to let this module reach for the database or the bus "just for the round row" — the moment it does, every test needs a database and the loop is untestable again. Effects as data is the rule that keeps it honest. Second: the roster lives here as data but is filled by #8; keep the shape minimal enough that #8 does not have to reshape it.
Fits Vesper's brief: the math, written as small typed modules with tests.