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

forked from trevato/cs

A room's result counts: the server writes the match it stepped, under the identity that joined #377

Merged opened this

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

Merged

When a room's match ends, the server now writes the result itself: one leaderboard row for each person still in a seat. The numbers come from the world this process stepped, and each row is saved under the identity checked when the socket connected.

shipped production is serving it deploy ae07c2d

opened this

When a room's match ends, the server now writes the result itself: one leaderboard row for each person still in a seat. The numbers come from the world this process stepped, and each row is saved under the identity checked when the socket connected.

Why #374 failed. The branch conflicted with main in src/client/main.test.ts, and one of my own tests was wrong. The server test read the rows back from the leaderboard on /. That leaderboard shows the top ten by kills from a database every server test writes to, and the rows main added pushed the room's zero-kill rows off it. I merged main, fixed the conflict (the NO_PICTURE import now comes from the engine), and removed that check. The query on the match table still proves the rows are written, and src/db.test.ts checks the leaderboard page against a database holding only that match.

What changed

  • The result (src/rooms/room.ts): resultsOf gives one result per seated member once the world reaches match_over, and nothing before. The score is the team's for the whole match. Kills, deaths, headshots, damage and seconds count only from when the member sat down, so a bot's kills before the join stay the bot's. There is no minimum: someone who joins for the last round still gets a finished match. Row ids are the room's match id plus the seat.
  • Written once (src/rooms/hub.ts): the hub is handed a Writer the way it is handed a Wire. finish marks the room recorded before calling the writer, so neither the world staying at match_over nor a writer that throws can cause a second write. A test enforces that nothing under src/rooms/ imports bun:sqlite or src/db.ts.
  • The person (src/seated.ts, new, and src/server.ts): who(request, identityKey) is read when the socket connects and stored with the session on the connection. The writer turns it into a Player with playerOf, checks the numbers with parseResult, and saves with saveMatch. A typed name never replaces a verified one, and a guest's row belongs to that browser.
  • The page stops posting (src/client/main.ts): this fixes a real bug. Setting postedFinal = true did not stop the posts, because posted = -1 still sent every scored round to /api/match. The new test sees 10 posts against the old code and none now. After a room's match, the summary says the server wrote the result.
  • The rule in words: the room hint on the menu says a match counts only for people still seated at the end. The README drops the line saying a room's result is not posted and gains a section, "Whose result a room's match is".

Left out, differently from the issue

  • The tests are in room.test.ts and hub.test.ts, not src/rooms/rooms.test.ts. The directory's own test requires that file not to exist.
  • The match table has no rules column, so the rules show in a row only as its score and round count. I didn't add a column.
  • #168 is unchanged: a single-player result is still the browser's word.

Tests: bun run test passes after the merge (typecheck and 1538 tests), and src/server.test.ts passed twice more on its own. The new tests cover:

  • two members played to match_over, checking the map and the final score;
  • a late joiner not getting the bot's earlier kills;
  • no row for a member who left early;
  • still one write when stepping past the end, or when the writer throws;
  • a real SQLite database with one signed-in member and one guest, then the leaderboard page;
  • a real WebSocket with a signed identity and a different typed name;
  • a room page sending nothing to /api/match.

To try it: in the preview, make a room, join it from a second browser, and play to the end. Both players then appear on the leaderboard at /, with the guest marked "(guest)".

🤖 Generated with Claude Code

Closes #318

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs