Skip to main content

vera_effects/
lib.rs

1//! # VERA — Verified Effect-Rule Architecture
2//!
3//! Generic infrastructure for effect-verified game loops.
4//!
5//! VERA's core idea: pure rule functions return effect enums, a mechanical
6//! apply function mutates state, and a trace records everything that happened
7//! for verification.
8//!
9//! This crate provides the generic types. Your game crate defines the concrete
10//! `Effect` and `Presentation` enums and uses these types parameterized over them.
11//!
12//! # Example
13//!
14//! ```rust
15//! use vera_effects::{Trace, TraceEntry, TraceSource, RuleOutput};
16//!
17//! // Your game defines concrete effect types
18//! #[derive(Debug, Clone, PartialEq)]
19//! enum Effect {
20//!     Heal { amount: i32 },
21//!     SpendAp { amount: i32 },
22//! }
23//!
24//! #[derive(Debug, Clone)]
25//! enum Presentation {
26//!     LogMessage(String),
27//! }
28//!
29//! // Rule functions return RuleOutput<Effect, Presentation>
30//! fn rule_heal(amount: i32) -> RuleOutput<Effect, Presentation> {
31//!     RuleOutput {
32//!         effects: vec![Effect::Heal { amount }],
33//!         presentation: vec![Presentation::LogMessage(format!("+{} HP", amount))],
34//!     }
35//! }
36//!
37//! // Trace records what happened
38//! let mut trace = Trace::<Effect>::default();
39//! trace.enabled = true;
40//! let output = rule_heal(25);
41//! for effect in &output.effects {
42//!     trace.record(effect, TraceSource::Rule { name: "rule_heal" }, 1);
43//! }
44//! assert!(trace.contains(&Effect::Heal { amount: 25 }));
45//! ```
46
47use std::fmt;
48
49// ---------------------------------------------------------------------------
50// Trace — records effects for verification
51// ---------------------------------------------------------------------------
52
53/// A single entry in the trace log.
54#[derive(Debug, Clone)]
55pub struct TraceEntry<E: Clone> {
56    pub turn: u32,
57    pub source: TraceSource<E>,
58    pub effect: E,
59}
60
61/// Where an effect came from.
62#[derive(Debug, Clone)]
63pub enum TraceSource<E: Clone> {
64    /// Produced by a rule function.
65    Rule { name: &'static str },
66    /// Produced by a reaction to another effect.
67    Reaction { name: &'static str, trigger: Box<E> },
68}
69
70/// Ordered record of all effects. Only populated when `enabled` is true.
71/// Ephemeral — not serialized, not saved.
72#[derive(Clone)]
73pub struct Trace<E: Clone + PartialEq> {
74    pub entries: Vec<TraceEntry<E>>,
75    pub enabled: bool,
76}
77
78impl<E: Clone + PartialEq> Default for Trace<E> {
79    fn default() -> Self {
80        Self {
81            entries: Vec::new(),
82            enabled: false,
83        }
84    }
85}
86
87impl<E: Clone + PartialEq> Trace<E> {
88    /// Record an effect if tracing is enabled.
89    pub fn record(&mut self, effect: &E, source: TraceSource<E>, turn: u32) {
90        if self.enabled {
91            self.entries.push(TraceEntry {
92                turn,
93                source,
94                effect: effect.clone(),
95            });
96        }
97    }
98
99    /// Check if a specific effect was recorded.
100    pub fn contains(&self, effect: &E) -> bool {
101        self.entries.iter().any(|e| &e.effect == effect)
102    }
103
104    /// All effects from a specific rule.
105    pub fn from_rule(&self, name: &str) -> Vec<&E> {
106        self.entries
107            .iter()
108            .filter(|e| matches!(&e.source, TraceSource::Rule { name: n } if *n == name))
109            .map(|e| &e.effect)
110            .collect()
111    }
112
113    /// All effects matching a predicate.
114    pub fn effects_matching<F>(&self, predicate: F) -> Vec<&E>
115    where
116        F: Fn(&E) -> bool,
117    {
118        self.entries
119            .iter()
120            .filter(|e| predicate(&e.effect))
121            .map(|e| &e.effect)
122            .collect()
123    }
124}
125
126impl<E: Clone + PartialEq + fmt::Debug> fmt::Debug for Trace<E> {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        f.debug_struct("Trace")
129            .field("enabled", &self.enabled)
130            .field("entries", &self.entries.len())
131            .finish()
132    }
133}
134
135impl<E: Clone + PartialEq + fmt::Debug> fmt::Display for Trace<E> {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        for entry in &self.entries {
138            writeln!(
139                f,
140                "  Turn {}: [{:?}] {:?}",
141                entry.turn, entry.source, entry.effect
142            )?;
143        }
144        Ok(())
145    }
146}
147
148// ---------------------------------------------------------------------------
149// RuleOutput — what a rule function returns
150// ---------------------------------------------------------------------------
151
152/// The output of a rule function: game effects to apply + presentation effects.
153#[derive(Debug, Clone)]
154pub struct RuleOutput<E, P> {
155    pub effects: Vec<E>,
156    pub presentation: Vec<P>,
157}
158
159impl<E, P> Default for RuleOutput<E, P> {
160    fn default() -> Self {
161        Self {
162            effects: Vec::new(),
163            presentation: Vec::new(),
164        }
165    }
166}