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 page and the input to Live Direction, Conductor Runtime, and Blend Kernel.
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 |
// 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.
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.
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.
// 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.
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:
- Segment-id uniqueness — a duplicate
segmentIdthrowsduplicate segment id <id>. - Cue target resolution — every
cue.appliesToSegmentIdthat is not the wildcard*must resolve to a real segment, elsecue <id> targets unknown segment <id>. - Lookahead policy —
renderEnvelope.minLookaheadSegmentsmust 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
CarryStatefor 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 plusmotion-descriptor-handoff,motion-descriptor-reset,audio-level-jump, andvariable-rate-sequencer. Each has real parameter-range validation (validateTransition) — e.g.latent-warm-startbridgeSteps ∈ [2, 12],optical-flow-morphmorphFrames ∈ [1, 48]andwarpStrength ∈ [0, 1],audio-crossfadefadeMs ∈ [50, 8000]with alinear | equal-power | logcurve, andvariable-rate-sequencerrate window inside[0.25, 4].
The transition catalog, technique allowlists, and the 12 cinematographic techniques are documented in Composition Surface and Cinematographic Technique Catalog.
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
SegmentSpechas eight fields (segmentId, kind, displayName, durationSeconds, tone, workflowClassId, parameters, inboundCarryState).intentlives 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_KINDSarray 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 — what a Living Scene is and the real-vs-aspirational framing.
- Live Direction, Conductor Runtime, and Blend Kernel
— how the Conductor consumes a score and carries
CarryState. - Composition Surface and Cinematographic Technique Catalog — the nine transition kinds and twelve techniques.
- Domain Templates and the Scene Score Editor
— the templates referenced by
Score.templateand the editors that author scores. - Scene Safety, Determinism, Provenance, and Cue Privacy — the intent-layer privacy and determinism release gate.
- Isis Generation Control — the workflow classes
named by
workflowClassIdand the reproducibility tolerance. - The features hub: ../features.md.