Cardinal Engine
Infrastructure for persistent, deterministic simulations. Not a game engine in the graphical sense.
Same input, same output. Always. Variance comes from behavioral state accumulation, not randomness at resolution.
All state in SQLite. Sessions end and resume. Long-running simulations page to disk rather than bloating memory.
Every game-critical calculation runs on Node.js backend. Client receives results only. Nothing consequential in browser.
Combat AI uses scored heuristic functions, not learned models. Predictable, debuggable, tunable without retraining.
LLM module isolated. It receives sanitized event payloads and returns text. No simulation state writable from it.
Combat, economy, progression, and narrative are discrete modules. Cardinal is not Crownless-specific infrastructure.
Four layers. Defined interfaces between them. Separation enforced at the module boundary.
Receives Arbiter actions, validates input, routes to game logic. Returns display state to client. Stateless.
Simulation loop, combat resolution, behavioral state management, progression. No I/O.
Called post-simulation only. Input: sanitized event data. Output: text string. One-way pipe.
All game state. Synchronous reads. Writes batched at simulation boundaries, not per-step.
The Boundary Rule
The AI services module receives a sanitized event payload after simulation completes. It returns a string. That string becomes a narrative record. The LLM has no access to live simulation state or Stray behavioral values.
Enforcement is architectural. The AI module does not import from game logic. The module boundary forbids it. Events in, text out, nothing writes back.
strays (id, arbiter_id, name, role, stats_json, behavioral_state_json, status) bastions (id, arbiter_id, facilities_json, resource_reserves, capacity) fractures (id, type, depth, threat_composition_json, modifiers_json, status) missions (id, fracture_id, stray_ids_json, outcome_json, log_json, completed_at) narrative_log (id, mission_id, stray_id, event_type, content, generated_at) arbiters (id, name, authority_rank, progression_json, created_at)