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}