Third tier of the ingestion API (spec Section 4). Powers one-off events with irregular shapes where neither record_winner (too simple) nor typed add_events (too verbose) fits cleanly. EventBuilder accumulates teams, weights, and outcome. Supports: - .team([keys]) — add a team - .weights([w..]) — per-member weights on the most-recently-added team - .ranking([ranks]) — explicit per-team ranks - .winner(i) — convenience: team i wins, others tied - .draw() — all teams tied - .commit() — finalize into an Event<T, K> and delegate to add_events Part of T2 of docs/superpowers/specs/2026-04-23-trueskill-engine-redesign-design.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
145 lines
3.6 KiB
Rust
145 lines
3.6 KiB
Rust
//! Tests for the new T2 public API surface: typed add_events(iter) and the
|
|
//! fluent event builder (added in Task 16).
|
|
|
|
use smallvec::smallvec;
|
|
use trueskill_tt::{ConstantDrift, ConvergenceOptions, Event, History, Member, Outcome, Team};
|
|
|
|
#[test]
|
|
fn add_events_bulk_via_iter() {
|
|
let mut h = History::builder()
|
|
.mu(0.0)
|
|
.sigma(2.0)
|
|
.beta(1.0)
|
|
.p_draw(0.0)
|
|
.drift(ConstantDrift(0.0))
|
|
.convergence(ConvergenceOptions {
|
|
max_iter: 30,
|
|
epsilon: 1e-6,
|
|
})
|
|
.build();
|
|
|
|
let events: Vec<Event<i64, &'static str>> = vec![
|
|
Event {
|
|
time: 1,
|
|
teams: smallvec![
|
|
Team::with_members([Member::new("a")]),
|
|
Team::with_members([Member::new("b")]),
|
|
],
|
|
outcome: Outcome::winner(0, 2),
|
|
},
|
|
Event {
|
|
time: 2,
|
|
teams: smallvec![
|
|
Team::with_members([Member::new("b")]),
|
|
Team::with_members([Member::new("c")]),
|
|
],
|
|
outcome: Outcome::winner(0, 2),
|
|
},
|
|
];
|
|
|
|
h.add_events(events).unwrap();
|
|
let report = h.converge().unwrap();
|
|
assert!(report.converged);
|
|
assert!(h.lookup(&"a").is_some());
|
|
assert!(h.lookup(&"b").is_some());
|
|
assert!(h.lookup(&"c").is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn add_events_draw() {
|
|
let mut h = History::builder()
|
|
.mu(25.0)
|
|
.sigma(25.0 / 3.0)
|
|
.beta(25.0 / 6.0)
|
|
.p_draw(0.25)
|
|
.drift(ConstantDrift(25.0 / 300.0))
|
|
.build();
|
|
|
|
let events: Vec<Event<i64, &'static str>> = vec![Event {
|
|
time: 1,
|
|
teams: smallvec![
|
|
Team::with_members([Member::new("alice")]),
|
|
Team::with_members([Member::new("bob")]),
|
|
],
|
|
outcome: Outcome::draw(2),
|
|
}];
|
|
h.add_events(events).unwrap();
|
|
h.converge().unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn add_events_rejects_mismatched_outcome_ranks() {
|
|
use trueskill_tt::InferenceError;
|
|
let mut h: History = History::builder().build();
|
|
let events: Vec<Event<i64, &'static str>> = vec![Event {
|
|
time: 1,
|
|
teams: smallvec![
|
|
Team::with_members([Member::new("a")]),
|
|
Team::with_members([Member::new("b")]),
|
|
],
|
|
outcome: Outcome::ranking([0, 1, 2]), // 3 ranks but 2 teams
|
|
}];
|
|
let err = h.add_events(events).unwrap_err();
|
|
assert!(matches!(err, InferenceError::MismatchedShape { .. }));
|
|
}
|
|
|
|
#[test]
|
|
fn fluent_event_builder_basic() {
|
|
let mut h = History::builder()
|
|
.mu(25.0)
|
|
.sigma(25.0 / 3.0)
|
|
.beta(25.0 / 6.0)
|
|
.p_draw(0.0)
|
|
.build();
|
|
|
|
h.event(1)
|
|
.team(["alice", "bob"])
|
|
.weights([1.0, 0.7])
|
|
.team(["carol"])
|
|
.ranking([1, 0])
|
|
.commit()
|
|
.unwrap();
|
|
|
|
let report = h.converge().unwrap();
|
|
assert!(report.converged);
|
|
assert!(h.lookup(&"alice").is_some());
|
|
assert!(h.lookup(&"bob").is_some());
|
|
assert!(h.lookup(&"carol").is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn fluent_event_builder_winner_convenience() {
|
|
let mut h = History::builder()
|
|
.mu(25.0)
|
|
.sigma(25.0 / 3.0)
|
|
.beta(25.0 / 6.0)
|
|
.p_draw(0.0)
|
|
.build();
|
|
|
|
h.event(1)
|
|
.team(["alice"])
|
|
.team(["bob"])
|
|
.winner(0)
|
|
.commit()
|
|
.unwrap();
|
|
h.converge().unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn fluent_event_builder_draw() {
|
|
let mut h = History::builder()
|
|
.mu(25.0)
|
|
.sigma(25.0 / 3.0)
|
|
.beta(25.0 / 6.0)
|
|
.p_draw(0.25)
|
|
.build();
|
|
|
|
h.event(1)
|
|
.team(["alice"])
|
|
.team(["bob"])
|
|
.draw()
|
|
.commit()
|
|
.unwrap();
|
|
h.converge().unwrap();
|
|
}
|