refactor: one word per concept

Three vocabulary collisions, from #75.

**"rating" meant three things**, one the opposite of the exported type.
`Rating` is documented as static *configuration* — "this returns what it
was told", against every other accessor's "what inference inferred". But
`quality`'s parameter was `rating_groups: &[&[Gaussian]]` and its prose
said "rating groups" four times, where "rating" means a *posterior* — the
one thing `Rating` is documented not to be. Two error messages used it
that way too.

So a reader who learned `Rating = config` passed `Rating` values to
`quality`, which takes `Gaussian`; and one who learned "rating = what
comes out" was baffled that `h.rating(&k)` is not their skill.

"rating" is now reserved for the type. `quality(teams: &[&[Gaussian]])`,
and "every rating is finite" became "every posterior is finite".

**"agent" was a private fourth name for a competitor** — ~200 identifiers
against 236 uses of "competitor", and it leaked into two `pub` signatures
on `TimeSlice`. Now that #73 has made those internal this is a pure
rename, so the crate has one word for the entity throughout.

**"player" survived in one public signature** — `free_for_all(players:)`
plus two doc lines. Renamed, along with three internal closure bindings.
Doc examples that use "player" as a *key* are left alone: that is a
user's data, not the crate's vocabulary.

The panic-message expectations in tests/quality.rs moved with the prose,
which is the point of asserting on message text — the tests caught the
rename rather than papering over it.

Not touched: "performance" (always skill widened by beta), "skill",
"member" and "team" are each used for exactly one thing already.

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:
2026-09-09 20:54:47 +02:00
co-authored by Claude Opus 5
parent 85c4d0d87d
commit fdd1539cab
6 changed files with 234 additions and 207 deletions
+110 -90
View File
@@ -268,7 +268,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> HistoryBuilder<
History {
size: 0,
time_slices: Vec::new(),
agents: CompetitorStore::new(),
competitors: CompetitorStore::new(),
keys: KeyTable::new(),
mu: self.mu,
sigma: self.sigma,
@@ -391,7 +391,7 @@ pub struct History<
> {
size: usize,
pub(crate) time_slices: Vec<TimeSlice<T>>,
pub(crate) agents: CompetitorStore<T, D>,
pub(crate) competitors: CompetitorStore<T, D>,
keys: KeyTable<K>,
mu: f64,
sigma: f64,
@@ -470,17 +470,18 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
return step;
}
competitor::clean(self.agents.values_mut(), false);
competitor::clean(self.competitors.values_mut(), false);
for j in (0..self.time_slices.len() - 1).rev() {
for agent in self.time_slices[j + 1].skills.keys() {
self.agents.get_mut(agent).unwrap().message =
Some(self.time_slices[j + 1].backward_prior_out(&agent, &self.agents));
for competitor in self.time_slices[j + 1].skills.keys() {
self.competitors.get_mut(competitor).unwrap().message = Some(
self.time_slices[j + 1].backward_prior_out(&competitor, &self.competitors),
);
}
let old = self.time_slices[j].posteriors();
self.time_slices[j].new_backward_info(&self.agents);
self.time_slices[j].new_backward_info(&self.competitors);
self.observer.on_slice_processed(
&self.time_slices[j].time,
j,
@@ -494,17 +495,17 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
.fold(step, |step, (a, old)| tuple_max(step, old.delta(new[a])));
}
competitor::clean(self.agents.values_mut(), false);
competitor::clean(self.competitors.values_mut(), false);
for j in 1..self.time_slices.len() {
for agent in self.time_slices[j - 1].skills.keys() {
self.agents.get_mut(agent).unwrap().message =
Some(self.time_slices[j - 1].forward_prior_out(&agent));
for competitor in self.time_slices[j - 1].skills.keys() {
self.competitors.get_mut(competitor).unwrap().message =
Some(self.time_slices[j - 1].forward_prior_out(&competitor));
}
let old = self.time_slices[j].posteriors();
self.time_slices[j].new_forward_info(&self.agents);
self.time_slices[j].new_forward_info(&self.competitors);
self.observer.on_slice_processed(
&self.time_slices[j].time,
j,
@@ -521,7 +522,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
if self.time_slices.len() == 1 {
let old = self.time_slices[0].posteriors();
self.time_slices[0].iteration(0, &self.agents);
self.time_slices[0].iteration(0, &self.competitors);
self.observer.on_slice_processed(
&self.time_slices[0].time,
0,
@@ -676,7 +677,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
let key = format!("{:?}", member.key);
let idx = self.keys.get_or_create(&member.key);
if self.agents.contains(idx) {
if self.competitors.contains(idx) {
return Err(InferenceError::AlreadyRegistered { key });
}
@@ -699,7 +700,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
drift_scale: member.drift_scale,
},
);
self.agents.insert(
self.competitors.insert(
idx,
Competitor {
rating,
@@ -725,7 +726,9 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
Q: std::hash::Hash + Eq + ?Sized,
{
let idx = self.keys.get(key)?;
self.agents.contains(idx).then(|| self.agents[idx].rating)
self.competitors
.contains(idx)
.then(|| self.competitors[idx].rating)
}
#[must_use]
@@ -771,8 +774,8 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
let mut data: HashMap<K, Vec<(T, Gaussian)>> = HashMap::new();
for (time, step) in self.filtered_pass() {
for (agent, posterior) in step.posteriors {
if let Some(key) = self.keys.key(agent).cloned() {
for (competitor, posterior) in step.posteriors {
if let Some(key) = self.keys.key(competitor).cloned() {
data.entry(key).or_default().push((time, posterior));
}
}
@@ -804,7 +807,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
.filter_map(|(time, step)| {
step.posteriors
.iter()
.find(|(agent, _)| *agent == idx)
.find(|(competitor, _)| *competitor == idx)
.map(|&(_, posterior)| (time, posterior))
})
.collect()
@@ -823,7 +826,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
// Bound before the closure so it captures the store rather than all of
// `&self`: capturing `&History` would drag `KeyTable<K>` in and demand
// `K: Sync` from every caller, which the key type need not satisfy.
let agents = &self.agents;
let competitors = &self.competitors;
#[cfg(feature = "rayon")]
{
@@ -831,7 +834,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
let per_slice: Vec<f64> = self
.time_slices
.par_iter()
.map(|ts| ts.log_evidence(targets, forward, agents))
.map(|ts| ts.log_evidence(targets, forward, competitors))
.collect();
per_slice.into_iter().sum()
}
@@ -839,7 +842,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
{
self.time_slices
.iter()
.map(|ts| ts.log_evidence(targets, forward, agents))
.map(|ts| ts.log_evidence(targets, forward, competitors))
.sum()
}
}
@@ -872,10 +875,10 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
let mut pass = Vec::with_capacity(self.time_slices.len());
for slice in &self.time_slices {
let step = slice.filtered_step(&messages, &self.agents);
let step = slice.filtered_step(&messages, &self.competitors);
for &(agent, posterior) in &step.posteriors {
messages.insert(agent, posterior);
for &(competitor, posterior) in &step.posteriors {
messages.insert(competitor, posterior);
}
pass.push((slice.time, step));
@@ -1120,13 +1123,13 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
let mut n = 0usize;
for (slice_idx, slice) in self.time_slices.iter().enumerate() {
for (agent, elapsed) in slice.appearances() {
let rating = &self.agents[agent].rating;
let row = match previous.get(&agent) {
for (competitor, elapsed) in slice.appearances() {
let rating = &self.competitors[competitor].rating;
let row = match previous.get(&competitor) {
None => {
let row = n;
n += 1;
first_rows.push((row, agent));
first_rows.push((row, competitor));
row
}
Some(&prev) => {
@@ -1143,16 +1146,17 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
}
}
};
previous.insert(agent, row);
latest.insert(agent, (row, slice_idx));
at_slice.insert((agent, slice_idx), row);
previous.insert(competitor, row);
latest.insert(competitor, (row, slice_idx));
at_slice.insert((competitor, slice_idx), row);
}
}
let mut lambda = vec![0.0; n * n];
for (row, agent) in first_rows {
lambda[row * n + row] += 1.0 / self.agents[agent].rating.prior.sigma().powi(2);
for (row, competitor) in first_rows {
lambda[row * n + row] +=
1.0 / self.competitors[competitor].rating.prior.sigma().powi(2);
}
for (a, b, drift) in drift_links {
lambda[a * n + a] += 1.0 / drift;
@@ -1161,7 +1165,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
lambda[b * n + a] -= 1.0 / drift;
}
for (slice_idx, slice) in self.time_slices.iter().enumerate() {
for (contrast, noise) in slice.scored_contrasts(&self.agents) {
for (contrast, noise) in slice.scored_contrasts(&self.competitors) {
for (ia, ca) in &contrast {
let ra = at_slice[&(*ia, slice_idx)];
for (ib, cb) in &contrast {
@@ -1511,7 +1515,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
let beta = self
.keys
.get(*key)
.map_or(self.beta, |index| self.agents[index].rating.beta);
.map_or(self.beta, |index| self.competitors[index].rating.beta);
performance_noise += beta * beta;
}
}
@@ -1795,8 +1799,8 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
// defect already rejected for `sigma` and `beta`. A non-finite gamma
// poisons every posterior derived from it.
for slice in &self.time_slices {
for (agent, elapsed) in slice.appearances() {
let drift = self.agents[agent]
for (competitor, elapsed) in slice.appearances() {
let drift = self.competitors[competitor]
.rating
.drift_variance_for_elapsed(elapsed);
if !drift.is_finite() || drift < 0.0 {
@@ -2006,14 +2010,14 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
let mut conflict_scan: Vec<Index> = priors.keys().copied().collect();
conflict_scan.sort_unstable();
for agent in &conflict_scan {
let batch = priors[agent];
let held = self.declared.get(agent).copied().unwrap_or_default();
for competitor in &conflict_scan {
let batch = priors[competitor];
let held = self.declared.get(competitor).copied().unwrap_or_default();
if let (Some(existing), Some(new)) = (held.prior, batch.prior) {
if existing != new {
return Err(InferenceError::ConflictingCompetitorConfig {
competitor: agent.get(),
competitor: competitor.get(),
field: "prior",
});
}
@@ -2021,15 +2025,15 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
if let (Some(existing), Some(new)) = (held.drift_scale, batch.drift_scale) {
if existing != new {
return Err(InferenceError::ConflictingCompetitorConfig {
competitor: agent.get(),
competitor: competitor.get(),
field: "drift_scale",
});
}
}
}
for (agent, batch) in &priors {
let entry = self.declared.entry(*agent).or_default();
for (competitor, batch) in &priors {
let entry = self.declared.entry(*competitor).or_default();
if batch.prior.is_some() {
entry.prior = batch.prior;
}
@@ -2038,22 +2042,22 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
}
}
competitor::clean(self.agents.values_mut(), true);
competitor::clean(self.competitors.values_mut(), true);
let mut this_agent = Vec::with_capacity(1024);
for agent in composition.iter().flatten().flatten() {
if this_agent.contains(agent) {
for competitor in composition.iter().flatten().flatten() {
if this_agent.contains(competitor) {
continue;
}
this_agent.push(*agent);
this_agent.push(*competitor);
// From `declared` rather than `priors`: a competitor configured by
// `register` before any event has nothing in this batch's map.
let config = self.declared.get(agent).copied().unwrap_or_default();
let config = self.declared.get(competitor).copied().unwrap_or_default();
if self.agents.contains(*agent) {
if self.competitors.contains(*competitor) {
// Seeding a competitor the history already knows. This used to
// be dropped on the floor: `remove` was only reached on the
// create path, so a prior applied on a competitor's very first
@@ -2062,7 +2066,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
continue;
}
let rating = &mut self.agents.get_mut(*agent).unwrap().rating;
let rating = &mut self.competitors.get_mut(*competitor).unwrap().rating;
if let Some(prior) = config.prior {
rating.prior = prior;
}
@@ -2082,7 +2086,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
// `clean` has just nulled every message, so the earliest
// slice's forward is exactly the prior.
for slice in &mut self.time_slices {
if let Some(skill) = slice.skills.get_mut(*agent) {
if let Some(skill) = slice.skills.get_mut(*competitor) {
skill.forward = seeded;
break;
}
@@ -2101,8 +2105,8 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
rating.drift_scale = scale;
}
self.agents.insert(
*agent,
self.competitors.insert(
*competitor,
Competitor {
rating,
message: None,
@@ -2144,20 +2148,20 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
let time_slice = &mut self.time_slices[k];
if k > 0 {
time_slice.new_forward_info(&self.agents);
time_slice.new_forward_info(&self.competitors);
}
for agent_idx in &this_agent {
if let Some(skill) = time_slice.skills.get_mut(*agent_idx) {
skill.elapsed = time_slice::compute_elapsed(
self.agents[*agent_idx].last_time.as_ref(),
self.competitors[*agent_idx].last_time.as_ref(),
&time_slice.time,
);
let agent = self.agents.get_mut(*agent_idx).unwrap();
let competitor = self.competitors.get_mut(*agent_idx).unwrap();
agent.last_time = Some(time_slice.time);
agent.message = Some(time_slice.forward_prior_out(agent_idx));
competitor.last_time = Some(time_slice.time);
competitor.message = Some(time_slice.forward_prior_out(agent_idx));
}
}
@@ -2184,29 +2188,41 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
if self.time_slices.len() > k && self.time_slices[k].time == t {
let time_slice = &mut self.time_slices[k];
time_slice.add_events(composition, results, weights, kinds_chunk, &self.agents);
time_slice.add_events(
composition,
results,
weights,
kinds_chunk,
&self.competitors,
);
for agent_idx in time_slice.skills.keys() {
let agent = self.agents.get_mut(agent_idx).unwrap();
let competitor = self.competitors.get_mut(agent_idx).unwrap();
agent.last_time = Some(t);
agent.message = Some(time_slice.forward_prior_out(&agent_idx));
competitor.last_time = Some(t);
competitor.message = Some(time_slice.forward_prior_out(&agent_idx));
}
k += 1;
} else {
let mut time_slice = TimeSlice::new(t, self.p_draw, self.convergence);
time_slice.add_events(composition, results, weights, kinds_chunk, &self.agents);
time_slice.add_events(
composition,
results,
weights,
kinds_chunk,
&self.competitors,
);
self.time_slices.insert(k, time_slice);
let time_slice = &self.time_slices[k];
for agent_idx in time_slice.skills.keys() {
let agent = self.agents.get_mut(agent_idx).unwrap();
let competitor = self.competitors.get_mut(agent_idx).unwrap();
agent.last_time = Some(t);
agent.message = Some(time_slice.forward_prior_out(&agent_idx));
competitor.last_time = Some(t);
competitor.message = Some(time_slice.forward_prior_out(&agent_idx));
}
k += 1;
@@ -2218,19 +2234,19 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
while self.time_slices.len() > k {
let time_slice = &mut self.time_slices[k];
time_slice.new_forward_info(&self.agents);
time_slice.new_forward_info(&self.competitors);
for agent_idx in &this_agent {
if let Some(skill) = time_slice.skills.get_mut(*agent_idx) {
skill.elapsed = time_slice::compute_elapsed(
self.agents[*agent_idx].last_time.as_ref(),
self.competitors[*agent_idx].last_time.as_ref(),
&time_slice.time,
);
let agent = self.agents.get_mut(*agent_idx).unwrap();
let competitor = self.competitors.get_mut(*agent_idx).unwrap();
agent.last_time = Some(time_slice.time);
agent.message = Some(time_slice.forward_prior_out(agent_idx));
competitor.last_time = Some(time_slice.time);
competitor.message = Some(time_slice.forward_prior_out(agent_idx));
}
}
@@ -2606,9 +2622,9 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> Joint<'_, T, D,
if slice.time > time {
break;
}
for (agent, _) in slice.appearances() {
if let Some(row) = self.at_slice.get(&(agent, slice_idx)) {
as_of.insert(agent, (*row, slice_idx));
for (competitor, _) in slice.appearances() {
if let Some(row) = self.at_slice.get(&(competitor, slice_idx)) {
as_of.insert(competitor, (*row, slice_idx));
}
}
}
@@ -2657,7 +2673,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> Joint<'_, T, D,
.keys
.get(*key)
.map_or(self.history.beta, |index| {
self.history.agents[index].rating.beta
self.history.competitors[index].rating.beta
});
noise += beta * beta;
}
@@ -2812,7 +2828,11 @@ mod tests {
let w = [vec![1.0], vec![1.0]];
let p = Game::ranked_with_arena(
h.time_slices[1].events[0].within_priors(false, &h.time_slices[1].skills, &h.agents),
h.time_slices[1].events[0].within_priors(
false,
&h.time_slices[1].skills,
&h.competitors,
),
&[0.0, 1.0],
&w,
P_DRAW,
@@ -3731,7 +3751,7 @@ mod tests {
let mut max_diff: f64 = 0.0;
for (key, capped_pts) in curves_capped.iter() {
let full_pts = curves_full.get(key).expect("agent missing in full");
let full_pts = curves_full.get(key).expect("competitor missing in full");
for (capped, full) in capped_pts.iter().zip(full_pts.iter()) {
max_diff = max_diff.max((capped.1.mu() - full.1.mu()).abs());
max_diff = max_diff.max((capped.1.sigma() - full.1.sigma()).abs());
@@ -3778,7 +3798,7 @@ mod tests {
let mut max_diff: f64 = 0.0;
for (key, u_pts) in curves_u.iter() {
let d_pts = curves_d.get(key).expect("agent missing in damped");
let d_pts = curves_d.get(key).expect("competitor missing in damped");
for (u, d) in u_pts.iter().zip(d_pts.iter()) {
max_diff = max_diff.max((u.1.mu() - d.1.mu()).abs());
max_diff = max_diff.max((u.1.sigma() - d.1.sigma()).abs());
@@ -3824,10 +3844,10 @@ mod tests {
let curves_a = h_a.learning_curves();
let curves_b = h_b.learning_curves();
for (key, a_pts) in curves_a.iter() {
let b_pts = curves_b.get(key).expect("agent missing in path B");
let b_pts = curves_b.get(key).expect("competitor missing in path B");
for (a, b) in a_pts.iter().zip(b_pts.iter()) {
assert_eq!(a.1.pi(), b.1.pi(), "mismatch at agent {key:?}");
assert_eq!(a.1.tau(), b.1.tau(), "mismatch at agent {key:?}");
assert_eq!(a.1.pi(), b.1.pi(), "mismatch at competitor {key:?}");
assert_eq!(a.1.tau(), b.1.tau(), "mismatch at competitor {key:?}");
}
}
}
@@ -3866,10 +3886,10 @@ mod tests {
let curves_a = h_a.learning_curves();
let curves_b = h_b.learning_curves();
for (key, a_pts) in curves_a.iter() {
let b_pts = curves_b.get(key).expect("agent missing in path B");
let b_pts = curves_b.get(key).expect("competitor missing in path B");
for (a, b) in a_pts.iter().zip(b_pts.iter()) {
assert_eq!(a.1.pi(), b.1.pi(), "mismatch at agent {key:?}");
assert_eq!(a.1.tau(), b.1.tau(), "mismatch at agent {key:?}");
assert_eq!(a.1.pi(), b.1.pi(), "mismatch at competitor {key:?}");
assert_eq!(a.1.tau(), b.1.tau(), "mismatch at competitor {key:?}");
}
}
@@ -3889,7 +3909,7 @@ mod tests {
let curves_c = h_c.learning_curves();
let mut max_diff: f64 = 0.0;
for (key, a_pts) in curves_a.iter() {
let c_pts = curves_c.get(key).expect("agent missing in path C");
let c_pts = curves_c.get(key).expect("competitor missing in path C");
for (a, c) in a_pts.iter().zip(c_pts.iter()) {
max_diff = max_diff.max((a.1.mu() - c.1.mu()).abs());
max_diff = max_diff.max((a.1.sigma() - c.1.sigma()).abs());
@@ -3931,10 +3951,10 @@ mod tests {
let curves_a = h_a.learning_curves();
let curves_b = h_b.learning_curves();
for (key, a_pts) in curves_a.iter() {
let b_pts = curves_b.get(key).expect("agent missing");
let b_pts = curves_b.get(key).expect("competitor missing");
for (a, b) in a_pts.iter().zip(b_pts.iter()) {
assert_eq!(a.1.pi(), b.1.pi(), "mismatch at agent {key:?}");
assert_eq!(a.1.tau(), b.1.tau(), "mismatch at agent {key:?}");
assert_eq!(a.1.pi(), b.1.pi(), "mismatch at competitor {key:?}");
assert_eq!(a.1.tau(), b.1.tau(), "mismatch at competitor {key:?}");
}
}
}