API Reference
import {
rating,
rate,
ordinal,
predictWin,
predictDraw,
predictRank,
} from 'openskill'
rating(options?)
Create a new rating.
rating(options?: { mu?: number; sigma?: number }): Rating
rating() // { mu: 25, sigma: 8.333... }
rating({ mu: 1500, sigma: 200 }) // { mu: 1500, sigma: 200 }
rate(teams, options?)
Update ratings after a match. teams is an array of arrays — each inner
array is one team. By default the team listed first won; use rank or
score for arbitrary orderings.
rate(
teams: Rating[][],
options?: {
model?: typeof plackettLuce
| typeof bradleyTerryFull
| typeof bradleyTerryPart
| typeof thurstoneMostellerFull
| typeof thurstoneMostellerPart
rank?: number[]
score?: number[]
margin?: number
weight?: number[][]
weightBounds?: [number, number] | null
tau?: number
limitSigma?: boolean
balance?: boolean
gamma?: Gamma
mu?: number
sigma?: number
beta?: number
epsilon?: number
kappa?: number
windowSize?: number
}
): Rating[][]
Default usage — first team wins
const [[a1n, a2n], [b1n, b2n]] = rate([
[a1, a2],
[b1, b2],
])
With ranks
Lower rank = better placement. Equal ranks are ties.
rate([[a1], [b1], [c1], [d1]], { rank: [4, 1, 3, 2] })
// → 🐌 🥇 🥉 🥈
With scores
Higher score = better. Equal scores are ties.
rate([[a1], [b1], [c1], [d1]], { score: [37, 19, 37, 42] })
// → 🥈 🐌 🥈 🥇
With victory margins
When both score and margin are given, rating updates are amplified for
wins by more than margin points. A 20-point blowout moves ratings more
than a 1-point squeaker; the amplification grows logarithmically, so extreme
blowouts don't produce runaway swings. Score gaps within the margin update
as ordinary wins/losses.
rate(
[
[a1, a2],
[b1, b2],
],
{ score: [37, 12], margin: 10 }
)
With partial-play weights
weight scales each player's rating update by their contribution to the
match — useful when a player only played part of a game, or you have a
per-player contribution metric (damage dealt, objectives captured, minutes
played). It's shaped like teams: one number per player.
rate(
[
[a1, a2],
[b1, b2],
],
{
weight: [
[1, 0.5], // a2 only played half the game
[1, 1],
],
}
)
By default each team's weights are normalized into [1, 2], so only the
relative differences within a team matter — matching
openskill.py bit-for-bit.
Change the range with weightBounds: [min, max], or pass
weightBounds: null to apply your raw weights as-is.
With tau and limitSigma
tau (default 25/300) is an additive dynamics factor that keeps sigma
from collapsing to zero over many games, so ratings stay responsive to skill
changes. A side effect is that sigma can increase, which can make a
player's ordinal drop even after a win. Set
limitSigma: true to clamp sigma so it never rises.
rate(teams, { tau: 0.3, limitSigma: true })
With balance
balance: true weights weaker players' contributions to their team's rating
more heavily, emphasizing skill disparity within a team.
rate(teams, { balance: true })
With windowSize
By default the partial-pairing models (bradleyTerryPart and
thurstoneMostellerPart) compare each team only with the teams directly above
and below it in the ranking, and add those updates. In this mode, tied teams can get
different updates, and the result can change with the order in which you list
the tied teams.
Set windowSize to use the sliding window from
openskill.py instead. Each team is
then compared with the teams at most windowSize places above or below it, and
the model averages those comparisons. openskill.py uses 4, so set
windowSize: 4 to get the same results. A larger window compares more teams, so
its accuracy is nearer to a full-pairing model, but it costs more time. The
other models ignore this option.
When the window reaches all teams (windowSize is teams.length - 1 or more),
tied teams with the same ratings get the same update, and the order of tied
teams has no effect, apart from floating-point rounding. In a larger game, tied
teams can still get different updates, because their windows cover different
teams. The next major version will make windowSize: 4 the default.
rate(teams, { model: bradleyTerryPart, windowSize: 4 })
Custom gamma
Advanced: pass a gamma function to override how much each team's sigma
shrinks per update. The default is sigma / c (where c is the total
uncertainty in the match).
ordinal(rating, options?)
Reduce a rating to a single number for display or sorting.
ordinal(r: Rating, options?: { z?: number; alpha?: number; target?: number }): number
ordinal({ mu: 25, sigma: 8.333 }) // ≈ 0.0
predictWin(teams, options?)
Returns an array of probabilities, one per team, that sums to 1.
const a1 = rating()
const b1 = rating({ mu: 33.564, sigma: 1.123 })
predictWin([[a1], [b1]])
// [0.2021..., 0.7978...]
predictDraw(teams, options?)
Returns the relative likelihood that a match between these teams ends in a draw. Useful for matchmaking quality scores.
predictDraw([[a1], [b1]])
// 0.2164...
predictRank(teams, options?)
Returns, for each team, its predicted placement and the probability of that placement.
const a1 = rating()
const b1 = rating({ mu: 30, sigma: 5 })
const c1 = rating({ mu: 20, sigma: 8 })
predictRank([[a1], [b1], [c1]])
// [[2, 0.3276...], [1, 0.4867...], [3, 0.1855...]]
All three predictors accept the same hyperparameter options as rate
(mu, sigma, beta, balance, …) so predictions stay consistent with
however you rate.
Selecting a model
import { rate } from 'openskill'
import { bradleyTerryFull } from 'openskill/models'
rate(teams, { model: bradleyTerryFull })
See Models for guidance on choosing one.