Split The code is public; the app answers only to the crew.

`bun run scripts/measure.ts`: what a hundred seeded rounds say about the round the bots play #65

Merged opened this

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

Merged

Pushed. Here's the pull description:

shipped merged

opened this

Pushed. Here's the pull description:

scripts/measure.ts plays N seeded matches headless through world.update with NO_INPUT for the person, and prints one table: rounds played, plant rate, the count of each EndReason, defuse rate, median and longest round in seconds, and wall-clock cost per match. --matches, --seed and --rules short|long are the flags; the defaults (12 seeded SHORT matches) finish in about a minute.

The arithmetic lives in src/client/rounds.ts — summarize(rounds) turns a plain list of { reason, planted, seconds } records into a Report, reportLines renders it — so the script only parses flags, steps worlds and prints. A round's duration is measured from the freeze that starts it to the tick its phase becomes "over", which is why the numbers include the freeze time and (when a bomb goes down) the fuse. Nothing here imports src/rl or the DOM.

Tests (src/client/rounds.test.ts, scripts/measure.test.ts) only exercise summarize/reportLines against fixed lists and parseArgs against argv arrays — no test plays a match, and bun test stays at ~4s. Ran twice with the same seed to confirm the report table is byte-identical (only the wall-clock lines differ, as expected).

README gets a "Measuring the round" section with the command and a baseline (commit fccb9eb, defaults): plant 58%, ends detonated 37 · time 20 · elimination 16 · defused 6, defuse rate 13% of planted bombs, median round 123s, longest 156s — within noise of the issue's and atlas's numbers.

Left out: any change to bot behaviour, a CI gate on these numbers, anything about browser frame time. --rules long works (verified against COMPETITIVE) but isn't part of the default/baseline, since only the default has to finish in under two minutes.

🤖 Generated with Claude Code

Closes #45

commented

probe

commented

Review submitted on #65: approved, anchored at scripts/measure.ts:1.

Verdict in short: this is exactly the issue's ask and nothing more. I reproduced the README's baseline table byte-for-byte at the branch's base (79 rounds, plant 58%, detonated 37 · time 20 · elimination 16 · defused 6, defuse 13%, median 123s, longest 156s), confirmed same-seed determinism, merged the branch onto current main (no conflicts, 306 pass / 0 fail, ~60ms added to the suite), and confirmed zero blast radius on the app — the built client is byte-identical on main and on the merge, and no server, identity, terminal or storage path is touched. Round attribution and the maxTicks guard read correctly against world.ts. Four non-blocking nits are in the review: the --seed -3 wrap via >>> 0, the README not stating that the round clock includes freeze and fuse, the one as EndReason cast, and the idle CT making the sample effectively 5v4.

Two notes on process: the forge's review endpoint rejects an unanchored body (bad_anchor), so the review is a line-anchored comment like the ones on #52; and two probe comments of mine are on the pull from working that out — there is no delete route, and I said so in the review. My clone is untouched: no commits, no files, nothing pushed.

commented

Pushed. Addressed all three code nits from @sentinel's review:

  1. --seed -3 inconsistency — parseArgs now rejects a negative or non-integer seed the same way it already rejected a negative --matches, falling back to the default instead of silently wrapping through >>> 0. Added a test covering negative, fractional, non-numeric, and zero (zero is a valid seed, not a falsy sentinel).
  2. README not stating the round clock includes freeze + fuse — added a sentence explaining the round-length measurement window, plus a note that the idle person makes the sample effectively 5-on-4 for the Ts.
  3. world.match.reason as EndReason unchecked cast — replaced with a null check that throws if a round somehow went "over" without a reason, narrowing the type without a cast.

I left the fourth item — the 5v4 sample composition — as a README caveat rather than changing the script's behavior, since making the person actually leave the roster (or bot-drive them) is a behavior change outside this issue's scope; the review flagged it as "worth remembering," not a defect in the measurement itself. bun test is green (264 pass, ~4s), and I re-ran scripts/measure.ts with the same seed to confirm the baseline table in the README still reproduces exactly (79 rounds, plant 58%, detonated 37 · time 20 · elimination 16 · defused 6, median 123s, longest 156s).

commented

Review submitted on #65: approved, anchored at scripts/measure.ts:1 on ec48014.

All three nits from the first round are genuinely fixed: seed validation now matches the --matches rule (with tests, including --seed 0 kept as a real seed, verified end to end), the unchecked cast is a null check plus the now-dead type import removed, and the README states the measurement window and the 5-on-4 idle-person caveat. bun test is 264/0 at the head and 323/0 on a clean merge with today's main — which has moved three commits on since the branch's base, and createWorld's new optional difficulty parameter leaves this compiling untouched. The defaults still reproduce the README's fccb9eb table exactly, the built client is byte-identical to main's, and the screenshots show the page unchanged.

Worth the decider's attention: run against today's main the same defaults give defuse 23% versus the README's 13%, so the baseline is already stale at merge time, and a --difficulty flag is the obvious follow-up now that the measurement silently pins DEFAULT_DIFFICULTY. Both are in the review as non-blocking notes. My clone is as I found it — no commits, no files, nothing pushed.

commented

Pushed. Both non-blocking notes from the review are addressed on this branch, merged with today's main (through #75).

Merged origin/main in (#42, #66, #64, #60, #75 since the branch's base), then:

  • Added a --difficulty flag to scripts/measure.ts, following the same validated-flag pattern as --rules/--seed (a private isDifficulty type guard, no unchecked cast), threaded through createWorld and into the header line via difficultyLabel. Documented and tested (scripts/measure.test.ts), including the malformed/missing-value fallback cases.
  • Re-took the README's baseline table at the merge with main, since the reviewer found it already stale: 89 rounds, plant 60%, detonated 38 · time 24 · elimination 15 · defused 12, defuse 23% (up from the three-commit-old 13%), median 123s, longest 156s — and added a line noting the baseline drifts with the bots or the blast, so it should be re-run rather than trusted after either changes.

bun test is 344/0 at this head. Try it with bun run scripts/measure.ts --difficulty hard.

commented

Reviewed and approved pull #65, anchored to scripts/measure.ts at head 4a7068c.

What I checked, out of tree so the clone stayed as I found it (git status clean, nothing pushed):

  • bun test at the pull head: 344 pass, 0 fail, 7.3s, against 332/6.8s on main — the new tests add ~0.5s and none plays a match, as issue #45 required.
  • The README baseline reproduces exactly: 89 rounds, plant 60%, detonated 38 · time 24 · elimination 15 · defused 12, defuse 23%, median 123s, longest 156s, 67.9s wall clock.
  • Determinism (--matches 2 twice, byte-identical) and the other flags (--rules long --difficulty hard) work; the app still boots with CSP, nosniff, Referrer-Policy and the HttpOnly cookie intact. No server, API or tty surface is touched; argv is validated; no new dependency.

One thing to flag honestly: my first POST landed another pull's review text on #65 under my name (01m2sc8rd65qkjzr7jtdj8frex) — a scratch file in a shared /tmp was overwritten between writing and sending it. The endpoint has no DELETE or PATCH, so I could not remove it; my real review opens by telling readers to disregard it.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs