docs(audio): specify the phase 3c contract

Phase 3c is delivered in four slices so an interrupted phase never leaves
the runtime worse than it started: contract, lifecycle and voices,
automation, and measured master protection. Slice 2 replaces working
scaffolding, and slice 4 cannot close without user-observed measurement.
Record that split in the implementation plan.

This commit is slice 1, the contract. Format Specification section 16
adds automation tracks with three modes and four interpolation curves,
their placement in the shared resolution pipeline, the seven-state
lifecycle with its permitted transitions, the engine-owned internal
release gain, a per-node table for computing determinable one-shot
endings, voice ceilings with an eviction order that always releases
rather than hard-stops, the master protection contract, and unlock and
pause behavior for audio.

Automation reuses the modulation registry rather than introducing a
second answer to which properties vary over time, and a track is
declared inside the graph that owns its target, so section 14.4 stands:
automation does not widen the external surface of a node property. Close
the container gap the new fields opened by adding `automation` to the
graph object in 14.3 and `release` to the recipe object in 15.16.

Master protection is specified in shape and provisional in value.
Finite-sample handling is normative because a NaN in the output buffer
is unacceptable at any ceiling; the peak ceiling, numerical tolerance,
and release behavior await GC6 measurement.

No runtime change. There is no lifecycle state machine, no voice ceiling
is enforced, and `automation` and `release` are still rejected as
unknown fields. No sound has been heard from any build.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011FWPdCqKaaDnP9NC3JAwh6
This commit is contained in:
2026-09-05 23:20:01 +00:00
co-authored by Claude Opus 5
parent cc36878a0f
commit 42e4b32fd6
5 changed files with 313 additions and 12 deletions
+4 -4
View File
@@ -9,10 +9,10 @@ XZBT is a self-contained browser runtime for declarative procedural audiovisual
| Resource | Purpose | Status |
| --- | --- | --- |
| [MVP Product Requirements Document](XZBT_0-1_MVP_Product_Requirements_Document.md) | Product scope, user behavior, delivery requirements, and release acceptance | Document revision 0.3; format version remains 0.1 |
| [Format Specification 0.1](docs/XZBT_0-1_Format_Specification.md) | Runtime semantics, contract inventory, and required authoring examples | Document revision 0.4; Phase 0 shared contracts and the Phase 3a/3b audio authoring contract complete, remaining subsystem contracts in dependency order |
| [Format Specification 0.1](docs/XZBT_0-1_Format_Specification.md) | Runtime semantics, contract inventory, and required authoring examples | Document revision 0.4; Phase 0 shared contracts and the complete Phase 3a-3c audio subsystem contract, remaining subsystem contracts in dependency order |
| [Gap Closure Decisions](docs/XZBT_0-1_Gap_Closure_Decisions.md) | Decisions and rationale for the seven pre-implementation gaps | Record revision 0.3; GC1GC5 Phase 0 evidence complete |
| [Verification Gates](docs/XZBT_0-1_Verification_Gates.md) | Evidence required before architecture commitment, subsystem work, and release | Phase 0 complete; later subsystem, GC6, and GC7 checks scheduled |
| [Implementation status](docs/IMPLEMENTATION_STATUS.md) | Current phase, stop reason, saved work, and resume prerequisites | Phase 3a/3b audio contract and implementation complete; Phase 1 direct-file and Phase 3 audible observations pending |
| [Implementation status](docs/IMPLEMENTATION_STATUS.md) | Current phase, stop reason, saved work, and resume prerequisites | Audio contract complete through Phase 3c; Phase 3c implementation, Phase 1 direct-file, and Phase 3 audible observations pending |
| [Implementation plan](docs/XZBT_0-1_Implementation_Plan.md) | Sequenced phases, completion/challenge mapping, and GC6/GC7 verification schedule | Phase 19 plan recorded |
The PRD is authoritative for product requirements. The format specification is authoritative for runtime semantics where a contract is explicitly defined. The decision record explains those choices; the verification gates define how to check them. These documents must be updated together when a decision changes. An unresolved conflict is a specification defect, not permission for an implementation to choose silently.
@@ -21,7 +21,7 @@ The earlier ChatGPT discussion, **Discuss Application Vision** (conversation `6a
## Planning entry point
Phase 0 is complete, and the production runtime is implemented through Phase 3b. Continue with Phase 3c (audio automation precedence, lifecycle, and master protection) from the implementation plan while preserving the verified launch model, shared semantic contracts, and production GC2/GC3 behavior. Two observations remain open in completed work: the Phase 1 direct-file two-fixture restart, and the Phase 3 audible acceptance — no sound has been heard from any build. Workload measurements and release soak tests remain later explicit gates.
Phase 0 is complete, the production runtime is implemented through Phase 3b, and the audio subsystem contract is written through Phase 3c. Continue with Phase 3c implementation slice 2 (lifecycle and voices) from the implementation plan while preserving the verified launch model, shared semantic contracts, and production GC2/GC3 behavior. Two observations remain open in completed work: the Phase 1 direct-file two-fixture restart, and the Phase 3 audible acceptance — no sound has been heard from any build. Workload measurements and release soak tests remain later explicit gates.
The full PRD completion criteria remain the 0.1 release target. Early integrated demonstrations are milestones, not completed MVPs. Reference exhibits develop alongside the engine; Phase 9 completes and audits the suite.
@@ -67,7 +67,7 @@ Run every suite with `npm test`.
The active performance supports typed parameters and state, read-only runtime signals, ValueSpec and ConditionSpec evaluation, ordered `set` and `override` actions, same-tick bindings with deterministic smoothing, numeric transitions, and priority-based temporary overrides. The audio subsystem validates and expands declared graphs without touching an `AudioContext`, instantiates them deterministically from the seeded stream, and realizes them through Web Audio; the application exposes gesture unlock, master volume, per-bus gain, and per-sound triggering. `exhibits/minimal-audio.xzbt` exercises components, modulation, buses, and both recipe modes.
No sound has yet been heard from a build: the audio acceptance challenge, peak and finite-sample capture, and listening observations are open Phase 3 gates, and audio automation precedence, the lifecycle state machine, unlock behavior, voice ceilings, and master protection are Phase 3c contracts. Visuals, cadence, events, scenarios, and the final schema-driven UI remain assigned to later phases.
The Phase 3c contract (section 16) specifies automation, the lifecycle state machine, determinable one-shot endings, voice ceilings, master protection, and unlock behavior, but the runtime does not implement it yet: `automation` and `release` are still rejected as unknown fields. No sound has yet been heard from a build, and the audio acceptance challenge, peak and finite-sample capture, and listening observations remain open Phase 3 gates. Visuals, cadence, events, scenarios, and the final schema-driven UI remain assigned to later phases.
## Local development server
+8 -2
View File
@@ -1,7 +1,7 @@
# XZBT implementation status
**Updated:** September 5, 2026
**State:** Phase 3a/3b audio authoring contract and implementation complete; Phase 3c audio contracts, the Phase 3 audible gates, and the Phase 1 direct-file import/restart observation remain pending
**State:** The audio subsystem contract is complete through Phase 3c; Phase 3c implementation (slices 2-4), the Phase 3 audible gates, and the Phase 1 direct-file import/restart observation remain pending
**Planning baseline:** `05fe2b4e021ba86e4a290d05b63c7cae0e386128`
**Exact demarcation:** GC1 direct-file feasibility (10/10 checks), GC2 shared format contracts, GC3 resolution semantics, GC4 clock/PRNG semantics, and GC5 ownership/failure semantics are complete at the Phase 0 contract-oracle level. The Phase 1 production runtime skeleton, the Phase 2 common grammar, and the Phase 3a/3b audio authoring contract and its implementation pass automated checks. Three gates remain open in the completed work: Phase 1's direct-file two-fixture restart observation, Phase 3's audible observation (no sound has been heard from any build), and the Phase 3c audio contracts on which real playback acceptance depends. Visual, cadence/event, scenario, final generated-UI, performance, and soak work remains assigned to later phases.
@@ -13,7 +13,7 @@ The user requested sequential implementation with a stop on problems. The [manua
| 0 — Contracts and feasibility | Complete | GC1 passed; GC2GC5 shared contracts and traces passed; GC6/GC7 later gates scheduled and mapped |
| 1 — Runtime skeleton | Implemented; acceptance pending | [Automated evidence](evidence/phase1/2026-09-05-runtime-skeleton.md) passes production-module, lifecycle, PRNG-vector, cache/restore, fixture-validation, and deterministic-build tests. Direct-file two-fixture import/restart remains a user-observed gate. |
| 2 — Common grammar | Complete | [Automated evidence](evidence/phase2/2026-09-05-common-grammar.md) covers production GC2 conformance, typed values, signals, actions, same-tick bindings, transitions, override precedence/release, and parameter restoration. |
| 3 — Audio engine | Phases 3a/3b complete; 3c not started | [Automated evidence](evidence/phase3/2026-09-05-audio-authoring-contract.md) covers Format Specification sections 14-15, all sixteen node types, routing and modulation, twelve legality rules, authoring limits, components, sounds/recipes, and buses. Phase 3c (automation precedence PRD 54, lifecycle PRD 57, safety limits and master protection PRD 58) and the PRD 129 audio acceptance challenge remain. |
| 3 — Audio engine | Phases 3a/3b complete; 3c contract complete, 3c implementation not started | [Automated evidence](evidence/phase3/2026-09-05-audio-authoring-contract.md) covers Format Specification sections 14-15, all sixteen node types, routing and modulation, twelve legality rules, authoring limits, components, sounds/recipes, and buses. The [Phase 3c contract](evidence/phase3/2026-09-05-phase3c-contract.md) adds section 16: automation tracks and precedence, the seven-state lifecycle and release, determinable one-shot endings, voice ceilings and eviction, master protection, and unlock and pause behavior. Phase 3c implementation slices 2-4 and the PRD 129 audio acceptance challenge remain. |
| 4 — Visual engine | Not started | Earlier phases and visual contracts |
| 5 — Events and cadence | Not started | Earlier phases and event/cadence contracts |
| 6 — Scenario director | Not started | Earlier phases and scenario contracts |
@@ -60,3 +60,9 @@ Implementation lives in `src/runtime/audio-contract.js` (declarative tables), `s
`npm test` runs 62 tests with zero failures. Two clean builds produce byte-identical artifacts with digest `64d9932ed863dbae66f309504b88e19370ad2c9017e92c32bd61ea1ea39a3f17`.
Phase 3 is not accepted. Audio automation precedence, the lifecycle state machine, unlock behavior, voice ceilings, and the measured master-protection contract are Phase 3c; no sound has been heard from any build, so the PRD 129 audio acceptance challenge, real GC4 synchronization checks, peak and finite-sample capture, and listening observations all remain open.
## Phase 3c contract (slice 1)
Format Specification section 16 completes the audio subsystem contract. Phase 3c is delivered in four slices, recorded in the implementation plan: the contract (done), lifecycle and voices, automation, and measured master protection. Each slice ends green and committable, because slice 2 replaces the working one-shot timer scaffolding and must not be left half-applied.
Section 16 is contract only. The runtime does not implement it: there is no lifecycle state machine, no voice ceiling, and `automation` and `release` are still rejected as unknown fields. Slice 3c-4 and the PRD 129 audio acceptance challenge are user-observed and cannot be closed by automated tests. No sound has been heard from any build.
+255 -6
View File
@@ -2,7 +2,7 @@
**XZBT format version:** 0.1
**Document revision:** 0.4
**Status:** Normative specification for shared contracts (document, types, ValueSpec, ConditionSpec, resolution, bindings, and transitions); the Audio subsystem authoring contract (Phase 3a sources/control sources, Phase 3b processing/routing/components/buses) is complete; audio automation precedence, lifecycle, and master protection (Phase 3c) and the remaining subsystem contracts are in progress
**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, and the remaining subsystem contracts are 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.
@@ -287,6 +287,12 @@ To ensure consistent error reporting between structural schema validation, seman
| `ERR_NO_AUDIBLE_PATH` | Semantic | A sound's expanded audio graph has no chain of audio routes from a non-control source to `output`. |
| `ERR_COMPONENT_RECURSION` | Semantic | An audio component instantiates itself transitively, or component nesting exceeds 8 levels. |
| `WARN_AUDIO_RATE_CLAMP` | Runtime | A frequency field was clamped to the device's `audioMaxFrequency` at node instantiation. |
| `ERR_AUTOMATION_CONFLICT` | Semantic | More than one automation track directly controls one property in a recipe instance. |
| `ERR_INDETERMINATE_ONESHOT` | Semantic | A `oneshot` recipe has no computable finite ending; its audible path begins at an unbounded source. |
| `WARN_AUTOMATION_FALLBACK` | Runtime | An `exponential` automation segment had a zero or sign-crossing endpoint and fell back to linear interpolation. |
| `WARN_VOICE_LIMIT` | Runtime | A voice ceiling was reached; an instance was evicted or a request refused. |
| `WARN_AUDIO_NONFINITE` | Runtime | A non-finite sample reached the master chain and the containing block was muted. |
| `WARN_AUDIO_UNAVAILABLE` | Runtime | No audio device is available; the performance continues silently. |
## 8. Shared value resolution, bindings, and transitions
@@ -505,7 +511,7 @@ Complete shared contracts before implementing dependent subsystems. Use PRD sect
| Values and conditions | 15-21, 33 | Operator arity/table, division-by-zero protection, sampling timing boundaries, edge-trigger re-arming | **Complete (Rev 0.2)** |
| References and bindings | 13, 17, 31-32 | Target-capability table, instance/input scope, evaluation order, cycles, disabled bindings, exact smoothing | **Complete (Rev 0.3 / GC3)** |
| Actions and transitions | 22-30 | Shared `set`/`override` fields and defaults, override target matrix, interrupted transitions, instance IDs; subsystem action matrices remain with their subsystems | **Shared contract complete (Rev 0.3 / GC3)** |
| Audio | 34-60 | Complete recipe/component shapes, node defaults, automation timing, clamp implementation, one-shot endings and tails, unlock behavior, master protection contract | **Authoring contract complete (Rev 0.4 / Phase 3a-3b):** units, graph objects, all sixteen node types, routing, modulation, graph legality, authoring limits, components, sounds/recipes, and buses. Automation precedence (54), lifecycle states (57), unlock behavior, and runtime safety/master protection (58) remain (Phase 3c) |
| 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 | Subsystem contract (Phase 5) |
| Visuals | 69-89 | Primitive/system/behavior fields, angle and motion units, transform order, depth projection, field behavior, morph compatibility, effect approximation and limits | Subsystem contract (Phase 4) |
| 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)** |
@@ -553,7 +559,7 @@ Structural JSON Schema does not replace semantic validation. The internal schema
This section opens the Audio contract required by section 11 (PRD 34-60). It covers the audio pipeline overview, canonical units, the shared audio-graph object shape, node-field resolution scope, the audio frequency ceiling, audio reproducibility scope, and the six source and control-source node types (PRD 36-42).
Processing nodes, routing and modulation, graph legality, components, sound definitions, and buses (PRD 43-53, 55, 56, 59, 60) are specified in section 15 (Phase 3b). Audio automation precedence (PRD 54), the full runtime lifecycle state machine (PRD 57), and the consolidated safety-limit and master-protection contract (PRD 58) are **Not yet specified** and arrive in Phase 3c.
Processing nodes, routing and modulation, graph legality, components, sound definitions, and buses (PRD 43-53, 55, 56, 59, 60) are specified in section 15 (Phase 3b). Automation tracks and precedence (PRD 54), the runtime lifecycle state machine and release (PRD 57), determinable one-shot endings, voice ceilings, master protection (PRD 58), and unlock and pause behavior (PRD 117-118) are specified in section 16 (Phase 3c).
### 14.1 Pipeline and scope
@@ -596,6 +602,7 @@ An **audio graph object** is the shared container for every audio node network i
| --- | --- | :---: | --- |
| `nodes` | object keyed by node ID | Yes | Each value is a node object (14.4). An empty `nodes` object is `ERR_SCHEMA_VALIDATION`. |
| `routes` | array | No (defaults to `[]`) | Route objects, specified in 15.12-15.13. A graph object with no route reaching `output` is `ERR_NO_AUDIBLE_PATH` unless it is a component graph, which terminates at its declared component output instead (15.15). |
| `automation` | array | No (defaults to `[]`) | Automation tracks, specified in 16.1. Declared on the graph object, never on a node. |
Audio graph objects are embedded at exactly three places, whose surrounding fields are specified in Phase 3b:
@@ -624,7 +631,7 @@ Node fields are **not** added to the section 8.1 target-capability table. A `Bin
This restriction governs **external** targeting only. Graph-internal modulation — a route whose `to` addresses `node.property` (15.13, PRD 52-53) — is the supported mechanism for varying a node field over time and is fully specified in Phase 3b. Section 15.13 lists which properties accept modulation; a modulation route to any other property is `ERR_UNSUPPORTED_TARGET`.
Automation tracks (PRD 54) are a separate Phase 3c stage and are **Not yet specified**; an `automation` field on any node or recipe is `ERR_UNKNOWN_FIELD` until that contract lands.
Automation tracks (PRD 54) are specified in sections 16.1 and 16.2. A track is declared on the graph object alongside `nodes` and `routes`, never on a node, so an `automation` field on a *node object* remains `ERR_UNKNOWN_FIELD`. Automation does not widen the external surface of 14.4: a track is authored inside the graph that owns the property, not in the document's binding list.
### 14.5 Audio frequency ceiling and validation staging
@@ -839,7 +846,7 @@ This section completes the authoring surface of the audio subsystem: the process
It builds directly on section 14: every node object declared here lives in a `nodes` map inside an audio graph object (14.3), its numeric fields follow the resolve-once scope of 14.4, its frequency fields follow the two-tier ceiling of 14.5, and it is externally unaddressable except through the modulation routes defined in 15.13.
Audio automation precedence (PRD 54), the runtime lifecycle state machine (PRD 57), and the runtime half of the safety-limit and master-protection contract (PRD 58 voice ceilings, peak ceiling, numerical tolerance, release behavior, finite-sample handling) remain **Not yet specified** and arrive in Phase 3c.
Automation tracks and precedence (PRD 54), the runtime lifecycle state machine and release (PRD 57), and the runtime half of the safety-limit and master-protection contract (PRD 58 voice ceilings, peak ceiling, numerical tolerance, release behavior, finite-sample handling) are specified in section 16 (Phase 3c). The master-protection *values* there remain provisional pending GC6 measurement.
### 15.1 Processing and routing node set
@@ -1088,7 +1095,7 @@ value = clamp(b + sum(s_i x d_i), property minimum, property maximum)
Multiple legal modulation routes are summed (PRD 53). The clamp uses the property's declared range from its node contract and is the audio application of the safety-clamp stage of section 8.1; it is not a separate precedence system. A control source's output is its own value in its own units before scaling: an `lfo` with `amplitude: 1` and `polarity: "bipolar"` contributes `[-d, +d]` to its target.
Automation (PRD 54) sits between binding and override in the shared pipeline and is **Not yet specified**; in Phase 3b the resolution of a node property is exactly base, then modulation sum, then safety clamp.
Automation (PRD 54) sits between binding and override in the shared pipeline and is specified in sections 16.1 and 16.2. With no automation track on a property, its resolution is exactly base, then modulation sum, then safety clamp.
### 15.14 Graph legality and authoring limits
@@ -1242,3 +1249,245 @@ The following codes are added to the section 7 table, which remains the single a
5. Two modulation routes onto one property sum and then clamp to the property's declared range.
6. A graph exceeding `128` expanded nodes or `256` routes after component expansion is `ERR_NODE_LIMIT_EXCEEDED`, while a graph at exactly those counts passes.
7. Expansion and validation run with no `AudioContext`, confirming the 14.5 separation between semantic validation and instantiation.
## 16. Audio Subsystem Contract — Automation, Lifecycle, and Protection (Phase 3c)
This section closes the audio subsystem. It covers automation tracks and their place in the shared resolution pipeline (PRD 54), the runtime lifecycle state machine and release behavior (PRD 57), determinable one-shot endings, voice ceilings and eviction, master output protection (PRD 58, 120), and unlock and pause behavior for audio (PRD 117-118).
Sections 14 and 15 define what an exhibit may *declare*. This section defines what the runtime *does* with it over time. Where an earlier section deferred a rule to "Phase 3c", this section is the referent.
**Implementation status.** This section is the Phase 3c *contract*; the runtime does not yet implement it. Until slices 3c-2 and 3c-3 land, a document declaring `automation` or `release` is rejected with `ERR_UNKNOWN_FIELD` by the current validator, sound instances have no lifecycle state machine, and no voice ceiling is enforced. The implementation plan records the slice order.
### 16.1 Automation tracks
An automation track drives one node property along an authored curve measured from the owning sound instance's start. Tracks are declared in the `automation` array of a graph object (14.3), alongside `nodes` and `routes`, and are therefore available in recipe graphs and component graphs alike:
```json
{
"automation": [
{
"target": "level.gain",
"mode": "absolute",
"interpolation": "smooth",
"points": [
{ "at": "0ms", "value": 0 },
{ "at": "800ms", "value": 1 },
{ "at": "4s", "value": 0.6 }
]
}
]
}
```
| Field | Type | Required | Notes |
| --- | --- | :---: | --- |
| `target` | string | Yes | `<node-key>.<property>`, resolved in the same graph. Follows the encapsulation rules of 15.15: a component's internals are unreachable, and a component instance exposes only its declared parameters. |
| `mode` | enum | No (default `absolute`) | `absolute`, `offset`, or `scale`. |
| `interpolation` | enum | No (default `linear`) | `step`, `linear`, `exponential`, or `smooth`. |
| `points` | array | Yes | `2` to `256` breakpoints, each `{ "at": DurationSpec, "value": ValueSpec<number> }`. |
`automation` is an array rather than a keyed map because a track has no identity an author needs to reference. It defaults to `[]`.
**Targets.** A track may address any property in the 15.13 modulatable registry, and no other. A track targeting a property absent from that registry is `ERR_UNSUPPORTED_TARGET`; the registry is deliberately the same one modulation uses, so a property is either time-varying or it is not, and the two mechanisms never disagree about which. A `target` naming an undeclared node is `ERR_INVALID_REFERENCE`.
**Exclusivity.** Only one automation track may directly control a property in a recipe instance (PRD 54). Two tracks addressing the same expanded target are `ERR_AUTOMATION_CONFLICT`. This is the automation counterpart of the `ERR_CONFLICTING_BINDING` rule in section 8.2, and for the same reason: two writers to one scalar has no defined answer. Modulation is unaffected — multiple modulation routes onto one property still sum (15.13), because summation *is* their defined answer.
**Points.** `at` is measured from the node instance's start and is sampled once at instantiation (14.4), like every other DurationSpec in this contract. Points must be in strictly increasing `at` order; equal or decreasing times are `ERR_INVALID_RANGE_ORDER`. Fewer than two points is `ERR_SCHEMA_VALIDATION` — a single point is a constant and belongs in the property's base value. Before the first point the track holds the first point's value; after the last point it holds the last point's value and does not loop.
**Modes.** Given the property's resolved base value `b` and the track's current curve value `a`, the track contributes:
| `mode` | Contribution | Use |
| --- | --- | --- |
| `absolute` | `a` | The curve *is* the value; the base is ignored. |
| `offset` | `b + a` | The curve displaces the authored base in the property's own unit. |
| `scale` | `b x a` | The curve multiplies the authored base. |
**Interpolation.** Between two points with values `v0` and `v1` and normalized progress `t` in `[0, 1)`:
| `interpolation` | Value | Notes |
| --- | --- | --- |
| `step` | `v0` | Holds until the next point, then jumps. |
| `linear` | `v0 + (v1 - v0) x t` | — |
| `exponential` | `v0 x (v1 / v0)^t` | Requires both endpoints strictly positive and same-signed; a segment with a zero or sign-crossing endpoint falls back to `linear` and raises `WARN_AUTOMATION_FALLBACK` once per track. A fallback is preferable to failing, because a legal base value can resolve to zero at instantiation. |
| `smooth` | `v0 + (v1 - v0) x (3t^2 - 2t^3)` | Smoothstep; zero first derivative at both endpoints. |
**Limits.** At most `64` automation tracks and `256` total automation points per expanded sound (PRD 58). Exceeding either is `ERR_NODE_LIMIT_EXCEEDED`.
### 16.2 Audio automation precedence
Section 8.1 fixes the shared resolution pipeline. This section states its audio application; it is not a competing system (PRD 54). For a node property:
```text
base ValueSpec -> binding (where supported) -> automation -> winning override -> modulation sum -> safety clamp -> engine parameter
```
Three consequences follow, and they are the whole content of this subsection.
**Binding remains unsupported for node properties.** Section 14.4 stands: an external `BindingSpec` targeting a node field is `ERR_UNSUPPORTED_TARGET`. Automation does not change this, because a track is declared *inside* the graph that owns the property, not in the document's binding list. Adding automation to a subsystem never widens that subsystem's external surface. `audio.buses.<id>.gain` remains the one audio target in the shared registry, and it takes binding, automation, override, and modulation as section 8.1 already states.
**Underlying automation continues while overridden** (PRD 54). An override masks the automation stage's output for as long as it wins; it does not pause, reset, or rewind the track. When the override releases, the property returns to whatever the track has reached by then, not to the value it held when the override took hold. This mirrors the parameter-masking rule of section 8.1, where releasing an override returns toward the current stored value rather than an obsolete snapshot.
**Every stage a target does not expose is absent, not an identity hook.** A property outside the 15.13 registry has no automation stage and no modulation stage. Being numeric grants nothing.
### 16.3 Lifecycle states
A sound instance occupies exactly one state (PRD 57):
| State | Meaning | Audio produced |
| --- | --- | :---: |
| `CREATED` | Graph expanded, values resolved, nodes constructed, nothing connected to a bus. | No |
| `SCHEDULED` | Start time fixed on the audio clock; awaiting it. | No |
| `ACTIVE` | Running and audible. | Yes |
| `RELEASING` | Release envelope running; no new work accepted. | Yes, decaying |
| `FINISHED` | Release complete; output silent; resources not yet reclaimed. | No |
| `DISPOSED` | Every node, connection, automation track, buffer, and subscription released. Terminal. | No |
| `FAILED` | Construction or activation raised; resources reclaimed as for `DISPOSED`. Terminal. | No |
Permitted transitions, and nothing else:
```text
CREATED -> SCHEDULED | FAILED
SCHEDULED -> ACTIVE | RELEASING | FAILED
ACTIVE -> RELEASING | FINISHED | FAILED
RELEASING -> FINISHED | FAILED
FINISHED -> DISPOSED
```
A stop request in `SCHEDULED` goes to `RELEASING` rather than straight to `FINISHED`, so a single code path handles teardown whether or not the voice ever sounded. `ACTIVE -> FINISHED` without a release is reserved for a one-shot that has reached its determinable ending, where the envelope has already returned to zero and a further release would be redundant. `DISPOSED` and `FAILED` are terminal; a second stop on a disposed instance is a no-op, never an error.
Every state change is observable to the runtime's own diagnostics, but state names are not exposed to exhibits. An exhibit describes desired behavior, not runtime bookkeeping.
### 16.4 Release and the internal release gain
The runtime provides an internal release gain for every sound instance, whether or not the exhibit authored one (PRD 57). It is the last node before the instance's bus, it is engine-owned, and no route can bypass or address it.
The default release is `50ms`. A recipe graph object (15.16) may declare one further field, `release`, a DurationSpec in `0ms` to `10s`; `0ms` is permitted and means an immediate cut, which is an author's explicit choice rather than a default. `release` is a recipe field, not a graph-object field: a component graph does not declare it, because release belongs to the sound instance and not to any part of it. Entering `RELEASING` ramps the internal release gain linearly from its current value to zero over the release duration, and `FINISHED` follows at the end of that ramp.
Release is unconditional. It runs on exhibit deactivation, on voice eviction, on pause-induced teardown, and on failure, so no path exists that stops a voice by disconnecting an already-running source. This is what keeps the click-and-pop surface to a single, testable code path.
Disposal must release nodes, connections, automation tracks, buffers, and subscriptions (PRD 57). A `FINISHED` instance that has not reached `DISPOSED` still holds resources and still counts against the voice ceilings of 16.6.
### 16.5 Determinable one-shot endings
A `oneshot` recipe must have a determinable ending (PRD 57). "Determinable" means the runtime can compute a finite upper bound on the instance's audible duration at instantiation, from the resolved graph alone, without observing output.
The bound is computed as the longest path from any source to `output`, where each node contributes:
| Node | Contribution to the bound |
| --- | --- |
| `impulse` | its resolved `duration` |
| `oscillator`, `noise` | **unbounded** — these run until stopped |
| `constant`, `lfo`, `sample-hold` | zero; control sources are not on an audible path (15.14 rule 5) |
| `delay` | `time x ceil(log(1/1000) / log(feedback))` for `feedback > 0`, else `time`; the time for the internal feedback path to fall `60` dB |
| `reverb` | `predelay + decay` |
| `resonator` | the longest `decay` among its retained modes |
| every other node | zero; they colour a signal without extending it |
The instance's ending is the maximum over all source-to-`output` paths of the sum of contributions along that path, plus the release duration of 16.4.
A `oneshot` recipe whose bound is unbounded — that is, one whose audible path begins at an `oscillator` or `noise` source — is `ERR_INDETERMINATE_ONESHOT` at validation. The author's remedies are to declare `mode: "continuous"` and stop the sound explicitly, or to gate the source through an `impulse`-driven path. This is a semantic error rather than a runtime one because it is decidable from the document, and catching it at import is the difference between a rejected exhibit and a voice that never frees itself.
A `continuous` recipe has no ending requirement and runs until explicitly stopped.
### 16.6 Voice ceilings and eviction
Approximate ceilings (PRD 58):
| Ceiling | Value |
| --- | --- |
| One-shot voices | `64` |
| Continuous sounds | `16` |
These are approximate and may be lowered by the runtime on a weaker device (PRD 58, 120). They are never raised above these values by a document; an exhibit cannot request a larger budget. A voice counts against its ceiling from `CREATED` until `DISPOSED`, so instances lingering in `FINISHED` still occupy budget — which is why disposal is part of the contract rather than an optimization.
When a new instance would exceed its ceiling, the runtime applies this policy in order and stops at the first candidate:
1. Dispose the oldest instance already in `FINISHED`.
2. Evict the oldest instance in `RELEASING`.
3. For a one-shot request only: evict the oldest `ACTIVE` one-shot by starting its release.
4. Otherwise refuse the new instance.
Eviction always releases (16.4); it never hard-stops. A one-shot request never evicts a continuous sound, and a continuous request never evicts a one-shot — the two budgets are independent, because a bed of ambience and a burst of transients fail differently and stealing across the boundary produces the worse failure in both directions.
Every eviction and every refusal raises `WARN_VOICE_LIMIT` once, naming the sound and the ceiling. A refusal is not an error: an exhibit that asks for a sixty-fifth simultaneous transient is behaving legally, and the runtime's job is to stay stable and say so.
Resource ceilings are centralized rather than scattered through subsystems (PRD 120). Every limit in sections 14-16 lives in one runtime table.
### 16.7 Master output protection
Every audible signal passes through engine-controlled master protection, and an exhibit cannot bypass it (PRD 58). The chain is engine-owned, is the only path from any bus to the output device, and is not addressable from a document: no route, binding, override, automation, or action can reach it. `audio.master` remains absent from a 0.1 document (15.17).
The protection contract has four parts. Their *shape* is normative now; their *values* are provisional and are confirmed by measurement under GC6 before audio acceptance (PRD 58).
| Part | Contract | Status |
| --- | --- | --- |
| Digital output peak ceiling | The absolute sample value at the output device never exceeds the ceiling. Provisional ceiling `-1.0` dBFS (`0.891` linear). | Provisional; pending GC6 |
| Numerical tolerance | Measured peak may exceed the ceiling by no more than the tolerance across a full measurement window. Provisional tolerance `0.1` dB. | Provisional; pending GC6 |
| Release behavior | Protection gain reduction recovers smoothly and never produces an audible pump on a sustained bed or a click on a transient. Verified by listening, not by a number alone. | Provisional; pending GC6 |
| Finite-sample handling | A non-finite sample (`NaN` or infinity) reaching the master chain is replaced with silence for that sample, the containing block is muted, and `WARN_AUDIO_NONFINITE` is raised once per instance. The count of affected blocks is recorded for the acceptance run. | Normative |
Only finite-sample handling is settled, because it is a correctness rule rather than a measured threshold: a `NaN` in the output buffer is never acceptable at any ceiling, and silencing it is strictly better than propagating it. The other three require real output on real hardware.
**The presence of a compressor does not establish that this contract passes** (PRD 58). A runtime may implement the chain with a limiter, a compressor, a soft clipper, or any combination; what it may not do is claim the contract on the strength of having built one. Acceptance requires the measured peak, the measured tolerance across worst-case overlapping recipes, and listening observations for clicks, clipping, and pumping.
### 16.8 Unlock behavior
Browsers may require user interaction before producing audio (PRD 118). The runtime treats the first user interaction that engages the exhibit as the point at which audio is initialized or resumed. Visual rendering may begin before audio authorization, and the UI must clearly represent muted or unavailable audio state.
When audio unlocks after visual startup, it aligns to the *current* logical position rather than replaying the interval it missed:
- **Expired one-shot invocations are not replayed.** Every one-shot whose invocation time has already passed is discarded, and `INFO_AUDIO_UNLOCK_SKIP` is raised once for the whole batch, carrying the count. One diagnostic for the batch, not one per skipped sound: a long pre-unlock interval would otherwise flood the panel with entries describing a single condition.
- **Continuous sounds start partially elapsed.** A continuous instance that should have begun at logical time `t0` and unlocks at `t1` starts at `ACTIVE` with its automation tracks and sample-hold ticks advanced to `t1 - t0`, not from zero. Its procedural draws are consumed in order to reach that position, so the seeded sequence is identical to an unbroken run — reproducibility does not depend on when the listener clicked.
- **The logical clock is not rewound.** Audio never causes the performance to replay elapsed time.
If audio is unavailable entirely — no `AudioContext`, or unlock refused — the performance continues silently. `WARN_AUDIO_UNAVAILABLE` is raised once, the UI shows the muted state, and no sound instance is created. A missing audio device degrades the exhibit; it does not fail it.
### 16.9 Pause and resume
Explicit pause and document visibility loss suspend audio along with the rest of the performance (PRD 117). Audio-specific rules:
- Suspending holds every voice in place. It does not release them, so resuming does not restart a bed of ambience that never stopped being wanted.
- No catch-up bursts. One-shots that would have been invoked while paused are discarded exactly as at unlock, and report through the same `INFO_AUDIO_UNLOCK_SKIP` batch.
- Automation tracks and sample-hold ticks resume from their held logical position; they do not fast-forward through the paused interval.
- Visibility restoration must not undo an explicit user pause.
- A pause longer than the runtime's stall bound releases all voices rather than holding them indefinitely, and resuming re-creates continuous sounds at the current logical position by the 16.8 rule. The exact bound is fixed with the GC4 audio lookahead and long-stall policy and is **Not yet specified** here.
### 16.10 New diagnostic codes
Added to the section 7 table, which remains the single authoritative list:
| Error Code | Stage | Cause |
| :--- | :--- | :--- |
| `ERR_AUTOMATION_CONFLICT` | Semantic | More than one automation track directly controls one property in a recipe instance. |
| `ERR_INDETERMINATE_ONESHOT` | Semantic | A `oneshot` recipe has no computable finite ending; its audible path begins at an unbounded source. |
| `WARN_AUTOMATION_FALLBACK` | Runtime | An `exponential` automation segment had a zero or sign-crossing endpoint and fell back to linear interpolation. |
| `WARN_VOICE_LIMIT` | Runtime | A voice ceiling was reached; an instance was evicted or a request refused. |
| `WARN_AUDIO_NONFINITE` | Runtime | A non-finite sample reached the master chain and the containing block was muted. |
| `WARN_AUDIO_UNAVAILABLE` | Runtime | No audio device is available; the performance continues silently. |
### 16.11 Required traces before Phase 3c implementation is accepted
Automated, and executable without an audio device:
1. Each automation mode and each interpolation curve produces its documented value at segment start, midpoint, and end; `step` holds; `smooth` has zero slope at both endpoints.
2. A track holds the first point's value before the first point and the last point's value after the last, and does not loop.
3. Two tracks on one expanded target are `ERR_AUTOMATION_CONFLICT`; two modulation routes on that same target still sum.
4. An `exponential` segment with a zero endpoint falls back to linear and raises `WARN_AUTOMATION_FALLBACK` exactly once.
5. An automation track targeting a property outside the 15.13 registry is `ERR_UNSUPPORTED_TARGET`; one naming an undeclared node is `ERR_INVALID_REFERENCE`; one reaching a component's internals is rejected by the 15.15 encapsulation rule.
6. `65` tracks or `257` total points is `ERR_NODE_LIMIT_EXCEEDED`; `64` and `256` pass.
7. Every permitted lifecycle transition is exercised and every forbidden one is rejected; a second stop on a `DISPOSED` instance is a no-op.
8. An override masking an automated property releases to the track's *current* value, not the value held when the override took hold.
9. The determinable-ending bound matches the 16.5 table for a graph combining `impulse`, `delay`, `reverb`, and `resonator`; a `oneshot` fed by an `oscillator` is `ERR_INDETERMINATE_ONESHOT`; the same recipe as `continuous` passes.
10. At the one-shot ceiling the eviction order of 16.6 is followed, `WARN_VOICE_LIMIT` is raised, a continuous sound is never evicted by a one-shot request, and a refused request leaves the runtime stable.
11. Disposal releases every node, connection, automation track, buffer, and subscription; a `FINISHED` instance still counts against its ceiling until `DISPOSED`.
12. A non-finite sample injected at the master chain mutes its block and raises `WARN_AUDIO_NONFINITE` once.
13. Unlocking after a delay skips expired one-shots with a single counted `INFO_AUDIO_UNLOCK_SKIP`, and starts a continuous sound partially elapsed with a procedural sequence identical to an unbroken run.
14. With no `AudioContext` available the performance runs silently, raises `WARN_AUDIO_UNAVAILABLE` once, and creates no instance.
User-observed, and **not** satisfiable by the above:
15. The measured digital output peak across worst-case overlapping recipes sits within the ceiling and tolerance of 16.7, on recorded hardware, with the environment details GC6 requires.
16. Listening observations across the reference exhibits report no clicks, clipping, or pumping on release, eviction, unlock, or pause.
17. The PRD 129 audio acceptance challenge passes in full.
Traces 15 through 17 close Phase 3 slice 3c-4. Until they do, Phase 3 is not accepted no matter how many automated traces pass.
+13
View File
@@ -20,6 +20,19 @@
Every phase ends with tests and an evidence record. A failed gate stops dependent work; it does not silently weaken the requirement.
## Phase 3c delivery slices
Phase 3c is delivered in four slices. Each slice ends green, committed, and pushable on its own, so an interrupted phase never leaves the runtime in a worse state than it started. Slice 2 in particular replaces working scaffolding, and must not be started unless it can be finished.
| Slice | Content | Depends on | Blocked by |
| --- | --- | --- | --- |
| 3c-1 — Contract | Format Specification section 16: automation tracks and precedence (PRD 54), lifecycle states and release (PRD 57), determinable one-shot endings, voice ceilings and eviction, master protection shape (PRD 58), unlock and pause behavior (PRD 117-118). Documentation only; no runtime change. | Sections 14-15 | — |
| 3c-2 — Lifecycle and voices | The seven-state machine, internal release gain, determinable-ending computation, voice ceilings with eviction, and full disposal. Replaces the fixed development timer currently used to release a one-shot. | 3c-1 | — |
| 3c-3 — Automation | Automation tracks, the three modes, the four interpolation curves, and their placement between binding and override in the shared resolution pipeline. Touches the Phase 2 resolution path, so it carries the widest regression surface in Phase 3. | 3c-1, 3c-2 | — |
| 3c-4 — Master protection | Replaces the placeholder master chain with the measured contract: peak ceiling, numerical tolerance, release behavior, and finite-sample handling, confirmed against real output. | 3c-2 | **User-observed.** Requires GC6 measurement and audible verification on real hardware; cannot be closed by automated tests. |
Slices 3c-1 through 3c-3 can be implemented and verified without an audio device. Slice 3c-4 and the PRD 129 audio acceptance challenge cannot: they need measured output and listening observations from a real browser, which the recorded test boundary assigns to user-performed runs. Phase 3 is not accepted until 3c-4 and the audio challenge close, regardless of the state of the first three slices.
An installment is complete only when its own contract, implementation, tests, and evidence record are all in place. A contract installment is reviewed against its PRD sections and the shared contracts before its dependent installment begins; the Phase 3a review, which closed four blocking defects before Phase 3b started, is the worked example.
## MVP completion-criteria map
@@ -0,0 +1,33 @@
# Phase 3c slice 1 — automation, lifecycle, and protection contract
**Date:** September 5, 2026
**Specification baseline:** Format Specification 0.1 revision 0.4, section 16
**Result:** Contract slice complete. No runtime change; no implementation claimed.
## Scope
Phase 3c is delivered in four slices, recorded in the implementation plan. This record covers **slice 3c-1 only**: the contract. Section 16 now specifies automation tracks and their place in the shared resolution pipeline (PRD 54), the seven-state lifecycle and release behavior (PRD 57), determinable one-shot endings, voice ceilings and eviction, master output protection (PRD 58, 120), and unlock and pause behavior for audio (PRD 117-118).
## Decisions worth recording
- **Automation reuses the modulation registry.** A track may target only a property in the 15.13 modulatable registry. A property is either time-varying or it is not; two mechanisms disagreeing about which would be a defect waiting to happen.
- **Automation does not widen the external surface.** A track is declared inside the graph that owns the property, so 14.4 stands unchanged: an external `BindingSpec` to a node field is still `ERR_UNSUPPORTED_TARGET`. `audio.buses.<id>.gain` remains the single audio target in the shared registry.
- **One track per property, but modulation still sums.** `ERR_AUTOMATION_CONFLICT` mirrors `ERR_CONFLICTING_BINDING`: two writers to one scalar has no defined answer, whereas summation is modulation's defined answer.
- **Determinable endings are decided at import.** Section 16.5 gives a concrete per-node contribution table and computes a finite bound from the resolved graph. A `oneshot` whose audible path begins at an `oscillator` or `noise` is `ERR_INDETERMINATE_ONESHOT` — a semantic error, because it is decidable from the document, and the alternative is a voice that never frees itself.
- **Eviction always releases.** No path stops a voice by disconnecting a running source, which keeps the click surface to one testable code path. One-shot and continuous budgets are independent; neither steals from the other.
- **Exponential automation degrades rather than fails.** A zero or sign-crossing endpoint falls back to linear with `WARN_AUTOMATION_FALLBACK`, because a legal base value can resolve to zero at instantiation.
- **Unlock reports once per batch.** A long pre-unlock interval raises a single counted `INFO_AUDIO_UNLOCK_SKIP` rather than flooding the panel with one entry per skipped one-shot.
- **Master protection is specified in shape, provisional in value.** Finite-sample handling is normative now because a `NaN` in the output buffer is never acceptable at any ceiling. The peak ceiling, numerical tolerance, and release behavior carry provisional values and are confirmed by GC6 measurement.
## Verification
This slice changes documentation only. The full suite still runs 62 tests with zero failures and all three exhibit fixtures still validate, confirming no regression, not that section 16 is implemented. Section cross-references resolve with no dangling targets, and every diagnostic code section 16 uses appears in the single section 7 table.
## Not established by this record
- **No implementation.** Sound instances have no lifecycle state machine, no voice ceiling is enforced, and the runtime shell still releases a one-shot on a fixed development timer. A document declaring `automation` or `release` is currently rejected with `ERR_UNKNOWN_FIELD`; those fields become authorable in slices 3c-2 and 3c-3.
- **No measurement.** The peak ceiling, numerical tolerance, and release behavior of 16.7 are provisional. Traces 15 through 17 of 16.11 — measured peak across worst-case overlapping recipes, listening observations, and the PRD 129 audio acceptance challenge — are user-observed and remain open.
- **No sound has been heard from any build.**
- **The GC4 audio long-stall bound** referenced by 16.9 is still unspecified and is fixed with the GC4 lookahead policy.
Phase 3 is not accepted. Slices 3c-2, 3c-3, and 3c-4 remain.