fix(simulation): address PR #50 review — determinism, overflow, invariants

- Sort eligible NPCs by StableId for deterministic conversation pairing (D-010)
- Replace single_mut() with per-observer iteration (D-010 principle 3, D-027)
- Widen current_stress * 100 to i32 in mood derivation to prevent i16 overflow
- Promote line_interval to named constant LINE_INTERVAL_TICKS
- Add 4 Sprint 14 component invariants (MoodState, InteractionMemory,
  ActivityState+PathRequest exclusion, double-tagged tier detection)
- Attach MoodState to content spawn pipeline and gauntlet fixup
- VecDeque for InteractionMemory.notable_events (O(1) pop_front)
- Rename who_knows() to who_knows_full_scan() to communicate O(N) cost
- Add O(N^2) growth limit comment on conversation pair scan
- Document Suspicious/Focused as externally-set moods on enum variants

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 19:18:34 +01:00
co-authored by Claude Opus 4.6
parent 019a7b2221
commit f0e0673c99
7 changed files with 205 additions and 28 deletions
+4 -4
View File
@@ -47,7 +47,7 @@ pub struct InteractionMemory {
pub last_interaction_tick: u64,
/// Notable events: walk-aways and confrontations.
/// Bounded by `MAX_NOTABLE_EVENTS` — oldest entries dropped when full.
pub notable_events: Vec<InteractionEvent>,
pub notable_events: std::collections::VecDeque<InteractionEvent>,
}
/// Maximum number of notable events retained per NPC pair.
@@ -65,9 +65,9 @@ impl InteractionMemory {
/// Append a notable event, dropping the oldest if at capacity.
pub fn push_event(&mut self, event: InteractionEvent) {
if self.notable_events.len() >= MAX_NOTABLE_EVENTS {
self.notable_events.remove(0);
self.notable_events.pop_front();
}
self.notable_events.push(event);
self.notable_events.push_back(event);
}
/// Returns `true` if this is the first meeting (count == 0).
@@ -145,7 +145,7 @@ mod tests {
});
assert_eq!(mem.notable_events.len(), MAX_NOTABLE_EVENTS);
assert_eq!(mem.notable_events[0].tick, 1); // tick=0 dropped
assert_eq!(mem.notable_events.last().unwrap().tick, 99);
assert_eq!(mem.notable_events.back().unwrap().tick, 99);
}
#[test]