# Realization & Living Suspects

A player types a commission into the Bureau Case Board — "a 1947 arson in the
warehouse district, hard, two suspects" — and a few minutes later walks a
fog-lit crime scene, thumbs a coroner's report whose smudged time-of-death is
the clue that breaks the case, and sits down across a table from a man who looks
them in the eye and lies. That last part is the trick of it: he lies _fairly_.
Somewhere in the evidence the player can already reach is the single fact that
catches him, and when they slide it across the table he cracks — not because a
dialogue tree had a "win" node, but because the lie was load-bearing and the
evidence dissolved its foundation.

Getting from a proven case _skeleton_ to that lived mystery is the work of the
realization layer, and three named subsystems own it. **Anansesɛm** is the
writers' room: it _speaks_ the case — the opening briefing, scene prose, partner
banter, a character sheet per suspect, and the interrogation scripts. **Loom**
is the asset fabric: it gives the case a face and a sound — crime-scene
environments, suspect portraits, evidence props and documents, a period score,
and a voiced line for every spoken one. **Ori** is the runtime that makes a
suspect a _mind_ rather than a menu — memory, a stance that hardens under
pressure, and the discipline never to tell a lie the player cannot refute. All
three obey one inherited covenant, the spine of the whole product: the LLM may
_speak_ the case, but it may never _invent_ it — no new suspect, no new room, no
number, and no lie the proof did not already author. The fairness that
[case generation, proof & canon](./case-generation-proof-and-canon.md) establish
at authoring time is exactly what these three are forbidden to break at
performance time. This is the realization half of the V8 feature set; the hub is
[../V8_features.md](../V8_features.md).

## What ships, honestly

The realization layer is **real and exercised end to end**, with the generative
pieces sitting behind typed seams. Three tiers, stated plainly:

- **Real, deterministic, and tested today.** The Loom service runs the entire
  mint→publish pipeline with **zero credentials**: `runPipeline`
  (`apps/v8/loom-service/src/pipeline.ts:127`) threads Clew → verify → Anansesɛm
  → Loom assets → compile → eval → safety → release. The §12.4 acceptance suite
  mints a case, clears all eight `ReleaseDecision` gates, compiles a real V5
  cold-cases pack through the build gate, and **reproduces bit-for-bit from its
  seed** — `5 passed (5)` (`apps/v8/loom-service/src/pipeline.test.ts`, verified
  green here). With no LLM injected, Anansesɛm still emits a complete, grounded
  surface; Loom's planner, retrieval ranker, voice caster, and MetaHuman mapper
  are real algorithms (`case-assets`, `53 passed`); Ori's memory stream,
  fair-lying guard, and offline voice are real and reproducible
  (`case-suspects`, `34 passed`); the writers' room is `18 passed`.
- **Provider-gated seams — fail-loud / fail-closed, not faked.** The actual
  generation is injected, never fabricated. Anansesɛm's prose enricher is a
  `CompletionFn` — absent, the deterministic base text stands; present, a
  provider error _propagates_. Loom's minter wants an Isis `JobRunner` — absent
  with real gaps to fill, `realizeCase` **throws** rather than ship a half-empty
  pack. Ori's live-avatar broker reads `OSHUN_INWORLD_*` / `OSHUN_NVIDIA_ACE_*`
  credentials — absent, it reports `{ configured: false }` and **fails closed**
  to a fully-playable offline interrogation.
- **Honest scope notes.** The shipped §12.4 pack is **text-only**: the offline
  deps (`apps/v8/loom-service/src/deps.ts`) wire no Isis runner, so no media is
  minted — Loom's fabric is real and unit-tested but not exercised by that run.
  And Ori is the **runtime** layer: it is driven by the game host at play time,
  not by the offline mint, which bakes the _static_ 12-beat trees Anansesɛm
  authored.

The architecture-side treatment of all three lives in
[Anansesɛm, Loom & Ori suspects](../architecture/anansesem-loom-and-ori-suspects.md);
this page stays on the player-facing surface and dips into code where a claim
needs backing.

## Anansesɛm — prose that can't lie about the facts

Anansesɛm (named for the Akan Anansi storytelling tradition) realizes everything
the player _reads or hears as words_. It is a real multi-agent **writers'
room**, not a single prompt: `realizeCase`
(`libs/yemaya/case-writers-room/src/dag.ts:244`) compiles and invokes a
`StateGraph` whose id is literally `anansesem-writers-room` (`dag.ts:97`). A
**Showrunner** sets the brief — title, era voice, and the grounding vocabulary —
and fans the work to five specialist writers: **StoryDirector** (the briefing),
**CharacterWriter** (a sheet per cast member), **DialogueWriter** (an
interrogation tree per suspect), **Cinematographer** (scene descriptions + shot
notes), and **SoundDesigner** (partner banter). An **Integrator** assembles
their disjoint slices into a `RealizedCase`. The graph is genuinely
`compile().invoke(state)`-driven, not narrated.

### The closed vocabulary — the room cannot name a stranger

The hard feature guarantee — _the writing never contradicts the proof_ — lives
in `grounding.ts`. From the ground truth plus the canon slice, the Showrunner
builds a closed `GroundedVocabulary`: the exact set of people (victim + suspects

- recurring canon characters), location ids and display names, and the weapon.
  `assertGrounded` is a real proper-noun extractor, not a shape check — it walks
  every realized string, lifts runs of two-or-more capitalized, name-shaped
  words (`extractProperNouns`, the shape that could smuggle in an invented
  "Cornelius Blackwood"), strips genitives and function words, and reports any
  token that resolves to no grounded entity as a `GroundingViolation`. A player
  can never be sent to interview someone the case does not contain.

### Twelve tells, one fair lie

`buildInterrogation` (`libs/yemaya/case-writers-room/src/interrogation.ts:123`)
aligns each suspect to V5's interrogation rig — **twelve facial-tell beats**
(`TELL_BEATS`) and the **Truth / Doubt / Lie** mechanic the player reads off the
suspect's face — with invariants that tie every line back to the skeleton:

- Every tree fills exactly 12 beats; beat 7 is the alibi beat, beat 9 the
  pressure beat.
- The **culprit lies exactly once** — their alibi (beat 7) is a `Lie` whose
  `refutedByClueId` is the alibi-flaw clue (the single discoverable flaw); the
  builder _throws_ if the session contains no such clue for the culprit
  (`interrogation.ts:146`), so a guilty party with an uncatchable alibi can
  never reach a player.
- An innocent's alibi is **Truth** (verified, with grounded corroborator names)
  or **Doubt** (unverifiable) — never a Lie about the alibi. A red-herring
  innocent may tell one _deflatable_ Lie tied to a fair herring clue, so the
  rule "every Lie carries a refuting clue" always holds.

Stress tells escalate with veracity (a Lie under pressure plays `gaze-aversion`
/ `swallow` / `jaw-clench`; Truth plays calm), so what the player _sees_ tracks
what the suspect is _doing_ — and that mapping is deterministic and grounded in
the real victim name, weapon, and place, not free-associated.

### The polish seam (fail-loud)

Every spoken or written line passes through `enrichLine` (`enrich.ts:32`). With
no `CompletionFn` it returns the deterministic base verbatim — the case is fully
playable with no model at all. With one injected, it asks the model to rewrite
the line in register and then **re-grounds the result**: `withinBoundary`
rejects any rewrite that introduces a name-shaped token outside the allowed set,
falling back to the deterministic base. A _provider_ error, by contrast,
propagates uncaught — the room never launders an outage into silently-degraded
prose. The model can make the prose sing; it cannot invent a suspect, and it
cannot fake success.

## Loom — the case you can see and hear

Loom binds a case to multimedia through Isis. Its entry, `realizeCase`
(`libs/yemaya/case-assets/src/realize.ts:62`), runs the fabric end to end: plan
→ retrieve → degrade-to-budget → mint the surviving gaps → bind, returning a
contract-valid `AssetManifest` keyed to the case.

### A plan derived from the case, never a fixed list

`planAssets` (`libs/yemaya/case-assets/src/plan.ts:166`) computes _exactly_ what
a specific case needs, every count read off its structure: one **environment**
per distinct story location (de-duped by slug so "Gatehouse" and "gatehouse" are
one scene), one **portrait** per suspect plus the victim, one **3D prop** per
physical/forensic clue and one **document/photo** per documentary/testimony
clue, case **music** plus one **sting** per revelation or climax beat, one
**voice-over** line per realized dialogue line, and optional **flashback/CCTV
video** per timeline beat at the crime scene. Each `PlannedAsset` carries a
`boundTo` target, so the produced media wires back into the _exact_ V5 evidence
node — the smudged report becomes _this_ clue, this portrait pins to _this_
suspect on the board.

### Reuse before mint — the world is consistent because it remembers

Before minting anything, `retrieveThenGenerate` asks an injected `AssetLibrary`
whether the world already holds the right asset — reuse beats mint when a
recurring district or NPC should _look the same as last time_. The default
ranker is real: a **specificity-weighted** tag overlap (identity tags like
`location:` / `subject:` / `clue:` weighted 4×, shared context tags like `cell:`
/ `era:` / `theme:` weighted 1×) blended `0.8 / 0.2` with a description-token
Jaccard, against a `DEFAULT_REUSE_THRESHOLD` of **0.62** (`retrieve.ts:61`,
`:147`). The weighting is the point: a wrong-location environment that shares
all the case boilerplate still falls below threshold and gets minted bespoke. It
is computed retrieval — never always-reuse, never always-mint.

### Voices that stay the same, faces that match

`castVoice` (`voices.ts:85`) assigns each character one stable, register-correct
voice by reducing a SHA-256 of `canonId::characterId` over a partitioned pool of
**12 distinct ElevenLabs-style voices**, so a grizzled male suspect never lands
a youthful voice and — crucially — _the same character sounds the same in every
case they appear in_. `assembleMetaHuman` (`metahuman.ts:87`) maps a portrait
plus persona tags onto MetaHuman Creator's real axes (an 18–80 age range, a 0–15
skin-tone ramp, hair, facial hair, body type, age-scaled wrinkle/scar overlays)
deterministically from the character id, so the suspect you interrogate has a
consistent, buildable face rather than a stock head.

### Degrade, never fake

`degradeToBudget` reserves `asset-jobs` from the per-case budget and, when the
ceiling is hit, **degrades in a real priority order** — environments and
portraits first, evidence next, optional flashback video dropped first — logging
every drop so the pack is never silently truncated. And the mint step is where
Loom refuses to fabricate: `mintGaps` (`orchestrate.ts:208`) builds a real
`JobRequest` per gap with period-correct dimensions and an anachronism
negative-prompt, and any runner error, job error, or job that returned _no
output file_ is raised as a `MintingError` (`orchestrate.ts:67`) — it never
invents a result url. `realizeCase` throws outright if real gaps remain with no
Isis runner wired (`realize.ts:80`). An absent backend is surfaced, not papered
over with a plausible-looking asset.

## Ori — suspects you actually interrogate

Ori (Yoruba for the "inner head," the seat of identity) is the runtime that
turns a suspect from a fixed dialogue tree into a questionable mind. Each
suspect carries a `SuspectOri` (`libs/yemaya/case-suspects/src/ori.ts:106`)
seeded deterministically from `caseId + suspectId`: a **total projection** of
the hidden ground truth — what they KNOW (their movements, who they saw, their
alibi), what they HIDE (the culprit conceals the deed behind a flawed alibi; an
innocent may hide an unrelated, harmless secret), and the set of **fair lies**
they may tell. Because the Ori invents nothing the symbolic layer didn't author,
a live interrogation can never drift out of solvability. Persona axes are
derived, not random: the culprit is _measurably_ more evasive and more stressed
under questioning than a cleared innocent (`ori.ts:216`) — and the player can
feel it.

```mermaid
stateDiagram-v2
  [*] --> cooperative
  cooperative --> guarded: pressure ≥ 0.25
  guarded --> deflecting: pressure ≥ 0.5 & evasive
  guarded --> guarded: pressure ≥ 0.5 & not evasive
  deflecting --> rattled: pressure ≥ 0.8 (culprit)
  rattled --> cracking: load-bearing clue presented
  guarded --> cracking: load-bearing clue presented
  cracking --> [*]: grounded confession
  note right of cracking
    Only the culprit can crack,
    and only on the one clue that
    refutes their alibi (fair-play).
  end note
```

### Memory keeps a suspect's story straight

V5 lets the player re-interrogate a suspect across several passes, and a living
mind must not contradict itself. `MemoryStream` (`memory.ts:89`) implements the
Park et al. _Generative Agents_ retrieval — every memory carries a poignancy,
and retrieval scores candidates by `recency · importance · relevance` with an
8-turn recency half-life. So a suspect cannot say "I was home" and later "I was
at the club": the prior statement is retrieved _before_ the next answer is
formed. Once accumulated poignancy crosses a threshold (`2.0`, tuned so two firm
accusations plus a presented clue trip it but idle small-talk does not),
`maybeReflect` synthesizes a higher-level belief — "the detective has hard
evidence and is closing in" — and `plan` (`memory.ts:207`) chooses the stance to
hold next turn: the cooperative → guarded → deflecting → rattled → cracking
ladder the diagram above traces, driven entirely by accumulated pressure.

### The fair-lying guard — the runtime half of the fairness covenant

`fair-lying.ts` is the runtime mirror of the fair-play proof Minos enforces at
authoring time. `canLie` (`fair-lying.ts:105`) returns **false** for any lie
that _no player-reachable clue can refute_: it indexes the session's clues by
the ground-truth fact each establishes (via each clue's
`metadata.groundTruthFactId`), and a lie is permitted only if a reachable clue
proves its `refutingFactId`. An Ori can never deliver an "authorial cheat"
through dialogue. `crackUnderEvidence` (`fair-lying.ts:149`) is its mirror: the
culprit cracks **only** when the player presents the specific clue that
establishes the fact refuting their _load-bearing_ alibi lie — an irrelevant
clue raises pressure but does not break them, and an innocent (who told the
truth) never cracks. The confession is grounded in the suspect's own ground
truth, not improvised. This is the "lies you can catch with clues the game
actually gives you" promise, enforced at every utterance.

### Live or offline, always playable

`FailClosedBroker` (`runtime.ts:174`) is the seam to a managed avatar service.
It reads ACE/Inworld credentials once and reports an honest
`status() = { configured, mode }`; it is "live" only when creds _and_ a real
live broker both exist. With neither, it fails closed to an
`OfflineInterrogation` — the memory stream, the guard, and a deterministic
`OfflineDialogueBackend` whose persona-driven voice is a pure function of the
Ori, the question, the stance, and whether a lie was authorized. A live LLM is
adapted in through `hathorBackendFrom`, which builds a grounded persona and
propagates any provider error (fail-loud). The host treats both modes through
one `send` surface, so the interrogation stays playable and fair-play-correct
even with no cloud avatar — a pre-rendered face is an upgrade, never a
prerequisite.

## How it fits together

The realization layer is the bridge between a _proof_ and a _performance_.
Upstream, the symbolic core hands it a case it has already solved; downstream,
the compiler and director turn its output into something a player walks into.

- **Upstream:**
  [Case generation, proof & canon](./case-generation-proof-and-canon.md) builds
  and proves the verified skeleton — the culprit Minos certified is the unique
  solution — that all three subsystems here are forbidden to contradict; Ori's
  fair-lying guard is the runtime echo of that authoring-time proof.
- **Downstream:**
  [Delivery, direction & operations](./delivery-direction-and-operations.md)
  compiles Anansesɛm's trees and Loom's manifest into the V5 cold-cases pack,
  play-tests it, and decides which generated mysteries reach which player, when
  — and the Loom service (`apps/v8/loom-service`) is the relay the UE5 client
  mints and fetches cases through.
- **Architecture:** the
  [Anansesɛm, Loom & Ori companion](../architecture/anansesem-loom-and-ori-suspects.md)
  carries the full graph topology, the grounding extractor internals, the Isis
  job contracts, and the broker state machine behind these features.

## Related

- The feature hub: [../V8_features.md](../V8_features.md)
- [Case Generation, Proof & Canon](./case-generation-proof-and-canon.md) — the
  solve-first ground truth, Minos's uniqueness proof, and the canon graph this
  layer realizes and writes back to
- [Delivery, Direction & Operations](./delivery-direction-and-operations.md) —
  Daedalus's V5 compile, Theseus's automated playtest, and the Oracle that
  places cases in the living city
- [Anansesɛm, Loom & Ori Suspects](../architecture/anansesem-loom-and-ori-suspects.md)
  — the architecture-side treatment of the same three subsystems
