Architecture
The three-tier Level-of-Detail spine is the single most important architectural pattern in Cenosis. Everything else — cognition, social systems, the director, persistence — is built to feed into or out of this spine.
The LOD Simulation Spine
With 200 NPCs in a town, only ~5 can run full LLM cognition. The rest must still progress coherently. The "always-live world" decision makes LOD a correctness requirement, not a performance optimization.
Full Sim (FS) — three nested clocks
For agents within the player's proximity. A hard cap of 8 concurrent FS agents is enforced.
| Layer | Cadence | Cost | What runs |
|---|---|---|---|
| Reactive | Every world tick (10 Hz) | µs | UtilitySelector scores affordances at current location by current needs. No LLM. |
| Tactical | Every ~5s game time | ms | GoalSelector picks a short-term goal. Optionally uses a local ONNX SLM; falls back to deterministic lowest-need selection. |
| Narrative | Event-driven | seconds | Cloud LLM via npc-budget. Handles dialog, reflection, long-term planning, SS→FS wake summaries. |
Statistical Sim (SS) — deterministic, no LLM
For off-camera agents. 200 SS agents per tick on a phone is fine. Each tick runs five steps in sequence:
- Schedule lookup —
agent.schedule.current_at(now)finds the current(activity, location). - Need decay — per-need hourly rates (Hunger 0.04/h, Energy 0.06/h awake, Social 0.03/h, Fun 0.02/h), clamped to [0, 1].
- Schedule execution — if the scheduled location differs from current, update location and record
SsEvent::StartedActivity. - Opportunistic affordance use — if any need falls below 0.30, scan affordances at current location, apply the winning affordance's need + mood deltas, record
SsEvent::UsedAffordance. - Social encounter sampling — for every pair of co-located agents, roll 5% per tick (ChaCha8Rng seeded from config). On success, update
RelationshipGraph(+0.05 Trust, Affection bidirectional) and recordSsEvent::SocialEncounter.
No LLM in SS — ever
This is non-negotiable. It is what makes off-camera history deterministic across replay. The SS event log is the audit trail — concrete events recorded, never derived on read.
Macro Sim (v2.5) — city scale
For 1,000+ agent cities. Aggregates agent → household → district → settlement. Has its own deterministic aggregate progression, hysteresis with SS, NPCB v16+ scale persistence, and load shedding. Tested with 10,080-agent city_scale_budget.
Tier Transitions
Hysteresis: Enter FS at 20m, exit at 30m by default. Without this, agents at the boundary thrash and each transition costs a cloud call.
Wake (SS → FS): When the host flags proximity, the engine promotes the agent, compiles its accumulated SsEvent log into a deterministic wake summary, and optionally queues an LLM render of that summary (the LLM renders the events — it never invents new ones).
Sleep (FS → SS): On proximity exit, reset ss_since, drop the event log, demote to AgentTier::StatSim.
Write-Ahead Log & Persistence
All state changes flow through a WAL before touching any projection:
Host game ──record_event()──▶ npc-ffi ──crossbeam──▶ WAL writer thread
│
npc_memory.wal (group commit)
│
SQLite projection store
(HNSW + relations + beliefs)
The WAL writer uses group commit — fsync every 32 events or 50ms. Key properties this gives you:
- Crash recovery — unprojected tails recover to tick barriers; partial ticks are detected and discarded.
- Deterministic replay — same WAL prefix + LLM cache → identical state.
- Repro bundles — single-file
.reprocaptures for bug reproduction and divergence localization.
Rule
WAL writes precede projection mutations. MemoryStore::append_event is the durability boundary. Projections are always derived — never the source of truth.
Cognition Pipeline
When a Full Sim agent needs narrative output (dialog, reflection, wake summary), the pipeline runs in this order:
- PromptBuilder — assembles a token-budgeted prompt. Trim order: memories → dialog → world context. Sections include persona, relationships, beliefs, topic memory, location, time, nearby agents, recent events, and the task block.
- BudgetGovernor — checks per-agent / session / hour caps, then checks
hash(prompt) → responsecache. Cache hit = zero spend. - CloudDispatcher — drains the priority queue (
PlayerDialog > NarrativeBeat > Reflection > Background), enforces provider-health backoff and hard timeouts. - ConstraintEngine — validates LLM output against persona
hard_constraints(forbidden topics/actions) before acceptance. - HostAction queue — completed responses flow back as
HostActionvariants the game engine drains via peek-then-pop.
Circuit Breaker
Repeated provider failures latch the session. All FS agents are demoted to SS and new promotions are refused. The world keeps running without cloud — just less narratively rich. The latch is surfaced through npc_engine_peek_metrics.
Social Systems
Relationships
RelationshipGraph is a directed multigraph with 6 asymmetric dimensions: Trust, Affection, Respect, Fear, Loyalty, Familiarity. Features:
- Data-extensible
RelationshipKindandRelationshipIntent(registry-driven, not enum) - Bounded importance-ranked history with source deduplication
- Opt-in baseline-setpoint decay
- Per-settlement standing aggregated from concrete evidence (no gossip-loop amplification)
Beliefs & Theory of Mind
BeliefStore holds per-agent theory-of-mind: what agent X believes about Y, and why (concrete BeliefSource events). Includes contradiction detection, provenance chains, and the PlausibleBeliefUpdate constraint — the LLM cannot reveal information the agent does not possess.
Gossip & Reputation
A first-class Tier-2 extension (npc-extend::rumor). Rumors spread between co-located SS agents via deterministic seeded sampling, with reputation effects and crowd scenes (>4 agents). NPCB v17 persists the rumor pool + reputation state.
Conversations
Stateful multi-agent scenes with intents, topics, emotional dialogue acts, commitments, and WAL-first consequences. Closed conversations atomically update relationships, beliefs, and commitments. SceneManager is the lifecycle authority.
Director & Storylets
StoryletEngine evaluates designer-authored triggers against world + social + agent context every tick. Storylets are data, not code — they define conditions and concrete effects:
- Mood changes, relationship deltas, memory injection, dialog seeds, world events
- Tension pacing and multi-beat storyline instances
- Faction-biased social drama
- Life-event pulses (birth, death, bereavement, inheritance)
- Economic event pulses (price spikes, bankruptcy)
Packs remain data
Triggers and affordances are pack-authored JSON, never Rust code. The v0.4 security boundary does not move — packs cannot execute arbitrary code.
Determinism & Replay
Cenosis is built around a determinism-first philosophy:
- SS agents are fully deterministic: same seed + schedule + RNG → identical event logs, needs, relationships.
- FS agents are deterministic modulo cloud responses, which the LLM cache provides.
- Parallel tick uses sorted-id processing order so parallel and single-threaded runs produce identical results.
- Determinism tests —
criterion_7ticks two independent towns with the same seed and bit-compares snapshots every 60 ticks. Any drift is a determinism bug.