# Persistent boards, gated exhibits, and citation codes

Status: **demo subset implemented**. Migration 025 implements level-local boolean
flags, normalized Document requirements, server-side filtering, persisted reveal
acknowledgements, and the arrival animation. Board-key reuse, narrative-triggered
live reveals, citation groups, and per-user playthrough overlays remain deferred.
The broader design extends the story flow graph
([story-graph.md](story-graph.md)) and the narrative layer
([narrative-todo.md](narrative-todo.md)); interlocks with the Claim/Case Report
work in [TODO.md](TODO.md) Milestone 5. Depends on the **flags / case-state**
primitive (a per-playthrough key→value store) that the gates and dialogue effects
also read and write.

## Why

A mystery is broken into short pieces so we can **meter how much evidence the
player carries at once**. Interruptions — a phone call, a knock, a revealed note —
should not throw the player off the board they are working on. So a single
investigation scene is a **persistent, player-mutated board** that several graph
nodes return to, and new evidence **arrives into that same board** when the story
warrants it rather than being dumped up front or split across duplicated templates.

Two capabilities, independent and both wanted:

1. **A board that survives across nodes** — `[board] → [phone call] → [same board]`
   returns the player to their exact arrangement and connections.
2. **Flag-gated exhibits inside that board**, revealed with a diegetic "it arrived"
   moment (reuses the existing document locator beam).

## Board identity and reuse

An authored level node **cannot** reference a clone id — the clone is created per
playthrough at runtime. Instead:

- A **level node** references a `level_template_version_id` **plus a `board_key`**
  (a logical board slot, e.g. `"harbor-desk"`). `board_key` defaults to the
  template if the author doesn't care about sharing.
- A **playthrough** holds a map `board_key → level_id` (the mutable clone).
- Entering a level node:
  - if the playthrough already has a clone for that `board_key` → **reuse it**
    (the player's mutated board, arrangement and connections intact);
  - otherwise → **clone the template** and record `board_key → new level_id`.

`board_key` is the thing that makes two nodes share a board. Two nodes on the same
template with **different** keys get **separate** boards.

## Gated exhibits

- Every exhibit is **cloned in**, including gated ones; a gated exhibit carries a
  **`reveal` condition over flags**. The player's saved board already contains the
  note — it is simply inert until the flag flips.
- **Visibility is computed server-side at load.** Given the playthrough's flags,
  the play-mode payload includes only exhibits whose `reveal` condition is met;
  hidden exhibits are stripped from the response (same discipline that already
  hides authoring-only fields like `expectedPartyKind`). Players cannot peek
  unrevealed evidence in the API.
- **Gate the edges too.** Any `exhibit_connection` or event link whose endpoint is
  a not-yet-revealed exhibit is hidden until that exhibit appears, so there are no
  dangling references.

## The arrival reveal

- The board's effective content can change **while the player is standing on it** —
  the phone is an always-available surface, so a call can set a flag mid-scene. So
  reveal is **not load-only**: after any phone/dialogue interaction that sets flags,
  re-evaluate board visibility and reveal live (the phone overlay refetches
  visibility on hang-up).
- Track a per-playthrough **`revealed-seen` set**. On (re)load or after an
  interaction, any exhibit now visible but not yet in the set → play the arrival
  animation (a sibling/reuse of the **document locator beam**), then add it to the
  set. The flourish fires **once**, not on every reload.

## Reset and New Game

- **Reset = tidy the desk back to the delivered state.** Authored exhibits return
  to their arrival positions and folders; the player's rearrangement is undone.
  **Flags are untouched**, so anything a flag has already revealed is still
  "arrived" and is restored at its authored position.
- **New Game** re-clones the whole tree and is therefore the only thing that
  **resets flags** and returns gated exhibits to hidden.

**Open decision — player notes on reset.** Milestone 5's rule is "reset discards
player-created claims." Current lean: reset also removes player-created exhibits
(it restores the *delivered* state), and New Game is the only wipe of flags. The
alternative is a gentler reset that re-files arrived exhibits but leaves the
player's own notes in place. Settle before implementing.

## Exhibit citation codes (A / B / M)

A single running number breaks when players create many note exhibits, so codes
live in fixed namespaces:

- **Authored exhibits: group letter + index** — `A1, A2, …` for the initial
  dossier, `B1, B2, …` for a batch that arrives later via a flag reveal, etc.
  **The letter is the arrival group**, which ties straight into gated reveal: "new
  evidence arrived" *is* the B-series lighting up. Indices are assigned **within
  each group at freeze time** and frozen in the template, so authored codes never
  shift, no matter what the player does.
- **Player-created exhibits: a running `M` (miscellaneous) series** — `M1, M2,
  M3…` in creation order, in their own namespace so player note-making cannot
  disturb authored codes. M-numbering is per-level-instance and (given the reset
  lean above) restarts only when player notes are cleared.
- The author chooses an exhibit's **group**; the system owns the **numbering**.

These codes are the **citation token everywhere** — board, luggage-tag Claims, the
Case Report, and the LLM gate.

## LLM gate contract

- A gate can be **scripted to require specific authored codes** — e.g. "the report
  must cite `A1` and `B2`, connected." That authored requirement list is the
  deterministic backbone *under* the LLM, per the section-9 intent of scripting the
  cognitive shim rather than trusting it blind.
- If the player types the full report freehand, they must include the Exhibit codes;
  the gate checks the required codes are present (and, later, that the reasoning
  holds).

## Relationship to fresh templates

This **complements**, not replaces, new templates:

- **Shared `board_key`** — interruptions *within* one investigation scene (calls, a
  knock, a revealed note). Cleanly subsumes the earlier "note appears on the next
  level" idea: the note is a gated exhibit on the *same* board that reveals after
  the call.
- **A fresh template** — the player genuinely moves to a new location/chapter with a
  different board.

## Implemented vs deferred

Nothing here is built yet. New plumbing, smallest-first:

1. **Flags** — the per-playthrough key→value store (shared with gates/dialogue).
2. **`board_key` reuse** in level traversal + the playthrough `board_key → level_id`
   map (clone-or-reuse).
3. **`reveal` conditions on exhibits** + server-side play-mode visibility filtering
   (exhibits and their edges).
4. **`revealed-seen` set** + the arrival animation (locator-beam sibling), live
   after flag changes.
5. **A / B / M citation codes** — authored group + frozen index, player M-series;
   surfaced on the board and threaded into Claims/report/LLM gate.

Reset (desk-restore, flags-persist) and New Game (full re-clone) semantics as above.
