A crate whose docs call the same thing three names is hard to learn regardless of how good the functions are. Three collisions, all cheap to fix.
1. "rating" — and one usage is the exact opposite of the Rating type
Rating the type is static configuration.rating.rs:10: "Static rating configuration: prior skill, performance noise beta, drift."History::rating() is explicit: "Every other accessor returns what inference inferred; this returns what it was told."
quality()'s parameter is rating_groups: &[&[Gaussian]] and its docs say "rating groups" four times — here "rating" means a posterior, i.e. exactly what Rating is documented not to be.
error.rs:71 and history.rs:1606: "every rating is finite, the ordering looks sensible" — here it is the fitted posterior again.
A reader who learns Rating = config passes Rating values to quality(); it takes Gaussian. A reader who learns "rating = what comes out" is baffled that h.rating(&k) doesn't give their skill — h.current_skill(&k) does.
Fix: reserve "rating" for the type. quality(teams: &[&[Gaussian]], beta: f64), and rewrite the prose to "teams of skill distributions". Rewrite the two "every rating is finite" strings to "every posterior is finite". Breaks: nothing — a parameter name and doc strings.
The third is called score_sigma everywhere it is a config field (HistoryBuilder::score_sigma, GameOptions::score_sigma, EventKind::Scored { score_sigma }) and plain sigma everywhere it is an argument. Its own doc has to disambiguate itself: "sigma overrides HistoryBuilder::score_sigma".
So .scores_with_sigma([3.0, 1.0], 2.0) reads as "these scores have prior sigma 2.0". Nothing in the name says which sigma.
Fix: one name everywhere — rename the field to score_sigma and the constructors to scores_with_noise(scores, score_sigma) (or scores_with_score_sigma, if literalness beats readability). Breaks: the Outcome::Scored field name (pattern matches) and both method names.
There is a fourth sigma conceptually — beta, performance noise — and neither HistoryBuilder::beta nor ::sigma distinguishes skill uncertainty from performance noise in its doc. A new user has no way to know which knob to turn. That is the single most load-bearing undocumented distinction in the crate.
3. "agent" is a private fourth name that leaks into public signatures
200 occurrences of agent in src/, but only 2 in doc comments — it is an identifier-level synonym for "competitor". Harmless if internal; it is not:
Fix: rename agent/agents → competitor/competitors crate-wide (the field is History.agents). With #73 un-exporting TimeSlice, this becomes a pure internal rename breaking nothing.
4. "player" survives in one public signature
Game::free_for_all(players: &[&Rating<T, D>], ..), plus "for two single-player teams" and "Wraps each player in a one-member team". Everything else says "competitor" — 236 uses, 149 in docs. Breaks: nothing.
Used consistently — no action needed
"performance" means, without exception, skill widened by beta (Rating::performance() is prior.forget(beta²)). "skill" consistently means the latent quantity or its Gaussian. "member" and "team" are stable. "estimate" is a fourth word for the same object as "posterior"/"skill", but always in the phrase "skill estimate", so it reads fine.
Found by an API audit, 2026-09-09.
A crate whose docs call the same thing three names is hard to learn regardless of how good the functions are. Three collisions, all cheap to fix.
## 1. "rating" — and one usage is the exact opposite of the `Rating` type
- **`Rating` the type is static configuration.** `rating.rs:10`: *"Static rating configuration: prior skill, performance noise `beta`, drift."* `History::rating()` is explicit: *"Every other accessor returns what inference **inferred**; this returns what it was **told**."*
- **`quality()`'s parameter is `rating_groups: &[&[Gaussian]]`** and its docs say "rating groups" four times — here "rating" means a **posterior**, i.e. exactly what `Rating` is documented *not* to be.
- **`error.rs:71` and `history.rs:1606`**: *"every **rating** is finite, the ordering looks sensible"* — here it is the fitted posterior again.
A reader who learns `Rating = config` passes `Rating` values to `quality()`; it takes `Gaussian`. A reader who learns "rating = what comes out" is baffled that `h.rating(&k)` doesn't give their skill — `h.current_skill(&k)` does.
**Fix:** reserve "rating" for the type. `quality(teams: &[&[Gaussian]], beta: f64)`, and rewrite the prose to "teams of skill distributions". Rewrite the two "every rating is finite" strings to "every **posterior** is finite". *Breaks:* nothing — a parameter name and doc strings.
## 2. "sigma" names three unrelated quantities
| Spelling | Meaning |
|---|---|
| `HistoryBuilder::sigma` | **prior** standard deviation |
| `Gaussian::sigma()` | a distribution's SD |
| `Outcome::Scored { sigma }`, `Outcome::scores_with_sigma(scores, sigma)`, `EventBuilder::scores_with_sigma` | **score observation noise** |
The third is called `score_sigma` everywhere it is a config *field* (`HistoryBuilder::score_sigma`, `GameOptions::score_sigma`, `EventKind::Scored { score_sigma }`) and plain `sigma` everywhere it is an *argument*. Its own doc has to disambiguate itself: *"`sigma` overrides `HistoryBuilder::score_sigma`"*.
So `.scores_with_sigma([3.0, 1.0], 2.0)` reads as "these scores have prior sigma 2.0". Nothing in the name says which sigma.
**Fix:** one name everywhere — rename the field to `score_sigma` and the constructors to `scores_with_noise(scores, score_sigma)` (or `scores_with_score_sigma`, if literalness beats readability). *Breaks:* the `Outcome::Scored` field name (pattern matches) and both method names.
There is a fourth sigma conceptually — `beta`, performance noise — and **neither `HistoryBuilder::beta` nor `::sigma` distinguishes skill uncertainty from performance noise in its doc.** A new user has no way to know which knob to turn. That is the single most load-bearing undocumented distinction in the crate.
## 3. "agent" is a private fourth name that leaks into public signatures
200 occurrences of `agent` in `src/`, but only 2 in doc comments — it is an identifier-level synonym for "competitor". Harmless if internal; it is not:
- `TimeSlice::add_events(..., agents: &CompetitorStore<T, D>)` — **`pub`**
- `TimeSlice::iteration(&mut self, from: usize, agents: &CompetitorStore<T, D>)` — **`pub`**
**Fix:** rename `agent`/`agents` → `competitor`/`competitors` crate-wide (the field is `History.agents`). With #73 un-exporting `TimeSlice`, this becomes a pure internal rename breaking nothing.
## 4. "player" survives in one public signature
`Game::free_for_all(players: &[&Rating<T, D>], ..)`, plus *"for two single-**player** teams"* and *"Wraps each **player** in a one-member team"*. Everything else says "competitor" — 236 uses, 149 in docs. *Breaks:* nothing.
## Used consistently — no action needed
**"performance"** means, without exception, skill widened by `beta` (`Rating::performance()` is `prior.forget(beta²)`). **"skill"** consistently means the latent quantity or its Gaussian. **"member"** and **"team"** are stable. "estimate" is a fourth word for the same object as "posterior"/"skill", but always in the phrase "skill estimate", so it reads fine.
Found by an API audit, 2026-09-09.
Three of the four collisions are closed; one naming choice is left for you.
1. "rating" — already resolved before this pass. quality takes teams: &[&[Gaussian]], and the "every rating is finite" strings in converge's doc and the README now say "every posterior is finite". No occurrence of "rating group" survives outside matrix.rs's internal prose about contrast rows.
2. "sigma" — the field and parameter are renamed. Outcome::Scored { score_sigma }, and scores_with_sigma(scores, score_sigma). That was the half that was pure inconsistency: every config site already spelled it score_sigma, and only this one spelled it sigma — with a doc that had to disambiguate itself ("sigma overrides HistoryBuilder::score_sigma").
The method names Outcome::scores_with_sigma and EventBuilder::scores_with_sigma are untouched, because that is a choice rather than a fix. Your two candidates:
scores_with_noise — reads best at the call site: .scores_with_noise([3.0, 1.0], 2.0).
scores_with_score_sigma — literal, matches the field exactly, stutters.
Say which and it is a two-line change.
3. "agent" — gone. Zero occurrences in src/. The two public leaks (TimeSlice::add_events, TimeSlice::iteration) had already closed when #73 un-exported TimeSlice, so the rest was an internal rename that broke nothing.
4. "player" — gone. Game::free_for_all no longer exists.
The one your issue called load-bearing
neither HistoryBuilder::beta nor ::sigma distinguishes skill uncertainty from performance noise in its doc. That is the single most load-bearing undocumented distinction in the crate.
Agreed, and fixed. Both docs now name the axis — sigma is epistemic, what the model does not yet know and evidence shrinks; beta is aleatoric, the scatter of a single showing that no amount of evidence removes — and, more usefully, each names the symptom that should send you to it:
ratings move too slowly → raise sigma (or gamma, if skill genuinely moves)
one upset swings ratings too far → raise beta
beta also now says it is what makes an upset possible at all: at beta == 0 the better competitor always wins.
Done in 055575a (merged as f14c783). Leaving this open on the method-name question alone.
Three of the four collisions are closed; one naming choice is left for you.
**1. "rating"** — already resolved before this pass. `quality` takes `teams: &[&[Gaussian]]`, and the "every rating is finite" strings in `converge`'s doc and the README now say "every **posterior** is finite". No occurrence of "rating group" survives outside `matrix.rs`'s internal prose about contrast rows.
**2. "sigma"** — the *field* and *parameter* are renamed. `Outcome::Scored { score_sigma }`, and `scores_with_sigma(scores, score_sigma)`. That was the half that was pure inconsistency: every config site already spelled it `score_sigma`, and only this one spelled it `sigma` — with a doc that had to disambiguate itself ("`sigma` overrides `HistoryBuilder::score_sigma`").
The *method* names `Outcome::scores_with_sigma` and `EventBuilder::scores_with_sigma` are untouched, because that is a choice rather than a fix. Your two candidates:
- `scores_with_noise` — reads best at the call site: `.scores_with_noise([3.0, 1.0], 2.0)`.
- `scores_with_score_sigma` — literal, matches the field exactly, stutters.
Say which and it is a two-line change.
**3. "agent"** — gone. Zero occurrences in `src/`. The two public leaks (`TimeSlice::add_events`, `TimeSlice::iteration`) had already closed when #73 un-exported `TimeSlice`, so the rest was an internal rename that broke nothing.
**4. "player"** — gone. `Game::free_for_all` no longer exists.
## The one your issue called load-bearing
> neither `HistoryBuilder::beta` nor `::sigma` distinguishes skill uncertainty from performance noise in its doc. That is the single most load-bearing undocumented distinction in the crate.
Agreed, and fixed. Both docs now name the axis — `sigma` is *epistemic*, what the model does not yet know and evidence shrinks; `beta` is *aleatoric*, the scatter of a single showing that no amount of evidence removes — and, more usefully, each names the symptom that should send you to it:
- ratings move too slowly → raise `sigma` (or `gamma`, if skill genuinely moves)
- one upset swings ratings too far → raise `beta`
`beta` also now says it is what makes an upset possible at all: at `beta == 0` the better competitor always wins.
Done in 055575a (merged as f14c783). Leaving this open on the method-name question alone.
scores_with_noise it is — 13a395f (merged as 5619360). That was the last item, so this closes.
Outcome::scores_with_noise(scores, score_sigma) and EventBuilder::scores_with_noise(scores, score_sigma). Both docs now say what the quantity is rather than only what it overrides: observation noise on the score margin, in the units of the scores, not a skill sigma. That is the sentence the old name needed and did not have.
Final state of the four collisions:
"rating"
reserved for the type; quality takes &[&[Gaussian]], prose says "posterior"
"sigma"
score_sigma everywhere it is score noise; beta and sigma docs now name the epistemic/aleatoric split and the symptom that sends you to each
"agent"
zero occurrences in src/
"player"
zero — free_for_all takes competitors
`scores_with_noise` it is — 13a395f (merged as 5619360). That was the last item, so this closes.
`Outcome::scores_with_noise(scores, score_sigma)` and `EventBuilder::scores_with_noise(scores, score_sigma)`. Both docs now say what the quantity is rather than only what it overrides: observation noise on the *score margin*, in the units of the scores, not a skill sigma. That is the sentence the old name needed and did not have.
Final state of the four collisions:
| | |
|---|---|
| "rating" | reserved for the type; `quality` takes `&[&[Gaussian]]`, prose says "posterior" |
| "sigma" | `score_sigma` everywhere it is score noise; `beta` and `sigma` docs now name the epistemic/aleatoric split and the symptom that sends you to each |
| "agent" | zero occurrences in `src/` |
| "player" | zero — `free_for_all` takes `competitors` |
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.
A crate whose docs call the same thing three names is hard to learn regardless of how good the functions are. Three collisions, all cheap to fix.
1. "rating" — and one usage is the exact opposite of the
RatingtypeRatingthe type is static configuration.rating.rs:10: "Static rating configuration: prior skill, performance noisebeta, drift."History::rating()is explicit: "Every other accessor returns what inference inferred; this returns what it was told."quality()'s parameter israting_groups: &[&[Gaussian]]and its docs say "rating groups" four times — here "rating" means a posterior, i.e. exactly whatRatingis documented not to be.error.rs:71andhistory.rs:1606: "every rating is finite, the ordering looks sensible" — here it is the fitted posterior again.A reader who learns
Rating = configpassesRatingvalues toquality(); it takesGaussian. A reader who learns "rating = what comes out" is baffled thath.rating(&k)doesn't give their skill —h.current_skill(&k)does.Fix: reserve "rating" for the type.
quality(teams: &[&[Gaussian]], beta: f64), and rewrite the prose to "teams of skill distributions". Rewrite the two "every rating is finite" strings to "every posterior is finite". Breaks: nothing — a parameter name and doc strings.2. "sigma" names three unrelated quantities
HistoryBuilder::sigmaGaussian::sigma()Outcome::Scored { sigma },Outcome::scores_with_sigma(scores, sigma),EventBuilder::scores_with_sigmaThe third is called
score_sigmaeverywhere it is a config field (HistoryBuilder::score_sigma,GameOptions::score_sigma,EventKind::Scored { score_sigma }) and plainsigmaeverywhere it is an argument. Its own doc has to disambiguate itself: "sigmaoverridesHistoryBuilder::score_sigma".So
.scores_with_sigma([3.0, 1.0], 2.0)reads as "these scores have prior sigma 2.0". Nothing in the name says which sigma.Fix: one name everywhere — rename the field to
score_sigmaand the constructors toscores_with_noise(scores, score_sigma)(orscores_with_score_sigma, if literalness beats readability). Breaks: theOutcome::Scoredfield name (pattern matches) and both method names.There is a fourth sigma conceptually —
beta, performance noise — and neitherHistoryBuilder::betanor::sigmadistinguishes skill uncertainty from performance noise in its doc. A new user has no way to know which knob to turn. That is the single most load-bearing undocumented distinction in the crate.3. "agent" is a private fourth name that leaks into public signatures
200 occurrences of
agentinsrc/, but only 2 in doc comments — it is an identifier-level synonym for "competitor". Harmless if internal; it is not:TimeSlice::add_events(..., agents: &CompetitorStore<T, D>)—pubTimeSlice::iteration(&mut self, from: usize, agents: &CompetitorStore<T, D>)—pubFix: rename
agent/agents→competitor/competitorscrate-wide (the field isHistory.agents). With #73 un-exportingTimeSlice, this becomes a pure internal rename breaking nothing.4. "player" survives in one public signature
Game::free_for_all(players: &[&Rating<T, D>], ..), plus "for two single-player teams" and "Wraps each player in a one-member team". Everything else says "competitor" — 236 uses, 149 in docs. Breaks: nothing.Used consistently — no action needed
"performance" means, without exception, skill widened by
beta(Rating::performance()isprior.forget(beta²)). "skill" consistently means the latent quantity or its Gaussian. "member" and "team" are stable. "estimate" is a fourth word for the same object as "posterior"/"skill", but always in the phrase "skill estimate", so it reads fine.Found by an API audit, 2026-09-09.
Three of the four collisions are closed; one naming choice is left for you.
1. "rating" — already resolved before this pass.
qualitytakesteams: &[&[Gaussian]], and the "every rating is finite" strings inconverge's doc and the README now say "every posterior is finite". No occurrence of "rating group" survives outsidematrix.rs's internal prose about contrast rows.2. "sigma" — the field and parameter are renamed.
Outcome::Scored { score_sigma }, andscores_with_sigma(scores, score_sigma). That was the half that was pure inconsistency: every config site already spelled itscore_sigma, and only this one spelled itsigma— with a doc that had to disambiguate itself ("sigmaoverridesHistoryBuilder::score_sigma").The method names
Outcome::scores_with_sigmaandEventBuilder::scores_with_sigmaare untouched, because that is a choice rather than a fix. Your two candidates:scores_with_noise— reads best at the call site:.scores_with_noise([3.0, 1.0], 2.0).scores_with_score_sigma— literal, matches the field exactly, stutters.Say which and it is a two-line change.
3. "agent" — gone. Zero occurrences in
src/. The two public leaks (TimeSlice::add_events,TimeSlice::iteration) had already closed when #73 un-exportedTimeSlice, so the rest was an internal rename that broke nothing.4. "player" — gone.
Game::free_for_allno longer exists.The one your issue called load-bearing
Agreed, and fixed. Both docs now name the axis —
sigmais epistemic, what the model does not yet know and evidence shrinks;betais aleatoric, the scatter of a single showing that no amount of evidence removes — and, more usefully, each names the symptom that should send you to it:sigma(orgamma, if skill genuinely moves)betabetaalso now says it is what makes an upset possible at all: atbeta == 0the better competitor always wins.Done in
055575a(merged asf14c783). Leaving this open on the method-name question alone.scores_with_noiseit is —13a395f(merged as5619360). That was the last item, so this closes.Outcome::scores_with_noise(scores, score_sigma)andEventBuilder::scores_with_noise(scores, score_sigma). Both docs now say what the quantity is rather than only what it overrides: observation noise on the score margin, in the units of the scores, not a skill sigma. That is the sentence the old name needed and did not have.Final state of the four collisions:
qualitytakes&[&[Gaussian]], prose says "posterior"score_sigmaeverywhere it is score noise;betaandsigmadocs now name the epistemic/aleatoric split and the symptom that sends you to eachsrc/free_for_alltakescompetitors