feat!: Game is the type you get, and one_v_one returns one

`lib.rs` advertised `Game` in "Core types" as "one match in isolation".
It had no public constructor: every `Game::*` returned `OwnedGame`, so
`let g: Game = Game::ranked(..)?` did not compile.

Names swapped. The public type is the owned one — `Game<T, D>`, no
lifetime — and the borrowing form is `pub(crate) GameRef<'a, T, D>`,
which is what it always was: an implementation detail about whether the
result and weight slices are borrowed from `History`'s storage. That
distinction meant nothing to someone scoring one match, and it showed
the module's surface twice in rustdoc, since both types carried
`posteriors()` / `log_evidence()`.

`one_v_one` returned `(Gaussian, Gaussian)` while every sibling returned
a game, making it the one constructor you could not ask for
`log_evidence()`. It returns `Self` now; `.posteriors()` recovers the old
shape, and the test that covers it now also asserts the evidence of two
identical ratings is exactly `ln(0.5)`.

`ranked`, `scored` and `free_for_all` had `# Errors` as their entire
doc, so rustdoc's index rendered the error list as the summary. They
have summary lines, and `Game` has a worked example — it was advertised
as a core type with none anywhere in the crate.

Closes #69.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
This commit is contained in:
2026-09-09 22:55:05 +02:00
co-authored by Claude Opus 5
parent 4c2e98c54b
commit 92d690d0f8
6 changed files with 157 additions and 101 deletions
+14 -9
View File
@@ -9,7 +9,7 @@ use crate::{
arena::ScratchArena,
color_group::ColorGroups,
drift::Drift,
game::Game,
game::GameRef,
gaussian::Gaussian,
rating::Rating,
storage::{CompetitorStore, SkillStore},
@@ -141,10 +141,15 @@ impl Event {
let teams = self.within_priors(false, skills, competitors);
let result = self.outputs();
let g = match self.kind {
EventKind::Ranked => {
Game::ranked_with_arena(teams, &result, &self.weights, p_draw, convergence, arena)
}
EventKind::Scored { score_sigma } => Game::scored_with_arena(
EventKind::Ranked => GameRef::ranked_with_arena(
teams,
&result,
&self.weights,
p_draw,
convergence,
arena,
),
EventKind::Scored { score_sigma } => GameRef::scored_with_arena(
teams,
&result,
&self.weights,
@@ -409,7 +414,7 @@ impl<T: Time> TimeSlice<T> {
let result = event.outputs();
let g = match event.kind {
EventKind::Ranked => Game::ranked_with_arena(
EventKind::Ranked => GameRef::ranked_with_arena(
teams,
&result,
&event.weights,
@@ -417,7 +422,7 @@ impl<T: Time> TimeSlice<T> {
self.convergence,
&mut self.arena,
),
EventKind::Scored { score_sigma } => Game::scored_with_arena(
EventKind::Scored { score_sigma } => GameRef::scored_with_arena(
teams,
&result,
&event.weights,
@@ -724,7 +729,7 @@ impl<T: Time> TimeSlice<T> {
let result = event.outputs();
match event.kind {
EventKind::Ranked => {
Game::ranked_with_arena(
GameRef::ranked_with_arena(
teams,
&result,
&event.weights,
@@ -735,7 +740,7 @@ impl<T: Time> TimeSlice<T> {
.log_evidence
}
EventKind::Scored { score_sigma } => {
Game::scored_with_arena(
GameRef::scored_with_arena(
teams,
&result,
&event.weights,