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
62 lines
5.7 KiB
Markdown
62 lines
5.7 KiB
Markdown
# Independent review — 2026-09-06 (America/Los_Angeles)
|
||
|
||
## Review scope
|
||
|
||
- **Local date:** 2026-09-06 (America/Los_Angeles, PDT / UTC−7). Review written 2026-09-06 17:31 UTC.
|
||
- **Branch:** `main`
|
||
- **HEAD:** `0af58da89dd6095fa9ca3ed546a2e48d7c7971b7` — `feat(visual): implement the slice 4d renderer core` (2026-09-06 16:38:44 +0000)
|
||
- **Today’s commits (LA calendar day, since 2026-09-06 07:00 UTC):**
|
||
- `1bc49018` docs(visual): apply the sections 17-19 review triage in full at revision 0.8
|
||
- `6587d3e4` feat(visual): align the schema, validator, and resolution engine with the 0.8 visual contract
|
||
- `699492f8` build(visual): bundle the visual contract and validation modules into the standalone artifact
|
||
- `0af58da8` feat(visual): implement the slice 4d renderer core
|
||
- **Uncommitted work (staged: none):** modified runtime/docs/build files plus untracked Phase 4e–4g modules, tests, exhibits, evidence, and tools. **When those uncommitted edits were made cannot be established from Git;** they are included because they are the current tree.
|
||
- **Out of scope:** existing files under `/reviews`, `.abacusai/`, `.labyricorn/devlog/second-test-entry/`, `Claude outputs/`. No implementation files were modified for this review.
|
||
- **Limitation:** review is of the working tree vs HEAD plus today’s commits; Phase 4a–4c *docs* commits from late 2026-09-05 PDT are not treated as “today.”
|
||
|
||
## Findings (by severity)
|
||
|
||
### 1. Spawn `lifetime` is passed through `parseDuration`, which only accepts duration strings
|
||
|
||
- **Priority:** P1
|
||
- **Where:** `src/runtime/visual-engine.js:466-468`, `src/runtime/actions.js:48-50`, `src/runtime/types.js:55-56`
|
||
- **Trigger:** `ActionExecutor` evaluates `action.lifetime` (or spawn-input substitution yields a number) and `VisualEngine.spawn` always does `parseDuration(sampleTree(value, …))`. `parseDuration` throws `ERR_INVALID_DURATION` unless `typeof value === 'string'`.
|
||
- **Impact:** A legal numeric or already-resolved lifetime cannot spawn; the action fails as a runtime fault instead of applying the duration. Graphic-object `lifetime` in `visual-motion.js:70` has the same string-only assumption after sampling.
|
||
- **Evidence:** `spawn` helper `duration = (value, fallback) => value === undefined ? fallback : parseDuration(...)`. Tests in `test/phase4-execution.test.mjs` never pass `action.lifetime` or a numeric lifetime.
|
||
- **Correction:** Accept already-numeric milliseconds (and duration strings) in one helper; evaluate action lifetime to that type consistently with the spec.
|
||
|
||
### 2. Local visual automation crashes if the target object is missing
|
||
|
||
- **Priority:** P1
|
||
- **Where:** `src/runtime/visual-automation.js:11-13`
|
||
- **Trigger:** `visualLocalTracks` walks `root.find(...)` then `while (object?.children.some(...))`. If `find` returns `undefined`, `object?.children` is `undefined` and `.some` is still invoked.
|
||
- **Impact:** Instantiating or advancing a system with a dangling automation target throws `TypeError` instead of a `RuntimeFault` / import error, taking down the visual tick (`failSystem` only catches errors *inside* the per-system try after tracks are built at `createSystem`).
|
||
- **Evidence:** Optional chaining stops at `children`, not at `.some`. Validator is supposed to reject bad targets, but runtime still concatenates tracks for exhibit-scope fallbacks (`visual-engine.js:403-406`) and spawned substitutes.
|
||
- **Correction:** Use `object?.children?.some(...)` and no-op or fault when `object` is missing.
|
||
|
||
### 3. Bloom / color-adjust write unclamped channel math into `Uint8ClampedArray` without documenting wrap vs clamp; blur radius is clamped to `max(width,height)`
|
||
|
||
- **Priority:** P2
|
||
- **Where:** `src/runtime/visual-effects.js:4-5`, `38`, `74`
|
||
- **Trigger:** `bloom` adds `pixels + glow * intensity`; `color-adjust` can emit values outside `[0,1]` before `* 255`. `visualBoxBlur` sets `r = min(max(width,height), round(radius))`.
|
||
- **Impact:** Clamped arrays hide overflow (no crash), but a full-frame-radius blur is O(pixels × max(edge)) and can hitch on large backing stores when `deviceRadius` is large. Tests only assert “pixels changed,” not energy conservation or radius semantics (`test/phase4-execution.test.mjs` 19.7.15).
|
||
- **Evidence:** No `Math.min(255, …)` before assignment; radius cap is the long edge, not a contract ceiling.
|
||
- **Correction:** Clamp processed channels explicitly; cap blur radius to the post-effect numeric range / a small pixel budget.
|
||
|
||
### 4. `remove` actions always report `executed` even when the instance id is unknown
|
||
|
||
- **Priority:** P3
|
||
- **Where:** `src/runtime/actions.js:53-55`, `src/runtime/visual-engine.js:475`
|
||
- **Trigger:** `this.visual.remove(action.target)` → `this.instances.get(id)?.remove(this.time)`.
|
||
- **Impact:** Callers cannot distinguish a successful release from a no-op (typo’d id). Low severity because spawn returns the id and tests use that id.
|
||
- **Correction:** Return `refused` / `failed` when the map has no entry.
|
||
|
||
## Verification
|
||
|
||
- `node --test test/phase4-*.test.mjs test/phase1-runtime.test.mjs`: **116 pass, 0 fail** (≈2.5s).
|
||
- Inspected Git status, today’s LA-window commits, unstaged diffs, and untracked `src/runtime/visual-*.js` plus tests.
|
||
- Traced spawn/remove, tick (`performance.js` `onTick` → `VisualSubsystem.advance` → `VisualEngine.advance`), automation loop, effects, lifecycle, and resolution effect sampling.
|
||
- **Not verified:** browser/Canvas `getImageData` on the real `XZBT.html` artifact; audio regression for `automationValueAt` loop (shared with visual); exact wall-clock of uncommitted files; full `test/*.test.mjs` outside phase1/phase4.
|
||
|
||
No P0 defects were confirmed. Remaining risk is concentrated in uncommitted Phase 4e–4g runtime (lifetime typing, automation target walk) rather than the four committed 4d/contract commits.
|