Skip to content

Ratings

gammonchain rates players with Glicko-2, weighted by match length (src/server/rating.js). The implementation is pure: no I/O and no clock. Rating updates are applied when a rated match finishes (Store.finishMatch() in src/server/db.js).

Why Glicko-2

A single backgammon result carries very little information. A 1-point match is close to a coin flip even between players 400 rating points apart. A rating system should therefore track how uncertain it is about each player, and let that uncertainty grow while a player is away. Glicko-2 does this with a rating deviation (RD) and a volatility. Elo has neither. Lichess made the same choice for the same reason.

Weighting by match length

A 9-point match separates skill from luck far better than a 1-pointer. Each result is weighted by

weight = sqrt(max(1, matchLength))

This is the same square-root scaling the classic FIBS formula uses, but applied to the Glicko-2 variance terms rather than to a fixed K-factor. For each result, the weight multiplies both the contribution to 1/v (the inverse of the estimated variance) and the contribution to the rating change:

1/v   = Σ w · g(φj)² · E · (1 − E)
Δsum  = Σ w · g(φj) · (s − E)

The rest of the update follows the Glicko-2 paper: the Illinois root-find for the new volatility, then the new deviation and rating. A money game counts as a 1-pointer (weight 1).

Parameters

constantvaluemeaning
starting rating1500
starting RD350a new player: nothing is known
starting volatility0.06
τ (tau)0.5how fast volatility may change. The comment calls 0.3–1.2 the sensible range
RD floor30never claim more certainty than this
RD ceiling350
provisional above RD110ratings are marked provisional until RD drops to 110 or below

Each match is applied as its own rating period for both players (applyMatch), using the ratings both players had before the match.

What counts

A match is rated only when all of these hold:

  • the challenge was posted as rated. The lobby's "rated" box is ticked by default, and a seek counts as rated only if its poster had a verified key;
  • neither seat is a bot. Bot games are never rated;
  • both players have a verified public key;
  • it is not a two-players-one-device match (those are always posted unrated);
  • the match has a winner. A match that was aborted, not finished, produces no record and no rating change. See Playing.

A forfeit (running out of time, abandoning, or disconnecting past the grace period) is a finished match with a winner, so it is rated like any other result.

For a rated match, finishMatch saves the new rating, RD and volatility for both players, adds one game to each, adds a win for the winner, and adds points for and against. Every match, rated or not, is also written to the matches index with the ratings before and after.

What is shown

display() turns a stored rating into what the site shows:

fieldvalue
ratingrounded rating
rdrounded deviation
provisionaltrue while RD > 110
conservativerating − 2·RD, a conservative figure for ranking, which is how Glicko ratings are meant to be used publicly

GET /api/leaderboard lists players by rating, by default only players with at least 3 games (minGames, limit up to 200). GET /api/player/<pub> returns one player's card and their 25 most recent matches. A rating is shown with its deviation and marked provisional until the deviation narrows, because early numbers move a lot and hiding that would cost the ladder credibility.

Not wired into the server

Called with no results, update(player, []) widens RD for a player who did not play in a rating period. rating.js also exports winProbability(). At present only the tests call either one: the server never runs an empty rating period, so RD does not grow while a player is inactive.

Rules engine, fairness protocol, verifier, analysis and worker: MIT. Server and client: AGPL-3.0-or-later.