Pools

The default is one rating per game

A pool defaults to the game key. Name one explicitly to share a ladder:

{ key: "blitz",   algorithm: "glicko", pool: "chess" }
{ key: "classic", algorithm: "glicko", pool: "chess" }   // one rating across both time controls

Or point several games at global for a single cross-game rating. A pool is also what the waiting queue buckets on when it pairs by skill — recording into one is a single call, and the reference has the config.

A pool is rated by exactly one algorithm

Because the stored state is that algorithm’s own.

The row records which algorithm wrote it, and a read validates the blob against it.

Two numbers per player, per pool

Skill rating (MMR) — moves both ways, weighted by opponent strength. Hideable per game.

Experience (XP) — monotonic, visible, and the input to an optional level ladder.

They are separate on purpose. One number cannot both rank a player fairly and reward them for turning up:

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.

A hidden rating still works

hideSkill hides it from players, not from the system.

A hidden rating still drives matchmaking. The player-facing read returns XP, rank and games played, with a null skill.

Which is the shape you want when the number would be discouraging or exploitable, and you still need the queue to pair fairly.

Only strangers count toward XP by default

A result played in a shared room with a friend counts toward XP and rank only when the game sets sharedRoomCounts: true — so friends cannot farm each other to climb.

Skill rating always updates from a real result either way.

That asymmetry is deliberate: a game against a friend is genuine evidence of strength, and it is not genuine evidence of engagement worth rewarding.

Levels are classified from XP

levels: [{ key: "bronze", from: 0 }, { key: "silver", from: 500 }, { key: "gold", from: 2000 }]

Worst to best. Computed on read from the XP already stored, so adding a level is a config change with no backfill.

What it stores

One table. One row per pool and player, holding:

The algorithm’s own state blob
The derived skill numberIndexed — bucketing a matchmaking queue is a range read over it
The XP total
Games played
When it last changed

Nothing else. No board table, no history table, and no config in the database — the games are config, reviewed and deployed like the rest of your app.

Where this sits between the neighbors

A leaderboard ranks what has happened. A rating estimates what will.

This is the input system the leaderboard deliberately left out, and the skill source matchmaking buckets its open queue on.

ESC