# vera-effects
**VERA — Verified Effect-Rule Architecture**
An architectural pattern and supporting types for game loops where every state mutation is inspectable, testable, and traceable. Designed for codebases developed with AI coding agents, where self-verification is the primary constraint.
## The problem
In a typical game loop, actions mutate state directly:
```rust
// Traditional: opaque mutation
fn use_item(&mut self, idx: usize) {
let def = get_item_def(&self.inventory[idx]);
self.hp += def.heal; // What changed?
self.inventory.remove(idx); // Why?
self.ap -= 1; // Was this correct?
self.refraction -= def.reduces_refraction; // How do we test this?
if def.reveals_map { self.reveal_all(); } // Can't unit test without full game state
}
```
When this breaks, you debug the entire game state. When an AI agent writes this, there's no structural gate between "compiles" and "works." The scaffold-and-abandon pattern — code that exists but isn't wired into gameplay — is invisible until a player hits it.
## The VERA pattern
VERA separates *deciding what should change* from *applying the change* from *recording what happened*:
```rust
// VERA: inspectable, testable, traceable
// 1. RULE — pure function, no mutation, returns a description of what should change
fn rule_use_item(idx: usize, ctx: &QueryContext) -> RuleOutput<Effect, Presentation> {
let def = ctx.item_def(&ctx.player.inventory[idx]);
let mut effects = vec![
Effect::Player(PlayerEffect::SpendAp { amount: 1 }),
Effect::Item(ItemEffect::Consume { item_id: def.id.clone(), index: idx }),
];
if def.heal > 0 {
effects.push(Effect::Player(PlayerEffect::Heal {
amount: def.heal.min(ctx.player.max_hp - ctx.player.hp),
}));
}
if def.reveals_map {
effects.push(Effect::Map(MapEffect::RevealAll));
}
RuleOutput { effects, presentation: vec![] }
}
// 2. APPLY — mechanical match, no logic, just field assignments
fn apply_effect(state: &mut GameState, effect: &Effect) {
match effect {
Effect::Player(PlayerEffect::Heal { amount }) => {
state.hp = (state.hp + amount).min(state.max_hp);
}
Effect::Player(PlayerEffect::SpendAp { amount }) => {
state.ap -= amount;
}
// ... exhaustive match — compiler catches unhandled variants
}
}
// 3. TRACE — records what happened for test assertions
for effect in &output.effects {
apply_effect(state, effect);
trace.record(effect, TraceSource::Rule { name: "rule_use_item" }, turn);
}
```
### What this gives you
| Test requires full game state setup | Test requires only a `QueryContext` (borrowed refs) |
| Debugging means tracing call chains | Debugging means reading the trace log |
| "Does this feature work?" → play the game | "Does this feature work?" → assert on effects |
| New effect variant compiles silently | New effect variant → compiler error if unhandled |
| AI agent writes code that compiles but isn't wired | AI agent must produce effects or tests fail |
### The three verification layers
1. **Rule unit tests** — pure input→output. No game state needed. Assert on the `Vec<Effect>` returned.
2. **Integration tests** — drive the full loop (command → rule → apply → state). Assert on final state AND on the trace.
3. **Compiler** — exhaustive `match` on Effect and Command enums. Unhandled variants are compile errors.
## What this crate provides
The generic infrastructure types. Your game defines the concrete Effect/Command enums.
```rust
use vera_effects::{Trace, TraceEntry, TraceSource, RuleOutput};
// Specialize for your game's types
type GameTrace = Trace<MyEffect>;
type GameRuleOutput = RuleOutput<MyEffect, MyPresentation>;
```
### Types
| `Trace<E>` | Ordered record of effects. Enabled during tests, zero-cost when disabled. |
| `TraceEntry<E>` | Single entry: effect + source + turn number |
| `TraceSource<E>` | Where an effect came from: `Rule { name }` or `Reaction { name, trigger }` |
| `RuleOutput<E, P>` | What a rule returns: game effects (traced) + presentation effects (not traced) |
### Trace API
```rust
let mut trace = Trace::<Effect>::default();
trace.enabled = true;
// Record effects
trace.record(&effect, TraceSource::Rule { name: "rule_heal" }, turn);
// Query
trace.contains(&Effect::Heal { amount: 25 }); // did this happen?
trace.from_rule("rule_heal"); // all effects from this rule
trace.effects_matching(|e| matches!(e, Effect::Heal { .. })); // filter by pattern
```
## The full pattern (not in this crate)
This crate provides the generic types. The full VERA pattern also includes:
- **Commands** — enum of player/system intentions (your game defines these)
- **Effects** — domain-scoped enums describing state mutations (your game defines these)
- **Rules** — pure functions: `(args, &Context, &mut RNG) → RuleOutput<E, P>`
- **Apply** — mechanical `match` on effects → field assignments
- **Reactions** — functions triggered by effects: `(effect, &Context, &mut RNG) → Vec<Effect>`
- **QueryContext** — borrowed read-only view of game state for rules
- **TestContext** — builder for constructing QueryContext in unit tests without full game state
These are domain-specific and live in your game crate, not here.
## When to use VERA
VERA is designed for:
- Turn-based games where actions produce discrete state changes
- Codebases developed with AI coding agents that need self-verification
- Projects where "it compiles" is not sufficient proof that a feature works
VERA is not designed for:
- Real-time physics simulations (continuous state, not discrete effects)
- Procedural generation pipelines (data transformation, not command→effect loops)
- Rendering (read-only, no state mutation)
## License
MIT