The VN scaffold — Script as the projection, summary and deltas as the truth
Source path: knowledge-base/specs/visual-novel/scaffold.md
# The VN scaffold — Script as the projection, summary and deltas as the truth
Status: **rough spec (direction).** Not an implementation contract; nothing here ships until scheduled.
This document owns the **substrate**: what the visual novel is made of, what is authoritative, and what is
merely a rendering of it. Every other document in this folder depends on the rule stated here, so it is
stated first and once.
> **The rule.** A scene's **summary and its typed deltas are the source of truth.** The Script — frames,
> cast, slots, props, moments — is a **projection** of that truth: generated, versioned, regenerable, and
> never authoritative. The VN reader is a projection of the projection.
Everything the medium wants follows from taking that seriously. Regenerable staging means a style change is
a re-render rather than a rewrite; a delta-grounded scaffold means expressions, plate variants, and cutscene
placement can be *derived* rather than guessed; and a projection that never writes back means a reader can
explore a branching record without ever endangering it.
Read alongside: [delivery](README.md) (what the modality is), [branching](branching.md) (how a fork is
produced), [production](production.md) (what the assets cost), [reader](reader.md) (the interface).
---
## 1. The layer cake
```text
Scene.summary + typed deltas ← canon. What happened, and what moved.
└ BeatPlan ← structure. The scene's spine.
└ prose (ProseVersion) ← the text. The work's voice.
└ ScriptContent (ScriptVersion) ← the scaffold. Blocking, casting, moments.
└ VN reader ← the experience. Transport, saves, POV, presentation.
```
The conditioned terminal stage receives:
| Input | Role |
|---|---|
| `<questions>` | Learning questions the reader must be able to answer; each is served by one or more frames. |
| `<closures>` | Thread closures derived from settled Causality Threads; settled Threads become visible fact, open Threads remain undecided. |
Script fidelity is to canon + Flags + questions. Prose is the richest material surface, not independent authority.
See [pentad/app-integration §5.2](../pentad/app-integration.md#52-generation--flags-as-read-only-context) for the
full generating-ring reading.
Each layer is derived from the ones above and may be regenerated without touching them. Each layer is
**versioned where it is stored** — `Scene.scriptVersions: ScriptVersion[]`, resolved through branch lineage
and fork time exactly as `proseVersions` and `planVersions` are. That is already how the record works, and
it is the property the VN depends on: *the same canon can carry many scaffolds.*
The inverse move is forbidden and worth naming, because it is the tempting one:
- Staging never invents an event. The composer is instructed to re-stage, not to write.
- Never re-parse a scaffold (or its prose) to recover canonical state. Deltas are declared, not extracted.
- A reader's traversal never writes to any of these layers.
---
## 2. What is already shipped
The scaffold generator exists. `generateSceneScript` (`src/lib/engine/ai/script.ts`) composes a scene into a
`ScriptContent` and commits it as a version, and the prompt it uses
(`src/lib/engine/prompts/script/compose.ts`) is already a stage-director brief rather than a text-to-images
call. Its inputs, in the order it receives them:
| Input | What it contributes |
|---|---|
| **Roster** | The exact ids that may be staged — POV character, participants, the location, and artifacts drawn from `artifactUsages` *and* `ownershipDeltas`. Invented ids are refused. |
| **`<logic-context>`** | Point-in-time knowledge, asymmetry, relationships, required events, arrivals, and artifact possession. A later Scene's knowledge cannot enter an earlier scaffold. |
| **`<scene-brief>`** | `sceneContext` for the scene with `<world-state>` stripped — the structural truth: summary, deltas, events, thread/world/relationship shifts. **Always included**, even before prose exists. |
| **`<beat-plan>`** | The structural spine; used to find natural frame boundaries. |
| **`<prose>`** | Optional and *additive*: the richest surface when present, the text whose exchanges become moments. |
| **`<prose-profile>`** | The work's voice — register, stance, devices — so staged lines sound like this work. |
| **`<steer>`** | GM intent, shaping staging without contradicting the prose. |
| **`<knowledge>`** | Per-lens knowledge and secret nodes admissible at the scene cursor; Script `reveals` may name only these ids. |
| **`<force-reading>`** | Descriptive World/System/Fate/Theme/Causality readout at the scene cursor; it is context, never an optimisation target. |
Two consequences matter for everything downstream:
1. **The delta layer is already an input to staging.** The scaffold is not composed from prose alone, so
"summary and deltas are the source of truth" is a property the pipeline mostly already has — the gap is
that nothing *derives staging decisions from specific deltas* (§4), it only reads them as context.
2. **Staging works without prose.** The brief alone can carry a scaffold. That is the cheap lane for
exploration: a scouted branch can be staged structurally before anyone pays to write it beautifully.
3. **Interiority follows the active ordered lens.** `Scene.povIds` is the ordered set of Character lenses
authorised to carry passages in the Scene; `Scene.povId` is its opening/default lens. Legacy Scenes that
predate the roster derive it from Domain settings and present participants rather than being rewritten.
A Beat may retain the active lens, switch to another allowed Character, or use `null` for narrator/choral space; Script moments
preserve those changes. Only the active Character may receive a `thought` moment. Multiple holders can
therefore share one Scene with comparable dramatic weight without blending minds inside one passage.
Missing or invalid model labels inherit the last valid lens, Single mode cannot switch, and a collective
Unit lens grants no individual private thought. The sanitizer enforces this on the regenerable Script
projection; it never rewrites Scene canon.
The composed output already carries the medium's grammar: frames as stable tableaux with a `locationId`
backdrop and a mood note, cast placed in five slots with `facing` and `state`, props attached to bearers,
and a moment sequence over the eight beat mechanisms plus the stage-native `reaction`. Three focal scales
are already modelled — **individual** (one lit actor), **group** (`speaker` plus `with`), and **stage**
(ensemble under a caption) — and the off-stage roster already has a home in the cast rail.
That inventory is why this is a scaffold spec and not an engine spec. The composer is close to what a VN
needs. What is missing is *derivation, identity, and presentation*.
---
## 3. Entity registers on the stage
The medium distinguishes four presences, and the record already distinguishes them by aspect:
| Register | Record | Aspect | Reuse |
|---|---|---|---|
| **Character sprite** | `Character` | 3:4 | Capital — generated once per identity, amortised across every branch |
| **Location plate** | `Location` | 16:9 | Capital — one plate, many variants (§5) |
| **Prop / artifact** | `Artifact` | 1:1 | Capital — attached to a bearer with `with`, or standing in the space |
| **Event CG** | *(none)* | 16:9 | **Consumable** — one moment, one bill, no reuse |
The first three compose at render time: a sprite is drawn *over* a plate, never baked into it. This is the
single decision that makes the economics work, and it is already how the renderer resolves entity images.
Producing composed art per scene multiplies spend by the branching factor for nothing — the figure *is* the
entity.
The fourth has no home in the model yet. A CG is not an entity image; it is a **moment-scoped illustration**
of one composed instant that will never recur. It therefore belongs on the scaffold, not on an entity — a
per-moment reference on the frame's sequence — and it is the only asset class whose cost scales with story
length rather than cast size. Rationing it is §4.
---
## 4. Deriving staging from deltas
This is the part that does not exist and is the highest-value addition, because it converts the delta layer
from *context the model reads* into *decisions the pipeline makes*. Four derivations, all deterministic,
none requiring a model call:
**CG placement.** Spend a cutscene where an **irreversible** delta lands — a thread resolution, an ownership
transfer, an entity death or entity-resolution delta, a system delta that closes an option. Not where the
prose is loudest. This is the same gate the decision spec uses to qualify a fork, applied to the art budget,
and it means the illustrated moments of a route are exactly its consequential ones. A route with no
irreversible delta gets no CG and should not: nothing happened.
**Expression selection.** A character's `state` is already staged per frame, but which *face* a sprite wears
should follow two bounded inputs: the frame deltas and state that name that character, and—when present—the
Behaviour Reading produced from the branch-and-moment Entity Interpretation Graph binding. The first says what
changed; the second proposes how this particular character might express it through competing tendencies,
inhibitions, and micro-movements. Factory resolves the semantic request to an approved expression inside the
correct appearance set. This is a presentation choice, not a new delta, a diagnosis, or a model's free guess
at mood.
**Plate variant selection.** Time, weather, and aftermath variants of a location plate are chosen by world
deltas that touch that location — a place burned, occupied, emptied, or rebuilt. A plate that never changes
across a route where the world changed is the flattest failure available.
**Cast rail as information boundary.** The rail already shows who is in play versus waiting. Under a POV
(§6) it must show who *this seat knows to be* in play — the rail is a disclosure surface, not a debug view.
The honest caveat: all four require the composer (or a pure post-pass over the composed scaffold) to receive
the deltas **addressed to specific frames and moments**, rather than as scene-level context. That mapping —
delta → frame → moment — is the one new structural thing the scaffold layer needs.
---
## 5. Regeneration and identity
Because the scaffold is a projection, it can be regenerated. Because assets are capital, they mostly must
not be. The two facts collide, and the resolution is a rule per layer:
- **Scaffold: freely regenerable.** A new staging of the same scene is a new `ScriptVersion`. Nothing is
invalidated except the reader's cursor into that scene, which re-resolves.
- **Sprites and plates: stable by default.** Regenerating an entity identity reference changes the person or
place a reader has already met. Factory creates versioned appearance and performance descendants and
reports their dependants; it never replaces every branch at once by accident.
- **CGs: bound to their moment.** A CG survives only as long as the moment it illustrates. If the scaffold
is regenerated and the moment is gone, the CG is orphaned — and since it cost real money, orphaning should
be reported, not silent.
- **Voice: cached per line version.** A re-read never re-synthesises; editing a line invalidates that line.
Identity across regenerations is the unsolved dependency, and it is worth stating bluntly here because three
documents lean on it: the image call currently sends `{ prompt, aspect_ratio }` and no reference image, so a
character's identity survives only as a stored prose description. Reference conditioning is a prerequisite
for a real sprite set, not a refinement of one ([production](production.md) §identity).
---
## 6. POV as a projection, not a filter
Single-POV and multi-POV are the same mechanism at different settings, and the mechanism already exists:
`Scenario` projects the room per seat, and concealed material is **absent** from the projection rather than
hidden in the UI. Applied to the VN:
- **A POV is a seat.** A route read under a POV shows the frames, moments, glossary entries, and rail
presences that seat could know. The reader is not trusted to forget.
- **Single POV** binds the work to one Character lens — the classic structure, and the cheapest to keep
coherent. Narrator texture may describe observable space but may not become another mind.
- **Multi-POV** supports both parallel routes and several holders inside one Scene. The Scene stores the
holders in `povIds`, while `povId` keeps the opening/default lens backward-compatible. Inside a Scene the
boundary is the ordered Beat: one lens per passage, an explicit change between passages, and Script
moments that preserve the active holder. The selected holders active in a Scene receive meaningful,
comparable weight unless the Scene purpose earns an asymmetry; mechanical turn-taking is not the goal.
- **Perspective movement is independent of the roster.** `povMovement` gives the author three stable
policies: `scene-bound` permits one Character lens per Scene; `selective` (the default, including legacy
records) permits an earned Beat-boundary switch when another holder reveals something unavailable;
`shared` expects several present holders to receive meaningful, roughly balanced access inside the Scene.
Single-character POV always resolves as `scene-bound`. Narrator texture may remain inside a Scene without
becoming permission to enter an unlisted mind.
- **A POV switch is a reveal** and should be treated as content. It earns its place by unlocking perception,
interpretation, interiority, or dramatic irony unavailable to the prior holder. Ordering constraints are
therefore narrative and technical: the sequence preserves exactly whose knowledge governs each passage.
Prose generation receives both the full Scene roster and the normalized Beat sequence; stale labels inherit
the preceding valid lens and an explicit `null` remains narrator/choral rather than snapping back to the opener.
- **Perspective is established at birth.** The creation wizard keeps the default path light, but its Advanced
disclosure exposes the same POV approach, movement, and optional identity direction that Domain Settings
later edits. Each paradigm owns a best-fit preset: singular-thinker forms begin tight, multi-thinker forms
begin plural, and populated narrative forms remain source-led unless the author says otherwise. Premise
suggestion reads the selected paradigm and POV preset, so it proposes material that can actually sustain
the intended access pattern rather than decorating a generic premise after the fact.
- **Identity inference resolves to the registry.** During generated Domain birth the model names exact ids from
the Character roster it creates; during corpus extraction the whole-work meta pass distinguishes private
narrative access from protagonist prominence, dialogue share, or mere presence. Both paths pass through one
deterministic resolver: stale ids are removed, observed Scene lenses are supporting evidence, and restricted
modes fall back through anchor Characters only when the evidence is incomplete. The persisted setting always
carries real Character ids, so later planning, prose, and Script generation enforce the same knowledge boundary.
The rule that keeps this from decaying: a clearer presentation is never a reason to leak. If the scaffold
under a seat would be incoherent without concealed material, the answer is a different scaffold for that
seat, not a peek.
---
## 7. The ledger
The local [VN experiment audit](../../sources/research/2026-09-02-2026-09-02-vn-experiment-audit.md)
demonstrates candidate moment/frame manifests and contextual CG sequencing outside the shipped scaffold.
Integration should reuse that evidence while extending `Script` and Stageplay's permanent seams; it must
not promote the lab compiler into a parallel canonical scaffold or renderer.
| Requirement | State | Where |
|---|---|---|
| Summary + typed deltas as canon | **shipped** | `Scene`, `WorldExpansion` deltas |
| Scaffold generated from brief + plan + prose | **shipped** | `generateSceneScript`, `SCRIPT_COMPOSE_SYSTEM` |
| Deltas present in the staging context | **shipped** | `<scene-brief>` (sceneContext, world-state stripped) |
| Point-in-time knowledge + ordered Beat/moment POV boundary | **shipped** | `deriveLogicRules`, BeatPlan, compose prompt, Script sanitizer |
| Scaffold versioned with branch isolation | **shipped** | `Scene.scriptVersions: ScriptVersion[]` |
| Frames · slots · facing · state · props · moments | **shipped** | `ScriptFrame` / `ScriptCastMember` / `ScriptMoment` |
| Focal scales (individual · group · stage) + cast rail | **shipped** | compose prompt + renderer |
| Sprite-over-plate composition at render time | **shipped** | entity `imageUrl` by aspect |
| Per-seat projection with concealment | **shipped** | `projectForSeat` (Scenario) |
| **Delta → frame → moment addressing** | **build** | new; the dependency for every derivation below |
| **CG as a moment-scoped asset class** | **build** | new; no home in the model today |
| **Derived CG placement at irreversible deltas** | **build** | new (pure) |
| **Derived expression / plate-variant selection** | **build** | new (pure) |
| **Reference-conditioned entity identity** | **build** | extends the image lane |
| **Branch-and-moment latent-model binding in the scaffold package** | **build** | pins the interpretation used for dialogue and expression |
| **Factory resolution of semantic expression needs to approved renditions** | **build** | extends the asset library without making assets canonical state |
| **POV-projected scaffold for a reading seat** | **build** | extends `projectForSeat` to the reader |
| **Orphaned-CG reporting on regeneration** | **build** | new |
---
## 8. Open questions
- When reader-seat projection ships, may one seat traverse several ordered Character lenses, or does a
published reading bind each lens sequence to a distinct seat?
- Does delta→moment addressing come from the composer (a model call that returns it) or from a pure pass
that matches declared deltas against staged moments? The pure pass is cheaper and auditable; the composer
knows more.
- May a scene carry more than one *published* scaffold at once — one per POV — or is a POV scaffold resolved
the way branch versions already are?
- When an entity image is regenerated, is prior art retained per branch (so an old route still looks like
itself) or replaced globally?
- Does a CG belong to the scene, the branch, or the route? Reuse across sister branches is either thrift or
a lie, depending on whether the moment is genuinely shared.
Open on GitHubRaw Markdown source