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
88 lines
8.8 KiB
Markdown
88 lines
8.8 KiB
Markdown
# Specification review — 2026-09-06 (America/Los_Angeles)
|
||
|
||
## Scope
|
||
|
||
- **Local date:** 2026-09-06 (America/Los_Angeles, PDT / UTC−7). Written 2026-09-06 17:38 UTC.
|
||
- **Branch:** `main`
|
||
- **HEAD:** `0af58da89dd6095fa9ca3ed546a2e48d7c7971b7` (`feat(visual): implement the slice 4d renderer core`)
|
||
- **Today’s commits (since 2026-09-06 07:00 UTC):** `1bc4901` (spec §§17–19 rev 0.8), `6587d3e` (schema/validator/resolution), `699492f` (standalone bundle), `0af58da` (4d renderer).
|
||
- **Uncommitted work:** Phase 4e–4g runtime (`src/runtime/visual-*.js`, engine/actions/app/resolution/performance), tests, exhibits, evidence. **Git cannot date uncommitted files;** they are in scope as the current tree.
|
||
- **Authoritative sources:** `docs/XZBT_0-1_Format_Specification.md` rev **0.8**, especially §§6, 8.1, 9.1, 10.1–10.2, **17–19** (19.1–19.7). Supporting: `docs/XZBT_0-1_Implementation_Plan.md` Phase 4 slice table. PRD cited only where the spec restates it.
|
||
- **Not used:** any file under `/reviews`.
|
||
- **Limitations:** Action Model (spawn/remove *shapes*) is explicitly not fixed in §19.2; Phase 6 scenarios and 4h/GC6 (traces 20–21) are deferred and not treated as defects. Display judgment not run.
|
||
|
||
## Traceability (today’s visual work → spec)
|
||
|
||
| Item | Spec | Implementation | Status |
|
||
| --- | --- | --- | --- |
|
||
| Scene / fit / primitives / compositing | 17.4–17.13 | `visual-engine.js`, `visual-geometry.js`, `visual-canvas2d.js` | **Satisfied** (4d tests 17.16.*) |
|
||
| Camera matrix, parallax, perspective, focalLength clamp | 19.3 | `cameraMatrix`, `planFrame` | **Satisfied** (static + execution tests) |
|
||
| Components, particles, emitters, repeaters, behaviors, fields, trails, links | 18.1–18.8 | `visual-systems.js`, `visual-behaviors.js`, `visual-fields.js`, `visual-distributions.js` | **Satisfied** (18.10 traces in `phase4-procedural.test.mjs`) |
|
||
| Exhibit + system automation, shared curves/modes, live vs authoring budgets | 19.1, 16.1 | `resolution.js`, `visual-automation.js`, `audio-automation.js` | **Partial** (curves/modes/budgets tested; local-track target walk unsafe; 19.7.3/4 coverage incomplete) |
|
||
| Lifecycle, spawn/remove, ownership, release factor | 19.2 | `visual-lifecycle.js`, `VisualEngine.spawn/remove/cleanup` | **Partial** (`DurationSpec` after evaluation vs `parseDuration`; action lifetime not specified in §19) |
|
||
| Post-effects chain, grain isolation, pass ceiling | 19.4 | `visual-effects.js`, `effectPlan` | **Partial** (pixel tests exist; radius/energy vs 19.4 table not fully asserted) |
|
||
| Aggregate ceilings + cadence | 19.5–19.6 | `enforcePopulations`, `visual-diagnostics.js` | **Satisfied** for automated 19.7.18 cases; **unverified** vs measured 4h values (provisional by spec) |
|
||
| 19.7 traces 1–19 | 19.7 | `test/phase4-execution.test.mjs` | **Partial** (many named tests; 19.7.3/4/8/13 not all matching the prose) |
|
||
| 19.7 traces 20–21 | 19.7 | — | **Deferred** (4h / display); not a defect |
|
||
|
||
## Findings (confirmed gaps)
|
||
|
||
### 1. `spawn.lifetime` / action lifetime after evaluation is not a duration literal
|
||
|
||
- **Priority:** P1
|
||
- **Spec:** §6.1 — durations are unit strings converted internally to milliseconds. §19.2 — `spawn.lifetime` and `spawn.release` are **DurationSpec**; instance duration is that logical duration from instantiation.
|
||
- **Impl:** `visual-engine.js:466-468` always `parseDuration(sampleTree(...))`, which requires `typeof value === 'string'` (`types.js:55-56`). `actions.js:48-50` may `evaluateValue(action.lifetime)` first.
|
||
- **Expected:** Authored DurationSpec remains a duration literal through sampling, then converts to ms. If evaluation yields a number (ms), it must still be accepted or rejected with `ERR_TYPE_MISMATCH`, not `ERR_INVALID_DURATION`.
|
||
- **Actual:** Any non-string (including a resolved number) throws `ERR_INVALID_DURATION` and aborts spawn.
|
||
- **Trigger:** `spawn` action with evaluated `lifetime`, or a DurationSpec that the value resolver does not leave as a string.
|
||
- **Impact:** Legal instance lifetimes fail at runtime; §19.2 “absent means until remove” path is the only reliably tested path.
|
||
- **Correction:** Convert DurationSpec with `parseDuration` only when the value is still a string; if already a finite number, treat as milliseconds (or reject per strict coercion ban §2 — pick one and match §6.1).
|
||
|
||
### 2. Local automation target walk throws `TypeError` instead of `ERR_INVALID_REFERENCE`
|
||
|
||
- **Priority:** P1
|
||
- **Spec:** §19.1 — target naming an undeclared object is `ERR_INVALID_REFERENCE`; outside the registry is `ERR_UNSUPPORTED_TARGET`. Import should reject; runtime must not invent a third failure mode.
|
||
- **Impl:** `visual-automation.js:11-13`: `while (object?.children.some(...))` — if `find` returns `undefined`, `.some` is called on `undefined`.
|
||
- **Expected:** Validator fault at import, or runtime `RuntimeFault` with those codes.
|
||
- **Actual:** Uncaught `TypeError` during `createSystem` / instantiate (outside the per-tick `try` in `advance`).
|
||
- **Trigger:** Dangling graphic-object key on a system `automation` track, or exhibit-scope fallback path `visual-engine.js:403-406`.
|
||
- **Impact:** Tick/activation abort rather than a documented diagnostic.
|
||
- **Correction:** Guard `object?.children?.some`; if missing, throw `RuntimeFault('ERR_INVALID_REFERENCE', ...)`.
|
||
|
||
### 3. Required traces 19.7.3 and 19.7.4 are not implemented as specified
|
||
|
||
- **Priority:** P2
|
||
- **Spec:** §19.7 items 3–4 (lines ~3255–3258 of the format spec): (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` as specified.
|
||
- **Impl:** `test/phase4-execution.test.mjs` “19.7.3” only checks one exhibit-scope stream; “19.7.4” only checks behavior/automation `ERR_AUTOMATION_CONFLICT`.
|
||
- **Expected:** The behaviors named in 19.7.3–4 actually run (spec: “a parsed stub is never a passed runtime trace”).
|
||
- **Actual:** Adjacent properties are tested; the named traces are not.
|
||
- **Trigger:** Acceptance of slice 4f against 19.7.
|
||
- **Impact:** Spawn-relative automation offset and target-error taxonomy can regress without a failing test.
|
||
- **Correction:** Add the two-spawn 4s offset assertion and the four negative target cases from 19.7.4.
|
||
|
||
### 4. Blur/bloom radius vs 19.4 “32 scene units” is not enforced in the pixel filter
|
||
|
||
- **Priority:** P3
|
||
- **Spec:** §19.5 authoring table / 19.4 — blur and bloom radius **32 scene units**; pipeline clamp of 8.1 on effect parameters.
|
||
- **Impl:** `visual-effects.js:4-5` caps box-blur radius at `max(width,height)` **pixels** (device radius from `effectPlan`). Contract numeric clamp is in `effectPlan` via `POST_EFFECTS`; the filter can still run at huge projected radii.
|
||
- **Expected:** Authored/resolved radius in scene units stays in the 19.4 table; device radius is a projection of that, not an unbounded pixel kernel.
|
||
- **Actual:** Device kernel can equal the long edge of the backing store.
|
||
- **Trigger:** Large `zoom` × `deviceRadius` with radius still inside 32 scene units.
|
||
- **Impact:** Frame hitch; not a silent spec violation of the authored 32 if `effectPlan` clamps scene units — **partial**. Confirm `POST_EFFECTS` max is 32 and that tests assert it (19.7.15 does not).
|
||
- **Correction:** Assert 19.4 ranges in execution tests; cap device radius by a pixel budget derived from the scene-unit max.
|
||
|
||
## Specification ambiguities (not defects)
|
||
|
||
1. **§19.2 vs Action Model:** spawn/remove *action shapes* are explicitly not fixed in 19.2. Whether `action.lifetime` is DurationSpec, ValueSpec\<number\>, or either is **undecided**. Finding 1 is a defect only for `spawn.lifetime` after `sampleTree`; action-lifetime typing needs a decision in the Action Model (Phase 5/6), not a silent assumption.
|
||
2. **DurationSpec vs TimeSpec (§6.2):** 19.2 names DurationSpec, not the random TimeSpec object. Whether `spawn.lifetime: { "random": { "min": "1s", "max": "2s" } }` is legal is **unspecified**. Implementation would currently fail `parseDuration` on an object.
|
||
3. **19.5 values** are provisional until 4h/trace 21; using 8192 particles / 2048 emitters in tests matches the *current table*, not a measured GC6 result.
|
||
4. **`instances.*` addressing** is runtime-only (19.2 / 8.1). Tests use `instances.s#N`; the Action Model still owns the public `remove` target grammar.
|
||
|
||
## Verification
|
||
|
||
- Read spec §§6, 17–19.7 and traced `spawn`, `advance`, automation, lifecycle, effects, resolution `visualValues`.
|
||
- `node --test test/phase4-*.test.mjs test/phase1-runtime.test.mjs`: **116 pass, 0 fail** (prior turn). Tests are evidence of implemented behavior, not of 19.7.3/4 completeness.
|
||
- **Not verified:** real-display 19.7.20, GC6 19.7.21, full Action Model document, audio 16.11 cross-check of every visual curve sample.
|
||
|
||
Actionable specification/implementation gaps exist (P1–P3 above). Traces 20–21 remain deferred by the spec itself.
|