Public: anyone can read this; the crew can change it.

forked from trevato/cs

The shooter's entry sheds its second concept: posting a result becomes its own module #262

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`src/client/report.ts` now owns a posted round end to end: the world turned into the body `src/result.ts` accepts, the one pending result (never a queue — a newer round replaces an older one), the single attempt in flight, the retry count and the backoff it waits out. It takes a `fetch`-shaped call, a clock, and the two things it has to say out loud — the sentence by the leaderboard and the board…

shipped production is serving it deploy ae07c2d

opened this

src/client/report.ts now owns a posted round end to end: the world turned into the body src/result.ts accepts, the one pending result (never a queue — a newer round replaces an older one), the single attempt in flight, the retry count and the backoff it waits out. It takes a fetch-shaped call, a clock, and the two things it has to say out loud — the sentence by the leaderboard and the board a 200 carries back — so it touches no document and no global; pending.ts stays the policy it asks about each answer, with its header pointed at its new owner. main.ts keeps only the wiring: createReporter({ fetch, now, say, board }), reporter.send() once a frame, and reporter.post(world, playId, name, final) on the frame a round is scored.

Behaviour is unchanged by construction — it is the same code, the same nextStep, the same /api/match body, the same map recorded with it, and the same "not posted" sentences in the same two places. The tests moved with it: report.test.ts drives the queue against a scripted fetch and a clock it moves itself (a fetch that fails once and then succeeds lands exactly one result, a 429 waits out its backoff, a 422 is never retried, the round retried is the newest one, four attempts is the cap, and standing() never claims a match is on the board while bytes are still in hand), and main.test.ts keeps the wiring tests — one POST per round scored in the shape the server accepts, the map it was played on, the retry ridden by the frame loop, and the summary's line.

What I chose not to do: fold pending.ts into the new module. They are two concepts — what a status means is arithmetic, what to do about it is a round, a network and a clock — and pending.test.ts is the contract for the first of them, so it stayed its own file and only its doc comment changed.

Bundle: 98,864 → 98,981 raw and 38,966 → 39,049 gzipped (+117 / +83, the seam itself: a module boundary the minifier will not inline across, the object the frame lends the reporter, and the endpoint named rather than inline). That fits the 103,000 / 41,000 ceiling with 4,019 raw and 1,951 gzipped to spare, so no ceiling moved, and neither other bundle changed a byte. bun run test is green (1,150 pass). To try it in the preview: play a round with the network throttled or offline and watch the line under the leaderboard go from retrying to saved or lost, and the summary's line refuse to say "saved" until the last post is answered.

🤖 Generated with Claude Code

Closes #251

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs