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
47 KiB
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
visualsblock vs §19.1 Two declaration scopes - What's wrong: §17.3 enumerates
scene,layers,systems,fields,camera,effectsand then: “any other property ofvisualsisERR_UNKNOWN_FIELD.” §19.1 places anautomationarray onvisualsitself (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, oroverrideaddressing any visual property isERR_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 itsstepblock”) vs §18.5 field table - What's wrong: §18.5 has
repeat,count,distribution,position,inputs,behaviors,fields,links. There is nostepfield, nostepblock, and no definition of what “numeric fields of itsstepblock” are. Repeaters also have norate(explicitlyERR_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
stepor delete the row.
4. Y-down scene vs “positive angles toward +y”
- Severity: Major
- Location: §17.2 Canonical units (“
0points along+x; positive angles turn toward+y”) vs §17.4 (xandyrun0to1from the top-left; virtual origin at top-left) - What's wrong: Top-left origin with
yincreasing 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+yin local object space is down. - Why it matters for 4d: Transform order
R × K × Sis useless if the sign ofRis ambiguous. Trace 3 of §17.16 (“a known local point maps to the documented device point”) cannot be written until this is fixed. Canvas 2Drotateis clockwise for y-down; a naivecos/sintoward +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.11origin“in the object's own local space”; §17.13 textalign“relative to the object's origin” - What's wrong: For
rectangle/rounded-rectangle, ispositionthe top-left ofsize, the center, or something else? Forellipse/ring/arc, ispositionthe center (implied byradiusbut never stated)? Forpoint, is the dot centered on the origin? Text hasalign/baseline, which implies the origin is an anchor, but boxes have no equivalent. Defaultoriginis{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/coverfixture 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 afterV”) - 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, afterM_local, after hierarchy, or afterV. §19.3 says afterV, 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.zused in the factor is never named as effectivez(z_parent + z_localfrom §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, oneV, 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
zcomponent oftranslateadds to the object'szand … is not part ofM_local.” - What's wrong:
M_localis therefore a 2D affine matrix. Perspective usesz. Hierarchy addsz. Particle integration movespin 3D. The spec never says whether parentMrotates/scales childz(it cannot, ifMis 2D) or only adds it. A child with localzunder 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-
zparticle” vs §17.8 document key order ofcontent - 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 withz=0group and a child atz=100vs a sibling system atz=50has 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
visibleValueSpec<boolean>, defaulttrue; §19.1 “visuals.layers.<id>.visibleis 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
visibleresolves once, a ValueSpec is pointless exceptrandom/chooseat activation. If it is meant to change, there is no writer: not automation (boolean), not binding (absent from §8.1), notset/override. The field is therefore a once-at-import flag dressed as a ValueSpec. Meanwhile systemvisibleis an §8.1 binding target and is explicitly not automatable. The twovisibleflags 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
visiblewould 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: “
9layer nesting levels … are eachERR_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:
containletterboxes “the remainder withbackground” 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).viewportignoresfitexcept that non-stretchis an error — so the only legalfitunderviewportisstretch, 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 defaultx,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-gradientwith the same stops along the axis fromcenteratangleto the paint's bounding-box edge.” A ray fromcenteratangleintersects a bounding box at one point only ifcenteris inside the box; ifcenteris 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? Particlecolorramps already interpolate sRGB; fog does not say. Objects withfill: nullstill 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.fillon alineisERR_UNKNOWN_FIELD” vs §17.12 Stroke-only primitives (line,polyline,arc,bezier— notpoint);pointis “a filled dot” - What's wrong:
pointuses fill, not stroke, but is not listed as fill-only (style.strokeon a point?).polylineis stroke-only in §17.9 notes but the stroke-only ERR list in §17.12 omits nothing it listed — OK — yetpath/splinecan 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,directionclockwise|counter-clockwise. Is the sweep the directed difference followingdirection, or the linear differenceend - startwithdirectionflipping 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_BOUNDSwhen magnitude exceeds 360: is that|end-start|or the directed sweep after wrapping?end = start + 360with 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
largeArcandsweepbooleans. SVG’ssweepis “positive angle” in y-down (clockwise). The visual contract’s positive angle is “toward +y.” If those disagree (defect 4), path arcs andarcprimitives 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?).
tension0to1default0.5is not mapped to a formula (Kochanek–Bartels?tau = 1 - tension?). Closed spline: no phantoms specified; standard is wrap indices.beziermodecount = 3n+1is for open cubics; aclosedbezier spline with that count cannot close smoothly without extra points — interaction ofclosed+bezieris 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
textgeometry fields omitweight,italic,letterSpacingthat §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
positionon particles vs §19.3 camera - What's wrong: A
graphicsystem’s objects sit in scene space with no system origin. Particles add a systempositionoffset to the distribution. Authors cannot move a whole graphic system without grouping. Not a contradiction, but 4d must not invent a system transform forgraphic. - 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 particlelifetime - What's wrong: Object-level
lifetime“removed from its container” has no state machine, norelease, 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-zties. Spawned systems forbid lifecycle fields on persistent systems, but objectlifetimeremains 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
filteron 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.blurstyle field vsfiltersvs post-effectblur— 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:
pointhaspointSize, notsize.ellipsehasradius.componenthas no size. Multiplying “size” is undefined for those.rotationat creation vsangularVelocityvsalignon 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,ahave z) vs §17.11 2DM_local - What's wrong:
p.zupdates; draw useszfor sort/fog/perspective scale only. Fine, butalign“rotation … to its velocity direction” — 2Datan2(vy,vx)or 3D? Unspecified. - Why it matters for 4d: Emitter
alignis a 4d rotation if 4d draws emitted objects.
26. Index-driven even with count === 1 and i / (count - 1)
- Severity: Minor
- Location: §18.3
linemodeeven; §18.5repeat.fractionalready special-casescount === 1 - What's wrong:
i / (count - 1)is division by zero when a burst or repeater hascount: 1. Fraction documents the fix; distributionevendoes 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
ringvs §17.9 ring primitive (innerRadiusmust be less thanradius) - What's wrong: “not below
radius” reads asinnerRadius >= radiusis 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 whetherinnerRadius === radiusis 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.5count - What's wrong: Grid is
columns × rowscells. Repeater has an independentcountup to 1024. Ifcount != columns * rows, extra items wrap? Truncate? Error? Unspecified.jitterconsumes 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,” andjitteris not ordered relative todepth. - 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
oscillatecenter + amplitude * w(frequency * t + phase / 360) - What's wrong: If
wexpects cycles,frequency * tis cycles andphase/360is cycles — OK. Ifwissin(2π · …)that must be stated.square/sawtoothpolarity (does square start high?) unset.orbitdoes not say whether it sets position or adds to it; composition says accumulate on position, which implies orbit is an offset — thencenteris absolute or relative? - Why it matters for 4d: 4d may stub behaviors; if it draws static poses only, less urgent. Still,
orbit+positioncomposition 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.pathas inlinecommandson 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
rectangles (no points) — same type, point count 0=0, no spline mode: legal lerp ofsize? Morph ofgroups? Morph oftext? “Point count” for primitives without points is undefined.pathcommand 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
ifrom 255..1 withj = floor(u * (i+1)). The spec does not give the exact FY index formula or how a stream sample in[0,1)becomesj. Duplicate permutation (Perlin often repeats 0..255 twice to 512) unset.curlof 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
fieldsarray order; addition commutative. Fine.enabledValueSpec 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
intervaldefault; §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-
zparticle 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.
indexrule withstrideon 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
graphicsystem, any numerictransform,style, or geometry property of an object in itscontenttree” - 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.opacityis therefore an exception to “constant for lifetime.” 4d must keep those fields mutable even though 17.14 sounds like they are baked. Also:style.fillas color is non-numeric and excluded;strokeWidthis included.visibleon an object is boolean — not automatable; systemvisibleis bindable only. Objectvisiblehas no writer except a once-resolved ValueSpec — same trap as layervisible. - 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 rowsvisuals.camera.<field>; §1.3 prefixes - What's wrong: Example targets are scope-relative (
camera.zoom). §8.1 uses absolutevisuals.camera.zoom. Bindings presumably need the absolute form. Automation in system scope “addressed relative to the system” — israteorcontent.hull.transform.rotationthe form? No ABNF.effects[<index>]vseffects.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.infiniteloop 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
oscillateontransform.scale.xconflicts with a track ontransform.scale(vector) or only.x. Unclear whether a system-level track on emitterposition.xconflicts with adrifton 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 -> FINISHEDis allowed but no cause (zero-lifetime spawn before first draw?).releasedefault 0ms still one tick inRELEASING(locked, OK). Spawn at ceiling refused not evicted (locked, OK).WARN_VISUAL_CEILINGonce 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_VALIDATIONorERR_UNKNOWN_FIELDwould fit;ERR_UNSUPPORTED_TARGETis 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), …)— camerax,yare scene-center defaults;cis 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 whetherx,yare 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 differentzscale differently while sharing oneV. A layerparallaxdoes not change objectz. OK if intended; worth stating that parallax is not a function of objectz(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.scalein device pixels;blur.radiusscene 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.
speedof scanlines has range “—” (unlimited?).color-adjust.saturationvs filter typesaturatenaming. Disabled effect “costs nothing that frame” vs pass budget counted how whenenabledis a ValueSpec resolved once — cannot toggle unless automated;enabledis boolean so not automatable. Soenabledis 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(notWARN_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-
zfirst 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” — droppingmaskchanges 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.1components.visual.<id> - What's wrong: Audio components live under the same
componentsobject (presumablycomponents.audiofrom 15.x). 4d loader must not treat everycomponentsentry as visual. Out of scope to fully check 15.x; flag if 18.1 is the first mention of thevisualsubkey. - Why it matters for 4d: Schema.
51. Implicit layer vs layer field required-by-reference
- Severity: Minor
- Location: §17.5 “When
visuals.layersis absent, one implicit layer … and a system'slayerfield isERR_INVALID_REFERENCE.” - What's wrong: Clear: you may not name a layer if none were declared. Default
layeron §17.7 is “implicit layer.” Consistent. Iflayersis present, omittinglayeron a system — §17.7 default “implicit layer” which does not exist. So every system must setlayerwheneverlayersis 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). Undercover, 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 blendadd? Unspecified. Canvas 2DglobalAlphavs 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;
componentis fifteenth object type. Text agrees. Diagnostic table §17.15 still says “not a member of Visual Primitive Set 0.1” without mentioningcomponent. After 18.1, unknown type is “outside the fifteen.” 4d if implemented before 4e might rejectcomponentcorrectly as invalid primitive; 4e then accepts it. Trace 2 of 17.16 says unknown type isERR_INVALID_PRIMITIVE_TYPE—componentin a 4d-only renderer would fail that if exhibits use it. - Why it matters for 4d: 4d scope: parse
componentas 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/childrenwith noidfield (§1.3, §17.8). - Strict unknown-field policy as a general rule (the
automationhole is the exception, not the rule). - Three coordinate spaces named;
width/heightonly forvirtual;viewportrejects non-stretchfit. - 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_localandz_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 isERR_OUT_OF_BOUNDSnot a line; bezier spline3n+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 reusesWARN_AUTOMATION_FALLBACK. - Unbounded emission diagnostic; initial
countwithout lifetime legal; oldest-first eviction. linksonemitter=ERR_UNKNOWN_FIELD; ribbons as trail mode.- Four §8.1 rows only;
visuals.layers.<id>.visibleabsent on purpose; systemvisiblebindable not automatable. - Spawn refuse vs voice evict, with rationale; no
SCHEDULEDstate;0msrelease still entersRELEASING. - 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_CONFLICTwidened, 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:
- Add
automationto the §17.3visualskey list. - Rewrite §17.16.14 to match §19.1’s four rows.
- State Y-down + rotation sign in one sentence.
- State box/ellipse local origin.
- Give one numeric camera example (scene point → display point) that uses a single space for
V. - Borrow §19.3 into the 4d scope list, or drop perspective from 4d traces.
- Delete or define repeater
step. - 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.