Public: anyone can read this; the crew can change it.

forked from trevato/cs

The standings can be kept per map: `standing` carries the map, and `standings` takes one or none #422

Merged opened this

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

Merged

#419 (merged as `20f4f9d`) already shipped most of this data layer. `standing` is keyed by `(user_id, name, map)`. A database from before the change rebuilds that table from `match` when it opens. `standings(db, limit, map?)` takes a `PlayedMap`, which the server checks at the boundary with `playedOn` from `src/mapname.ts`. And pruning a match already recomputes that player's row on every map. Thi…

shipped production is serving it deploy ae07c2d

opened this

#419 (merged as 20f4f9d) already shipped most of this data layer. standing is keyed by (user_id, name, map). A database from before the change rebuilds that table from match when it opens. standings(db, limit, map?) takes a PlayedMap, which the server checks at the boundary with playedOn from src/mapname.ts. And pruning a match already recomputes that player's row on every map. This pull adds the proof #414 asked for, and fixes a failing check on main. bun run test passes: typecheck plus 1743 tests.

New tests in src/db.test.ts:

  • Whole board equals the old query: after 120 writes across four maps, with guests, 0-round matches, a later post on another map, a rename and a delete, standings(db) equals the original GROUP BY over match, row for row. The per-map boards together contain each counted match exactly once.
  • Migrated database reads as before: a database from before match rows carried a map, with the old one-row-per-player standing, gives the same board after opening as it did before. That board is also its whole de_forge board.
  • de_forge excludes generated maps: the de_forge board leaves out one player's results on a generated map, while the whole board adds them in.
  • Query plan: the existing timing test now also checks the plan for the unfiltered read (map = '').

Why a kept row for the whole board, not a sum: summing a player's per-map rows means grouping every player and sorting the totals before LIMIT can apply. That is the same GROUP BY the standing table was built to replace. The kept map = '' row costs one more INSERT … SELECT per write, limited to that one player's matches. The reason is now written in the comment above the table in src/db.ts.

Query plans, for the whole board and for one map:

  • WHERE map = '': SEARCH standing USING INDEX standing_by_rank (map=?)
  • WHERE map = 'gen_1z': SEARCH standing USING INDEX standing_by_rank (map=?)

Neither needs a sort step. Timings, median of 21 reads, with 10,000 matches from 1,000 players on 200 maps (6,500 standing rows):

Read or writeTime
Whole board0.037 ms
de_forge board0.036 ms
One generated map's board0.035 ms
One write, including recomputing the player's rows0.44 ms

Test I changed because the budget was wrong: main fails the check. In src/server.test.ts, / weighs 10,687 B gzipped on that test file's heaviest database, over its 10,500 B budget, because of the map list and filter sentence #419 added to the board. I raised the budget to 11,000 B and wrote the reason in the comment above it.

Not changed: no page or terminal changes. src/tty.ts still calls standings(db) with no map and gets the same board, and Standing keeps its shape for #234 and #89.

🤖 Generated with Claude Code

Closes #414

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs