Tapestria Logo
Docs · Architecture

Architecture

How Tapestria is built — three stores, an immutable event log, a deterministic rules engine, and a three-stage LLM pipeline. The system view, not the player view.

This section is the system-level view of Tapestria. If the Mechanics docs are about what playing feels like, this is about what's under the floor. It's also the part of the codebase I'd most want a senior engineer to push back on.

The shape of the system

Three stores, each authoritative for a different concern:

  • A connected world model — the source of truth for what exists in Aethernia. Locations, NPCs, factions, items, quests, characters, and their typed relationships.
  • An immutable event log — the source of truth for what happened in Aethernia, in chronological order. Every world-state change is appended here first, then applied to the world model. Append-only, replayable.
  • A short-lived runtime store — for in-progress turn state (combat HP, initiative, turn locks, condition durations). Cheap to lose; the durable stores hold everything that needs to outlast a session.

A backend with persistent WebSocket sessions sits in front of all three. A provider-agnostic LLM layer routes to whichever model the call type is configured for. Two clients consume the backend: a mobile-first player client that ships to web, iOS, and Android, and an admin panel for content authoring.

Why this shape

Why a graph for world state

The world has lots of typed relationships between entities, and the queries we want are mostly relational — "what NPCs are in this location's parent?", "which factions does this NPC belong to and what's its disposition toward the player's party?", "what events happened in this location's awareness subtree?", "what items did this NPC give to whom?".

In a relational DB these are joins on join tables; doable but tedious and slow to iterate on. In a document store they're application-level traversals. In a graph DB they're one Cypher query and the engine knows how to walk the edges.

There are also things we get for cheap by being graph-native: ancestral location context (POIs inside ward inside city), faction hierarchies, NPC knowledge propagation through social ties. We'd be reimplementing graph traversal on top of SQL if we'd gone the other way.

The trade is operational maturity — graph stores are less battle-tested than relational stores and harder to scale horizontally. We treat that as a known cost we accept for the iteration speed.

Why an immutable event log

Two reasons.

Replayability. Every world-state change is an entry in the event log. If the graph ever falls out of sync — a partial write, a corrupted state — we can rebuild the graph by replaying events from the log. Without that, a graph corruption is unrecoverable.

Cross-store atomicity. The graph and the event log are two stores. A change has to land in both, atomically. The pipeline writes durably to the event log first, then applies the mutation to the graph, then marks the entry applied. If the graph apply fails, the entry stays unapplied and a sweeper retries. Players never see partial state.

It also means the entire history of Aethernia is a chronological list of events anyone can read. That turns out to be load-bearing for the narrator (which gets event-shaped context) and for the LLM-driven NPC knowledge model (which is "what events did this NPC witness").

Why a separate runtime store for session state

The runtime state of an in-progress turn — combat HP, initiative, the lock that serializes concurrent inputs on the same session — changes too fast for the durable stores and is allowed to be lost. An in-memory store is the right tool. The session lock is owner-checked on release, so a stale process can't clobber a live one.

Why deterministic rules + LLM narrative

This is the architectural claim that gets the most attention from technical readers, so it deserves a careful version.

The mainstream pattern for "AI RPG" is "ask the LLM what happens." The failure modes of that pattern are well-known: hallucinated stats, fudged dice rolls, inconsistent enforcement of rules, forgotten consequences, narrative drift. AI Dungeon ships those failures because that pattern is what AI Dungeon is.

Tapestria splits the responsibility. Mechanics are someone else's problem — the D&D 5e SRD's. The rules engine implements the SRD in pure deterministic code: dice, modifiers, skill checks, combat resolution, conditions, effects, spells, leveling. The rules engine has no access to any LLM and cannot call one; this is enforced architecturally, not as a convention. A pull request that tried to break the boundary would fail at build time.

The LLM is restricted to two jobs: turning free text into a structured intent (the parser), and turning the engine's result into prose (the narrator). Neither job involves deciding mechanical outcomes. Both run against strictly typed outputs, so the model can't even format a response that breaks the contract.

The LLM Pipeline page walks through the full sequence in detail, including the mutation validation layer that catches the narrator when it tries to invent world changes.

Architectural seams

The backend is organised into separate domains with hard boundaries between them:

  • A pure-rules engine — dice, combat, conditions, effects, equipment, skills, spells. No I/O, no LLM access.
  • An event pipeline — validates events, writes durably, applies mutations to the graph.
  • A world layer — graph reads and writes consumed by the event handlers.
  • A session layer — per-session lifecycle: turn orchestration, combat coordination, encounters, narrative state.
  • An LLM-facing layer — context assembly, prompt rendering, call routing, mutation validation. The only part of the backend that talks to a model.
  • A content-authoring surface — used by the admin tools only.
  • A seed layer — SRD ingestion and world content seeding.

Crossing these boundaries is the bug class our regression tests exist to prevent.

Scope

A few things worth being explicit about:

  • One party per session. Multiple parties of multiple players adventuring in the same world is the design; multiple parties meeting in the same scene is not. The shared world is the canon; each session is a single party's perspective on it.
  • D&D 5e SRD as the rule system. No homebrew classes, no custom races. Sticking to the SRD is what makes "the AI can't change the rules" verifiable instead of aspirational. Other rule systems are not part of Tapestria.
  • English-language launch. Translation is a real undertaking with localization implications across every prompt, every piece of seeded lore, and every UI string. Out of scope for the initial release.

Read on

  • Gameplay as Graph Mutation — the unifying design lens: world is a graph, gameplay mutates the graph, quests are durable commits and skills/spells are atomic operators. The conceptual frame the rest of these docs hang from.
  • LLM Pipeline — the parse → engine → narrate → aftermath pipeline and the verifiable mechanisms that keep the LLM from hallucinating the rules.
  • Mechanics — the player-facing version of all this.
  • Shared World — what one canonical Aethernia looks like in practice.

A Living World Awaits

Join the Waitlist