Files
XZBT/reviews/.completed-artifacts/05-visual-fix-log.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

22 KiB
Raw Blame History

XZBT 0.1 §§1719 visual triage — fix log (what was applied, and the design chosen for each)

Record of edits to docs/XZBT_0-1_Format_Specification.md closing the thirty repair packages (V1V30) and three additional defects (A1A3) of 01-triage.md, in the batch order that document's closure table sets. Follows the 04-fix-log.md pattern for the §§1416 audio pass.

Standing decision for this pass: the user directed that the triage's own Recommended fix be adopted for every Tier-1/2/3 design decision and logged rather than reviewed individually. Every row below therefore records a chosen design, not a deferral. Where the triage offered a recommendation, it was taken; where it named a decision without a recommendation, the choice made is stated with its reason.

  • Baseline: working tree at commit 527220e, spec revision 0.7.
  • Result: spec revision 0.8. Doc-only — no schema, runtime, fixture, or test file was touched by this pass. npm test unchanged at 102 passing, zero failures.
  • Net diff: 704 insertions / 88 deletions, one file.
  • New tool: tools/verify-spec-contract.py, the contract harness previously run ad hoc, now committed. Post-edit run: 126 headings, 46 declared diagnostic codes with 43 used and 3 declared-only, 107 distinct cross-references all resolving, zero unresolved, 170 balanced fence markers, 112 well-formed tables.

Completion state of every package below is spec fixed. None is implementation verified: no visual runtime exists yet, and no trace in §§17.16, 18.10, or 19.7 has run.


Batch 1 — contradictory schema surfaces and impossible traces

Package Chosen design Sections edited
V1 automation added to the §17.3 allowed-field table as an optional array defaulting to [], with a note that the system-scoped array is a field of the system and not of this container. The two scopes and their time origins are untouched; no capability row was added. 17.3
V2 Trace 17.16.14 rewritten around legal and illegal capabilities per target family, keeping the per-object rejection case and adding the type-mismatch and undeclared-reference cases. The stale "the table is unchanged by this slice" assertion is deleted, and the trace now says explicitly that accepting a target is not evidence its pipeline runs — that is trace 7 of §19.7. 17.16.14
V5 fill and stroke retyped ValueSpec<color>, paint object, or null. A new Color leaves and ValueSpecs block names the exact set that accepts a ValueSpec — fill, stroke, glow.color, shadow.color, and paint stops[].color — and fixes three limits: a ValueSpec<color> never returns a paint object (ERR_TYPE_MISMATCH), a paint's structural fields stay literal, and system-level colors (scene.background, depthFog.color, effect color) stay literal. Once-at-instantiation sampling preserved. 17.12
V11 The step registry clause is deleted, not rescued. A repeater now has no automatable property, with the reason stated (copies are created once at instantiation, so no later change can reach them) and the ERR_UNSUPPORTED_TARGET / ERR_INVALID_REFERENCE distinction spelled out. No repeater layout language was invented. 19.1
V12 One rule replaces "only on a persistent scope": infinite is legal in every scope, bounded by the lifetime of whatever owns the track. A four-row table covers exhibit scope, persistent, finite-lifetime spawned, and indefinite spawned. No diagnostic; ping-pong's round-trip count unit untouched. 19.1
V17 Two conditional rules. Viewport fit: absent fit is accepted with no mapping effect and the contain default is not taken; only an explicitly authored non-stretch value is ERR_SCHEMA_VALIDATION. Layer omission: when visuals.layers is present a system's layer is required and an omission is ERR_INVALID_REFERENCE — no silent first-layer fallback — and a present-but-empty map is ERR_SCHEMA_VALIDATION. 17.4, 17.5, 17.7
V26 Trace 17.16.9's nonexistent "9 layer nesting levels" replaced by "17 layers", with a note that group depth is trace 4's case. 17.16.9
A1 The design decision of this batch. All five spawned-instance lifecycle fields move into a dedicated spawn object on the system: spawn.lifetime, spawn.release, spawn.ownership, spawn.inputs, spawn.cancelWithScenario. A top-level lifetime is unambiguously per item (particles, emitter) and spawn.lifetime is per instance, so a spawned emitter expresses both, and a spawned repeater takes spawn.lifetime without contradicting §18.5's ban on a repeater lifetime. A template's own inputs.* reference scope is stated and kept disjoint from a component's. Patching only the persistent-system prohibition — which would have left the spawned ambiguity intact — was rejected as the triage instructs. 17.7, 19.2, 19.7.8
A2 direction declared on the noise field: a number in degrees, required under mode: "value" and ERR_UNKNOWN_FIELD under curl/gradient. The conditional trail on a particle's render object is removed rather than declared: one placement (the system's trail), no precedence rule, ERR_UNKNOWN_FIELD elsewhere. 18.7, 18.8

Batch 2 — renderer geometry and composition

Package Chosen design Sections edited
V3 A full named-space composition replaces the ambiguous matrix prose. Four spaces (local, scene, CSS, device) are named; the fit matrix F = T(ox, oy) × S(sx, sy) carries the centered contain/cover offsets that were missing; the camera center is mapped through F before the translation is formed (q = F(cameraCenter), translation q c), which is the unit mismatch the review found; the normative chain is x_device = B × P × V × F × M_effective × x_local with DPR applied once by B; perspective is a stage P about the projection center, not a vague post-multiplication. Nonuniform stretch is stated to produce parallelograms from rotated squares, and axis-less appearance scalars take the single uniform factor g = zoom · k · sqrt(sx · sy). Perspective does scale stroke, blur, glow, and shadow. 17.2, 17.6, 19.3
V4 Local extents and anchors fixed for all fourteen primitives, with requiredness and defaults given rather than inferred (rectangle anchors top-left, circular primitives center, ring angles default 0/360). Arc sweep is directed: ` d
V9 Sortable units defined for every system, not just particles. A graphic system's top-level objects sort individually; each particles, emitter, and repeater is atomic, with representative depth = the lowest zEffective among its live items (generalizing §18.2's existing rule rather than replacing it) and internal order by depth then creation ordinal. Cross-unit ties, empty systems, and links all covered. Per-point z is given exactly one effect — per-point perspective under perspective — while sorting and fog use the object's own zEffective, which keeps the one-object fog rule intact. 17.6
V10 An eight-stage normative per-object compositing order plus a layer stage. The effective alpha is own opacity × ancestor opacities × release factor, and layer opacity is explicitly not in it (applied once at the layer stage), removing the §17.5/§17.12 double-application. Buffer accounting covers layer buffers and object buffers in one 16-slot pool, with reuse (concurrent, not cumulative), allocation order (layers first, then draw order), refusal semantics per feature, and farthest-first shedding. Buffer refusal is named a diagnosed exception to §17.1's appearance guarantee. WARN_VISUAL_APPROXIMATION is reused, not re-minted. 17.5, 17.12
V18 Fog arithmetic fixed: per-component blend in non-premultiplied sRGB, no linearization, alpha never fogged, fog color's alpha ignored, and gradient stops fogged individually before the paint is constructed. Conic fallback axis fixed against the object's local-space AABB, with explicit rules for a center outside the box, a non-intersecting ray, and a degenerate box; stops and offsets preserved exactly, against the reviews' suggestion. 17.6, 17.12
V20 focalLength range closed at 1 to 100000 scene units. Literals outside are ERR_OUT_OF_BOUNDS at import; resolved values from random, binding, automation, override, or modulation clamp into it as the §8.1 safety-clamp stage, with no implementation epsilon anywhere. 19.3
V29 size is a uniform local geometry scale on the render object, applied innermost, with a per-render-type mapping table (including pointSize, ellipse radius, text size, and components) and an explicit exclusion of appearance dimensions. Velocity alignment — emitter.align, face-motion, ribbon perpendicular — uses XY velocity only, atan2(vy, vx), with a stated zero-XY-speed rule (hold previous rotation; authored rotation at creation). 18.2
A3 The 4096 × 4096 backing store wins over the "floor of 1" multiplier: on a CSS display larger than 4096 the multiplier goes below 1 and the frame is upsampled, raising WARN_VISUAL_APPROXIMATION once per resolution change; the multiplier is never raised above min(devicePixelRatio, 2). "Pinned to the display" becomes "pinned against camera translation", with zoom and rotation explicitly still applying at parallax: 0. 19.3, 19.5

Batch 3 — procedural evaluation

Package Chosen design Sections edited
V6 A three-class field-ownership table replaces "every ValueSpec above resolves once per particle": system-instantiation values (including count, rate, and burst count, so creation counts are known before allocation), system channels (position, acceleration, drag, visible), and per-item values. A second table fixes what a live channel does to existing versus future items — rate and position reach only new items, acceleration and drag reach every live item on the next tick — and states that a track composes against the sampled base under its mode. Burst-before-continuous creation order and ordinal assignment fixed. Applied to §18.4 with emit substituted. 17.14, 18.2, 18.4
V8 Coherent noise written out as an algorithm: the twelve gradients in fixed order and unnormalized; the FisherYates permutation with an explicit 255-sample draw count (correcting "one sample per entry"); the full lattice hash and trilinear/quintic evaluation; h mod 12 selection defended as deterministic rather than unbiased; octave normalization by accumulated amplitude; sample coordinates; and the three mode vector constructions, with curl as the explicit perpendicular of the scalar potential's gradient by central difference at h = 1e-3. Published tolerances: 1e-9 for scalar oracles, 1e-4 · amplitude / scale for curl divergence, tested at non-lattice as well as lattice points. Behavior noise gets one shared permutation table per exhibit plus a per-behavior offset pair (2 samples, not 1), with per-behavior coordinate formulas — the shared table chosen so that a large particle system does not derive 255 samples per item. 18.6, 18.7, 18.10.15, 18.10.16
V13 n = 1 placement uses fraction 0, matching repeat.fraction. Even ring/path/line/grid placements, even-mode ring radius (mid-annulus), grid wrap when n ≠ rows × columns, the three depth inverse functions, ellipse perimeter (uniform in parameter, one sample, stated as the choice), rectangle perimeter (one sample, correcting the two-sample assertion), and a normative path arc-length flattening tolerance of 0.1 scene units are all written out. "Field order" is replaced by an explicit per-distribution draw list, with declaration order distinguished from variate order and angle-before-radius kept. The grid-with-jitter and depth sub-block sample costs are reconciled with trace 18.10.10, which is rewritten. Burst creation-index rule fixed. 18.3, 18.10.10
V14 A complete field contract table for all seventeen behaviors (type, requiredness, default, range). Waveforms given as equations in cycles with a common phase origin and all four zero-crossing at phi = 0 where they can be. pulse rise/fall envelope and face-motion frame-rate-independent smoothing (1 smoothing^dt) fixed. An accumulating versus fresh table prevents an orbit being integrated as a per-tick displacement, with orbit's contribution written out. A behavior channel write-set table supports conflict detection, and velocity.* and points[*] are noted as outside the automatable registry, so those behaviors can never conflict — which also disposes of the reviews' vector-track counterexample. vortex gets an explicit y-down perpendicular (r.y, r.x) with the note that a force strength is not an angle. 18.6, 18.7
V15 Morph restricted to point-list geometry: polyline, polygon, spline (matching mode), and path only when every command is move/line/close and the two op sequences are identical. Everything else is ERR_MORPH_INCOMPATIBLE. Target points are read live, not snapshotted, with document-key evaluation order making it decidable and a mutual morph pair ERR_CYCLIC_DEPENDENCY. 18.6, 18.10.14
V16 The distribution path's sibling-key shorthand is removed — a system's siblings are systems, so the container never existed — leaving inline commands under the full §17.13 contract. The object-attached follow-path sibling reference, which does have a container, is preserved explicitly. 18.3
V21 Link style defaults to the resolved style of the system's own render/repeat object, naming the exact source. fadeWithDistance: true without maxDistance is ERR_SCHEMA_VALIDATION, with the alternative normalization rejected and the reason given. index, stride, closed, and nearest tie-breaks operate on a densely re-indexed live ordering, so deaths close gaps. Nearest ties break by ascending creation ordinal. The 256 pairwise-population bound is stated as intentionally conservative (checked against the upper bound, not the live count) and enforced at import for literals and at the instantiation boundary for a procedural repeater.count. 18.8, 18.10.18
V22 One canonical component-input location: the component object's own inputs (§18.1), everywhere. The system-level inputs fields on emitter and repeater are removed and are now ERR_UNKNOWN_FIELD. Chosen over the reverse because it leaves both normative examples valid as written, keeps component uniform wherever it appears, and leaves spawn.inputs (A1) as the only other inputs, in a different container with a different meaning — so there is no precedence rule, no duplicate-key case, and no masked-sampling question. 18.4, 18.5

Batch 4 — live resources and later execution

Package Chosen design Sections edited
V7 Authored-record validation and live-instance accounting separated into two checks over two populations: an import authoring bound counting each declaration once, and a live budget counting instantiated records from CREATED to DISPOSED. A spawn crossing either is refused atomically — nothing allocated, no partial instance, no spawn ordinal consumed, WARN_VISUAL_CEILING under the §19.5 cadence, not a scenario failure. Admitting an instance with tracks silently dropped is explicitly forbidden. Reclamation at DISPOSED, not FINISHED; FAILED reclaims immediately. The §19.5 row is amended from "authoring bound, not a shed" accordingly. 19.1, 19.5, 19.7.6
V19 Radius conversion fixed as r_device = r_scene · zoom · sqrt(sx · sy) · dpr_effective, with the two terms an effect cannot have — object depth k and layer parallax — excluded explicitly and by name. Reduced-resolution processing multiplies by the resolution factor inside the buffer. Effect ValueSpecs, enabled included, resolve once at activation in array-then-field order. saturation/hueRotate mapped to the §17.12 saturate/hue-rotate operations without renaming the authored fields. Device-pixel scanline spacing left alone as a valid choice. 19.4
V23 spawn.ownership and spawn.cancelWithScenario are kept as two different relationships — resource ownership versus an independently retained originating-scenario relationship — which is what makes the flag non-redundant on a persistent-owned instance; the documented false case is preserved. Idempotent remove, the direct CREATED → FINISHED and → FAILED paths, and post-FAILED accounting are stated in prose, not only in traces. The release factor is defined here (separate multiplier, initialized to 1, monotonic, never writes an authored opacity), which disposes of the claimed opacity pop without adding a public system opacity property. 19.2
V24 One cadence for every resource diagnostic: keyed by (code, subject), at most once per logical second, with the 120-tick sustained report as the same code plus a sustained detail rather than a second code, not bypassing the rate limit, and both counters resetting after one clear tick. Approximation shedding uses the same cadence; the per-instance capability warnings of §17.12 and §19.4 are explicitly left alone. §19.2's bare "once" per refused spawn is reconciled to it. 19.2, 19.5, 19.7.18
V25 Mechanical: the central table gains path commands (512), text characters (256), strokeDash entries (8), burst entries (16), grid dimensions (256), custom oscillator partials (64), nearest links per item (8), pairwise-linked population (256), and visual automation points per track (256); the resonator row is fixed to 16 fixed in §15.10 and de-duplicated. Design: the static-draw-load gap is closed with two new authoring bounds — 64 declared visual systems and 16384 expanded static visual objects after component and repeater expansion, with the counting rule stated — both marked provisional pending 4h in exactly the sense the aggregate values are. 17.3, 19.5

Batch 5 — contract synchronization

Package Chosen design Sections edited
V27 A single staging table for resolved-value validation: non-integer resolved integers are ERR_TYPE_MISMATCH at the instantiation boundary with no rounding rule (§2 strictness preserved); over-length resolved text is ERR_OUT_OF_BOUNDS; out-of-range component input values are ERR_OUT_OF_BOUNDS at import or instantiation. The clamp-versus-reject split is explained by whether the value changes the shape or the magnitude of what is built. ERR_INVALID_PRIMITIVE_TYPE's cause broadened to name the fourteen primitives plus component in both §7 and §17.15. Audio/visual diagnostic parity was not forced, and ERR_UNSUPPORTED_TARGET for unsupported persistent ownership was left alone, as the triage directs. 7, 17.14, 17.15
V28 §17.14 now describes a sampled base with the effective value produced by behaviors, automation, and the pipeline; its "no visual analogue" prose points at §19.4's grain exception as the one exemption; the instances.* citation is corrected to §8.1's action-addressing namespace rather than a §1.3 document namespace; §19.2's lifecycle row is marked a restatement of §17.7's. No new live boolean writer, seeded grain stream, or altered lifecycle enum. 17.14, 19.2
V30 A cross-slice contract map added to §19.7: what 4d, 4e, and 4f each must implement versus carry as data without executing. Trace 17.16.6's perspective requirement is explicitly kept in 4d — dropping it would have removed the only automated check on the composition chain where V3's defects lived — and the map ends with "a parsed stub is never a passed runtime trace". 19.7

Findings the triage cleared, and that this pass did not act on

All twenty-plus cleared findings in §"Findings checked and cleared" of 01-triage.md were left unimplemented, as instructed. In particular the locked conventions survive unchanged: degrees with positive angles toward +y; increasing z farther from the camera with greater-z-first sorting; the fixed 1000/60 ms tick; seeded decisions with no per-frame sampling; the four-family §8.1 visual capability surface with no per-object row; the non-evicting spawn policy; ping-pong's round-trip count unit; links drawn before their items; and the visual component namespace components.visual.

What is still open after this pass

  1. No implementation exists. Every package is spec fixed. Slice 4d has not started; the JSON Schema still has a Phase 0 visuals stub, validator.js still has no validateVisualSubsystem(), and resolution.js still rejects all four visual target families. That is the Stage 0 code-alignment work of 02-application-code-triage.md, which this pass deliberately did not touch so that the contract change lands as its own commit, per the project's contract-before-implementation convention.
  2. Every §19.5 value remains provisional, the two new static bounds included. No evidence record may call any of them measured until trace 21 of §19.7 runs on real hardware in slice 4h.
  3. Traces are not run. §§17.16, 18.10, and 19.7 are amended contracts, not results.