Review Phase 3a before building on it, then implement Phase 3b. The Phase 3a draft had four blocking defects: nodes were described as a keyed map while every documented example carried an inline `id` field, so under the strict unknown-field policy each minimal example would have failed its own acceptance trace; no section said where a node lives; the `audioMaxFrequency` ceiling was declared a semantic-stage error while depending on a live AudioContext sample rate; and the sample-hold PRNG child key that section 9.3 requires was undocumented. Close all four, plus nine further gaps in noise seeding, spectral definitions, impulse decay math, Nyquist handling, missing-field codes, LFO phase origin, the units table, node-type staging, and a duplicated diagnostics table. Add Format Specification section 15 for Phase 3b: nine processing and routing node contracts, the component instance node, audio routing and modulation with an explicit modulatable-property registry, twelve graph legality rules, authoring limits, components with a component-scoped `inputs.*` namespace, sound definitions and recipes, and buses. Implement the subsystem in three modules. audio-contract.js holds the declarative node, limit, and modulation tables every consumer reads. audio-graph.js validates, expands components, and checks legality without ever opening an AudioContext. audio-engine.js resolves node fields once from the seeded stream, clamps frequencies to the live device ceiling, realizes the graph through Web Audio, and owns the runtime AudioSubsystem. Extend the schema, delegate the standalone validator's audio checks to the shared module rather than carrying a second implementation, and add a generic audio fixture. Phase 3 is not accepted. Automation precedence, the lifecycle state machine, unlock behavior, voice ceilings, and master protection are Phase 3c. No sound has been heard from any build, so the audio acceptance challenge, peak and finite-sample capture, and listening observations remain open. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_011FWPdCqKaaDnP9NC3JAwh6
7.3 KiB
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:
- Node identity contradicted itself. The draft said nodes are keyed by node ID while every documented minimal example carried an inline
idfield, so under the strict unknown-field policy each example would have emittedERR_UNKNOWN_FIELDand trace 1 could never pass. Resolved in favour of the keyed map, consistent withparameters,state,audio.buses,sounds, andmodulators; anidfield on a node is now explicitlyERR_UNKNOWN_FIELD. - 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. audioMaxFrequencyhad no validation stage. It was declared a semantic-stageERR_OUT_OF_BOUNDSwhile depending on a liveAudioContext.sampleRate. Section 14.5 now separates a device-independent semantic ceiling of 24000 from instantiation-time clamping againstmin(24000, sampleRate x 0.45), withWARN_AUDIO_RATE_CLAMPinstead of failure, so a cached exhibit still activates on a different device.- The sample-hold PRNG child key was undocumented, which section 9.3 requires. Section 14.12 now fixes it as
<sound-instance-key>|node|<node-path>.
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 anAudioContext;src/runtime/audio-engine.js, deterministic instantiation plus Web Audio realization and theAudioSubsystemruntime owner;- audio structural definitions in
schema/xzbt-0.1.schema.json; tools/validate-exhibit.mjsnow 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.<id>.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.xzbtis 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.