Rate players, then rank them

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
elo1v1, and you want a number players can reason about. One dial
glicko1v1 where players come and go. Carries uncertainty, so a returning player finds their level in a handful of games rather than dozens
trueskillAnything 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.

ESC