docs: document the whole public surface and deny(missing_docs)
80 undocumented public items, including three that are first contact:
`History::current_skill` — the method the crate's own first example calls
— `EventBuilder`, the type `h.event(t)` hands you, and `Gaussian::mu()`.
Now zero, and `#![deny(missing_docs)]` keeps it that way.
Several docs are measurements rather than readings of the code:
- `Outcome::Ranked` says ranks are used ordinally, so `[0, 1, 2]` and
`[0, 5, 90]` are the same observation. Measured: bit-identical
posteriors for both.
- `OwnedGame::log_evidence` says two identically-rated competitors give
exactly `ln(0.5)`. Written as a doctest, so it runs.
- `Member::weight` says zero and negative are accepted. Measured.
- `ConvergenceReport::final_step` is `(|Δmu|, |Δsigma|)` in skill units,
NOT natural parameters. That one had to be traced through
`Gaussian::delta` rather than assumed from the neighbouring vocabulary.
- `GameOptions::score_sigma` rejects non-positive and NaN but accepts
`+inf`, which is what the guard actually says.
README: it is the front door for a crate on a private registry, and it
opened with a link dump followed by 130 lines on drift. The first
`record_winner → converge → current_skill` block was at line 226 of 307.
It now leads with what the crate is, an install line, a quickstart, a
"which entry point?" table, and the `converge`-is-strict rationale that
was the crate's most opinionated recent decision and went unmentioned.
The two canonical examples disagreed on spelling (`History::default()`
vs `History::builder().build()`, `current_skill("a")` vs
`current_skill(&"a")`); they now agree. Five new README blocks are
doctested, taking the suite from 19 to 25.
`pub use smallvec;`. Four public items name `SmallVec` in their
signatures, and the only `Joint` example failed to compile from a
consumer crate with `unresolved import smallvec` — the dependency was in
the API but not reachable. Both worked examples now use the re-export,
so they teach the path that works downstream.
Vocabulary, from #75: "agent" was a fourth word for competitor, 200
occurrences, and it had reached public signatures before #73 un-exported
`TimeSlice`. Now zero.
Closes #77. Refs #75.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
This commit is contained in:
@@ -13,8 +13,25 @@ use crate::{gaussian::Gaussian, outcome::Outcome, time::Time};
|
||||
/// A single match at time `time` involving some number of teams.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Event<T: Time, K> {
|
||||
/// When the match happened, on the history's time axis.
|
||||
///
|
||||
/// Events sharing a `time` land in the same time slice and are fitted
|
||||
/// together, so nothing distinguishes their order. Drift is driven by the
|
||||
/// gap between a competitor's *consecutive appearances*, not by the gap
|
||||
/// between slices, so a competitor idle across several slices accumulates
|
||||
/// the whole span at once when it next plays.
|
||||
pub time: T,
|
||||
/// The teams that took part, positionally aligned with `outcome`: team `i`
|
||||
/// here is the team `outcome` ranks or scores at index `i`.
|
||||
///
|
||||
/// Ingestion rejects fewer than two teams (`NotEnoughTeams`) and any team
|
||||
/// with no members (`EmptyTeam`).
|
||||
pub teams: SmallVec<[Team<K>; 4]>,
|
||||
/// How the match ended: ranks (lower is better) or per-team scores (higher
|
||||
/// is better), one entry per entry of `teams`.
|
||||
///
|
||||
/// A tie — two equal ranks — needs a positive `p_draw`, otherwise
|
||||
/// ingestion fails with `TieWithoutDrawProbability`.
|
||||
pub outcome: Outcome,
|
||||
}
|
||||
|
||||
@@ -22,16 +39,31 @@ pub struct Event<T: Time, K> {
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[must_use]
|
||||
pub struct Team<K> {
|
||||
/// The competitors playing together, in no significant order: the team's
|
||||
/// performance is the weight-scaled sum over its members, which does not
|
||||
/// depend on how they are listed.
|
||||
///
|
||||
/// Must be non-empty — an empty team contributes no performance at all, so
|
||||
/// ingestion rejects it with `EmptyTeam` rather than returning a plausible
|
||||
/// posterior for whoever it was matched against.
|
||||
pub members: SmallVec<[Member<K>; 4]>,
|
||||
}
|
||||
|
||||
impl<K> Team<K> {
|
||||
/// A team with no members yet, to be filled through the public `members`
|
||||
/// field.
|
||||
///
|
||||
/// Committing it while still empty is an `EmptyTeam` error.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
members: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A team of exactly these competitors.
|
||||
///
|
||||
/// Members must be built already — `Member::from(key)` covers the common
|
||||
/// case of a plain key at default weight with no overrides.
|
||||
pub fn with_members<I: IntoIterator<Item = Member<K>>>(members: I) -> Self {
|
||||
Self {
|
||||
members: members.into_iter().collect(),
|
||||
@@ -64,8 +96,26 @@ impl<K> Default for Team<K> {
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[must_use]
|
||||
pub struct Member<K> {
|
||||
/// The competitor's identity. Equal keys across events are the same
|
||||
/// competitor: `History` interns each distinct key to an internal `Index`
|
||||
/// the first time it sees it, and every later appearance resolves to that
|
||||
/// same competitor's temporal state.
|
||||
pub key: K,
|
||||
/// This member's share of the team's performance, for this event only.
|
||||
///
|
||||
/// The team's performance is the sum of `weight × member performance`, so
|
||||
/// `1.0` is a full share and `0.5` counts the member half; the message
|
||||
/// coming back to the member is divided by the same weight. Defaults to
|
||||
/// `1.0`.
|
||||
///
|
||||
/// Must be finite — a NaN or infinite weight is `InvalidParameter` at
|
||||
/// ingestion. Zero and negative are accepted, both being expressible in
|
||||
/// the same arithmetic.
|
||||
pub weight: f64,
|
||||
/// Starting skill for this competitor, replacing the history's `mu`/`sigma`
|
||||
/// default. `None` keeps the history default.
|
||||
///
|
||||
/// Competitor configuration, not a per-event value; see the type docs.
|
||||
pub prior: Option<Gaussian>,
|
||||
/// Multiplier on the drift *variance* this competitor accumulates.
|
||||
/// `None` means 1.0.
|
||||
@@ -73,6 +123,8 @@ pub struct Member<K> {
|
||||
}
|
||||
|
||||
impl<K> Member<K> {
|
||||
/// A competitor taking a full share of its team's performance, with no
|
||||
/// configuration overrides: the history's prior and drift apply.
|
||||
pub fn new(key: K) -> Self {
|
||||
Self {
|
||||
key,
|
||||
@@ -82,6 +134,12 @@ impl<K> Member<K> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Change how much of the team's performance this member accounts for.
|
||||
///
|
||||
/// Unlike `prior` and `drift_scale`, this is genuinely per-event: the same
|
||||
/// key can carry a different weight in every event it appears in, which is
|
||||
/// what makes it usable for partial participation — a substitute who
|
||||
/// played half the match, a doubles partner credited unequally.
|
||||
pub fn with_weight(mut self, weight: f64) -> Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
|
||||
Reference in New Issue
Block a user