Files
trueskill-tt/tests/equivalence.rs
T
logaritmiskandClaude Opus 5 92d690d0f8 feat!: Game is the type you get, and one_v_one returns one
`lib.rs` advertised `Game` in "Core types" as "one match in isolation".
It had no public constructor: every `Game::*` returned `OwnedGame`, so
`let g: Game = Game::ranked(..)?` did not compile.

Names swapped. The public type is the owned one — `Game<T, D>`, no
lifetime — and the borrowing form is `pub(crate) GameRef<'a, T, D>`,
which is what it always was: an implementation detail about whether the
result and weight slices are borrowed from `History`'s storage. That
distinction meant nothing to someone scoring one match, and it showed
the module's surface twice in rustdoc, since both types carried
`posteriors()` / `log_evidence()`.

`one_v_one` returned `(Gaussian, Gaussian)` while every sibling returned
a game, making it the one constructor you could not ask for
`log_evidence()`. It returns `Self` now; `.posteriors()` recovers the old
shape, and the test that covers it now also asserts the evidence of two
identical ratings is exactly `ln(0.5)`.

`ranked`, `scored` and `free_for_all` had `# Errors` as their entire
doc, so rustdoc's index rendered the error list as the summary. They
have summary lines, and `Game` has a worked example — it was advertised
as a core type with none anywhere in the crate.

Closes #69.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
2026-09-09 22:55:05 +02:00

64 lines
2.2 KiB
Rust

//! Equivalence tests: every historical golden from the pre-T2 tests is
//! reproduced here at the integration level via the new public API.
//!
//! The in-crate tests in `src/history.rs::tests` and
//! `src/time_slice.rs::tests` are the primary regression net for numerical
//! behavior. This file provides Game-level goldens that stand alone and are
//! more naturally expressed as integration tests.
use approx::assert_ulps_eq;
use trueskill_tt::{ConstantDrift, Game, GameOptions, Gaussian, Outcome, Rating};
type R = Rating<i64, ConstantDrift>;
fn ts_rating(mu: f64, sigma: f64, beta: f64, gamma: f64) -> R {
R::new(
Gaussian::from_ms(mu, sigma),
beta,
ConstantDrift::new(gamma),
)
}
#[test]
fn game_1v1_golden_matches_historical() {
let a = ts_rating(25.0, 25.0 / 3.0, 25.0 / 6.0, 25.0 / 300.0);
let b = ts_rating(25.0, 25.0 / 3.0, 25.0 / 6.0, 25.0 / 300.0);
let post = Game::<i64, _>::one_v_one(&a, &b, Outcome::winner(0, 2), &GameOptions::default())
.unwrap()
.posteriors();
let (a_post, b_post) = (post[0][0], post[1][0]);
// Historical golden from pre-T2 test_1vs1 (team 0 wins):
assert_ulps_eq!(
a_post,
Gaussian::from_ms(29.205220, 7.194481),
epsilon = 1e-6
);
assert_ulps_eq!(
b_post,
Gaussian::from_ms(20.794779, 7.194481),
epsilon = 1e-6
);
}
#[test]
fn game_1v1_draw_golden() {
let a = ts_rating(25.0, 25.0 / 3.0, 25.0 / 6.0, 25.0 / 300.0);
let b = ts_rating(25.0, 25.0 / 3.0, 25.0 / 6.0, 25.0 / 300.0);
let g = Game::<i64, _>::ranked(
&[&[a], &[b]],
Outcome::draw(2),
&GameOptions {
p_draw: 0.25,
score_sigma: 1.0,
convergence: Default::default(),
},
)
.unwrap();
let p = g.posteriors();
// Historical golden from pre-T2 test_1vs1_draw. The mean is 25.0 exactly
// by symmetry — two identical competitors drawing cannot move apart — and
// the reference's 24.999999 is that value transcribed to six decimals.
assert_ulps_eq!(p[0][0], Gaussian::from_ms(25.0, 6.469480), epsilon = 1e-6);
assert_ulps_eq!(p[1][0], Gaussian::from_ms(25.0, 6.469480), epsilon = 1e-6);
}