eloquent-works / rating-kit
A comprehensive Laravel rating engine for players, teams, multiplayer competitions, seasons, leaderboards, rating history, and pluggable rating algorithms.
Requires
- php: ^8.2
- illuminate/console: ^12.0 || ^13.0
- illuminate/database: ^12.0 || ^13.0
- illuminate/events: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- mockery/mockery: ^1.6
- orchestra/testbench: ^10.0 || ^11.0
- phpunit/phpunit: ^11.5 || ^12.0
This package is not auto-updated.
Last update: 2026-07-28 11:29:47 UTC
README
Laravel RatingKit is a comprehensive, extensible rating engine for Laravel applications.
It supports 1v1, 2v2, 3v3, arbitrary N-vs-N team matches, free-for-alls, and competitions containing multiple teams. Ratings can be isolated by game, variant, queue, time control, season, region, or any custom pool.
โจ Features
- Fourteen bundled rating algorithms
- Any number of players per team
- Any number of teams per match
- Free-for-all and ranked-placement results
- Draws and tied ranks
- Weighted participation and substitutes
- Polymorphic
HasRatingstrait for users, teams, bots, clans, and other models - Model helpers for current ratings, statistics, ranks, histories, matches, and adjustments
- Separate rating pools
- Seasonal ratings
- Provisional ratings
- Rating deviation and volatility
- Rating floors and ceilings
- Win, draw, loss, and streak statistics
- Rating histories and audit trails
- Safe rollback of the latest match
- Idempotent external match IDs
- Direction-aware recurring inactivity decay
- Leaderboards, shared ranks, and conservative leaderboards
- Persistent leaderboard snapshots
- Match outcome prediction and match-quality scoring
- Pool statistics including average, median, minimum, and maximum
- Manual rating adjustments and resets
- Custom Eloquent models and table names
- Numeric, UUID, and ULID rateable model keys
- Per-match algorithm option snapshots
- Pluggable custom algorithms
- Transactional writes and row locking
- Lifecycle events
- Artisan installation, decay, snapshot, and season commands
- PHPUnit, PHPStan, Pint, and GitHub Actions support
๐งฎ Bundled Algorithms
| Key | Algorithm | Typical use |
|---|---|---|
elo |
Elo | General head-to-head and team competition |
elo_mov |
Margin-of-victory Elo | Score-based sports and games |
fide |
FIDE-style Elo | Chess-style rating pools |
uscf |
USCF-style Elo | Development-sensitive chess ratings |
glicko |
Glicko | Ratings with uncertainty |
glicko2 |
Glicko-2 | Uncertainty and volatility tracking |
weng_lin |
Weng-Lin / OpenSkill-style | Bayesian team and multiplayer ratings |
bradley_terry |
Bradley-Terry | Pairwise comparison models |
plackett_luce |
Plackett-Luce | Ordered multi-team and free-for-all results |
thurstone_mosteller |
Thurstone-Mosteller | Gaussian performance modeling |
dwz |
DWZ-style | Development-sensitive chess ratings |
egf |
EGF-style | Go-style rating adaptation |
ingo |
Ingo-style | Lower-is-stronger historical rating systems |
fifa |
FIFA-style Elo | Importance-weighted team competition |
Federation-named drivers are configurable practical adaptations. They are not claims of certification by the named federation.
๐ Installation
Install the package through Composer:
composer require eloquent-works/rating-kit
Publish the configuration and migrations, then migrate:
php artisan rating-kit:install --migrate
Add HasRatings to every model that can receive a rating:
use EloquentWorks\RatingKit\Traits\HasRatings; class User extends Authenticatable { use HasRatings; }
The trait gives the model polymorphic rating relationships and convenient helpers:
$user->ratings; $user->ratingFor('chess.blitz', 'glicko2'); $user->currentRating('chess.blitz', 'glicko2'); $user->ratingStats('chess.blitz', 'glicko2'); $user->ratingRank('chess.blitz', 'glicko2'); $user->ratingHistory('chess.blitz', 'glicko2'); $user->recentRatedMatches();
A user can own independent ratings for every pool, algorithm, and season. See the full HasRatings guide.
โก One Player vs One Player
use EloquentWorks\RatingKit\Facades\RatingKit; $match = RatingKit::oneVsOne( left: $winner, right: $loser, result: 'left', algorithm: 'glicko2', pool: 'chess.blitz', externalId: 'game-10001', );
Record a draw:
RatingKit::oneVsOne( left: $playerA, right: $playerB, result: 'draw', algorithm: 'elo', pool: 'chess.rapid', );
๐ฅ Two Players vs Two Players
RatingKit::teamVsTeam( left: [$playerA, $playerB], right: [$playerC, $playerD], result: 'left', algorithm: 'glicko2', pool: 'chess.teams.blitz', );
๐ก๏ธ Three Players vs Three Players
RatingKit::teamVsTeam( left: [$playerA, $playerB, $playerC], right: [$playerD, $playerE, $playerF], result: 'right', algorithm: 'weng_lin', pool: 'arena.3v3.ranked', );
The same API supports 4v4, 5v5, 10v10, or any other team size.
๐ Multiple Teams in One Match
use EloquentWorks\RatingKit\Data\RecordMatch; use EloquentWorks\RatingKit\Data\Team; $match = RatingKit::record(new RecordMatch( teams: [ new Team([$a, $b], rank: 1, score: 18, name: 'Red'), new Team([$c, $d], rank: 2, score: 14, name: 'Blue'), new Team([$e, $f], rank: 3, score: 9, name: 'Green'), ], algorithm: 'plackett_luce', pool: 'arena.trios', externalId: 'match-9001', ));
Teams may share a rank to represent a tie.
๐ฅ Free-for-All Results
RatingKit::freeForAll([ ['participant' => $winner, 'rank' => 1, 'score' => 25], ['participant' => $second, 'rank' => 2, 'score' => 18], ['participant' => $third, 'rank' => 3, 'score' => 12], ['participant' => $fourth, 'rank' => 3, 'score' => 12], ], algorithm: 'plackett_luce', pool: 'battle-royale.solo');
โ๏ธ Weighted Participants
Use weights for substitutes, partial participation, handicaps, or unequal contribution:
use EloquentWorks\RatingKit\Data\Participant; RatingKit::teamVsTeam( left: [ new Participant($starter, weight: 1.0), new Participant($substitute, weight: 0.35), ], right: [$opponentA, $opponentB], result: 'left', algorithm: 'elo', pool: 'football.doubles', );
๐๏ธ Separate Rating Pools
The same user can hold independent ratings:
$user->currentRating('chess.standard', 'glicko2'); $user->currentRating('chess.blitz', 'glicko2'); $user->currentRating('chess.teams', 'weng_lin'); $user->currentRating('variant.atomic', 'elo');
Pools work well for:
- Different games
- Variants
- Time controls
- Ranked and casual queues
- Team sizes
- Regions
- Platforms
- Tournament circuits
๐ Seasons
$season = RatingKit::createSeason( name: 'Season 1', slug: 'season-1', pool: 'arena.ranked', startsAt: now(), endsAt: now()->addMonths(3), ); RatingKit::teamVsTeam( left: $redTeam, right: $blueTeam, result: 'left', algorithm: 'glicko2', pool: 'arena.ranked', seasonId: $season->id, ); RatingKit::closeSeason($season);
Closing a season automatically captures a final leaderboard for every algorithm used during that season.
๐ Leaderboards
$leaderboard = RatingKit::leaderboard( pool: 'chess.blitz', algorithm: 'glicko2', limit: 100, includeProvisional: false, );
Use a conservative score based on rating uncertainty:
$leaderboard = RatingKit::leaderboard( pool: 'chess.blitz', algorithm: 'glicko2', conservative: true, );
Competitors with the same score share the same competition rank. Retrieve one competitor's rank:
$rank = RatingKit::rankOf($user, 'chess.blitz', 'glicko2');
๐ฎ Match Predictions
$prediction = RatingKit::predict([ new Team([$playerA, $playerB], rank: 1), new Team([$playerC, $playerD], rank: 2), ], pool: 'arena.2v2', algorithm: 'glicko2');
The result contains a normalized estimated winning share for each team.
Measure how balanced the proposed match is on a scale from 0.0 to 1.0:
$quality = RatingKit::matchQuality([ new Team([$playerA, $playerB], rank: 1), new Team([$playerC, $playerD], rank: 2), ], pool: 'arena.2v2', algorithm: 'glicko2');
๐ Pool Statistics
$statistics = RatingKit::poolStatistics( pool: 'chess.blitz', algorithm: 'glicko2', );
The result includes total, established, and provisional counts together with average, median, minimum, and maximum ratings.
๐งพ Rating History
$history = $user->ratingHistory('chess.blitz', 'glicko2'); $peak = $user->peakRating('chess.blitz', 'glicko2');
Every history record can preserve:
- Rating before and after
- Rating deviation before and after
- Volatility before and after
- Match ID
- Adjustment reason
- Custom metadata
โฉ๏ธ Rollback and Voiding
The latest match for every participant can be safely voided:
RatingKit::void($match, 'The result was entered incorrectly.');
RatingKit refuses an unsafe rollback when one of the participants already has a later result.
๐ ๏ธ Manual Adjustments
RatingKit::adjust( model: $user, amount: 25, reason: 'tournament_bonus', pool: 'chess.standard', algorithm: 'glicko2', ); RatingKit::setRating($user, 1800, reason: 'verified_import'); RatingKit::resetRating($user);
Manual changes are written to rating history.
๐ค Inactivity Decay
Decay is disabled by default. When enabled, eligible ratings are adjusted once per configured period after the inactivity threshold. Higher-is-better algorithms move down toward the configured minimum; lower-is-better algorithms move up toward the configured maximum.
'decay' => [ 'enabled' => true, 'inactive_after_days' => 90, 'period_days' => 30, 'points_per_period' => 10.0, 'minimum_rating' => 1200.0, 'maximum_rating' => 2200.0, ],
๐งฉ Custom Algorithms
Implement RatingAlgorithm and register it at runtime:
RatingKit::algorithms()->extend('my_algorithm', MyAlgorithm::class);
Or add it to config/rating-kit.php:
'algorithms' => [ 'my_algorithm' => App\Ratings\MyAlgorithm::class, ],
๐ฃ Events
RatingKit dispatches events for:
MatchRatedMatchVoidedRatingUpdatedRatingAdjustedSeasonClosedLeaderboardSnapshotted
Event dispatching can be disabled in configuration.
๐งฐ Artisan Commands
php artisan rating-kit:install --migrate php artisan rating-kit:decay php artisan rating-kit:snapshot --pool=chess.blitz --algorithm=glicko2 php artisan rating-kit:close-season season-1
๐ Documentation
The complete documentation is available in docs/README.md.
- Installation
- Quick start
- Algorithms
- Team and multiplayer matches
- Pools and variants
- Seasons
- Leaderboards
- History and rollback
- Configuration
- Custom algorithms
- API reference
โ Supported Versions
- PHP 8.2+
- Laravel 12
- Laravel 13
๐งช Quality Checks
composer validate --strict
composer audit
composer format:test
composer analyse
composer test
Or run the complete quality suite:
composer quality
๐ Security
Please review SECURITY.md for responsible vulnerability reporting.
๐ค Contributing
Contributions are welcome. See CONTRIBUTING.md.
๐ License
Laravel RatingKit is open-source software licensed under the MIT license.