Skip to main content

proof_engine/narrative/
memory.rs

1//! NPC memory system — NPCs remember player actions and reference them.
2
3use std::collections::VecDeque;
4
5#[derive(Debug, Clone)]
6pub struct Memory {
7    pub event: String,
8    pub sentiment: f32,
9    pub timestamp: f64,
10    pub importance: f32,
11    pub fading: bool,
12}
13
14/// An NPC's memory bank.
15#[derive(Debug, Clone)]
16pub struct NpcMemory {
17    pub memories: VecDeque<Memory>,
18    pub max_memories: usize,
19    pub forget_threshold: f32,
20}
21
22impl NpcMemory {
23    pub fn new(capacity: usize) -> Self {
24        Self { memories: VecDeque::new(), max_memories: capacity, forget_threshold: 0.1 }
25    }
26
27    pub fn remember(&mut self, event: &str, sentiment: f32, importance: f32, time: f64) {
28        self.memories.push_back(Memory {
29            event: event.to_string(), sentiment, timestamp: time, importance, fading: false,
30        });
31        while self.memories.len() > self.max_memories {
32            self.memories.pop_front();
33        }
34    }
35
36    /// Decay old memories.
37    pub fn tick(&mut self, current_time: f64) {
38        for m in &mut self.memories {
39            let age = current_time - m.timestamp;
40            if age > 100.0 && m.importance < 0.5 {
41                m.fading = true;
42            }
43        }
44        self.memories.retain(|m| !m.fading || m.importance > self.forget_threshold);
45    }
46
47    /// Recall memories matching a keyword.
48    pub fn recall(&self, keyword: &str) -> Vec<&Memory> {
49        self.memories.iter().filter(|m| m.event.contains(keyword)).collect()
50    }
51
52    /// Most impactful memory.
53    pub fn strongest_memory(&self) -> Option<&Memory> {
54        self.memories.iter().max_by(|a, b| a.importance.partial_cmp(&b.importance).unwrap())
55    }
56
57    /// Overall sentiment toward the player.
58    pub fn overall_sentiment(&self) -> f32 {
59        if self.memories.is_empty() { return 0.0; }
60        let sum: f32 = self.memories.iter().map(|m| m.sentiment * m.importance).sum();
61        let weight: f32 = self.memories.iter().map(|m| m.importance).sum();
62        if weight > 0.0 { sum / weight } else { 0.0 }
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn test_memory() {
72        let mut mem = NpcMemory::new(10);
73        mem.remember("helped with crops", 0.8, 0.5, 1.0);
74        mem.remember("stole an apple", -0.5, 0.3, 2.0);
75        assert_eq!(mem.memories.len(), 2);
76        assert!(mem.overall_sentiment() > 0.0);
77    }
78
79    #[test]
80    fn test_recall() {
81        let mut mem = NpcMemory::new(10);
82        mem.remember("battle at the bridge", 0.3, 0.7, 1.0);
83        mem.remember("quiet evening", 0.1, 0.2, 2.0);
84        let battles = mem.recall("battle");
85        assert_eq!(battles.len(), 1);
86    }
87}