Skip to main content

proof_engine/narrative/
consequences.rs

1//! Consequence tracking — player actions create ripples through the narrative.
2
3use std::collections::HashMap;
4
5#[derive(Debug, Clone)]
6pub struct Consequence {
7    pub action: String,
8    pub effects: Vec<Effect>,
9    pub timestamp: f64,
10    pub resolved: bool,
11}
12
13#[derive(Debug, Clone)]
14pub struct Effect {
15    pub target: String,
16    pub change_type: ChangeType,
17    pub magnitude: f32,
18    pub delayed_until: Option<f64>,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum ChangeType {
23    Reputation, Relationship, WorldState, QuestState, DialogueUnlock, AreaAccess, PriceChange,
24}
25
26/// Tracks all consequences of player actions.
27#[derive(Debug, Clone)]
28pub struct ConsequenceTracker {
29    pub consequences: Vec<Consequence>,
30    pub reputation: HashMap<String, f32>,
31    pub world_flags: HashMap<String, bool>,
32}
33
34impl ConsequenceTracker {
35    pub fn new() -> Self {
36        Self { consequences: Vec::new(), reputation: HashMap::new(), world_flags: HashMap::new() }
37    }
38
39    pub fn record(&mut self, action: &str, effects: Vec<Effect>, time: f64) {
40        for effect in &effects {
41            match effect.change_type {
42                ChangeType::Reputation => {
43                    *self.reputation.entry(effect.target.clone()).or_insert(0.0) += effect.magnitude;
44                }
45                ChangeType::WorldState => {
46                    self.world_flags.insert(effect.target.clone(), effect.magnitude > 0.0);
47                }
48                _ => {}
49            }
50        }
51        self.consequences.push(Consequence { action: action.to_string(), effects, timestamp: time, resolved: false });
52    }
53
54    pub fn reputation_with(&self, faction: &str) -> f32 {
55        self.reputation.get(faction).copied().unwrap_or(0.0)
56    }
57
58    pub fn flag_set(&self, flag: &str) -> bool {
59        self.world_flags.get(flag).copied().unwrap_or(false)
60    }
61
62    /// Get pending delayed consequences at the given time.
63    pub fn pending_at(&self, time: f64) -> Vec<&Consequence> {
64        self.consequences.iter().filter(|c| !c.resolved && c.effects.iter().any(|e| e.delayed_until.map_or(false, |t| t <= time))).collect()
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_consequence_tracking() {
74        let mut tracker = ConsequenceTracker::new();
75        tracker.record("helped_farmer", vec![
76            Effect { target: "village".into(), change_type: ChangeType::Reputation, magnitude: 10.0, delayed_until: None },
77        ], 0.0);
78        assert_eq!(tracker.reputation_with("village"), 10.0);
79    }
80}