Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d629d0d94 | ||
|
|
7ca0daa48e |
+1
-1
@@ -4,7 +4,7 @@ use std::time::Duration;
|
||||
|
||||
use smallvec::SmallVec;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct ConvergenceOptions {
|
||||
pub max_iter: usize,
|
||||
pub epsilon: f64,
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ pub trait Drift<T: Time>: Copy + Debug + Send + Sync {
|
||||
/// A non-finite gamma is caught a second time regardless:
|
||||
/// `History::converge` validates the drift variance each competitor actually
|
||||
/// accumulates, which also covers a custom [`Drift`] implementation.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct ConstantDrift(f64);
|
||||
|
||||
impl ConstantDrift {
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ impl DiffFactor {
|
||||
/// `p_draw` and `convergence` apply to ranked outcomes (`Game::ranked`).
|
||||
/// `score_sigma` applies only to scored outcomes (`Game::scored`); it controls
|
||||
/// how much the engine trusts the observed score margin (smaller σ = more trust).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct GameOptions {
|
||||
pub p_draw: f64,
|
||||
pub score_sigma: f64,
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
//! The traits a consumer needs on the public types, pinned so they cannot be
|
||||
//! removed by accident.
|
||||
//!
|
||||
//! This is written from a consumer's position — deriving `Debug` on a struct
|
||||
//! that *holds* a `History` — because that is the thing that failed. Asserting
|
||||
//! `History: Debug` in isolation would not have caught the generic-bound half:
|
||||
//! `Rating` derives `PartialEq`, but that is only usable if `D: PartialEq`, and
|
||||
//! the crate's own only `Drift` impl did not satisfy it.
|
||||
|
||||
use trueskill_tt::{
|
||||
ConstantDrift, ConvergenceOptions, ConvergenceReport, Event, GameOptions, Gaussian, History,
|
||||
HistoryBuilder, InferenceError, Member, Outcome, Rating, Team,
|
||||
};
|
||||
|
||||
/// The reported failure, verbatim: a consumer holding a history in app state.
|
||||
#[derive(Debug)]
|
||||
#[allow(
|
||||
dead_code,
|
||||
reason = "held only so `derive(Debug)` has something to render"
|
||||
)]
|
||||
struct App {
|
||||
history: History,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_struct_holding_a_history_can_derive_debug() {
|
||||
let app = App {
|
||||
history: History::default(),
|
||||
};
|
||||
|
||||
let rendered = format!("{app:?}");
|
||||
|
||||
// Summarising, not a dump of every skill store — the same choice `Joint`'s
|
||||
// manual `Debug` makes about its n² factorisation.
|
||||
assert!(rendered.contains("competitors"), "{rendered}");
|
||||
assert!(rendered.contains("time_slices"), "{rendered}");
|
||||
assert!(
|
||||
!rendered.contains("SkillStore"),
|
||||
"History's Debug should summarise, not dump: {rendered}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn history_builder_is_debug_and_clone() {
|
||||
let b: HistoryBuilder<i64, ConstantDrift, _, &'static str> = History::builder();
|
||||
let cloned = b.clone();
|
||||
assert!(!format!("{cloned:?}").is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_and_input_value_types_are_comparable() {
|
||||
assert_eq!(ConstantDrift::new(0.1), ConstantDrift::new(0.1));
|
||||
assert_ne!(ConstantDrift::new(0.1), ConstantDrift::new(0.2));
|
||||
|
||||
assert_eq!(ConvergenceOptions::default(), ConvergenceOptions::default());
|
||||
assert_eq!(GameOptions::default(), GameOptions::default());
|
||||
|
||||
// `Rating: PartialEq` is only reachable through `D: PartialEq`.
|
||||
assert_eq!(Rating::<i64, ConstantDrift>::default(), Rating::default());
|
||||
assert_ne!(
|
||||
Rating::default(),
|
||||
Rating::<i64, ConstantDrift>::default().with_drift_scale(2.0)
|
||||
);
|
||||
|
||||
assert_eq!(Member::new("a"), Member::new("a"));
|
||||
assert_ne!(Member::new("a"), Member::new("b"));
|
||||
assert_eq!(
|
||||
Team::with_members([Member::new("a")]),
|
||||
Team::with_members([Member::new("a")])
|
||||
);
|
||||
|
||||
let event = || Event {
|
||||
time: 1,
|
||||
teams: [
|
||||
Team::with_members([Member::new("a")]),
|
||||
Team::with_members([Member::new("b")]),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
outcome: Outcome::winner(0, 2),
|
||||
};
|
||||
assert_eq!(event(), event());
|
||||
|
||||
assert_eq!(Gaussian::default(), Gaussian::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_history_is_send_and_sync_and_default() {
|
||||
fn assert_send_sync<X: Send + Sync>() {}
|
||||
assert_send_sync::<History>();
|
||||
assert_send_sync::<InferenceError>();
|
||||
|
||||
let mut h = History::default();
|
||||
let report: ConvergenceReport = h.converge().expect("an empty history converges");
|
||||
assert_eq!(report, report.clone());
|
||||
}
|
||||
Reference in New Issue
Block a user