# Phase 3a/3b audio subsystem — contract and automated evidence **Date:** September 5, 2026 **Artifact:** `XZBT.html` **SHA-256:** `64d9932ed863dbae66f309504b88e19370ad2c9017e92c32bd61ea1ea39a3f17` **Specification baseline:** Format Specification 0.1 revision 0.4, sections 14-15 **Result:** Phase 3a and Phase 3b contracts complete; implementation and automated acceptance passed. Phase 3c and the user-observed audible gates remain open. ## Phase 3a review outcome Phase 3a was reviewed before Phase 3b began. Four blocking defects were found in the draft and are now closed: 1. **Node identity contradicted itself.** The draft said nodes are keyed by node ID while every documented minimal example carried an inline `id` field, so under the strict unknown-field policy each example would have emitted `ERR_UNKNOWN_FIELD` and trace 1 could never pass. Resolved in favour of the keyed map, consistent with `parameters`, `state`, `audio.buses`, `sounds`, and `modulators`; an `id` field on a node is now explicitly `ERR_UNKNOWN_FIELD`. 2. **No container shape.** The draft never said where a node lives. Section 14.3 now defines the audio graph object (`nodes`, `routes`) and names its three embedding points. 3. **`audioMaxFrequency` had no validation stage.** It was declared a semantic-stage `ERR_OUT_OF_BOUNDS` while depending on a live `AudioContext.sampleRate`. Section 14.5 now separates a device-independent semantic ceiling of 24000 from instantiation-time clamping against `min(24000, sampleRate x 0.45)`, with `WARN_AUDIO_RATE_CLAMP` instead of failure, so a cached exhibit still activates on a different device. 4. **The sample-hold PRNG child key was undocumented,** which section 9.3 requires. Section 14.12 now fixes it as `|node|`. Nine further gaps were closed in the same pass: noise/impulse seeding scope, pink/brown spectral definitions, impulse decay envelope math, Nyquist handling for custom partials, the missing-`harmonics` code, LFO phase origin, the two units rows dropped from PRD 35, the staged `ERR_INVALID_NODE_TYPE` wording, the PRD 33 modulator cross-reference, and the duplicated diagnostics table. ## Delivered - section 14: pipeline, canonical units, audio graph objects, node-field resolution scope, frequency ceiling staging, audio reproducibility scope, and the six source and control-source node contracts; - section 15: nine processing and routing node contracts, the component instance node, audio routing, modulation semantics with the modulatable-property registry, twelve graph legality rules, authoring limits, components with the component-scoped `inputs.*` namespace, sound definitions and recipes, and buses; - four new diagnostic codes (`ERR_INVALID_ROUTE`, `ERR_NO_AUDIBLE_PATH`, `ERR_COMPONENT_RECURSION`, `WARN_AUDIO_RATE_CLAMP`) added to the single section 7 table; - `src/runtime/audio-contract.js`, the declarative node/limit/modulation tables shared by every consumer; - `src/runtime/audio-graph.js`, pure validation, component expansion, and legality checking that never opens an `AudioContext`; - `src/runtime/audio-engine.js`, deterministic instantiation plus Web Audio realization and the `AudioSubsystem` runtime owner; - audio structural definitions in `schema/xzbt-0.1.schema.json`; - `tools/validate-exhibit.mjs` now delegates its audio checks to the shared runtime module rather than reimplementing them; - `exhibits/minimal-audio.xzbt`, a generic fixture exercising components, modulation, buses, a shared recipe, and both recipe modes; - an audio panel in the runtime shell: gesture unlock, master volume, per-bus gain, and per-sound triggering. ## Automated verification `npm test` runs 62 tests with zero failures across GC2-GC5, Phase 1, Phase 2, and the new Phase 3 suite. The Phase 3 suite covers every trace required by sections 14.13 and 15.19: | Trace | Check | | --- | --- | | 14.13-1 | All fifteen non-composite node types validate their minimal example inside a complete graph; all sixteen documented invalid cases emit exactly their documented code. | | 14.13-2 | A node carrying `id` is `ERR_UNKNOWN_FIELD`; a node keyed `output` is `ERR_INVALID_ID`. | | 14.13-3 | Semantic validation rejects 30000 Hz with no `AudioContext` present; instantiation at 44100 Hz clamps to 19845 Hz with one `WARN_AUDIO_RATE_CLAMP`, and at 96000 Hz does not clamp. | | 14.13-4 | A `random` frequency samples once, repeats for the same seed and ordinal, and differs at the next ordinal. | | 14.13-5 | An external binding to a node field fails; `audio.buses..gain` still validates. | | 14.13-6 | Sample-hold sequences are identical for the same seed and ordinal, and differ when the seed, the ordinal, or the node key changes. Slew is clamped to the tick period. | | 15.19-1 | Minimal and invalid examples for gain, filter, compressor, waveshaper, delay, reverb, stereo-pan, mixer, and resonator. | | 15.19-2 | Rules 3, 4, 5, 6, 7, 9, 10 and the self-route case each emit their named diagnostic; matched passing fixtures do not. | | 15.19-3 | A control-source-only path to output fails; the same shape with an oscillator passes. | | 15.19-4 | Component expansion resolves `inputs.*`, applies declared defaults, rejects reaching an internal node, rejects an undeclared value key, rejects audio into an input-less component, accepts an exposed parameter as a modulation target, and rejects self-instantiation. | | 15.19-5 | Two modulation routes onto one property are both retained with their resolved depths. | | 15.19-6 | 128 expanded nodes pass; 129 is `ERR_NODE_LIMIT_EXCEEDED`. 64 partials and 16 resonator modes pass; 65 and 17 do not. | | 15.19-7 | Expansion and validation run with `globalThis.AudioContext` undefined, asserted directly in the suite. | A realization smoke test drives every node type through an `AudioContext` stand-in, confirms the graph reaches the destination, and confirms every started source is stopped on disposal. Two clean builds produce byte-identical artifacts with digest `64d9932ed863dbae66f309504b88e19370ad2c9017e92c32bd61ea1ea39a3f17`; all three exhibit fixtures pass the standalone validator. ## Not established by this record - **Phase 3c contracts.** Audio automation precedence (PRD 54), the lifecycle state machine (PRD 57), unlock behavior and pre-unlock one-shot handling, voice ceilings, and the measured master-protection contract (PRD 58 peak ceiling, numerical tolerance, release behavior, finite-sample handling) are unspecified. The runtime's master chain is engine-owned and unbypassable, but its ceiling is a placeholder, not an accepted contract; the presence of a compressor does not establish that the protection contract passes. - **Audible observation.** No sound has been heard from this build. The PRD 129 audio acceptance challenge, real GC4 synchronization checks, peak and finite-sample capture, and listening observations for clicks and clipping all remain open user-observed gates. - **One-shot endings.** Without the Phase 3c lifecycle contract the runtime shell releases a one-shot voice on a fixed development timer. That is a development affordance, not the determinable ending PRD 57 requires. - **Reference exhibits.** `minimal-audio.xzbt` is a contract fixture, not reference Exhibit A or B; those begin at their mapped milestones. - **Phase 1 direct-file gate.** The two-fixture direct-file import and full-browser-restart observation remains open and is unaffected by this work.