Skip to content
Surround

Ranks

Ranks use OGS's scale, shown as kyu and dan from 30k to 9d.

rating = 1500 + 173.7178 · μ
rank   = 23.15 · ln(rating / 525)

Rank 0 is 30k, rank 29 is 1k, rank 30 is 1d, and rank 38 is 9d, the top rank shown (the AI pro plays there). Onchain the scale is a table of μ at each whole rank, and ranks are kept in tenths.

Provisional ranks

A rank shows "?" while:

  • φ, aged to now, is above 1.0, or
  • the player doesn't yet have at least one win and one loss.

A new player's first results move their rating a long way. The "?" says the rank isn't settled yet, and it comes back if a player is away long enough for their uncertainty to grow past 1.0.

Starting bands

A new player chooses where to start, as on OGS. The contract knows four bands; the owner's policy decides which are open:

BandStarts atOpen
123kyes
217kyes
3rating 1500, about 6kyes
41kno

A band can be chosen only by a player with no rated games, and only as the ticket names it. A 1k start is worth too much to someone farming accounts for rank. Opening only 23k and 17k deflated the simulated pool by about five ranks, so 6k stays open too (evidence).

Drift

A rating system with fixed starting bands can drift: when beginners improve, the pool as a whole sinks (about 2.6 logits over 10 years in simulation).

replay.mjs in the SDK watches for this in the contract's events: newcomers' first games against players with 10 or more games, points won against points expected. Newcomers start at fixed values, so a pool that has sunk makes them score below prediction.

When that happens the owner sets rank_offset_tenths, which shifts every displayed rank, the anchors' included, and changes no stored rating. It is 0 at launch.

Where ranks are shown

The channel emits a PlayerRank event for each player after every rated game, which Torii indexes for the app. SurroundRatings is the source of truth: getPlayerRating in the SDK reads it directly.