Files
XZBT/reviews/02-triage.md
T
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

28 KiB
Raw Blame History

XZBT 0.1 Phase 4d4g — code review triage

Inputs:

This follows 01-triage.md (visual contract §§1719 specification triage) and 00-triage.md (§§1416 specification triage). Those triages addressed defects in the format specification text; this triage addresses defects in the implementation code and its conformance to the specification, discovered by four independent reviews of the Phase 4d4g working tree at HEAD 0af58da.

All claims below were verified against the source files named, the specification (docs/XZBT_0-1_Format_Specification.md rev 0.8), and the test suites. No source files were modified. Where a reviewer's proposed fix is wrong or incomplete, the correction is noted.

Result: 20 distinct defects confirmed across 27 reported findings. No false positives. 3 findings are duplicates (merged below), and 1 finding is confirmed but overstated in severity. All four reviews found real issues; R2 and R3 are substantially stronger — each found issues missed by the other three. The overlap pattern demonstrates the value of independent multi-pass review: only 4 of 20 defects were found by more than one reviewer.


Scorecard

R1 R2 R3 SR
Findings reported 4 9 14 4
Real 4 9 14 4
Unique catches 0 4 8 0
Proposed fix wrong/incomplete 0 0 0 0
Overstated severity 0 0 1 (P2-7) 0

R2 is the strongest single pass: it is the only reviewer to identify the compositing surface gap (C1), the validator divergence (C6), the schema system-variant contradictions (C7), and the bundle self-containment regex weakness (C14). R3 is comparably strong, uniquely finding the velocity-integration gap (C3), the glow rendering defect (C10), the boolean coercion gap (C11), the face-motion smoothing error (C12), the trail fade defect (C13), the ring zero-sweep defect (C16), and several P3 issues. R1 and SR found nothing missed by R2 and R3, but independently confirmed critical issues.


Tier 1 — Production failures: crashes, silent subsystem shutdown, or spec violations at wrong lifecycle stage

C1. Production render path never creates compositing surfaces; masks crash, layer opacity silently ignored

Reported by: R2 §1. R3 P3.3 (partial). R1 did not find.

Confirmed. VisualSubsystem.render calls renderFrame without a createSurface factory. The mask branch of drawNode (visual-canvas2d.js:233) gets null from the dummy surface provider and throws TypeError. app.js:42-49 catches, logs, and calls visual.deactivate() — permanent visual shutdown on the first masked-group frame. Layer opacity/blend are only applied in the buffered composite branch (visual-canvas2d.js:236); without a surface pool, layer opacity is silently dropped — a bindable 8.1 property does nothing in the browser.

R3 P3.3 identifies the same null-surface crash but proposes only a guard/null-skip with warning and considers re-attaching visuals on a later frame. R2's fix is more complete: pass a surface factory from VisualSubsystem.render and add a render-level test exercising a masked group and a non-opaque layer through the subsystem, not just renderFrame with a manually-injected factory.

Files: src/runtime/visual-subsystem.js:61-63, src/runtime/visual-canvas2d.js:99,230-243, src/runtime/app.js:42-49.

Correction: Pass a surface factory (OffscreenCanvas or document.createElement('canvas') fallback) from VisualSubsystem.render. Add subsystem-level test for masked groups and non-opaque layers. Consider whether app.js should re-attach visuals on subsequent frames rather than permanently deactivating.


C2. System-level behaviors arrays bypass semantic validation; accepted documents fail at wrong lifecycle stage

Reported by: R2 §2. R3 P1-3 (same defect, different angle). R1 did not find.

Confirmed. visual-validation.js:332-333 checks only array length for system behaviors; validateBehaviors at :614 runs only for object hosts. Six classes of semantic error (unknown type, invalid property channel, undeclared field, point-wander on point render, follow-path conflicts, and more) import clean and then throw at activation or silently no-op at runtime. Spec §18.9/18.10 trace 12 requires these errors at import (ERR_INVALID_BEHAVIOR_TYPE, ERR_INVALID_BEHAVIOR_TARGET, ERR_INVALID_REFERENCE, ERR_SCHEMA_VALIDATION).

R2 and R3 converge on the same fix with different emphasis. R2 focuses on the system-scope call to validateBehaviors with field context; R3 adds that options.fields must be set and behavior/target incompatibility (e.g. point-wander on a non-point-list template) should be rejected before activation.

Files: src/runtime/visual-validation.js:332-333, :614, :653, :789.

Correction: Run validateBehaviors for system-level behaviors arrays at import, with options.fields set from the system context. Reject point-list/morph behaviors at system scope unless the template provides point lists. Reject unknown types, invalid references, and invalid targets with the documented diagnostic codes.


C3. Velocity-accumulating behaviors are silently inert on graphic objects and repeater copies

Reported by: R3 P1-2 only.

Confirmed. Graphic-object hosts run advanceItem with a fake {type:'repeater'} context (visual-motion.js:47); the integrator in visual-systems.js:314-325 is gated by this.type !== 'repeater'. Behaviors like attract, repel, bounce, field-follow force/velocity, and wander accumulate velocity (item.vx) but displacement is never applied. Probe: graphic point with attract {x:500,y:300}, strength:80 had vx ≈ 0.235 but remained at (0,0) after 2s of ticks. drift works on the same host, masking the gap in fixtures. Spec §18.6: "the integrator of 18.2 turns into displacement."

Files: src/runtime/visual-motion.js:47, src/runtime/visual-systems.js:314-325, src/runtime/visual-behaviors.js:307-319, 332.

Correction: Integrate vx/vy for object/repeater hosts in advanceVisualMotion, or reject velocity-accumulating behaviors on hosts without an integrator at semantic validation with the documented diagnostic code.


C4. morph corrupts point z to NaN on z-less geometry; poisons perspective projection

Reported by: R2 §3. R3 P1-1 (same defect). R1 §3 (related, lower severity assessment).

Confirmed. visual-behaviors.js:348: item.points[index].z += ((target[index].z ?? 0) - item.points[index].z) * amount — the base item.points[index].z is undefined, so undefined + … → NaN. Under perspective projection, NaN flows through primitiveSubpaths into plan coordinates; project() guards with subpath.start[2] ?? 0, which does not catch NaN (not nullish) → coordinates become NaN → object disappears. Orthographic hides this because z is unused; the current suite has no perspective-morph case.

R3 provides the most precise fix: guard both sides as (item.points[index].z ?? 0), matching point-wander's existing guard at line 235.

R1's §3 is a different but related issue: bloom/color-adjust unclamped channel math and blur radius capped at max(width,height). Not a duplicate of C4 — see C15 below.

Files: src/runtime/visual-behaviors.js:348.

Correction: item.points[index].z += ((target[index].z ?? 0) - (item.points[index].z ?? 0)) * amount;. Add a perspective-scene morph test asserting finite plan coordinates.


C5. spawn.lifetime / action lifetime: parseDuration rejects non-string values

Reported by: R1 §1. R2 §8. SR §1. All converge.

Confirmed. visual-engine.js:466-468 always calls parseDuration(sampleTree(…)), which requires typeof value === 'string' (types.js:55-56). A numeric or already-resolved lifetime throws ERR_INVALID_DURATION instead of applying the duration. The spec (§6.1, §19.2) defines spawn.lifetime and spawn.release as DurationSpec, which includes the bounded {random: …} TimeSpec form.

R1 proposes accepting already-numeric milliseconds. SR correctly identifies the design ambiguity: the Action Model (Phase 5/6) has not yet fixed action.lifetime typing, so this is a defect only for spawn.lifetime after sampleTree; action-lifetime typing needs a separate decision. R2 notes the dual-filing issue between spec and validator (validator is literal-only; spec says DurationSpec).

Files: src/runtime/visual-engine.js:466-468, src/runtime/actions.js:48-50, src/runtime/types.js:55-56, src/runtime/visual-validation.js:361-364.

Correction: Convert with parseDuration only when the value is a string; if already a finite number, treat as milliseconds. Alternatively, restrict the §19.2 table to "duration literal" — pick one and align spec, validator, and runtime. The validator's current literal-only check (visual-validation.js:361-364) is consistent with a restriction decision; the spec text needs to match whichever option is chosen. Design decision required.


Reported by: R2 §6 only.

Confirmed. tools/validate-exhibit.mjs:572 falls through to ERR_UNSUPPORTED_TARGET for every visuals.* binding target. exhibits/minimal-visual.xzbt (committed in 6587d3e) and exhibits/exhibit-d.xzbt (untracked) both FAIL the CLI validator while passing the production validator (parseAndValidateExhibit). The gc2/gc3 tests instantiate ExhibitValidator only with their own fixtures, so the divergence is uncaught.

Files: tools/validate-exhibit.mjs:572, src/runtime/validator.js.

Correction: Either delegate from tools/validate-exhibit.mjs to src/runtime/validator.js (single source of truth), or port the four §8.1 visual target families into ExhibitValidator. Add a test that runs the CLI validator over exhibits/*.xzbt and exhibits/visual-challenges/*.xzbt to keep both paths in sync.


Tier 2 — Spec violations, silent data corruption, or incorrect rendering (non-crashing)

C7. JSON Schema system variants contradict the validator and spec §§18.4, 19.2

Reported by: R2 §7 only.

Confirmed. schema/xzbt-0.1.schema.json system variants (:2878/:2920/:3081/:3229) allow top-level release, ownership, inputs, cancelWithScenario — spec §19.2 and the validator (visual-validation.js:314) make each ERR_UNKNOWN_FIELD. The emitter variant allows links (validator rejects at :325). The repeater variant allows rate/burst/limit/capacity (see C8). The runtime never executes the schema, so this ships an inaccurate contract artifact rather than breaking imports.

Files: schema/xzbt-0.1.schema.json.

Correction: Move spawn-only fields out of the variants' property lists. Drop links from the emitter variant. Align the repeater variant with the §18.5 table. Reconcile required-field declarations (see C9).


C8. Repeater silently accepts emitter-only fields (rate/burst/limit/capacity/trail)

Reported by: R2 §5 only.

Confirmed. Spec §18.5 (line 2399): "A repeater has no rate, burst, limit, capacity, lifetime, or inputs; each is ERR_UNKNOWN_FIELD." Validation accepts all of them. rate alone then throws ERR_UNBOUNDED_EMISSION at activation (misleading for a system type with no emission); rate + limit passes and rate is silently ignored; trail silently records/draws trail history even though the field is not in the repeater table (the schema, inconsistently, does forbid trail).

Files: src/runtime/visual-validation.js:325-333, src/runtime/visual-systems.js:80,96, schema/xzbt-0.1.schema.json:3229.

Correction: Extend per-type unknown-field validation to reject rate/burst/limit/capacity/trail (and fields if unintended) on repeaters. Remove these from the schema's repeater variant.


C9. Procedural system admission gaps: required fields and emission semantics deferred to activation

Reported by: R3 P2-6 only.

Confirmed. particles without render (spec §18.2 table: required), emitter without emit (spec §18.4: required), and particles with rate and neither lifetime nor limit — all import VALID. Spec §18.2/18.4 and the semantic-stage list (§18.9) demand import-time rejection. Instead they pass validation and only fail (or silently draw nothing) at activation/instantiation.

Files: src/runtime/visual-validation.js, src/runtime/visual-systems.js:96-99.

Correction: Enforce per-type required fields at import. Enforce the static half of the unbounded-emission rule at import (runtime check stays for stochastic rates).


C10. Glow paints over the object interior; silently dropped on text

Reported by: R3 P2-2 only.

Confirmed. visual-canvas2d.js:162-181: the point disc is re-filled with glow.color (a white point with red glow renders as a solid red disc), shapes are re-stroked on the identical path with glow color overpainting the real stroke, and text gets no glow branch at all (silent, while the engine still grants text a buffer). Spec §17.12 adds glow around the result, not over it.

Files: src/runtime/visual-canvas2d.js:162-181.

Correction: Draw only the halo via a zero-offset blurred shadow of the original geometry without re-filling/re-stroking the path. Apply the same technique to fillText/strokeText for text.


C11. Boolean leaves accept any literal; visible: 0 renders visible, enabled: "false" executes

Reported by: R3 P2-3 only.

Confirmed. visual-validation.js:233 validates effect enabled and object/layer/system visible only as ValueSpecs (structure); consumers use === false semantics. Spec §2 bans coercion: "a non-zero number will not be coerced into a boolean. Type mismatches produce ERR_TYPE_MISMATCH." Probes: visible: 0 imports valid and the runtime draws the object (expectation: hidden); enabled: "false" and enabled: 0 execute the effect (only exact false skips).

Files: src/runtime/visual-validation.js:233, src/runtime/visual-engine.js:529, :643-645, :698.

Correction: Type-check boolean leaves against the declared type at validation (ERR_TYPE_MISMATCH), applying the effect color-literal check pattern (visual-validation.js:244-247) to booleans.


C12. face-motion snaps on first tick and reverts on zero velocity

Reported by: R3 P2-4 only.

Confirmed. visual-behaviors.js:279-283: const current = instance.state.rotation ?? target initializes follow state to the target, so the first tick contributes the full turn regardless of smoothing. When XY speed drops below 1e-6, the behavior breaks, the fresh rotation bucket was zeroed, and the object pops back to the authored rotation. Spec §18.6: "An object with zero velocity holds its previous rotation."

Files: src/runtime/visual-behaviors.js:279-283.

Correction: Initialize instance.state.rotation lazily to the current drawn rotation on first tick. On the zero-velocity branch, emit the held (previous) value.


C13. Trail fade is a flat uniform alpha; head never keeps the item's own opacity

Reported by: R3 P2-5 only.

Confirmed. visual-engine.js:1014, :1053, :1064: line/ribbon mode draws the whole trail at the flat tail factor; nothing multiplies by the item's ramped opacityMultiplier. Spec §18.8: "fade … Opacity multiplier at the tail; the head keeps the item's own opacity." An item at ramped opacity 0.81 renders a full-strength head point but a line trail at flat alpha 0.4.

Files: src/runtime/visual-engine.js:1014, :1053, :1064.

Correction: Per-vertex/per-segment alpha from head itemOpacity to tail itemOpacity * fade (segmented strokes), or state the uniform-color approximation with WARN_VISUAL_APPROXIMATION.


C14. Bundle self-containment regex weakened past side-effect imports

Reported by: R2 §9 only.

Confirmed. test/phase1-runtime.test.mjs:41: the regex was changed from \bimport\s+[^;(] to ^[ \t]*import[ \t][^;(\n]*from[ \t]*['"] (multiline), requiring from. A side-effect import (import './chunk.js';) in the bundle would no longer be caught.

Files: test/phase1-runtime.test.mjs:41.

Correction: Add a second assertion for ^[ \t]*import[ \t]*['"] (side-effect form) so both statement shapes are covered while prose mentions remain tolerated.


C15. Bloom/color-adjust write unclamped channel math; blur radius capped at max(width,height) pixels

Reported by: R1 §3. SR §4 (spec side).

Confirmed. visual-effects.js:4-5: bloom adds pixels + glow * intensity; color-adjust can emit values outside [0,1] before * 255. Uint8ClampedArray hides overflow (no crash), but a full-frame-radius blur is O(pixels × max(edge)) and can hitch on large backing stores. Spec §19.5/19.4 defines blur and bloom radius as 32 scene units; the device radius from effectPlan can exceed this under high zoom. SR's finding confirms that POST_EFFECTS max may be 32 in scene units, but the pixel filter can still run at huge projected radii.

Files: src/runtime/visual-effects.js:4-5, :38, :74.

Correction: Clamp processed channels explicitly before assignment. Cap device blur radius by a pixel budget derived from the scene-unit maximum. Assert §19.4 ranges in execution tests.


C16. ring with equal start/end angles draws a visible radial spoke instead of nothing

Reported by: R3 P2-8 only.

Confirmed. visual-geometry.js:348-351: when startAngle === endAngle, directedSweep returns sweep 0, but the partial-sector branch still emits outer-point → lineTo inner-point → closed, which strokes as a straight radial line. Spec §17.9: "d == 0 draws nothing and raises no diagnostic." The sibling arc primitive handles sweep 0 correctly.

Files: src/runtime/visual-geometry.js:348-351.

Correction: Return no segments when sweep === 0 (keep the full-annulus branch).


C17. morph/point-wander on path objects: validation allows, runtime silently no-ops

Reported by: R2 §4 only.

Confirmed. visual-validation.js:31-33 includes 'path' in POINT_LIST_TYPES/MORPH_TYPES. pointsOf (visual-motion.js:44) returns raw command objects ({op, to}) whose points live in to; the behaviors read/write .x/.y/.z on the command object itself, producing junk keys while geometry is untouched. An accepted, authored behavior silently does nothing.

Files: src/runtime/visual-validation.js:31-33, src/runtime/visual-motion.js:44, src/runtime/visual-behaviors.js:225-236, 344-349.

Correction: Either drop 'path' from POINT_LIST_TYPES/MORPH_TYPES until command-point addressing exists, or make pointsOf/the behaviors address command.to points for move/line ops.


C18. Resolved non-integer creation counts silently rounded instead of faulting

Reported by: R3 P2-7 only. Overstated severity — downgraded from P2 to borderline P2/P3.

Confirmed. visual-systems.js:59, 63: Math.round on resolved count and burst count. Spec §18.2: "an integer field whose resolved value is not an integer is ERR_TYPE_MISMATCH at that boundary rather than being rounded." The authoring error is unmarked. Severity is lower than R3's P2 because the behavior (rounding) is deterministic and reproducible — it masks an error but doesn't cause incorrect rendering in practice.

Files: src/runtime/visual-systems.js:59, 63.

Correction: After sampling, throw RuntimeFault('ERR_TYPE_MISMATCH', …) when !Number.isInteger(resolved) for count and each burst count.


Tier 3 — Low-severity, editorial, or testing gaps

C19. Local visual automation crashes on missing target object

Reported by: R1 §2. SR §2 (spec side).

Confirmed. visual-automation.js:11-13: while (object?.children.some(…)) — if find returns undefined, .some is called on undefined. Should throw RuntimeFault('ERR_INVALID_REFERENCE', …) rather than an unguarded TypeError.

SR correctly notes this should be ERR_INVALID_REFERENCE per §19.1 (undeclared object), not an untyped crash. R1 proposes object?.children?.some(…) with a no-op or fault.

Files: src/runtime/visual-automation.js:11-13.

Correction: Use object?.children?.some(…). When object is missing, throw RuntimeFault('ERR_INVALID_REFERENCE', …).

Note

R1 rated this P1; however, the validator is supposed to reject bad targets at import. The runtime crash occurs only for exhibit-scope fallbacks and spawned substitutes that bypass validation. Downgraded to T3 for triage because the happy path (validated documents) is not affected.


C20. remove actions always report executed even when the instance ID is unknown

Reported by: R1 §4 only.

Confirmed. actions.js:53-55, visual-engine.js:475: this.instances.get(id)?.remove(this.time) — callers cannot distinguish a successful release from a no-op. Low severity because spawn returns the ID and tests use that ID.

Files: src/runtime/actions.js:53-55, src/runtime/visual-engine.js:475.

Correction: Return refused / failed when the map has no entry.


Additional P3 findings (from R3, confirmed)

These are confirmed but lower-priority items from R3's P3 section:

  1. Diagnostics raised list grows unboundedvisual-diagnostics.js:19, 39, 63: push per tick, no pruning in endTick, per-frame slice() copy. Slow memory growth over long sessions. Fix: clear or ring-buffer after the per-tick plan copy.

  2. Degenerate path arcsvisual-geometry.js:144-149, 153-186: coincident-endpoint arc forces acos(0) and emits a phantom bulge; negative radius drawn mirrored instead of ERR_OUT_OF_BOUNDS. Fix: early-return on coincident endpoints; check raw radii before Math.abs.

  3. Post-effect transient memory unbounded at backing ceilingvisual-effects.js:6-39: each blur pass allocates full-frame copies at up to 4096 device-px. No tie-in to the §19.5 pass budget. Fix: tile the blur or cap effect radius relative to the backing store.

  4. window-traffic challenge fixturetools/visual-challenge-fixtures.mjs:56-60: per-copy wrap bounds are ±30 px around each copy's own origin; cars cycle in place, never crossing the window. The 4g suite only checks finiteness/equality. Fix: if window-crossing is intended, use the window rect as bounds. If cell-stutter is intended, document it.

  5. Schema has no consumer; already diverges from validatorschema/xzbt-0.1.schema.json: no code in src/tools/tests uses the schema for validation. Schema marks per-system fields like render/emit as required that the validator admits without (see C9). Fix: consider validating exhibit fixtures against the schema in CI.


Spec-impl alignment items (from SR, confirmed)

S1. Required traces 19.7.3 and 19.7.4 are not implemented as specified

Reported by: SR §3 only.

Confirmed. test/phase4-execution.test.mjs "19.7.3" only checks one exhibit-scope stream; "19.7.4" only checks ERR_AUTOMATION_CONFLICT. The spec requires: (3) system-scope at from instantiation — two spawns 4s apart produce identical curves offset by 4s; (4) cross-scope / undeclared / out-of-registry targets raise ERR_INVALID_REFERENCE vs ERR_UNSUPPORTED_TARGET.

Files: test/phase4-execution.test.mjs.

Correction: Add the two-spawn 4s offset assertion and the four negative target cases from §19.7.4. These are test gaps, not runtime defects.


Canvas 2D backend rendering gaps (from R3, confirmed but implementation-layer)

B1. Per-object buffer grants not consumed by the canvas backend; §17.12 stage order realized per-op

Reported by: R3 P2-1 only.

Confirmed. visual-canvas2d.js:88-96, 97-106, 227-241 never reads node.buffered; visual-engine.js:1171-1201 allocates per-object buffers and sets plan.buffered. Fill, stroke, glow, and filter ops each composite individually under the node's blend/alpha instead of the object being rasterized once and composited once (spec §17.12 buffer table/stage order). Observable: double-blending on translucent/blended stroke-over-fill overlap.

Files: src/runtime/visual-canvas2d.js:88-96, 97-106, 227-241, src/runtime/visual-engine.js:1171-1201.

Correction: When node.buffered is true, draw the node (subtree for groups) into a pool surface with default state, then composite once with the node's alpha/blend, mirroring the layer path at visual-canvas2d.js:234-241.


Testing gaps summary

No test covers:

  • Perspective-projected morph (C4)
  • Velocity behaviors on graphic/repeater hosts (C3)
  • Semantic validation on per-item behavior arrays (C2)
  • Backend rendering of buffered nodes, glow, blur, filters, or masked groups through a real surface pool (C1, C10, B1)
  • Zero-sweep ring / degenerate path arcs (C16, P3.2)
  • Typed-boolean leaves (C11)
  • Face-motion smoothing / zero-velocity (C12)
  • Trail head-opacity / fade taper in line/ribbon (C13)
  • Non-integer resolved counts (C18)
  • Long-run diagnostics growth (P3.1)
  • Post-effect runs against real readback (P3.3)
  • System-scope spawn-relative automation offset (S1 / 19.7.3)
  • Target-error taxonomy (S1 / 19.7.4)
  • CLI validator over all exhibits (C6)
  • Side-effect imports in bundle (C14)

Ordered by impact and dependency. Items marked (design) require a decision before the fix can be written.

# Files Change Kind
C1 visual-subsystem.js, visual-canvas2d.js, app.js Pass surface factory; add masked-group + layer-opacity render test Fix + test
C2 visual-validation.js Run validateBehaviors for system-level behavior arrays at import Fix + test
C3 visual-motion.js, visual-systems.js, visual-behaviors.js Integrate velocity for object/repeater hosts, or reject at validation Fix + test
C4 visual-behaviors.js Guard both sides of the z delta; add perspective-morph test Fix + test
C5 visual-engine.js, types.js, visual-validation.js, spec Accept numeric ms or restrict spec to literal — (design) Design + fix
C6 tools/validate-exhibit.mjs, src/runtime/validator.js Unify or sync the two validators; add cross-exhibit test Fix + test
C7 schema/xzbt-0.1.schema.json Remove spawn-only fields from variants; drop links from emitter Schema fix
C8 visual-validation.js, schema Reject emitter-only fields on repeaters; align schema Fix + schema
C9 visual-validation.js Enforce per-type required fields at import Fix + test
C10 visual-canvas2d.js Draw glow as blurred shadow halo, not re-fill/re-stroke; add text glow Fix
C11 visual-validation.js Type-check boolean leaves (ERR_TYPE_MISMATCH) Fix + test
C12 visual-behaviors.js Lazy init rotation; hold previous on zero velocity Fix + test
C13 visual-engine.js Per-segment alpha taper from head to tail Fix + test
C14 test/phase1-runtime.test.mjs Add side-effect import assertion Test
C15 visual-effects.js Clamp channels; cap device radius to pixel budget Fix + test
C16 visual-geometry.js Return no segments when sweep === 0 Fix + test
C17 visual-validation.js, visual-motion.js, visual-behaviors.js Drop 'path' from morph/wander types, or address command.to Fix
C18 visual-systems.js Throw ERR_TYPE_MISMATCH on non-integer resolved count Fix + test
C19 visual-automation.js Guard ?.children?.some; throw ERR_INVALID_REFERENCE Fix
C20 actions.js, visual-engine.js Return refused/failed for unknown instance ID Fix
B1 visual-canvas2d.js, visual-engine.js Consume node.buffered grants in the canvas backend Fix + test
S1 test/phase4-execution.test.mjs Add 19.7.3 spawn-offset and 19.7.4 target-error tests Test