chore: add CI, crate metadata, and crate-level documentation
There was no CI of any kind — no workflows directory at all — despite a full release pipeline (release.toml, cliff.toml, a maintained CHANGELOG, three tagged releases). The workflow covers the feature combinations that actually have distinct behaviour, including a release-profile job: `debug_assert!` is compiled out there, which is exactly where the validation this branch added has to hold, and a debug-only suite would never have seen it. Determinism is checked at RAYON_NUM_THREADS of 1, 2, 4 and 8. The Justfile gains test/lint/fmt/determinism recipes so the same checks run locally with one command, and `just ci` runs the lot. `Cargo.toml` had only name, version and edition, so `cargo publish` would have been rejected. Added description, repository, readme, keywords, categories, exclude, and `rust-version = "1.85"` — the edition-2024 floor, now verified by a CI job. Two let-chains introduced earlier on this branch would have pushed that to 1.88; they are rewritten to keep the floor where it was. `src/lib.rs` had no `//!` header at all, so the docs.rs landing page would have been a bare symbol list — conspicuous given every other module has one. It now explains what Through Time does differently, and carries three runnable examples (which `cargo test --doc` checks, where previously there was nothing to check), including the draw/p_draw interaction that is the easiest way to get an error out of this crate. `cargo publish --dry-run` now packages and verifies cleanly. The only remaining blocker is `license`, which is yours to choose — noted as a TODO in the manifest rather than picked unilaterally. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DnsaJg74eNSva3PJjK2eej
This commit is contained in:
+86
@@ -1,3 +1,89 @@
|
||||
//! TrueSkill Through Time — Bayesian skill rating over a time axis.
|
||||
//!
|
||||
//! Where plain TrueSkill gives each competitor one running estimate, TrueSkill
|
||||
//! Through Time treats a whole history as a single model and infers skill *at
|
||||
//! every point in time*. Evidence flows both directions: a result today
|
||||
//! sharpens the estimate of who someone was last year, so early estimates stop
|
||||
//! being frozen guesses and comparisons across eras become meaningful.
|
||||
//!
|
||||
//! This is a Rust port of
|
||||
//! [TrueSkillThroughTime.py](https://github.com/glandfried/TrueSkillThroughTime.py).
|
||||
//!
|
||||
//! # Getting started
|
||||
//!
|
||||
//! Record results, converge, then read off skills:
|
||||
//!
|
||||
//! ```
|
||||
//! use trueskill_tt::History;
|
||||
//!
|
||||
//! let mut history = History::default();
|
||||
//!
|
||||
//! history.record_winner(&"alice", &"bob", 1)?;
|
||||
//! history.record_winner(&"bob", &"carol", 2)?;
|
||||
//! history.record_winner(&"alice", &"carol", 3)?;
|
||||
//!
|
||||
//! let report = history.converge()?;
|
||||
//! assert!(report.converged);
|
||||
//!
|
||||
//! let alice = history.current_skill("alice").unwrap();
|
||||
//! assert!(alice.mu() > 0.0, "alice won every game she played");
|
||||
//! # Ok::<(), trueskill_tt::InferenceError>(())
|
||||
//! ```
|
||||
//!
|
||||
//! Teams, weights, explicit rankings and continuous scores go through the
|
||||
//! fluent event builder:
|
||||
//!
|
||||
//! ```
|
||||
//! use trueskill_tt::History;
|
||||
//!
|
||||
//! let mut history = History::builder().p_draw(0.1).build();
|
||||
//!
|
||||
//! history
|
||||
//! .event(1)
|
||||
//! .team(["alice", "bob"])
|
||||
//! .team(["carol", "dave"])
|
||||
//! .ranking([0, 1])
|
||||
//! .commit()?;
|
||||
//!
|
||||
//! history.converge()?;
|
||||
//! # Ok::<(), trueskill_tt::InferenceError>(())
|
||||
//! ```
|
||||
//!
|
||||
//! # Draws need a draw probability
|
||||
//!
|
||||
//! A `p_draw` of zero asserts that draws cannot happen, so a tied result has
|
||||
//! no representable likelihood and is rejected:
|
||||
//!
|
||||
//! ```
|
||||
//! use trueskill_tt::{History, InferenceError};
|
||||
//!
|
||||
//! let mut history = History::default(); // p_draw defaults to 0.0
|
||||
//! let err = history.record_draw(&"alice", &"bob", 1).unwrap_err();
|
||||
//! assert!(matches!(err, InferenceError::TieWithoutDrawProbability { .. }));
|
||||
//! ```
|
||||
//!
|
||||
//! This also applies to [`Outcome::winner`] for three or more teams, which
|
||||
//! ties every loser. Configure a positive `p_draw` for those.
|
||||
//!
|
||||
//! # Core types
|
||||
//!
|
||||
//! - [`History`] — the top-level container: ingests events, runs
|
||||
//! forward/backward message passing, and answers queries.
|
||||
//! - [`Gaussian`] — the probability type, stored in natural parameters
|
||||
//! (`pi = 1/sigma²`, `tau = mu/sigma²`) so message passing is add/subtract.
|
||||
//! - [`Game`] — one match in isolation, for scoring a hypothetical without a
|
||||
//! history.
|
||||
//! - [`Outcome`] — how a match ended: ranks, or continuous scores.
|
||||
//! - [`Rating`] — a competitor's static configuration (prior, `beta`, drift).
|
||||
//!
|
||||
//! # Feature flags
|
||||
//!
|
||||
//! - `approx` — implements [`approx`](https://docs.rs/approx) equality traits
|
||||
//! for [`Gaussian`]. Useful in tests.
|
||||
//! - `rayon` — parallelises the within-slice sweep and the per-slice passes of
|
||||
//! `learning_curves`/`log_evidence`. Opt-in; results stay bit-identical
|
||||
//! regardless of worker count.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use std::{
|
||||
|
||||
Reference in New Issue
Block a user