feat(scenario): implement the phase 6 scenario director

Add Format Specification section 21 (Scenario Model 0.1) and the runtime
that realizes it: the scenario director, seven trigger classes, timelines
with repeats and branches, admission and concurrency control, nested
ownership with bounded cleanup, and the production GC5 resource counters.

Exhibit E provides a reproducible forty-minute long-scenario reference and
scenario-challenge.xzbt covers the PRD 131 cases. A standalone
acceptance/soak page is built by tools/build-scenario-acceptance.mjs.

252 automated checks pass. The two-hour real-duration development soak
required by the GC6 schedule remains pending.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01M7dgfQ12mpM4JjSMv3inLA
This commit is contained in:
2026-09-06 22:54:31 +00:00
co-authored by Claude Opus 5
parent c4332363a9
commit 1cde2f9f68
29 changed files with 15553 additions and 54 deletions
+193 -9
View File
@@ -1,8 +1,8 @@
# XZBT Format Specification 0.1
**XZBT format version:** 0.1
**Document revision:** 0.9
**Status:** Normative specification for shared contracts (document, types, ValueSpec, ConditionSpec, resolution, bindings, and transitions); the Audio subsystem contract is complete (Phase 3a sources/control sources, Phase 3b processing/routing/components/buses, Phase 3c automation/lifecycle/protection); master-protection values remain provisional pending GC6 measurement; the Visual subsystem contract is complete (Phase 4a scene/primitives/transforms/appearance in section 17, Phase 4b components/procedural systems/behaviors/fields in section 18, and Phase 4c automation/lifecycle/camera/post-effects/ceilings in section 19); the aggregate visual ceilings of 19.5 remain provisional pending the slice 4h GC6 measurement; the Cadence and Event Subsystems contract is complete in section 20 (Phase 5); and the remaining subsystem contracts are in progress
**Document revision:** 0.10
**Status:** Normative specification for shared contracts (document, types, ValueSpec, ConditionSpec, resolution, bindings, and transitions); the Audio subsystem contract is complete (Phase 3a sources/control sources, Phase 3b processing/routing/components/buses, Phase 3c automation/lifecycle/protection); master-protection values remain provisional pending GC6 measurement; the Visual subsystem contract is complete (Phase 4a scene/primitives/transforms/appearance in section 17, Phase 4b components/procedural systems/behaviors/fields in section 18, and Phase 4c automation/lifecycle/camera/post-effects/ceilings in section 19); the aggregate visual ceilings of 19.5 remain provisional pending the slice 4h GC6 measurement; the Cadence and Event Subsystems contract is complete in section 20 (Phase 5); Scenario Model 0.1 is complete in section 21 (Phase 6); UI and library-hardening contracts remain in progress
**Related resources:** [PRD](../XZBT_0-1_MVP_Product_Requirements_Document.md), [decisions](XZBT_0-1_Gap_Closure_Decisions.md), [verification](XZBT_0-1_Verification_Gates.md)
This document defines normative syntax and runtime semantics for XZBT 0.1 exhibits. An exhibit is a UTF-8 JSON document that configures generic procedural visual, audio, cadence, and orchestration primitives. It does not contain executable JavaScript.
@@ -535,7 +535,7 @@ Complete shared contracts before implementing dependent subsystems. Use PRD sect
| Audio | 34-60 | Complete recipe/component shapes, node defaults, automation timing, clamp implementation, one-shot endings and tails, unlock behavior, master protection contract | **Complete (Rev 0.4 / Phase 3a-3c):** units, graph objects, all sixteen node types, routing, modulation, graph legality, authoring limits, components, sounds/recipes, buses, automation tracks and precedence, lifecycle states and release, determinable one-shot endings, voice ceilings, unlock and pause behavior, and the master-protection contract shape. Master-protection *values* (peak ceiling, tolerance, release behavior) are provisional pending GC6 measurement |
| Cadence | 61-68 | Exact selection/cooldown/overlap fields, clocks, pool collisions, pending audio, manual sampling isolation and continuous-sample termination | **Complete (Rev 0.9 / Phase 5):** section 20 fixes cadence classes, clocks, interval ranges, selection algorithm, cooldown, overlap policy, anti-repetition relaxation, minimum automatic gap, ambient maintenance, and manual SAMPLE PRNG stream isolation |
| Visuals | 69-89 | Primitive/system/behavior fields, angle and motion units, transform order, depth projection, field behavior, morph compatibility, effect approximation and limits | **Complete (Rev 0.7 / Phase 4a-4c):** section 17 fixes the pipeline, canonical units and the angle convention, the `visuals` container, layers, the scene model and coordinate/fit modes, depth sign and sorting, the fourteen primitives, common properties, transform composition order, appearance and the safe blend set, paths and splines, and the once-at-instantiation resolution boundary. Section 18 fixes visual components and their `inputs` scope, particle systems and their normative integrator, the nine placement distributions, emitters and exact emission timing, repeaters and the `repeat.*` scope, the seventeen-behavior vocabulary and its channel set, the six procedural fields and their normative coherent-noise function, and trails, ribbons, and links. Section 19 fixes visual automation and its two declaration scopes and loop modes, the four visual rows it adds to the section 8.1 table and nothing beyond them, the persistent and spawned system lifecycle and its ownership, the camera matrix and both projection modes, the seven post-effects and the boundary of permitted approximation, and the centralized runtime ceilings for the whole engine. Aggregate ceiling *values* are provisional pending the slice 4h GC6 measurement |
| Events/scenarios | 90-102 | Shared ownership, hooks/failure ordering, condition rearming, deferred ordering/expiry, dispatch limits; full trigger/timeline shapes remain for Phase 6 | **Shared lifecycle contract complete (Rev 0.3 / GC5); Event Model 0.1 complete (Rev 0.9 / Phase 5 in section 20); scenario triggers/timelines Phase 6** |
| Events/scenarios | 90-102 | Shared ownership, hooks/failure ordering, condition rearming, deferred ordering/expiry, dispatch limits; complete trigger/timeline shapes | **Shared lifecycle contract complete (GC5); Event Model in section 20; Scenario Model in section 21 (Revision 0.10 / Phase 6)** |
| Generated UI | 103-107 | Widget compatibility, button actions, parameter validation and override display, group/control ordering | Subsystem contract (Phase 7) |
| Runtime/library | 108-112, 117-127 | Clock/audio synchronization, stalls, seed/stream derivation complete in GC4; import equality, update compatibility, transactions, failure recovery, persistence schema remain | **Shared clock/PRNG contract complete (Rev 0.3 / GC4); subsystem remainder Phase 1/8** |
@@ -3397,10 +3397,10 @@ When a class timer expires:
* $2$ selections ago: multiplier $0.25$
* $3$ selections ago: multiplier $0.50$
* $4$ selections ago: multiplier $0.75$
* Older / not in recency queue: multiplier $1.0$
* Older / not in recency queue: multiplier `1`
$\text{effectiveWeight} = \text{baseWeight} \times \text{multiplier}$.
6. **Pool Relaxation (PRD 65):** If the sum of effective weights for all remaining candidates is $0$ (which occurs when all eligible sounds are penalized to zero, such as in single-sound pools):
* Relax anti-repetition penalties: set all multipliers to $1.0$ ($\text{effectiveWeight} = \text{baseWeight}$).
* Relax anti-repetition penalties: set all multipliers to `1` ($\text{effectiveWeight} = \text{baseWeight}$).
* If the sum of base weights is still $0$, the class firing is skipped with no sound played.
7. **Weighted Selection:** Draw a pseudo-random value $u \in [0, 1)$ from `rng.stream('cadence', '<class>:select:<ordinal>')` and pick the winning sound proportional to its effective weight.
8. **Execution:** Instantiate and play the winning sound via the audio subsystem.
@@ -3411,7 +3411,7 @@ When a class timer expires:
To prevent simultaneous or jarringly close auditory collisions between different automatic classes:
* The runtime tracks $\text{lastAutomaticSoundTime}$, the logical timestamp of the most recent automatic one-shot start.
* A class firing is permitted only if $\text{currentLogicalTime} - \text{lastAutomaticSoundTime} \ge \text{minGap}$ (default $1.5\text{s}$).
* A class firing is permitted only if $\text{currentLogicalTime} - \text{lastAutomaticSoundTime} \ge \text{minGap}$ (default `1500ms`).
* If multiple classes become due simultaneously or while a gap hold is in effect, they are queued and serviced in strict canonical priority order:
$$\text{rare} > \text{occasional} > \text{intermittent} > \text{routine}$$
* Deferred classes retain their firing opportunity: when the required $\text{minGap}$ has elapsed, the highest-priority deferred class fires immediately and resets the gap timer.
@@ -3569,10 +3569,194 @@ Phase 5 reuses existing standard codes from the Section 7 table:
6. **Sound Action Execution:** Sound actions validate sound references, pass inputs to recipes, check caller usage permissions, and reject unauthorized usage with `ERR_UNSUPPORTED_TARGET`.
7. **Cadence Eligibility & Cooldown:** A sound with `when: false` or active cooldown ($\text{elapsed} < \text{cooldown}$) is excluded from selection.
8. **Cadence Overlap Policy:** A sound with `overlap: false` is excluded from selection while any voice of that sound is active in the audio engine; a sound with `overlap: true` admits overlapping voices up to voice ceilings.
9. **Cadence Anti-Repetition Multipliers:** Consecutive firings verify multipliers $0.0$, $0.25$, $0.50$, $0.75$, $1.0$ against the class recency history.
9. **Cadence Anti-Repetition Multipliers:** Consecutive firings verify multipliers `0`, `0.25`, `0.50`, `0.75`, `1` against the class recency history.
10. **Pool Relaxation:** When all eligible sounds have effective weight zero, anti-repetition penalties relax to base weights and selection succeeds rather than permanently stalling.
11. **Cadence Intensity Scaling:** Decreasing intensity increases interval spacing ($T / \text{intensity}$); setting intensity to $0$ stops one-shot class scheduling completely.
12. **Minimum Automatic Gap & Priority:** Simultaneous class firings enforce `minGap` (default $1.5\text{s}$) between audio starts and service deferred classes in priority order ($\text{rare} > \text{occasional} > \text{intermittent} > \text{routine}$).
12. **Minimum Automatic Gap & Priority:** Simultaneous class firings enforce `minGap` (default `1500ms`) between audio starts and service deferred classes in priority order ($\text{rare} > \text{occasional} > \text{intermittent} > \text{routine}$).
13. **Ambient Maintenance:** Ambient sounds (`cadence.class: "ambient"`) auto-start on audio unlock and are maintained by the runtime.
14. **Manual SAMPLE Isolation:** Manual SAMPLE playback draws exclusively from `rng.stream('sample', ...)` and leaves cadence clocks, cooldowns, recency history, and automatic PRNG sequences byte-identical.
14. **Manual SAMPLE Isolation:** Manual SAMPLE playback draws exclusively from the `manual-sample` domain and leaves cadence clocks, cooldowns, recency history, and automatic PRNG sequences byte-identical.
## 21. Scenario Model 0.1
This section completes PRD 92102. Sections 9 and 10 remain authoritative for clock,
ownership, dispatch and failure. Scenarios are finite declarative performances. Their
director runs on the same fixed tick as cadence, after resolution and cadence and before
visual advancement. It never installs a timer for a timeline entry. Rendering samples the
current result and does not advance scenarios.
### 21.1 Definition and limits
`scenarios` is an ID-keyed object. Unknown fields in every object below are
`ERR_UNKNOWN_FIELD`; wrong shapes are `ERR_SCHEMA_VALIDATION`. IDs use section 1.
| Field | Type | Default / requirement |
| --- | --- | --- |
| `name` | string | Optional display name; ID is the fallback |
| `enabled` | boolean | `true`; affects new starts, does not cancel an active instance |
| `priority` | integer | `50`; range 0 through 100 |
| `group` | ID string | Optional; required with group exclusivity |
| `trigger` | trigger object | `{ "type": "manual" }` |
| `eligibility` | object with optional `when`, `timeout` | ConditionSpec and positive literal duration; timeout defaults to `5m` |
| `concurrency` | object | Fields and defaults in section 21.3 |
| `cooldown` | literal duration | `0ms`; measured from the last termination, including failure |
| `duration` | positive literal duration | Optional upper bound; without it completion follows the last beat |
| `onStart` | ActionSpec array | `[]` |
| `timeline` | timeline-entry array | Required; may be empty |
| `onComplete`, `onCancel` | restricted ActionSpec arrays | `[]`; section 10.2 |
| `tags` | string array | Optional; at most 16 strings of at most 32 characters |
At most 64 definitions, 1024 entries per definition, and 1024 total occurrences of
one repeated entry are authorable; excess is `ERR_OUT_OF_BOUNDS`. The runtime admits
at most 16 active instances and at most one instance of each definition. Capacity is a
start conflict and obeys the requested conflict policy. These scenario limits extend
the centralized inventory of section 19.5 and remain provisional pending GC6.
Instance IDs are `instances.scenario-<definition>-<ordinal>`, with a monotonically
increasing ordinal per definition. The director retains only live instances, bounded
cleanup owners, and the last terminal status per definition. Completed histories are
not retained. The lifecycle follows PRD 93: definition/eligibility/scheduling,
`STARTING`, `ACTIVE`, `COMPLETING` or `CANCELLING`, then `COMPLETED`, `CANCELLED` or
`FAILED`. Exactly one termination hook runs. `signals.scenario.active` is true when
any scenario instance is starting or active.
### 21.2 Trigger shapes and sampling
| Type | Additional fields | Opportunity |
| --- | --- | --- |
| `manual` | none | Start API or scenario control action |
| `once` | optional `at` (default `0ms`) | First tick at/after elapsed performance time |
| `interval` | required positive `every` | After each interval, starting one interval after activation |
| `random-interval` | required positive `min`, `max`, min <= max | Independently sample the first and each subsequent interval |
| `probability` | required positive `every`, required numeric `chance` in [0,1] | One probability draw per interval |
| `condition` | required `when`, optional nonnegative `for` (default `0ms`) | Hold/rearm state machine in section 10.3 |
| `event` | required declared `event` ID | Notification at event invocation, before its actions |
Triggers use performance logical time. The first true tick starts a condition's hold
timer; subsequent true ticks accumulate the hold. A true initial condition remains
disarmed until a false tick. Firing consumes the edge even if admission is deferred or
rejected. Disabled definitions still advance trigger clocks and rearming state; admission
rechecks enabled. Intervals shorter than a tick produce at most one opportunity per tick,
with no catch-up burst. Invalid durations use `ERR_INVALID_DURATION`, reversed ranges
use `ERR_INVALID_RANGE_ORDER`, missing references use `ERR_INVALID_REFERENCE`.
Each definition has one stream `scenario / <id>:trigger:1`. Each admitted instance
uses `scenario / <id>:<ordinal>` for its start actions, timeline timing, weighted branches,
repeat intervals, and nested action values, in execution order. Neither manual sampling
nor another scenario's actions consume that stream. Sound and visual instantiation use
their own domains. Trigger event input values are copied into a deferred request once;
they are not an authorable scenario input namespace in 0.1.
### 21.3 Admission, exclusivity, and controls
`concurrency` permits exactly `mode` (`parallel` or `exclusive`, required when the
object is present), `scope` (`global` default, or `group`), and `policy` (`defer`
default, `reject`, or `replace`). Parallel is the default mode. Exclusivity is symmetric:
either participant's global exclusive scope blocks overlap; group exclusive scope blocks
overlap with the same group. Same-definition overlap always conflicts.
`replace` succeeds only if the incoming priority is strictly greater than every
conflicting instance's priority. Check eligibility and capacity before cancelling any
victim. Equal/lower priority rejects. `reject` drops an inadmissible opportunity;
`defer` retains it under section 10.4. Failed eligibility or cooldown obeys the same
policy. Replacement hooks and cleanup precede the incoming start hook. Pending admission
sorts by descending priority, creation logical time, then definition document order.
Starts and beats consume one ordinary dispatch unit each in addition to their actions;
even empty-start feedback is bounded by section 10.5.
`control` actions for this subsystem have `target: "scenarios.<id>"` and `command`
equal to `start`, `stop`, `enable` or `disable`. Stop cancels an active instance and
removes the definition's deferred request. Enable/disable does not reset cooldown or
trigger state. Other scenario commands are `ERR_UNSUPPORTED_TARGET`. Global pause and
resume are runtime controls and preserve independent user and visibility pause reasons.
Events generated during ordinary dispatch may admit scenarios later in the same tick;
new scenarios get their own owner. Static validation rejects event/scenario invocation
and trigger feedback cycles with `ERR_CYCLIC_DEPENDENCY`, including indirect events and
timeline branches. This conservative check rejects cycles even if authored conditions
would make them unreachable. The runtime budget remains the second backstop.
### 21.4 Timeline, relative timing, repeats, and branches
An entry permits exactly `id`, `at`, `after`, `delay`, `repeat`, `actions`, `choose`.
Use exactly one of `at` (nonnegative literal duration from instance start) or `after`
(another entry's unique ID). `delay` belongs only to `after`, defaults to `0ms`, and
accepts a nonnegative literal or `{ "random": { "min": "20s", "max": "50s" } }`.
The anchor is the first occurrence's scheduled time, independent of its action outcomes
or repeat count. Forward anchors are legal; missing anchors and cycles are rejected.
After successful `onStart`, resolve first-occurrence timestamps by walking entries in
document order, resolving each anchor before its dependent and sampling each relative
delay once. An instance keeps one mutable scheduling record per entry rather than
expanding every repetition. Beats execute in non-decreasing scheduled time; ties use
scenario definition document order, then entry document order. Timing is quantized only
at dispatch: execute on the first tick at/after the scheduled timestamp. A skipped or
zero-weight branch still counts as an occurrence and anchors dependent entries.
`repeat` permits exactly required integer `count` (11024, including the first beat)
and required positive `every` (literal or bounded random TimeSpec). Sample the next
interval after each occurrence and add it to the preceding scheduled timestamp.
Choose exactly one of `actions` or nonempty `choose`. A branch permits required finite
nonnegative numeric `weight`, optional ConditionSpec `when`, and required `actions`.
At each occurrence evaluate branch conditions, discard ineligible/zero weights, draw
one weighted choice and run its actions. An empty eligible pool is a no-op.
An explicit duration keeps the instance active even after its timeline empties. Beats
at the duration execute before completion; later beats are cancelled. Without duration,
completion follows the last beat, including repeats. Empty undurated scenarios finish
in their start tick. The application exposes the director's start/cancel/status controls;
the final schema-generated presentation belongs to Phase 7.
### 21.5 Resource ownership and termination
Section 10 governs all termination causes. Indirect event resources retain the owner,
priority, usage authorization and stream of their caller. Sound `ownership: "scenario"`
means that concrete caller owner; `persistent` and the existing `performance` spelling
transfer to the performance root. A continuous sound requested before audio unlock is
retained as a bounded logical intent (at most 16), removed if its owner terminates, and
started when audio is available. Missed one-shots are not replayed. This does not claim
sample-accurate reconstruction of pre-unlock oscillator phase.
Termination removes future timeline records before running the hook, continues past
hook failures, and transfers both scenario-scope and duration overrides and live audio
to a cleanup owner. Visual cleanup honors the independent originating-scenario relation
of section 19.2. Release durations and cleanup retention are capped at five logical
seconds. Counters include releasing audio pools, not only the actively playing set.
Performance teardown runs hooks before disposing audio/visual engines, and force-clears
cleanup immediately. No persistent `set` is rolled back. Persistent resources remain
until performance teardown or explicit removal.
### 21.6 Required examples and verification traces
Minimal five-second scenario:
```json
{"xzbt":"0.1","meta":{"id":"five-second","name":"Five seconds"},"scenarios":{"brief":{"trigger":{"type":"once"},"duration":"5s","timeline":[]}}}
```
The composition example is `exhibits/exhibit-e.xzbt`: a 40-minute sequence with random
trigger timing, random relative delays, repeated beats, weighted branching, persistent
state, cadence interaction, continuous sound, visual spawning, override and recovery.
Exhibits A and B add temporary parameter overrides. `exhibits/scenario-challenge.xzbt`
provides short, multi-minute, condition, event, parallel, exclusive and deferred cases.
Invalid examples: `priority: 101``ERR_OUT_OF_BOUNDS`; `{ "at": "1s", "after":
"a", "actions": [] }` → `ERR_SCHEMA_VALIDATION`; `after: "missing"` →
`ERR_INVALID_REFERENCE`; reciprocal `after` anchors → `ERR_CYCLIC_DEPENDENCY`;
`onCancel: [{ "type": "spawn", "target": "visuals.systems.burst" }]`
`ERR_UNSUPPORTED_TARGET`; `onFailure: []``ERR_UNKNOWN_FIELD`.
1. Five-second and multi-minute duration endpoints, including a beat exactly at the endpoint.
2. Equal-time order, forward relative references, random delays and lazy finite repeats.
3. Weighted branching, excluded branches, persistent mutations and unchanged manual PRNG stream.
4. Once, interval, probability, random interval, manual, condition and event triggers.
5. Startup-true condition disarming, hold cancellation and false-tick rearming (GC5).
6. Deferred deduplication, retained expiry, eligibility/cooldown recheck, and priority ordering (GC5).
7. Symmetric global/group exclusivity, parallel starts, higher-priority replacement and capacity.
8. Nested event ownership using production visual and audio instances and override counters (GC5).
9. Completion, cancellation, critical startup/action failure and hook failure preserve cleanup and prior set (GC5).
10. Duration overrides release at owner termination, masked user edits survive, and five-second cleanup is bounded (GC5).
11. Ordinary dispatch/depth failures preserve the independent 256-action termination budget (GC5).
12. Repeated completion/cancellation/failure cycles return resource and scheduler counters to baseline (GC5).
13. Forty-minute Exhibit E runs accelerated through the same fixed-step production tick; equal seeds give equal traces.
14. Pause/resume preserves logical time; disposal leaves no owned resources or subscriptions.
15. Two real hours on a frozen combined workload, with GC6 environment, frame intervals, resource/memory trend,
audio peak/nonfinite measurements and listening observations. Accelerated traces cannot satisfy this gate.