Public: anyone can read this; the crew can change it.
forked from trevato/cs · proposes
A match you can play again: the id is a thing you can paste, and you get the same map, seed and bots #197
Check · Review · Land · Live done — Landed and live in production.
The id is now the app's most useful string. `src/matchid.ts` is the one door that says what a match id is — hex and dashes, 8–64 characters, exactly the shape `src/result.ts` has always accepted — and the result parser, the `?match=` query and the menu's field all ask it instead of each holding a copy of the regular expression. The menu takes an id (pasted, or put there by `/?match=<id>&map=<de_fo…
shipped production is serving it deploy ae07c2d
The id is now the app's most useful string. src/matchid.ts is the one door that says what a match id is — hex and dashes, 8–64 characters, exactly the shape src/result.ts has always accepted — and the result parser, the ?match= query and the menu's field all ask it instead of each holding a copy of the regular expression. The menu takes an id (pasted, or put there by /?match=<id>&map=<de_forge|generated>, which the server validates and the page escapes), src/client/main.ts validates the field again before anything reaches generateLevel, and anything that is not an id is a fresh match plus a sentence under the field — never an error page, and the sentence comes back off when the id is fixed. What you are playing is on the page in a This match section under the stage — the id (one click selects it whole), the map's name, and Link to this match as a real 44px <a href> — and in words in the pause panel, for the full screen; the terminal's matches tab prints the id of the row under the cursor, ready to paste into the same field.
A rematch is a new row because the id and the row key are different strings: the id is the match, and a result is posted under <match>--<nonce> (twelve hex from a fresh uuid) for this playing of it, so two people on one id, or one person twice, are separate rows and matchOf still reads the match back out of a key. A player who never touches any of this gets exactly today's behaviour — fresh uuid, that uuid as the row key, de_forge — and src/server.test.ts asserts the unlinked page's markup is unchanged.
Worth looking at: the end-to-end test in src/client/main.test.ts folds two worlds started by the page from one pasted id through the engine's digest door for fourteen seconds of the fixed script (the whole freeze plus two live seconds) and gets the same hash, with withoutEntropy on so a stray Math.random would throw rather than hide; one character away hashes differently. Two choices: ?map= rides along in the link because the id carves a map only when the match asked for a generated one, and a link that reproduced the bots but not the layout would be a lie — it takes only the Map control's own two values, so #148 widens that validator to seed names rather than fighting it; and I left the Map control otherwise alone, so #148 still has the fold-into-one-choice work the issue asks of whichever lands second.
main has been merged in. Both sides had raised the app's bundle ceiling for their own feature, so the merge keeps #185's larger 96,000 / 43,500 and drops this branch's 95,000; #173's cost is re-measured on top of main at 728 raw / 350 gzipped (92,018 → 92,746 raw, 41,774 → 42,124 gzipped), which fits with 2,254 raw bytes to spare, and the note in src/games.ts says so. static/ is one app bundle again, rebuilt from the merged sources — main carries two committed app.*.js, one of them stale, which its own build tests would flag. bun test: 877 pass, 0 fail.
🤖 Generated with Claude Code
The id is now the app's most useful string.
src/matchid.tsis the one door that says what a match id is — hex and dashes, 8–64 characters, exactly the shapesrc/result.tshas always accepted — and the result parser, the?match=query and the menu's field all ask it instead of each holding a copy of the regular expression. The menu takes an id (pasted, or put there by/?match=<id>&map=<de_forge|generated>, which the server validates and the page escapes),src/client/main.tsvalidates the field again before anything reachesgenerateLevel, and anything that is not an id is a fresh match plus a sentence under the field — never an error page, and the sentence comes back off when the id is fixed. What you are playing is on the page in a This match section under the stage — the id (one click selects it whole), the map's name, and Link to this match as a real 44px<a href>— and in words in the pause panel, for the full screen; the terminal's matches tab prints the id of the row under the cursor, ready to paste into the same field.A rematch is a new row because the id and the row key are different strings: the id is the match, and a result is posted under
<match>--<nonce>(twelve hex from a fresh uuid) for this playing of it, so two people on one id, or one person twice, are separate rows andmatchOfstill reads the match back out of a key. A player who never touches any of this gets exactly today's behaviour — fresh uuid, that uuid as the row key, de_forge — andsrc/server.test.tsasserts the unlinked page's markup is unchanged.Worth looking at: the new end-to-end test in
src/client/main.test.tsfolds two worlds started by the page from one pasted id through the engine'sdigestdoor for fourteen seconds of the fixed script (the whole freeze plus two live seconds) and gets the same hash, withwithoutEntropyon so a strayMath.randomwould throw rather than hide; one character away hashes differently. Two things I chose:?map=is included in the link because the id carves a map only when the match asked for a generated one, and a link that reproduced the bots but not the layout would be a lie — it takes only the Map control's own two values, so #148 widens that validator to seed names rather than fighting it; and I left the Map control alone otherwise, so #148 still has the fold-into-one-choice work the issue asks of whichever lands second. The app bundle grew 728 raw / 359 gzipped bytes (91,492 → 92,220 raw), 220 past the old raw ceiling, so the ceiling moved to 95,000 / 43,500 with the reasoning written intosrc/games.ts.bun test: 870 pass, 0 fail.🤖 Generated with Claude Code
Closes #173