The game is beer die: 2v2 or 3v3, first to 21, win by 2, and the winners stay on. Someone at the table taps who won and what the losers finished on. Everyone else's phone shows the rankings, a game log, head-to-heads and badges. Ratings are OpenSkill, replayed from the full game history on every change.
Built for bad party wifi
Every game is written to a queue in local storage first and shown immediately, then flushed to the server in order. Each game carries a client-generated id and the write path is idempotent on it, so a retry after an ambiguous failure cannot record a game twice.
The queue tells a transient failure (a database cold start, a dropped connection: keep it and retry) from a permanent one (a malformed or refused game: set it aside and say so), because retrying a permanent failure forever blocks every game behind it. The server maps errors to status codes through an explicit allowlist, so anything unexpected is retryable by default. A stuck queue can be recovered. A discarded game cannot.
Ratings are a pure function of history
The games table is append-only apart from a voided flag. Ratings, per-game rating changes, rank movement, streaks and badges are all derived by replaying it in order. An undo is just a void, a tuning change is just a replay, and there is no stored rating that can drift out of sync with the games it came from. The replay is cached against a fingerprint of the table (row count, highest ordinal, voided count), which only ever grows, so a stale cache is always detected and rebuilt.
Margin of victory counts, but only past a point. A win within five points is an ordinary win; beyond that the extra weight grows logarithmically, so a skunk counts for more without dominating a season. New players are provisional until ten games, and the rankings say so instead of hiding them.

A four-digit PIN that stays safe on the open internet
The house wanted a PIN, not accounts. With ten thousand possibilities, the design limits guessing to one place and makes it slow. The PIN is checked only at the gate, which is rate limited in Postgres (serverless instances share no memory) under a transaction-scoped advisory lock, so a hundred parallel guesses cannot all read zero failures. A global cap of twenty wrong guesses a day works out to about 250 days to find the PIN on average.
The session cookie holds a signature, never the PIN, so hammering the write API with every possible cookie value earns ten thousand rejections and learns nothing. Changing the PIN or resetting the invite link signs out every device at once, because both are inside every signature.

Status
Live and in use. Rankings, the game log and player pages are public; changing anything needs the house PIN or an invite link. A nightly job writes a JSON backup to blob storage. Known gaps: 2v2 and 3v3 only, and no rating decay, so a badge marks inactive players instead. 561 tests across 39 files run in CI against Postgres 17.