Three unrelated small defects, all requiring signature changes: - `Game::one_v_one` hardcoded `GameOptions::default()`, so a 1v1 could never set `p_draw` or convergence options — and a drawn 1v1 was therefore unreachable through it, since the default `p_draw` is zero. It now takes `&GameOptions` like every other constructor. - `Observer::on_batch_processed` was declared on the trait and never called from anywhere: implementors wired up a callback that could not fire. It is now called after each slice sweep, and renamed `on_slice_processed` to match the vocabulary the codebase adopted in T2 — the unit of work is a `TimeSlice`, not a batch. A slice is swept once travelling backward and once forward, so a multi-slice history fires it twice per slice per iteration; the doc comment says so. - `pub mod factors` sat beside `pub(crate) mod factor`, two module paths differing by one character with only one of them importable. The public facade is now `graph`. Tests cover each as a behaviour rather than a compile check: a drawn 1v1 succeeds only when p_draw is supplied, and the observer tests fail if any callback stops firing. BREAKING CHANGE: `Game::one_v_one` takes a fourth `&GameOptions` argument; `Observer::on_batch_processed` is renamed `on_slice_processed`; the `factors` module is renamed `graph`. Closes #21 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
58 lines
2.1 KiB
Rust
58 lines
2.1 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(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 (a_post, b_post) =
|
|
Game::<i64, _>::one_v_one(&a, &b, Outcome::winner(0, 2), &GameOptions::default()).unwrap();
|
|
// 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);
|
|
}
|