Files
XZBT/reviews/.completed-artifacts/visual-contract-review-abacus-ai-agent-20260905-214821-deepseekv4.md
LabyricornandClaude Opus 5 c4332363a9 docs: raise the format specification to revision 0.9 and land the reconciliation
Revision 0.9 adds section 20, the cadence and event subsystems contract, and
carries two corrections the implementation forced. Section 6.1 now states that a
duration is the authored literal or a non-negative finite number already in
milliseconds, since a DurationSpec may be the resolved output of a ValueSpec or
a bounded TimeSpec, with the one documented exception of an automation track's
`at`, which 19.1 keeps literal-only so that point ordering stays decidable at
import. Section 20.11 documents the rejection of an undeclared input name in an
event action's `with` map as ERR_UNKNOWN_FIELD — the section's own convention
for that shape of error, replacing an invented code that appeared nowhere in the
registry.

The review record is committed with the code it describes: the two code triages
that found these defects, the reconciliation plan that sequenced the fixes, and
a follow-up debt record listing what was deliberately left open — the unchecked
JSON Schema artifact, degenerate path arcs, post-effect transient allocation,
the window-traffic fixture's per-copy wrap bounds, and the unstated
`ownership: "persistent"` value on a sound action. None of the five blocks phase
6; all five are written down rather than dropped.

Devlog entries are backfilled for the two milestones that had none: phase 3c
slice 2, the audio lifecycle and voice ceilings, and slice 4d, the renderer
core. The implementation status summary now reflects the reconciled state rather
than the in-flight one.

231 tests pass. tools/verify-spec-contract.py reports 46 declared diagnostic
codes with every used code resolving and its two long-standing unresolved
cross-references unchanged.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01ShxxFqFmCUDQnQvFNm4TKy
2026-09-06 21:54:09 +00:00

487 lines
47 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Visual contract review — Abacus AI Agent
Independent adversarial read of XZBT Format Specification **rev 0.7**, sections **17–19**, before Phase 4 slice **4d** (renderer core). Existing files under `reviews/` were not opened.
Standing decisions were treated as locked. This pass checks whether the spec text actually states them and whether it contradicts itself elsewhere.
Severity:
- **Blocker** — 4d cannot implement a unique, testable behavior without guessing.
- **Major** — two conforming 4d implementations can diverge, or a later slice will have to unwind 4d choices.
- **Minor** — underspecified edge, wrong diagnostic, or local inconsistency that 4d can paper over with a default.
---
## Defects
### 1. `visuals.automation` is both required and forbidden
- **Severity:** Blocker
- **Location:** §17.3 *The `visuals` block* vs §19.1 *Two declaration scopes*
- **What's wrong:** §17.3 enumerates `scene`, `layers`, `systems`, `fields`, `camera`, `effects` and then: “any other property of `visuals` is `ERR_UNKNOWN_FIELD`.” §19.1 places an `automation` array on `visuals` itself (`visuals.automation`). Under the strict unknown-field policy that array is illegal at import.
- **Why it matters for 4d:** The renderer core will parse `visuals`. If it follows §17.3 it rejects every exhibit that uses the §19.1 example. If it follows §19.1 it violates the §17.3 table. Slice 4d must know the legal key set before it writes a schema or a loader.
### 2. Trace 14 of §17.16 forbids the four §8.1 visual rows §19.1 adds
- **Severity:** Blocker
- **Location:** §17.16 trace 14 vs §17.14 last paragraph vs §19.1 *The visual rows of the section 8.1 target-capability table*
- **What's wrong:** Trace 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**.” §19.1 then adds four families (`visuals.camera.<field>`, `visuals.layers.<id>.opacity`, `visuals.systems.<id>.visible`, `visuals.effects[<index>].<param>`) that **are** binding/override targets. §17.14 already describes those four rows as if they exist. The acceptance gate for 4d therefore requires the opposite of the closed contract.
- **Why it matters for 4d:** 4d is the first code that will implement (or stub) target resolution. Implementing trace 14 literally means rejecting camera/layer/system/effect bindings that 4f traces 7 require to succeed. The 4d gate must be restated as “any visual property **outside the four §19.1 rows**.”
### 3. Automatable registry names a repeater `step` block that does not exist
- **Severity:** Blocker
- **Location:** §19.1 *The automatable registry* (“for `repeater`, the numeric fields of its `step` block”) vs §18.5 field table
- **What's wrong:** §18.5 has `repeat`, `count`, `distribution`, `position`, `inputs`, `behaviors`, `fields`, `links`. There is no `step` field, no `step` block, and no definition of what “numeric fields of its `step` block” are. Repeaters also have no `rate` (explicitly `ERR_UNKNOWN_FIELD`).
- **Why it matters for 4d:** Less for the first draw path than for the property-path resolver 4d will share with automation. A registry row with no corresponding schema is an unimplementable target. Either define `step` or delete the row.
### 4. Y-down scene vs “positive angles toward +y”
- **Severity:** Major
- **Location:** §17.2 *Canonical units* (“`0` points along `+x`; positive angles turn toward `+y`”) vs §17.4 (`x` and `y` run `0` to `1` **from the top-left**; virtual origin at top-left)
- **What's wrong:** Top-left origin with `y` increasing downward is a left-handed screen space. “Positive toward `+y`” is then **clockwise** on the display (standard canvas), not the mathematical CCW convention a reader of “toward +y” in a Cartesian plane will assume. The spec never says whether rotation is screen-clockwise or math-CCW, and never says whether `+y` in local object space is down.
- **Why it matters for 4d:** Transform order `R × K × S` is useless if the sign of `R` is ambiguous. Trace 3 of §17.16 (“a known local point maps to the documented device point”) cannot be written until this is fixed. Canvas 2D `rotate` is clockwise for y-down; a naive `cos/sin` toward +y with y-down will mirror every exhibit.
### 5. Local origin of box primitives is unspecified
- **Severity:** Major
- **Location:** §17.9 `rectangle`, `rounded-rectangle`, `ellipse`, `ring`, `arc`; §17.11 `origin` “in the object's own local space”; §17.13 text `align` “relative to the object's origin”
- **What's wrong:** For `rectangle` / `rounded-rectangle`, is `position` the top-left of `size`, the center, or something else? For `ellipse` / `ring` / `arc`, is `position` the center (implied by `radius` but never stated)? For `point`, is the dot centered on the origin? Text has `align`/`baseline`, which implies the origin is an anchor, but boxes have no equivalent. Default `origin` is `{0,0}`, so if a rectangle’s local space has its corner at 0, rotation is about the corner; if centered, about the center. These produce different pixels.
- **Why it matters for 4d:** First thing the primitive rasterizer needs. Two implementations will disagree on every rotated panel and every `contain`/`cover` fixture in trace 1/3.
### 6. Perspective factor vs camera matrix: two post-multiplies, no combined formula
- **Severity:** Major
- **Location:** §17.6 *Perspective scaling* (“object's transform is post-multiplied by `focalLength / (focalLength + z)` about the camera's projection center”) vs §19.3 (“applied **after** `V`”)
- **What's wrong:** §17.11 already post-multiplies `M_parent × M_local`. “Post-multiplied” in §17.6 does not say whether the factor is applied in local space, after `M_local`, after hierarchy, or after `V`. §19.3 says after `V`, about the **display** projection center. Applying a uniform scale about the display center to an already-view-transformed point is not the same as scaling the object about the camera look-at in scene space. `z` used in the factor is never named as **effective** `z` (`z_parent + z_local` from §17.11), though that is the only reading that matches hierarchy.
- **Why it matters for 4d:** Trace 6 of §17.16 and trace 14 of §19.7 are the 4d/4f camera tests. Without a single matrix (or a worked numeric example: one point, one `z`, one `V`, one output), 4d will pick an order that 4f then has to break.
### 7. `translate.z` is excluded from `M_local` but perspective needs a 3D point
- **Severity:** Major
- **Location:** §17.11 “The `z` component of `translate` adds to the object's `z` and … is **not** part of `M_local`.”
- **What's wrong:** `M_local` is therefore a 2D affine matrix. Perspective uses `z`. Hierarchy adds `z`. Particle integration moves `p` in 3D. The spec never says whether parent `M` rotates/scales child `z` (it cannot, if `M` is 2D) or only adds it. A child with local `z` under a rotated group: is depth the scalar sum, or a transformed coordinate? §17.11 says sum, which means rotation never tilts depth — consistent with 2.5D, but then “perspective about projection center” on a 2D matrix is a uniform XY scale, not a projective transform. That should be stated as “uniform XY scale, no vanishing-point projection.”
- **Why it matters for 4d:** Implementers will reach for a 4×4 perspective matrix. The contract wants a 2D scale. If 4d ships a real projection, every later fixture fails.
### 8. Depth sort unit is inconsistent (object vs system vs particle cloud)
- **Severity:** Major
- **Location:** §17.6 “Within one layer, **objects** are drawn farthest first” vs §18.2 “Particles of one system draw as **one unit** … The system's own position in its layer's depth sort is its **lowest-`z` particle**” vs §17.8 document key order of `content`
- **What's wrong:** Graphic systems contain many objects with independent `z`. Are those objects sorted **across systems** in the layer, or is each system an atomic band like particles? §17.6 says objects; §18.2 special-cases particles so they never interleave with unrelated objects. Emitters create full objects — §18.4 never says whether those objects join the layer object-sort or stay an atomic emitter band. Repeaters: same gap. A graphic system with `z=0` group and a child at `z=100` vs a sibling system at `z=50` has two legal draw orders.
- **Why it matters for 4d:** Painter’s algorithm is the entire 2.5D model. 4d must pick a sort key. Wrong choice is a visual bug in every multi-system scene and is expensive to change.
### 9. Layer `visible` is a ValueSpec but is not automatable, bindable, or in §8.1 — and still “advances”
- **Severity:** Major
- **Location:** §17.5 `visible` ValueSpec\<boolean\>, default `true`; §19.1 “`visuals.layers.<id>.visible` is deliberately **not** in the table”; §17.14 “every such field is resolved **once**, at its owning object's instantiation boundary”
- **What's wrong:** If layer `visible` resolves once, a ValueSpec is pointless except `random`/`choose` at activation. If it is meant to change, there is no writer: not automation (boolean), not binding (absent from §8.1), not `set`/`override`. The field is therefore a once-at-import flag dressed as a ValueSpec. Meanwhile system `visible` **is** an §8.1 binding target and is explicitly **not** automatable. The two `visible` flags look alike in §17 and behave unlike in §19.
- **Why it matters for 4d:** 4d will implement layer skip. It needs to know: evaluate once at activation, or subscribe to the pipeline? Implementing a live binding for layer `visible` would violate the locked “deliberately absent” decision; treating it as constant makes the ValueSpec type a lie.
### 10. §17.16 trace 9 cites “9 layer nesting levels” — layers do not nest
- **Severity:** Major
- **Location:** §17.16 trace 9 vs §17.5 (layers composite in **document key order**, no parent field) vs §17.9 (group nesting 8)
- **What's wrong:** Trace 9: “`9` layer nesting levels … are each `ERR_VISUAL_LIMIT_EXCEEDED`.” There is no layer nesting in §17.5. Group nesting is 8. Layer **count** is 16. The acceptance test for 4d is unrunnable as written.
- **Why it matters for 4d:** 4d’s exit gate includes a test that cannot be authored. Likely intent: group nesting 9 / 8, and separately 17 layers.
### 11. Fit mapping is underspecified for `cover` crop and letterbox placement
- **Severity:** Major
- **Location:** §17.4 `fit`; §17.16 trace 1
- **What's wrong:** `contain` letterboxes “the remainder with `background`” but does not say the scene is **centered** in the display (vs aligned top-left). `cover` “cropping the overflow” does not say which region is kept (centered crop vs origin-aligned). `viewport` ignores `fit` except that non-`stretch` is an error — so the only legal `fit` under `viewport` is `stretch`, which is also ignored. Device-pixel-ratio (§19.5) is applied after fit; the spec never gives the composed scene→CSS→device transform.
- **Why it matters for 4d:** Trace 1 is the first 4d test and requires “expected display point” including letterbox offsets. Without centering/crop rules those points are not determined.
### 12. Camera default “scene center” under `viewport` is not a scene quantity
- **Severity:** Major
- **Location:** §19.3 “the display center for `viewport`” as default `x`,`y`; camera is in “scene units”
- **What's wrong:** Viewport scene units **are** CSS pixels of the display, origin top-left, and the scene has no intrinsic size. Defaulting the camera to “display center” makes the default camera depend on the window size. Two machines, same exhibit, different default view. That contradicts “an exhibit that never mentions the camera” looking identical (§19.3’s own rationale) and contradicts reproducibility across display sizes.
- **Why it matters for 4d:** Identity camera vs “centered on display” are different matrices as soon as the canvas is not the design size. 4d will bake this into every frame.
### 13. Conic-gradient fallback axis is not a unique segment
- **Severity:** Major
- **Location:** §17.12 *Conic gradient fallback*
- **What's wrong:** Fallback is “a `linear-gradient` with the same stops along the axis from `center` at `angle` to the paint's bounding-box edge.” A ray from `center` at `angle` intersects a bounding box at **one** point only if `center` is inside the box; if `center` is outside there may be two intersections or none. “Bounding-box edge” does not name which edge. Stop mapping from a 360° conic onto a 1D axis is lossy; the spec does not say whether offsets are used as-is (wrong) or the 0°–180° half is taken.
- **Why it matters for 4d:** This is the **only** appearance fallback 4d must implement, and trace 10 requires a documented linear gradient. Without a unique segment, the warning can fire and the pixels still differ.
### 14. Fog blends “resolved fill, stroke, glow, and shadow colors” — gradients and alpha unspecified
- **Severity:** Major
- **Location:** §17.6 *Depth fog*
- **What's wrong:** Fog fraction is exact. What is “blended toward `color`”? For a gradient, every stop? The rasterized samples? Glow/shadow have their own colors; is the fog color’s alpha used? Premultiplied? `density * clamp(...)` as a lerp in sRGB or linear? Particle `color` ramps already interpolate sRGB; fog does not say. Objects with `fill: null` still have glow/shadow.
- **Why it matters for 4d:** Trace 7 asks for blended colors at near/mid/far. Without a color space and a rule for paints, the trace has no oracle.
### 15. Stroke-only primitives vs `point` fill; `style.fill` on `line` vs table
- **Severity:** Minor
- **Location:** §17.9 `line` “`style.fill` on a `line` is `ERR_UNKNOWN_FIELD`” vs §17.12 *Stroke-only primitives* (`line`, `polyline`, `arc`, `bezier` — **not** `point`); `point` is “a filled dot”
- **What's wrong:** `point` uses fill, not stroke, but is not listed as fill-only (`style.stroke` on a point?). `polyline` is stroke-only in §17.9 notes but the stroke-only ERR list in §17.12 omits nothing it listed — OK — yet `path`/`spline` can be open and still accept fill. Open path with fill: legal? Canvas fills open subpaths by closing them; the spec is silent.
- **Why it matters for 4d:** Open-path fill is a classic renderer fork.
### 16. Arc sweep, direction, and “magnitude exceeds 360”
- **Severity:** Major
- **Location:** §17.9 `arc` / `ring`; §17.2 angles
- **What's wrong:** `startAngle`, `endAngle`, `direction` `clockwise` | `counter-clockwise`. Is the sweep the directed difference following `direction`, or the linear difference `end - start` with `direction` flipping the canvas arc flag? A sweep of 0: nothing, or a full ring (ring says “angles default to a full ring” — default values never given numerically)? `ERR_OUT_OF_BOUNDS` when magnitude exceeds 360: is that `|end-start|` or the directed sweep after wrapping? `end = start + 360` with clockwise: full circle or error?
- **Why it matters for 4d:** Arc/ring are in Primitive Set 0.1; 4d must emit a unique path.
### 17. Path `arc` command vs SVG sweep/largeArc in y-down space
- **Severity:** Minor
- **Location:** §17.13 path `op: arc`
- **What's wrong:** Endpoint-parameterized elliptical arc with `largeArc` and `sweep` booleans. SVG’s `sweep` is “positive angle” in y-down (clockwise). The visual contract’s positive angle is “toward +y.” If those disagree (defect 4), path arcs and `arc` primitives will rotate opposite ways.
- **Why it matters for 4d:** Path vs primitive inconsistency will show up in Exhibit geometry immediately.
### 18. Spline Catmull-Rom: phantom points, closed form, and tension mapping
- **Severity:** Major
- **Location:** §17.13 *Spline*
- **What's wrong:** Open spline: “first and last points are duplicated as the phantom endpoints.” That is one CR parameterization (uniform, centripetal?). `tension` `0` to `1` default `0.5` is not mapped to a formula (Kochanek–Bartels? `tau = 1 - tension`?). Closed spline: no phantoms specified; standard is wrap indices. `bezier` mode `count = 3n+1` is for **open** cubics; a `closed` bezier spline with that count cannot close smoothly without extra points — interaction of `closed` + `bezier` is undefined.
- **Why it matters for 4d:** Organic geometry and morph (equal point count) depend on a unique curve. 4d will pick a CR variant; 4e morph interpolation will bake it in.
### 19. Text: `maxWidth` “condensed horizontally” without a scale rule; missing `font` on the 17.9 geometry row
- **Severity:** Minor
- **Location:** §17.13 text; §17.9 `text` geometry fields omit `weight`, `italic`, `letterSpacing` that §17.13 adds
- **What's wrong:** Condensing to `maxWidth` — uniform scale of glyphs? Tracking only? What if the run is already shorter? Overflow of 256 characters after resolution: diagnostic? Generic CSS families: metrics differ per platform, so text layout is **not** reproducible even though 17.14 claims procedural identity. That may be acceptable (like grain) but is not exempted.
- **Why it matters for 4d:** 4d will measure text. Platform font metrics will fail any pixel fixture. The spec should either exempt text metrics from reproducibility or require a measurement rule (e.g. no pixel tests on text bounds).
### 20. Graphic system has no system-level transform/position; camera and particles do
- **Severity:** Minor
- **Location:** §17.7 / §17.8 vs §18.2 `position` on particles vs §19.3 camera
- **What's wrong:** A `graphic` system’s objects sit in scene space with no system origin. Particles add a system `position` offset to the distribution. Authors cannot move a whole graphic system without grouping. Not a contradiction, but 4d must not invent a system transform for `graphic`.
- **Why it matters for 4d:** Tempting to put a system matrix in the renderer. Spec does not allow it for `graphic`.
### 21. `lifetime` on a visual object vs system lifecycle vs particle lifetime
- **Severity:** Major
- **Location:** §17.10 object `lifetime`; §17.7 / §19.2 system lifecycle; §18.2 particle `lifetime`
- **What's wrong:** Object-level `lifetime` “removed from its container” has no state machine, no `release`, no ownership, no diagnostic, and is legal on persistent graphic content. Is that a 4d concern (remove from draw list on logical clock) or 4f? Removing a group’s child mid-run changes document key order used for equal-`z` ties. Spawned systems forbid lifecycle fields on persistent systems, but object `lifetime` remains legal on persistent content — a back door that looks like lifecycle.
- **Why it matters for 4d:** The draw list is 4d’s. If objects can disappear without 19.2, 4d needs a removal path now.
### 22. Filters “after the object is drawn and before it composites into its layer”
- **Severity:** Minor
- **Location:** §17.12 *Filters* vs *Cost* (offscreen buffer)
- **What's wrong:** CSS `filter` on a raster vs filter on vector before raster is different. Hue-rotate amount in degrees — range unset (unlike 0–4 / 0–1). Blend vs filter vs opacity vs layer opacity order: object opacity multiplies inherited and layer; blend requires offscreen; filters after draw. Glow/shadow vs filter order unset. `blur` style field vs `filters` vs post-effect `blur` — three blurs.
- **Why it matters for 4d:** Offscreen pass graph is 4d. Ambiguous order → different pixels and different pass counts vs §19.5.
### 23. Clip inheritance and mask vs draw order
- **Severity:** Minor
- **Location:** §17.12 clipping / masking
- **What's wrong:** Mask child “is not drawn; its rendered alpha multiplies the alpha of the group's remaining children.” Remaining children are still depth-sorted among themselves? Mask is rasterized untransformed? With the group’s transform? Does the mask include the mask child’s own style opacity? Clip intersected with ancestor — in which space after nested transforms?
- **Why it matters for 4d:** Mask/clip are in traces 12. Need a unique raster definition.
### 24. Particle `size` ramp “multiplier on `render`'s own size” for `point` / `ellipse` / `component`
- **Severity:** Minor
- **Location:** §18.2 `size`
- **What's wrong:** `point` has `pointSize`, not `size`. `ellipse` has `radius`. `component` has no size. Multiplying “size” is undefined for those. `rotation` at creation vs `angularVelocity` vs `align` on emitters.
- **Why it matters for 4d:** Particle drawing is likely in 4d even if emission is 4e; the instance transform must be defined.
### 25. Particle / emitter motion in 3D vs 2D draw
- **Severity:** Minor
- **Location:** §18.2 integrator (`p`, `v`, `a` have z) vs §17.11 2D `M_local`
- **What's wrong:** `p.z` updates; draw uses `z` for sort/fog/perspective scale only. Fine, but `align` “rotation … to its velocity direction” — 2D `atan2(vy,vx)` or 3D? Unspecified.
- **Why it matters for 4d:** Emitter `align` is a 4d rotation if 4d draws emitted objects.
### 26. Index-driven `even` with `count === 1` and `i / (count - 1)`
- **Severity:** Minor
- **Location:** §18.3 `line` mode `even`; §18.5 `repeat.fraction` already special-cases `count === 1`
- **What's wrong:** `i / (count - 1)` is division by zero when a burst or repeater has `count: 1`. Fraction documents the fix; distribution `even` does not.
- **Why it matters for 4d:** Only if 4d implements distributions; still a landmine for 4e that the contract should close now.
### 27. Ring distribution: “not below `radius` is `ERR_INVALID_RANGE_ORDER`”
- **Severity:** Minor
- **Location:** §18.3 `ring` vs §17.9 ring primitive (`innerRadius` **must be less than** `radius`)
- **What's wrong:** “not below `radius`” reads as `innerRadius >= radius` is the error, i.e. inner must be **below** radius — OK — but the wording is easy to invert. Primitive uses strict less-than; distribution does not say whether `innerRadius === radius` is a legal zero-width ring (perimeter) or an error. Primitive would error on equality.
- **Why it matters for 4d:** Shared geometry helpers.
### 28. `grid` item count vs `repeater.count` / `columns * rows`
- **Severity:** Major
- **Location:** §18.3 `grid`; §18.5 `count`
- **What's wrong:** Grid is `columns × rows` cells. Repeater has an independent `count` up to 1024. If `count != columns * rows`, extra items wrap? Truncate? Error? Unspecified. `jitter` consumes samples even though “grid without jitter consumes no samples” — with jitter, order of x/y jitter vs depth sub-block is only “field order of its row,” and `jitter` is not ordered relative to `depth`.
- **Why it matters for 4d/4e:** Placement fixtures (trace 10 of §18.10).
### 29. Behaviors: `oscillate` formula `phase / 360` with frequency in Hz
- **Severity:** Minor
- **Location:** §18.6 `oscillate` `center + amplitude * w(frequency * t + phase / 360)`
- **What's wrong:** If `w` expects cycles, `frequency * t` is cycles and `phase/360` is cycles — OK. If `w` is `sin(2π · …)` that must be stated. `square`/`sawtooth` polarity (does square start high?) unset. `orbit` does not say whether it **sets** position or **adds** to it; composition says accumulate on position, which implies orbit is an offset — then `center` is absolute or relative?
- **Why it matters for 4d:** 4d may stub behaviors; if it draws static poses only, less urgent. Still, `orbit` + `position` composition must be known before any motion lands.
### 30. `follow-path` `path` as sibling key vs component encapsulation
- **Severity:** Minor
- **Location:** §18.6 `follow-path`; §18.1 encapsulation
- **What's wrong:** Sibling key inside a component is OK; from outside, `ERR_INVALID_REFERENCE`. Cross-system path follow: not mentioned. `path` as inline `commands` on a behavior duplicates 17.13 without vertex limits.
- **Why it matters for 4d:** Low until 4e.
### 31. Morph “equal type, point count, and spline mode” vs rectangles / text / groups
- **Severity:** Minor
- **Location:** §18.6 *Morph compatibility* vs locked decision
- **What's wrong:** Locked decision matches the spline rule. Morph of two `rectangle`s (no points) — same type, point count 0=0, no spline mode: legal lerp of `size`? Morph of `group`s? Morph of `text`? “Point count” for primitives without points is undefined. `path` command morph: equal command count or equal endpoint count?
- **Why it matters for 4d:** Morph is 4e, but 4d may store geometry in a form that cannot lerp.
### 32. Noise: 12 edge-midpoint gradients of a cube, 256 permutation, Fisher–Yates
- **Severity:** Major (for 4e, but the algorithm is in-scope for this read)
- **Location:** §18.7 *Coherent noise is normative*
- **What's wrong:** Classic Perlin uses 12 **edge** vectors of a cube (the 12 midpoints of cube edges, yes). Permutation of 256 shuffled with one sample **per entry** — Fisher–Yates on 256 entries needs 256 samples if specified that way, but standard FY uses `i` from 255..1 with `j = floor(u * (i+1))`. The spec does not give the exact FY index formula or how a stream sample in `[0,1)` becomes `j`. Duplicate permutation (Perlin often repeats 0..255 twice to 512) unset. `curl` of a **scalar** 3D noise is zero if you take `∇ × (N, N, N)` or needs three offset noise fields; “curl of the noise potential” with one scalar is not a unique vector. This is the hardest reproducibility trap in §18.
- **Why it matters for 4d:** If 4d does not implement fields, still: do not invent a noise helper in 4d that 4e must match. Spec should give a tabulated sample (trace 15 of §18.10 promises “documented sample values” that are not in the spec).
- **Gap:** Trace 15 requires documented sample values; **the spec contains none**. That is a missing fixture, not just an algorithm gap.
### 33. `visuals.fields` vs §17.3 — fields are listed; OK. Field IDs vs system `fields` array order
- **Severity:** Minor
- **Location:** §18.7
- **What's wrong:** Sum in system `fields` array order; addition commutative. Fine. `enabled` ValueSpec on a field — once at instantiation (17.14), so cannot be automated. Strength of a field is not in the automatable registry (locked). OK.
### 34. Trail `interval` default “one logical tick” — tick length is a runtime, not an exhibit constant
- **Severity:** Major
- **Location:** §18.8 `interval` default; §9.1 logical tick
- **What's wrong:** History sampled every tick by default means trail **shape depends on tick rate**, contradicting “a trail has the same shape at any **render** rate” (frame rate, not tick rate). Trace 17 of §18.10 only varies frame rate. If tick length differs across runtimes, trails diverge. Default should be a DurationSpec literal (e.g. the 9.1 tick) named explicitly.
- **Why it matters for 4d:** If 4d allocates trail buffers, stride is unknown.
### 35. Links draw “before their system's items, at the system's own depth position”
- **Severity:** Minor
- **Location:** §18.8 vs §18.2 lowest-`z` particle as system sort key
- **What's wrong:** Links at system depth vs particles sorted among themselves: links can draw in front of far particles of the same system. Maybe intended. `index` rule with `stride` on a changing particle pool — emitters cannot have links; particles can, and creation ordinals change under eviction. After oldest-first eviction, indices are not stable. Unspecified.
- **Why it matters for 4d:** Draw order inside a system.
### 36. Automatable registry vs §8.1: emitter `rate` automatable but not bindable — registry allows it; graphic per-object properties automatable from **system** scope
- **Severity:** Major
- **Location:** §19.1 registry “for a `graphic` system, any numeric `transform`, `style`, or geometry property of an object in its `content` tree”
- **What's wrong:** Locked decision: automatable registry is **broader** than §8.1 — confirmed. But §17.14 said visual object fields resolve **once** and are constant for the object’s lifetime, with time variation from behaviors, automation, and external control. Automation writing `content.band.style.opacity` is therefore an exception to “constant for lifetime.” 4d must keep those fields **mutable** even though 17.14 sounds like they are baked. Also: `style.fill` as color is non-numeric and excluded; `strokeWidth` is included. `visible` on an **object** is boolean — not automatable; system `visible` is bindable only. Object `visible` has **no** writer except a once-resolved ValueSpec — same trap as layer `visible`.
- **Why it matters for 4d:** 4d must not intern style as immutable GPU constants. Per-object opacity must remain a frame-time input for 4f automation.
### 37. Cross-scope `target` strings: relative paths vs `camera.zoom` vs `visuals.camera.zoom`
- **Severity:** Major
- **Location:** §19.1 example `"target": "camera.zoom"`; §8.1 rows `visuals.camera.<field>`; §1.3 prefixes
- **What's wrong:** Example targets are scope-relative (`camera.zoom`). §8.1 uses absolute `visuals.camera.zoom`. Bindings presumably need the absolute form. Automation in system scope “addressed relative to the system” — is `rate` or `content.hull.transform.rotation` the form? No ABNF. `effects[<index>]` vs `effects.0` — §1.3 says indexed form only.
- **Why it matters for 4d:** Path parser is shared. Wrong grammar → every 4f track fails.
### 38. `loop` on visual tracks vs “audio track shape verbatim”
- **Severity:** Minor
- **Location:** §19.1 vs locked decision
- **What's wrong:** Locked: audio shape **plus** `loop`. Text agrees. Ping-pong count = complete round trip: agrees. `infinite` loop on spawned system with finite lifetime is legal — OK. `infinite` “legal only on a persistent scope” in one sentence, then immediately allowed on spawned with finite lifetime — **contradiction in two consecutive sentences**.
- **Why it matters for 4d:** Validation of `loop.count`.
### 39. `ERR_AUTOMATION_CONFLICT` for behavior + track on the same **object channel**
- **Severity:** Minor
- **Location:** §19.1 vs §18.6 composition
- **What's wrong:** Locked decision matches. Unclear whether `oscillate` on `transform.scale.x` conflicts with a track on `transform.scale` (vector) or only `.x`. Unclear whether a system-level track on emitter `position.x` conflicts with a `drift` on each emitted item (different objects).
- **Why it matters for 4d:** Conflict detection may live next to the property store 4d creates.
### 40. Lifecycle: `CREATED -> ACTIVE | FINISHED | FAILED` — when FINISHED from CREATED?
- **Severity:** Minor
- **Location:** §19.2 state table
- **What's wrong:** No `SCHEDULED` (locked, OK). `CREATED -> FINISHED` is allowed but no cause (zero-lifetime spawn before first draw?). `release` default 0ms still one tick in `RELEASING` (locked, OK). Spawn at ceiling refused not evicted (locked, OK). `WARN_VISUAL_CEILING` once naming template — vs §19.5 “once per ceiling per second.” A refused spawn could warn every spawn or once per second; 19.2 says once (per event?), 19.5 says once per ceiling per second.
- **Why it matters for 4d:** 4d may not spawn yet, but the warning rate is a shared diagnostic policy.
### 41. `cancelWithScenario: false` without persistent ownership is `ERR_UNSUPPORTED_TARGET`
- **Severity:** Minor
- **Location:** §19.2
- **What's wrong:** That is a schema/semantic conflict, not an unsupported **target**. `ERR_SCHEMA_VALIDATION` or `ERR_UNKNOWN_FIELD` would fit; `ERR_UNSUPPORTED_TARGET` is the 8.1 capability miss. Wrong code will be wired into tests.
- **Why it matters for 4d:** Low; 4f traces.
### 42. Camera matrix `R(-rotation)` vs object `R(rotation)`
- **Severity:** Major
- **Location:** §19.3 `V = T(c) × S(zoom) × R(-rotation) × T(-c) × T(-p·Δ)`
- **What's wrong:** Negating rotation is correct for a view matrix **if** object rotation uses the same convention. Combined with defect 4, the extra minus may double-correct. `T(-p * (x - c_x), …)` — camera `x,y` are scene-center defaults; `c` is **display** projection center after fit. Subtracting a scene coordinate from a display coordinate is a **unit/space mix** unless fit has already mapped them into one space. The formula never says whether `x,y` are converted to display pixels first.
- **Why it matters for 4d:** This is the **normative camera matrix** 4d must implement. As written it mixes scene units and display units. Trace 13 of §19.7 cannot have a unique documented display point.
### 43. Parallax and perspective both claimed to affect “the layer”
- **Severity:** Minor
- **Location:** §17.6 parallax on camera translation; §19.3 perspective **per object** after `V`
- **What's wrong:** Locked: parallax multiplies translation only — text agrees. Perspective is per-object `z`, so two objects on one layer at different `z` scale differently while sharing one `V`. A layer `parallax` does not change object `z`. OK if intended; worth stating that parallax is **not** a function of object `z` (only layer field).
- **Why it matters for 4d:** Do not implement depth-based parallax.
### 44. Post-effects: `scanlines.spacing` in **device pixels** vs everything else in scene units
- **Severity:** Major
- **Location:** §19.4 `scanlines.spacing`, `grain.scale` in device pixels; `blur.radius` scene units scaled by zoom
- **What's wrong:** Reproducibility: scanline period changes with DPR and window size. Grain is exempt; scanlines are **not**. Two machines at different DPR get different line density. `speed` of scanlines has range “—” (unlimited?). `color-adjust.saturation` vs filter type `saturate` naming. Disabled effect “costs nothing that frame” vs pass budget counted how when `enabled` is a ValueSpec resolved once — cannot toggle unless automated; `enabled` is boolean so **not** automatable. So `enabled` is another once-only ValueSpec that cannot be bound (not in §8.1 except `.<param>` numeric). Cannot turn effects off at runtime despite the field.
- **Why it matters for 4d:** Effect chain is after the renderer; 4d may still allocate the composited frame. Scanlines/grain in device pixels must be a conscious 4d/4f choice or exhibits are resolution-dependent without a warning.
### 45. `WARN_VISUAL_APPROXIMATION` used for three different events
- **Severity:** Minor
- **Location:** §17.1 / §17.12 conic fallback; §19.4 reduced-res blur/bloom and skipped unavailable effect; §19.5 shedding offscreen buffers also raises `WARN_VISUAL_APPROXIMATION` (not `WARN_VISUAL_CEILING`)
- **What's wrong:** Buffer shedding is a ceiling event but uses the approximation warning. Authors cannot distinguish “GPU cannot conic” from “too many glow buffers.” Locked decision said reduced-res blur/bloom raises approximation once per effect instance — text agrees. Buffer shed using the same code is extra.
- **Why it matters for 4d:** 4d implements glow/blur buffers and must pick a diagnostic.
### 46. §19.5 offscreen-buffer shed “farthest-`z` first” vs draw order farthest first
- **Severity:** Minor
- **Location:** §19.5 compositing buffers
- **What's wrong:** Shed farthest-`z` first means distant fancy objects lose glow first (keep near effects). Opposite of painter priority. Fine if intended. “Draw the excess objects without their non-default blend/mask/blur/glow/filters” — dropping `mask` changes silhouette, which **is** a material substitution PRD 89 forbids, while the table claims neither kind of limit is silently transformed into something materially different.
- **Why it matters for 4d:** Pass budget enforcement.
### 47. Authoring bound vs runtime ceiling: automation records listed in **both** tables
- **Severity:** Minor
- **Location:** §19.5 aggregate table “Automation records … Authoring bound (19.1), not a shed” **inside** the runtime-ceiling table
- **What's wrong:** Mixes kinds. Spawned instances appear in both tables. Harmless but 4d/4f readers will implement a shed for automation that must not exist.
- **Why it matters for 4d:** Don’t shed tracks.
### 48. §17.1 pipeline order vs actual data flow
- **Severity:** Minor
- **Location:** §17.1 “primitives → components → procedural systems → layers → camera → post effects → display”
- **What's wrong:** Components and primitives are **inside** systems; systems **belong to** layers. Pipeline as a feed-forward list will mislead a 4d architecture (there is no “component pass” after primitives). Real order: tick systems → sort objects in layers → apply camera per layer → composite layers → effects.
- **Why it matters for 4d:** Don’t build seven sequential passes matching that list.
### 49. §17.14 “Rendering consumes no procedural stream” vs grain vs wander offsets
- **Severity:** Minor
- **Location:** §17.14; §18.6 wander consumes stream **once at instantiation**; §19.4 grain exempt
- **What's wrong:** Locked grain exemption is stated. 17.14’s “no visual analogue” of 14.6 is slightly overstated once grain exists. Not a 4d draw-loop sample bug if 4d respects 17.14.
- **Why it matters for 4d:** Frame loop must not call the PRNG.
### 50. `components` root vs `components.visual`
- **Severity:** Minor
- **Location:** §1.1 `components`; §18.1 `components.visual.<id>`
- **What's wrong:** Audio components live under the same `components` object (presumably `components.audio` from 15.x). 4d loader must not treat every `components` entry as visual. Out of scope to fully check 15.x; flag if 18.1 is the first mention of the `visual` subkey.
- **Why it matters for 4d:** Schema.
### 51. Implicit layer vs `layer` field required-by-reference
- **Severity:** Minor
- **Location:** §17.5 “When `visuals.layers` is absent, one implicit layer … and a system's `layer` field is `ERR_INVALID_REFERENCE`.”
- **What's wrong:** Clear: you may not name a layer if none were declared. Default `layer` on §17.7 is “implicit layer.” Consistent. If `layers` is present, omitting `layer` on a system — §17.7 default “implicit layer” which **does not exist**. So every system **must** set `layer` whenever `layers` is declared. Never stated as required-conditional.
- **Why it matters for 4d:** Validation.
### 52. `cover`/`contain` and camera projection center
- **Severity:** Major (related to 11 and 42)
- **Location:** §19.3 “projection center is the center of the **display** rectangle after `fit`”
- **What's wrong:** Under `contain`, letterbox is background; projection center is display center, which may not be scene center on screen if letterboxing is not centered (defect 11). Under `cover`, cropped scene center may not match display center. Perspective about display center while scene origin is top-left is a specific choice that should have a numeric example.
- **Why it matters for 4d:** Same as camera matrix.
### 53. Color alpha “multiplies the applicable opacity” vs fog vs layer
- **Severity:** Minor
- **Location:** §17.2 color; §17.12 opacity
- **What's wrong:** `#rrggbbaa` × object opacity × group opacity × layer opacity × fog. Premultiply when, relative to blend `add`? Unspecified. Canvas 2D `globalAlpha` vs fillStyle alpha differ.
- **Why it matters for 4d:** Every translucent primitive.
### 54. `ERR_INVALID_PRIMITIVE_TYPE` for fifteen types vs “Primitive Set 0.1 is fourteen”
- **Severity:** Minor
- **Location:** §17.9 vs §18.1 vs §17.15 table
- **What's wrong:** Locked: fourteen closed; `component` is fifteenth object type. Text agrees. Diagnostic table §17.15 still says “not a member of Visual Primitive Set 0.1” without mentioning `component`. After 18.1, unknown type is “outside the fifteen.” 4d if implemented before 4e might reject `component` correctly as invalid primitive; 4e then accepts it. Trace 2 of 17.16 says unknown type is `ERR_INVALID_PRIMITIVE_TYPE` — `component` in a 4d-only renderer would fail that if exhibits use it.
- **Why it matters for 4d:** 4d scope: parse `component` as a stub group or reject? Spec does not give a 4d subset.
### 55. Slice 4d is not given a subset of §17–19
- **Severity:** Blocker
- **Location:** §19 opening “Slice 4d begins the renderer”; §17.16 traces 1–14 as 4d acceptance; traces 15–16 are 4h; §18.10 traces belong to 4e; §19.7 to 4f
- **What's wrong:** 4d acceptance traces include perspective, fog, masks, conic fallback, bindings (trace 14), and “section 8.1 table unchanged.” They do **not** include camera matrix §19.3, yet §17.6 perspective is defined in terms of 19.3. 4d cannot implement trace 6 without 19.3, and 19.3 is a 4c/4f section. Circular implementation dependency.
- **Why it matters for 4d:** The renderer core **must** implement a camera to pass 17.16.6, but camera is specified in 19.3 and tested in 19.7.13–14. 4d needs an explicit borrow of 19.3 or a reduced orthographic-only 4d gate.
---
## Standing decisions — compliance check
| Decision | Spec actually says it? | Contradicted elsewhere? |
| --- | --- | --- |
| Angles degrees, 0 along +x, positive toward +y | Yes, §17.2 | Yes — y-down top-left (§17.4) makes “toward +y” clockwise; not acknowledged |
| Increasing z farther; sort greater z first; ties document key order | Yes, §17.6 | Partially — particle systems are atomic (§18.2); emitters/repeaters unspecified |
| Perspective `focalLength / (focalLength + z)`; ortho: z for sort/parallax/fog only | Yes, §17.6, §19.3 | Formula vs `V` space mix (§19.3) |
| Visual objects keyed, no `id` | Yes, §17.8 | No |
| Transform order `T(pos+translate) × T(origin) × R × K × S × T(−origin)`; `M_parent × M_local` | Yes, §17.11 | `z` not in `M_local`; camera `V` separate |
| ValueSpec once at instantiation; render consumes no stream | Yes, §17.14 | Automation mutates later (§19.1); grain exempt (§19.4) |
| `layer` system property not object | Yes, §17.10 | No |
| Depth fog per object, exact, no diagnostic | Yes, §17.6 | Color-space unspecified |
| Conic only appearance fallback, warn once per paint | Yes, §17.12 | Fallback segment not unique |
| `font` sans-serif/serif/monospace | Yes, §17.13 | No |
| Live numeric text deferred | Yes, §17.13 | No |
| Fourteen primitives closed; `component` fifteenth | Yes, §17.9, §18.1 | §17.15 cause text stale |
| Component parameters number/boolean/string/color | Yes, §18.1 | No |
| `inputs.*` / `repeat.*` construct-scoped | Yes, §18.1, §18.5 | No |
| Expansion path is §9.3 key | Yes, §18.1 | No |
| Integrator `v ← (v+a·dt)·(1−drag)^dt`; `p ← p+v·dt` | Yes, §18.2 | No |
| Life ramps endpoints once | Yes, §18.2 | No |
| Emission accumulator; `floor(rate·t)`; no reset | Yes, §18.4 | Particles say “Emission timing is 18.4” — OK |
| `ERR_UNBOUNDED_EMISSION`; `count` alone legal | Yes, §18.2, §18.4 | No |
| Pool oldest-first; aggregate §19.5 | Yes | No |
| Index-driven + continuous rate = `ERR_INVALID_DISTRIBUTION` | Yes, §18.3 | No |
| Sample order x,y,z; angle before radius | Yes, §18.3 | Polar `ring` field order in table is radius before angles — **sample** order vs **field** order clash |
| Coherent noise 3D, 12 gradients, 256 perm, quintic, curl default | Yes, §18.7 | Curl of scalar potential not unique; no sample table |
| Ribbons = `trail.mode`; `links` on emitter = `ERR_UNKNOWN_FIELD` | Yes, §18.8 | No |
| Morph equal type, points, spline mode | Yes, §18.6 | Non-point primitives underspecified |
| Exactly four §8.1 visual rows; no layer.visible | Yes, §19.1 | §17.16.14 and §17.3 contradict |
| Two automation scopes; cross-scope `ERR_INVALID_REFERENCE` | Yes, §19.1 | `visuals.automation` vs unknown-field |
| Track shape = §16.1 plus `loop`; ping-pong count = round trip | Yes | `infinite` only-persistent vs spawned-legal clash |
| Automatable ⊃ bindable | Yes | Repeater `step` does not exist |
| Behavior + track = `ERR_AUTOMATION_CONFLICT`; else pipeline then behaviors | Yes, §19.1 | Channel granularity fuzzy |
| Lifecycle enum; fields on persistent = `ERR_UNKNOWN_FIELD`; spawn key `id#ordinal` | Yes, §19.2 | No |
| Five states + FAILED; no SCHEDULED; release 0ms still one RELEASING tick | Yes | CREATED→FINISHED unexplained |
| Spawn at ceiling refused, not evicted | Yes | Warn-once vs once-per-second |
| Camera matrix normative; parallax translation only; projection authored once | Yes, §19.3 | Matrix mixes scene and display spaces |
| Seven effects, array order; blur/bloom two passes; approx warn; skip not substitute | Yes, §19.4 | No |
| Grain exempt, no stream | Yes | Scanlines also device-pixel but not exempt |
| §19.5 centralized; values provisional | Yes | Automation row in runtime table |
**Sample-order clash (extra):** §18.3 “field order of its row” for `ring` is `center, radius, innerRadius, startAngle, endAngle, direction, mode`, but “polar forms angle before radius.” A fixture cannot satisfy both. **Severity: Major** for 4e; 4d should not implement distributions until this is fixed.
---
## What was checked and found sound
- Identifier regex and keyed `content`/`children` with no `id` field (§1.3, §17.8).
- Strict unknown-field policy as a general rule (the `automation` hole is the exception, not the rule).
- Three coordinate spaces named; `width`/`height` only for `virtual`; `viewport` rejects non-stretch `fit`.
- Layer cap 16; group nest 8; polygon/polyline 512; spline 256; path commands 512; gradient stops 16; filters 4.
- Transform composition string matches the locked order; hierarchy `M_parent × M_local` and `z_parent + z_local`.
- Negative scale = mirror; scale 0 legal and draws nothing; skew magnitude ≥ 90 is `ERR_OUT_OF_BOUNDS`.
- Safe blend set closed; stroke-only declared-fill vs inherited-fill distinction.
- Path must start with `move`; zero-radius path arc is `ERR_OUT_OF_BOUNDS` not a line; bezier spline `3n+1`.
- Fonts limited to three generic families; live numeric text explicitly deferred with rationale.
- Component parameter types (four scalars) vs audio number-only; `inputs.*` not a §8.1 row.
- Particle integrator formula and `dt`-correct drag; life-ramp-without-lifetime = `ERR_SCHEMA_VALIDATION`; exponential-through-zero reuses `WARN_AUTOMATION_FALLBACK`.
- Unbounded emission diagnostic; initial `count` without lifetime legal; oldest-first eviction.
- `links` on `emitter` = `ERR_UNKNOWN_FIELD`; ribbons as trail mode.
- Four §8.1 rows only; `visuals.layers.<id>.visible` absent on purpose; system `visible` bindable not automatable.
- Spawn refuse vs voice evict, with rationale; no `SCHEDULED` state; `0ms` release still enters `RELEASING`.
- Projection not automatable; grain stream exemption and rationale.
- Centralized ceiling table present; values labeled provisional pending 4h; runtimes may lower never raise.
- Diagnostic reuse policy (`ERR_COMPONENT_RECURSION`, `ERR_AUTOMATION_CONFLICT` widened, etc.) is consistent in intent.
---
## 4d-specific recommendation (not a spec change, a sequencing note)
Do not start the renderer against §17.16 as a closed gate. Minimum contract fixes before 4d code:
1. Add `automation` to the §17.3 `visuals` key list.
2. Rewrite §17.16.14 to match §19.1’s four rows.
3. State Y-down + rotation sign in one sentence.
4. State box/ellipse local origin.
5. Give one numeric camera example (scene point → display point) that uses a single space for `V`.
6. Borrow §19.3 into the 4d scope list, or drop perspective from 4d traces.
7. Delete or define repeater `step`.
8. Fix “layer nesting” in trace 9 to group nesting / layer count.
Until (3)–(5) land, two renderer cores can both “conform” and disagree on every rotated, zoomed, or letterboxed frame.