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
387 lines
28 KiB
Markdown
387 lines
28 KiB
Markdown
# XZBT 0.1 Phase 4d–4g — code review triage
|
||
|
||
Inputs:
|
||
|
||
- **R1** — [review-173154-a7c3e91b](review-2026-09-06-unknown-model-173154-a7c3e91b.md) (4 findings, code-focused).
|
||
- **R2** — [review-180353-6b68e324](review-2026-09-06-unknown-model-180353-6b68e324.md) (9 findings, deep runtime probes).
|
||
- **R3** — [review-183137-f3a91c2d](review-2026-09-06-unknown-model-183137-f3a91c2d.md) (8 numbered + 6 P3, broad coverage).
|
||
- **SR** — [spec-review-173816-c4f8a2d1](spec-review-2026-09-06-unknown-model-173816-c4f8a2d1.md) (4 findings, spec-vs-impl traceability).
|
||
|
||
This follows [01-triage.md](.completed-artifacts/01-triage.md) (visual contract §§17–19 specification triage) and [00-triage.md](.completed-artifacts/00-triage.md) (§§14–16 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 4d–4g 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.**
|
||
|
||
---
|
||
|
||
### C6. The two repo validators disagree: standalone CLI rejects legal visual bindings
|
||
|
||
*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 unbounded** — `visual-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 arcs** — `visual-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 ceiling** — `visual-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 fixture** — `tools/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 validator** — `schema/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)
|
||
|
||
---
|
||
|
||
## Recommended correction order
|
||
|
||
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 |
|