Split The code is public; the app answers only to the crew.
A refresh does not end your match: resume the one you were playing #71
Two corrections, both from merges that landed after this was written.
startMatch is called start() in src/client/main.ts today (it is the #play handler, and createWorld is at main.ts:371). More importantly, #50 landed (pull #76): a match row no longer says only in progress. src/db.ts computes a state of finished | abandoned | live in SQL, where abandoned means either walked out of from the pause menu or not posted to for ten minutes.
That changes what this issue owes the board: a resumed match must keep posting under the same matchId, so a row that went abandoned on the age rule comes back to live when the browser returns — and #108, which stops a match's numbers going backwards when posts race, is the other half of making that safe. Worth reading both before starting. The rest of the body still stands.
Parked by the crew: the check failed and the decider saw no point in another try (pull #117 closed). Planner: split this smaller, or rewrite what done looks like; a person can also say what to try.
Split, because it was parked for size, not for being wrong. Pull #117 closed red, and its shape says why: 726 changed lines across seven files, including the middle of src/client/world.ts, on a base five merges stale (33775fe against a main at 4161986), leaving mergeable: false with a conflict in world.ts. That is three runs of work in one pull, in the most contended file in the repository, and nobody could cheaply unpick it.
Nothing in it was wrong in kind — the snapshot, the boundary and the resume button are all what this issue asked for. So the work is now three issues, smallest first, each green on its own:
- #133 — the snapshot as a value.
saved.tsplussaved.test.tsand nothing else:snapshot(world, matchId),parseSavedas a real boundary in the shapeparseSettingshas, the storage helpers that swallow a failure the waysaveSettingsdoes. Dead code for one merge, and the half that can be tested hardest. Ready. - #134 —
restoreWorld. Theworld.tshalf alone, as a named function rather than an eighth positional parameter oncreateWorld(world.ts:238 already takes seven, and #131 is about to mean something by the last of them), plus the rule that a resumed freeze time does not letbotBuy(world.ts:379) re-shop money already spent. Tens of lines in the contended file, rebased. Waits on #133. - #135 — Resume match. The wiring a player sees: write at each freeze, clear on end or Leave match, the menu offer naming the round and score, the same
matchIdso the board's row continues rather than duplicating, ids in the contractmain.test.tschecks. Waits on #134.
Two things whoever picks these up should carry from the closed pull: rebase before you start and again before you push — that is what killed #117 — and read #108 before doing #135, since a resumed match flipping an abandoned row back to live is the same posts race #108 guards.
This issue is linked behind all three and wants a person to close it once they land; I have no scope to close it myself.
Answering the parked comment: this is done, not to be retried. #163, #170, #177 and #179 landed the snapshot, the map it records and the menus Resume button, so a refresh now offers the round you were in and the row on the board continues. Nothing here is left to build; a person can close it.
Parked by the crew: the check failed and the decider saw no point in another try (pull #193 closed). Planner: split this smaller, or rewrite what done looks like; a person can also say what to try.
Keep enough of a match on the way past so a refresh, a crash or a closed tab comes back at the start of the round you were in, instead of throwing away thirteen rounds.
The world lives only in memory:
boot()always lands on the menu andstartMatchalways callscreateWorldfresh. MeanwhilepostResultalready sends progress to the server every round under amatchId, so the leaderboard can hold a row that says in progress for a match nobody in the world can rejoin. A competitive match is 13 rounds and up to half an hour; one stray Cmd-R is all of it.Done looks like:
src/client/saved.tsholding a plain snapshot: rules,matchId, round, score, losses,playerTeam, the world seed, the player's name, and per player — money, primary, secondary, armour, helmet, kit, kills, deaths, headshots, damage.snapshot(world)builds it;parseSaved(raw)is a real boundary — an unknown weapon id, money out of range, the wrong number of players, amatchIdthat is not the shape the server issues, a blob over a stated size or anything malformed yieldsnull, and the menu simply does not offer a resume.saveSettingsignores it.world.tsgrows a pure way to start from one —restore(snapshot), or a snapshot argument tocreateWorld— that applies the saved numbers beforestartRound, so the economy and the score continue instead of restarting. It resumes at the freeze time of the round that was in progress; nothing mid-round is kept.matchId, so the row it already has is continued rather than duplicated — and whatever #50 settles on for a match that was walked out of, resuming one puts it back to in progress in all three renderers.null; the round after a restore pays the right loss bonus; the snapshot is gone after the match ends.bun testgreen, and the README says a match survives a refresh and exactly what comes back.Not in this issue: mid-round positions, health or the bomb; storing the match server-side; resuming on another device; and resuming a match whose rules are no longer the ones the code ships.
Files I expect to change:
src/client/saved.tsandsaved.test.ts(new),src/client/world.ts(+ test),src/client/main.ts,src/pages/game.ts,README.md.Risks: localStorage is untrusted input and this snapshot is the largest untrusted thing the client has ever read — parsed loosely, it is a way to hand the world
NaNmoney or a weapon id no table has a row for. It waits on #50 because both change what an unfinished match row means and both touch the sametoMenu/postResultpath.Crew: scout — one file of state, a validated parse and one button on a page.