Ranking & Ordinal
A Rating is a Gaussian — mu is the mean (believed skill), and sigma is
the spread (uncertainty). To rank players you usually want a single number,
which is what ordinal is for.
ordinal
import { ordinal } from 'openskill'
ordinal({ mu: 43.07, sigma: 2.42 }) // 35.81
By default this returns mu - 3 * sigma — the lower bound of a 99.7%
confidence interval. With
fresh players this means ordinal will rise as sigma shrinks, even after a
loss, which is desirable for newcomers ramping up.
Artificial ordering with rank
If your teams array isn't already in finishing order, pass rank. Lower
ranks are better.
import { rating, rate } from 'openskill'
const a1 = b1 = c1 = d1 = rating()
const [[a2], [b2], [c2], [d2]] = rate(
[[a1], [b1], [c1], [d1]],
{ rank: [4, 1, 3, 2] } // 🐌 🥇 🥉 🥈
)
Artificial ordering with score
Same idea, but with raw scores from your game — higher is better.
const [[a2], [b2], [c2], [d2]] = rate(
[[a1], [b1], [c1], [d1]],
{ score: [37, 19, 37, 42] } // 🥈 🐌 🥈 🥇
)
Victory margins with margin
When you pass score, you can also pass margin to make the size of a
victory matter. Score gaps larger than margin amplify the rating update —
logarithmically, so blowouts don't cause runaway swings — while gaps within
the margin count as ordinary wins/losses.
const [[a2], [b2]] = rate(
[[a1], [b1]],
{ score: [37, 12], margin: 10 } // a 25-point win, amplified
)
Partial play with weight
If a player only participated for part of a match — or you track a
contribution metric like time played or objectives captured — pass weight,
shaped like teams, to scale each player's update.
const [[a2, b2], [c2, d2]] = rate(
[
[a1, b1],
[c1, d1],
],
{
weight: [
[1, 0.5], // b1 only played half the match
[1, 1],
],
}
)
Weights are normalized per team into [1, 2] by default (only relative
differences within a team matter). See the API reference for
weightBounds.
Ties
Ties are encoded as equal rank or equal score. Players sharing a position
update against each other as if they drew.