refactor(api): generify Drift, Rating, Competitor, TimeSlice, CompetitorStore, History over T: Time
Drift now takes &T -> &T and is generic over the time axis. Untimed impls return elapsed=0. ConstantDrift impl covers all T via the Time trait. An additional variance_for_elapsed(i64) method on the trait serves callers that work with the pre-cached i64 elapsed count. Competitor.last_time moves from i64 with MIN sentinel to Option<T> with None sentinel. receive(&T) computes variance from last_time dynamically; receive_for_elapsed(i64) uses a pre-cached elapsed count (needed in convergence sweeps where last_time has already advanced). TimeSlice.time changes from i64 to T. compute_elapsed is now generic over T and takes Option<&T> for the last-seen time. new_forward_info uses receive_for_elapsed to preserve the cached elapsed during sweeps. History<D> becomes History<T, D>; HistoryBuilder<D> becomes HistoryBuilder<T, D>; Game<D> becomes Game<T, D>. Defaults keep existing call sites compiling with zero changes: T = i64, D = ConstantDrift. add_events / add_events_with_prior stay on impl History<i64, D> since times: Vec<i64> is i64-specific (Task 8 will generalise this). In !self.time mode the old i64::MAX sentinel guaranteed elapsed=1 for every slice transition regardless of time gaps. Replaced by advancing all previously-seen agents' last_time to Some(current_slice_time) at the end of each slice; this preserves elapsed=1 between adjacent slices in sequential-integer untimed mode. The time: bool field on History and .time(bool) on HistoryBuilder are NOT removed by this task — deferred to Task 8 so this commit is purely a type-level generification. Part of T2 of docs/superpowers/specs/2026-04-23-trueskill-engine-redesign-design.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,17 +1,17 @@
|
||||
use crate::{Index, competitor::Competitor, drift::Drift};
|
||||
use crate::{Index, competitor::Competitor, drift::Drift, time::Time};
|
||||
|
||||
/// Dense Vec-backed store for competitor state in History.
|
||||
///
|
||||
/// Indexed directly by Index.0, eliminating HashMap hashing in the
|
||||
/// forward/backward sweep. Uses `Vec<Option<Competitor<D>>>` so slots can be
|
||||
/// forward/backward sweep. Uses `Vec<Option<Competitor<T, D>>>` so slots can be
|
||||
/// absent without an explicit present mask.
|
||||
#[derive(Debug)]
|
||||
pub struct CompetitorStore<D: Drift> {
|
||||
competitors: Vec<Option<Competitor<D>>>,
|
||||
pub struct CompetitorStore<T: Time = i64, D: Drift<T> = crate::drift::ConstantDrift> {
|
||||
competitors: Vec<Option<Competitor<T, D>>>,
|
||||
n_present: usize,
|
||||
}
|
||||
|
||||
impl<D: Drift> Default for CompetitorStore<D> {
|
||||
impl<T: Time, D: Drift<T>> Default for CompetitorStore<T, D> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
competitors: Vec::new(),
|
||||
@@ -20,7 +20,7 @@ impl<D: Drift> Default for CompetitorStore<D> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Drift> CompetitorStore<D> {
|
||||
impl<T: Time, D: Drift<T>> CompetitorStore<T, D> {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
@@ -31,7 +31,7 @@ impl<D: Drift> CompetitorStore<D> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, idx: Index, competitor: Competitor<D>) {
|
||||
pub fn insert(&mut self, idx: Index, competitor: Competitor<T, D>) {
|
||||
self.ensure_capacity(idx.0);
|
||||
if self.competitors[idx.0].is_none() {
|
||||
self.n_present += 1;
|
||||
@@ -39,11 +39,11 @@ impl<D: Drift> CompetitorStore<D> {
|
||||
self.competitors[idx.0] = Some(competitor);
|
||||
}
|
||||
|
||||
pub fn get(&self, idx: Index) -> Option<&Competitor<D>> {
|
||||
pub fn get(&self, idx: Index) -> Option<&Competitor<T, D>> {
|
||||
self.competitors.get(idx.0).and_then(|slot| slot.as_ref())
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, idx: Index) -> Option<&mut Competitor<D>> {
|
||||
pub fn get_mut(&mut self, idx: Index) -> Option<&mut Competitor<T, D>> {
|
||||
self.competitors
|
||||
.get_mut(idx.0)
|
||||
.and_then(|slot| slot.as_mut())
|
||||
@@ -61,34 +61,34 @@ impl<D: Drift> CompetitorStore<D> {
|
||||
self.n_present == 0
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (Index, &Competitor<D>)> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = (Index, &Competitor<T, D>)> {
|
||||
self.competitors
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, slot)| slot.as_ref().map(|a| (Index(i), a)))
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (Index, &mut Competitor<D>)> {
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (Index, &mut Competitor<T, D>)> {
|
||||
self.competitors
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.filter_map(|(i, slot)| slot.as_mut().map(|a| (Index(i), a)))
|
||||
}
|
||||
|
||||
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Competitor<D>> {
|
||||
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Competitor<T, D>> {
|
||||
self.competitors.iter_mut().filter_map(|s| s.as_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Drift> std::ops::Index<Index> for CompetitorStore<D> {
|
||||
type Output = Competitor<D>;
|
||||
fn index(&self, idx: Index) -> &Competitor<D> {
|
||||
impl<T: Time, D: Drift<T>> std::ops::Index<Index> for CompetitorStore<T, D> {
|
||||
type Output = Competitor<T, D>;
|
||||
fn index(&self, idx: Index) -> &Competitor<T, D> {
|
||||
self.get(idx).expect("competitor not found at index")
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Drift> std::ops::IndexMut<Index> for CompetitorStore<D> {
|
||||
fn index_mut(&mut self, idx: Index) -> &mut Competitor<D> {
|
||||
impl<T: Time, D: Drift<T>> std::ops::IndexMut<Index> for CompetitorStore<T, D> {
|
||||
fn index_mut(&mut self, idx: Index) -> &mut Competitor<T, D> {
|
||||
self.get_mut(idx).expect("competitor not found at index")
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn insert_then_get() {
|
||||
let mut store: CompetitorStore<ConstantDrift> = CompetitorStore::new();
|
||||
let mut store: CompetitorStore<i64, ConstantDrift> = CompetitorStore::new();
|
||||
let idx = Index(7);
|
||||
store.insert(idx, Competitor::default());
|
||||
assert!(store.contains(idx));
|
||||
@@ -110,7 +110,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn iter_in_index_order() {
|
||||
let mut store: CompetitorStore<ConstantDrift> = CompetitorStore::new();
|
||||
let mut store: CompetitorStore<i64, ConstantDrift> = CompetitorStore::new();
|
||||
store.insert(Index(2), Competitor::default());
|
||||
store.insert(Index(0), Competitor::default());
|
||||
store.insert(Index(5), Competitor::default());
|
||||
@@ -120,7 +120,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn index_operator_works() {
|
||||
let mut store: CompetitorStore<ConstantDrift> = CompetitorStore::new();
|
||||
let mut store: CompetitorStore<i64, ConstantDrift> = CompetitorStore::new();
|
||||
store.insert(Index(3), Competitor::default());
|
||||
let _ = &store[Index(3)];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user