diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md index adce46e..f120ae6 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 slice 4a, the first 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; 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 **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; 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 open at Phase 4a-4b; 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 slice 4a complete; 4b–4h pending | [Slice-4a contract evidence](evidence/phase4/2026-09-06-phase4a-contract.md). Format Specification section 17 covers PRD 69–76; sections 18 and 19 (slices 4b, 4c) precede any renderer implementation. Slice 4h and the PRD 130 visual challenge are user-observed. | +| 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. | | 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 | diff --git a/docs/XZBT_0-1_Format_Specification.md b/docs/XZBT_0-1_Format_Specification.md index 65738d2..214835d 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.5 -**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, with procedural systems and automation/lifecycle/effects to follow in sections 18-19), and the remaining subsystem contracts are in progress +**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 **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. @@ -297,8 +297,15 @@ 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). | +| `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). | | `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. | +| `ERR_INVALID_BEHAVIOR_TYPE` | Semantic | A behavior `type` is not a member of Visual Behavior Set 0.1. | +| `ERR_INVALID_BEHAVIOR_TARGET` | Semantic | A behavior addresses a channel outside its permitted set, or one the owning primitive does not have. | +| `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. | ## 8. Shared value resolution, bindings, and transitions @@ -519,7 +526,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.5 / Phase 4a):** 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. Procedural systems, behaviors, and fields remain Phase 4b (section 18); 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 | **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) | | 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** | @@ -1562,6 +1569,7 @@ Angles are degrees throughout the visual contract. The audio contract's `phase` | `scene` | object | Yes when `visuals` is present | Scene model (17.4). | | `layers` | object keyed by layer ID | No | Layer set (17.5). Absent means one implicit layer. | | `systems` | object keyed by system ID | No (defaults to `{}`) | Visual system objects (17.7). An exhibit with no systems renders only `scene.background`. | +| `fields` | object keyed by field ID | No (defaults to `{}`) | Procedural fields (18.7). | | `camera` | object | No | Camera (19.3). | | `effects` | array | No (defaults to `[]`) | Post-effect chain (19.4). | @@ -1683,6 +1691,8 @@ Every value in a `content` or `children` container is a **visual object**: a `ty A `type` outside this set is `ERR_INVALID_PRIMITIVE_TYPE`. Per the strict unknown-field policy, any property a primitive's contract does not declare — including a geometry field belonging to another primitive — is `ERR_UNKNOWN_FIELD`. +Visual Primitive Set 0.1 is exactly these fourteen geometry primitives and is closed. Section 18.1 adds one further **visual object type**, `component`, which declares no geometry of its own and instantiates a `components.visual.*` sub-assembly in place; the same `ERR_INVALID_PRIMITIVE_TYPE` covers a `type` outside the fifteen. This mirrors the audio contract, where 15.11 added a `component` node to the node set 14.3 opened. + `points` entries are `{ "x": number, "y": number, "z"?: number }`. A point's `z` offsets the object's `z` for depth purposes but does not sort points independently; an object is sorted and drawn as one unit. **Groups.** A `group` composes a transform (17.11), a style scope (17.12), and an optional clip or mask over its `children`. Group nesting deeper than `8` levels is `ERR_VISUAL_LIMIT_EXCEEDED`, matching the audio component nesting bound of 15.15. A `group` with an empty `children` object is `ERR_SCHEMA_VALIDATION`. @@ -1698,7 +1708,7 @@ Where applicable to the primitive (PRD 73): | `size` | `{width, height}`, each ValueSpec\ | Per primitive | — | Only where the primitive's row declares it. | | `transform` | object | No | identity | Transform model (17.11). | | `style` | object | No | inherited | Appearance (17.12). | -| `behaviors` | array | No (defaults to `[]`) | — | Behavior instances (18.6). Rejected as `ERR_UNKNOWN_FIELD` until section 18 declares them. | +| `behaviors` | array | No (defaults to `[]`) | — | Behavior instances (18.6), `0` to `8` entries. | | `visible` | ValueSpec\ | No | `true` | A hidden object and its descendants are not drawn; their behaviors and automation still advance. | | `lifetime` | DurationSpec | No | — | Logical duration after which the object is removed from its container. Absent means it lives as long as its system. | | `layer` | — | — | — | Declared on the *system* (17.7), not on an object. `layer` on a visual object is `ERR_UNKNOWN_FIELD`. | @@ -1867,7 +1877,7 @@ Added to the section 7 table, which remains the single authoritative list: | `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; section 19.5 adds the runtime ceilings). | +| `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; section 18 adds the procedural-system ceilings and 19.5 adds the runtime ceilings). | | `WARN_VISUAL_APPROXIMATION` | Runtime | A declared appearance feature is unavailable on the active renderer and this contract's documented fallback was used. | ### 17.16 Required traces before slice 4d implementation is accepted @@ -1895,3 +1905,429 @@ User-observed, and **not** satisfiable by the above: 16. The early combined GC6 benchmark of slice 4h, on recorded hardware at `1920 x 1080` with the environment details GC6 requires. Traces 15 and 16 close Phase 4 slice 4h. Until they do, Phase 4 is not accepted no matter how many automated traces pass. + +--- + +## 18. Visual Subsystem Contract — Components, Procedural Systems, Behaviors, and Fields (Phase 4b) + +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. + +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. + +### 18.1 Visual components (`components.visual.`) + +```json +{ + "components": { + "visual": { + "panel": { + "parameters": { + "tint": { "type": "color", "default": "#3a6ea5" }, + "lit": { "type": "boolean", "default": false } + }, + "content": { + "frame": { + "type": "rounded-rectangle", + "size": { "width": 24, "height": 16 }, + "radius": 2, + "style": { "fill": { "ref": "inputs.tint" }, "stroke": "#0b1118" } + } + } + } + } + } +} +``` + +A `components.visual.` entry is a reusable sub-assembly of visual objects (PRD 77). It may combine primitives, groups, styles, transforms, and behaviors, exactly as an inline `content` container may. + +| Field | Type | Required | Default | Notes | +| --- | --- | :---: | --- | --- | +| `parameters` | object keyed by parameter ID | No | `{}` | Exposed inputs. Each is `{ "type": enum, "default"?: value, "min"?: number, "max"?: number, "unit"?: string }`. | +| `content` | object keyed by visual object ID | Yes | — | Visual objects (17.9). An empty `content` object is `ERR_SCHEMA_VALIDATION`. | +| `transform` | object | No | identity | Transform model (17.11), applied to the whole component instance. | +| `style` | object | No | inherited | Appearance (17.12); the root style scope for the component's content. | +| `behaviors` | array | No | `[]` | Behavior instances (18.6) attached to the instance as a whole. | + +`parameters..type` is one of `number`, `boolean`, `string`, or `color` — the shared scalar types of section 2. `min`, `max`, and `unit` are permitted only where `type` is `number`; elsewhere they are `ERR_UNKNOWN_FIELD`. A `default` whose type does not match `type` is `ERR_TYPE_MISMATCH`; a `default` outside a declared `min`/`max` is `ERR_OUT_OF_BOUNDS`. The audio contract admits only `number` (15.15) because an audio node field is always numeric; a visual field may be a color, a label, or a visibility flag, so the visual component set is the four shared scalar types and no more. Neither list is a ValueSpec type: `parameters` declares an input's *type*, and the instantiating site supplies a ValueSpec for it. + +**Component-local references.** Inside a component's `content`, and only there, a ValueSpec may use the reference form `{ "ref": "inputs." }` to read the instance's value for an exposed parameter. This is the same component-graph-scoped namespace rule 15.15 fixes for audio: `inputs.*` used anywhere else is `ERR_INVALID_REFERENCE`, it is not added to the section 1.3 document namespaces, and it grants no row in the section 8.1 target table. A reference to an undeclared parameter is `ERR_INVALID_REFERENCE`. A parameter with no `default` and no value supplied at the instantiating site is `ERR_INVALID_REFERENCE` at instantiation. + +**Encapsulation.** External documents may address a component only through its exposed parameters. A reference or a `mask` reaching an object key inside a component from outside it is `ERR_INVALID_REFERENCE`. Object keys inside a component are scoped to that component and may repeat keys used by the instantiating container without conflict; the expansion path of 18.1 disambiguates them for PRNG stream derivation, as 14.12 does for audio. + +**The `component` visual object type.** Section 17.9 fixes Visual Primitive Set 0.1 at fourteen geometry primitives. This section adds one further **visual object type** that is not a geometry primitive: + +| `type` | Fields | Notes | +| --- | --- | --- | +| `component` | `component`, `inputs` | Instantiates `components.visual.` in place. `inputs` is an object of ValueSpecs keyed by the component's exposed parameter IDs. | + +This mirrors 15.11, where the audio contract added a `component` node to the node set defined in 14.3. A `component` object accepts the common visual properties of 17.10 — `position`, `z`, `transform`, `style`, `behaviors`, `visible`, `lifetime` — and behaves as a `group` whose children are the component's `content`. It declares no geometry of its own; `size`, `radius`, `points`, and every other geometry field are `ERR_UNKNOWN_FIELD` on it. An `inputs` key naming an undeclared parameter is `ERR_INVALID_REFERENCE`. A `type` outside the fourteen primitives and `component` remains `ERR_INVALID_PRIMITIVE_TYPE`. + +**Nesting and recursion.** A component's `content` may contain `component` objects. Component nesting deeper than `8` levels, and any component that instantiates itself transitively, is `ERR_COMPONENT_RECURSION` — the same code and the same bound section 15.14 fixes for audio, reused rather than duplicated under a visual name. Component nesting and `group` nesting (17.9) are counted independently; each is bounded at `8`. + +**Expansion path and stream derivation.** A component instance expands into the containing tree at a **stable expansion path**: the instantiating object's full path (17.8) followed by each container key inside the component in turn. That path, not the component ID, is the stable instance key contributed to section 9.3 stream derivation, so two instances of one component sample independently and reproducibly, and reordering unrelated siblings does not perturb either. + +### 18.2 Particle systems (`particles`) + +```json +{ + "type": "particles", + "layer": "near", + "capacity": 400, + "count": 400, + "lifetime": "12s", + "distribution": { "type": "rectangle", "center": { "x": 800, "y": 450 }, "size": { "width": 1600, "height": 900 } }, + "velocity": { "x": { "random": { "min": -6, "max": 6 } }, "y": { "random": { "min": 4, "max": 18 } } }, + "drag": 0.05, + "size": { "from": 2.4, "to": 0.6, "curve": "linear" }, + "opacity": { "from": 0, "to": 0.85, "curve": "smooth" }, + "render": { "type": "point", "style": { "fill": "#cfe4ff" } } +} +``` + +A `particles` system maintains a bounded pool of lightweight items that share one appearance definition and one motion model. It is the cheapest way to place and move many objects, and it is what PRD 130 items 1, 3, and 9 are built from. + +| Field | Type | Required | Default | Notes | +| --- | --- | :---: | --- | --- | +| `render` | visual object or `component` object | Yes | — | The object drawn for each particle (17.9, 18.1). Its own `position`, `z`, and `visible` are `ERR_UNKNOWN_FIELD`; the particle owns those. | +| `capacity` | number | No | `256` | Integer, `1` to `4096`. Maximum concurrent particles in the pool. | +| `count` | ValueSpec\ | No | `0` | Integer. Particles created at the system's instantiation boundary (17.14). | +| `rate` | ValueSpec\ | No | `0` | Particles per logical second, continuous. Emission timing is 18.4. | +| `burst` | array | No | `[]` | Burst entries (18.4), `0` to `16`. | +| `limit` | number | No | — | Integer. Total particles this system may ever create. Absent is unbounded. | +| `lifetime` | DurationSpec | Conditional | — | Per-particle logical lifetime. Required when the system can create particles without bound; see below. | +| `distribution` | object | No | `{ "type": "point" }` | Initial placement (18.3), in the system's local space. | +| `position` | `{x, y}`, each ValueSpec\ | No | `{0, 0}` | Offset added to every distributed position. | +| `velocity` | `{x, y, z}`, each ValueSpec\ | No | `{0, 0, 0}` | Scene units per logical second. | +| `acceleration` | `{x, y, z}`, each ValueSpec\ | No | `{0, 0, 0}` | Scene units per logical second squared. | +| `drag` | ValueSpec\ | No | `0` | `0` to `1`. Fraction of velocity lost per logical second; see below. | +| `size` | ValueSpec\ or life ramp | No | — | Multiplier on `render`'s own size. | +| `rotation` | ValueSpec\ | No | `0` | Degrees at creation. | +| `angularVelocity` | ValueSpec\ | No | `0` | Degrees per logical second. | +| `opacity` | ValueSpec\ or life ramp | No | `1` | `0` to `1`. Multiplies `render`'s style opacity. | +| `color` | `color` or life ramp | No | — | When present, replaces `render`'s resolved `style.fill`, and its `style.stroke` where that is non-`null`. | +| `z` | ValueSpec\ | No | `0` | Depth (17.6). A `depth` distribution (18.3) writes this instead. | +| `behaviors` | array | No | `[]` | Behavior instances (18.6), applied per particle. | +| `fields` | array of strings | No | `[]` | Field IDs (18.7) this system's particles respond to, `0` to `4`. | +| `trail` | object | No | — | Trail block (18.8). | +| `links` | object | No | — | Link block (18.8). | + +**Per-particle resolution boundary.** Every ValueSpec above resolves **once per particle, at that particle's creation**, extending the rule of 17.14 rather than modifying it: creation is an instantiation boundary. `capacity` and `limit` are plain numbers, not ValueSpecs, because a pool bound that could differ per particle is not a bound. Fields are sampled from the system's stream in depth-first, property-document order (9.3), with the documented child key `#`, where the ordinal is monotonic within the system and starts at `0`. Two runs of one seed therefore create identical particles in identical order. + +**Life ramps.** `size`, `opacity`, and `color` may be a **life ramp** instead of a ValueSpec: + +```json +{ "from": 0, "to": 1, "curve": "smooth" } +``` + +| Field | Type | Required | Default | Notes | +| --- | --- | :---: | --- | --- | +| `from` | ValueSpec of the field's type | Yes | — | Value at normalized age `0`. | +| `to` | ValueSpec of the field's type | Yes | — | Value at normalized age `1`. | +| `curve` | enum | No | `linear` | `step`, `linear`, `exponential`, `smooth` — the curve set PRD 85 fixes for automation. | + +Normalized age is `clamp(age / lifetime, 0, 1)` on the logical clock, and is the `age` property PRD 78 requires. A ramp on a particle with no `lifetime` is `ERR_SCHEMA_VALIDATION`, because normalized age is undefined without one. `exponential` on a `from` or `to` of `0`, or on a pair that crosses zero, falls back to linear interpolation and raises `WARN_AUTOMATION_FALLBACK`, matching 16.1. A `color` ramp interpolates in sRGB component space including alpha. + +A life ramp is an **interpolation of already-resolved endpoints**, not a re-sampling. `from` and `to` resolve once at particle creation and the curve between them is a pure function of age. Rendering therefore still consumes no procedural stream (9.3, 17.14). + +**Motion integration is normative.** Particle motion advances on the fixed logical tick of 9.1 and never on the frame. For a tick of `dt` logical seconds, in this order: + +```text +v <- (v + a * dt) * (1 - drag)^dt +p <- p + v * dt +theta <- theta + angularVelocity * dt +``` + +Field forces (18.7) are summed into `a` before the velocity update; behaviors (18.6) apply after the position update, in array order. Semi-implicit Euler in this order is the normative integrator: a renderer that integrates position before velocity, or applies drag as a per-tick multiplier independent of `dt`, produces visibly different motion at a different tick rate and does not conform. A frame between two ticks draws the most recent tick's state; it does not advance the simulation. + +**Unbounded emission.** A system whose `rate` resolves above `0` or whose `burst` array is non-empty, with no `lifetime` and no `limit`, would grow until it hit its `capacity` and then churn forever with no authored intent. That combination is `ERR_UNBOUNDED_EMISSION` at semantic validation. `count` alone needs no `lifetime`: a fixed initial population that never expires is a legal and common starfield. + +**Pool exhaustion.** When a creation would exceed `capacity`, the oldest living particle is evicted and replaced, mirroring the oldest-first voice eviction of 16.6. Eviction is silent at the system level; the aggregate runtime ceiling across all systems, and its diagnostic, are fixed in 19.5. `capacity` above `4096` is `ERR_VISUAL_LIMIT_EXCEEDED`. + +**Drawing.** Particles of one system draw as one unit within their layer, sorted among themselves by effective `z` descending and then by creation ordinal ascending, under the same stable rule as 17.6. The system's own position in its layer's depth sort is its lowest-`z` particle, so a particle system never interleaves with unrelated objects on a per-particle basis; interleaving deep and near content is what layers (17.5) are for. + +### 18.3 Placement distributions + +A **distribution** answers one question: where does an item start? It is used by `particles` (18.2), `emitter` (18.4), and `repeater` (18.5), and by the `path` and `grid` placements PRD 79 requires. A distribution never animates anything; motion is behaviors (18.6) and fields (18.7). + +All coordinates are in the owning system's local space, in scene units (17.2). A distribution is `{ "type": enum, ...type-specific fields }`. A `type` outside the nine below is `ERR_INVALID_DISTRIBUTION_TYPE`. + +| `type` | Fields | Placement | +| --- | --- | --- | +| `point` | `at` (`{x, y, z?}`, default origin) | Every item at one position. Consumes no procedural sample. | +| `uniform` | `min`, `max` (`{x, y, z?}`) | Uniform inside the axis-aligned box. `max` component below `min` is `ERR_INVALID_RANGE_ORDER`. | +| `line` | `from`, `to` (`{x, y, z?}`), `mode` | `random` (default) samples uniformly along the segment; `even` places item `i` at `i / (count - 1)`. | +| `rectangle` | `center`, `size`, `fill` | `fill` is `area` (default) or `perimeter`. `perimeter` samples by edge length, so a long edge receives proportionally more items. | +| `ellipse` | `center`, `radius` (number or `{x, y}`), `fill` | `area` (default) is uniform by area, not by radius: sample `r = radius * sqrt(u)`. `perimeter` places on the boundary. | +| `ring` | `center`, `radius`, `innerRadius`, `startAngle`, `endAngle`, `direction`, `mode` | Uniform by area within the annulus or annular sector. `innerRadius` defaults to `0`; not below `radius` is `ERR_INVALID_RANGE_ORDER`. `mode` `even` distributes angle evenly by index. | +| `path` | `path`, `mode`, `align` | `path` is either a `commands` array (17.13) or the key of a sibling `path`, `spline`, `polyline`, or `polygon` object in the same container. `mode` is `random` (default, uniform by arc length) or `even`. `align` (default `false`) sets the item's initial `rotation` to the curve tangent. | +| `grid` | `origin`, `columns`, `rows`, `spacing`, `jitter` | Deterministic, row-major from `origin`. `columns` and `rows` are `1` to `256`. `jitter` (`{x, y}`, default `{0, 0}`) offsets each cell by a uniform sample in `[-jitter, +jitter]`. | +| `depth` | `near`, `far`, `curve` | Assigns `z` only; `x` and `y` stay at the item's `position`. `curve` is `uniform` (default), `linear`, or `exponential`, biasing items toward `near`. `far` not above `near` is `ERR_INVALID_RANGE_ORDER`. | + +**Depth composition.** Any distribution except `depth` accepts an optional `depth` sub-block with the `depth` type's own fields, so a `grid` of panels can also be spread in `z` without a second distribution. A `depth` sub-block on a `depth` distribution is `ERR_UNKNOWN_FIELD`. A distribution whose fields carry a `z` component and that also declares a `depth` sub-block is `ERR_UNKNOWN_FIELD`: `z` comes from one source. + +**Index-driven modes need a known count.** `even` on `line`, `ring`, or `path`, and the `grid` type itself, place item `i` of `n` by index. They are legal in a `repeater` (whose `count` is fixed) and in a `particles` system's `count` population and its `burst` entries (each burst has its own `n`), and they are `ERR_INVALID_DISTRIBUTION` on a continuous `rate` emission, where `n` is unknown at creation time. + +**Sample consumption.** A distribution's samples are drawn from the owning system's stream in the field order of its row, once per item, at that item's creation. The order is normative so that a fixture's placements are byte-identical across renderers: `x` before `y` before `z`, and for polar forms angle before radius. `point`, `grid` without `jitter`, and every `even` mode consume no samples at all, which is what makes a large static grid reproducible without exhausting a stream. + +### 18.4 Emitters (`emitter`) + +```json +{ + "type": "emitter", + "layer": "near", + "emit": { "type": "component", "component": "spark", "inputs": { "tint": "#ffcf9b" } }, + "rate": 6, + "burst": [{ "at": "0s", "count": 24 }], + "capacity": 120, + "lifetime": { "random": { "min": "1.2s", "max": "3.4s" } }, + "distribution": { "type": "ellipse", "center": { "x": 400, "y": 300 }, "radius": 40 }, + "velocity": { "x": { "random": { "min": -30, "max": 30 } }, "y": -80 } +} +``` + +An `emitter` creates full visual objects or component instances over time (PRD 80). It is heavier per item than a particle — each emitted item is an independent object with its own transform, style, behaviors, and children — and its ceilings are correspondingly lower. + +| Field | Type | Required | Default | Notes | +| --- | --- | :---: | --- | --- | +| `emit` | visual object or `component` object | Yes | — | The object created per emission (17.9, 18.1). Its `position`, `z`, and `visible` are `ERR_UNKNOWN_FIELD`; the emitter owns those. | +| `rate` | ValueSpec\ | No | `0` | Emissions per logical second. | +| `burst` | array | No | `[]` | `0` to `16` entries of `{ "at": DurationSpec, "count": ValueSpec }`, `at` measured from the emitter's instantiation boundary. | +| `limit` | number | No | — | Integer. Total emissions this emitter may ever make. | +| `capacity` | number | No | `64` | Integer, `1` to `512`. Maximum concurrent live items. | +| `lifetime` | DurationSpec | Conditional | — | Per-item logical lifetime; see the unbounded-emission rule below. | +| `distribution` | object | No | `{ "type": "point" }` | Initial placement (18.3). | +| `position` | `{x, y}`, each ValueSpec\ | No | `{0, 0}` | Offset added to every distributed position. | +| `velocity` | `{x, y, z}`, each ValueSpec\ | No | `{0, 0, 0}` | Scene units per logical second. | +| `acceleration` | `{x, y, z}`, each ValueSpec\ | No | `{0, 0, 0}` | Scene units per logical second squared. | +| `drag` | ValueSpec\ | No | `0` | `0` to `1` per logical second, integrated as in 18.2. | +| `align` | boolean | No | `false` | When `true`, the item's `rotation` is set to its velocity direction at creation. | +| `inputs` | object of ValueSpecs | No | `{}` | Only when `emit` is a `component` object; supplies its parameters. Resolved per emission. | +| `behaviors` | array | No | `[]` | Behavior instances (18.6), applied per item. | +| `fields` | array of strings | No | `[]` | Field IDs (18.7), `0` to `4`. | +| `trail` | object | No | — | Trail block (18.8). | + +**Emission timing is normative.** The emitter keeps a fractional accumulator, initially `0`. On each logical tick of `dt` seconds it adds `rate * dt`, then emits `floor(accumulator)` items and subtracts that integer, so the cumulative emission count after `t` seconds at a constant rate is `floor(rate * t)` exactly, with no drift and no dependence on tick alignment. When `rate` changes under automation (19.1), the accumulator is not reset. A burst entry emits its whole `count` on the first tick at or after its `at` offset. Items emitted on one tick are created in ordinal order and are indistinguishable in ordering from items emitted one per tick. + +**Per-item resolution.** Every ValueSpec above except `capacity` and `limit` resolves once per emitted item, at its emission, using the emitter's stream with the child key `#`. `rate` and `burst[].at` are properties of the emitter, not of an item, and resolve once at the emitter's own instantiation boundary. + +**Unbounded emission.** An emitter with a non-zero `rate` or a non-empty `burst`, and neither `lifetime` nor `limit`, is `ERR_UNBOUNDED_EMISSION`, for the reason given in 18.2. An emitter with a `limit` and no `lifetime` is legal: it creates a bounded number of persistent items. + +**Capacity.** When an emission would exceed `capacity`, the oldest live item is removed and replaced. Removal runs the item's own removal path — its `trail` history is discarded and its behaviors stop — and is not a scenario failure. `capacity` above `512` is `ERR_VISUAL_LIMIT_EXCEEDED`. The aggregate ceiling over all emitters and its diagnostic are 19.5. + +**Ownership.** Items an emitter creates are owned by the emitter, and the emitter is owned by whatever owns the system (section 10.1). Ownership of *spawned systems* — an emitter created by an action rather than declared in `visuals.systems` — is 19.2, not this section. + +### 18.5 Repeaters (`repeater`) + +```json +{ + "type": "repeater", + "layer": "far", + "repeat": { "type": "component", "component": "panel", "inputs": { "lit": { "choose": [{ "value": true, "weight": 1 }, { "value": false, "weight": 3 }] } } }, + "count": 96, + "distribution": { "type": "grid", "origin": { "x": 120, "y": 90 }, "columns": 12, "rows": 8, "spacing": { "x": 34, "y": 26 } } +} +``` + +A `repeater` creates a fixed number of **persistent** copies of an object or component at its instantiation boundary and never creates another (PRD 81). Panel arrays, building-like structures, instrument grids, repeating indicators, abstract cells, windows, and machinery patterns are all one repeater over a distribution. + +| Field | Type | Required | Default | Notes | +| --- | --- | :---: | --- | --- | +| `repeat` | visual object or `component` object | Yes | — | The object copied. Its `position` and `z` are `ERR_UNKNOWN_FIELD`; the repeater owns those. | +| `count` | ValueSpec\ | Yes | — | Integer, `1` to `1024`. Resolved once, at the repeater's instantiation boundary. | +| `distribution` | object | No | `{ "type": "point" }` | Placement (18.3). | +| `position` | `{x, y}`, each ValueSpec\ | No | `{0, 0}` | Offset added to every distributed position. | +| `inputs` | object of ValueSpecs | No | `{}` | Only when `repeat` is a `component` object. Resolved per copy. | +| `behaviors` | array | No | `[]` | Behavior instances (18.6), applied per copy. | +| `fields` | array of strings | No | `[]` | Field IDs (18.7), `0` to `4`. | +| `links` | object | No | — | Link block (18.8). | + +A `repeater` has no `rate`, `burst`, `limit`, `capacity`, or `lifetime`; each is `ERR_UNKNOWN_FIELD`. Copies live as long as the repeater. `count` above `1024` is `ERR_VISUAL_LIMIT_EXCEEDED`. + +**Per-copy resolution and the `repeat.*` namespace.** Each copy is an instantiation boundary (17.14): a `random` or `choose` inside `repeat` or `inputs` is sampled once per copy, in ascending copy index, from the repeater's stream with the child key `#`. Inside a repeater's `repeat` and `inputs`, and only there, a ValueSpec may read three copy-scoped values: + +| Reference | Type | Value | +| --- | --- | --- | +| `repeat.index` | number | The copy's `0`-based index. | +| `repeat.count` | number | The resolved `count`. | +| `repeat.fraction` | number | `index / (count - 1)`, or `0` when `count` is `1`. | + +`repeat.*` is scoped exactly as `inputs.*` is (18.1, 15.15): used outside a repeater it is `ERR_INVALID_REFERENCE`, it is not a section 1.3 document namespace, and it grants no section 8.1 capability. It is what makes an index-driven array — a gradient of tints across a panel wall, a ring of ticks at increasing angles — expressible without an authored list of ninety-six objects. + +**Copies are not addressable.** A copy has no document key, so nothing outside the repeater can reference one. A reference path naming a copy is `ERR_INVALID_REFERENCE`. Per-copy variation is authored through `repeat.*` and `inputs`, which is the whole point of the construct. + +### 18.6 Visual behaviors + +A **behavior** is a declared, generic motion or appearance rule attached to a visual object, a system, a particle, or an emitted item. Behaviors are the first of the three time-variation mechanisms 17.14 names, and the only one available before section 19 lands. + +`behaviors` is an array of `0` to `8` entries; more is `ERR_VISUAL_LIMIT_EXCEEDED`. Each entry is `{ "type": enum, ...type-specific fields }`, and a `type` outside the seventeen below is `ERR_INVALID_BEHAVIOR_TYPE`. Every behavior field is a ValueSpec resolved once at its owning object's instantiation boundary (17.14); a behavior's *effect* varies with logical time, its *configuration* does not. + +Behaviors advance on the fixed logical tick of 9.1, after the particle integration of 18.2 and in array order. Each writes to one or more **channels** — `position`, `z`, `transform.rotation`, `transform.scale`, `style.opacity`, and the geometry channels named below. Within one object, behaviors compose by accumulation on `position`, `z`, and `transform.rotation`, by multiplication on `transform.scale` and `style.opacity`, and last-writer-wins on everything else. Composition against visual automation and against external control is fixed in 19.1; until then no external writer exists, so array order is the whole story. + +| `type` | Fields | Effect | +| --- | --- | --- | +| `drift` | `velocity` (`{x, y, z}`), `damping` | Adds a constant velocity, optionally decaying by `(1 - damping)^dt` per logical second. | +| `rotate` | `speed`, `origin` | Adds `speed` degrees per logical second to `transform.rotation` about `origin` (default the object's own `origin`). | +| `oscillate` | `property`, `amplitude`, `frequency`, `phase`, `waveform`, `center` | Drives one channel as `center + amplitude * w(frequency * t + phase / 360)`. `waveform` is `sine` (default), `triangle`, `square`, or `sawtooth`. | +| `orbit` | `center`, `radius` (number or `{x, y}`), `speed`, `phase` | Moves the object around `center` at `speed` degrees per logical second. A `{x, y}` radius gives an elliptical orbit. | +| `wander` | `strength`, `rate`, `maxSpeed` | A coherent two-dimensional random walk: the wander direction turns by a value-noise sample (18.7) advanced at `rate` per logical second, and `strength` scales the resulting acceleration. `maxSpeed` clamps the accumulated wander velocity. | +| `follow-path` | `path`, `speed` or `duration`, `loop`, `align`, `offset` | Moves the object along a curve. `path` is a `commands` array or a sibling object key, as in 18.3. Exactly one of `speed` (scene units per logical second) and `duration` is required; both or neither is `ERR_SCHEMA_VALIDATION`. `loop` is `once` (default), `repeat`, or `ping-pong`. `align` (default `false`) sets `transform.rotation` to the tangent. `offset` is a `0`-to-`1` starting position along the curve. | +| `point-wander` | `amplitude`, `rate`, `indices` | Moves the individual points of the owning object by independent coherent noise, giving the slow organic deformation PRD 76 asks for. `indices` optionally restricts the effect to listed point indices. | +| `pulse` | `property`, `amplitude`, `frequency`, `curve`, `duty` | A periodic one-shot envelope on one channel: each period rises and falls over `duty` (`0` to `1`, default `0.5`) of the period under `curve`, and holds at the base value for the rest. | +| `twinkle` | `property`, `min`, `max`, `rate` | Independent per-object flicker between `min` and `max`, driven by a value-noise stream advanced at `rate` per logical second. Its phase offset is drawn once from the owning stream, so siblings twinkle out of step without any authored jitter. | +| `noise-displace` | `amplitude` (`{x, y, z}`), `scale`, `speed`, `octaves`, `persistence` | Offsets `position` by coherent noise (18.7) sampled at the object's own position, giving flow-like collective motion without a declared field. | +| `face-motion` | `offset`, `smoothing` | Sets `transform.rotation` to the direction of the object's current velocity, plus `offset` degrees. `smoothing` (`0` to `1`, default `0`) is an exponential follow. An object with zero velocity holds its previous rotation. | +| `wrap` | `bounds`, `margin` | When the object leaves `bounds`, it re-enters from the opposite side. `bounds` is `scene` (default) or `{x, y, width, height}`. `margin` (default `0`) delays the wrap until the object is fully outside. | +| `bounce` | `bounds`, `restitution`, `axes` | Reflects velocity at the `bounds` edges. `restitution` is `0` to `1` (default `1`). `axes` is `both` (default), `x`, or `y`. | +| `attract` | `target`, `strength`, `falloff`, `minDistance`, `maxDistance` | Accelerates the object toward `target` — a `{x, y}` point or a sibling object key. | +| `repel` | same as `attract` | Accelerates the object away from `target`. | +| `field-follow` | `field`, `strength`, `mode` | Couples the object to a declared field (18.7). `mode` is `force` (default, the field is added to acceleration), `velocity` (the field sets velocity directly), or `direct` (the field displaces position). | +| `morph` | `to`, `duration`, `loop`, `curve` | Interpolates the owning object's geometry toward the geometry of the object named by `to`. | + +**Shared numeric conventions.** `frequency` and `rate` are in occurrences per logical second (17.2); `speed` is degrees per logical second for angular behaviors and scene units per logical second for linear ones, as each row states; `phase` and every angle are degrees (17.2). `falloff` on `attract` and `repel` is `none`, `linear` (default), `inverse`, or `inverse-square`; `minDistance` (default `1`) clamps the denominator so the force is finite at the target, and `maxDistance` (absent by default) is the radius outside which the behavior contributes nothing. + +**The `property` channel set.** `oscillate`, `pulse`, and `twinkle` name one channel in `property`: + +```text +position.x position.y z +transform.rotation transform.scale.x transform.scale.y +style.opacity style.strokeWidth style.pointSize +size.width size.height radius +``` + +`twinkle` defaults `property` to `style.opacity`. A `property` outside this set, or one the owning primitive does not have — `size.width` on an `ellipse`, `radius` on a `rectangle` — is `ERR_INVALID_BEHAVIOR_TARGET`. This set is **not** the section 8.1 target-capability table and does not extend it: a behavior is an internal, declared writer inside its own object, whereas 8.1 governs external bindings, `set`, and `override`. Section 19.1 decides, separately, which visual properties become externally addressable. + +**`point-wander` targets.** `point-wander` requires an owning object with an addressable point list: `spline`, `polyline`, `polygon`, or a `path` whose commands carry explicit endpoints. On any other primitive it is `ERR_INVALID_BEHAVIOR_TARGET`. Point indices are stable for the object's lifetime, exactly as 17.13 fixes, so index `i` always denotes the same point; an `indices` entry outside `0` to `count - 1` is `ERR_OUT_OF_BOUNDS`. + +**Morph compatibility.** Section 17.13 defers the morph rule to this section; it is: the source and target objects must have the same `type`, the same point count, and — for `spline` — the same `mode`. Any mismatch is `ERR_MORPH_INCOMPATIBLE` at semantic validation, not a silent resample, because resampling one shape onto another's point count would change the geometry the author wrote. `to` naming a missing sibling key is `ERR_INVALID_REFERENCE`. Morphing reads the target's resolved points and does not suppress it: the target draws normally unless the author also hides it with `visible` or uses it as the group's `mask` (17.12). `duration` is a DurationSpec, `loop` is `once` (default), `repeat`, or `ping-pong`, and `curve` is the four-curve set of 18.2. + +**Behaviors and reproducibility.** `wander`, `twinkle`, `point-wander`, and `noise-displace` are the only behaviors that consume the procedural stream, and each consumes it exactly once, at instantiation, to derive its noise offsets. Their subsequent motion is a pure function of logical time and those offsets. No behavior samples per frame or per tick, so 9.3's guarantee holds: rendering consumes no procedural stream, and identical seeds produce identical motion. + +### 18.7 Procedural fields (`visuals.fields.`) + +A **field** is a named vector function over scene space that other systems read. Fields draw nothing themselves; they are referenced by a system's `fields` array (18.2, 18.4, 18.5) or by a `field-follow` behavior (18.6). This is how one declared force acts on several unrelated systems at once, which is what PRD 83 asks for. + +`visuals.fields` is an object keyed by field ID, `0` to `8` entries; more is `ERR_VISUAL_LIMIT_EXCEEDED`. A system or behavior may reference at most `4` fields. A `fields` entry naming an undeclared field is `ERR_INVALID_REFERENCE`. + +```json +{ + "visuals": { + "fields": { + "current": { "type": "noise", "scale": 220, "speed": 0.08, "octaves": 3, "persistence": 0.5, "amplitude": 40, "mode": "curl" }, + "sink": { "type": "attractor", "center": { "x": 800, "y": 450 }, "strength": 900, "falloff": "inverse-square", "minDistance": 30 } + } + } +} +``` + +| `type` | Fields | Vector at a point | +| --- | --- | --- | +| `directional` | `direction`, `strength` | Constant, `strength` scene units per second squared along `direction` degrees. Gravity, wind, and current are all this one field. | +| `radial` | `center`, `strength`, `falloff`, `minDistance`, `maxDistance` | Along the outward ray from `center`. Negative `strength` points inward. | +| `vortex` | `center`, `strength`, `falloff`, `minDistance`, `maxDistance` | Perpendicular to the outward ray, counter-clockwise for positive `strength`. | +| `attractor` | `center`, `strength`, `falloff`, `minDistance`, `maxDistance` | Toward `center`. | +| `repulsor` | `center`, `strength`, `falloff`, `minDistance`, `maxDistance` | Away from `center`. | +| `noise` | `scale`, `speed`, `octaves`, `persistence`, `amplitude`, `mode`, `center`, `size` | Coherent noise; see below. | + +`falloff`, `minDistance`, and `maxDistance` carry the meanings fixed in 18.6. Every field also accepts an optional `bounds` (`{x, y, width, height}`) outside which it contributes nothing, and an optional `enabled` ValueSpec\ defaulting to `true`. A `type` outside the six is `ERR_INVALID_FIELD_TYPE`. + +**Coherent noise is normative.** PRD 83 requires seeded coherent noise with configurable `scale`, `speed`, `octaves`, and `persistence`. Reproducibility (9.3) promises identical procedural decisions, not identical pixels — but a field that drove motion differently on two conforming renderers would make a fixture untestable, so the noise function itself is fixed here: + +* Three-dimensional gradient noise on the integer lattice, sampled at `(x / scale, y / scale, t * speed)`, where `t` is logical seconds (9.1). +* Lattice gradients are the twelve edge-midpoint vectors of a cube, selected by a permutation table of `256` entries derived at field instantiation from the field's own stream (9.3, `visual` domain, child key ``), shuffled by Fisher-Yates consuming one sample per entry. +* Interpolation uses the quintic fade `6u^5 - 15u^4 + 10u^3` on each axis. +* Octaves sum at doubling frequency and `persistence` amplitude decay, normalized so the result stays within `[-1, 1]`, then scaled by `amplitude`. +* `octaves` is an integer `1` to `4`; `persistence` is `0` to `1` (default `0.5`); `scale` must be above `0` or `ERR_OUT_OF_BOUNDS`; `speed` defaults to `0`, which freezes the field. + +`mode` selects how the scalar noise becomes a vector: `curl` (default) takes the curl of the noise potential, giving divergence-free flow that never piles items into a point; `gradient` takes its gradient; `value` returns the scalar along `direction` degrees. `center` and `size` optionally restrict the sampled region; outside it the field contributes nothing. + +**Fields do not read each other.** A field's vector depends only on position and logical time. Fields never reference other fields, systems, or objects, so there is no evaluation order to fix and no cycle to detect. Fields are summed into a system's acceleration in the order of that system's `fields` array; addition is commutative, so the order is fixed for traceability rather than for correctness. + +**Cost.** A field costs one evaluation per affected item per logical tick, not per frame. The aggregate ceiling on field evaluations, like every other runtime ceiling, is 19.5. + +### 18.8 Trails, ribbons, and links + +**Trails and ribbons.** A `trail` block records an item's recent positions and draws them (PRD 84). It is declared on a `particles` system (18.2), on an `emitter` (18.4), or on the `trail` field of a particle's `render` object, and it applies per item. + +| Field | Type | Required | Default | Notes | +| --- | --- | :---: | --- | --- | +| `length` | number | No | `16` | Integer, `2` to `128`. History samples retained per item. | +| `interval` | DurationSpec | No | one logical tick | Logical interval between history samples. | +| `mode` | enum | No | `line` | `line`, `ribbon`, or `points`. | +| `width` | ValueSpec\ | No | inherits `style.strokeWidth` | Scene units at the head of the trail. | +| `taper` | ValueSpec\ | No | `1` | `0` to `1`. Fraction of `width` remaining at the tail. | +| `fade` | ValueSpec\ | No | `1` | `0` to `1`. Opacity multiplier at the tail; the head keeps the item's own opacity. | +| `style` | object | No | the item's own style | Appearance (17.12) for the trail geometry. | +| `curve` | enum | No | `linear` | `linear` or `catmull-rom` smoothing through the history samples. | + +`line` draws the history as a `polyline` of `length` samples. `ribbon` draws it as a continuous strip whose half-width at sample `i` interpolates from `width` at the head to `width * taper` at the tail, oriented perpendicular to the local direction of travel — this is the ribbon system PRD 84 asks for, expressed as a trail render mode rather than as a separate system type, because a ribbon is the same history buffer drawn with area instead of a stroke. `points` draws one `point` per sample at `style.pointSize`. + +History is sampled on the logical clock, never on the frame, so a trail has the same shape at any render rate. A newly created item draws no trail until it has two samples. An item removed by capacity, `lifetime`, or system disposal discards its history immediately; trails do not outlive their owner. `length` above `128` is `ERR_VISUAL_LIMIT_EXCEEDED`, and the aggregate history ceiling across all systems is 19.5. + +**Links.** A `links` block draws connecting geometry between the items of one system according to a generic rule (PRD 84). It is legal on a `particles` system (18.2) and on a `repeater` (18.5); on an `emitter` it is `ERR_UNKNOWN_FIELD`, because an emitter's population changes continuously and its link set would have to be rebuilt every tick at the cost the ceiling below exists to prevent. + +| Field | Type | Required | Default | Notes | +| --- | --- | :---: | --- | --- | +| `rule` | enum | No | `distance` | `distance`, `nearest`, or `index`. | +| `maxDistance` | ValueSpec\ | Conditional | — | Required for `distance`; optional for `nearest`. Scene units. | +| `count` | number | Conditional | — | Required for `nearest`. Integer, `1` to `8`. Links per item. | +| `stride` | number | No | `1` | `index` only. Links item `i` to item `i + stride`. | +| `closed` | boolean | No | `false` | `index` only. Links the last item back to the first. | +| `maxLinks` | number | No | `256` | Integer, `1` to `1024`. Links drawn per system per tick. | +| `style` | object | No | the system's style | Appearance (17.12) for the link geometry. | +| `fadeWithDistance` | boolean | No | `false` | When `true`, a link's opacity scales linearly from full at distance `0` to zero at `maxDistance`. | + +`distance` links every pair closer than `maxDistance`; it is the rule 0.1 requires at minimum, and it is what PRD 130 item 12's constellation is built from. `nearest` links each item to its `count` nearest neighbours. `index` links items by creation order and needs no distance search at all. + +**Determinism and cost.** Link pairs are enumerated in ascending `(lower index, higher index)` order and de-duplicated, so a pair is drawn once regardless of rule. Links draw before their system's items, at the system's own depth position (18.2). When the enumerated pair count exceeds `maxLinks`, the excess is dropped in that same ascending order — deterministically, not arbitrarily — and the aggregate link ceiling and its diagnostic are 19.5. + +A system whose live item count exceeds `256` while `links` is declared with rule `distance` or `nearest` is `ERR_VISUAL_LIMIT_EXCEEDED` at semantic validation where the count is statically known (`repeater.count`, `particles.capacity`), because a pairwise search over more items than that cannot be made to fit the per-tick budget PRD 120 sets. `index` has no such limit; it is linear in the item count. + +### 18.9 New diagnostic codes + +Added to the section 7 table, which remains the single authoritative list: + +| Error Code | Stage | Cause | +| :--- | :--- | :--- | +| `ERR_INVALID_DISTRIBUTION_TYPE` | Semantic | A placement distribution `type` is not one of the nine of 18.3. | +| `ERR_INVALID_DISTRIBUTION` | Semantic | A distribution is structurally valid but illegal in its context — an index-driven placement on a continuous-rate emission. | +| `ERR_INVALID_BEHAVIOR_TYPE` | Semantic | A behavior `type` is not a member of the Visual Behavior Set 0.1 of 18.6. | +| `ERR_INVALID_BEHAVIOR_TARGET` | Semantic | A behavior addresses a channel outside its permitted set, or one the owning primitive does not have. | +| `ERR_INVALID_FIELD_TYPE` | Semantic | A `visuals.fields.` `type` is not one of the six 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 can create items without bound: it declares emission but neither `lifetime` nor `limit`. | + +`ERR_COMPONENT_RECURSION`, `ERR_VISUAL_LIMIT_EXCEEDED`, `ERR_INVALID_SYSTEM_TYPE`, `ERR_INVALID_PRIMITIVE_TYPE`, `ERR_INVALID_REFERENCE`, `ERR_INVALID_RANGE_ORDER`, `ERR_OUT_OF_BOUNDS`, `ERR_TYPE_MISMATCH`, `ERR_UNKNOWN_FIELD`, `ERR_SCHEMA_VALIDATION`, and `WARN_AUTOMATION_FALLBACK` are reused rather than duplicated under visual-specific names, on the same principle 17.15 states. + +### 18.10 Required traces before slice 4e implementation is accepted + +Automated, and executable without a display measurement: + +1. A visual component instantiates with defaults, with supplied `inputs`, and with a `component` object nested inside another component; `inputs.` resolves inside the component and is `ERR_INVALID_REFERENCE` outside it; an undeclared parameter key in `inputs` is `ERR_INVALID_REFERENCE`; a `default` of the wrong type is `ERR_TYPE_MISMATCH`. +2. Component nesting `9` levels deep and a component instantiating itself are each `ERR_COMPONENT_RECURSION`, and `8` levels pass; a reference from outside a component to an object key inside it is `ERR_INVALID_REFERENCE`. +3. Two instances of one component sample independently from their expansion paths, and reordering unrelated siblings leaves both instances' resolved values unchanged. +4. Particle fields resolve once per particle: a `random` size holds its value for that particle's whole life across many frames, two runs of one seed produce identical particles in identical creation order, and rendering `600` frames consumes no procedural stream. +5. The integrator of 18.2 is verified against a closed-form case: constant acceleration with zero drag reaches the documented position after `n` ticks, and halving the tick length leaves the trajectory within the documented tolerance, while integrating position before velocity does not. +6. `drag` is `dt`-correct: one tick of `1s` and ten ticks of `0.1s` leave the same velocity to within floating-point tolerance. +7. A life ramp interpolates `from` to `to` under each of the four curves at age `0`, midpoint, and `1`; a ramp without `lifetime` is `ERR_SCHEMA_VALIDATION`; an `exponential` ramp through zero falls back to linear and raises `WARN_AUTOMATION_FALLBACK`. +8. `rate` emission is exact: at a constant rate the cumulative count after `t` logical seconds is `floor(rate * t)` under three different tick lengths, and a burst emits its whole `count` on the first tick at or after its `at`. +9. Emission with neither `lifetime` nor `limit` is `ERR_UNBOUNDED_EMISSION`; `count` alone without `lifetime` passes; exceeding `capacity` evicts the oldest item and leaves the live count at `capacity`. +10. Each of the nine distributions places a known item set at the documented positions from a fixed seed; `even` and `grid` consume no procedural samples; an index-driven placement on a continuous `rate` emission is `ERR_INVALID_DISTRIBUTION`; an unknown distribution `type` is `ERR_INVALID_DISTRIBUTION_TYPE`. +11. A repeater resolves `repeat.index`, `repeat.count`, and `repeat.fraction` per copy, samples a `choose` once per copy in ascending index, and is `ERR_INVALID_REFERENCE` when `repeat.*` is used outside a repeater or when an external path names a copy. +12. Each of the seventeen behaviors advances a known object to a documented state after a fixed number of logical ticks; an unknown `type` is `ERR_INVALID_BEHAVIOR_TYPE`; a `property` outside the channel set, and `size.width` on an `ellipse`, are each `ERR_INVALID_BEHAVIOR_TARGET`; `point-wander` on a `rectangle` is `ERR_INVALID_BEHAVIOR_TARGET`; `9` behaviors on one object is `ERR_VISUAL_LIMIT_EXCEEDED`. +13. Behavior composition follows array order: two `drift` behaviors accumulate on `position`, two `oscillate` behaviors on `transform.scale.x` multiply, and swapping the array order changes the result only where the rule says it should. +14. `morph` between two `spline` objects of equal `mode` and point count interpolates each point; differing point counts, differing `type`, and differing spline `mode` are each `ERR_MORPH_INCOMPATIBLE`; `to` naming a missing key is `ERR_INVALID_REFERENCE`. +15. The noise function of 18.7 reproduces documented sample values at fixed lattice coordinates from a fixed seed; two runs of one seed agree exactly; `octaves` of `5`, `persistence` above `1`, and a `scale` of `0` are each rejected with the documented code; an unknown field `type` is `ERR_INVALID_FIELD_TYPE`; `9` declared fields and `5` referenced fields are each `ERR_VISUAL_LIMIT_EXCEEDED`. +16. `curl` mode is divergence-free to within the documented tolerance over a sampled grid, so items following it do not accumulate at a point. +17. Trail history is sampled on the logical clock: the same trail geometry results at three different frame rates over the same logical span; a trail of `129` samples is `ERR_VISUAL_LIMIT_EXCEEDED`; a removed item's history is discarded in the same tick. +18. Link enumeration is deterministic and de-duplicated in ascending pair order under each of the three rules; `distance` links exactly the pairs within `maxDistance`; exceeding `maxLinks` drops the tail of that order rather than an arbitrary subset; `links` on an `emitter` is `ERR_UNKNOWN_FIELD`; a `distance` rule over a statically known population above `256` is `ERR_VISUAL_LIMIT_EXCEEDED`. +19. A binding, `set`, or `override` addressing any property introduced in this section — a behavior field, a field strength, an emitter `rate` — is `ERR_UNSUPPORTED_TARGET`, and the section 8.1 table remains unchanged by this slice. + +User-observed, and **not** satisfiable by the above: + +20. The fourteen PRD 130 challenge items are reachable by composing this vocabulary with sections 17 and 19, judged visually on a real display, with no subject-specific renderer code. This is trace 15 of 17.16 and is not a second gate. +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. diff --git a/docs/XZBT_0-1_Implementation_Plan.md b/docs/XZBT_0-1_Implementation_Plan.md index 476301b..3bb16d4 100644 --- a/docs/XZBT_0-1_Implementation_Plan.md +++ b/docs/XZBT_0-1_Implementation_Plan.md @@ -41,8 +41,8 @@ Phase 4 is the largest milestone in this plan: twenty-one PRD sections (69-89) p | Slice | Content | Depends on | Blocked by | | --- | --- | --- | --- | -| 4a — Contract I | 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. | 4a | — | +| 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 | — | | 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 | — | diff --git a/docs/evidence/phase4/2026-09-06-phase4b-contract.md b/docs/evidence/phase4/2026-09-06-phase4b-contract.md new file mode 100644 index 0000000..a077f3a --- /dev/null +++ b/docs/evidence/phase4/2026-09-06-phase4b-contract.md @@ -0,0 +1,160 @@ +# Phase 4b evidence — Visual subsystem contract, part II (Format Specification section 18) + +**Date:** September 6, 2026 +**Slice:** 4b — Contract II (documentation only; no runtime change) +**Baseline:** `main` at `3e27a66` (Phase 4a, section 17) +**Specification revision:** 0.5 → 0.6 +**PRD coverage:** 77-84 + +## What this slice fixes + +Section 18 continues the Visual contract section 17 opened. It covers reusable visual components and +their input scope, particle systems and the normative motion integrator, the nine placement +distributions, emitters and exact emission timing, repeaters and the copy-scoped `repeat.*` namespace, +the seventeen-behavior vocabulary and the channel set behaviors may write, the six procedural fields and +the normative coherent-noise function behind them, and trails, ribbons, and links. + +Section 19 (Phase 4c) remains the owner of visual automation, lifecycle and ownership, camera, +post-processing, and the centralized ceilings. The forward references to 19.1, 19.2, and 19.5 are the +only unresolved numeric references in section 18, and they close when 4c lands. + +## Decisions taken, so 4e does not relitigate them + +1. **`component` is a visual object type, not a fifteenth primitive.** Visual Primitive Set 0.1 stays + closed at the fourteen geometry primitives section 17.9 fixed. Section 18.1 adds `component` beside + that set, exactly as 15.11 added a `component` node beside the audio node set 14.3 opened, so a + component instance can be placed inline in any `content` or `children` container instead of only + being reachable through a repeater of count one. +2. **Visual component parameters admit four types, audio's admit one.** `number`, `boolean`, `string`, + and `color` — the shared scalar types of section 2. The audio contract (15.15) permits only `number` + because an audio node field is always numeric; a visual field may be a color, a label, or a + visibility flag. `min`, `max`, and `unit` remain number-only. +3. **`inputs.*` and `repeat.*` are construct-scoped, not document namespaces.** Both follow the rule + 15.15 fixed for audio components: legal only inside their construct, `ERR_INVALID_REFERENCE` + anywhere else, absent from the section 1.3 namespace list, and granting no section 8.1 capability. +4. **A component's expansion path, not its ID, is its stable instance key** for section 9.3 stream + derivation. Two instances of one component therefore sample independently, and reordering unrelated + siblings does not perturb either — the same property 14.12 gives audio components. +5. **Particle motion has a normative integrator.** Semi-implicit Euler, velocity before position, with + drag as `(1 - drag)^dt` so it is tick-length independent. A renderer that integrates position first, + or applies drag once per tick regardless of `dt`, produces visibly different motion at a different + tick rate and does not conform. Traces 5 and 6 pin both halves. +6. **Life ramps interpolate resolved endpoints; they do not re-sample.** `size`, `opacity`, and `color` + may ramp over normalized age using the same four curves PRD 85 fixes for automation. `from` and `to` + resolve once at particle creation, so 17.14 and 9.3 hold unchanged: rendering still consumes no + procedural stream. This is what gives PRD 78 its `age` property without breaking reproducibility. +7. **Emission timing is a fractional accumulator, not a per-tick rounding.** Cumulative count after `t` + seconds at a constant rate is `floor(rate * t)` exactly, under any tick length, with no drift and no + reset when automation changes the rate later. +8. **Unbounded emission fails validation.** A system that declares emission but neither a per-item + `lifetime` nor a total `limit` is `ERR_UNBOUNDED_EMISSION`. An initial `count` with no `lifetime` + stays legal — a fixed starfield that never expires is the common case, not the dangerous one. +9. **Pool exhaustion evicts oldest-first**, mirroring the voice eviction of 16.6, and is silent at the + system level. The aggregate ceiling across systems and its diagnostic belong to 19.5. +10. **Index-driven placement needs a known count.** `even` modes and the `grid` type place item `i` of + `n`, so they are legal in a repeater and in a fixed `count` or `burst` population and are + `ERR_INVALID_DISTRIBUTION` on continuous `rate` emission. Distribution sample-consumption order is + normative (`x`, then `y`, then `z`; angle before radius) so fixtures place identically everywhere. +11. **Coherent noise is specified, not left to the renderer.** Three-dimensional gradient noise on the + integer lattice, twelve edge-midpoint gradients, a `256`-entry permutation shuffled from the field's + own stream at instantiation, and the quintic fade `6u^5 - 15u^4 + 10u^3`. Reproducibility (9.3) + promises identical decisions rather than identical pixels, but a field that drove motion differently + on two conforming renderers would make a fixture untestable, so the function itself is pinned. + `curl` is the default mode because it is divergence-free and will not pile items into a point. +12. **Ribbons are a trail render mode, not a separate system type.** A ribbon is the same history buffer + drawn with area instead of a stroke, so `trail.mode` is `line`, `ribbon`, or `points`. PRD 84 is + satisfied without a fourth procedural system. +13. **`links` is illegal on an emitter.** A `particles` system and a `repeater` have bounded, known + populations; an emitter's changes continuously and its pair search would have to be rebuilt every + tick at exactly the cost the ceiling exists to prevent. All three PRD 84 rules — `distance`, + `nearest`, `index` — are specified, with `distance` the required 0.1 minimum. +14. **Behaviors write internal channels; they do not extend section 8.1.** The `property` channel set of + 18.6 is a behavior's own permitted write set. External addressability of visual properties remains a + section 19.1 decision, and a binding, `set`, or `override` naming anything introduced here is still + `ERR_UNSUPPORTED_TARGET`. +15. **The morph rule section 17.13 deferred is now fixed:** equal `type`, equal point count, and equal + spline `mode`, with `ERR_MORPH_INCOMPATIBLE` on any mismatch. Resampling one shape onto another's + point count would change the geometry the author wrote, so it fails validation instead. + +## Amendments to section 17 + +Four, all consequences of this slice rather than corrections: + +- **17.3** gains a `fields` row for the `visuals.fields` container. Without it the strict unknown-field + policy would make every declared field `ERR_UNKNOWN_FIELD`. +- **17.9** records that Visual Primitive Set 0.1 is closed at fourteen and that 18.1 adds the + `component` object type beside it. +- **17.10**'s `behaviors` row no longer reads "Rejected as `ERR_UNKNOWN_FIELD` until section 18 declares + them"; it now carries the `0` to `8` bound. +- **17.15**'s local `ERR_VISUAL_LIMIT_EXCEEDED` row names the procedural-system ceilings this section + adds. + +No decision recorded in section 17 was reversed. + +## New diagnostic codes + +Seven were added to the section 7 table, which remains the single authoritative list: +`ERR_INVALID_DISTRIBUTION_TYPE`, `ERR_INVALID_DISTRIBUTION`, `ERR_INVALID_BEHAVIOR_TYPE`, +`ERR_INVALID_BEHAVIOR_TARGET`, `ERR_INVALID_FIELD_TYPE`, `ERR_MORPH_INCOMPATIBLE`, and +`ERR_UNBOUNDED_EMISSION`. Section 18.9 repeats them for locality, exactly as 15.18, 16.10, and 17.15 do. + +`ERR_COMPONENT_RECURSION` is reused for visual component recursion and over-nesting rather than +duplicated under a visual name, along with `ERR_VISUAL_LIMIT_EXCEEDED`, `ERR_INVALID_REFERENCE`, +`ERR_INVALID_RANGE_ORDER`, `ERR_OUT_OF_BOUNDS`, `ERR_TYPE_MISMATCH`, `ERR_UNKNOWN_FIELD`, +`ERR_SCHEMA_VALIDATION`, and `WARN_AUTOMATION_FALLBACK`. + +## Authoring limits fixed in this slice + +| Limit | Bound | Diagnostic | +| --- | --- | --- | +| Visual component nesting depth | 8 (matches audio, 15.14) | `ERR_COMPONENT_RECURSION` | +| Particle `capacity` per system | 4096 | `ERR_VISUAL_LIMIT_EXCEEDED` | +| Emitter `capacity` per system | 512 | `ERR_VISUAL_LIMIT_EXCEEDED` | +| Repeater `count` | 1024 | `ERR_VISUAL_LIMIT_EXCEEDED` | +| `burst` entries per system | 16 | `ERR_SCHEMA_VALIDATION` | +| Behaviors per object | 8 | `ERR_VISUAL_LIMIT_EXCEEDED` | +| Declared fields per exhibit | 8 | `ERR_VISUAL_LIMIT_EXCEEDED` | +| Fields referenced per system or behavior | 4 | `ERR_VISUAL_LIMIT_EXCEEDED` | +| Noise `octaves` | 4 | `ERR_OUT_OF_BOUNDS` | +| Grid `columns` / `rows` | 256 each | `ERR_VISUAL_LIMIT_EXCEEDED` | +| Trail `length` | 128 samples | `ERR_VISUAL_LIMIT_EXCEEDED` | +| Links per system per tick (`maxLinks`) | 1024 | `ERR_VISUAL_LIMIT_EXCEEDED` | +| Statically known population under a pairwise link rule | 256 | `ERR_VISUAL_LIMIT_EXCEEDED` | +| Nearest-neighbour links per item (`count`) | 8 | `ERR_SCHEMA_VALIDATION` | + +These are authoring bounds a document must satisfy at validation. The aggregate runtime ceilings — +total active particles, total spawned systems, total links, total field evaluations, and the per-tick +budget they share (PRD 89, 120) — remain slice 4c and are not set here. Slice 4h is what determines +whether these bounds are the right ones. + +## Verification performed + +This slice changes documentation only. No runtime source, schema, fixture, or test file was touched. + +- Every diagnostic code used in section 18 (19 distinct codes) resolves to a row in the section 7 table; + zero unresolved, checked programmatically. +- Every numeric cross-reference in section 18 resolves to an existing heading, except the intended + forward references to 19.1, 19.2, and 19.5. +- All code fences in section 18 are balanced and every table row is well formed. +- `npm test` passes 102 tests with zero failures, confirming the existing suite is unaffected. +- The section 11 contract register's Visuals row now reads **In progress (Rev 0.6 / Phase 4a-4b)** and + names what remains for 4c. + +## Test boundary + +Traces 1-19 of section 18.10 are automated and belong to slice 4e, where the procedural systems exist to +run them. Traces 20 and 21 — the PRD 130 visual challenge judged on a real display, and the early +combined GC6 benchmark at `1920 x 1080` — are user-observed and close slice 4h. Trace 20 is the same gate +as trace 15 of 17.16, restated for locality rather than added as a second gate. + +## State at this checkpoint + +Sections 17 and 18 are contract only. No visual runtime 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. Phase 3 also remains unaccepted while its own user-observed gates (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 — the pass that found thirteen +defects in sections 14-16, nine of which any single reviewer would have missed — remains an open +decision, and section 18 has now roughly doubled the surface that review would cover.