M5: genre-neutral authoritative narrative state, with review corrections

Replaces AI-DnD's RPG relative-delta world state with the genre-neutral typed
narrative state of ADR 010: explicit, absolute, allowlisted events proposed by
the model, validated by the application, applied to one authoritative document,
and snapshotted per position so restore stays a row read.

This commit includes the corrective pass that followed the independent review
in planning/reports/M5-IMPLEMENTATION-REPORT.md. The invariant it exists to
hold is:

    visible active transcript position == stored head == authoritative state

Narrator editing (D10, STORY-BRANCH-SEMANTICS §§14-15)

  A narrator edit no longer rewrites a row. It returns to the state before the
  turn, takes the reader's exact text as the accepted narration, re-derives the
  state that text implies, and becomes a new active continuation — while the
  original narration keeps its words, its live flag and its whole future as
  retained history. At the tip the correction is another take; with story below
  it, it forks. No new history machinery: this is the existing fork/take/head
  path with the reader's text in place of a generated reply. The §14A refusal
  is therefore gone for narrator turns, and remains only for player input.

Pre-M5 positions

  Migration 88 backfills the empty narrative document onto every action written
  before M5, and a missing snapshot now restores the empty document instead of
  leaving the previous position's state standing. Restoring to an old Save
  Point no longer leaves a later position's entities and facts on screen.

Narrator context

  Replayed history carries prose only; the machine-readable block is no longer
  reconstructed into past turns, where it contradicted the authoritative state
  in the same prompt. A fact withdrawn by a manual correction is now named as
  no longer true, with the reader's reason, rather than silently dropped.

Also

  - state_changes joins the action-list bulk read, removing one query per row.
  - Extraction takes only the application's own protocol payload: an ordinary
    ```json or ```python block in a story survives, and a mangled proposal
    still does not reach the reader.

Planning: ADR 013 records the authoritative document shape; §§14-15/14A, D10,
C04 and BUILD-MILESTONES are updated to describe what exists. Debt is recorded
against M8 (scenario editor UX) and M9 (export of the audit trail).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PWU4gTfLYY6Qq9U7aa9Qw2
This commit is contained in:
JesseMarkowitz
2026-09-05 07:01:50 -04:00
co-authored by Claude Opus 5
parent 62a997f364
commit b7005e6fdd
57 changed files with 7257 additions and 474 deletions
+90
View File
@@ -410,3 +410,93 @@
}
.mode-select button.active + button { border-left-width: 0; }
/* Story State (M5). The same furniture as the other panels: what the campaign
believes is not a different species of thing from the lines it believes them
on, and a reader moving between panels should not have to relearn the shapes. */
.state-panel { display: flex; flex-direction: column; gap: 14px; }
.state-intro, .state-hint {
margin: 0;
color: var(--text-dim);
font-size: 0.78rem;
line-height: 1.55;
}
.state-group { display: flex; flex-direction: column; gap: 6px; }
.state-group h3 {
margin: 0;
font-size: 0.64rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--text-dim);
font-weight: normal;
}
.state-row {
display: flex;
flex-direction: column;
gap: 3px;
padding: 7px 10px;
border: 1px solid var(--border);
border-radius: 5px;
background: var(--bg-input);
}
.state-label {
font-family: var(--font-story);
font-size: 0.94rem;
color: var(--text);
}
.state-detail {
font-size: 0.74rem;
color: var(--text-dim);
font-variant-numeric: tabular-nums;
}
.state-tools { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.state-tools button {
padding: 2px 8px;
font-size: 0.7rem;
color: var(--text-dim);
background: transparent;
border: 1px solid var(--border);
}
.state-tools button:hover:not(:disabled) {
color: var(--accent-bright);
border-color: var(--border-bright);
}
.state-tools button:disabled { opacity: 0.4; cursor: default; }
.state-correction { display: flex; flex-direction: column; gap: 7px; }
.state-correction form { display: flex; flex-direction: column; gap: 7px; }
.state-correction label {
font-size: 0.64rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--text-dim);
}
.state-correction input {
padding: 5px 9px;
font-family: var(--font-story);
font-size: 0.92rem;
}
.state-correct-open, .state-history-open {
align-self: flex-start;
padding: 3px 9px;
font-size: 0.72rem;
color: var(--text-dim);
background: transparent;
border: 1px solid var(--border);
}
.state-correct-open:hover, .state-history-open:hover {
color: var(--accent-bright);
border-color: var(--border-bright);
}
.state-history { display: flex; flex-direction: column; gap: 4px; }
.state-history-row {
display: flex;
flex-direction: column;
gap: 1px;
padding: 5px 9px;
border-left: 2px solid var(--border);
}
.state-history-what { font-size: 0.8rem; color: var(--text); }
.state-history-when {
font-size: 0.68rem;
color: var(--text-dim);
font-variant-numeric: tabular-nums;
}