`Index` was public, `History::intern` and `History::lookup` returned
one, and no public method anywhere accepted one. It was a handle with
nowhere to go — and `key_table.rs` advertised the hot-path story it was
meant to enable ("power users can promote `&K` to `Index` and skip the
lookup"), which was never reachable through the public API.
It also shadowed `std::ops::Index`, which `CompetitorStore` implements,
so `use trueskill_tt::*` alongside `use std::ops::*` collided.
All three are `pub(crate)` now. `intern` stays internal because
ingestion needs it; `lookup` is gone entirely, since `current_skill`,
`rating` and `learning_curve` already answer "does this history know
this key" and all three take a borrowed key.
The three tests that used them asserted things a caller cannot observe.
They now assert what the interning bought:
- `record_winner_creates_two_competitors` compares posteriors instead of
comparing two opaque indices for inequality.
- `intern_is_idempotent` becomes `a_repeated_key_is_one_competitor` — a
key appearing in two events gives one competitor with a two-point
learning curve, which is the observable form of the same claim.
- `lookup_returns_none_for_missing` becomes `an_unknown_key_is_unknown`.
Closes #73.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
64 lines
1.9 KiB
Rust
64 lines
1.9 KiB
Rust
use trueskill_tt::{ConstantDrift, ConvergenceOptions, History};
|
|
|
|
#[test]
|
|
fn record_winner_builds_history() {
|
|
let mut h = History::builder()
|
|
.mu(25.0)
|
|
.sigma(25.0 / 3.0)
|
|
.beta(25.0 / 6.0)
|
|
.drift(ConstantDrift::new(25.0 / 300.0))
|
|
.convergence(ConvergenceOptions {
|
|
max_iter: 30,
|
|
epsilon: 1e-6,
|
|
alpha: 1.0,
|
|
})
|
|
.build();
|
|
|
|
h.record_winner(&"alice", &"bob", 1).unwrap();
|
|
let _ = h.converge().unwrap();
|
|
|
|
// `lookup` returned an `Index` that nothing public accepted, so the
|
|
// observable claim is the one worth making: two distinct competitors, each
|
|
// with their own posterior, and the winner ahead.
|
|
assert_eq!(h.competitor_count(), 2);
|
|
let alice = h.current_skill("alice").expect("alice played");
|
|
let bob = h.current_skill("bob").expect("bob played");
|
|
assert!(alice.mu() > bob.mu());
|
|
}
|
|
|
|
/// The same key names the same competitor across events, which is what
|
|
/// interning bought and the only part of it a caller can observe.
|
|
#[test]
|
|
fn a_repeated_key_is_one_competitor() {
|
|
let mut h: History = History::builder().build();
|
|
h.record_winner(&"alice", &"bob", 1).unwrap();
|
|
h.record_winner(&"alice", &"carol", 2).unwrap();
|
|
|
|
assert_eq!(h.competitor_count(), 3);
|
|
assert_eq!(h.learning_curve("alice").expect("known").len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn an_unknown_key_is_unknown() {
|
|
let h: History = History::builder().build();
|
|
assert!(h.current_skill("nobody").is_none());
|
|
assert!(h.learning_curve("nobody").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn record_draw_with_p_draw_set() {
|
|
let mut h = History::builder()
|
|
.mu(25.0)
|
|
.sigma(25.0 / 3.0)
|
|
.beta(25.0 / 6.0)
|
|
.drift(ConstantDrift::new(25.0 / 300.0))
|
|
.p_draw(0.25)
|
|
.build();
|
|
|
|
h.record_draw(&"alice", &"bob", 1).unwrap();
|
|
let _ = h.converge().unwrap();
|
|
|
|
assert!(h.current_skill("alice").is_some());
|
|
assert!(h.current_skill("bob").is_some());
|
|
}
|