From 94aafeae270d0774d1e0488542bec2401dfb4bfe Mon Sep 17 00:00:00 2001 From: Labyricorn Date: Sun, 6 Sep 2026 02:38:18 +0000 Subject: [PATCH] docs(audio): close seven defects across the phase 3a-3c contract Triage of three independent reviews of sections 14-16 (reviews/00-triage.md) confirmed 13 distinct defects. Seven are doc-only and land here; the rest are held for design decisions or for reconciliation with the slice 3c-2 runtime. - T4 ERR_INVALID_RANGE_ORDER refiled Semantic / Runtime in section 7, covering both sample-hold min/max (literal at import, resolved at instantiation) and automation `at` ordering (import). 14.12 states the split. - T5 16.1 documents the sampling boundary and order for automation point values, satisfying 9.3's requirement of a documented boundary. - T6 15.14 no longer classes the automation track/point limits as runtime ceilings; section 7's ERR_NODE_LIMIT_EXCEEDED cause list extended. - T10 16.2 notes that the override stage is always absent for node properties. - T11 14.9's impulse envelope column relabelled "Value as p -> 1-" with the pre-fade values, resolving the exponential row's self-contradiction. - T12 15.16 names ERR_SCHEMA_VALIDATION for the name and tags caps, which the schema already enforces on definitions/Sound. - T13 16.2's masking cross-reference corrected from 8.1 to 8.3/8.4. Committed path-scoped on main: other modified files are in-flight slice 3c-2 runtime work and are deliberately left in the working tree. Follow-up: T4 is now spec-mandated but unimplemented. audio-graph.js guards the range-order check on both bounds being literal numbers, so procedural bounds pass silently and no instantiation-time check exists. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TvjMnfLaSNVVvzKJbdm6Wj --- docs/XZBT_0-1_Format_Specification.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/XZBT_0-1_Format_Specification.md b/docs/XZBT_0-1_Format_Specification.md index 7eea9b3..6ae7dac 100644 --- a/docs/XZBT_0-1_Format_Specification.md +++ b/docs/XZBT_0-1_Format_Specification.md @@ -282,8 +282,8 @@ To ensure consistent error reporting between structural schema validation, seman | `INFO_AUDIO_UNLOCK_SKIP` | Runtime | One or more pre-unlock one-shots were intentionally not replayed. | | `INFO_AUDIO_PAUSE_SKIP` | Runtime | One or more one-shots invoked while paused were intentionally not replayed. | | `ERR_INVALID_NODE_TYPE` | Semantic | Audio graph node `type` is not a member of Audio Graph Node Set 0.1. | -| `ERR_NODE_LIMIT_EXCEEDED` | Semantic | An authoring limit is exceeded (oscillator partials, resonator modes, expanded nodes or routes per sound). | -| `ERR_INVALID_RANGE_ORDER` | Semantic | Declared paired bounds (e.g. `sample-hold` `min`/`max`) are not in strictly increasing order after resolution. | +| `ERR_NODE_LIMIT_EXCEEDED` | Semantic | An authoring limit is exceeded (oscillator partials, resonator modes, expanded nodes or routes per sound, or automation tracks or points per expanded sound). | +| `ERR_INVALID_RANGE_ORDER` | Semantic / Runtime | Paired bounds (e.g. `sample-hold` `min`/`max`) are not in strictly increasing order — literal pairs at import, resolved pairs at node instantiation — or automation `at` values are not strictly increasing (import). | | `ERR_INVALID_ROUTE` | Semantic | An audio or modulation route is structurally resolvable but illegal under the audio graph legality rules. | | `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. | @@ -740,11 +740,11 @@ A `noise` node has no built-in volume control; author a downstream `gain` node ( An impulse is inherently one-shot: it emits a single finite burst of `duration` and then produces silence. Given resolved amplitude `a`, resolved duration `d`, and normalized progress `p = t / d` over `0 <= p < 1`, the envelope applied to the `color` source is: -| `decay` | Envelope | Value at `p = 1` | +| `decay` | Envelope | Value as `p → 1⁻` | | --- | --- | --- | -| `flat` | `a` | `0` | +| `flat` | `a` | `a` | | `linear` | `a x (1 - p)` | `0` | -| `exponential` | `a x e^(-6.907755 x p)` (`-60` dB at `p = 1`) | `0` | +| `exponential` | `a x e^(-6.907755 x p)` (`-60` dB at `p = 1`) | `a x 0.001` | The envelope is exactly `0` for `p >= 1`. Because `flat` and `exponential` do not reach zero on their own, the runtime applies a terminal linear fade to zero over the final `min(1ms, d x 0.1)` of the burst; this fade is part of the contract, not an optional anti-click measure. @@ -816,7 +816,7 @@ Control-only source (PRD 42). A `sample-hold` reaching `output` through any chai | `max` | ValueSpec\ | `-1000` to `1000` | `1` | Yes (14.4 scope). | | `slew` | DurationSpec 0.1 | `0ms` to `1s` | `0ms` | Sampled once at instantiation (14.4). | -`min` resolving to a value greater than or equal to `max` is `ERR_INVALID_RANGE_ORDER`. +Literal `min` and `max` that are not strictly increasing are `ERR_INVALID_RANGE_ORDER` at import. Where either bound is a resolved ValueSpec, ordering is checked once at node instantiation after 14.4 resolution and is likewise `ERR_INVALID_RANGE_ORDER`. **Tick schedule.** With resolved rate `r`, tick `k` (`k = 0, 1, 2, ...`) occurs at instance-relative time `k / r` seconds, measured from the node instance's start. Tick `0` draws the node's first held value; there is no pre-roll or default-held value before it. Because `rate` is resolved once (14.4), the tick period is fixed for the node instance's lifetime. @@ -1129,7 +1129,7 @@ The authoring-time limits enforced in Phase 3b are the subset of PRD 58 that a d | Resonator modes | `16` | | Custom oscillator partials | `64` | -The remaining PRD 58 limits — approximate one-shot voices (`64`), approximate continuous sounds (`16`), automation tracks (`64`), and automation points (`256`) — are runtime ceilings rather than document properties and belong to Phase 3c with the lifecycle contract. Master protection likewise remains Phase 3c: this section does not establish that the protection contract passes, and the presence of a compressor in a graph never constitutes master protection (PRD 58). +The remaining PRD 58 limits — approximate one-shot voices (`64`) and approximate continuous sounds (`16`) — are runtime ceilings rather than document properties and belong to Phase 3c with the lifecycle contract (16.6). Automation tracks and points are not runtime ceilings: at most `64` tracks and `256` points per expanded sound are decidable after component expansion and are enforced semantically in 16.1 as authoring-time limits. Master protection likewise remains Phase 3c: this section does not establish that the protection contract passes, and the presence of a compressor in a graph never constitutes master protection (PRD 58). ### 15.15 Audio components @@ -1201,6 +1201,8 @@ A `sounds.` entry separates semantic metadata from synthesis (PRD 59): | `bus` | string | No | A declared `audio.buses.`. An undeclared bus is `ERR_INVALID_REFERENCE`. Omitted, the sound routes to the engine master. | | `recipe` | object | Yes | An audio graph object plus the fields below, or `{ "use": "" }` naming an `audio.recipes.`. | +A `name` longer than `128` characters, more than `16` tags, or a tag longer than `32` characters is `ERR_SCHEMA_VALIDATION`. + A recipe graph object adds two fields to the graph shape of 14.3: | Field | Type | Required | Notes | @@ -1294,7 +1296,7 @@ An automation track drives one node property along an authored curve measured fr **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 a **duration literal only** (section 6.1). Unlike every other duration in this contract it may *not* be a procedural `TimeSpec` (section 6.2), and a `TimeSpec` there is `ERR_TYPE_MISMATCH`. The reason is the staging rule of 14.5: points must be in strictly increasing `at` order, and an order over values that resolve randomly at instantiation could not be checked at the semantic stage at all. Fixing `at` as a literal keeps the ordering rule decidable at import, where a malformed curve should be caught. A track's *values* remain full ValueSpecs and may be random; it is only the curve's shape in time that is authored, not sampled. +**Points.** `at` is measured from the node instance's start and is a **duration literal only** (section 6.1). Unlike every other duration in this contract it may *not* be a procedural `TimeSpec` (section 6.2), and a `TimeSpec` there is `ERR_TYPE_MISMATCH`. The reason is the staging rule of 14.5: points must be in strictly increasing `at` order, and an order over values that resolve randomly at instantiation could not be checked at the semantic stage at all. Fixing `at` as a literal keeps the ordering rule decidable at import, where a malformed curve should be caught. A track's *values* remain full ValueSpecs and may be random; it is only the curve's shape in time that is authored, not sampled. A track's point values are resolved once at the owning sound instance's instantiation boundary, sampled from that instance's stream in depth-first, property-document order, with `automation` taken after `nodes` and `routes`. 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. @@ -1327,9 +1329,9 @@ base ValueSpec -> binding (where supported) -> automation -> winning override -> 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..gain` remains the one audio target in the shared registry, and it takes binding, automation, override, and modulation as section 8.1 already states. +**Binding remains unsupported for node properties.** Section 14.4 stands: an external `BindingSpec` targeting a node field is `ERR_UNSUPPORTED_TARGET`, and an `override` action addressing one is barred the same way. The `winning override` stage of the pipeline above is therefore always absent for node properties — absent, not an identity hook (8.1). 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..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. +**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 masked-override rules of sections 8.3 and 8.4, where underlying stages keep evaluating while masked (8.3) and release resolves against the current lower value recomputed without the releasing override (8.4) — never 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.