You need: rating composed. Add leaderboard and matchmaking for the rest.
They answer different questions
The algorithms is how to pick one, pools is what a rating is stored against, Using Rating is the call, and the waiting queue is what reads the result.
A leaderboard ranks what has happened. A rating estimates what will.
A weekly points board says who won most this week. A rating says who is likely to win the next one — which is what a matchmaking queue needs, and what a points board cannot tell it.
Set up a rating
rating({
games: [
{ key: "duel", algorithm: "elo", xp: { win: 20, draw: 10, loss: 5 } },
],
serverAuthoritative: true,
recordScope: "rating:record",
})Then pithy migrate. One table, nothing to provision.
Pick the algorithm by shape, not preference
| Use it when | |
|---|---|
elo | 1v1, and you want a number players can reason about. One dial |
glicko | 1v1 where players come and go. Carries uncertainty, so a returning player finds their level in a handful of games rather than dozens |
trueskill | Anything above two players, or any team result. The only built-in that rates teams |
Record an outcome
POST /rating/games/duel/outcomes
{ "ranks": { "u1": 1, "u2": 2 } }Finishing place per user id. 1 wins; ties share a place.
The body carries the result and nothing else — the pool comes from config, the algorithm from the pool, the timestamp from the server.
Mint the record scope for your trusted server’s token, never a player’s. A rating a client can write is a rating a client can invent, and every queue and rank downstream inherits the lie.
Two numbers, and why they are two
Skill (MMR) moves both ways, weighted by opponent strength.
XP only ever rises, and drives rank and levels.
One number cannot do both. A ladder that pays out for playing stops estimating strength, and a rating that only estimates strength has nothing to show a player who is not winning yet.
Hide the rating without losing it
{ key: "duel", algorithm: "glicko", hideSkill: true }A hidden rating still drives matchmaking. The player-facing read returns XP, rank and games played with a null skill.
That is the shape you want when the number would discourage or be exploitable, and the queue still has to pair fairly.
Friends do not count toward XP
By default only games against strangers earn XP and rank, so friends cannot farm each other to climb. Set sharedRoomCounts: true per game to allow it.
Skill still updates either way — a game against a friend is genuine evidence of strength, and not genuine evidence of engagement worth rewarding.
Feed the queue
matchmaking({ games: [{ key: "duel", players: 2, skillPool: "duel", … }] })The queue buckets on that pool’s skill number, widening the band every second a player waits.
Omit skillPool and it buckets on region alone — matchmaking degrades rather than breaking when rating is absent.
And still publish a board
Rating and leaderboard are not alternatives. Publish match results to a board for the visible standings, and keep the rating for the pairing — see publish results to a leaderboard.
Levels from XP
levels: [{ key: "bronze", from: 0 }, { key: "gold", from: 2000 }]Worst to best, classified on read, so adding one needs no backfill.