# VERA Architecture
> Verified Effect-Rule Architecture
## Overview
VERA is an architectural pattern for game loops where every state mutation is inspectable and testable. It was designed for a specific constraint: codebases developed by AI coding agents that must self-verify their work.
The core insight: if game logic is split into "decide what should change" (pure functions) and "apply the change" (mechanical match), then the decision can be tested without the game, and the application can be verified by the compiler.
## The pipeline
```
Input → Command → Rule(context, rng) → Vec<Effect>
↓
apply(state, effect) ← mechanical, no logic
↓
trace.record(effect) ← test infrastructure
↓
reactions(effect, ctx) ← cascading effects
↓
derives() ← FOV, lighting, spatial index
```
## Components
### Command
An enum representing player intentions or system triggers.
```rust
enum Command {
Move { dx: i32, dy: i32 },
UseItem { index: usize },
Attack { target_x: i32, target_y: i32 },
EndTurn,
}
```
Commands are the entry point into VERA. UI code translates keypresses into Commands. System ticks produce Commands. Nothing else enters the pipeline.
### Rule
A pure function that reads state and returns effects. Rules never mutate state.
```rust
fn rule_attack(
target: (i32, i32),
ctx: &QueryContext,
rng: &mut ChaCha8Rng,
) -> RuleOutput<Effect, Presentation> {
let hit = roll_attack(ctx, rng);
let mut effects = Vec::new();
if hit {
let damage = calc_damage(ctx, rng);
effects.push(Effect::Combat(CombatEffect::DealDamage {
target_idx: ctx.enemy_idx_at(target.0, target.1).unwrap(),
amount: damage,
}));
} else {
effects.push(Effect::Combat(CombatEffect::Miss { ... }));
}
RuleOutput { effects, presentation: vec![] }
}
```
Rules take a `QueryContext` (borrowed read-only view of state) and optionally `&mut RNG`. They return `RuleOutput<E, P>` — game effects (traced) and presentation effects (not traced).
The key property: rules are testable without game state. Construct a `QueryContext` from a test builder, call the rule, assert on the returned effects.
### Effect
A domain-scoped enum describing a single atomic state mutation.
```rust
enum Effect {
Player(PlayerEffect),
Combat(CombatEffect),
Item(ItemEffect),
Map(MapEffect),
}
enum PlayerEffect {
Heal { amount: i32 },
SpendAp { amount: i32 },
SetPosition { x: i32, y: i32 },
}
enum CombatEffect {
DealDamage { target_idx: usize, amount: i32 },
Miss { target_idx: usize },
Kill { target_idx: usize, enemy_id: String, x: i32, y: i32 },
}
```
Effects are data — they describe what should change, not how. The `#[derive(PartialEq)]` enables direct assertion in tests: `assert!(effects.contains(&Effect::Player(PlayerEffect::Heal { amount: 25 })))`.
Domain scoping (PlayerEffect, CombatEffect, etc.) keeps the enum organized and gives exhaustive match per domain.
### Apply
A mechanical `match` that translates effects into state mutations. No logic, no conditionals, no function calls — just field assignments.
```rust
fn apply_effect(state: &mut GameState, effect: &Effect) {
match effect {
Effect::Player(PlayerEffect::Heal { amount }) => {
state.player.hp = (state.player.hp + amount).min(state.player.max_hp);
}
Effect::Player(PlayerEffect::SpendAp { amount }) => {
state.player.ap -= amount;
}
// Every variant handled — compiler enforces this
}
}
```
The apply function is the only place state mutates. If a new Effect variant is added, the compiler forces you to handle it here.
### Trace
An ordered record of all effects applied during a game action. Enabled during tests, zero-cost when disabled.
```rust
// During test execution
trace.enabled = true;
// After applying effects
trace.record(&effect, TraceSource::Rule { name: "rule_attack" }, turn);
// In test assertions
assert!(trace.contains(&Effect::Combat(CombatEffect::DealDamage { ... })));
assert_eq!(trace.from_rule("rule_attack").len(), 2);
```
When a test fails, the trace shows exactly what happened — which rule produced which effects, in what order. No call-chain debugging.
### Reaction
A function triggered by a specific effect, producing more effects. Enables cascading behavior.
```rust
// Kill triggers loot drop and quest progress
fn collect_reactions(effect: &Effect) -> Vec<Effect> {
match effect {
Effect::Combat(CombatEffect::Kill { enemy_id, x, y, .. }) => {
vec![
Effect::Item(ItemEffect::SpawnLoot { x, y, table: enemy_id }),
Effect::Quest(QuestEffect::Progress { kind: "kill", target: enemy_id }),
]
}
_ => vec![],
}
}
```
Reactions run after primary effects are applied. Cascade depth is limited (typically 10) to prevent infinite loops.
### Presentation
Effects that produce visual feedback but don't change game state. Not traced, not tested against.
```rust
enum Presentation {
LogMessage { text: String, msg_type: String },
DamageNumber { x: i32, y: i32, amount: i32 },
ScreenShake { intensity: f32 },
}
```
Separating presentation from game effects means rules can produce log messages and visual feedback without polluting the trace.
### QueryContext
A borrowed read-only view of game state, constructed once per command dispatch.
```rust
struct QueryContext<'a> {
player: &'a PlayerState,
map: &'a Map,
enemies: &'a [Enemy],
// ... only what rules need to read
}
```
QueryContext solves the borrow checker ergonomics: rules take `&QueryContext` (shared borrow), dispatch holds `&mut self.rng` separately. No `&self` / `&mut self` conflict.
### TestContext
A builder for constructing QueryContext in unit tests without GameState.
```rust
let ctx = TestContext::new()
.with_player_hp(50)
.with_player_ap(10)
.with_inventory(vec!["healing_salve".into()])
.with_enemy_at(5, 5, 0)
.build();
let output = rule_use_item(0, &ctx);
assert!(output.effects.contains(&Effect::Player(PlayerEffect::Heal { amount: 25 })));
```
## Comparison with other patterns
| State mutation | Direct (`self.hp += heal`) | System queries + mutation | Effect enum → mechanical apply |
| Testability | Requires full game state | Requires world + components | Requires only QueryContext |
| Verifiability | None (opaque) | Component queries | Trace + exhaustive match |
| AI agent friendliness | Low (must trace call chains) | Medium (system scheduling is implicit) | High (pure functions, enum match) |
| Rust idiom fit | Methods on structs | Trait objects, dynamic dispatch | Enums, match, free functions |
## Design principles
1. **Rust-idiomatic.** Enums, match, free functions, borrowed structs. No proc macros, no trait objects for core dispatch. An LLM trained on Rust code will naturally produce VERA-compatible code.
2. **Compiler as verifier.** Exhaustive match on Effect and Command enums means unhandled variants are compile errors. The scaffold-and-abandon pattern — adding code that compiles but isn't wired — becomes structurally visible.
3. **Traces are test infrastructure.** Zero cost when disabled. Only materialized during test runs. Not saved, not serialized.
4. **Incremental adoption.** Legacy methods coexist with VERA dispatch during migration. No flag day required.