Four README code blocks no longer compiled: `Player` was renamed `Rating`
in T2, the `Drift` trait gained a `T: Time` parameter and a second method,
and two blocks were missing imports outright. The `Rating` example needed
more than a rename — with the binding unused, `T` is ambiguous because
`ConstantDrift` implements `Drift<T>` for every `T`, so it now carries an
explicit annotation.
Nothing compiled those blocks. `src/lib.rs` gains a `cfg(doctest)` struct
carrying `#[doc = include_str!("../README.md")]`, which turns every `rust`
block into a doctest without displacing the curated crate docs as the
front page. Verified it bites: reintroducing `Player` fails the build with
E0432 rather than shipping. Illustrative blocks are fenced `text` — note
that a bare fence defaults to `rust` under rustdoc, which is how the
`variance_delta = elapsed * γ²` formula became a compile error.
Prose fixes: README claimed `Gaussian::forget` takes a square root (it
works in variance space) and pointed at a `.gamma()` builder method that
does not exist. CLAUDE.md's data-flow diagram spliced the public ingestion
shape into the internal one — `Team` is not in that chain — listed
`cdf()`/`erfc()` as public when they are `pub(crate)` and private, and
called `SkillStore` public when only `CompetitorStore` escapes the crate.
Rustdoc fixes: `EventBuilder::scores_with_sigma` claimed a debug-assert
that `Outcome::scores_with_sigma` never had and whose own docs contradict;
rejection happens at ingestion as `InvalidParameter`. `event.rs` described
`add_events_with_prior` as replaced when it is still the ingestion
chokepoint. `factors.rs` advertised `Game::custom` without noting it is
`#[doc(hidden)]`. Internal T2/T4 milestone labels are dropped from public
items; the ones in the private `time_slice` module are left alone.
Closes #35
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014b6wy2q8rnFK8U8GPJVQNU
102 lines
3.0 KiB
Rust
102 lines
3.0 KiB
Rust
use std::marker::PhantomData;
|
|
|
|
use crate::{
|
|
BETA, GAMMA,
|
|
drift::{ConstantDrift, Drift},
|
|
gaussian::Gaussian,
|
|
time::Time,
|
|
};
|
|
|
|
/// Static rating configuration: prior skill, performance noise `beta`, drift.
|
|
///
|
|
/// A configuration rather than a person: the per-history temporal state
|
|
/// (messages, last appearance) lives on `Competitor`.
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct Rating<T: Time = i64, D: Drift<T> = ConstantDrift> {
|
|
pub(crate) prior: Gaussian,
|
|
pub(crate) beta: f64,
|
|
pub(crate) drift: D,
|
|
/// Multiplier on the drift *variance* this competitor accumulates; 1.0 is
|
|
/// the neutral default. Set per competitor via `Member::with_drift_scale`.
|
|
pub(crate) drift_scale: f64,
|
|
pub(crate) _time: PhantomData<T>,
|
|
}
|
|
|
|
impl<T: Time, D: Drift<T>> Rating<T, D> {
|
|
pub fn new(prior: Gaussian, beta: f64, drift: D) -> Self {
|
|
Self {
|
|
prior,
|
|
beta,
|
|
drift,
|
|
drift_scale: 1.0,
|
|
_time: PhantomData,
|
|
}
|
|
}
|
|
|
|
/// Scale how fast this competitor drifts, relative to `drift`.
|
|
///
|
|
/// Multiplies the drift *variance*, so the scale is in the same units as
|
|
/// `gamma`. `0.0` pins the competitor still.
|
|
#[must_use]
|
|
pub fn with_drift_scale(mut self, drift_scale: f64) -> Self {
|
|
self.drift_scale = drift_scale;
|
|
self
|
|
}
|
|
|
|
/// The configured prior skill estimate.
|
|
#[must_use]
|
|
pub fn prior(&self) -> Gaussian {
|
|
self.prior
|
|
}
|
|
|
|
/// Performance noise: how much a single showing varies around the skill.
|
|
#[must_use]
|
|
pub fn beta(&self) -> f64 {
|
|
self.beta
|
|
}
|
|
|
|
/// The drift model governing how skill may move between events.
|
|
#[must_use]
|
|
pub fn drift(&self) -> D {
|
|
self.drift
|
|
}
|
|
|
|
/// This competitor's multiplier on the drift variance; 1.0 is neutral.
|
|
#[must_use]
|
|
pub fn drift_scale(&self) -> f64 {
|
|
self.drift_scale
|
|
}
|
|
|
|
/// Drift variance accumulated over `from -> to`, scaled for this competitor.
|
|
///
|
|
/// The single place the scale is applied for a `Time`-typed span. Callers
|
|
/// must go through this rather than `self.drift` directly, so a competitor's
|
|
/// scale cannot be silently skipped.
|
|
pub(crate) fn drift_variance_delta(&self, from: &T, to: &T) -> f64 {
|
|
self.drift.variance_delta(from, to) * self.drift_scale * self.drift_scale
|
|
}
|
|
|
|
/// Drift variance for a cached elapsed count, scaled for this competitor.
|
|
///
|
|
/// The counterpart of `drift_variance_delta` for the cached-elapsed paths.
|
|
pub(crate) fn drift_variance_for_elapsed(&self, elapsed: i64) -> f64 {
|
|
self.drift.variance_for_elapsed(elapsed) * self.drift_scale * self.drift_scale
|
|
}
|
|
|
|
pub(crate) fn performance(&self) -> Gaussian {
|
|
self.prior.forget(self.beta.powi(2))
|
|
}
|
|
}
|
|
|
|
impl Default for Rating<i64, ConstantDrift> {
|
|
fn default() -> Self {
|
|
Self {
|
|
prior: Gaussian::default(),
|
|
beta: BETA,
|
|
drift: ConstantDrift(GAMMA),
|
|
drift_scale: 1.0,
|
|
_time: PhantomData,
|
|
}
|
|
}
|
|
}
|