# Scene Score Schema

The Scene Score is the durable, portable contract at the heart of every Living
Scene: an ordered list of Segments plus a Cue Plan plus a Render Envelope, which
the Conductor runtime streams and from which a kept Living Offering is
deterministically re-rendered. It serves the runtime (the schema the Conductor
trusts), the BFF and queues (the schema that validates payloads crossing process
boundaries), and persistence (the shape that is stored and replayed). It is the
sibling contract to the
[Living Scenes — Concept and Customer Promise](./living-scenes-overview.md) page
and the input to
[Live Direction, Conductor Runtime, and Blend Kernel](./direction-conductor-blend.md).

The canonical schema is defined twice, on purpose, so both sides of every
process boundary validate against the same shape: the Zod contracts in
`libs/contracts/src/living-scene/score.ts`
(`@oshun/contracts/living-scene/score`) mirror the runtime TypeScript types in
`libs/yemaya/living-scenes-runtime/src/score/score-schema.ts`. The contract file
says so in its own header: "Mirror the runtime types … so cross-process
boundaries (BFF, queues, persistence) can validate Score payloads against the
same schema the runtime trusts." Everything below quotes the real symbol and
field names; where the older prose in `V1/features.md` described different
fields, this page corrects it and says what is actually there.

## The Score envelope

A `Score` (`ScoreSchema`) is the top-level object. Its fields:

| Field                  | Type / constraint                     | Meaning                                                                                   |
| ---------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------- |
| `scoreId`              | non-empty string                      | stable identity of the score                                                              |
| `template`             | non-empty string                      | the template/workflow-class this score instantiates (e.g. `tara-contemplative-arc`)       |
| `tenantId`             | non-empty string                      | owning tenant                                                                             |
| `authorUserId`         | non-empty string                      | author                                                                                    |
| `intent`               | `ScoreIntentLayer \| null` (optional) | the private-vs-public intent layer (see below) — lives on the **Score**, not on a Segment |
| `segments`             | `SegmentSpec[]`, min 1                | the ordered segment list                                                                  |
| `cuePlan`              | `CueSpec[]`                           | the accepted Live Direction record                                                        |
| `renderEnvelope`       | `RenderEnvelope`                      | the reproducibility envelope                                                              |
| `createdAtUnixSeconds` | positive int                          | creation timestamp                                                                        |

Note the important structural fact corrected from the older docs: **intent is a
property of the Score (`ScoreIntentLayer`), not of each Segment.** Transitions
are likewise _not_ stored on segments — they are decided at boundaries by the
Blend Kernel — and grounding, persona, and accessibility roles are not Segment
fields either. (See "What the older docs got wrong" below.)

## SegmentSpec — the real eight fields

Each Segment is a `SegmentSpec` (`score.ts:47-60`). The older `V1/features.md`
prose claimed a thirteen-field shape
(`{intent, public_redacted_intent, duration_band, style_anchor, motion_descriptor, audio_role, narration_script_pin, sophia_grounding_pin, lilith_tone_band, persona_binding, transition_in, transition_out, accessibility_role}`).
**None of those thirteen names exist verbatim.** The real schema is:

| Field               | Type / constraint                  | Meaning                                              |
| ------------------- | ---------------------------------- | ---------------------------------------------------- |
| `segmentId`         | string, 1–80 chars                 | unique within the score (enforced; duplicates throw) |
| `kind`              | `SegmentKind` enum                 | the segment's role (see enum below)                  |
| `displayName`       | string, 1–120 chars                | human label                                          |
| `durationSeconds`   | positive number, ≤ 3600            | segment length                                       |
| `tone`              | `ToneClass` enum                   | tonal register                                       |
| `workflowClassId`   | non-empty string                   | the Isis workflow class that generates it            |
| `parameters`        | `Record<string, number \| string>` | generation parameters                                |
| `inboundCarryState` | `CarryState \| null`               | the continuity handoff from the prior boundary       |

```jsonc
// A single SegmentSpec (illustrative shape from score.ts)
{
  "segmentId": "seg-02-moonrise",
  "kind": "sky-event-arc",
  "displayName": "Moon rises over the sea",
  "durationSeconds": 42,
  "tone": "wonder",
  "workflowClassId": "nyx-sky-briefing.v3",
  "parameters": { "azimuth": 118, "phase": "waxing-gibbous" },
  "inboundCarryState": null,
}
```

`SegmentKind` (`SegmentKindSchema`, `score.ts:15-25`) is one of nine values:
`opening`, `breath-anchor`, `visual-meditation`, `narration-bed`,
`sky-event-arc`, `lesson-visualizer`, `grounded-explainer`, `living-offering`,
`closing`.

`ToneClass` (`ToneClassSchema`) is one of `contemplative`, `gentle-instructive`,
`celebratory`, `reverent`, `reflective`, `wonder`.

## CarryState — the continuity handoff protocol

`CarryState` (`CarryStateSchema`, `score.ts:38-44`) is the concrete continuity
protocol carried across a segment boundary. The older docs gestured at the right
ideas ("CLIP-style style anchor, last-frame conditioning, motion-vector
descriptor, color-LUT alignment, audio tail") but never gave the field names or
constraints. They map almost one-to-one onto the real schema:

| Field                       | Constraint                          | Continuity role                                  |
| --------------------------- | ----------------------------------- | ------------------------------------------------ |
| `clipAnchorAssetId`         | non-empty string                    | style/clip anchor asset to seed the next segment |
| `lastFrameConditioningHash` | `/^[0-9a-f]{32,}$/` (≥32 hex chars) | last-frame conditioning fingerprint              |
| `motionDescriptor`          | string, 1–120 chars                 | motion-vector descriptor to continue motion      |
| `lutId`                     | non-empty string                    | color-LUT alignment between segments             |
| `audioTailDescriptor`       | string, 1–120 chars                 | audio tail to crossfade into the next bed        |

The Conductor propagates this as the outbound state of the leaving segment
becomes the inbound state of the next; see
[Live Direction, Conductor Runtime, and Blend Kernel](./direction-conductor-blend.md).

## The Cue Plan — accepted Live Direction

`cuePlan` is an array of `CueSpec` (`CueSpecSchema`, `score.ts:78-85`). Each cue
records an accepted Live Direction event:

| Field                | Constraint                         | Meaning                                                                                                           |
| -------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `cueId`              | string, 1–80 chars                 | cue identity                                                                                                      |
| `verb`               | `ScoreCueVerb` enum                | the constrained verb (see below)                                                                                  |
| `args`               | `Record<string, string \| number>` | verb arguments                                                                                                    |
| `appliesToSegmentId` | non-empty string (or `*`)          | target segment; `*` is the wildcard, others must resolve to a real segment id                                     |
| `channels`           | `CueChannel[]`, min 1              | source modality — `voice`, `tap`, and/or `text` (serialized as an array because a `Set` is not JSON-serializable) |

The cue verb vocabulary is a **closed set of nine verbs** (`ScoreCueVerbSchema`,
`score.ts:62-72`), mirrored in the runtime cue parser as `CUE_VERBS_VOCABULARY`
(`libs/yemaya/living-scenes-runtime/src/cues/cue-parser.ts`):

`linger`, `advance`, `morph`, `recall`, `shift-style`, `shape-by`,
`save-moment`, `abandon`, `kill`.

Free-text cues are parsed and constrained onto this vocabulary — a cue that does
not map to one of these verbs is not accepted. (Per-template subsets further
narrow which verbs a given template will honor; e.g. Veritas forbids `morph`.)
The full Live Direction behavior is in
[Live Direction, Conductor Runtime, and Blend Kernel](./direction-conductor-blend.md).

## Render Envelope — the reproducibility contract

`RenderEnvelope` (`RenderEnvelopeSchema`, `score.ts:91-107`) captures the inputs
that make a re-render reproducible. The older prose summarized this only as
"engine version + model hashes + locale/persona/seeds," but the real schema is a
tightly-typed resolution/frame-rate/GPU contract with **literal-union** value
constraints (so an out-of-range resolution or fps cannot even parse):

| Field                  | Constraint                                                         | Meaning                               |
| ---------------------- | ------------------------------------------------------------------ | ------------------------------------- |
| `envelopeId`           | non-empty string                                                   | envelope identity                     |
| `displayName`          | string, 1–120 chars                                                | human label                           |
| `widthPx`              | literal `1080 \| 1440 \| 1920 \| 2560 \| 3840`                     | render width                          |
| `heightPx`             | positive int ≤ 7680                                                | render height                         |
| `fps`                  | literal `24 \| 30 \| 60`                                           | frame rate                            |
| `maxBitrateKbps`       | positive int                                                       | encode ceiling                        |
| `gpuClass`             | `GpuClass` enum: `rtx-4090 \| a100-40gb \| a100-80gb \| h100-80gb` | the approved GPU class                |
| `minLookaheadSegments` | literal `2 \| 3 \| 4`                                              | how far the Conductor pre-warms ahead |

`minLookaheadSegments` is not a free integer — it is the literal union `2|3|4`,
and there is an additional **release-gate invariant** enforced in
`deepParseScore` (`score.ts:165-167`): a Render Envelope must require **≥ 2**
lookahead segments, or validation throws
`renderEnvelope must require ≥ 2 lookahead segments`. This is the contract-level
expression of the runtime's "always pre-warm at least the next segment" rule.

```jsonc
// A RenderEnvelope (illustrative shape from score.ts)
{
  "envelopeId": "env-contemplative-1080p30",
  "displayName": "Contemplative 1080p / 30fps / A100-40GB",
  "widthPx": 1080,
  "heightPx": 1920,
  "fps": 30,
  "maxBitrateKbps": 9000,
  "gpuClass": "a100-40gb",
  "minLookaheadSegments": 2,
}
```

The envelope is what makes a Living Offering portable: re-rendering a score
under the same envelope is intended to produce a pixel-equivalent video within
Isis-defined tolerance on any approved GPU node, and determinism is a release
gate — failing reproducibility blocks promotion of a workflow class to the
contemplative tier. (As noted on the overview page, actual frame-level
determinism is the downstream renderer's responsibility; the schema pins the
inputs that make it _checkable_.)

## The intent layer — private by default

`ScoreIntentLayer` (`ScoreIntentLayerSchema`, `score.ts:121-129`) is the
private-vs-public intent contract, attached to the Score (not per Segment):

| Field                    | Constraint                   | Meaning                                                                              |
| ------------------------ | ---------------------------- | ------------------------------------------------------------------------------------ |
| `intentId`               | string, 1–120 chars          | intent identity                                                                      |
| `privateText`            | string, 1–20,000 chars       | the user's real intent — **never** exposed off-device without elevated authorization |
| `publicRedactedText`     | string, 1–20,000 chars       | the only intent text exposed off-device or in operator inspection                    |
| `privateIntentHash`      | `/^[0-9a-f]{64}$/` (SHA-256) | hash binding the private text                                                        |
| `capturedAtUnixSeconds`  | positive int                 | capture time                                                                         |
| `redactionCategoriesHit` | `LilithRedactionCategory[]`  | which Lilith categories were redacted                                                |

`LilithRedactionCategory` (`LilithRedactionCategorySchema`) enumerates the
sensitive classes that drive redaction: `named-person`, `deceased`,
`medical-condition`, `abuse-disclosure`, `location-of-safety`, `minor-identity`,
`financial-account`, `legal-status`. The score is private by default; the
public-redacted intent field is the only intent text exposed off-device or in
operator inspection without elevated authorization. The cue-privacy and
redaction story is detailed in
[Scene Safety, Determinism, Provenance, and Cue Privacy](./scene-governance.md).

## Cross-field validation — `deepParseScore`

Schema parse alone is not enough; `deepParseScore` (`score.ts:151-169`) layers
three cross-field invariants on top of `ScoreSchema.parse`, mirroring the
in-runtime `validateScore` semantics so callers crossing process boundaries get
identical guarantees:

1. **Segment-id uniqueness** — a duplicate `segmentId` throws
   `duplicate segment id <id>`.
2. **Cue target resolution** — every `cue.appliesToSegmentId` that is not the
   wildcard `*` must resolve to a real segment, else
   `cue <id> targets unknown segment <id>`.
3. **Lookahead policy** — `renderEnvelope.minLookaheadSegments` must be ≥ 2.

Because the contract and the runtime mirror each other, the BFF, the queues, and
persistence all reject a malformed score the same way the Conductor would.

## Transitions are not stored on segments

The older `V1/features.md` text listed transition specs as if they were Segment
fields (`transition_in` / `transition_out`) and named only five typed
transitions (`latent-warm-start`, `optical-flow-morph`, `color-LUT-match`,
`audio-crossfade`, `narrative-pivot`). Both points are stale:

- Transitions are **decided at boundaries by the Blend Kernel**, not stored on
  segments. The score carries `CarryState` for continuity; the technique chosen
  at a boundary is a Blend Kernel / catalog concern.
- There are **nine** transition kinds, not five (`TRANSITION_KINDS`,
  `libs/yemaya/blend-kernel/src/transitions.ts:13-23`): the original five plus
  `motion-descriptor-handoff`, `motion-descriptor-reset`, `audio-level-jump`,
  and `variable-rate-sequencer`. Each has real parameter-range validation
  (`validateTransition`) — e.g. `latent-warm-start` `bridgeSteps ∈ [2, 12]`,
  `optical-flow-morph` `morphFrames ∈ [1, 48]` and `warpStrength ∈ [0, 1]`,
  `audio-crossfade` `fadeMs ∈ [50, 8000]` with a `linear | equal-power | log`
  curve, and `variable-rate-sequencer` rate window inside `[0.25, 4]`.

The transition catalog, technique allowlists, and the 12 cinematographic
techniques are documented in
[Composition Surface and Cinematographic Technique Catalog](./composition-cinematography.md).

## What the older docs got wrong (accuracy notes)

For readers cross-checking against the original `V1/features.md` "Scene Score
Schema" prose:

- **Segment fields** — the documented thirteen-field Segment shape does not
  exist; the real `SegmentSpec` has eight fields
  (`segmentId, kind, displayName, durationSeconds, tone, workflowClassId, parameters, inboundCarryState`).
  `intent` lives on the Score, not the Segment; transitions are not stored on
  segments; grounding/persona/accessibility are not Segment fields.
- **Transition list** — five named transitions is stale; the real
  `TRANSITION_KINDS` array has nine.
- **Render Envelope** — it is far richer than "engine version + model hashes":
  it pins resolution, fps, bitrate ceiling, GPU class, and the lookahead literal
  with a ≥2 release-gate invariant.

## Related

- [Living Scenes — Concept and Customer Promise](./living-scenes-overview.md) —
  what a Living Scene is and the real-vs-aspirational framing.
- [Live Direction, Conductor Runtime, and Blend Kernel](./direction-conductor-blend.md)
  — how the Conductor consumes a score and carries `CarryState`.
- [Composition Surface and Cinematographic Technique Catalog](./composition-cinematography.md)
  — the nine transition kinds and twelve techniques.
- [Domain Templates and the Scene Score Editor](./domain-templates-and-editor.md)
  — the templates referenced by `Score.template` and the editors that author
  scores.
- [Scene Safety, Determinism, Provenance, and Cue Privacy](./scene-governance.md)
  — the intent-layer privacy and determinism release gate.
- [Isis Generation Control](./isis-generation-control.md) — the workflow classes
  named by `workflowClassId` and the reproducibility tolerance.
- The features hub: [../features.md](../features.md).
