From d3cd688a3c21e9d40cecf1ff82efd41a923ece0c Mon Sep 17 00:00:00 2001 From: Labyricorn Date: Sun, 6 Sep 2026 04:29:40 +0000 Subject: [PATCH] docs(visual): close the visual contract with the phase 4c automation, lifecycle, camera, effects, and ceiling rules Format Specification revision 0.7 adds section 19, the third and final visual contract section, covering PRD 85-89 and 119-120. Visual automation reuses the audio track shape of 16.1 and adds only `loop`. It is declared in two scopes: `visuals.automation`, measured from activation, and `visuals.systems..automation`, measured from that system's instantiation boundary, so a track on a spawned system behaves identically however late the spawn happens. Section 8.1 gains exactly four visual rows, all system-level: camera fields, layer opacity, system visibility, and post-effect parameters. No per-object geometry, transform, or style property becomes externally addressable in 0.1, so the statements in 17.14 and trace 19 of 18.10 survive unchanged. This is the decision 17.14 and 18.6 both deferred. Systems are persistent or spawned by an explicit `lifecycle` enum. A spawned system is a template, instantiated only by a spawn action, with a five-state machine plus FAILED, a `0ms` default release, and a spawn ceiling that refuses rather than evicts. The camera matrix is normative, including that parallax multiplies the camera translation only. `projection` is authored once and is neither automatable nor bindable. Seven post-effects, applied in normative array order with a stated approximation boundary. `grain` is explicitly exempt from reproducibility and consumes no procedural stream. Section 19.5 is the centralized ceiling table PRD 120 requires. It separates authoring bounds, which reject an exhibit at import, from runtime ceilings, which shed work by a documented deterministic rule and keep the exhibit running, and it restates every ceiling first fixed in sections 10 and 14 through 18. Its aggregate values are provisional pending the slice 4h GC6 measurement. Two diagnostic codes added: ERR_INVALID_EFFECT_TYPE and WARN_VISUAL_CEILING. ERR_AUTOMATION_CONFLICT widens to cover a visual scope and the behavior collision rather than minting a second code. Documentation only: no runtime, schema, fixture, or test file was touched. The visual contract now contains no unresolved forward reference, and npm test still passes 102 tests with zero failures. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01AzX3848PwqYwzdUCWvjegL --- docs/IMPLEMENTATION_STATUS.md | 35 +- docs/XZBT_0-1_Format_Specification.md | 353 +++++++++++++++++- docs/XZBT_0-1_Implementation_Plan.md | 4 +- .../phase4/2026-09-06-phase4c-contract.md | 207 ++++++++++ 4 files changed, 583 insertions(+), 16 deletions(-) create mode 100644 docs/evidence/phase4/2026-09-06-phase4c-contract.md diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md index f120ae6..bf34247 100644 --- a/docs/IMPLEMENTATION_STATUS.md +++ b/docs/IMPLEMENTATION_STATUS.md @@ -1,10 +1,10 @@ # XZBT implementation status **Updated:** September 6, 2026 -**State:** Phase 3 audio engine implemented through slice 3c-4 and automatically verified; Phase 4 has begun with slices 4a and 4b, the first two of three visual contract sections. Slice 3c-4 hardware measurement/listening acceptance, the Phase 3 audible gates, and the Phase 1 direct-file import/restart observation remain pending +**State:** Phase 3 audio engine implemented through slice 3c-4 and automatically verified; the Phase 4 visual subsystem contract is complete across slices 4a, 4b, and 4c, and no visual runtime code exists yet. Slice 3c-4 hardware measurement/listening acceptance, 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, Phase 2 common grammar, and audio engine through Phase 3c slice 4's implementation pass automated checks. Phase 1's direct-file two-fixture restart observation, Phase 3's audible acceptance (no sound has been heard from a production build), and real-browser measured master protection remain open. The visual subsystem contract is open at Phase 4a-4b; visual implementation, cadence/event, scenario, final generated-UI, performance, and soak work remains assigned to later phases. +**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, Phase 2 common grammar, and audio engine through Phase 3c slice 4's implementation pass automated checks. Phase 1's direct-file two-fixture restart observation, Phase 3's audible acceptance (no sound has been heard from a production build), and real-browser measured master protection remain open. The visual subsystem contract is complete at Phase 4a-4c and its aggregate ceilings are provisional pending the slice 4h GC6 measurement; visual implementation, cadence/event, scenario, final generated-UI, performance, and soak work remains assigned to later phases. The user requested sequential implementation with a stop on problems. The [manual version 3 evidence](evidence/phase0/2026-09-04-user-run-v3.md) verifies embedded data-URL worklet loading in direct-file Chrome. The subsequent [user-performed restart test](evidence/phase0/2026-09-04-user-restart.md) restored Blue Study activity 0.37 and master volume 0.19 immediately on reopening. Native tone output and AudioContext suspend/resume are also observed. Ordinary file import, selection of both exhibits, regular Chrome mode, and [directory cancellation/denial fallback](evidence/phase0/2026-09-05-user-directory-fallback.md) have been confirmed. @@ -14,7 +14,7 @@ The user requested sequential implementation with a stop on problems. The [manua | 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 | Slices 3c-1–3 complete; 3c-4 implementation verified, user acceptance pending | [Contract](evidence/phase3/2026-09-05-phase3c-contract.md), [lifecycle/voices](evidence/phase3/2026-09-05-phase3c-lifecycle-voices.md), [automation](evidence/phase3/2026-09-05-phase3c-automation.md), and [protection evidence](evidence/phase3/2026-09-05-phase3c-protection.md). Slice 4 requires real-browser measurement and user listening; PRD 129 and real GC4 audio acceptance remain open. | -| 4 — Visual engine | Contract slices 4a and 4b complete; 4c–4h pending | [Slice-4a contract evidence](evidence/phase4/2026-09-06-phase4a-contract.md) and [slice-4b contract evidence](evidence/phase4/2026-09-06-phase4b-contract.md). Format Specification section 17 covers PRD 69–76 and section 18 covers PRD 77–84; section 19 (slice 4c) precedes any renderer implementation. Slice 4h and the PRD 130 visual challenge are user-observed. | +| 4 — Visual engine | Contract slices 4a, 4b, and 4c complete; 4d–4h pending | [Slice-4a](evidence/phase4/2026-09-06-phase4a-contract.md), [slice-4b](evidence/phase4/2026-09-06-phase4b-contract.md), and [slice-4c contract evidence](evidence/phase4/2026-09-06-phase4c-contract.md). Format Specification section 17 covers PRD 69–76, section 18 covers PRD 77–84, and section 19 covers PRD 85–89 and 119–120. Slice 4d is the first slice that writes renderer code. Slice 4h and the PRD 130 visual challenge are user-observed. | | 5 — Events and cadence | Not started | Earlier phases and event/cadence contracts | | 6 — Scenario director | Not started | Earlier phases and scenario contracts | | 7 — Dynamic UI | Not started | Earlier phases and UI contracts | @@ -112,3 +112,32 @@ Section 17 is contract only. No visual runtime code exists, the JSON Schema has and nothing has been drawn from any build. The deliberate deferral of live numeric text readouts, and the open question of whether sections 17-19 pass a multi-model review before slice 4d begins, are recorded in the [slice-4a evidence](evidence/phase4/2026-09-06-phase4a-contract.md). + +## Phase 4b and 4c visual contract (slices 2 and 3 of eight) + +Format Specification revision 0.6 adds section 18: visual components and their construct-scoped `inputs`, +particle systems with a normative semi-implicit Euler integrator and `dt`-correct drag, nine placement +distributions with a normative sample order, emitters whose fractional accumulator makes cumulative +emission exactly `floor(rate x t)`, repeaters and the copy-scoped `repeat.*` namespace, a seventeen-behavior +vocabulary and the internal channel set behaviors may write, six procedural fields with a fully specified +coherent-noise function, and trails, ribbons, and links. Seven diagnostic codes were added. + +Revision 0.7 adds section 19 and closes the contract: visual automation in two declaration scopes with +`repeat` and `ping-pong` loop modes, the persistent and spawned system lifecycle with its own six-state +machine and refuse-rather-than-evict spawn ceiling, the normative camera matrix and both projection modes, +a seven-effect post-processing chain with a stated approximation boundary, and the centralized runtime +ceilings for the whole engine. Two diagnostic codes were added, and the section 8.1 target-capability table +gained exactly four visual rows — camera fields, layer opacity, system visibility, and post-effect +parameters. No per-object visual property is externally addressable in 0.1. + +Section 19.5 is now the single centralized ceiling table PRD 120 requires: it restates every ceiling first +fixed in sections 10 and 14 through 18 alongside the aggregate visual ceilings, and separates authoring +bounds, which reject an exhibit at import, from runtime ceilings, which shed work by a documented +deterministic rule and keep the exhibit running. Every aggregate value there is provisional in the same +sense the master-protection values of 16.7 are: the shape is normative, the numbers are confirmed or +replaced by the slice 4h GC6 measurement. + +The visual contract now contains no unresolved forward reference, and `npm test` still passes 102 tests +with zero failures — all three slices are documentation-only. `src/runtime` has no renderer module, the +JSON Schema has no visual definitions, and nothing has been drawn from any build. Whether sections 17-19 +pass a multi-model review before slice 4d begins remains the one open decision, and 4d is the next slice. diff --git a/docs/XZBT_0-1_Format_Specification.md b/docs/XZBT_0-1_Format_Specification.md index 214835d..a8978e3 100644 --- a/docs/XZBT_0-1_Format_Specification.md +++ b/docs/XZBT_0-1_Format_Specification.md @@ -1,8 +1,8 @@ # XZBT Format Specification 0.1 **XZBT format version:** 0.1 -**Document revision:** 0.6 -**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; the Visual subsystem contract is open (Phase 4a scene/primitives/transforms/appearance in section 17 and Phase 4b components/procedural systems/behaviors/fields in section 18, with automation, lifecycle, camera, post-effects, and the centralized ceilings to follow in section 19), and the remaining subsystem contracts are in progress +**Document revision:** 0.7 +**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; the Visual subsystem contract is complete (Phase 4a scene/primitives/transforms/appearance in section 17, Phase 4b components/procedural systems/behaviors/fields in section 18, and Phase 4c automation/lifecycle/camera/post-effects/ceilings in section 19); the aggregate visual ceilings of 19.5 remain provisional pending the slice 4h 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. @@ -55,7 +55,7 @@ Metadata provides provenance and UI presentation details. It never executes or m ```text .[.] ``` - Valid canonical namespace prefixes: `parameters.*`, `state.*`, `signals.*`, `modulators.*`, `audio.buses.*`, `visuals.systems.*`. Dots are strictly forbidden within identifier names themselves. + Valid canonical namespace prefixes: `parameters.*`, `state.*`, `signals.*`, `modulators.*`, `audio.buses.*`, `visuals.systems.*`, `visuals.layers.*`, `visuals.camera.*`, `visuals.effects.*`. Dots are strictly forbidden within identifier names themselves. An ordered container is indexed rather than keyed: `visuals.effects[].` and `points[].x` (17.13) are the two such forms in 0.1. --- @@ -288,7 +288,7 @@ 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_AUTOMATION_CONFLICT` | Semantic | More than one writer directly controls one property: two automation tracks on one recipe instance or one visual scope, or a visual behavior and an automation track on one object channel. | | `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. | @@ -297,7 +297,7 @@ To ensure consistent error reporting between structural schema validation, seman | `ERR_INVALID_SYSTEM_TYPE` | Semantic | A `visuals.systems.` `type` is not a member of the Visual System Set 0.1. | | `ERR_INVALID_PRIMITIVE_TYPE` | Semantic | A visual object `type` is not a member of Visual Primitive Set 0.1. | | `ERR_INVALID_PATH` | Semantic | A path command sequence is structurally valid but illegal: it does not begin with `move`, or it closes a subpath that is not open. | -| `ERR_VISUAL_LIMIT_EXCEEDED` | Semantic / Runtime | A visual authoring or runtime ceiling is exceeded (layers, group nesting, vertices, path commands, spline points, gradient stops, filters, particle capacity, emitter capacity, repeater count, behaviors per object, declared or referenced fields, trail length, link count). | +| `ERR_VISUAL_LIMIT_EXCEEDED` | Semantic / Runtime | A visual authoring or runtime ceiling is exceeded (layers, group nesting, vertices, path commands, spline points, gradient stops, filters, particle capacity, emitter capacity, repeater count, behaviors per object, declared or referenced fields, trail length, link count, automation tracks and points, post-effect entries). | | `WARN_VISUAL_APPROXIMATION` | Runtime | A declared appearance feature is unavailable on the active renderer and the documented fallback was used. | | `ERR_INVALID_DISTRIBUTION_TYPE` | Semantic | A placement distribution `type` is not a member of the placement distribution set of 18.3. | | `ERR_INVALID_DISTRIBUTION` | Semantic | A placement distribution is structurally valid but illegal in its context, such as an index-driven placement on a continuous-rate emission. | @@ -306,6 +306,8 @@ To ensure consistent error reporting between structural schema validation, seman | `ERR_INVALID_FIELD_TYPE` | Semantic | A `visuals.fields.` `type` is not a member of the procedural field set of 18.7. | | `ERR_MORPH_INCOMPATIBLE` | Semantic | A `morph` behavior's source and target differ in `type`, point count, or spline `mode`. | | `ERR_UNBOUNDED_EMISSION` | Semantic | A particle system or emitter declares emission but neither a per-item `lifetime` nor a total `limit`, so it can create items without bound. | +| `ERR_INVALID_EFFECT_TYPE` | Semantic | A `visuals.effects` entry `type` is not a member of the post-effect vocabulary of 19.4. | +| `WARN_VISUAL_CEILING` | Runtime | An aggregate runtime ceiling of 19.5 was reached: work was shed or a spawn refused, and the exhibit continues. | ## 8. Shared value resolution, bindings, and transitions @@ -324,8 +326,12 @@ Stages that the target does not expose are absent, not identity hooks available | `parameters.` | Declared primitive; stored user value | Yes | No | Yes | No | Declared `min`/`max`; integer targets round to the nearest integer after interpolation and before clamping | | `state.` | Declared primitive; current state value | Yes | No | Yes | No | Declared `min`/`max` | | `audio.buses..gain` | Number; bus `gain` ValueSpec | Yes | Yes | Yes | Yes | `[0, 4]` in 0.1 before master output protection | +| `visuals.camera.` | Number; camera `x`, `y`, `zoom`, `rotation`, `focalLength` | Yes | Yes | Yes | Yes | The per-field range of 19.3 | +| `visuals.layers..opacity` | Number; layer `opacity` | Yes | Yes | Yes | No | `[0, 1]` | +| `visuals.systems..visible` | Boolean; system `visible` | Yes | No | Yes | No | — | +| `visuals.effects[].` | Number; the post-effect parameter | Yes | Yes | Yes | No | The parameter's range in 19.4 | -`signals.*` and `modulators.*` are read-only sources and never binding or action targets. `instances.*` is a runtime action-addressing namespace, not a ValueSpec or binding namespace. Sound inputs are sampled at sound invocation and cannot be ordinary binding targets. Visual properties, audio recipe-instance properties, and any target family absent from this table remain unsupported until their subsystem contract adds an explicit capability row. Merely being numeric does not grant automation, override, or modulation support. +`signals.*` and `modulators.*` are read-only sources and never binding or action targets. `instances.*` is a runtime action-addressing namespace, not a ValueSpec or binding namespace. Sound inputs are sampled at sound invocation and cannot be ordinary binding targets. The four visual rows are the whole visual surface: section 19.1 adds them and adds nothing else, so per-object geometry, transform, and style properties, procedural-system fields, behavior fields, field strengths, and emitter rates are not binding, `set`, or `override` targets in 0.1. Audio recipe-instance properties and any target family absent from this table remain unsupported until their subsystem contract adds an explicit capability row. Merely being numeric does not grant automation, override, or modulation support. Parameters store user configuration separately from exhibit defaults. State stores simulation values separately from parameters. State is not an implicit layer above user configuration. References read the value resolved for the current logical tick. Parameter controls read and edit stored user values and show an override indicator whenever any live override exists for the parameter, including a masked or releasing override. @@ -526,7 +532,7 @@ Complete shared contracts before implementing dependent subsystems. Use PRD sect | 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 | **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 | **In progress (Rev 0.6 / Phase 4a-4b):** section 17 fixes the pipeline, canonical units and the angle convention, the `visuals` container, layers, the scene model and coordinate/fit modes, depth sign and sorting, the fourteen primitives, common properties, transform composition order, appearance and the safe blend set, paths and splines, and the once-at-instantiation resolution boundary. Section 18 fixes visual components and their `inputs` scope, particle systems and their normative integrator, the nine placement distributions, emitters and exact emission timing, repeaters and the `repeat.*` scope, the seventeen-behavior vocabulary and its channel set, the six procedural fields and their normative coherent-noise function, and trails, ribbons, and links. Automation, lifecycle, camera, post-effects, and the centralized ceilings remain Phase 4c (section 19) | +| Visuals | 69-89 | Primitive/system/behavior fields, angle and motion units, transform order, depth projection, field behavior, morph compatibility, effect approximation and limits | **Complete (Rev 0.7 / Phase 4a-4c):** section 17 fixes the pipeline, canonical units and the angle convention, the `visuals` container, layers, the scene model and coordinate/fit modes, depth sign and sorting, the fourteen primitives, common properties, transform composition order, appearance and the safe blend set, paths and splines, and the once-at-instantiation resolution boundary. Section 18 fixes visual components and their `inputs` scope, particle systems and their normative integrator, the nine placement distributions, emitters and exact emission timing, repeaters and the `repeat.*` scope, the seventeen-behavior vocabulary and its channel set, the six procedural fields and their normative coherent-noise function, and trails, ribbons, and links. Section 19 fixes visual automation and its two declaration scopes and loop modes, the four visual rows it adds to the section 8.1 table and nothing beyond them, the persistent and spawned system lifecycle and its ownership, the camera matrix and both projection modes, the seven post-effects and the boundary of permitted approximation, and the centralized runtime ceilings for the whole engine. Aggregate ceiling *values* are provisional pending the slice 4h GC6 measurement | | 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)** | | Generated UI | 103-107 | Widget compatibility, button actions, parameter validation and override display, group/control ordering | Subsystem contract (Phase 7) | | Runtime/library | 108-112, 117-127 | Clock/audio synchronization, stalls, seed/stream derivation complete in GC4; import equality, update compatibility, transactions, failure recovery, persistence schema remain | **Shared clock/PRNG contract complete (Rev 0.3 / GC4); subsystem remainder Phase 1/8** | @@ -1644,10 +1650,12 @@ A **visual system** is the addressable unit of the visual subsystem and the reso | --- | --- | :---: | --- | --- | | `type` | string enum | Yes | — | `graphic` in this section. Section 18 adds `particles`, `emitter`, and `repeater`. Any other value is `ERR_INVALID_SYSTEM_TYPE`. | | `layer` | string | No | implicit layer | A declared layer ID (17.5). | -| `visible` | ValueSpec\ | No | `true` | A hidden system is not drawn; its behaviors and automation still advance. | +| `visible` | ValueSpec\ | No | `true` | A hidden system is not drawn; its behaviors and automation still advance. It is an external target by the section 8.1 rows 19.1 adds. | +| `lifecycle` | string enum | No | `persistent` | `persistent` or `spawned` (19.2). A `spawned` system is a template: declared and validated at import, instantiated only by a `spawn` action. | +| `automation` | array | No | `[]` | System-scoped automation tracks (19.1), with `at` measured from this system's instantiation boundary. | | *(type-specific fields)* | — | Per type | — | 17.8 for `graphic`; 18.2-18.5 for the others. | -Lifecycle fields (`lifetime`, `ownership`, `inputs`, `cancelWithScenario`) belong to spawned systems and are specified in 19.2. A `graphic` system declared in `visuals.systems` is persistent. +Lifecycle fields (`lifetime`, `release`, `ownership`, `inputs`, `cancelWithScenario`) are legal only when `lifecycle` is `spawned` and are specified in 19.2; on a `persistent` system each is `ERR_UNKNOWN_FIELD`. A system declared in `visuals.systems` without a `lifecycle` field is persistent. ### 17.8 The `graphic` system @@ -1866,7 +1874,9 @@ Time variation therefore comes from exactly three mechanisms, all specified late 2. **Visual automation** (19.1) — declared tracks over numeric properties, with the `step`, `linear`, `exponential`, and `smooth` curves and the `repeat` and `ping-pong` loop modes of PRD 85. 3. **External control** — bindings, overrides, and modulation, for exactly those visual properties that section 19.1 adds to the section 8.1 target-capability table. -Until section 19.1 lands, no visual property is in that table. A `BindingSpec`, `set` action, or `override` action addressing a visual property — for example `visuals.systems.horizon.content.band.style.opacity` — is `ERR_UNSUPPORTED_TARGET`. Section 1.3 reserves `visuals.systems.*` as a *reference path* namespace; reserving a namespace does not grant a capability, and as section 8.1 states, merely being numeric does not grant automation, override, or modulation support. +Mechanisms 1 and 2 are internal writers declared inside the visual subsystem; mechanism 3 comes from outside it. Adding the first two never widens the third (16.2). + +Section 19.1 adds exactly four visual rows to that table, all system-level: `visuals.camera.`, `visuals.layers..opacity`, `visuals.systems..visible`, and `visuals.effects[].`. No per-object property is among them. A `BindingSpec`, `set` action, or `override` action addressing one — for example `visuals.systems.horizon.content.band.style.opacity` — is `ERR_UNSUPPORTED_TARGET`, and remains so in 0.1. Section 1.3 reserves `visuals.systems.*` as a *reference path* namespace; reserving a namespace does not grant a capability, and as section 8.1 states, merely being numeric does not grant automation, override, or modulation support. ### 17.15 New diagnostic codes @@ -1912,7 +1922,7 @@ Traces 15 and 16 close Phase 4 slice 4h. Until they do, Phase 4 is not accepted This section continues the Visual contract opened in section 17. It covers reusable visual components (PRD 77), particle systems (PRD 78), placement distributions (PRD 79), emitters (PRD 80), repeaters (PRD 81), the behavior vocabulary (PRD 82), procedural fields (PRD 83), and trails, ribbons, and links (PRD 84). -Section 17 remains the owner of the scene, the primitive set, the transform model, and appearance; every construct here composes those and adds none of its own geometry. Visual automation (PRD 85), visual lifecycle and ownership (PRD 86), camera and projection (PRD 87), post-processing (PRD 88), and visual safety limits together with the centralized runtime ceilings (PRD 89, 119-120) are specified in section 19 (Phase 4c). The forward references to 19.1, 19.2, and 19.5 in this section are the only unresolved numeric references it contains. +Section 17 remains the owner of the scene, the primitive set, the transform model, and appearance; every construct here composes those and adds none of its own geometry. Visual automation (PRD 85), visual lifecycle and ownership (PRD 86), camera and projection (PRD 87), post-processing (PRD 88), and visual safety limits together with the centralized runtime ceilings (PRD 89, 119-120) are specified in section 19 (Phase 4c), which resolves every reference to 19.1, 19.2, and 19.5 made here. The subject-neutrality rule of 17.1 governs this section unchanged. No component, distribution, behavior, or field is named after an exhibit's subject: there is no `snow`, `rain`, `flock`, or `traffic` construct. The fourteen PRD 130 challenge items must be reachable by composing this generic vocabulary. @@ -2331,3 +2341,324 @@ User-observed, and **not** satisfiable by the above: 21. The early combined GC6 benchmark of slice 4h, which is what fixes whether the ceilings named throughout this section are the right ones. Traces 1-19 belong to slice 4e, where the procedural systems exist to run them. Traces 20 and 21 close slice 4h. Phase 4 is not accepted until both do, no matter how many automated traces pass. + +--- + +## 19. Visual Subsystem Contract — Automation, Lifecycle, Camera, Effects, and Ceilings (Phase 4c) + +This section closes the Visual contract sections 17 and 18 opened. It covers visual automation and its loop modes (PRD 85), the visual lifecycle and ownership of persistent and spawned systems (PRD 86), the camera and its projection modes (PRD 87), the post-effect chain (PRD 88), and visual safety limits together with the centralized runtime ceilings of the whole engine (PRD 89, 119-120). + +Sections 17 and 18 define what an exhibit may *declare*. This section defines what the runtime *does* with it over time and what it refuses to do, which is the same division sections 14-15 and 16 use for audio. Every forward reference to 19.1, 19.2, and 19.5 left open by sections 17 and 18 resolves here; after this section the visual contract contains no unresolved forward reference. + +**Implementation status.** No visual runtime code exists. Sections 17, 18, and 19 are contract only: the JSON Schema has no visual definitions, `src/runtime` has no renderer module, and nothing has been drawn from any build. Slice 4d begins the renderer; 4f implements this section. The ceilings of 19.5 are provisional in the same sense the master-protection values of 16.7 are provisional — their *shape* is normative now, their *values* are confirmed by the slice 4h GC6 measurement. + +### 19.1 Visual automation + +A visual automation track drives one numeric visual property along an authored curve on the logical clock (PRD 85). Tracks reuse the audio automation shape of 16.1 field for field, so an author who has written one has written the other; this subsection states only what differs. + +```json +{ + "visuals": { + "camera": { "x": 800, "y": 450, "zoom": 1 }, + "automation": [ + { + "target": "camera.zoom", + "mode": "absolute", + "interpolation": "smooth", + "loop": { "mode": "ping-pong", "count": "infinite" }, + "points": [ + { "at": "0ms", "value": 1 }, + { "at": "24s", "value": 1.35 } + ] + } + ] + } +} +``` + +| Field | Type | Required | Default | Notes | +| --- | --- | :---: | --- | --- | +| `target` | string | Yes | — | A property path in the automatable registry below, relative to the array's scope. | +| `mode` | enum | No | `absolute` | `absolute`, `offset`, or `scale`, with the contributions 16.1 fixes. | +| `interpolation` | enum | No | `linear` | `step`, `linear`, `exponential`, or `smooth`, with the curves 16.1 fixes, including the `exponential` fallback to linear and its `WARN_AUTOMATION_FALLBACK`. | +| `loop` | object | No | — | Absent means the track holds its last point's value forever, exactly as an audio track does. | +| `points` | array | Yes | — | `2` to `256` breakpoints of `{ "at": , "value": ValueSpec }`, strictly increasing in `at`. | + +**Two declaration scopes, each with its own time origin.** An `automation` array may appear in exactly two places: + +| Scope | Time origin for `at` | Targets | +| --- | --- | --- | +| `visuals.automation` | Exhibit activation | `scene.*`, `layers..*`, `camera.*`, and `effects[].*` properties in the registry below. | +| `visuals.systems..automation` | That system's instantiation boundary (17.14) — activation for a persistent system, the spawn moment for a spawned one | Properties of that system, addressed relative to the system. | + +The second scope is what makes automation work on a spawned system: a track written against spawn-relative time behaves identically however late the spawn happens, which is the same property 16.1 gives an audio track measured from its sound instance's start. A track in one scope naming a target in the other is `ERR_INVALID_REFERENCE`; automation never reaches across scope, and no track may address another system. + +`at` is a duration literal only, never a procedural `TimeSpec`, for the reason 16.1 gives: the strictly-increasing ordering rule must stay decidable at import. Point *values* remain full ValueSpecs and resolve once, at the owning scope's instantiation boundary, sampled in depth-first document order after that scope's other fields. + +**Loop modes** (PRD 85). `loop` is `{ "mode": "repeat" | "ping-pong", "count": = 1> | "infinite" }`, `count` defaulting to `infinite`. The loop period is the `at` of the last point. `repeat` restarts from the first point, so a track whose first and last values differ steps discontinuously at the wrap; that is the author's choice, not a defect. `ping-pong` plays the curve forward then backward, and one `count` unit is one *complete* forward-and-back cycle, so that a finite count always ends where it began. Before the first point the track holds the first point's value under either mode. After a finite `count` is exhausted the track holds the value it ended on and stops advancing. An `infinite` loop is what PRD 85 means by loop count being infinite for persistent visual behavior; it is legal only on a persistent scope, and an `infinite` loop on a spawned system with a finite `lifetime` (19.2) is legal and simply ends with the system. + +**The automatable registry.** A track may address these properties and no others. A `target` outside this registry is `ERR_UNSUPPORTED_TARGET`; a `target` naming an undeclared layer, system, object, or effect index is `ERR_INVALID_REFERENCE`. + +| Scope | Automatable properties | +| --- | --- | +| `scene` | `depthFog.near`, `depthFog.far`, `depthFog.density` | +| `layers.` | `opacity`, `parallax` | +| `camera` | `x`, `y`, `zoom`, `rotation`, `focalLength` | +| `effects[]` | Every numeric parameter of the effect's own table in 19.4 | +| A system | `visible` is **not** automatable (it is boolean); for a `graphic` system, any numeric `transform`, `style`, or geometry property of an object in its `content` tree, addressed by container keys; for `particles` and `emitter`, `rate`, `position.x`, `position.y`, `acceleration.x`, `acceleration.y`, `acceleration.z`, and `drag`; for `repeater`, the numeric fields of its `step` block | + +Non-numeric properties are outside the registry by construction: PRD 85 says automation targets numeric properties, and a curve between two colors or two booleans is a different mechanism (a life ramp, 18.2) with a different contract. + +**Exclusivity, and composition against behaviors.** Only one track may directly control one property of one instance; two tracks on one expanded target are `ERR_AUTOMATION_CONFLICT`, the same rule and the same code as 16.1. A behavior (18.6) that writes a channel an automation track also targets on the same object is `ERR_AUTOMATION_CONFLICT` as well: a behavior accumulates or multiplies onto a channel while an automation `mode` displaces the base, and two writers with different composition rules on one scalar have no defined answer. This is the resolution 18.6 defers here. + +Where they do *not* collide, the order is fixed: the shared pipeline of 8.1 resolves the property, and the object's behaviors then compose over that resolved value in array order, per 18.6. Behaviors are the object's own internal motion; the pipeline's output is the base they displace. + +**The visual rows of the section 8.1 target-capability table.** This slice adds exactly four target families to the shared registry. They are all *system-level*: camera, layer, system visibility, and effect parameters. + +| Target family | Type and base | Binding target | Automation | Override | Additive modulation | Safety clamp | +| --- | --- | :---: | :---: | :---: | :---: | --- | +| `visuals.camera.` | Number; camera `x`, `y`, `zoom`, `rotation`, `focalLength` (19.3) | Yes | Yes | Yes | Yes | The per-field range of 19.3 | +| `visuals.layers..opacity` | Number; layer `opacity` (17.5) | Yes | Yes | Yes | No | `[0, 1]` | +| `visuals.systems..visible` | Boolean; system `visible` (17.7) | Yes | No | Yes | No | — | +| `visuals.effects[].` | Number; the effect's own parameter (19.4) | Yes | Yes | Yes | No | The parameter's range in 19.4 | + +Nothing else. Per-object geometry, transform, and style properties are **not** externally addressable in 0.1; nor are procedural-system fields, behavior fields, field strengths, emitter rates, or particle parameters. A `BindingSpec`, `set` action, or `override` action addressing one of them remains `ERR_UNSUPPORTED_TARGET`, exactly as 17.14 and trace 19 of 18.10 state, and those two statements survive this slice unchanged. + +The reason is the one 16.2 gives for audio. Automation is declared *inside* the subsystem that owns the property, so adding it never widens the subsystem's external surface; binding, `set`, and `override` come from outside, and every target they can reach is plumbing that slices 4d through 4f must carry, that the override stack must mask and release correctly, and that a future revision cannot withdraw. A camera, a layer's opacity, a system's visibility, and an effect's amount are the four handles an exhibit needs to make its visuals respond to state — Exhibit D's instrument displays are built from state-driven *geometry inside a system*, which automation and behaviors reach without any external capability. The narrower surface is therefore not a limitation on what 0.1 can express; it is the smallest surface that expresses it, and a later revision can widen it compatibly, while nothing can narrow it. + +Boolean targets take no automation and no modulation stage: those stages are absent, not identity hooks (8.1). `visuals.layers..visible` is deliberately **not** in the table; a layer is hidden by animating its `opacity` to `0`, which is continuous and needs no second mechanism. + +**Limits.** At most `128` automation tracks and `2048` total automation points per exhibit, counted across both scopes and across every live spawned instance. Exceeding either is `ERR_VISUAL_LIMIT_EXCEEDED`. These are the "animation records" of PRD 89 and are restated in the centralized table of 19.5. + +### 19.2 Visual lifecycle and ownership + +A visual system is either **persistent** or **spawned** (PRD 86). The discriminator is explicit: + +| Field | Type | Required | Default | Notes | +| --- | --- | :---: | --- | --- | +| `lifecycle` | enum | No | `persistent` | `persistent` or `spawned`. Added to the 17.7 system field table by this section. | +| `lifetime` | DurationSpec | No | — | `spawned` only. Logical duration from instantiation to the start of release. Absent means the instance runs until removed or its owner terminates. | +| `release` | DurationSpec | No | `0ms` | `spawned` only. `0ms` to `10s`. | +| `ownership` | enum | No | inherited | `spawned` only. `persistent` transfers the instance to the performance root (10.1). Any other value is `ERR_SCHEMA_VALIDATION`. | +| `inputs` | object | No | `{}` | `spawned` only. Declared parameters, with the shape, types, and construct scope 18.1 fixes for visual components. | +| `cancelWithScenario` | boolean | No | `true` | `spawned` only. `false` requires `ownership: "persistent"`, or `ERR_UNSUPPORTED_TARGET`. | + +A lifecycle field on a `persistent` system is `ERR_UNKNOWN_FIELD`, per the strict unknown-field policy. This resolves the deferral 17.7 records. + +**A spawned system is a template, not a drawn system.** A `visuals.systems.` with `lifecycle: "spawned"` is declared, validated, and counted against the authoring limits at import, and is **not** instantiated or drawn at activation. It is instantiated only by a `spawn` action naming it, once per action, with the supplied values bound to its `inputs`. Its instantiation boundary (17.14) is the spawn moment; every ValueSpec in it, and every point value of its own `automation` array, resolves there. Two spawns of one template therefore sample independently and reproducibly, from the stream child key `#` (9.3), with the ordinal monotonic per template from `0`. + +The `spawn` and `remove` **action shapes** belong to the Action Model and are not fixed here, exactly as section 16 fixes the sound-instance lifecycle without redefining the `sound` action. This subsection fixes the *resource* contract that those actions operate on. A spawned instance is addressed through the `instances.*` runtime namespace section 1.3 reserves; per 8.1 that namespace is an action-addressing namespace and never a ValueSpec, binding, or automation target. + +**States.** A visual system instance occupies exactly one state: + +| State | Meaning | Drawn | +| --- | --- | :---: | +| `CREATED` | Template expanded, values resolved, objects constructed, not yet in a layer. | No | +| `ACTIVE` | In its layer and advancing on the logical clock. | Yes | +| `RELEASING` | Release ramp running; no new items are created; existing items still advance. | Yes, fading | +| `FINISHED` | Release complete; contributes nothing to the frame; resources not yet reclaimed. | No | +| `DISPOSED` | Objects, particles, emitted items, trail history, automation tracks, behavior state, field references, and subscriptions released. Terminal. | No | +| `FAILED` | Construction raised; resources reclaimed as for `DISPOSED`. Terminal. | No | + +```text +CREATED -> ACTIVE | FINISHED | FAILED +ACTIVE -> RELEASING | FINISHED | FAILED +RELEASING -> FINISHED | FAILED +FINISHED -> DISPOSED +``` + +There is no `SCHEDULED` state. The audio machine has one because a voice is committed to the audio clock before it sounds (16.3); a visual system has no second clock to commit to, so a spawn either constructs and becomes `ACTIVE` on the next logical tick or it fails. State names are runtime bookkeeping and are not exposed to exhibits. + +**Release is a composited fade, and its default is immediate.** Entering `RELEASING` ramps the instance's composited opacity linearly from `1` to `0` over `release`, then enters `FINISHED`. The default is `0ms` — immediate removal — where the audio default is `50ms`, and the difference is deliberate: an audio release exists to prevent a click, which is a defect, while a visual pop is merely abrupt and is sometimes exactly what an author wants. An author who wants a fade declares one. A release of `0ms` still passes through `RELEASING` for one tick, so a single teardown path handles every removal, which is the property 16.4 is protecting. + +Release is unconditional and runs on `lifetime` expiry, on an explicit `remove`, on owner termination, on exhibit deactivation, and on failure. Per 10.2, cleanup uses the shorter of the authored release and `5s`, and the cleanup owner's five-second deadline still applies, after which remaining instances are force-disposed with `WARN_CLEANUP_FORCED`. + +**Ownership** follows 10.1 without amendment. A spawned instance inherits the dispatch context of the action that created it — a scenario instance, or the performance root — unless it declares `ownership: "persistent"` and the action requests it. A scenario-owned instance is released during that scenario's cleanup; `cancelWithScenario: false` on a persistent-owned instance is what lets a visual outlive the scenario that started it, which is the case PRD 86 names. A persistent system declared in `visuals.systems` is owned by the performance root, is created at activation, and is released only at deactivation. + +**A persistent system's `visible` is not its lifecycle.** A hidden system still advances its behaviors, automation, particles, and emissions, and still counts against every ceiling in 19.5 (17.7). Hiding is a compositing decision; removal is a lifecycle decision. An exhibit that wants the work to stop must remove the system, not hide it. + +**Ceiling.** At most `64` spawned instances live at once across every template, counted from `CREATED` until `DISPOSED`. A spawn that would exceed it is refused — not evicted — and raises `WARN_VISUAL_CEILING` once, naming the template and the ceiling. Refusal rather than eviction is the opposite of the voice policy of 16.6, and for a reason: an evicted voice fades out in milliseconds and is forgiven, whereas a visual system evicted mid-scene disappears in front of the viewer. A refused spawn is not a scenario failure. + +### 19.3 Camera and projection + +`visuals.camera` is optional; absent, every field takes its default and the camera is an identity view of the scene (PRD 87). + +| Field | Type | Required | Default | Range | Notes | +| --- | --- | :---: | --- | --- | --- | +| `x` | ValueSpec\ | No | Scene center | — | Camera center in scene units. | +| `y` | ValueSpec\ | No | Scene center | — | Camera center in scene units. | +| `zoom` | ValueSpec\ | No | `1` | `0.01` to `100` | Uniform scale about the projection center. | +| `rotation` | ValueSpec\ | No | `0` | — | Degrees, positive toward `+y` (17.2). | +| `projection` | enum | No | `orthographic` | — | `orthographic` or `perspective`. Any other token is `ERR_SCHEMA_VALIDATION`. | +| `focalLength` | ValueSpec\ | No | `1000` | above `0` | Scene units. `ERR_OUT_OF_BOUNDS` at or below `0`. | + +The scene center is `(width / 2, height / 2)` for `virtual`, `(0.5, 0.5)` for `normalized`, and the display center for `viewport` (17.4). A camera default that is not the scene center would make an exhibit that never mentions the camera look different from one that declares its defaults, which is the kind of surprise a contract exists to prevent. + +**The projection center** is the center of the display rectangle after `fit` resolution (17.4). Every camera operation and the perspective factor of 17.6 act about that one point. + +**The camera matrix is normative.** For a layer with parallax `p` (17.5), the view matrix applied to that layer's composited content is, in composition order: + +```text +V = T(c) x S(zoom) x R(-rotation) x T(-c) x T(-p x (x - c_x), -p x (y - c_y)) +``` + +where `c` is the projection center. Parallax multiplies the camera *translation* only, never its zoom or rotation, so a distant layer drifts more slowly than a near one without also being scaled or tilted differently — which is what makes the deep parallax field of PRD 130 item 1 hold together. A layer with `parallax: 0` is pinned to the display and is the mechanism for a fixed overlay. + +Under `perspective`, each object additionally receives the uniform factor `focalLength / (focalLength + z)` about the projection center, exactly as 17.6 states, applied after `V`. Under `orthographic`, `z` still drives sorting, parallax, and fog but never scale. Culling of objects at `z <= -focalLength` is unchanged from 17.6 and raises no diagnostic. + +**`projection` is not automatable and not externally addressable.** Switching projection mid-run has no continuous meaning: there is no value between `orthographic` and `perspective`, so an interpolating mechanism cannot express the change and a stepping one would snap the whole scene. `x`, `y`, `zoom`, `rotation`, and `focalLength` are automatable and are in the 8.1 table by 19.1; `projection` is authored once. This is the direct reading of PRD 87, which says camera *properties* may be automated or bound to state and modulators, in a document whose other enums are likewise fixed at authoring. + +**Camera and appearance units.** Stroke widths, blur radii, glow radii, and shadow offsets are measured in scene units before camera scaling (17.2), so zooming in thickens a stroke exactly as it enlarges the geometry it outlines. This is the behavior a design space implies and it is stated here because it is the question a renderer author asks first. + +### 19.4 Post-processing (`visuals.effects`) + +`visuals.effects` is an ordered array of post-effect entries applied to the composited frame after every layer has been composited and before display (PRD 88). It defaults to `[]`; an exhibit with no effects pays for none. + +Every entry is `{ "type": , "enabled": ValueSpec, ...parameters }`. `enabled` defaults to `true`; a disabled effect is skipped entirely and costs nothing that frame. A `type` outside the seven below is `ERR_INVALID_EFFECT_TYPE`, and any parameter a type's own table does not declare is `ERR_UNKNOWN_FIELD`. + +| `type` | Parameters | Range | Default | Effect | +| --- | --- | --- | --- | --- | +| `vignette` | `amount` | `0` to `1` | `0.5` | Darkening toward the frame edge. | +| | `radius` | `0` to `1` | `0.75` | Fraction of the half-diagonal at which darkening begins. | +| | `softness` | `0` to `1` | `0.5` | Edge softness of that transition. | +| | `color` | `color` | `#000000` | Color blended toward. | +| `scanlines` | `amount` | `0` to `1` | `0.3` | Blend strength of the line pattern. | +| | `spacing` | `1` to `64` | `3` | Line period in device pixels. | +| | `thickness` | `0` to `1` | `0.5` | Fraction of the period the dark band occupies. | +| | `speed` | — | `0` | Periods per logical second of vertical scroll. | +| `grain` | `amount` | `0` to `1` | `0.15` | Blend strength of the noise. | +| | `scale` | `0.25` to `16` | `1` | Grain cell size in device pixels. | +| | `speed` | `0` to `60` | `24` | Regenerations per logical second. `0` freezes one pattern. | +| `color-adjust` | `brightness`, `contrast`, `saturation` | `0` to `4` | `1` | The 17.12 filter semantics, applied to the frame. | +| | `hueRotate` | — | `0` | Degrees. | +| `blur` | `radius` | `0` to `32` | `4` | Scene units, scaled by camera `zoom`. | +| `bloom` | `threshold` | `0` to `1` | `0.7` | Luminance above which a pixel contributes. | +| | `intensity` | `0` to `2` | `0.6` | Strength of the added bloom. | +| | `radius` | `0` to `32` | `8` | Scene units. | +| `fade` | `color` | `color` | `#000000` | Full-surface blend, the transition primitive. | +| | `amount` | `0` to `1` | `0` | Blend fraction. | + +Every numeric parameter above is a ValueSpec\, is automatable by 19.1, and is an 8.1 binding and override target by the table in that subsection. `color` parameters and `type` are authored once. A parameter outside its range is `ERR_OUT_OF_BOUNDS` when it is a literal at import and clamped with no diagnostic when it resolves out of range at instantiation, which is the staging rule 14.5 fixes for audio and the same rule applies here. + +**Order is array order**, and it is normative because these operations do not commute: `bloom` before `color-adjust` blooms the authored colors, and after it blooms the adjusted ones. `fade` last is the usual transition placement. The runtime does not reorder the chain for efficiency. + +**Cost and passes.** `blur` and `bloom` each require a full-frame readback and count as **two** passes; `bloom` needs a bright-pass and a blend. `vignette`, `scanlines`, `grain`, `color-adjust`, and `fade` are single-pass pointwise operations over the frame and count as **one**. At most `4` effect entries are authorable (`ERR_VISUAL_LIMIT_EXCEEDED`), which is why the post-effect pass ceiling of 19.5 is `8`: four entries of two passes each. The offscreen compositing buffers of 17.5 and 17.12 have their own separate ceiling in the same table; the two budgets are counted separately because one is per object and the other is per frame. + +**Approximation.** PRD 88 permits a runtime to approximate an effect differently depending on renderer capability, and 0.1 fixes the boundary of that permission: + +* `blur` and `bloom` may be computed at a reduced resolution and upsampled, and their radius may be realized by a separable two-pass approximation. A runtime that does either raises `WARN_VISUAL_APPROXIMATION` once per effect instance, never once per frame, and the frame remains within one visually equivalent step of the exact result. +* `grain` is **exempt from reproducibility and consumes no procedural stream.** Its per-pixel noise is generated from a frame counter and pixel coordinate, not from a seeded stream (9.3), so two runs of one seed are not guaranteed pixel-identical while the grain is enabled. This is the only place in the visual contract where that is true, and it is stated rather than discovered: section 9.3 promises identical *decisions*, grain drives no decision, and pulling per-pixel samples from the exhibit's stream would make the stream position depend on resolution and frame rate — which would break every other reproducibility guarantee to protect one that nobody needs. +* An effect a renderer cannot perform at all is skipped for the frame and raises `WARN_VISUAL_APPROXIMATION` once per effect instance. It is never silently replaced with a different effect. + +No post-effect is subject-specific, and none may be used to smuggle geometry into the frame: the chain reads the composited frame and writes the composited frame, and it has no access to scene objects, systems, or state beyond its own declared parameters. + +### 19.5 Visual safety limits and the centralized runtime ceilings + +PRD 120 requires that resource ceilings be centralized rather than scattered through individual subsystems. This subsection is that center. Every ceiling the engine enforces appears here, whether it was first stated in section 14, 15, 16, 17, or 18; a subsystem section states a limit for locality, and this table is where a runtime reads it and where a change is made. + +**Two kinds of limit, and they behave differently.** PRD 89 requires that clearly excessive values normally fail validation rather than be silently transformed into something materially different. That gives the dividing line: + +| Kind | Checked | On breach | Diagnostic | +| --- | --- | --- | --- | +| **Authoring bound** — a value in the document | Import (structural or semantic) | The exhibit is rejected | `ERR_VISUAL_LIMIT_EXCEEDED`, `ERR_NODE_LIMIT_EXCEEDED`, `ERR_OUT_OF_BOUNDS`, or `ERR_SCHEMA_VALIDATION`, as each limit's own section states | +| **Runtime ceiling** — an aggregate the document does not name | Every logical tick or frame | Work is shed by a documented rule; the exhibit keeps running | `WARN_VISUAL_CEILING`, raised once per ceiling per second, never once per shed item | + +An authoring bound fails loudly because the author wrote a number and should learn it is wrong. A runtime ceiling sheds quietly because the exhibit is legal and the machine is merely small, and PRD 119 requires the runtime to remain usable when performance degrades. Neither is ever silently transformed into something materially different, which is the behavior PRD 89 forbids. + +**Aggregate runtime ceilings.** + +| Ceiling | Value | Shedding rule when reached | +| --- | --- | --- | +| Live particles, all systems | `8192` | Evict the oldest particle in the system holding the most, then retry (18.2 is oldest-first within a system) | +| Live emitted items, all emitters | `2048` | Evict the oldest item in the emitter holding the most (18.4) | +| Live spawned system instances | `64` | Refuse the spawn (19.2). Never evict a live system | +| Link segments drawn per tick | `4096` | Drop the tail of the ascending pair order of 18.8, deterministically | +| Trail history samples retained | `32768` | Truncate the oldest samples of the longest histories first | +| Field evaluations per logical tick | `32768` | Evaluate fields for the nearest items first and treat the remainder as zero force for that tick | +| Offscreen compositing buffers per frame | `16` | Draw the excess objects without their non-default `blend`, `mask`, `blur`, `glow`, or `filters`, farthest-`z` first, and raise `WARN_VISUAL_APPROXIMATION` | +| Post-effect passes per frame | `8` | Skip the trailing effects of the chain (19.4) | +| Device-pixel-ratio multiplier | `min(devicePixelRatio, 2)` | Clamp | +| Backing store | `4096 x 4096` device pixels | Lower the multiplier until it fits, to a floor of `1` | +| Automation records, all scopes | `128` tracks / `2048` points | Authoring bound (19.1), not a shed | +| Logical ticks of shed before the runtime reports degradation | `120` consecutive | Raise `WARN_VISUAL_CEILING` naming the sustained ceiling | + +**Ceilings from the other subsystems, restated for centralization.** + +| Ceiling | Value | Fixed in | +| --- | --- | --- | +| One-shot voices | `64` | 16.6 | +| Continuous sounds | `16` | 16.6 | +| Expanded audio nodes and routes per sound | Per section 15.14 | 15.14 | +| Audio component nesting | `8` | 15.15 | +| Resonator modes | Per section 14 | 15.10 | +| Audio automation tracks / points per sound | `64` / `256` | 16.1 | +| Ordinary dispatch units per logical tick | `1024` | 10.5 | +| Nested event depth | `16` | 10.5 | +| Termination-hook actions per owner | `256` | 10.5 | +| Layers | `16` | 17.5 | +| Group nesting | `8` | 17.9 | +| Polyline / polygon vertices | `512` | 17.9 | +| Spline points | `256` | 17.13 | +| Gradient stops | `16` | 17.12 | +| Filters per object | `4` | 17.12 | +| Visual component nesting | `8` | 18.1 | +| Particle `capacity` per system | `4096` | 18.2 | +| Emitter `capacity` per system | `512` | 18.4 | +| Repeater `count` | `1024` | 18.5 | +| Behaviors per object | `8` | 17.10, 18.6 | +| Declared fields / referenced per system | `8` / `4` | 18.7 | +| Trail `length` | `128` | 18.8 | +| `maxLinks` per system | `1024` | 18.8 | +| Post-effect entries | `4` | 19.4 | +| Blur and bloom radius | `32` scene units | 19.4 | +| Spawned instances | `64` | 19.2 | + +Scenario instances and timeline expansions are the two entries of PRD 120's list that no contract has yet fixed; they are set with the scenario contract in Phase 6 and belong in this table when they are. Naming the gap here is what keeps the table the single center rather than a snapshot. + +**Lowering, never raising.** A runtime may lower any ceiling in either table on a weaker device, exactly as 16.6 permits for voices. A document can never raise one: there is no field, parameter, or capability by which an exhibit requests a larger budget, and a document that could would be able to defeat the protection PRD 89 exists to provide. + +**Frame-time governance** (PRD 119). The runtime monitors frame interval and sheds work in the order of the aggregate table above — particles first, then emitted items, then links, then trail history, then compositing buffers, then post-effect passes — before it lowers the device-pixel-ratio multiplier, and it never sheds an authored persistent object or a declared system. The order is normative so that a degraded frame is the same degraded frame everywhere. The *thresholds* that trigger governance are provisional and are derived from the slice 4h GC6 measurement, in the same relationship 16.7 has with the audio protection values: the shape is normative now, the numbers are confirmed by measurement. Until 4h runs, every value in the aggregate table is a considered estimate and nothing more, and no evidence record may describe them as measured. + +### 19.6 New diagnostic codes + +Added to the section 7 table, which remains the single authoritative list: + +| Error Code | Stage | Cause | +| :--- | :--- | :--- | +| `ERR_INVALID_EFFECT_TYPE` | Semantic | A `visuals.effects` entry `type` is not one of the seven of 19.4. | +| `WARN_VISUAL_CEILING` | Runtime | An aggregate runtime ceiling of 19.5 was reached and work was shed or a spawn refused. | + +Two, and no more. Every other diagnostic this section needs already exists and is reused rather than duplicated under a visual-specific name, on the principle 17.15 and 18.9 state: `ERR_AUTOMATION_CONFLICT`, `WARN_AUTOMATION_FALLBACK`, `ERR_UNSUPPORTED_TARGET`, `ERR_INVALID_REFERENCE`, `ERR_VISUAL_LIMIT_EXCEEDED`, `ERR_NODE_LIMIT_EXCEEDED`, `ERR_OUT_OF_BOUNDS`, `ERR_UNKNOWN_FIELD`, `ERR_SCHEMA_VALIDATION`, `WARN_VISUAL_APPROXIMATION`, and `WARN_CLEANUP_FORCED`. + +`ERR_AUTOMATION_CONFLICT`'s section 7 cause text is widened by this slice from "in a recipe instance" to cover a visual scope and the behavior collision of 19.1. Widening the one code is correct where minting a second would not be: the condition is identical in both subsystems — two writers, one scalar, no defined answer — and an author who has learned what the code means in audio has learned what it means in visuals. + +### 19.7 Required traces before slice 4f implementation is accepted + +Automated, and executable without a display measurement: + +1. Each automation `mode` and each interpolation curve produces its documented value at segment start, midpoint, and end on a visual target, matching the audio results of trace 1 of 16.11 exactly, since the curves are shared. +2. `loop.mode` `repeat` restarts from the first point at the loop period and `ping-pong` reverses; a finite `count` of `n` ends on the first point under `ping-pong` and on the last under `repeat`; `infinite` never stops; a track with no `loop` holds its last value forever. +3. A `visuals.automation` track's `at` is measured from activation and a `visuals.systems..automation` track's from that system's instantiation boundary: two spawns of one template `4s` apart produce identical curves offset by `4s`. +4. A track naming a target in the other scope, another system, an undeclared layer, or an out-of-range effect index is `ERR_INVALID_REFERENCE`; a target outside the 19.1 registry — a `color`, a `visible`, a field `strength`, a behavior parameter — is `ERR_UNSUPPORTED_TARGET`. +5. Two tracks on one expanded visual target are `ERR_AUTOMATION_CONFLICT`; a behavior writing a channel a track targets on the same object is `ERR_AUTOMATION_CONFLICT`; the same behavior on an object with no track on that channel passes and composes by the 18.6 rule. +6. `129` tracks or `2049` total points across both scopes and live spawned instances is `ERR_VISUAL_LIMIT_EXCEEDED`; `128` and `2048` pass. +7. Each of the four new section 8.1 rows takes binding, override, and — where the row permits — automation and modulation, through the shared pipeline, with masking and release behaving as 8.3 and 8.4 require; a `set` or `override` on any per-object, particle, emitter, behavior, or field property is `ERR_UNSUPPORTED_TARGET`, and trace 19 of 18.10 still passes unchanged. +8. A lifecycle field on a `persistent` system is `ERR_UNKNOWN_FIELD`; `cancelWithScenario: false` without `ownership: "persistent"` is `ERR_UNSUPPORTED_TARGET`; a `spawned` template is not drawn at activation and draws only after a spawn. +9. Every permitted state transition of 19.2 is exercised and every forbidden one rejected; a `release` of `0ms` still passes through `RELEASING` for one tick; a second `remove` on a `DISPOSED` instance is a no-op. +10. Two spawns of one template resolve independently from `#`, and one seed reproduces both exactly across two runs and across two frame rates. +11. A scenario-owned spawned instance is released at scenario cleanup; a `persistent`-owned instance with `cancelWithScenario: false` survives it; a cleanup that exceeds the 10.2 deadline force-disposes with `WARN_CLEANUP_FORCED`. +12. A `65`th live spawned instance is refused with a single `WARN_VISUAL_CEILING`, no live instance is evicted, and the refusal is not a scenario failure. +13. The camera matrix of 19.3 places a known object at documented display coordinates for each of zoom, rotation, translation, and their composition; a layer at `parallax: 0.2` translates by one fifth of the camera translation and is neither scaled nor rotated differently; `parallax: 0` is pinned. +14. Under `perspective`, an object at `z` renders at the documented factor about the projection center and an object at `z <= -focalLength` is culled with no diagnostic; under `orthographic` the same object's scale is unchanged while its sorting, parallax, and fog are not. +15. Each of the seven effects produces its documented change on a known frame; array order is honored (`bloom` before and after `color-adjust` differ); a disabled effect costs no pass; a `type` outside the seven is `ERR_INVALID_EFFECT_TYPE`; a fifth entry is `ERR_VISUAL_LIMIT_EXCEEDED`. +16. `grain` consumes no procedural stream: with grain enabled, two runs of one seed produce identical decision traces, and disabling grain does not shift any later procedural sample. +17. A `blur` or `bloom` computed at reduced resolution raises `WARN_VISUAL_APPROXIMATION` once per effect instance and not once per frame; an unavailable effect is skipped, not substituted. +18. Each aggregate ceiling of 19.5 sheds by its documented rule under a synthetic overload: the shed is deterministic, the exhibit keeps running, `WARN_VISUAL_CEILING` is raised at most once per ceiling per second, and no authored persistent object or declared system is shed. +19. No document can raise a ceiling: every field, parameter, and action that names a count is checked to clamp or reject, never to widen. + +User-observed, and **not** satisfiable by the above: + +20. The PRD 130 visual acceptance challenge passes in full, judged on a real display. This is trace 15 of 17.16 and trace 20 of 18.10, restated for locality; it is one gate, not three. +21. The early combined GC6 benchmark of slice 4h at `1920 x 1080` runs its 30-second warm-up and 120-second window on recorded hardware, and its results either confirm the aggregate ceilings of 19.5 or replace them. + +Traces 1-19 belong to slice 4f. Traces 20 and 21 close slice 4h. Phase 4 is not accepted until both do, no matter how many automated traces pass, and the ceilings of 19.5 are not measured until trace 21 says so. diff --git a/docs/XZBT_0-1_Implementation_Plan.md b/docs/XZBT_0-1_Implementation_Plan.md index 3bb16d4..0e88f5c 100644 --- a/docs/XZBT_0-1_Implementation_Plan.md +++ b/docs/XZBT_0-1_Implementation_Plan.md @@ -43,7 +43,7 @@ Phase 4 is the largest milestone in this plan: twenty-one PRD sections (69-89) p | --- | --- | --- | --- | | 4a — Contract I | **Complete at Revision 0.5.** Format Specification section 17: pipeline and canonical units, the `visuals` container, layers, scene model and coordinate/fit modes (PRD 70), 2.5D depth (PRD 71), the fourteen geometry primitives (PRD 72), common visual properties (PRD 73), the transform model (PRD 74), appearance and the safe blend set (PRD 75), and paths and splines (PRD 76). Documentation only; no runtime change. | Sections 1-13 | — | | 4b — Contract II | Format Specification section 18: visual components (PRD 77), particle systems (PRD 78), placement distributions (PRD 79), emitters (PRD 80), repeaters (PRD 81), the behavior vocabulary (PRD 82), procedural fields (PRD 83), and trails, ribbons, and links (PRD 84). Documentation only. **Complete at Revision 0.6.** | 4a | — | -| 4c — Contract III | Format Specification section 19: visual automation and loop modes (PRD 85), visual lifecycle and ownership (PRD 86), camera and projection (PRD 87), post-processing (PRD 88), visual safety limits (PRD 89), the centralized runtime ceilings of PRD 120, and the visual rows added to the section 8.1 target-capability table. Documentation only. | 4a, 4b | — | +| 4c — Contract III | **Complete at Revision 0.7.** Format Specification section 19: visual automation and loop modes (PRD 85), visual lifecycle and ownership (PRD 86), camera and projection (PRD 87), post-processing (PRD 88), visual safety limits (PRD 89), the centralized runtime ceilings of PRD 120, and the four system-level visual rows added to the section 8.1 target-capability table. Documentation only. | 4a, 4b | — | | 4d — Renderer core | Canvas 2D backend, the scene/coordinate/fit resolution, layers, the transform stack, the fourteen primitives, appearance and paint, paths and splines, and depth sorting. Widest new-code surface in the phase. | 4a | — | | 4e — Procedural systems | Visual components, particles, distributions, emitters, repeaters, behaviors, fields, trails/ribbons, and distance links. | 4b, 4d | — | | 4f — Automation, lifecycle, and effects | Visual automation into the shared section 8 resolution path, spawned-system lifecycle and ownership, camera projection, post-effect passes, and enforcement of the centralized ceilings. Touches the Phase 2 and Phase 3c resolution path, so it carries the widest regression surface in Phase 4. | 4c, 4e | — | @@ -52,7 +52,7 @@ Phase 4 is the largest milestone in this plan: twenty-one PRD sections (69-89) p Slices 4a through 4g are implementable and verifiable without a display measurement. Slice 4h and the PRD 130 visual acceptance challenge are not: the challenge's judgement is visual, and the benchmark is a hardware measurement, which the recorded test boundary assigns to user-performed runs. Phase 4 is not accepted until 4h and the visual challenge close, regardless of the state of the first seven slices. -The three contract slices land before any of the implementation slices, because the section 17-19 contract fixes decisions that 4d through 4f would otherwise have to relitigate: the resolution boundary for visual ValueSpec fields, the transform composition order, depth semantics, and which visual properties are externally addressable at all. Whether the finished contract also passes a multi-model review before 4d begins — the pass that found thirteen defects in sections 14-16, nine of which any single reviewer would have missed — is an open decision recorded here rather than assumed. +The three contract slices are complete. They landed before any implementation slice because the section 17-19 contract fixes decisions that 4d through 4f would otherwise have to relitigate: the resolution boundary for visual ValueSpec fields, the transform composition order, depth semantics, and which visual properties are externally addressable at all — which section 19.1 settles at exactly four system-level target families and nothing per-object. Whether the finished contract also passes a multi-model review before 4d begins — the pass that found thirteen defects in sections 14-16, nine of which any single reviewer would have missed — is the one open decision, and it is now due rather than pending, because 4d is the next slice. Phase 3 remains unaccepted while its user-observed gates (traces 15-17 of section 16.11 and the Phase 1 direct-file restart observation) are open. Those gates are pending measurement, not failed, and no Phase 4 slice depends on them; Phase 4 nonetheless carries that debt forward and cannot close it. diff --git a/docs/evidence/phase4/2026-09-06-phase4c-contract.md b/docs/evidence/phase4/2026-09-06-phase4c-contract.md new file mode 100644 index 0000000..2e45045 --- /dev/null +++ b/docs/evidence/phase4/2026-09-06-phase4c-contract.md @@ -0,0 +1,207 @@ +# Phase 4c evidence — Visual subsystem contract, part III (Format Specification section 19) + +**Date:** September 6, 2026 +**Slice:** 4c — Contract III (documentation only; no runtime change) +**Baseline:** `main` at `2deec01` (Phase 4a and 4b devlog) +**Specification revision:** 0.6 → 0.7 +**PRD coverage:** 85-89, 119-120 + +## What this slice fixes + +Section 19 closes the Visual contract. It covers visual automation and its loop modes, the visual +lifecycle and ownership of persistent and spawned systems, the camera and its two projection modes, the +seven-effect post-processing chain and the exact boundary of permitted renderer approximation, and +visual safety limits together with the centralized runtime ceilings of the whole engine. + +With this slice the visual contract contains no unresolved forward reference. Every reference to 19.1, +19.2, and 19.5 left open by sections 17 and 18 now resolves, and the section 11 register's Visuals row +reads **Complete (Rev 0.7 / Phase 4a-4c)**. + +## Decisions taken, so 4f does not relitigate them + +1. **Automation has two declaration scopes, each with its own time origin.** `visuals.automation` is + measured from activation and reaches scene, layer, camera, and effect properties; + `visuals.systems..automation` is measured from that system's instantiation boundary and reaches + that system's own properties. The second scope is what makes a track on a spawned system behave + identically however late the spawn happens — the property 16.1 gives an audio track measured from its + sound instance's start. A track naming a target in the other scope is `ERR_INVALID_REFERENCE`. +2. **The track shape is the audio track shape.** `target`, `mode`, `interpolation`, `points`, the three + modes, the four curves, the `exponential` fallback and its `WARN_AUTOMATION_FALLBACK`, the + two-to-`256` point bound, and the strictly-increasing duration-literal `at` are all reused verbatim + from 16.1 rather than restated differently. `loop` is the only field visual automation adds, because + it is the only thing PRD 85 asks for that PRD 54 did not. +3. **`ping-pong` counts complete round trips.** One `count` unit is forward and back, so a finite count + always ends where it began. `repeat` restarts from the first point and may step discontinuously at + the wrap; that is the author's choice, not a defect. +4. **The section 8.1 table gains exactly four visual rows, all system-level:** + `visuals.camera.`, `visuals.layers..opacity`, `visuals.systems..visible`, and + `visuals.effects[].`. Nothing per-object becomes externally addressable in 0.1. This is + the decision 17.14 and 18.6 both deferred here, and it is deliberately the smallest surface that + expresses what the reference exhibits need: Exhibit D's instrument displays are state-driven geometry + *inside* a system, which automation and behaviors reach with no external capability at all. A later + revision can widen the surface compatibly; nothing can narrow it. +5. **`visuals.layers..visible` is deliberately absent** from that table. A layer hides by animating + `opacity` to `0`, which is continuous and needs no second mechanism. +6. **The automatable registry is broader than the section 8.1 rows, and that is the point.** Automation + is declared inside the subsystem that owns the property, so adding it never widens the subsystem's + external surface — the rule 16.2 states for audio. An emitter's `rate` is automatable and is still + `ERR_UNSUPPORTED_TARGET` for a binding, `set`, or `override`, so trace 19 of 18.10 passes unchanged. +7. **A behavior and an automation track on the same object channel is `ERR_AUTOMATION_CONFLICT`.** A + behavior accumulates or multiplies onto a channel while an automation `mode` displaces the base; two + writers with different composition rules on one scalar have no defined answer. Where they do not + collide, the shared pipeline resolves first and behaviors compose over the resolved value in array + order. This is the composition rule 18.6 defers here. +8. **`lifecycle` is an explicit enum, not an inference.** A system is `persistent` unless it says + `spawned`; lifecycle fields on a persistent system are `ERR_UNKNOWN_FIELD`. Making the discriminator + the presence of a field would have been implicit magic that no validator could report clearly. +9. **A spawned system is a template.** It is declared, validated, and counted at import, and is not + instantiated or drawn at activation. Its instantiation boundary is the spawn moment and its stream + child key is `#`, mirroring the particle rule of 18.2. +10. **The visual lifecycle has no `SCHEDULED` state.** The audio machine has one because a voice is + committed to the audio clock before it sounds; a visual system has no second clock to commit to. + Five states plus `FAILED`, with the same terminal and no-op-on-second-stop rules as 16.3. +11. **Visual release defaults to `0ms`, where audio defaults to `50ms`.** An audio release prevents a + click, which is a defect; a visual pop is merely abrupt and is sometimes what the author wants. A + `0ms` release still passes through `RELEASING` for one tick, so a single teardown path handles every + removal — the property 16.4 exists to protect. +12. **A spawn at the ceiling is refused, never evicted** — the opposite of the voice policy of 16.6, and + for a stated reason: an evicted voice fades out in milliseconds and is forgiven, while a visual + system evicted mid-scene disappears in front of the viewer. A refused spawn is not a scenario + failure. +13. **The camera matrix is normative**, including where parallax enters it: parallax multiplies the + camera *translation* only, never its zoom or rotation, so a distant layer drifts more slowly without + also being scaled or tilted differently. The projection center is the center of the display + rectangle after `fit` resolution, and every camera operation and the perspective factor of 17.6 act + about that one point. +14. **`projection` is authored once and is neither automatable nor bindable.** There is no value between + `orthographic` and `perspective`: an interpolating mechanism cannot express the change and a stepping + one would snap the whole scene. PRD 87's automatable camera *properties* are the five numeric ones. +15. **Post-effect order is normative and the runtime does not reorder the chain.** `bloom` before + `color-adjust` blooms authored colors; after it, adjusted ones. These operations do not commute, so + array order is contract rather than convenience. +16. **`grain` is exempt from reproducibility and consumes no procedural stream.** Its per-pixel noise + comes from a frame counter and pixel coordinate, not a seeded stream. Section 9.3 promises identical + *decisions*; grain drives no decision, and pulling per-pixel samples from the exhibit's stream would + make stream position depend on resolution and frame rate — breaking every other reproducibility + guarantee to protect one nobody needs. This is the only such exemption in the visual contract, and + it is stated rather than discovered. +17. **Approximation has a boundary.** `blur` and `bloom` may be computed at reduced resolution and + upsampled, raising `WARN_VISUAL_APPROXIMATION` once per effect instance and never once per frame. An + effect a renderer cannot perform is skipped, never silently substituted with a different one. +18. **Authoring bounds fail; runtime ceilings shed.** This is the dividing line PRD 89 implies and 19.5 + states: a value the author wrote that is out of range rejects the exhibit, while an aggregate the + document never named sheds work by a documented rule and keeps running, because PRD 119 requires the + runtime to remain usable when performance degrades. Neither is ever silently transformed into + something materially different. +19. **Every shedding rule is deterministic and normative**, so a degraded frame is the same degraded + frame everywhere: oldest-particle-first from the largest system, the ascending pair order of 18.8 + for links, farthest-`z` first for compositing buffers, and the trailing effects of the chain for + passes. +20. **A document can never raise a ceiling.** There is no field, parameter, or action by which an + exhibit requests a larger budget; a runtime may lower any ceiling on a weaker device, exactly as + 16.6 permits for voices. + +## Amendments to sections 1, 7, 8, 11, 17, and 18 + +All consequences of this slice rather than corrections. No decision recorded in section 17 or 18 was +reversed. + +- **1.3** adds `visuals.layers.*`, `visuals.camera.*`, and `visuals.effects.*` to the canonical namespace + prefixes, and records that an ordered container is indexed rather than keyed — the + `visuals.effects[]` and `points[]` forms, which are the only two in 0.1. +- **7** gains `ERR_INVALID_EFFECT_TYPE` and `WARN_VISUAL_CEILING`; `ERR_AUTOMATION_CONFLICT`'s cause + text widens from "in a recipe instance" to cover a visual scope and the behavior collision; + `ERR_VISUAL_LIMIT_EXCEEDED`'s cause text names automation records and post-effect entries. +- **8.1** gains the four visual rows, and its closing paragraph now states that those four are the whole + visual surface rather than that visual properties are unsupported. +- **11**'s Visuals row reads **Complete (Rev 0.7 / Phase 4a-4c)** and names what section 19 fixes and + what remains provisional. +- **17.7**'s system field table gains `lifecycle` and `automation` rows, and its lifecycle-field + deferral is replaced by the rule that those fields are legal only when `lifecycle` is `spawned`. +- **17.14**'s "Until section 19.1 lands, no visual property is in that table" paragraph is replaced by + the four rows that now exist and the statement that no per-object property is among them. +- **18**'s opening paragraph no longer describes 19.1, 19.2, and 19.5 as unresolved forward references. + +## New diagnostic codes + +Two, added to the section 7 table, which remains the single authoritative list: `ERR_INVALID_EFFECT_TYPE` +and `WARN_VISUAL_CEILING`. Section 19.6 repeats them for locality, exactly as 15.18, 16.10, 17.15, and +18.9 do. + +Eleven existing codes are reused rather than duplicated under visual-specific names: +`ERR_AUTOMATION_CONFLICT`, `WARN_AUTOMATION_FALLBACK`, `ERR_UNSUPPORTED_TARGET`, `ERR_INVALID_REFERENCE`, +`ERR_VISUAL_LIMIT_EXCEEDED`, `ERR_NODE_LIMIT_EXCEEDED`, `ERR_OUT_OF_BOUNDS`, `ERR_UNKNOWN_FIELD`, +`ERR_SCHEMA_VALIDATION`, `WARN_VISUAL_APPROXIMATION`, and `WARN_CLEANUP_FORCED`. + +## Limits fixed in this slice + +Authoring bounds — checked at import, rejecting the exhibit: + +| Limit | Bound | Diagnostic | +| --- | --- | --- | +| Automation tracks per exhibit, both scopes | 128 | `ERR_VISUAL_LIMIT_EXCEEDED` | +| Automation points per exhibit, both scopes | 2048 | `ERR_VISUAL_LIMIT_EXCEEDED` | +| Post-effect entries | 4 | `ERR_VISUAL_LIMIT_EXCEEDED` | +| Blur and bloom radius | 32 scene units | `ERR_OUT_OF_BOUNDS` | +| Camera `zoom` | 0.01 to 100 | `ERR_OUT_OF_BOUNDS` | +| Camera `focalLength` | above 0 | `ERR_OUT_OF_BOUNDS` | +| Spawned `release` | 0ms to 10s | `ERR_OUT_OF_BOUNDS` | + +Aggregate runtime ceilings — checked every tick or frame, shedding work and continuing: + +| Ceiling | Value | Shed | +| --- | --- | --- | +| Live particles, all systems | 8192 | Oldest in the largest system | +| Live emitted items, all emitters | 2048 | Oldest in the largest emitter | +| Live spawned system instances | 64 | Refuse the spawn | +| Link segments per tick | 4096 | Tail of the ascending pair order | +| Trail history samples | 32768 | Oldest samples of the longest histories | +| Field evaluations per tick | 32768 | Nearest items first; remainder is zero force | +| Offscreen compositing buffers per frame | 16 | Drop the excess objects' buffered appearance, farthest-`z` first | +| Post-effect passes per frame | 8 | Skip trailing effects | +| Device-pixel-ratio multiplier | min(dpr, 2) | Clamp | +| Backing store | 4096 x 4096 device pixels | Lower the multiplier to a floor of 1 | + +Section 19.5 also restates, in one table, every ceiling first fixed in sections 10, 14, 15, 16, 17, and +18, which is what PRD 120 means by centralizing limits rather than scattering them. Scenario instances +and timeline expansions are the two entries of PRD 120's list no contract has yet fixed; the gap is named +in the table so it stays a center rather than a snapshot. + +**These values are provisional.** Their relationship to slice 4h is the relationship 16.7's +master-protection values have to GC6: the shape is normative now, the numbers are confirmed or replaced +by measurement. No evidence record may describe them as measured until trace 21 of 19.7 runs. + +## Verification performed + +This slice changes documentation only. No runtime source, schema, fixture, or test file was touched. + +- Every diagnostic code used in section 19 (13 distinct codes) resolves to a row in the section 7 table; + zero unresolved, checked programmatically. +- Every numeric cross-reference in the whole specification (107 distinct, 48 of them made by section 19) + resolves to an existing heading. Zero unresolved forward references remain anywhere in the document — + the first time that has been true since section 17 was written. +- All 116 code fences in the document are balanced, 6 of them in section 19, and every table row in the + document is well formed once escaped pipes are discounted. +- `npm test` passes 102 tests with zero failures, confirming the existing suite is unaffected. + +## Test boundary + +Traces 1-19 of section 19.7 are automated and belong to slice 4f, where automation, lifecycle, camera, +and effects exist to run them. Traces 20 and 21 are user-observed and close slice 4h: the PRD 130 visual +challenge judged on a real display — the same gate as trace 15 of 17.16 and trace 20 of 18.10, restated +for locality rather than added as a third gate — and the early combined GC6 benchmark at `1920 x 1080`, +which is what decides whether the ceilings above are the right ones. + +## State at this checkpoint + +The visual contract is complete and no visual code exists. `src/runtime` has no renderer module, the JSON +Schema has no visual definitions, `exhibits/` has no visual fixture, and nothing has been drawn from any +build. Slice 4d is the first slice of Phase 4 that writes runtime code. + +Phase 3 remains unaccepted while traces 15-17 of section 16.11 and the Phase 1 direct-file restart +observation are open. No Phase 4 slice depends on them and no Phase 4 slice can close them. + +Whether sections 17-19 pass a multi-model review before slice 4d begins remains open, and it is now a +decision with a deadline rather than a standing question: 4d is the next slice, the review's whole value +is in catching contradictions before implementation defends them, and the surface has grown from the +sections 14-16 pass that found thirteen defects to three sections of comparable size.