Skip to content

feat(scaling): Stephenson rating system (PlayerRatings steph()) - #302

Merged
seonghobae merged 2 commits into
seonghobae-glicko2from
seonghobae-stephenson
Jul 31, 2026
Merged

feat(scaling): Stephenson rating system (PlayerRatings steph())#302
seonghobae merged 2 commits into
seonghobae-glicko2from
seonghobae-stephenson

Conversation

@seonghobae

@seonghobae seonghobae commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Iteration 61: Stephenson rating system (stephenson_rating)

Implements the Stephenson rating system as in CRAN PlayerRatings 1.1-0 steph().

Citation governance

  • READ (normative): PlayerRatings 1.1-0 R/ratings.R lines 591-737 (steph() driver) and src/ratings.c lines 157-202 (stephenson_c kernel) — every formula line-cited in the Rust core header.
  • NOT independently verifiable: the system's provenance (Alec Stephenson's winning entry, 2010 Kaggle chess-rating contest) is attributed by the package; no journal paper exists, so the R/C source is the sole normative authority.

What it adds over Glicko

  • Per-game neighborhood variance term ngames * hval^2 inside the deviation update.
  • Per-game bonus bval/100 added to each played game's score on both sides.
  • Participants-only lambda drift toward opponents' ratings (lambda/100) * sum(r_opp - r_self) / ngames.
  • (lag+1) * cval^2 per-period deviation-variance inflation clamped at rdmax^2 (note (lag+1), unlike Glicko-2's lag * sigma^2).
  • Prior-run continuation via init_games / init_lag; per-game white advantage gamma.

Verification

  • Spec-verify: APPROVED-WITH-CHANGES (4 mandatory changes, all adopted): u64 counts, explicit init_games/init_lag length validation, lag-continuation scope clarification, current-run-only W/D/L tallies.
  • Oracle: faithful line-referenced Python port of the R driver + C kernel, EXECUTED; anchors S1 (heterogeneous init), S2 (two-period draw/lag), S3 (full knobs incl. asymmetric gamma), S4 (rdmax clamp + prior-run continuation), S5 (bval symmetry), S2-lambda0 contrast — all pinned at 1e-12 rel.
  • Mutation kills (all EXECUTED, all KILLED): MU1 bval drop, MU2 lambda sign flip, MU3 ngames*hval^2 -> hval^2, MU4 (lag+1) -> lag, MU5 opponent-g -> own-g.
  • Suites: cargo mlsirm-core --lib 802 pass; pytest test_paper_features.py 310 pass; st_mc_500 (500-rep MC, #[ignore]) pass.

Surface

  • Rust: mlsirm_core::scaling::stephenson_rating (+ StephensonResult).
  • PyO3: stephenson_rating (plain name, scaling-section precedent).
  • Python: fast_mlsirm.stephenson_rating with PlayerRatings defaults init=(2200, 300), cval=10, hval=10, bval=0, lambda_=2, rdmax=350.

Stacked on #301 (Glicko-2).


Adversarial impl-review outcome

Round 1 (commit 3514293): FINDINGS ? 3 confirmed

  • High: Python wrapper enforced the 2..=10000 n_players cap only in the Rust core, AFTER length-n allocations ? huge n_players attempted a huge allocation instead of raising ValueError.
  • Medium: white/black player-id columns were cast through the float view, losing integer fidelity at/above the dtype's exact-integer bound before the u64 range checks.
  • Medium: Rust core games/lag counter increments on caller-supplied u64 state could overflow (debug panic / release wrap) with init_games/init_lag near u64::MAX.

Fixes (commit 3e4fc99): early Python cap before any allocation; raw-integer-dtype direct u64 casts + fidelity rejection for float/object player ids; up-front u64::MAX - g overflow guard in the Rust core. Regression tests added on both sides (Rust st_error_contract overflow cases; Python fidelity + pre-allocation cap cases).

Round 2 (commit 3e4fc99): CLEAN ? all three fixes verified, including the negative integer player-id path; targeted Rust + Python error-contract suites re-run green.

Rust core stephenson_rating extending Glicko with per-game neighborhood
variance (ngames*hval^2), per-game bonus bval/100 on both sides,
participants-only lambda drift toward opponents, and (lag+1)*cval^2
per-period deviation-variance inflation clamped at rdmax^2. Normative
source: CRAN PlayerRatings 1.1-0 R driver (ratings.R 591-737) + C kernel
(ratings.c stephenson_c 157-202), both READ and line-cited; no journal
paper exists (Kaggle-2010 provenance noted as NOT independently
verifiable). PyO3 binding + NumPy wrapper with PlayerRatings defaults.

Tests anchored to an EXECUTED faithful oracle port (S1 heterogeneous
init, S2 two-period draw/lag, S3 full knobs, S4 rdmax clamp +
prior-run continuation, S5 bval symmetry, lambda=0 contrast; 1e-12
pins), 500-rep MC invariants (#[ignore]), and five EXECUTED mutation
kills: bval drop, lambda sign flip, per-game hval scaling drop,
(lag+1)->lag, opponent-g->own-g.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cba5e181-9573-4d3c-9ef8-79b82db091b3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch seonghobae-stephenson

Comment @coderabbitai help to get the list of available commands.

- Enforce the 2..=10000 n_players cap in the Python wrapper BEFORE any
  length-n allocation (High: huge n_players previously attempted the
  allocation instead of raising ValueError).
- Preserve integer fidelity for white/black player-id columns: integer
  dtypes cast directly to u64; float/object inputs are rejected at or
  above the dtype's exact-integer bound before the uint64 cast, matching
  the existing period-label contract (Medium).
- Guard u64 counter overflow in the Rust core: init_games/init_lag
  values that could overflow across the run's increments now return Err
  instead of panicking (debug) or wrapping (release) (Medium).

Regression tests: Rust st_error_contract overflow cases; Python
fidelity + pre-allocation cap cases.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@seonghobae
seonghobae merged commit 423e020 into seonghobae-glicko2 Jul 31, 2026
6 checks passed
@seonghobae
seonghobae deleted the seonghobae-stephenson branch July 31, 2026 12:38
@seonghobae

Copy link
Copy Markdown
Contributor Author

Note: this PR was squash-merged into a non-main stacked base, so its commits did not land on main via this PR. The feature set reached main via #374 (stack tip integration) after #290.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant