Conceptual Foundations

Why the system works the way it does, and the metaphors that keep it honest. Package-level runtime architecture now lives in engine/src/tangl/story/STORY_DESIGN.md; this page remains the higher-level conceptual rationale.

StoryTangl models interactive narrative as a graph of interdependent possibilities that collapses into a specific story through traversal. This document explains the conceptual vocabulary behind that sentence — where it comes from, why it matters, and how each idea maps to real machinery in the engine.

Every metaphor here earns its place by corresponding to an implemented (or concretely planned) mechanism. If a concept doesn’t connect to code, it belongs in the research agenda, not here.


The Central Thesis

A story is a structured collapse of possibility into experience.

An author defines a space of potential narratives — characters who could meet, events that could transpire, objects that could matter. A reader (or player, or algorithm) navigates that space, and the act of navigation progressively resolves ambiguity into commitment. What was once “the villain could be anyone” becomes “the villain is Marta.” What was once “you could go east” becomes “you went east, and now the west road is buried.”

The engine’s job is to make this collapse auditable (every decision is logged), deterministic (the same choices reproduce the same story), and extensible (new kinds of narrative content and mechanics plug in without changing the core loop).

Why “Untangling”

The original backronym — the Abstract Narrative Graph Library — described the data structure. The working metaphor is better: the story space starts tangled, a web of interdependent requirements, roles, and consequences, and the engine’s job is to untangle it into a single coherent thread.

This is not merely poetic. The resolver literally walks the dependency graph, finds nodes that satisfy open requirements, binds them, and advances the frontier. Each step reduces the degrees of freedom in the remaining graph. The journal that emerges is the untangled thread — a linear narrative extracted from a combinatorial space.


Three Models, Three Layers

StoryTangl separates what could happen from what does happen from what the reader sees. This separation is the engine’s most important architectural commitment.

Fabula — the Possibility Space

The fabula is the complete graph of events, characters, places, and their relationships — everything that could be narrated. It is the “tangled” state: a castle exists, a dragon exists, a knight exists, and there are dependency edges encoding that the dragon guards the treasure and the knight needs a sword.

In the engine, authored fabula first lives as a compiled StoryTemplateBundle: a set of structural templates (scenes, blocks, actions), concept templates (actors, locations, assets), and dependency declarations capturing what could be instantiated. Materialization then turns that compiled possibility space into one navigable runtime StoryGraph.

The fabula contains more stories than any single traversal will realize, just as a chessboard contains more games than any single match will play.

Implementation: StoryCompilerStoryTemplateBundle; StoryMaterializerStoryGraph.

Episodic Process — the Traversal

The episodic process is what happens when a cursor moves through the fabula. At each step, the engine validates the proposed move, provisions any unresolved dependencies, applies state updates, and emits journal fragments. This is the resolution pipeline — the phase bus that drives the story forward.

The episodic process is where possibility collapses into commitment. Before a block is visited, its role slots might be open (“this scene needs a villain”). During provisioning, the resolver binds a specific actor to that role. After the block is visited, that binding is frozen — the villain is Marta, permanently, for this story instance.

Implementation: Frame.follow_edge() → the eight-phase pipeline (VALIDATE → PLANNING → PREREQS → UPDATE → JOURNAL → FINALIZE → POSTREQS → advance frontier).

Syuzhet — the Realized Narrative

The syuzhet is the journal — the linear sequence of content fragments that a reader actually experiences. It is the “untangled” thread: prose paragraphs, character descriptions, choice prompts, media references, emitted in the order the traversal produced them.

The syuzhet is presentation-agnostic. The same journal can be rendered as terminal text, a rich web page, or an ebook. Fragment types carry semantic roles (narrative content, dialog, choices, media) but not rendering instructions. The service layer translates fragments into a specific presentation format based on client capabilities.

Implementation: Journal / OrderedRegistry of BaseFragment subclasses (ContentFragment, ChoiceFragment, MediaFragment); service-layer render profiles transform fragments for specific clients.

Why This Separation Matters

The same fabula can produce radically different syuzhets depending on:

  • Player choices — different traversal paths through the structural graph

  • Provisioning outcomes — different actors cast into the same roles

  • Discourse rules — different ordering, focalization, or pacing applied to the same underlying events (planned, not yet implemented)

This is not a theoretical nicety. It means you can test structural properties (reachability, completability, role satisfaction) against the fabula before any traversal happens, and you can replay a specific syuzhet deterministically from a snapshot plus a choice log.


The Compiler Metaphor

The fabula/episodic/syuzhet separation maps directly to a compiler pipeline:

Front-end:  Scripts (YAML, Twee, Ink...)
               ↓ parse + compile
IR:         StoryTemplateBundle / compiled template graph (the fabula)
               ↓ traverse + resolve
Back-end:   Journal Fragments (the syuzhet)
               ↓ render
Output:     CLI / Web / PDF / ...

Like LLVM’s intermediate representation, the story graph is a universal substrate that captures narrative intent while abstracting away both the source format and the target medium.

This is why transpilers between interactive fiction formats are architecturally natural: a Twine file and a StoryTangl YAML script are different front-ends compiling to the same IR. The graph doesn’t care where it came from.

And like a compiler, the engine’s phases are deterministic, ordered passes over the IR. Each phase has a defined contract (what it reads, what it may mutate, what it returns). The pipeline is auditable because every phase produces receipts.

Implementation: StoryCompiler.compile() (front-end) → StoryTemplateBundle (IR) → StoryMaterializer.create_story() + Frame.follow_edge() phases (back-end passes) → Journal (output).


Narrative Debt and Credit

When the episodic process recruits a concept — say, casting an actor into a role — but the journal hasn’t yet introduced that concept to the reader, the story carries narrative debt. The reader doesn’t know who Marta is yet, even though the engine has already committed to her as the villain.

Conversely, when the journal introduces a concept before it becomes structurally necessary, the story holds narrative credit. Mentioning the gun on the wall in Act I creates credit that Act III’s crisis may draw down. (Chekhov knew this, but didn’t have a dependency graph.)

This maps directly to the provisioning system:

  • Narrative debt = an unsatisfied dependency edge (the role slot is open, or is filled but the corresponding concept hasn’t been journaled)

  • Narrative credit = a concept published to the namespace before any structural node requires it (foreshadowing, world-building)

  • Debt resolution = the journal handler that emits an introduction fragment when a newly-bound concept first appears in rendered output

  • Bankruptcy = a structural dead end where required concepts cannot be provisioned (a softlock, which formal verification should catch)

A well-structured story balances its narrative books: debts are incurred strategically (mysteries, in medias res openings) and resolved before they become confusing. The engine’s dependency system makes this balance mechanically visible — you can query the graph for outstanding debts at any point in the traversal.


Superposition of Possible Stories

Forward-looking: this is conceptual design for a future constraint authority over the fact ledger, not a fully implemented subsystem today.

StoryTangl can also be understood as maintaining a superposition of possible stories. A linear narrative fixes beginning, middle, and end before the reader begins. A traditional branching story pre-considers many deterministic paths. A more dynamic story can instead begin with a field of compatible latent truths: possible motives, identities, relationships, role assignments, symbolic meanings, future beats, and story arcs.

Traversal collapses that field gradually. Each journaled disclosure removes some possibilities and strengthens others. What has been disclosed to the reader becomes hard to revise; what has only been assumed by planners, handlers, or hidden state can remain soft. If an unexpected choice or event occurs, the engine may retcon any still-unobserved soft truth so the new beat fits the evidence already fixed in the syuzhet.

This suggests a useful distinction:

  • Observed truth: fixed because it has appeared in the journal or has been explicitly committed by the runtime ledger.

  • Committed hidden truth: fixed internally, but not yet disclosed.

  • Soft truth: provisional assumptions used by planners or authorities, but still revisable if the story can re-solve coherently.

  • Potential truth: candidate explanations, identities, motives, roles, and payoffs still available in the design space.

  • Contradicted truth: no longer available unless reframed as dream, lie, rumor, hallucination, mistaken identity, fiction-within-fiction, or another explicit interpretive move.

Under this model, retcon is not cheating. It is a controlled narrative operation over soft truth, constrained by the observed ledger. The design goal is not to make every possible hidden fact mutable forever. It is to know which facts have become accountable to the reader and which facts remain negotiable.

Latent Hooks and Foreshadowing

Foreshadowing is retrospective. A disclosed element becomes foreshadowing only when a later payoff makes the earlier disclosure matter in hindsight. Until then, the same element may be ordinary world-building, atmosphere, a motif, a red herring, or unused texture.

The gun on the wall is therefore not inherently Chekhov’s gun. At first it is a latent hook: a disclosed story element with possible affordances and meanings. If Act III needs a threat, crime, clue, inheritance dispute, family memory, or ironic accident, the gun can be promoted into payoff. If no compatible future beat calls for it, the gun remains world-building. If too much salience was spent on it, the story may need to pay it off, deflate it, or intentionally reclassify it as misdirection.

This gives future planning systems a concrete question to ask:

  • Which disclosed hooks remain latent?

  • Which hooks are compatible with the current telos and emerging story arc?

  • Which hooks have accumulated enough salience that they should pay off, invert, or be deliberately retired?

  • Which proposed twist can be supported by earlier disclosures without violating the observed syuzhet?

The implementation implied by this section is not present yet. It points toward a future constraint authority over the fact ledger: hard disclosures, soft assumptions, latent hooks, morphology/genre constraints, and authorial telos would be solved together before the next beat is reduced to journal prose.


Landmarks in Story Space

The graph’s nodes are landmarks — fixed points of authored narrative content. Between them lies interpolation territory: template-driven text, procedurally selected details, contextual descriptions drawn from the namespace.

The density of landmarks determines the confidence of the narrative:

  • Dense landmarks (many authored blocks, specific dialog) → a tightly controlled experience, closer to a traditional novel or visual novel

  • Sparse landmarks (few key scenes, procedural connections) → a more emergent experience, closer to a tabletop RPG or roguelike narrative

  • Mixed density → the typical case, where critical plot points are densely authored and transitional content is procedurally generated

This is the “authoritative nonsense” principle: between landmarks, the engine generates confident, detailed-sounding descriptions through vocabulary banks and templates. The same mechanical seed (“roll on the tavern atmosphere table”) renders through different thematic vocabularies to produce genre- appropriate prose. The reader experiences specificity; the author only wrote structure.

Implementation: Template registries with scope-based selection; vocabulary banks as namespace contributors; render handlers that interpolate authored content with contextual detail.


Parametric Story Space

Stories exist in a low-dimensional parametric space defined by structural properties: protagonist agency, dramatic tension, stakes, pacing, moral complexity. These parameters are the controls of the narrative — the knobs an author or algorithm can turn.

The fabula graph encodes specific points in this space. Different traversals through the same fabula trace different curves through the same parameter space. A path where the player always cooperates traces a different tension curve than a path where the player always defies authority.

This parametric view enables several capabilities:

  • Similarity metrics — quantify how “different” two story paths are by measuring distance in parameter space

  • Interpolation — generate a story “between” two known paths by blending their parameter trajectories

  • Style transfer — project the same parameter trajectory through different thematic vocabularies (“tell this tension curve as noir” vs. “tell it as comedy”)

  • Verification — check that all traversals through a fabula stay within acceptable bounds on critical parameters (e.g., tension never drops to zero, agency never becomes illusory)

The key insight: because the parametric space is defined by the graph structure (not by the prose), it provides ground truth for controlled experiments. You can manipulate a clean semantic model and observe how it projects into narrative — unlike LLM-based approaches where the parameters and the prose are entangled.

Implementation status: Parametric space is conceptually defined but not yet instrumented. The graph structure implies these parameters; extracting them requires analytics tooling that is on the research roadmap. See the research agenda for “Narrative Shape Space” directions.


The Observer Roles

Three roles interact with the narrative system, each engaging a different layer:

Role

Engages

Primary Actions

Creator

Fabula

Define story world, write scripts, set up concept templates and dependencies

Navigator

Episodic process

Make choices, trigger traversals, observe state changes

Presenter

Syuzhet

Format journal output for a specific medium and audience

These roles may be filled by humans, algorithms, or AI agents — the engine doesn’t care. A human author creates the fabula; a human player navigates it; a web client presents it. But equally: an LLM could author worlds, a Monte Carlo tree search could navigate for testing, and a PDF generator could present for print.

The multi-lane extension (planned) allows multiple navigators to traverse the same fabula simultaneously from different perspectives, producing interleaved syuzhets that can be cross-referenced.

The important conceptual move is that navigation is an orthogonal role. The navigator is not necessarily the presenter, and is not necessarily the creator. A human reader may pick choices directly, but so may a scheduler, a test harness, an AI policy, or a replay driver. Multi-lane stories extend that same idea rather than introducing a new ontology: several navigators operate on the same underlying fabula, each with its own visibility, perspective, and decision policy, while the service/presentation layer remains free to surface one lane, several lanes, or a stitched cross-reference view.

Implementation: Creator → StoryCompiler + Materializer; Navigator → Frame + Ledger (user choices drive follow_edge); Presenter → GatewayHooks + render profiles in the service layer.


Determinism and Replay

Every mutation in the engine is event-sourced: state changes are captured as patches in the ledger, and the complete history of a story instance is reproducible from a snapshot plus the patch log.

This is not just an implementation convenience — it’s a narratological commitment. The syuzhet is a particular collapse of the fabula. To study narrative structure, you need to be able to replay that collapse, branch from any point, and compare alternative collapses. Without deterministic replay, the engine would be a storytelling tool. With it, the engine is a narratological instrument.

Determinism requires discipline:

  • Seeded RNG — random choices during provisioning use a seed derived from (story_id, step, choice_hash), so the same choice at the same point always produces the same outcome

  • Ordered dispatch — handler execution follows deterministic priority ordering within each phase; receipts record exactly what ran

  • External IO logging — any non-deterministic input (LLM responses, media generation) is recorded in the patch so replay doesn’t need to re-invoke external services

Implementation: Ledger (event-sourced patch log); DiffReplay (snapshot + patches → reconstructed state); seeded RNG in resolver; JobReceipt audit trail on every dispatch.


Theoretical Heritage

StoryTangl draws on established narratological theory, but treats it as engineering specification rather than literary criticism. The question is not “is Genette’s taxonomy correct?” but “can we implement it as code, and does the implementation reveal anything the theory didn’t predict?”

Key correspondences:

Theorist

Concept

Engine Mapping

Bal

Fabula / Story / Text

Graph / Frame / Journal

Genette

Order, duration, frequency, focalization

Phase bus operations (planned discourse layer)

Chatman

Kernels vs. satellites

Required vs. optional dependency edges

Barthes

Nuclei vs. catalyzers

Planning-phase offers (critical) vs. provisioning backfill (optional)

Propp

Morphological functions

Re-entrant subgraph templates (cycle patterns)

Aarseth

Scriptons / textons

Journal output / graph + ledger

Short

Storylets + QBN

Offer system with dependency preconditions

The engine is designed to validate these theories empirically: if Bal’s three layers genuinely capture independent concerns, then modifying the discourse layer should change how a story reads without changing what happens. If Chatman’s kernel/satellite distinction is real, then removing satellite nodes should leave the story structurally complete. These are testable claims, and the engine provides the apparatus to test them.

For implementation-specific follow-up, see the subpackage design notes and the research-direction summary in the root README.


What This Document Does Not Cover

  • How the phase pipeline works → see engine/src/tangl/vm/VM_DESIGN.md

  • How the graph primitives work → see engine/src/tangl/core/CORE_DESIGN.md

  • How story concepts are modeled → see engine/src/tangl/story/

  • How the service layer transforms fragments → see engine/src/tangl/service/

  • What research directions follow from this → see the root README.md and package-level design notes

This document is the why. The subpackage design docs are the how.