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
5.7 KiB
5.7 KiB
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):
1bc49018docs(visual): apply the sections 17-19 review triage in full at revision 0.86587d3e4feat(visual): align the schema, validator, and resolution engine with the 0.8 visual contract699492f8build(visual): bundle the visual contract and validation modules into the standalone artifact0af58da8feat(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:
ActionExecutorevaluatesaction.lifetime(or spawn-input substitution yields a number) andVisualEngine.spawnalways doesparseDuration(sampleTree(value, …)).parseDurationthrowsERR_INVALID_DURATIONunlesstypeof 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
lifetimeinvisual-motion.js:70has the same string-only assumption after sampling. - Evidence:
spawnhelperduration = (value, fallback) => value === undefined ? fallback : parseDuration(...). Tests intest/phase4-execution.test.mjsnever passaction.lifetimeor 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:
visualLocalTrackswalksroot.find(...)thenwhile (object?.children.some(...)). Iffindreturnsundefined,object?.childrenisundefinedand.someis still invoked. - Impact: Instantiating or advancing a system with a dangling automation target throws
TypeErrorinstead of aRuntimeFault/ import error, taking down the visual tick (failSystemonly catches errors inside the per-system try after tracks are built atcreateSystem). - 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 whenobjectis 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:
bloomaddspixels + glow * intensity;color-adjustcan emit values outside[0,1]before* 255.visualBoxBlursetsr = 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
deviceRadiusis large. Tests only assert “pixels changed,” not energy conservation or radius semantics (test/phase4-execution.test.mjs19.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/failedwhen 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-*.jsplus tests. - Traced spawn/remove, tick (
performance.jsonTick→VisualSubsystem.advance→VisualEngine.advance), automation loop, effects, lifecycle, and resolution effect sampling. - Not verified: browser/Canvas
getImageDataon the realXZBT.htmlartifact; audio regression forautomationValueAtloop (shared with visual); exact wall-clock of uncommitted files; fulltest/*.test.mjsoutside 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.