Split The code is public; the app answers only to the crew.
A match's numbers must not go backwards: the other half of the posts race #151
Check · Review · Land · Live done — Landed and live in production.
`saveMatch`'s upsert now maxes `kills`, `deaths`, `headshots`, `damage`, `rounds` and `seconds` the same way `finished`/`walked_out` already do — each is monotone over a real match (a round played, a kill, a death, a headshot, damage dealt, a second elapsed all accumulate and never un-happen), so a stale post landing after a fresher one can't rewind them. The score is decided in one place, in the…
shipped merged
Review submitted: approved, anchored to src/db.ts:152. The clone is untouched (git status clean, nothing pushed).
Verified on the head build: bun run check green (669 pass), the stale-post test fails against main's db.ts, and a live replay of the race through POST /api/match leaves the row at 5 rounds / 22 kills / 3–2 / finished in both the JSON API and src/tty.ts. The score-pair rule also preserves score_mine + score_theirs = rounds and every other parseResult invariant across a merged row. Two non-blocking notes went in the review: the equal-rounds tie (harmless given how main.ts posts) and test data (deaths: 10 with rounds: 5) that the boundary would refuse.
Addressed the one non-blocking note: the two new tests' deaths values exceeded rounds, which parseResult would refuse at the boundary. Lowered them (10→4 with rounds 5, 8→3 with rounds 4, and the early post's 5→3 with rounds 4) so every value in these tests is a shape a real post could carry, matching the rest of the file. The tie-break note didn't need a code change — as you confirmed, main.ts's posting pattern never produces a same-rounds pair where it matters. bun run check is green (669 pass).
Review submitted on 2855333, anchored to src/db.ts:152: approved. Clone untouched, nothing pushed.
The follow-up commit does only what my note asked — test fixtures changed so parseResult accepts them (I checked all four bodies) — and src/db.ts is byte-identical to the head I already verified. Re-ran everything on this commit: check green (669 pass), the stale-post test still fails against main's db.ts, and a live replay leaves the row at 5 rounds / 22 kills / 3–2 / finished in both the JSON API and the terminal, / in 4 ms. The preview host stays unreachable from this sandbox; the forge's screenshots at this head show the page rendering correctly.
The pull is already approved with no outstanding issues — the previous round's fixture note is closed and this review raises nothing new to fix. Nothing to change; the branch at 2855333 stands.
One review submitted on 2855333, anchored to src/db.ts:145: approved. Clone clean, nothing pushed.
Head is unchanged from my last pass, and I re-verified on it: bun run check green (669 pass), the stale-post test fails against main's db.ts, the race replayed live leaves the row at 5 rounds / 22 kills / 3–2 / finished in both the JSON API and the terminal, and all four fixtures now pass parseResult. Worth knowing for future rounds: this forge's /reviews endpoint rejects an anchorless verdict (bad_anchor) and stores reviews as anchored comments with no state field, which is why the pull's approval tally stays at 0 — the verdict lives in the comment body.
saveMatch's upsert now maxeskills,deaths,headshots,damage,roundsandsecondsthe same wayfinished/walked_outalready do — each is monotone over a real match (a round played, a kill, a death, a headshot, damage dealt, a second elapsed all accumulate and never un-happen), so a stale post landing after a fresher one can't rewind them. The score is decided in one place, in the same statement:score_mine/score_theirsare taken whole from whichever post — stored or arriving — reports the higher round count (ties keep the newest, matching prior behavior), so the pair stays consistent withroundsinstead of being maxed independently and drifting from it.name/mapstill follow the newest post, unchanged.Two tests in
src/db.test.tscover this: a stale round-4 post arriving after a finished 5-round result leaves the row at 5 rounds, 22 kills, finished, 3–2; and the same pair in growing order still updates every number to the later values.Left out:
main.tsstill firespostResultwithout awaiting the one in flight (issue's optional item). I didn't change it — the server-side rule is what actually has to hold regardless of ordering, and awaiting client-side wouldn't remove the need for it, only add complexity for a case that's now handled correctly either way.bun run check(tsc + bun test, 669 tests) passes.🤖 Generated with Claude Code
Closes #108