docs(visual): specify the phase 4b component, procedural system, and behavior contract

Format Specification section 18 (PRD 77-84) closes slice 4b: visual
components and their inputs scope, particle systems with a normative
integrator and life ramps, the nine placement distributions, emitters
with exact fractional-accumulator emission timing, repeaters and the
copy-scoped repeat.* namespace, the seventeen-behavior vocabulary and
its channel set, the six procedural fields with a normative coherent
noise function, and trails, ribbons, and links.

Seven diagnostic codes are added to the section 7 table. Section 17 is
amended in four places the new section requires: the visuals.fields
container, the component visual object type beside the closed fourteen
primitives, the now-declared behaviors field, and the widened visual
limit row. Revision 0.5 -> 0.6.

Documentation only; no runtime, schema, fixture, or test file changed.
npm test remains 102 passing.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019dTNHWyTdFobdevrKspoV6
This commit is contained in:
2026-09-06 04:13:36 +00:00
co-authored by Claude Opus 5
parent 3e27a668e2
commit 245af6ece3
4 changed files with 607 additions and 11 deletions
@@ -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.