# Domain Templates and the Scene Score Editor

Living Scenes ship at V1 as five authored **domain templates** — one per
practicing domain (Tara, Nyx, Veritas, Metis, Arete) — plus the authoring
surfaces creators and operators use to build, rehearse, and promote new scores
from those templates. A template is not a loose theme: it is a real workflow
class with a machine ID, a locked cue-verb allowlist, a tone band, grounding and
intent requirements, and a per-fixture eval gate that blocks promotion. This
page sits in the living-scenes-core area alongside
[Scene Score Schema](./scene-score-schema.md),
[Live Direction, Conductor Runtime, and Blend Kernel](./direction-conductor-blend.md),
and
[Composition Surface and Cinematographic Technique Catalog](./composition-cinematography.md);
it is where the abstract Score contract meets concrete, named, tested domain
content.

> **What is real vs. modeled.** The template catalog, the per-template cue and
> technique allowlists, the tone-gating with crisis collapse, the breath-cycle
> pacing, the fixture eval gates, the compose-assist budgets, and the editor's
> pure decision helpers are all implemented and tested today. What is _modeled
> but not computed here_ is everything pixel- and frame-level: latent video
> generation, optical-flow warps, FVD computation, and GPU determinism. Those
> arrive as **score inputs** the pure functions consume — the runtime and blend
> kernel state plainly that "Actual GPU dispatch lives outside this module" and
> "Implementations of the actual DSP / shader code live downstream." This page
> keeps that line honest.

## Where the code lives

| Concern                                                           | Package / path                                                                                                                     |
| ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Template catalog, eval gates, Tara breath specs                   | `libs/isis/workflow-classes/src/living-scene/template-catalog.ts`, `tara.ts`                                                       |
| Score / Segment / RenderEnvelope / CarryState contracts           | `libs/contracts/src/living-scene/score.ts` (mirrored at `libs/yemaya/living-scenes-runtime/src/score/score-schema.ts`)             |
| Conductor state machine                                           | `@yemaya/living-scenes-runtime` (v0.1.0) — `src/conductor/conductor.ts`                                                            |
| Transitions, technique catalog, tone gating, compatibility scorer | `@yemaya/blend-kernel` (v0.1.0) — `src/transitions.ts`, `src/catalog/`, `src/compatibility/scorer.ts`                              |
| Compose Assist budgets + gold-set eval                            | `libs/yemaya/living-scenes-runtime/src/compose-assist/compose-assist.ts`                                                           |
| Scene telemetry events                                            | `libs/oshun/embodiment-psyche/src/events/scene-events.ts`                                                                          |
| AAA Score Editor (web + desktop)                                  | `apps/yemaya/studio-web/src/score-editor/ScoreEditorPage.tsx`, `apps/yemaya/studio-desktop/src/renderer/pages/ScoreEditorPage.tsx` |
| Lilith Studio Tara venue editor (contemplative web app)           | `apps/oshun/web/src/app/lilith-studio/scenes/TaraSceneEditor.tsx`, `/lilith-studio/scene/new`                                      |

The two runtime packages carry exactly the names the docs cite:
`@yemaya/living-scenes-runtime` and `@yemaya/blend-kernel`.

## Domain Templates

All five templates ship at V1 launch as workflow-class instances under the
`living-scene.*` family. Each template has its own grounding, persona, locale,
accessibility, and eval pipeline; none is descoped to V1.x. Templates are
versioned and promotable through Isis; the contemplative-product Studio Living
Scene card resolves the available templates from the user's tier and tenant
entitlements.

The catalog (`LIVING_SCENE_TEMPLATES` in `template-catalog.ts`) pins five
**canonical machine IDs** — these, not the friendly names, are what the runtime,
the allowlists, and the gates key off:

| Machine ID                   | Display name               | Allowed cue verbs                                               | `morphForbidden` | Reduced-motion required | Strictest Lilith tone | Sophia grounding | Intent capture |
| ---------------------------- | -------------------------- | --------------------------------------------------------------- | ---------------- | ----------------------- | --------------------- | ---------------- | -------------- |
| `tara-contemplative-arc`     | Tara Contemplative Arc     | linger, advance, save-moment, abandon, kill                     | no               | yes                     | yes                   | no               | no             |
| `nyx-sky-briefing`           | Nyx Sky Briefing           | linger, advance, morph, shift-style, save-moment, abandon, kill | no               | yes                     | no                    | yes              | no             |
| `veritas-grounded-explainer` | Veritas Grounded Explainer | linger, advance, recall, save-moment, abandon, kill             | **yes**          | no                      | no                    | yes              | no             |
| `metis-lesson-visualizer`    | Metis Lesson Visualizer    | linger, advance, recall, shape-by, save-moment, abandon, kill   | no               | yes                     | no                    | yes              | no             |
| `arete-living-offering`      | Arete Living Offering      | linger, advance, morph, shape-by, save-moment, abandon, kill    | no               | yes                     | yes                   | no               | **yes**        |

`templateAllowsVerb({ templateId, verb })` enforces this allowlist, and it
short-circuits `morph` whenever `morphForbidden` is set — which is why Veritas
can never accept a `morph` cue even though `morph` is a valid verb in the global
`ScoreCueVerbSchema`. The complete verb vocabulary is the nine-member
`ScoreCueVerbSchema` (`score.ts:62-72`): `linger`, `advance`, `morph`, `recall`,
`shift-style`, `shape-by`, `save-moment`, `abandon`, `kill`, mirrored verbatim
as `CUE_VERBS_VOCABULARY` in the cue parser so the parser and the contract can
never drift.

### Tara — Contemplative Arcs (`tara-contemplative-arc`)

- Five sub-variants ship at V1 (`TARA_SUBVARIANTS`): `loving-kindness`,
  `body-scan`, `breath-anchor`, `nature-immersion`, `gratitude`.
- Score templates pace visuals to a **breath-cycle clock**. Each sub-variant
  carries a concrete `BreathCycle` of
  `{inhaleSeconds, holdInSeconds, exhaleSeconds, holdOutSeconds}` plus a target
  arc length, a palette bias for the blend kernel, and a captions cadence:

  | Sub-variant      | Breath cycle (in / holdIn / ex / holdOut) | Arc minutes | Palette     | Captions WPM |
  | ---------------- | ----------------------------------------- | ----------- | ----------- | ------------ |
  | loving-kindness  | 4 / 1 / 6 / 1                             | 12          | dawn-rose   | 80           |
  | body-scan        | 4 / 0 / 8 / 0                             | 15          | stone-grey  | 70           |
  | breath-anchor    | 4 / 4 / 4 / 4 (box breathing)             | 8           | cool-sea    | 60           |
  | nature-immersion | 5 / 0 / 7 / 0                             | 18          | forest-moss | 75           |
  | gratitude        | 4 / 1 / 6 / 1                             | 10          | warm-amber  | 85           |

  `buildBreathPacedShape` sizes a score by dividing the arc seconds by the cycle
  seconds (`Math.floor(arcSeconds / secondsPerCycle)`), clamping the arc to
  `(0, 30]` minutes, so a user-configurable override still produces a whole
  number of breath cycles. A cue like `linger` extends the current segment
  without breaking continuity — useful when the user wants to dwell in a phase.

- Persona binding to approved contemplative voice profiles only; Lilith
  contemplative-tone band on the strictest setting (`strictestToneBand: true`);
  no Sophia source pin (`sophiaGroundingRequired: false`) because there are no
  empirical claims.
- The reduced-motion alternative ships in lockstep
  (`reducedMotionVariantRequired: true`). `assertReducedMotionLockstep` _fails
  loud_ with a `TaraVariantParityError` if a normal variant has no
  reduced-motion sibling, or if the two variants disagree on captions WPM —
  caption cadence must match across the pair, or promotion is blocked.

### Nyx — Sky Briefings (`nyx-sky-briefing`)

- Tonight-only sky briefing tied to the Nyx celestial-event clock; the score
  advances camera/visual focus across visible objects as the narrator names
  them, time-aligned to ±60 ms.
- Requires observer location (sensitive PII); explicit opt-in per session, with
  locale fallback to a regional-default location.
- Northern and Southern hemisphere parity at V1 launch; ephemeris computed from
  Skyfield-class libraries; constellation art from Yemaya-cleared rights or
  Isis-generated under licensed style anchors.
- Event coverage: planetary alignments, moon phases, meteor showers, ISS passes,
  aurora forecasts (where available), comets, conjunctions.
- Cue verbs include `linger` (stay on a constellation), `morph` (to a target sky
  region), `shift-style`, and `shape-by` (e.g. narrator pace). Grounding is
  required (`sophiaGroundingRequired: true`) because positions and timing are
  empirical, and its `groundingCorrectness` gate is set at a 0.92 pass rate.
- Caching: tonight's briefing materializes once per location-bucket per night;
  per-user personalization is on top of the cached base render. See
  [Domain — Nyx](./domain-nyx.md).

### Veritas — Grounded Explainers (`veritas-grounded-explainer`)

- Score template binds to a Sophia source pin set; visuals are mood/style
  anchors drawn from the topic hub, never empirical claims. Specific facts
  arrive via narration/text only, never via inferred imagery.
- The cue verb set **excludes `morph`** (`morphForbidden: true`), since
  free-form visual morph would risk visual claim drift; the available verbs are
  `linger`, `advance`, `recall`, `save-moment`, `abandon`, `kill`.
- The counterclaim, retraction, and source-update cascade reaches into kept
  Veritas Living Offerings: an artifact whose pinned source is later corrected
  surfaces a banner and offers re-render under the corrected source. The
  compatibility scorer reinforces this at the boundary level — a Veritas pair
  where _both_ segments cite only retracted Sophia source pins is declared
  `hard-incompatible` regardless of mechanical scores (see below).
- Eval fixtures include adversarial would-be-misleading cases (e.g., a cited
  claim about a 19th-century printing press where the visual could plausibly
  assert a date or location not in the source); the template must hold the line.
  Veritas carries the strictest gates of all five — golden 0.97, adversarial-cue
  0.99, grounding-correctness 0.97. See [Domain — Veritas](./domain-veritas.md)
  and [Sophia Grounding](./sophia-grounding.md).

### Metis — Lesson Visualizers (`metis-lesson-visualizer`)

- Score template binds to a lesson's knowledge-graph anchor set; the "what if
  X?" branch is constrained to what-ifs the lesson's KG supports, with graceful
  fallback ("let's see — that'll need a different lesson") when the cue exits
  the supported manifold.
- **Themis academic-integrity interlock**: the tone-gating module blocks _every_
  technique on any segment whose KG node is in an in-progress assessment set.
  When `templateId === 'metis-lesson-visualizer'`, `metisSegmentNodeId` is set,
  and that node is in `metisAssessmentLockedNodes`, `assertTechniqueAllowed`
  throws `GatingError('themis-assessment-locked')` — the lesson visualizer
  simply cannot run on an assessment surface.
- Standards-aligned for institutional delivery: tenant-specific standards
  bindings (Common Core, IB, NGSS, etc.) are honored where the tenant's
  curriculum metadata is wired.
- Persona binding to approved teacher personas; Lilith teacher-persona safety
  policy applies (no diagnostic statements, no treatment plans). Grounding is
  required, with a `groundingCorrectness` gate at 0.95.
- Its cue verb set is among the most expressive: `linger`, `advance`, `recall`,
  `shape-by` (e.g. student pace), `save-moment`, `abandon`, `kill`. See
  [Domain — Metis](./domain-metis.md).

### Arete — Living Offerings (`arete-living-offering`)

- The most personal template; the user states an intention (voice/text), and the
  score generates a contemplative arc tuned to it. Default duration: 4–8
  minutes; user-configurable within Arete's pace bands. This is the one template
  with `intentCaptureRequired: true`.
- The intent is captured on the **Score**, not on a segment, in the
  `ScoreIntentLayer` (`score.ts:121-129`): `intentId`, `privateText`,
  `publicRedactedText`, `privateIntentHash` (64-hex), `capturedAtUnixSeconds`,
  and `redactionCategoriesHit`. The private intent is kept for the originating
  user only; the public-redacted intent (a Lilith-redacted version) is the only
  intent text ever exposed in shared artifacts or in operator inspection without
  elevated authorization. The redaction categories themselves are an enum:
  `named-person`, `deceased`, `medical-condition`, `abuse-disclosure`,
  `location-of-safety`, `minor-identity`, `financial-account`, `legal-status`.
- Strongest crisis-signal pre-screen on the intent text; an intent that triggers
  a crisis frame produces no offering and routes to the existing Lilith crisis
  pathway.
- Reusable by the user: re-engaging the same offering produces a deterministic
  re-render unless the user requests a `branch from this moment` (which forks a
  new score with the same intent and a new seed).
- Sharing is opt-in per artifact, governed by the Shareability Matrix; a shared
  Living Offering carries only the public-redacted intent, never the private
  intent. See [Domain — Arete](./domain-arete.md) and
  [Keep, Share, Shareability, Takedown, and Lineage](./scene-sharing-distribution.md).
- Arete is the only template whose technique allowlist spans the full catalog,
  including the three operator-curated high-energy cuts; those three are gated
  separately (see Tone gating below).

## The Segment is leaner than it reads — the real schema

Earlier drafts of this material described a thirteen-field segment
(`{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 fields exist verbatim.** The real `SegmentSpecSchema`
(`score.ts:47-60`) is:

```jsonc
{
  "segmentId": "string (1..80)",
  "kind": "opening | breath-anchor | visual-meditation | narration-bed | sky-event-arc | lesson-visualizer | grounded-explainer | living-offering | closing",
  "displayName": "string (1..120)",
  "durationSeconds": "number, (0, 3600]",
  "tone": "contemplative | gentle-instructive | celebratory | reverent | reflective | wonder",
  "workflowClassId": "string",
  "parameters": "Record<string, number | string>",
  "inboundCarryState": "CarryState | null",
}
```

Several facts follow from this and matter for authoring:

- **Intent lives on the Score, not the Segment** — `ScoreIntentLayer`, above.
- **Transitions are not stored on segments.** A segment has no `transition_in` /
  `transition_out`; transitions are computed at the _boundary_ between two
  adjacent segments by the blend kernel and recorded by Compose Assist as
  `techniquesByBoundary` (a boundary id → technique id map).
- **Grounding, persona, and accessibility are not Segment fields.** They are
  template-level requirements (the catalog flags above), persona/voice bindings,
  and accessibility variants enforced elsewhere — not columns on the segment.
- Free-form per-segment knobs live in the open `parameters` record, constrained
  by the segment's `workflowClassId`. The full schema, including `CueSpec` and
  the deep-validation invariants, is documented in
  [Scene Score Schema](./scene-score-schema.md).

### The RenderEnvelope is richer than "engine version + model hashes"

`RenderEnvelopeSchema` (`score.ts:91-107`) pins concrete, _literal-union_
production parameters so a kept artifact re-renders identically:

| Field                       | Type / allowed values                             |
| --------------------------- | ------------------------------------------------- |
| `envelopeId`, `displayName` | strings                                           |
| `widthPx`                   | literal `1080 \| 1440 \| 1920 \| 2560 \| 3840`    |
| `heightPx`                  | positive int ≤ 7680                               |
| `fps`                       | literal `24 \| 30 \| 60`                          |
| `maxBitrateKbps`            | positive int                                      |
| `gpuClass`                  | `rtx-4090 \| a100-40gb \| a100-80gb \| h100-80gb` |
| `minLookaheadSegments`      | literal `2 \| 3 \| 4`                             |

`deepParseScore` enforces a hard **≥ 2 lookahead** release-gate invariant on top
of the literal union (`score.ts:165-167`): even though the type already excludes
1, the deep parse throws `renderEnvelope must require ≥ 2 lookahead segments` so
that cross-process payloads (BFF, queues, persistence) cannot smuggle a
single-segment lookahead past the schema. The same function deduplicates segment
ids and rejects any cue that targets an unknown segment (a cue may target the
wildcard `*`).

### CarryState — the boundary handoff protocol

The "CLIP-style style anchor, last-frame conditioning, motion-vector descriptor,
color-LUT alignment, audio tail" prose maps almost one-to-one onto a real
schema, `CarryStateSchema` (`score.ts:38-44`), which the docs previously never
named:

| Field                       | Constraint                | Carries                                  |
| --------------------------- | ------------------------- | ---------------------------------------- |
| `clipAnchorAssetId`         | non-empty                 | the style anchor asset                   |
| `lastFrameConditioningHash` | regex `/^[0-9a-f]{32,}$/` | last-frame conditioning (≥ 32 hex chars) |
| `motionDescriptor`          | ≤ 120 chars               | motion-vector descriptor                 |
| `lutId`                     | non-empty                 | color-LUT alignment                      |
| `audioTailDescriptor`       | ≤ 120 chars               | the audio tail handed to the crossfade   |

This is the contract the Conductor propagates across boundaries (next section)
and the [Latent-carry inspector](#authoring-the-scene-score-editor) visualizes.

## How a template's content reaches frames

Templates do not render pixels. They produce decisions that the Conductor, the
Blend Kernel, and the compatibility scorer consume. The full runtime is covered
in
[Live Direction, Conductor Runtime, and Blend Kernel](./direction-conductor-blend.md);
here is the slice that template authors must understand.

### The Conductor state machine (modelled, deterministic)

`@yemaya/living-scenes-runtime`'s `conductor.ts` is a pure segment-slot state
machine. Each slot moves through a `SegmentRenderState`:

```
pending → pre-warming → ready → streaming → done
                                   (or → aborted)
```

- `initialiseConductor({ score })` builds one `SegmentSlot` per segment, all
  `pending`, playhead at index 0, backpressure off, seeding each slot's
  `carryStateInbound` from the segment's `inboundCarryState`.
- `planPreWarm` marks every `pending` slot in the window
  `[playheadIdx + 1, playheadIdx + minLookaheadSegments]` as `pre-warming`. This
  is where `minLookaheadSegments` earns its ≥ 2 invariant — there is always at
  least one slot warming ahead of the playhead.
- `setBackpressure(active: true)` **truncates the lookahead to 1**: when
  downstream is slower than realtime, the conductor only pre-warms the immediate
  next slot, shedding speculative work rather than piling up.
- `completePreWarm` flips a `pre-warming` slot to `ready` (and fails loud with a
  `ConductorError('invalid-transition')` if the slot wasn't pre-warming).
- `advancePlayhead({ carryStateOutbound })` marks the current slot `done`,
  writes its `carryStateOutbound`, and promotes the next slot to `streaming` —
  **carrying the outbound carry-state forward** as the next slot's inbound state
  (`carryStateInbound: input.carryStateOutbound ?? next.carryStateInbound`). It
  refuses to advance into a slot that isn't `ready`/`streaming`.
- `planReconnect({ atSegmentId })` answers the resumability question after a
  client drops: it returns `{ resumable, resumeCarryState, reason }`, resumable
  only when the slot is `streaming` or `ready`, handing back the inbound
  carry-state the client needs to rejoin mid-segment.

Pure logic throughout — the module's own header is explicit: "the conductor
models scheduling decisions in deterministic state. Actual GPU dispatch lives
outside this module."

### Transitions and the technique catalog

The blend kernel owns nine **transition kinds** — the low-level DSP/shader
primitives (`TRANSITION_KINDS`, `transitions.ts:13-23`): `latent-warm-start`,
`optical-flow-morph`, `color-lut-match`, `audio-crossfade`, `narrative-pivot`,
`motion-descriptor-handoff`, `motion-descriptor-reset`, `audio-level-jump`,
`variable-rate-sequencer`. (Earlier prose listed only the first five; the
catalog has nine.) Each has real, specific `validateTransition` parameter ranges
that fail loud with a `TransitionValidationError` when violated:

| Transition                  | Validated ranges                                                    |
| --------------------------- | ------------------------------------------------------------------- |
| `latent-warm-start`         | `bridgeSteps` ∈ [2, 12]; `cfgScale` (nullable) ∈ [0, 30]            |
| `optical-flow-morph`        | `morphFrames` ∈ [1, 48]; `warpStrength` ∈ [0, 1]                    |
| `color-lut-match`           | non-empty source/target LUT ids; `matchFrames` ∈ [1, 240]           |
| `audio-crossfade`           | `fadeMs` ∈ [50, 8000]; `curve` ∈ {linear, equal-power, log}         |
| `narrative-pivot`           | non-empty `pivotPromptId`                                           |
| `motion-descriptor-handoff` | non-empty descriptor ids; `blendFrames` ∈ [1, 120]                  |
| `motion-descriptor-reset`   | `resetStrategy` ∈ {clean-break, new-anchor}; `holdFrames` ∈ [0, 48] |
| `audio-level-jump`          | `deltaDb` ∈ [−18, 18]; `recoveryMs` ∈ [0, 3000]                     |
| `variable-rate-sequencer`   | non-empty `beatGridId`; `0.25 ≤ minRate ≤ maxRate ≤ 4`              |

Above those primitives sit the twelve **cinematographic techniques** authors and
customers actually pick (`TECHNIQUE_IDS`, `cinematographic-catalog.ts:16-29`):
`hard-cut`, `match-cut`, `l-cut`, `j-cut`, `dissolve`, `smash-cut`, `whip-pan`,
`jump-cut`, `match-action`, `cross-cut`, `montage`, `fade-to-black`. Each entry
binds a `kernelComposition` of transition kinds, an overlap-window range, tone
bands, a per-template allowlist, an accessibility fallback, a `provenanceTag`
matching `technique:[a-z0-9-]+`, and **concrete `evalThresholds`** — not just
qualitative descriptions. A few illustrative numbers from the real catalog:

| Technique       | `maxFvd` | `maxFlicker` | `maxColorJump` | `minNarrationAlignment` | Reduced-motion fallback |
| --------------- | -------- | ------------ | -------------- | ----------------------- | ----------------------- |
| `dissolve`      | 0.3      | 0.03         | 0.08           | 0.90                    | dissolve                |
| `match-cut`     | 0.3      | 0.04         | 0.10           | 0.92                    | dissolve                |
| `smash-cut`     | 0.5      | 0.06         | 0.18           | 0.85                    | hard-cut                |
| `whip-pan`      | 0.5      | 0.05         | 0.18           | 0.85                    | fade-to-black           |
| `fade-to-black` | 0.3      | 0.03         | 0.10           | 0.92                    | fade-to-black           |

`fade-to-black` is the universal fallback: the only technique allowed in every
tone band, and the one reached for when a clean recovery is needed. These
thresholds are _gate inputs_: the blend kernel's `isBlendContinuityAcceptable`
compares a downstream-supplied `ContinuityScorecard` (`fvd`, `flickerScore`,
`colorJump`, `motionVectorContinuity`, `audioRmsDeltaDb`) against the thresholds
and returns a pass/fail with reasons. The FVD and flicker numbers themselves are
_computed downstream_, not by this module — this is the honest line between the
deterministic gate and the modeled perceptual metrics. The full technique table
and overlap windows live in
[Composition Surface and Cinematographic Technique Catalog](./composition-cinematography.md).

### Compatibility scoring at a boundary

`scoreSegmentPair` (`scorer.ts`) returns a verdict for any adjacent pair across
seven dimensions: `style-anchor`, `motion-descriptor`, `audio-role`,
`narration`, `tone-band`, `grounding`, `persona`; the `composite` is the plain
mean of the seven. Two honesty notes about the _style-anchor_ and
_motion-descriptor_ dimensions: the doc once described "CLIP-embedding distance
between the outgoing and incoming anchors," but the implemented scorer uses
**exact-ID equality**, not an embedding distance — `styleScore` returns `1` when
`styleAnchorId` matches and `0.35` otherwise (`scorer.ts:75-77`); `motionScore`
likewise returns `1` vs `0.5` on exact descriptor equality. Grounding overlap
_is_ a real set metric — a **Dice coefficient** over the two segments' Sophia
source-pin hashes. A CLIP-style embedding metric is modeled as a possible future
input, not computed in this pure module.

Before any mechanical scoring, a **hard-incompatible policy gate** runs and can
veto a pair outright:

- crisis ↔ non-crisis pair _without_ a `fade-to-black` recovery transition;
- pedagogical ↔ entertainment intra-Score pairing;
- a Tara contemplative arc paired with a forbidden high-energy technique
  (`smash-cut`, `whip-pan`, `jump-cut`);
- a Veritas pair where both segments cite _only_ mutually-retracted Sophia pins.

## Tone gating, allowlists, and crisis collapse

`assertTechniqueAllowed` (`tone-gating.ts`) is the enforcement point that runs
in three places — the customer technique picker, Compose Assist candidate
generation, and score promotion (`ENFORCEMENT_POINTS`). Its precedence order
matters:

1. **Crisis-frame collapse takes absolute precedence.** Regardless of template,
   within one frame of crisis-frame activation the technique allowlist collapses
   to the safe trio
   `CRISIS_COLLAPSE_ALLOWLIST = {hard-cut, dissolve, fade-to-black}`. Anything
   else throws `GatingError('crisis-frame-collapse')`.
2. **Themis interlock for Metis** — every technique blocked on in-progress
   assessment segments (above).
3. **Per-template allowlist** — the technique must be in
   `technique.templateAllowlist`.
4. **Arete operator-curated cuts.**
   `OPERATOR_CURATED_ARETE_TECHNIQUES = {smash-cut, whip-pan, jump-cut}` are
   never allowed under Lilith's strictest tone band
   (`GatingError('strictest-lilith-tone-block')`) and otherwise require an
   explicit `operatorCuratedTechniqueGrant`
   (`GatingError('operator-curated-technique-required')`).

> **Two allowlists, kept in sync.** There are intentionally two technique
> allowlists in code: the per-technique `templateAllowlist` in
> `cinematographic-catalog.ts`, and the per-template
> `TEMPLATE_TECHNIQUE_ALLOWLISTS` map in `compose-assist.ts`. They currently
> **agree for all five templates** (e.g. Tara allows hard-cut, match-cut, l-cut,
> j-cut, dissolve, match-action, fade-to-black; Arete spans the full twelve),
> but they are a duplicated source of truth that can drift, so any new technique
> must be added in both places. The docs describe a single conceptual allowlist;
> the code has two enforcement copies.

## Compose Assist — budgets and gold-set eval

Compose Assist runs as an Isis `AgentRun` with budget caps, tool grants, a cost
ledger, and operator audit (per §18). Its request carries an `AgentRunBudget`
(`maxTokens`, `maxCostCents`, `maxLatencyMs`) and `AgentRunGrants` (`toolIds`,
`allowEgress`), a `tier`
(`contemplative | curated-creator | aaa-creator | operator-admin`), the
`templateId`, `locale`, and a `priorSegmentSummary`. The output is a candidate
segment plus ranked techniques with confidence bands, a rationale, and a
`continuityPreScore`; a soft-refusal fires when no viable suggestion exists.

**Tier caps** (`TIER_CAPS`, `compose-assist.ts:447-452`):

| Tier | Segment cap | Duration cap | Routing                                        |
| ---- | ----------- | ------------ | ---------------------------------------------- |
| free | 6 segments  | 90 seconds   | inline compose surface                         |
| paid | 24 segments | 8 minutes    | inline compose surface                         |
| aaa  | —           | —            | `routesToStudioEditor: true` (full AAA editor) |

A draft can only be **promoted** to a score when its `continuityPreScore`
(overall and per boundary) is **≥ 0.85** — both `validateCompositionDraft` and
the per-boundary `assertPromotableBoundary` throw
`ComposeAssistError('continuity-pre-score-out-of-range')` below that threshold.
They also check that each boundary connects adjacent segment cards and clears
the workflow-class policy gate. Promotion produces a `PromotedCompositionScore`
with a `segmentOrder`, a `techniquesByBoundary` map, and a parallel
`reducedMotionTechniquesByBoundary` map so the reduced-motion variant is fixed
at promotion time, not improvised at playback.

The gold-set harness `evaluateGoldSet` returns a real
`GoldSetEvaluation = {precision, recall, refusalRate, correctRefusals, falseRefusals}`
over labeled items (each marked `expectsSuggestion` or not), and a
champion/challenger comparison gates promotion of the assistant model itself
against `minPrecision` / `minRecall` floors. See
[Agent Invocation, Budgets, Memory, and Feedback Loops](./agentic-invocation-budgets-grants.md).

## Template eval gates

Before any template version can be promoted to production, its fixture set must
clear the per-template gate in `TEMPLATE_EVAL_GATES`
(`template-catalog.ts:147-199`). `evaluateFixtureSet` reports
`{ pass, failures }`, listing every threshold a `FixtureSetReport` misses. **All
five templates require a minimum of 30 fixtures**; the pass-rate thresholds are
tuned per template:

| Template                     | min fixtures | golden | adversarial-cue | crisis-trigger | locale-parity | a11y-parity | grounding-correctness |
| ---------------------------- | ------------ | ------ | --------------- | -------------- | ------------- | ----------- | --------------------- |
| `tara-contemplative-arc`     | 30           | 0.95   | 0.98            | **1.00**       | 0.90          | 0.95        | n/a                   |
| `nyx-sky-briefing`           | 30           | 0.95   | 0.97            | **1.00**       | 0.90          | 0.95        | 0.92                  |
| `veritas-grounded-explainer` | 30           | 0.97   | 0.99            | **1.00**       | 0.90          | 0.97        | 0.97                  |
| `metis-lesson-visualizer`    | 30           | 0.95   | 0.97            | **1.00**       | 0.90          | 0.97        | 0.95                  |
| `arete-living-offering`      | 30           | 0.95   | 0.99            | **1.00**       | 0.90          | 0.95        | n/a                   |

The **crisis-trigger gate is 1.0 for every template** — there is zero tolerance
for a crisis-signal fixture that fails to route correctly. Templates without
empirical claims (Tara, Arete) carry no `groundingCorrectness` gate (`null`);
the grounded templates (Nyx, Veritas, Metis) require it, and
`evaluateFixtureSet` fails loud if a grounding gate is configured but no
grounding pass-rate was reported. More on the eval philosophy in
[Latency, Accessibility, Eval Sets, and Tests](./scene-quality-and-eval.md).

## Scene telemetry — the Live Direction Channel

Every authored scene emits a uniform, traceable event feed on the Psyche
envelope. `scene-events.ts` defines eight `SCENE_EVENT_KINDS`:
`scene.segment-start`, `scene.segment-end`, `scene.transition-start`,
`scene.transition-end`, `scene.live-direction-applied`,
`scene.policy-intervention`, `scene.crisis-frame`, `scene.fallback-engaged`.
Each event extends a `SceneEventEnvelope` carrying `eventId`, `sessionId`,
`sequence`, `emittedAtUnixMillis`, `traceId`, `parentSpanId`, and `spanId`.
`verifyEventStream` enforces a **monotonic, gap-free sequence** (`sequence`
strictly `lastSequence + 1`) and a **single trace-id** for the whole session —
any trace-id change mid-stream or any sequence gap throws a `SceneEventError`.
This is the real telemetry surface the contemplative viewer and the Score
Editor's replay scrubber both consume; see
[Psyche Real-Time Runtime](./psyche-realtime-runtime.md).

## Authoring (the Scene Score Editor)

### AAA Scene Score Editor — Yemaya Studio

The primary Scene Score Editor lives in Yemaya Studio
(`apps/yemaya/studio-{web,desktop}`) and is gated to the AAA-creator tier and to
operators. It is built from the runtime's pure decision helpers — the editor UI
calls real functions, not mocks: `runFixtureRehearsal`, `getReplayEventAtFrame`,
`getTransitionFindings`, `isTransitionGatePassed`, `createPromotedVersion`, and
`updateSegmentParameter` (imported into `ScoreEditorPage.tsx`).

- **Timeline view**: ordered Segment cards (`SegmentCard`) with duration bands,
  transition pickers between cards (`TransitionPicker`), narration-script
  overlay, motion-descriptor strip, narrator-cadence overlay, audio-bus tracks.
- **Segment inspector** (`SegmentInspector`): editable fields from the score
  schema, with workflow-class-locked parameter ranges
  (`updateSegmentParameter(segment, range.key, value)`), persona/voice picker
  (allowlist), Lilith tone-band selector, Sophia source pin selector,
  accessibility-role selector.
- **Transition inspector** (`TransitionInspector`): typed transition with
  overlap window slider, preview against the fixture set, an eval scorecard
  surfaced via `getTransitionFindings`, and manual override blocked when
  `isTransitionGatePassed` is false — the editor will not let an author save a
  boundary below the release-gate threshold.
- **Latent-carry inspector**: visualizes the `CarryState` handoff across each
  boundary; surfaces violations of the carry-state contract.
- **Fixture-rehearsal harness** (`runFixtureRehearsal(score)`): runs the score
  against the workflow class's fixture set; reports per-fixture eval scores;
  promotion to the contemplative tier is blocked until all fixtures pass the
  thresholds in the table above.
- **Replay scrubber** (`getReplayEventAtFrame(score, frame)`): scrubs a
  previously rendered session frame-by-frame with per-event annotations (cue
  applied, transition fired, policy intervention, fallback engaged) drawn from
  the scene event feed.
- **Score versioning** (`createPromotedVersion`): every save creates a new score
  version; promotion through Isis environments (dev → staging → prod) is gated
  through workflow-class release gates and Lilith policy review.
- **Score templates library**: the editor seeds new scores from approved
  templates (the five domain templates above plus any tenant-specific templates)
  with locked policy bindings.

### A second editor exists on the contemplative product

The AAA editor is _not_ the only authoring surface. A distinct **Lilith Studio**
Tara venue editor ships on the contemplative Oshun web app at
`apps/oshun/web/src/app/lilith-studio/scenes/TaraSceneEditor.tsx`, with a
new-scene flow at `/lilith-studio/scene/new`. It is a venue/staging editor for
Tara contemplative sessions — exporting concrete authoring data such as
`TARA_SCENE_EDITOR_ZONES`, `TARA_SCENE_EDITOR_LIGHTING`,
`TARA_SCENE_EDITOR_AUDIO`, and accessibility profiles
(`TARA_SCENE_MOTION_PROFILES` with reduced-motion remediation,
`TARA_SCENE_CAPTION_PROFILES`). So the earlier claim that the Scene Score Editor
"does not appear on the contemplative product under any entitlement" is only
partly accurate: the _full AAA Score Editor_ is Studio-only, but a Lilith-Studio
scene authoring surface does live on the contemplative product. See
[Creator Roles and the Authoring Workspace](./authoring-workspace-and-roles.md)
and [Lilith Persona Policy](./lilith-persona-policy.md).

## Customer-facing surfaces and the BFF

Customers see authored scenes through the public scene viewer
(`apps/oshun/web/src/app/scene/[id]/page.tsx`, plus `/scene/[id]/embed` and
`/scene/[id]/report`) and compose new ones through the inline compose surfaces
(`apps/oshun/web/src/app/studio/compose/ComposeClient.tsx` and the mobile
`apps/oshun/mobile/app/compose.tsx`). All of these talk to the Living Scenes BFF
route (`apps/oshun/bff/src/routes/living-scenes.ts`, ~1,871 lines), which owns
the real keep / share / idempotency logic and is covered by end-to-end tests
(`scene-viewer-deepening.spec.ts`, `living-scene-card.spec.ts`,
`public-scene-abuse-report.spec.ts`). The surface-by-tier boundaries are
detailed in [Product Surfaces](./product-surfaces.md) and
[Generation Audience Tiers and Surface Boundaries](./generation-tiers-and-surfaces.md).

## Related

- [Scene Score Schema](./scene-score-schema.md)
- [Live Direction, Conductor Runtime, and Blend Kernel](./direction-conductor-blend.md)
- [Composition Surface and Cinematographic Technique Catalog](./composition-cinematography.md)
- [Living Scenes — Concept and Customer Promise](./living-scenes-overview.md)
- [Scene Safety, Determinism, Provenance, and Cue Privacy](./scene-governance.md)
- [Latency, Accessibility, Eval Sets, and Tests](./scene-quality-and-eval.md)
- [Creator Roles and the Authoring Workspace](./authoring-workspace-and-roles.md)
- Domains: [Tara](./domain-tara.md) · [Nyx](./domain-nyx.md) ·
  [Veritas](./domain-veritas.md) · [Metis](./domain-metis.md) ·
  [Arete](./domain-arete.md)
- Hub: [../features.md](../features.md)
