From 3e27a668e2f1bc67637285bff19f55c48e0905fe Mon Sep 17 00:00:00 2001 From: Labyricorn Date: Sun, 6 Sep 2026 03:57:46 +0000 Subject: [PATCH] docs(visual): specify the phase 4a scene, primitive, and appearance contract Open the Visual subsystem contract at Format Specification revision 0.5 with section 17, covering PRD 69-76: the renderer-neutral pipeline, canonical units on a single degrees convention, the visuals container, layers, the scene model with three coordinate spaces and three fit modes, 2.5D depth with a fixed sign, stable sorting, parallax, perspective factor and per-object fog, the fourteen primitives of Visual Primitive Set 0.1, common visual properties, a normative transform composition order, appearance with the eight-mode safe blend set and gradient paint, paths and splines with independently addressable points, text restricted to the three generic font families, and the once-at-instantiation resolution boundary for visual ValueSpec fields. Visual objects are keyed rather than listed and carry no id field, matching the audio node-identity rule that a Phase 3a blocking defect established. No visual property becomes externally addressable in this slice; a binding, set, or override naming one remains ERR_UNSUPPORTED_TARGET, and section 8.1 is unchanged until section 19.1 adds its rows. Add five diagnostic codes to the section 7 table, record the eight-slice Phase 4 delivery plan, mark the section 11 Visuals register row in progress, and refresh the implementation status, which had not been updated since the 3c-1 contract. Documentation only; no runtime, schema, fixture, or test file is touched. npm test passes 102 tests with zero failures. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GwmTQU6zLwh3qbBvq5zf5B --- docs/IMPLEMENTATION_STATUS.md | 30 +- docs/XZBT_0-1_Format_Specification.md | 402 +++++++++++++++++- docs/XZBT_0-1_Implementation_Plan.md | 21 + .../phase4/2026-09-06-phase4a-contract.md | 120 ++++++ 4 files changed, 566 insertions(+), 7 deletions(-) create mode 100644 docs/evidence/phase4/2026-09-06-phase4a-contract.md diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md index 94c7ce6..adce46e 100644 --- a/docs/IMPLEMENTATION_STATUS.md +++ b/docs/IMPLEMENTATION_STATUS.md @@ -1,10 +1,10 @@ # XZBT implementation status -**Updated:** September 5, 2026 -**State:** Phase 3c slices 1–3 complete; slice 4 master protection implemented and automatically verified. Slice 4 hardware measurement/listening acceptance, the Phase 3 audible gates, and the Phase 1 direct-file import/restart observation remain pending +**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 **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. Visual, 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; 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 | Not started | Earlier phases and visual contracts | +| 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. | | 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 | @@ -90,3 +90,25 @@ The placeholder compressor is replaced by an embedded AudioWorklet with stereo-l `npm test` passes **102 tests**, including 14 slice-4 tests. Production and acceptance builds are deterministic and self-contained. The [protection evidence](evidence/phase3/2026-09-05-phase3c-protection.md) records coverage and limits. A [standalone user-run page](../prototypes/phase3/XZBT-audio-acceptance.html) provides a frozen overlap workload, exact audio-frame capture, twelve challenge recipes and JSON export; see [run instructions](../prototypes/phase3/README.md). **Acceptance checkpoint:** Implementation is ready for user testing. The candidate −1 dBFS ceiling, 0.1 dB tolerance and audible release behavior remain provisional. No production audio has been heard or measured; traces 15–17, PRD 129, reference-exhibit listening and real GC4 synchronization remain open. Phase 3 is not accepted, and later phases have not started. + +## Phase 4a visual contract (slice 1 of eight) + +Format Specification revision 0.5 adds section 17, the first of three Visual subsystem contract +sections. It fixes the visual pipeline and its renderer-neutral scope, canonical units with a single +degrees convention, the `visuals` container, layers, the scene model and the `normalized`/`viewport`/ +`virtual` coordinate spaces with `contain`/`cover`/`stretch` fit, 2.5D depth (sign, sorting, parallax, +perspective factor, and per-object depth fog), the fourteen primitives of Visual Primitive Set 0.1, +common visual properties, the normative transform composition order, appearance with the eight-mode safe +blend set and gradient paint, paths and splines with independently addressable points, text restricted to +the three generic font families, and the once-at-instantiation resolution boundary for visual ValueSpec +fields. Five diagnostic codes were added to the section 7 table. + +Phase 4 is delivered in eight slices, recorded in the implementation plan: three contract slices +(sections 17, 18, 19), three implementation slices (renderer core, procedural systems, automation and +effects), a challenge-and-exhibit slice, and the user-observed early combined GC6 benchmark. Each slice +ends green and committable. + +Section 17 is contract only. No visual runtime code exists, the JSON Schema has no visual definitions, +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). diff --git a/docs/XZBT_0-1_Format_Specification.md b/docs/XZBT_0-1_Format_Specification.md index f00b9cd..65738d2 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.4 -**Status:** Normative specification for shared contracts (document, types, ValueSpec, ConditionSpec, resolution, bindings, and transitions); the Audio subsystem contract is complete (Phase 3a sources/control sources, Phase 3b processing/routing/components/buses, Phase 3c automation/lifecycle/protection); master-protection values remain provisional pending GC6 measurement, and the remaining subsystem contracts are in progress +**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 **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. @@ -294,6 +294,11 @@ To ensure consistent error reporting between structural schema validation, seman | `WARN_VOICE_LIMIT` | Runtime | A voice ceiling was reached; an instance was evicted or a request refused. | | `WARN_AUDIO_NONFINITE` | Runtime | A non-finite sample reached the master chain and the containing block was muted. | | `WARN_AUDIO_UNAVAILABLE` | Runtime | No audio device is available; the performance continues silently. | +| `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). | +| `WARN_VISUAL_APPROXIMATION` | Runtime | A declared appearance feature is unavailable on the active renderer and the documented fallback was used. | ## 8. Shared value resolution, bindings, and transitions @@ -514,7 +519,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 | Subsystem contract (Phase 4) | +| 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) | | 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** | @@ -1499,3 +1504,394 @@ User-observed, and **not** satisfiable by the above: 17. The PRD 129 audio acceptance challenge passes in full. Traces 15 through 17 close Phase 3 slice 3c-4. Until they do, Phase 3 is not accepted no matter how many automated traces pass. + +--- + +## 17. Visual Subsystem Contract — Scene, Primitives, Transforms, and Appearance (Phase 4a) + +This section opens the Visual contract required by section 11 (PRD 69-89). It covers the visual pipeline overview, canonical units, the `visuals` container and layer set, the scene model and coordinate spaces (PRD 70), 2.5D depth (PRD 71), the fourteen geometry primitives (PRD 72), common visual properties (PRD 73), the transform model (PRD 74), appearance (PRD 75), and paths and splines (PRD 76). + +Visual components, particle systems, placement distributions, emitters, repeaters, behaviors, procedural fields, and trails/ribbons/links (PRD 77-84) are specified in section 18 (Phase 4b). 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). + +### 17.1 Pipeline and scope + +The visual pipeline is, in order: visual primitives (17.9-17.13) → reusable `components.visual.*` components (18.1) → procedural systems (18.2-18.8) → layers (17.5) → camera (19.3) → post effects (19.4) → display (PRD 69). + +The schema is renderer-neutral: nothing in this section names a drawing API, and every construct is expressible by any renderer that can composite transformed, styled 2D geometry. XZBT 0.1 realizes it with Canvas 2D (PRD 69). Where a declared appearance feature is unavailable on the active renderer, this contract fixes the fallback and the runtime raises `WARN_VISUAL_APPROXIMATION`; it never silently omits the feature and never substitutes a materially different one. + +XZBT never implements a primitive, field, or system named after a specific exhibit's subject. There is no `planet`, `tree`, `ship`, or `machine` primitive (PRD 72). Every construct in this contract is generic, and the fourteen PRD 130 challenge items must be reachable by composition alone. + +### 17.2 Canonical units + +| Quantity | Unit | Notes | +| --- | --- | --- | +| position, size, length | scene units of the declared coordinate space (17.4) | Not CSS pixels unless `coordinateSpace` is `viewport`. | +| depth | scene units on the `z` axis | Increasing `z` is **farther from the camera** (17.6). | +| angle | degrees | `0` points along `+x`; positive angles turn toward `+y`. Applies to `rotation`, `skew`, `arc` bounds, and conic gradient angles. | +| scale | unitless multiplier | `1` is unscaled. Negative values mirror. | +| opacity and normalized controls | `0` to `1` | Polarity-free control ranges (`opacity`, glow `strength`, filter `amount`, `tension`). | +| color | `color` (section 2) | Alpha may be carried in `#rrggbbaa` and multiplies the applicable opacity. | +| stroke width, blur radius, glow radius, shadow offset | scene units | Measured before camera scaling (19.3), so a zoomed camera scales them with the geometry. | +| time | DurationSpec 0.1 | Section 6; a procedural `TimeSpec` is permitted wherever a field's contract says DurationSpec. | +| rate | occurrences per logical second | Section 9.1 logical time, never wall time or frame count. | + +Angles are degrees throughout the visual contract. The audio contract's `phase` field (14.7) is already degrees; no visual field is authored in radians or turns. + +### 17.3 The `visuals` block + +```json +{ + "visuals": { + "scene": { "coordinateSpace": "virtual", "width": 1600, "height": 900, "background": "#020308" }, + "layers": { "far": { "parallax": 0.2 }, "near": {} }, + "systems": { + "horizon": { + "type": "graphic", + "layer": "far", + "content": { + "band": { "type": "rectangle", "position": { "x": 0, "y": 620 }, "size": { "width": 1600, "height": 4 }, "style": { "fill": "#1d2b3a" } } + } + } + } + } +} +``` + +| Field | Type | Required | Notes | +| --- | --- | :---: | --- | +| `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`. | +| `camera` | object | No | Camera (19.3). | +| `effects` | array | No (defaults to `[]`) | Post-effect chain (19.4). | + +Per the strict unknown-field policy (section 1), any other property of `visuals` is `ERR_UNKNOWN_FIELD`. Layer and system keys follow the shared identifier rule (section 1.3); a key failing the regex is `ERR_INVALID_ID`. + +### 17.4 Scene model (`visuals.scene`) + +| Field | Type | Required | Default | Notes | +| --- | --- | :---: | --- | --- | +| `coordinateSpace` | enum | No | `virtual` | `normalized`, `viewport`, or `virtual` (PRD 70). | +| `width` | number | Conditional | — | Required if and only if `coordinateSpace` is `virtual`. `1` to `16384`. | +| `height` | number | Conditional | — | Required if and only if `coordinateSpace` is `virtual`. `1` to `16384`. | +| `fit` | enum | No | `contain` | `contain`, `cover`, or `stretch`. | +| `background` | `color` | No | `#000000` | Cleared to this color before every frame. | +| `depthFog` | object | No | — | Depth fog (17.6). | + +`width` or `height` present while `coordinateSpace` is not `virtual` is `ERR_UNKNOWN_FIELD`; either absent while it is `virtual` is `ERR_SCHEMA_VALIDATION`. + +The three coordinate spaces: + +* **`normalized`** — the scene is the unit square. `x` and `y` run `0` to `1` from the top-left. The square is mapped into the display surface under `fit`. +* **`viewport`** — scene units are CSS pixels of the display surface, origin at its top-left. The scene has no intrinsic size, so `fit` is ignored and a `fit` other than `stretch` is `ERR_SCHEMA_VALIDATION`. Content is responsible for its own responsiveness; the runtime does not scale it. +* **`virtual`** — the scene is a fixed `width` x `height` design space with the origin at its top-left, mapped into the display surface under `fit`. This is the recommended space for reproducible composition, and the one the reference exhibits use. + +`fit` resolves the scene rectangle against the display rectangle: `contain` scales uniformly until the scene fits entirely inside, letterboxing the remainder with `background`; `cover` scales uniformly until the scene covers the display, cropping the overflow; `stretch` scales each axis independently, changing the aspect ratio. Coordinates outside the scene rectangle are legal and are simply outside the visible area under `contain` and `stretch`. + +Scene-to-device resolution — the device-pixel-ratio multiplier and its ceiling — is a runtime resource concern and is fixed in 19.5, not by the exhibit. An exhibit never declares a device resolution. + +### 17.5 Layers (`visuals.layers`) + +Layers are the compositing groups of PRD 69. Each `visuals.layers.` is: + +| Field | Type | Required | Default | Notes | +| --- | --- | :---: | --- | --- | +| `opacity` | ValueSpec\ | No | `1` | `0` to `1`. Applied to the composited layer, not per object. | +| `blend` | enum | No | `normal` | Safe blend set (17.12). | +| `visible` | ValueSpec\ | No | `true` | An invisible layer is composited out entirely and its systems still advance. | +| `parallax` | number | No | `1` | Multiplier on camera translation for this layer (17.6, 19.3). `0` pins the layer to the display. | + +Layers composite in document key order, back to front. A system's `layer` must name a declared layer or `ERR_INVALID_REFERENCE`. When `visuals.layers` is absent, one implicit layer with every default applies and a system's `layer` field is `ERR_INVALID_REFERENCE`. + +More than `16` layers is `ERR_VISUAL_LIMIT_EXCEEDED`. A layer whose `opacity` or `blend` is not the default requires an offscreen compositing buffer; those buffers count against the pass budget of 19.5. + +### 17.6 Visual depth + +XZBT 0.1 supports 2.5D depth through an optional `z` on any visual object (PRD 71). It has no meshes, no lighting model, and no occlusion beyond draw order. + +**Sign.** Increasing `z` is farther from the camera. `z` defaults to `0`. + +**Depth sorting.** Within one layer, objects are drawn farthest first: sort by effective `z` descending, breaking ties by document key order. The sort is stable, so equal-`z` objects always draw in document order — the same document-order rule sections 14.4 and 9.3 already use for sampling. + +**Perspective scaling.** Under the `perspective` camera projection (19.3), an object's transform is post-multiplied by the uniform factor `focalLength / (focalLength + z)` about the camera's projection center. `z <= -focalLength` places an object at or behind the eye; such an object is culled for that frame and raises no diagnostic. Under `orthographic`, `z` affects sorting, parallax, and fog but never scale. + +**Parallax.** A layer's `parallax` multiplies the camera's translation before it is applied to that layer, giving the deep parallax field of PRD 130 item 1 without any subject-specific renderer. + +**Depth fog.** `visuals.scene.depthFog` is optional: + +| Field | Type | Required | Default | Notes | +| --- | --- | :---: | --- | --- | +| `color` | `color` | Yes | — | Color the object is blended toward. | +| `near` | number | No | `0` | `z` at which fogging begins. | +| `far` | number | Yes | — | `z` at which fogging reaches full `density`. `far` must exceed `near` or `ERR_INVALID_RANGE_ORDER`. | +| `density` | number | No | `1` | `0` to `1`. Maximum blend fraction. | + +The fog fraction for an object is `density * clamp((z - near) / (far - near), 0, 1)`, and its resolved fill, stroke, glow, and shadow colors are blended toward `color` by that fraction before drawing. Fog applies per object, not per pixel, so it is exact and renderer-independent; it is not an approximation and raises no diagnostic. + +### 17.7 Visual system objects (`visuals.systems.`) + +A **visual system** is the addressable unit of the visual subsystem and the resource whose namespace section 1.3 reserves as `visuals.systems.*`. + +| Field | Type | Required | Default | Notes | +| --- | --- | :---: | --- | --- | +| `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. | +| *(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. + +### 17.8 The `graphic` system + +```json +{ + "type": "graphic", + "content": { + "hull": { "type": "polygon", "points": [{ "x": 0, "y": 0 }, { "x": 90, "y": 0 }, { "x": 45, "y": 70 }] } + } +} +``` + +| Field | Type | Required | Notes | +| --- | --- | :---: | --- | +| `content` | object keyed by visual object ID | Yes | Visual objects (17.9). An empty `content` object is `ERR_SCHEMA_VALIDATION`. | + +**Object identity.** Visual objects are addressed by their key in the containing `content` or `children` object, matching how the document keys `parameters`, `state`, `audio` nodes (14.3), buses, and systems. A visual object therefore carries **no** `id` field; supplying one is `ERR_UNKNOWN_FIELD`. Keys follow the shared identifier rule (section 1.3) and are unique within their container by construction. Two objects in different containers may share a key; a full object path is the system ID followed by each container key in turn. + +Document key order within a container is draw order for equal `z` (17.6), so a renderer must preserve the source key order of `content` and `children` rather than reordering them. + +### 17.9 Visual objects and Visual Primitive Set 0.1 + +Every value in a `content` or `children` container is a **visual object**: a `type` from Visual Primitive Set 0.1, its type-specific geometry fields, and the common properties of 17.10. + +| `type` | Geometry fields | Notes | +| --- | --- | --- | +| `point` | — | A zero-extent mark drawn as a filled dot of `style.pointSize` (17.12). The cheapest primitive; the starfield of PRD 130 item 1 is points under a repeater. | +| `line` | `to` (`{x, y, z?}`) | From the object's origin to `to`. Stroke only; `style.fill` on a `line` is `ERR_UNKNOWN_FIELD`. | +| `polyline` | `points` (array, `2` to `512`) | Open chain. Stroke only. | +| `polygon` | `points` (array, `3` to `512`) | Implicitly closed. Fill and stroke. | +| `rectangle` | `size` (`{width, height}`) | Axis-aligned in local space before `transform`. | +| `rounded-rectangle` | `size`, `radius` | `radius` is a number or `{topLeft, topRight, bottomRight, bottomLeft}`. Each radius clamps to half the shorter side. | +| `ellipse` | `radius` (number or `{x, y}`) | A single number is a circle. | +| `arc` | `radius`, `startAngle`, `endAngle`, `direction` | `direction` is `clockwise` (default) or `counter-clockwise`. Stroke only. A sweep whose magnitude exceeds `360` degrees is `ERR_OUT_OF_BOUNDS`. | +| `ring` | `radius`, `innerRadius`, `startAngle`, `endAngle`, `direction` | An annulus or annular sector. `innerRadius` must be less than `radius` or `ERR_INVALID_RANGE_ORDER`. Angles default to a full ring. Fill and stroke. | +| `path` | `commands` | 17.13. | +| `bezier` | `c1`, `c2`, `to` | One cubic segment from the object's origin. Stroke only. | +| `spline` | `points`, `mode`, `closed`, `tension` | 17.13. | +| `text` | `text`, `font`, `size`, `align`, `baseline`, `maxWidth` | 17.13. | +| `group` | `children` | A transform and style scope. Fills and strokes are not drawn for the group itself. | + +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`. + +`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`. + +### 17.10 Common visual properties + +Where applicable to the primitive (PRD 73): + +| Field | Type | Required | Default | Notes | +| --- | --- | :---: | --- | --- | +| `position` | `{x, y}`, each ValueSpec\ | No | `{0, 0}` | The object's origin in its parent's local space. | +| `z` | ValueSpec\ | No | `0` | Depth coordinate (17.6). PRD 73 names this property "depth"; it is spelled `z` throughout this contract, matching PRD 71. | +| `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. | +| `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`. | + +`layer` is deliberately a system-level property: layers are compositing groups, and allowing sibling objects within one transform hierarchy to composite into different layers would make the hierarchical transform of 17.11 meaningless. + +### 17.11 Transform model + +```json +{ + "transform": { + "translate": { "x": 12, "y": 0, "z": -40 }, + "rotation": 30, + "scale": { "x": 1.5, "y": 1.5 }, + "skew": { "x": 0, "y": 0 }, + "origin": { "x": 45, "y": 35 } + } +} +``` + +| Field | Type | Default | Notes | +| --- | --- | --- | --- | +| `translate` | `{x, y, z}`, each ValueSpec\ | `{0, 0, 0}` | Added to `position` and `z`. | +| `rotation` | ValueSpec\ | `0` | Degrees (17.2). | +| `scale` | `{x, y}`, each ValueSpec\ | `{1, 1}` | Negative values mirror. `0` collapses the object; this is legal and draws nothing. | +| `skew` | `{x, y}`, each ValueSpec\ | `{0, 0}` | Degrees. Each magnitude must be below `90` or `ERR_OUT_OF_BOUNDS`. | +| `origin` | `{x, y}`, each ValueSpec\ | `{0, 0}` | Pivot for rotation, scale, and skew, in the object's own local space. | + +**Composition order is normative.** An object's local matrix is, applied right to left to a local-space point: + +```text +M_local = T(position + translate) x T(origin) x R(rotation) x K(skew) x S(scale) x T(-origin) +``` + +so a point is scaled, then skewed, then rotated about `origin`, then translated. The `z` component of `translate` adds to the object's `z` and takes part in depth sorting and perspective (17.6); it is not part of `M_local`. + +**Hierarchy.** For an object inside a `group`, the effective matrix is `M_parent x M_local`, and effective `z` is `z_parent + z_local`. Transforms therefore compose down the tree exactly as PRD 74 requires, and a group's `origin` pivots its whole subtree. + +A renderer must not fold the order differently even where the result would coincide for a particular object; exhibits depend on the stated order wherever rotation and non-uniform scale are combined. + +### 17.12 Appearance + +`style` is an object whose fields are inherited by a `group`'s descendants unless the descendant redeclares them. Inheritance is per field, not per object. + +| Field | Type | Default | Notes | +| --- | --- | --- | --- | +| `fill` | `color`, paint object, or `null` | `null` | `null` draws no fill. | +| `stroke` | `color`, paint object, or `null` | `null` | `null` draws no stroke. | +| `strokeWidth` | ValueSpec\ | `1` | Scene units. `0` draws no stroke. | +| `strokeCap` | enum | `butt` | `butt`, `round`, `square`. | +| `strokeJoin` | enum | `miter` | `miter`, `round`, `bevel`. | +| `strokeDash` | array of numbers | — | Alternating on/off lengths in scene units, `1` to `8` entries. | +| `strokeDashOffset` | ValueSpec\ | `0` | Scene units. | +| `pointSize` | ValueSpec\ | `1` | Diameter of a `point`, in scene units. | +| `opacity` | ValueSpec\ | `1` | `0` to `1`. Multiplies with any inherited group opacity and with layer opacity. | +| `blend` | enum | `normal` | Safe blend set below. | +| `glow` | object or `null` | `null` | `{ "color": color, "radius": number, "strength": number }`. `strength` is `0` to `1`. | +| `shadow` | object or `null` | `null` | `{ "color": color, "blurRadius": number, "offsetX": number, "offsetY": number }`. | +| `blur` | ValueSpec\ | `0` | Object blur radius in scene units. | +| `filters` | array | `[]` | Ordered filter entries, `0` to `4`. | +| `clip` | object or `null` | `null` | Clip region; see below. | +| `mask` | string or `null` | `null` | `group` only; see below. | + +**Safe blend set (PRD 75).** `normal`, `add`, `screen`, `multiply`, `overlay`, `lighten`, `darken`, `difference`. Any other token is `ERR_SCHEMA_VALIDATION`. This is the set every target renderer supports identically; no other blend mode is authorable in 0.1. + +**Paint objects.** A `fill` or `stroke` may be a gradient instead of a flat `color`: + +| Field | Type | Required | Notes | +| --- | --- | :---: | --- | +| `type` | enum | Yes | `linear-gradient`, `radial-gradient`, `conic-gradient`. | +| `stops` | array | Yes | `2` to `16` entries of `{ "offset": number, "color": color }`. | +| `from`, `to` | `{x, y}` | `linear-gradient` only | Gradient axis in the object's local space. | +| `center`, `radius` | `{x, y}`, number | `radial-gradient` only | `innerRadius` optional, default `0`. | +| `center`, `angle` | `{x, y}`, number | `conic-gradient` only | `angle` is the start angle in degrees, default `0`. | + +Stop `offset` values are `0` to `1` and must be strictly increasing; a non-increasing pair is `ERR_INVALID_RANGE_ORDER`, the same code section 14 uses for inverted bounds. More than `16` stops is `ERR_VISUAL_LIMIT_EXCEEDED`. + +**Conic gradient fallback.** PRD 75 makes conic gradients conditional on renderer support. Where the active renderer has no conic gradient, the runtime substitutes a `linear-gradient` with the same stops along the axis from `center` at `angle` to the paint's bounding-box edge, and raises `WARN_VISUAL_APPROXIMATION` once per paint instance, not once per frame. This is the only appearance fallback in 0.1; every other field in this table is exact on every target renderer. + +**Filters.** Each entry is `{ "type": enum, "amount": ValueSpec }` with `type` in `brightness`, `contrast`, `saturate`, `hue-rotate`, `grayscale`, `sepia`, `invert`. `amount` is `0` to `4` for `brightness`, `contrast`, and `saturate`; `0` to `1` for `grayscale`, `sepia`, and `invert`; and degrees for `hue-rotate`. Filters apply in array order, after the object is drawn and before it composites into its layer. More than `4` entries is `ERR_VISUAL_LIMIT_EXCEEDED`. + +**Clipping.** `clip` is `{ "shape": "rectangle" | "ellipse", "x": number, "y": number, "width": number, "height": number }`, expressed in the object's local space and intersected with any clip inherited from an ancestor. A clip on a non-`group` primitive clips only that primitive. + +**Masking.** `mask` is legal only on a `group` and names one key in that group's own `children`. The named child is not drawn; its rendered alpha multiplies the alpha of the group's remaining children. A `mask` naming a missing key is `ERR_INVALID_REFERENCE`; a `mask` on a non-`group` object is `ERR_UNKNOWN_FIELD`. + +**Cost.** A non-default `blend`, a `mask`, a `blur`, a `glow`, or any `filters` entry requires an offscreen buffer for the object or group. Those buffers count against the pass budget fixed in 19.5; this section declares the cost, and section 19 sets the ceiling. + +**Stroke-only primitives.** `line`, `polyline`, `arc`, and `bezier` have no interior. A `fill` *declared* on one of them is `ERR_UNKNOWN_FIELD`; a `fill` *inherited* from an ancestor group is ignored for those primitives and still applies to their fillable siblings. Inheritance never turns into a validation error. + +### 17.13 Paths, splines, and text + +**Path (`path`).** `commands` is an ordered array of `1` to `512` command objects (PRD 76): + +| `op` | Fields | Notes | +| --- | --- | --- | +| `move` | `to` | Begins a subpath at `to`. | +| `line` | `to` | Straight segment. | +| `quadratic` | `c`, `to` | One quadratic segment with control point `c`. | +| `cubic` | `c1`, `c2`, `to` | One cubic segment. | +| `arc` | `radius`, `rotation`, `largeArc`, `sweep`, `to` | Endpoint-parameterized elliptical arc. `radius` is a number or `{x, y}`; `rotation` is degrees; `largeArc` and `sweep` are booleans defaulting to `false`. | +| `close` | — | Closes the current subpath back to its `move` point. | + +A `path` also accepts `fillRule`, an enum of `nonzero` (default) or `evenodd`. + +Path legality: + +* A `commands` array whose first entry is not `move` is `ERR_INVALID_PATH`. +* A `close` with no open subpath, or two consecutive `close` commands, is `ERR_INVALID_PATH`. +* An `arc` with a zero or negative radius component is `ERR_OUT_OF_BOUNDS`. It is **not** silently degraded to a `line`: PRD 89 requires clearly excessive or degenerate values to fail validation rather than become something materially different. +* More than `512` commands is `ERR_VISUAL_LIMIT_EXCEEDED`. + +**Spline (`spline`).** + +| Field | Type | Required | Default | Notes | +| --- | --- | :---: | --- | --- | +| `points` | array of `{x, y, z?}` | Yes | — | `2` to `256` entries. | +| `mode` | enum | No | `catmull-rom` | `catmull-rom`, `bezier`, `linear` (PRD 76). | +| `closed` | boolean | No | `false` | Joins the last point back to the first. | +| `tension` | ValueSpec\ | No | `0.5` | `0` to `1`. `catmull-rom` only; present under another mode is `ERR_UNKNOWN_FIELD`. | +| `fillRule` | enum | No | `nonzero` | Applies only when the spline is `closed` and has a fill. | + +In `bezier` mode the points are read as `p0, c1, c2, p1, c1, c2, p2, ...`, so the entry count must satisfy `count = 3n + 1` for an integer `n >= 1`; any other count is `ERR_SCHEMA_VALIDATION`. In `linear` mode the spline is a polyline over its points, differing from `polyline` only in that its points are individually addressable. In `catmull-rom` mode the curve passes through every point; for an open spline the first and last points are duplicated as the phantom endpoints. + +**Independently mutable points.** PRD 76 requires spline points to be independently mutable so that slow point wandering and morphing can build evolving organic geometry. This contract fixes what that means: a spline's point *count* is fixed at instantiation (17.14) and its point *indices* are stable for the object's lifetime, so index `i` always denotes the same point. Individual coordinates are addressable as `points[].x`, `.y`, and `.z` by the behaviors of 18.6 and by the visual automation of 19.1. More than `256` points is `ERR_VISUAL_LIMIT_EXCEEDED`. Morphing between two splines requires equal point counts and equal `mode`; section 18.6 fixes that rule and its diagnostic. + +**Text (`text`).** + +| Field | Type | Required | Default | Notes | +| --- | --- | :---: | --- | --- | +| `text` | ValueSpec\ | Yes | — | Max `256` characters after resolution. | +| `font` | enum | No | `sans-serif` | `sans-serif`, `serif`, `monospace` only. | +| `size` | ValueSpec\ | No | `16` | Scene units, not CSS pixels. | +| `weight` | enum | No | `normal` | `normal`, `bold`. | +| `italic` | boolean | No | `false` | — | +| `align` | enum | No | `left` | `left`, `center`, `right`, relative to the object's origin. | +| `baseline` | enum | No | `alphabetic` | `top`, `middle`, `alphabetic`, `bottom`. | +| `letterSpacing` | ValueSpec\ | No | `0` | Scene units. | +| `maxWidth` | ValueSpec\ | No | — | When set, the rendered run is condensed horizontally to fit; it is never wrapped or truncated. | + +`font` is restricted to the three generic CSS families because section 13.3 requires the standalone artifact to carry every asset it needs and make no external request. A named family would resolve differently on each viewer's machine and could not be embedded without shipping a font file; 0.1 does not ship one. + +**Live numeric readouts are not in 0.1.** `text` resolves once at instantiation (17.14), so it renders authored strings and static labels, not values that change while the exhibit runs. The instrument and radar displays of PRD 130 item 7 and Exhibit D are built from generic geometry — `arc`, `ring`, `path`, and `polyline` driven by state through the mechanisms of section 19 — with `text` supplying fixed labels. This is a deliberate scope decision, not an oversight: a live readout needs a number-to-string formatting contract (precision, rounding, locale, unit suffixes) that PRD 72 does not specify and that would be the first subject-shaped construct in the visual set. Slice 4g revisits it if the Exhibit D visuals cannot be built without it. + +### 17.14 Visual field resolution scope + +Numeric, boolean, string, and color fields on visual objects are authored with ValueSpec 0.1 (section 4) and DurationSpec 0.1 (section 6) exactly where each field table says so. Every such field is resolved **once**, at its owning object's instantiation boundary, and is constant for that object's lifetime. Nested ValueSpecs within one system are sampled in depth-first, property-document order from the system instance's own stream, per section 9.3 — the same rule section 14.4 fixes for audio node fields. + +The instantiation boundary is: activation, for a persistent system declared in `visuals.systems`; the spawn moment, for a spawned system (19.2); and the emission or repetition moment, for an object created by an emitter or repeater (18.4, 18.5). + +**Rendering consumes no procedural stream.** Section 9.3 already fixes this. A frame is not a decision point, so no visual field is re-sampled per frame, and an exhibit's procedural decisions are identical across render rates and machines. Visual draws use the `visual` stream domain; the reproducibility scope of section 14.6 has no visual analogue, because no visual construct in 0.1 generates per-sample output outside the seeded streams. + +Time variation therefore comes from exactly three mechanisms, all specified later in this contract: + +1. **Behaviors** (18.6) — drift, orbit, wander, point-wander, morph, and the rest of the PRD 82 vocabulary, attached to an object and advanced on the logical clock. +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. + +### 17.15 New diagnostic codes + +Added to the section 7 table, which remains the single authoritative list: + +| Error Code | Stage | Cause | +| :--- | :--- | :--- | +| `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). | +| `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 + +Automated, and executable without a display measurement: + +1. Each of the three coordinate spaces maps a known scene point to the expected display point under each `fit` mode, including the letterbox offsets of `contain` and the crop of `cover`; `width` or `height` outside `virtual` is `ERR_UNKNOWN_FIELD`, and either missing inside it is `ERR_SCHEMA_VALIDATION`. +2. Every primitive in Visual Primitive Set 0.1 instantiates from a minimal example, and an unknown `type` is `ERR_INVALID_PRIMITIVE_TYPE` while an unknown *property* on a known type is `ERR_UNKNOWN_FIELD`. +3. The transform composition order of 17.11 is verified by a case where rotation and non-uniform scale do not commute: a known local point maps to the documented device point, and reversing any two stages changes the result. +4. A nested `group` composes `M_parent x M_local` and `z_parent + z_local`; nesting `9` levels deep is `ERR_VISUAL_LIMIT_EXCEEDED` and `8` passes. +5. Depth sorting draws greater `z` first and preserves document key order for equal `z`; the sort is stable across repeated frames with unchanged input. +6. Perspective scaling matches `focalLength / (focalLength + z)` at three depths; an object at `z = -focalLength` is culled without a diagnostic; under `orthographic` the same object is unscaled. +7. Depth fog blends an object's colors by `density * clamp((z - near) / (far - near), 0, 1)` at `near`, midpoint, and beyond `far`; `far <= near` is `ERR_INVALID_RANGE_ORDER`. +8. Style inheritance is per field: a child redeclaring `fill` keeps its ancestor's `stroke`; a `fill` inherited by a `line` is ignored while a `fill` declared on a `line` is `ERR_UNKNOWN_FIELD`. +9. Gradient stops out of increasing order are `ERR_INVALID_RANGE_ORDER`; `17` stops, `5` filters, `9` layer nesting levels, `513` path commands, `257` spline points, and `513` polygon vertices are each `ERR_VISUAL_LIMIT_EXCEEDED`, and each limit's maximum legal value passes. +10. A renderer reporting no conic-gradient support falls back to the documented linear gradient and raises `WARN_VISUAL_APPROXIMATION` exactly once per paint instance across many frames, never once per frame. +11. Path legality: a first command other than `move` and an unopened `close` are `ERR_INVALID_PATH`; a zero-radius `arc` is `ERR_OUT_OF_BOUNDS` rather than a line; a `bezier`-mode spline whose point count is not `3n + 1` is `ERR_SCHEMA_VALIDATION`. +12. A `mask` naming a missing child is `ERR_INVALID_REFERENCE`; a `mask` on a non-`group` object is `ERR_UNKNOWN_FIELD`; the masked child is not itself drawn. +13. Visual ValueSpec fields resolve once: a `random` position sampled at instantiation holds its value across many frames, two runs of the same seed produce identical resolved values, and rendering `600` frames consumes no procedural stream. +14. A binding, `set`, or `override` addressing any visual property is `ERR_UNSUPPORTED_TARGET`, and the section 8.1 table is unchanged by this slice. + +User-observed, and **not** satisfiable by the above: + +15. The fourteen PRD 130 challenge items are reachable by composition of this primitive, transform, and appearance set with sections 18 and 19, judged visually on a real display, with no subject-specific renderer code. +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. diff --git a/docs/XZBT_0-1_Implementation_Plan.md b/docs/XZBT_0-1_Implementation_Plan.md index a7f51d0..476301b 100644 --- a/docs/XZBT_0-1_Implementation_Plan.md +++ b/docs/XZBT_0-1_Implementation_Plan.md @@ -35,6 +35,27 @@ Slices 3c-1 through 3c-3 can be implemented and verified without an audio device An installment is complete only when its own contract, implementation, tests, and evidence record are all in place. A contract installment is reviewed against its PRD sections and the shared contracts before its dependent installment begins; the Phase 3a review, which closed four blocking defects before Phase 3b started, is the worked example. +## Phase 4 delivery slices + +Phase 4 is the largest milestone in this plan: twenty-one PRD sections (69-89) plus the shared ceilings of PRD 119-120, a fourteen-item acceptance challenge (PRD 130), and the first visuals for four reference exhibits. It is delivered in eight slices under the same rule as Phase 3c — each slice ends green, committed, and pushable on its own — and under the project rule that a subsystem contract is finished immediately before the implementation that depends on it. + +| 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 | — | +| 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 | — | +| 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 | — | +| 4g — Challenge fixtures and exhibit visuals | The fourteen PRD 130 challenge fixtures, proven to need no subject-specific renderer code, and the first visuals for Exhibits A-D. | 4d, 4e, 4f | — | +| 4h — Early combined GC6 benchmark | Freeze the benchmark ID, version, seed, and counts; run the 30-second warm-up and 120-second measurement window; record frame intervals, runtime counters, and memory samples; derive the workload ceilings that fix later optimization strategy. | 4g | **User-observed.** Requires a real reference computer and browser at 1920 x 1080; cannot be closed by automated tests. | + +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. + +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. + ## MVP completion-criteria map | Completion criterion group | Primary milestone | Final evidence | diff --git a/docs/evidence/phase4/2026-09-06-phase4a-contract.md b/docs/evidence/phase4/2026-09-06-phase4a-contract.md new file mode 100644 index 0000000..a283b1a --- /dev/null +++ b/docs/evidence/phase4/2026-09-06-phase4a-contract.md @@ -0,0 +1,120 @@ +# Phase 4a evidence — Visual subsystem contract, part I (Format Specification section 17) + +**Date:** September 6, 2026 +**Slice:** 4a — Contract I (documentation only; no runtime change) +**Baseline:** `main` at `6aab663` (Phase 3c slice 4) +**Specification revision:** 0.4 → 0.5 +**PRD coverage:** 69-76 + +## What this slice fixes + +Section 17 opens the Visual contract required by section 11. It covers the visual pipeline and its +renderer-neutral scope, canonical units, the `visuals` container, layers, the scene model and the +three coordinate spaces, 2.5D depth, the fourteen geometry primitives of Visual Primitive Set 0.1, +common visual properties, the transform model, appearance and paint, paths and splines and text, and +the resolution boundary for visual ValueSpec fields. + +Sections 18 (Phase 4b) and 19 (Phase 4c) are named throughout as the owners of procedural systems and +of automation, lifecycle, camera, post-effects, and the centralized ceilings. Those forward references +are deliberate and mirror how section 14 referenced sections 15 and 16 before they were written; they +are the only unresolved numeric references in section 17 and they close when 4b and 4c land. + +## Decisions taken, so 4d does not relitigate them + +1. **Angles are degrees everywhere**, `0` along `+x`, positive turning toward `+y`. No visual field is + authored in radians or turns. This matches the audio contract's `phase` field (14.7). +2. **Increasing `z` is farther from the camera.** Depth sorting draws greater `z` first and breaks ties + by document key order, using the same document-order rule sections 9.3 and 14.4 already rely on. + Perspective scaling is `focalLength / (focalLength + z)`; `orthographic` leaves `z` affecting sorting, + parallax, and fog but never scale. +3. **Visual objects are keyed, not listed.** `content` and `children` are objects keyed by ID and a + visual object carries no `id` field. This is the same node-identity rule section 14.3 adopted after + the contradictory version was one of the four blocking defects the Phase 3a review closed; adopting + it up front here avoids repeating that defect in a second subsystem. +4. **Transform composition order is normative:** + `M_local = T(position + translate) x T(origin) x R(rotation) x K(skew) x S(scale) x T(-origin)`, + with `M_parent x M_local` down a group hierarchy. Trace 3 pins it with a case where rotation and + non-uniform scale do not commute, so a renderer cannot fold the order differently and pass. +5. **Visual ValueSpec fields resolve once, at their object's instantiation boundary**, exactly as + section 14.4 fixes for audio node fields. Rendering consumes no procedural stream, so procedural + decisions stay identical across render rates and machines. Time variation comes from behaviors + (18.6), visual automation (19.1), and the section 8.1 rows that 19.1 adds — from nothing else. +6. **No visual property is externally addressable in this slice.** A binding, `set`, or `override` + naming one is `ERR_UNSUPPORTED_TARGET`, and the section 8.1 table is unchanged. Section 1.3 reserves + the `visuals.systems.*` namespace; reserving a namespace does not grant a capability. +7. **`layer` is a system property, not an object property.** Letting siblings inside one transform + hierarchy composite into different layers would make the hierarchical transform meaningless. +8. **Depth fog is per object, not per pixel**, so it is exact on any renderer and raises no diagnostic. + The conic gradient is the *only* appearance fallback in 0.1, and it raises `WARN_VISUAL_APPROXIMATION` + once per paint instance rather than once per frame. +9. **`font` is restricted to the three generic CSS families.** Section 13.3 requires the standalone + artifact to make no external request, and 0.1 ships no font file. +10. **Degenerate geometry fails validation rather than being transformed.** A zero-radius `arc` is + `ERR_OUT_OF_BOUNDS`, not a silently substituted line, per PRD 89. + +## Deliberate scope deferral + +**Live numeric readouts are not in 0.1.** Because `text` resolves once at instantiation, it renders +authored strings and static labels only. The instrument and radar displays of PRD 130 item 7 and +Exhibit D are to be built from generic geometry driven by state. The reason is recorded in 17.13: a live +readout needs a number-to-string formatting contract (precision, rounding, locale, unit suffixes) that +PRD 72 does not specify and that would introduce the first subject-shaped construct into the visual set. +**Slice 4g revisits this** if the Exhibit D visuals cannot be built without it. This is the one place in +section 17 where a later slice may have to reopen the contract. + +## New diagnostic codes + +Five codes were added to the section 7 table, which remains the single authoritative list: +`ERR_INVALID_SYSTEM_TYPE`, `ERR_INVALID_PRIMITIVE_TYPE`, `ERR_INVALID_PATH`, +`ERR_VISUAL_LIMIT_EXCEEDED`, and `WARN_VISUAL_APPROXIMATION`. Section 17.15 repeats them for locality, +exactly as 15.18 and 16.10 do. + +`ERR_INVALID_RANGE_ORDER`, `ERR_OUT_OF_BOUNDS`, `ERR_UNKNOWN_FIELD`, `ERR_SCHEMA_VALIDATION`, +`ERR_INVALID_REFERENCE`, `ERR_INVALID_ID`, and `ERR_UNSUPPORTED_TARGET` are reused rather than +duplicated under visual-specific names. + +## Authoring limits fixed in this slice + +| Limit | Bound | Diagnostic | +| --- | --- | --- | +| Layers per exhibit | 16 | `ERR_VISUAL_LIMIT_EXCEEDED` | +| Group nesting depth | 8 (matches audio component nesting, 15.15) | `ERR_VISUAL_LIMIT_EXCEEDED` | +| `polyline` / `polygon` vertices | 512 | `ERR_VISUAL_LIMIT_EXCEEDED` | +| `path` commands | 512 | `ERR_VISUAL_LIMIT_EXCEEDED` | +| `spline` points | 256 | `ERR_VISUAL_LIMIT_EXCEEDED` | +| Gradient stops | 16 | `ERR_VISUAL_LIMIT_EXCEEDED` | +| `filters` entries | 4 | `ERR_VISUAL_LIMIT_EXCEEDED` | +| `strokeDash` entries | 8 | `ERR_SCHEMA_VALIDATION` | +| `text` length | 256 characters | `ERR_SCHEMA_VALIDATION` | + +Runtime ceilings — active particles, emission rate, active spawned systems, link count, trail history, +blur radius, post-processing passes, and rendering resolution (PRD 89, 120) — belong to slice 4c and are +not set here. + +## Verification performed + +This slice changes documentation only. No runtime source, schema, fixture, or test file was touched. + +- Every diagnostic code used in section 17 resolves to a row in the section 7 table (checked + programmatically; zero unresolved). +- Every numeric cross-reference in section 17 resolves to an existing heading, except the intended + forward references to 18.1, 18.2, 18.4, 18.5, 18.6, 18.8, 19.1, 19.2, 19.3, 19.4, and 19.5. +- `npm test` was run to confirm the existing suite is unaffected; the result is recorded in the commit. +- The section 11 contract register's Visuals row now reads **In progress (Rev 0.5 / Phase 4a)** and names + what remains for 4b and 4c. + +## Test boundary + +Traces 1-14 of section 17.16 are automated and belong to slice 4d, where the renderer exists to run them. +Traces 15 and 16 — 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. Phase 4 is not accepted until both do. + +## State at this checkpoint + +Section 17 is contract only. No visual runtime code exists; `src/runtime` has no renderer module and the +JSON Schema has no visual definitions. 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 section 17-19 passes 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.