Every associated function named Game::* returns OwnedGame:
Game::ranked (game.rs:470)
-> Result<OwnedGame<T, D>, _>
Game::scored (:530)
-> Result<OwnedGame<T, D>, _>
Game::free_for_all (:601)
-> Result<OwnedGame<T, D>, _>
Game::ranked_with_arena
pub(crate)
So Game<'a, T, D> has no public constructor at all, and let g: Game = Game::ranked(..)? does not compile. Owned is an implementation detail — borrowed versus owned slices — that means nothing to someone scoring one match, and both types carry a posteriors() / log_evidence() pair, so rustdoc shows the surface twice.
Every sibling returns a game; the one convenience wrapper returns a tuple, so it is the only constructor you cannot ask for log_evidence(). An auditor reading the family guessed both the arity and the return type wrong.
Fix
Swap the names so the public type is the one you get:
That removes a type from the public API, removes a lifetime parameter from Game, halves the apparent surface of the module, and makes the advertised core type constructible.
While here
Game::ranked, Game::scored and Game::free_for_all have /// # Errors as their entire doc — no summary line, so rustdoc's index renders the error list as the summary. There is no example of Game anywhere: not in the README, not in lib.rs, not on the type. It is advertised as a core type with no worked example.
free_for_all's parameter is players:, the only place the crate says "player" rather than "competitor" (236 uses elsewhere).
Breaks: removes OwnedGame from the public API; changes one_v_one's return type.
Found by an API audit, 2026-09-09; the return type reproduced independently.
`lib.rs` advertises `Game` in "Core types" as *"one match in isolation"*. It cannot be constructed.
## Measured
```rust
let g = Game::<i64, _>::ranked(&[&[a], &[a]], Outcome::winner(0, 2), &GameOptions::default())?;
println!("{}", std::any::type_name_of_val(&g));
// trueskill_tt::game::OwnedGame<i64, trueskill_tt::drift::ConstantDrift>
```
Every associated function named `Game::*` returns `OwnedGame`:
| | |
|---|---|
| `Game::ranked` (`game.rs:470`) | `-> Result<OwnedGame<T, D>, _>` |
| `Game::scored` (`:530`) | `-> Result<OwnedGame<T, D>, _>` |
| `Game::free_for_all` (`:601`) | `-> Result<OwnedGame<T, D>, _>` |
| `Game::ranked_with_arena` | `pub(crate)` |
So `Game<'a, T, D>` has **no public constructor at all**, and `let g: Game = Game::ranked(..)?` does not compile. `Owned` is an implementation detail — borrowed versus owned slices — that means nothing to someone scoring one match, and both types carry a `posteriors()` / `log_evidence()` pair, so rustdoc shows the surface twice.
## `one_v_one` breaks the family a second way
`game.rs:586`:
```rust
pub fn one_v_one(a, b, outcome, options) -> Result<(Gaussian, Gaussian), _>
```
Every sibling returns a game; the one convenience wrapper returns a tuple, so it is the only constructor you cannot ask for `log_evidence()`. An auditor reading the family guessed both the arity and the return type wrong.
## Fix
Swap the names so the public type is the one you get:
```rust
pub struct Game<T: Time = i64, D: Drift<T> = ConstantDrift> { .. } // today's OwnedGame
pub(crate) struct GameRef<'a, T, D> { .. } // today's Game<'a>
impl<T: Time, D: Drift<T>> Game<T, D> {
pub fn ranked(teams: &[&[Rating<T, D>]], outcome: Outcome, options: &GameOptions) -> Result<Self, InferenceError>;
pub fn scored(..) -> Result<Self, InferenceError>;
pub fn free_for_all(competitors: &[&Rating<T, D>], ..) -> Result<Self, InferenceError>;
pub fn one_v_one(..) -> Result<Self, InferenceError>; // was (Gaussian, Gaussian)
pub fn posteriors(&self) -> Vec<Vec<Gaussian>>;
pub fn log_evidence(&self) -> f64;
}
```
That removes a type from the public API, removes a lifetime parameter from `Game`, halves the apparent surface of the module, and makes the advertised core type constructible.
## While here
`Game::ranked`, `Game::scored` and `Game::free_for_all` have `/// # Errors` as their **entire** doc — no summary line, so rustdoc's index renders the error list as the summary. There is no example of `Game` anywhere: not in the README, not in `lib.rs`, not on the type. It is advertised as a core type with no worked example.
`free_for_all`'s parameter is `players:`, the only place the crate says "player" rather than "competitor" (236 uses elsewhere).
**Breaks:** removes `OwnedGame` from the public API; changes `one_v_one`'s return type.
Found by an API audit, 2026-09-09; the return type reproduced independently.
Done in 92d690d (merged as 8e34410), exactly as proposed.
Game<T, D> is the public owned type — no lifetime parameter — and the borrowing form is pub(crate) GameRef<'a, T, D>. OwnedGame is gone from the public API.
one_v_one returns Self. The test covering it now also asserts what it could not ask before: two identical ratings give exactly ln(0.5).
Two small consequences of the swap worth recording:
GameRef::log_evidence and GameRef::posteriors became dead once Game stopped delegating to them — every in-crate caller reads the fields directly. log_evidence is deleted; posteriors is kept #[cfg(test)], since the module's own goldens use it. Same shape as the earlier get_composition / get_results fix.
free_for_all's parameter was already competitors, so the "player" item was closed before this pass.
The doc items are done too: ranked, scored and free_for_all have summary lines, and Game carries a worked example — an underdog beating a favourite, asserting both posteriors moved the right way and that log_evidence is below ln(0.5) because an upset is improbable. It runs as a doctest.
Done in 92d690d (merged as 8e34410), exactly as proposed.
`Game<T, D>` is the public owned type — no lifetime parameter — and the borrowing form is `pub(crate) GameRef<'a, T, D>`. `OwnedGame` is gone from the public API.
```
trueskill_tt::game::Game<i64, trueskill_tt::drift::ConstantDrift>
one_v_one log_evidence = -0.6931471805599453
```
`one_v_one` returns `Self`. The test covering it now also asserts what it could not ask before: two identical ratings give exactly `ln(0.5)`.
Two small consequences of the swap worth recording:
- `GameRef::log_evidence` and `GameRef::posteriors` became dead once `Game` stopped delegating to them — every in-crate caller reads the fields directly. `log_evidence` is deleted; `posteriors` is kept `#[cfg(test)]`, since the module's own goldens use it. Same shape as the earlier `get_composition` / `get_results` fix.
- `free_for_all`'s parameter was already `competitors`, so the "player" item was closed before this pass.
The doc items are done too: `ranked`, `scored` and `free_for_all` have summary lines, and `Game` carries a worked example — an underdog beating a favourite, asserting both posteriors moved the right way and that `log_evidence` is below `ln(0.5)` because an upset is improbable. It runs as a doctest.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
lib.rsadvertisesGamein "Core types" as "one match in isolation". It cannot be constructed.Measured
Every associated function named
Game::*returnsOwnedGame:Game::ranked(game.rs:470)-> Result<OwnedGame<T, D>, _>Game::scored(:530)-> Result<OwnedGame<T, D>, _>Game::free_for_all(:601)-> Result<OwnedGame<T, D>, _>Game::ranked_with_arenapub(crate)So
Game<'a, T, D>has no public constructor at all, andlet g: Game = Game::ranked(..)?does not compile.Ownedis an implementation detail — borrowed versus owned slices — that means nothing to someone scoring one match, and both types carry aposteriors()/log_evidence()pair, so rustdoc shows the surface twice.one_v_onebreaks the family a second waygame.rs:586:Every sibling returns a game; the one convenience wrapper returns a tuple, so it is the only constructor you cannot ask for
log_evidence(). An auditor reading the family guessed both the arity and the return type wrong.Fix
Swap the names so the public type is the one you get:
That removes a type from the public API, removes a lifetime parameter from
Game, halves the apparent surface of the module, and makes the advertised core type constructible.While here
Game::ranked,Game::scoredandGame::free_for_allhave/// # Errorsas their entire doc — no summary line, so rustdoc's index renders the error list as the summary. There is no example ofGameanywhere: not in the README, not inlib.rs, not on the type. It is advertised as a core type with no worked example.free_for_all's parameter isplayers:, the only place the crate says "player" rather than "competitor" (236 uses elsewhere).Breaks: removes
OwnedGamefrom the public API; changesone_v_one's return type.Found by an API audit, 2026-09-09; the return type reproduced independently.
Done in
92d690d(merged as8e34410), exactly as proposed.Game<T, D>is the public owned type — no lifetime parameter — and the borrowing form ispub(crate) GameRef<'a, T, D>.OwnedGameis gone from the public API.one_v_onereturnsSelf. The test covering it now also asserts what it could not ask before: two identical ratings give exactlyln(0.5).Two small consequences of the swap worth recording:
GameRef::log_evidenceandGameRef::posteriorsbecame dead onceGamestopped delegating to them — every in-crate caller reads the fields directly.log_evidenceis deleted;posteriorsis kept#[cfg(test)], since the module's own goldens use it. Same shape as the earlierget_composition/get_resultsfix.free_for_all's parameter was alreadycompetitors, so the "player" item was closed before this pass.The doc items are done too:
ranked,scoredandfree_for_allhave summary lines, andGamecarries a worked example — an underdog beating a favourite, asserting both posteriors moved the right way and thatlog_evidenceis belowln(0.5)because an upset is improbable. It runs as a doctest.