feat(cadence): implement the phase 5 event and cadence subsystems

Section 20 in full: the automatic scheduling algorithm with its eligible-pool
filtering, cooldown and overlap exclusion, weight evaluation, the anti-repetition
multiplier table applied by recency-queue position, pool relaxation when total
effective weight reaches zero, and reschedule by uniform sample divided by
intensity; the priority clocks and minimum-gap servicing of 20.6 with deferred
classes retained; intensity clamping and the pause threshold of 20.7, under
which ambient voices keep running; the 1024-unit dispatch budget and sixteen
levels of event nesting; and manual SAMPLE evaluation in an isolated stream that
does not perturb cadence scheduling.

Two defects found by the section 20 review triage are fixed here rather than
shipped. A sound action's usage check tested whether the target sound permitted
*some* action context instead of the caller's own, so a scenario-only sound was
playable from a manual action; it now checks the caller's context and rejects
with ERR_UNSUPPORTED_TARGET, the code 20.2.2 and 20.12 actually name, and the
two cases the previous test never exercised — manual-only from scenario, and
scenario-only from manual — are covered.

Traces 7 through 10 drove `triggerSound()` and `calculateEligiblePool()`, a
duplicate scheduler with no production call site, rather than the
`advance()`/`fireClass()` path a real exhibit runs. The two implementations
disagreed on recency-history semantics: 20.5.9 requires a plain queue of the
last four firings, and the duplicate moved an existing entry to the front
instead. Nothing caught it because nothing called both. Those traces now drive
`update()`/`advance()` with a seeded RNG, as traces 11 through 14 already did,
and the duplicate is deleted rather than reconciled.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01ShxxFqFmCUDQnQvFNm4TKy
This commit is contained in:
2026-09-06 21:53:51 +00:00
co-authored by Claude Opus 5
parent d5d7091289
commit 3db1df058f
7 changed files with 1788 additions and 11 deletions
@@ -0,0 +1,99 @@
# Phase 5: Events and Cadence Acceptance Evidence
**Date:** September 6, 2026
**Status:** Complete and fully verified
**Specification References:** PRD 6168 (Cadence Model), 9091 (Event Model 0.1), 139 (Milestone criteria); Format Specification Revision 0.9, Section 20 ("Cadence and Event Subsystems Contract")
---
## 1. Executive Summary
Phase 5 completes the Cadence and Event subsystems of XZBT 0.1, delivering:
1. **Contract & Schema:** Complete Section 20 normative contract in the Format Specification and JSON Schema definitions for `CadenceConfig`, `CadenceClockRange`, `SoundCadence`, `ActionSpec`, and `EventDefinition`.
2. **Static & Semantic Validation:** Implemented in `src/runtime/cadence-validation.js`, integrated with `src/runtime/validator.js` and `tools/validate-exhibit.mjs`. Static cycle detection for events (`ERR_CYCLIC_DEPENDENCY`), clock range ordering (`ERR_INVALID_RANGE_ORDER`), and sound cadence metadata validation.
3. **Action Model 0.1:** Implemented in `src/runtime/actions.js` with `type: 'event'` and `type: 'sound'`, per-tick action dispatch budget (1024 units/tick), 16-level event recursion limit, strict input parameter scoping, default value substitution, type validation (`ERR_TYPE_MISMATCH`), undeclared parameter rejection (`ERR_UNKNOWN_FIELD`), and sound usage gating (`ERR_UNSUPPORTED_TARGET`).
4. **Procedural Cadence Engine:** Implemented in `src/runtime/cadence.js` with clock interval management for `routine`, `intermittent`, `occasional`, and `rare` classes, minimum gap scheduling (`minGap`, default 1.5s), priority queue ordering (`rare` > `occasional` > `intermittent` > `routine`), weighted random selection, cooldown checking, overlap refusal, anti-repetition recency multipliers (`0.0`, `0.25`, `0.50`, `0.75`) with automatic pool relaxation, continuous ambient sound voice auto-start and maintenance, and strict PRNG domain isolation (`cadence` domain).
5. **Content Integration:** All four reference exhibits (`exhibits/exhibit-{a,b,c,d}.xzbt`) populated with valid audio synthesis graphs, sounds, cadence clocks, and lifecycle events.
6. **Verification:** All 14 acceptance traces of Section 20.15 verified in `test/phase5-cadence.test.mjs`. The full suite of 229 automated tests passes with zero failures. Deterministic builds of `XZBT.html` produce byte-identical SHA-256 digests.
---
## 2. Test Execution and Acceptance Traces
The 14 acceptance traces defined in Section 20.15 of the Format Specification are verified by `test/phase5-cadence.test.mjs`:
| Trace | Title | Verification Target | Status |
|---|---|---|---|
| **Trace 1** | Schema & Static Validation | Top-level cadence and sound cadence validation; clock range with `min > max` rejected with `ERR_INVALID_RANGE_ORDER`. | PASS |
| **Trace 2** | Static Cycle Detection | Static dependency graph analysis detects direct, mutual, and transitive event cycles, rejecting them with `ERR_CYCLIC_DEPENDENCY`. | PASS |
| **Trace 3** | Event Scoping & Typing | Declared default inputs are applied; caller overrides are strictly type-checked; undeclared parameters are rejected with `ERR_UNKNOWN_FIELD`. | PASS |
| **Trace 4** | Event Recursion Limit | Dynamic event nesting exceeding 16 levels halts dispatch cleanly with `ERR_DISPATCH_BUDGET`. | PASS |
| **Trace 5** | Dispatch Budget | Per-tick action execution budget of 1024 units is strictly enforced; excess actions refused with `ERR_DISPATCH_BUDGET`. | PASS |
| **Trace 6** | Sound Action Usage | Actions attempting to trigger sounds from unauthorized contexts are refused with `ERR_UNSUPPORTED_TARGET`. | PASS |
| **Trace 7** | Cooldown Refusal | Sounds are refused if re-triggered before their declared `cooldown` duration has elapsed. | PASS |
| **Trace 8** | Overlap Refusal | Sounds configured with `overlap: false` refuse dispatch when another voice instance of that sound is active. | PASS |
| **Trace 9** | Anti-Repetition Multipliers | Recent sound playbacks receive monotonically graded multipliers: rank 0 = `0.0`, rank 1 = `0.25`, rank 2 = `0.50`, rank 3 = `0.75`. | PASS |
| **Trace 10** | Pool Relaxation | When all candidate sounds in a pool are penalized to effective weight zero, weights relax to base values so scheduling never deadlocks. | PASS |
| **Trace 11** | Intensity Scaling | Higher intensity compresses clock intervals; lower intensity expands intervals; clamped to safe bounds (>= 100ms). | PASS |
| **Trace 12** | Minimum Gap & Priority | Automatic sound scheduling respects the minimum gap (`minGap`, default 1.5s) and prioritizes `rare` > `occasional` > `intermittent` > `routine`. | PASS |
| **Trace 13** | Ambient Voice Maintenance | Continuous ambient sounds auto-start when audio is unlocked and maintain exactly one voice, restarting if stopped. | PASS |
| **Trace 14** | Manual SAMPLE PRNG Isolation | Interspersing manual SAMPLE draws from the `manual-sample` / `sample` PRNG domain has zero effect on cadence scheduling. | PASS |
### Test Suite Execution Output
```
> node test/phase5-cadence.test.mjs
✔ Trace 1: Schema & Static Validation of cadence and sound metadata (3.4712ms)
✔ Trace 2: Static cycle detection rejects circular event action chains (0.5927ms)
✔ Trace 3: Event input scoping, defaults application, and strict type checking (1.2611ms)
✔ Trace 4: Dynamic event nesting exceeding 16 levels is halted (0.4059ms)
✔ Trace 5: Per-tick 1024-unit action dispatch budget is strictly enforced (0.9943ms)
✔ Trace 6: Sound actions targeting unauthorized sounds are refused with ERR_UNSUPPORTED_TARGET (0.5403ms)
✔ Trace 7: Sound cooldown refrains from re-dispatching before interval expires (0.7546ms)
✔ Trace 8: Overlap false prevents new instance while existing voice is active (0.6852ms)
✔ Trace 9: Anti-repetition penalties apply 0.0, 0.25, 0.50, 0.75 recency multipliers (0.2897ms)
✔ Trace 10: Anti-repetition relaxation to base weights when all candidates are penalized to zero (0.2865ms)
✔ Trace 11: Intensity scaling inversely compresses or expands clock intervals (0.1554ms)
✔ Trace 12: Minimum gap (1.5s) and priority ordering (rare > occasional > intermittent > routine) (0.1433ms)
✔ Trace 13: Continuous ambient sounds auto-start and maintain exactly one active voice (0.1067ms)
✔ Trace 14: Manual SAMPLE evaluations run in isolated stream without perturbing cadence scheduling (0.9915ms)
tests 14
suites 0
pass 14
fail 0
cancelled 0
skipped 0
todo 0
duration_ms 15.0156
```
Full repository regression (`npm test`): **229 tests pass, 0 fail**.
---
## 3. Exhibits AD Content Validation
All reference exhibits were augmented with audio graphs, sounds, cadence configurations, and events:
- **Exhibit A (Procedural Machine):** `machine-hum` (ambient drone), `relay-click` (routine percussive), `steam-purge` (intermittent burst), `emergency-siren` (rare alarm), and `minor-disturbance` event.
- **Exhibit B (Deep Abstract Field):** `pad-drone` (ambient sine drone), `harmonic-resonance` (intermittent chord), `sub-swell` (occasional low swell), and `field-shift` event.
- **Exhibit C (Natural Environment):** `wind-atmosphere` (ambient pink noise), `organic-chimes` (occasional resonant bell), `wind-gust` (rare swept gust), and `breeze-surge` event.
- **Exhibit D (Instrument Display):** `radar-pulse` (routine telemetry ping), `button-click` (manual UI click), `telemetry-alert` (occasional alert chime), and `sweep-alert` event.
All exhibits validated cleanly via `tools/validate-exhibit.mjs`:
```
[PASS] exhibits/exhibit-a.xzbt (0 errors)
[PASS] exhibits/exhibit-b.xzbt (0 errors)
[PASS] exhibits/exhibit-c.xzbt (0 errors)
[PASS] exhibits/exhibit-d.xzbt (0 errors)
```
---
## 4. Deterministic Build Digest
Rebuilding `XZBT.html` via `node tools/build-xzbt.mjs` incorporates all new runtime modules (`src/runtime/cadence-validation.js`, `src/runtime/cadence.js`, and updated `actions.js`, `audio-engine.js`, `performance.js`, `app.js`):
- **Artifact:** `XZBT.html`
- **File size:** 502,989 bytes
- **SHA-256 Digest:** `f3f634d7d33b2f8a57cae541fc31b4dcedce88b2ee2648093f92a2cfc168d718`
- **Determinism:** Successive independent builds generate the identical byte sequence and hash.