Performance Rating is a TypeScript library implementing performance-rating tiebreaks for chess tournaments, following the FIDE Tiebreak Regulations (sections 10.2–10.5). Zero runtime dependencies.
The FIDE DP_TABLE and PD_TABLE lookup tables are embedded directly — no
runtime fetching required.
npm install @echecs/performance-ratingimport { tournamentPerformanceRating } from '@echecs/performance-rating';
import type { Game, GameKind } from '@echecs/performance-rating';
const players = [
{ id: 'A', rating: 1800 },
{ id: 'B', rating: 1600 },
{ id: 'C', rating: 1700 },
{ id: 'D', rating: 1900 },
];
// games[n] = round n+1; Game has no `round` field
const games: Game[][] = [
[{ black: 'B', result: 1, white: 'A' }], // round 1
[{ black: 'C', result: 0.5, white: 'A' }], // round 2
[{ black: 'A', result: 0, white: 'D' }], // round 3
// Byes excluded from performance calculations
[{ black: '', kind: 'half-bye', result: 0.5, white: 'A' }], // round 4
];
const tpr = tournamentPerformanceRating('A', games, players);
// Returns average opponent rating + DP_TABLE offset for the player's score %All functions share the same signature:
(playerId: string, games: Game[][], players: Player[]) => number;They return 0 when no rated opponents have been faced. Round is determined by
array position: games[0] = round 1, games[1] = round 2, etc. The Game type
has no round field. The optional kind?: GameKind field on Game classifies
unplayed rounds; only over-the-board games (where black !== white) contribute
to performance calculations.
Tournament Performance Rating (TPR). Computes the average rating of all opponents faced, then adds the DP_TABLE offset for the player's percentage score (points ÷ games played). Byes are excluded from both the opponent average and the score percentage.
import { tournamentPerformanceRating } from '@echecs/performance-rating';
// also exported as `tiebreak`
import { tiebreak } from '@echecs/performance-rating';perfectTournamentPerformance(playerId, games, players) — Perfect Tournament
Performance. Computes the performance rating for a hypothetical scenario where
the player scores 100% against the same set of opponents, using a binary search
over the PD_TABLE scoring probability function. Returns 0 when no games have
been played. Returns minRating - 800 for a 0% score and maxRating + 800 for
a 100% score.
import { perfectTournamentPerformance } from '@echecs/performance-rating/perfect';
// also exported as `tiebreak`
import { tiebreak } from '@echecs/performance-rating/perfect';averagePerformanceRatingOfOpponents(playerId, games, players) — Average
Performance Rating of Opponents. Applies tournamentPerformanceRating to each
opponent of playerId (using the full players and games data) and returns
the arithmetic mean. Returns 0 when no opponents have been faced.
import { averagePerformanceRatingOfOpponents } from '@echecs/performance-rating/average';
// also exported as `tiebreak`
import { tiebreak } from '@echecs/performance-rating/average';averagePerfectPerformanceOfOpponents(playerId, games, players) — Average
Perfect Performance of Opponents. Applies perfectTournamentPerformance to each
opponent of playerId and returns the arithmetic mean. Returns 0 when no
opponents have been faced.
import { averagePerfectPerformanceOfOpponents } from '@echecs/performance-rating/average-perfect';
// also exported as `tiebreak`
import { tiebreak } from '@echecs/performance-rating/average-perfect';All types are exported from every subpath (root, /perfect, /average,
/average-perfect).
interface Game {
black: string;
kind?: GameKind;
result: Result;
white: string;
}interface Player {
id: string;
rating?: number; // optional — unrated players are excluded from calculations
}type Result = 0 | 0.5 | 1;Classifies unplayed rounds. Games with a kind set (or where black === white)
are excluded from all performance calculations.
type GameKind =
| 'forfeit-loss'
| 'forfeit-win'
| 'full-bye'
| 'half-bye'
| 'pairing-bye'
| 'zero-bye';Contributions are welcome. Please open an issue at github.com/echecsjs/performance-rating/issues.