docs(audio): reconcile phase 3c eviction, pause discard, and recipe fields
Close four specification gaps found during pre-implementation review of section 16. Add the missing \ elease\ field to the recipe graph object table in 15.16, completing the allowed-field table for the container introduced in 16.4. Reconcile the 16.6 eviction policy: step 2 previously called for evicting an instance in RELEASING without specifying how an already-releasing voice could be evicted under the non-hard-stop rule. Clarify that evicting an already-releasing voice advances its release ramp to immediate completion and disposes it, guaranteeing that the eviction policy terminates and reclaims resources in all cases. Resolve the incoherence between 16.8 unlock and 16.9 pause discard batching by adding INFO_AUDIO_PAUSE_SKIP to section 7 and 16.10, and recording that resume discards report their own batch rather than sharing an unlock batch. Clarify that 16.5 ending bound computation operates on the expanded audio graph, resolving paths through component inputs and internal sources deterministically across feed-forward structures, and remove the impossible author remedy of gating an oscillator through an impulse.
This commit is contained in:
@@ -280,6 +280,7 @@ To ensure consistent error reporting between structural schema validation, seman
|
||||
| `WARN_CLOCK_STALL` | Runtime | Elapsed wall time or accumulated work exceeded the fixed-step per-turn limits and was discarded. |
|
||||
| `WARN_CLEANUP_FORCED` | Runtime | A cleanup owner reached its deadline and force-disposed remaining resources. |
|
||||
| `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. |
|
||||
@@ -1200,11 +1201,12 @@ A `sounds.<id>` entry separates semantic metadata from synthesis (PRD 59):
|
||||
| `bus` | string | No | A declared `audio.buses.<id>`. 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": "<recipe-id>" }` naming an `audio.recipes.<id>`. |
|
||||
|
||||
A recipe graph object adds one field to the graph shape of 14.3:
|
||||
A recipe graph object adds two fields to the graph shape of 14.3:
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
| --- | --- | :---: | --- |
|
||||
| `mode` | enum | No (default `oneshot`) | `oneshot` or `continuous` (PRD 57). A `oneshot` recipe must have a determinable ending; the runtime state machine that enforces it is Phase 3c. |
|
||||
| `release` | DurationSpec 0.1 | No (default `50ms`) | `0ms` to `10s` (16.4, PRD 57). Instance release duration. |
|
||||
|
||||
`audio.recipes.<recipe-id>` holds the same shape and exists so several sounds can share one graph. A `recipe` object containing both `use` and graph fields is `ERR_SCHEMA_VALIDATION`, and a `use` naming an undeclared recipe is `ERR_INVALID_REFERENCE`.
|
||||
|
||||
@@ -1383,12 +1385,12 @@ The bound is computed as the longest path from any source to `output`, where eac
|
||||
| `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 |
|
||||
| `component` | the bound of the component's own graph, computed by this same rule over its internal nodes, from its sources or its `input` to its `output` |
|
||||
| `component` | the bound of the component's internal graph: from `input` to `output` for an incoming route, or from internal sources to `output` |
|
||||
| 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. Component contributions recurse, and the recursion terminates because component nesting is capped at `8` levels (15.14 rule 11). A component whose internal graph is itself unbounded makes every path through it unbounded, exactly as an unbounded source does at recipe root.
|
||||
The instance's ending is the maximum over all source-to-`output` paths of the sum of contributions along that path on the expanded audio graph, plus the release duration of 16.4. Because component expansion inlines internal subgraphs into an acyclic graph (15.14 rule 7) bounded by the nesting limit of `8` (rule 11), longest-path computation terminates deterministically for any legal graph topology. A component whose internal graph contains an audible path from an `oscillator` or `noise` makes every path through it unbounded, exactly as an unbounded source does at recipe root.
|
||||
|
||||
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 `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 remedy is to declare `mode: "continuous"` and stop the sound explicitly. 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.
|
||||
|
||||
@@ -1406,11 +1408,11 @@ These are approximate and may be lowered by the runtime on a weaker device (PRD
|
||||
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`.
|
||||
2. Evict the oldest instance in `RELEASING` by advancing its release ramp to immediate completion and disposing it.
|
||||
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.
|
||||
Eviction always releases (16.4); it never hard-stops an active voice. A voice in `RELEASING` is already decaying to zero; evicting it completes that release immediately. 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.
|
||||
|
||||
@@ -1450,7 +1452,7 @@ If audio is unavailable entirely — no `AudioContext`, or unlock refused — th
|
||||
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.
|
||||
- No catch-up bursts. One-shots that would have been invoked while paused are discarded on resume, and report once for the paused batch through `INFO_AUDIO_PAUSE_SKIP` carrying the count.
|
||||
- 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.
|
||||
@@ -1461,6 +1463,7 @@ Added to the section 7 table, which remains the single authoritative list:
|
||||
|
||||
| Error Code | Stage | Cause |
|
||||
| :--- | :--- | :--- |
|
||||
| `INFO_AUDIO_PAUSE_SKIP` | Runtime | One or more one-shots invoked while paused were intentionally not replayed. |
|
||||
| `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. |
|
||||
|
||||
Reference in New Issue
Block a user