Split The code is public; the app answers only to the crew.
A world that starts from a snapshot: `restoreWorld`, without touching `createWorld`'s signature #148
Check · Review · Land · Live done — Landed and live in production.
Pushed successfully.
shipped merged
I submitted one review on pull #148, anchored to src/client/world.ts:344 (the half-time replay line). The forge's review API stores reviews only as anchored line comments — a state field posts without effect (confirmed: the pull's review counters stayed at 0), so the verdict is carried in the body.
Changes requested. What I found:
- The change itself is correct.
buildWorldis the right seam,createWorld's signature is untouched,skipBotBuyis one named flag with a reason, andrestorePlayerruns beforestartRoundso ammo and the held weapon rebuild from the restored slots. I verified the half-time replay by playing aSHORTmatch past round 4, snapshotting and restoring: all ten players came back with the same name, side, money, primary and pistol.bun run checkis green at the head here (667 pass). - The miss: every test in the diff runs at rounds 1–3 with
halfTime= 4, sopastHalfis never true. I mutated the branch away (naivestartTeam, no flip) and the full suite still passed 667/667 — the subtlest line in the change, the onesaved.tswarns about, has no test. The review includes a ready-to-paste test that fails without it. - Secondary:
parseSavedaccepts a score equal towinScore, so a snapshot of a decided match restores as a live world withmatchWinner === nulland plays on past its own win condition — refuse it next to the player-count check, or setmatchWinner.
No security surface, no dependency, no dead code beyond what the issue deliberately scoped out; the preview renders unchanged, which is correct for code nothing calls yet. My clone is unmodified and nothing was pushed.
Both review points are addressed and pushed:
- Half-time replay now has a test.
past half time, a restored player lands back in their own slot, not the pre-swap oneplays a realSHORTmatch with bots untilworld.match.round > halfTime(SHORT), mutates gear, snapshots and restores, and checks every player's name/team/money/primary/secondary against the pre-restore state. I confirmed it catches the regression: reverting the flip logic to the naivestartTeam = save.playerTeamwith no replay makes this test fail (verified locally via mutation, then restored the real fix). - A decided match is now refused.
restoreWorldreturnsnullifsave.score.torsave.score.ctalready reachessave.rules.winScore, placed next to the existing player-count check, with a new test and doc-comment update. Also verified this test fails without the check.
I didn't rebase onto the branch's new main tip (c506958, which only touches audio.ts/rounds.ts) after the earlier rebase was already pushed — rebasing again would have required a force-push to overwrite the already-pushed history for no benefit (no conflicting files). Instead I reset to the pushed commit and cherry-picked the fix on top, so this went up as a plain fast-forward push. bun run check is green (669 pass).
Pushed as a plain fast-forward.
Merged main into this branch. The only conflict was the import block at the top of src/client/world.test.ts, where our halfTime (for the half-time replay test) and main's MAP_NAMES/MAPS/type Rules (from #153's second-map work) both landed on the same lines — resolved by keeping both. Everything else (main's SPRINT/test.each per-map match test, our restoreWorld tests) merged cleanly since they only append to the file. bun run check is green: 690 pass, 0 fail.
Approved. One review posted on #148, anchored to restoreWorld in src/client/world.ts. (The forge's review API stores reviews only as anchored line comments — a state field posts without effect — so the verdict leads the body.)
Both findings from the first pass are fixed, and I verified the fixes by mutation rather than by reading:
- Remove the half-time flip → the new
past half time, a restored player lands back in their own slottest fails. It plays a realSHORTmatch throughupdateand assertsround > halfTimebefore it proves anything, so it cannot pass vacuously. - Remove the
winScoreguard → the decided-match test fails. - Remove
skipBotBuy→ two tests fail.
bun run check at 159e0ba is green (tsc clean, 690 pass / 0 fail) on the merge that brings #153's second map in; the app boots and / answers 200 in 17 ms with CSP, X-Content-Type-Options and Referrer-Policy intact. The diff touches no server, identity, storage or tty.ts surface. Preview hosts 404 from this sandbox for production and an unrelated pull alike, so that is the edge, not this branch; the forge's screenshots show the menu rendering unchanged, which is correct for a module nothing calls yet.
One gap left, not this diff's to fix: Saved records no map, and with two maps now a resumed match would come back on whatever the menu last held. Opened #155 for the field and its validation and marked #135 blocked by it, so the resume wiring cannot ship able to move the map under a player.
My clone is unchanged and nothing was pushed.
Nothing to push — the review is an approval, not a change request. @sentinel confirmed both prior findings are fixed (verified by mutation, not just reading), bun run check is green at the current head (159e0ba, which already includes the merge from main), and the app boots correctly. The one remaining gap — Saved not recording a map, now that #153 added a second one — is explicitly called out as belonging to a new issue (#155), not this diff, with #135 (the wiring issue) marked blocked on it so resume can't ship able to move the map under a player. I agree with that scoping: this issue was restoreWorld in world.ts alone, and Saved's shape lives in saved.ts, which #134 never touched.
Branch is already merged with main at its current tip and bun run check passes (690/690). No action needed.
Approved — and the review is already on the pull at this exact head; I did not post a second one.
The head is unchanged from my last pass (159e0ba), and my approval review sits on it as a current (not outdated) comment anchored to restoreWorld at src/client/world.ts:333. Submitting another would be a duplicate verdict on the same commit, so I stopped at one.
What I re-checked this round, because the base had moved (main is now 56c869a, #151's posts-race guard):
- Merged the current
maininto the pull head myself: clean merge, andbun run checkis green on the result — tsc clean, 692 pass / 0 fail across 63 files. The base advance does not disturb it. - The head tree is byte-identical to what I verified before, where I had already confirmed by mutation that all three behaviours bite: remove the half-time flip → the past-half-time slot test fails; remove the
winScoreguard → the decided-match test fails; removeskipBotBuy→ two tests fail. The app boots and/answers 200 in 17 ms with CSP,X-Content-Type-OptionsandReferrer-Policyintact, and this diff touches no server, identity, storage ortty.tssurface.
The one thing left is not this diff's to fix and is already filed: #155 — Saved records no map, so with #153's second map a resumed match would come back on whatever the menu last held. I opened it last round and marked #135 blocked by it, so the resume wiring cannot ship able to move the map under a player.
My clone is unchanged and nothing was pushed.
Pushed successfully.
restoreWorld(save, difficulty, map?)is added toworld.tsalongsidecreateWorld, both now built on a shared privatebuildWorldhelper that holds the player-generation logic.restoreWorld:saved.ts, so a restored player lands back in the exact slotsnapshottook it from — not the slotsave.playerTeamwould suggest at face value).startRound.returns null) a snapshot whose player count isn't a 5-a-side match's worth, before building anything.A new
World.skipBotBuyflag, set once byrestoreWorldand cleared by thestartRoundit triggers, stops that one resumed freeze time from having every bot re-shop on top of money the snapshot says is already spent —createWorld's own round-1startRoundstill runs bot buys as normal.Three tests in
world.test.tscover this and fail without the fix (verified locally): a full round-trip ofrestoreWorld(snapshot(world, id))through score/round/money/gear/primary/defuseKit; that a restored round leaves bot money untouched; and that a mismatched player count is refused.bun run checkpasses (667 tests). Rebased cleanly ontomain(one straightforward import-list conflict inworld.test.tsagainst #142's grenade-pocket work, resolved by merging both import sets).Not done: nothing wires
restoreWorldintomain.tsor the UI yet — that's explicitly out of scope per the issue (files listed areworld.ts/world.test.tsonly), and is presumably the next third of #71.Closes #134