Skip to content

Switch player ratings from Elo to Glicko - #126

Merged
ejsmith merged 1 commit into
mainfrom
glicko-ratings
Oct 28, 2026
Merged

ejsmith merged 1 commit into
mainfrom
glicko-ratings

Conversation

@ejsmith

@ejsmith ejsmith commented Oct 28, 2026

Copy link
Copy Markdown
Owner

Summary

Sucker is luck-heavy, so the Elo board mostly ranked hot streaks and activity. A newcomer who went 5–0 on coin flips could land above a player with a real 60% edge. Leaders could also stop playing and keep their spot forever. This switches ratings to Glicko-1 and ranks players by a cautious score that reflects how sure we are of their rating.

  • Shown rating = rating − 2 × deviation. Players see one number everywhere: the leaderboard, the profile, their world rank, and each game's rating_before/rating_after. New players start at 900 and climb as games build confidence.
  • Starting deviation 150, not the textbook 350. Dice luck keeps skill gaps small. A 350 start overreacts to early luck: replaying production history with it, 10 of 332 losses raised the loser's shown rating, by up to 68 points. With 150, none did.
  • Idle decay. Deviation grows while a player sits out (variance +10² per day, capped at 150). A settled player loses about 30–45 shown points after a month idle and 75–105 after three. Their first games back count for more, so they climb back quickly.
  • Lopsided-pair guard (new). Once either player has won 19 of a pair's last 20 games, games between them stop counting until that record drops below 19. The most lopsided real stretch so far is 17 of 20.
  • Kept: 7-day repeat-opponent damping (now a ¼ game weight), forfeits as results, win/loss/tie only, and the 5-game minimum for the rating board.

Changes

  • supabase/migrations/20261028120000_switch_ratings_to_glicko.sql
    • Adds player_standings.rating_deviation and rated_at. rating now holds the Glicko estimate. peak_rating tracks the highest shown rating and defaults to 900.
    • Moves the rating math into rate_completed_game. apply_completed_game_standings calls it, then updates streaks and milestone feats exactly as before.
    • Adds the rating_deviation_at and conservative_rating helpers. ranked_leaderboard('rating') and get_player_card compute the shown rating as of now(). The card takes its rank straight from the board, so they can't disagree.
    • Resets every rating and replays all completed games in completion order. Streaks and feats are not touched.
  • supabase/schema.sql, shared/database.types.ts, and docs/community-leaderboards.md are updated to match.
  • Integration and e2e expectations are updated for the new numbers.

The RPC shapes don't change, so the app and the game-action Edge Function need no updates. Rollout: apply the migration.

Known limitation

No rating formula can tell a thrown game from a real one. The guard stops a long farm against a second account. But with a 150 start, the 20 thrown games the guard still allows would put a fresh account's farmer at a shown ~1227, just above today's #1. Someone who throws only some games can also climb more slowly. A wider start (350) blunts this, at the cost of the loss-raises-rating problem above. Hiding the account from boards remains the backstop.

Testing

  • npm run typecheck, npm run typecheck:edge, npm run lint -- --max-warnings=0, npm run test (217 passed), npm run test:edge (44 passed), and deno check on the integration test.
  • Applied the migration to a local database inside a rolled-back transaction:
    • The replay of 90 local games matched an independent Python implementation on every rating, deviation, peak, and before/after value (0 mismatches).
    • Streaks and feats were unchanged.
    • The board, card rank, and rankings agreed.
  • Ran synthetic games through the completion trigger:
    • The first 20 straight wins between a pair counted and games 21–22 did not.
    • A first-game tie shows 900 → 920.48, a win 900 → 971.24, and a loss 900 → 869.72.
    • Re-applying a rated game is a no-op, and idle decay lowers the shown rating.
  • Replaying production history offline: the top two are unchanged and Update build tooling versions #3–Speed up CI caching #7 reshuffle. Changing the decay rate or the starting deviation only swaps neighbors.
  • Not run locally: npm run test:integration:supabase, because it resets the shared local Supabase database. CI runs it.

🤖 Generated with Claude Code

Dice luck keeps skill gaps small, so Elo let a short lucky run top the
board and never moved players who stopped playing. Ratings are now
Glicko-1 with a starting deviation of 150, and players see and rank by
the cautious end of their range (rating minus twice the deviation).
The deviation grows while a player sits out, so idle players drift down
the board until they play again.

Repeat-opponent damping stays. A new guard stops rating games between a
pair once either player has won 19 of their last 20, so games thrown to
a second account stop counting. The migration resets ratings and replays
every completed game; streaks and feats are untouched.

The leaderboard, player card, and rankings keep their shapes, so the app
and Edge Functions need no changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-28T06:22:02.771934Z 44610bd PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ejsmith
ejsmith merged commit a4e0c5e into main Oct 28, 2026
11 of 12 checks passed
@ejsmith
ejsmith deleted the glicko-ratings branch October 28, 2026 20:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant