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
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
# XZBT 0.1 §§17–19 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 (V1–V30) and three additional defects (A1–A3) of
|
||||
[01-triage.md](01-triage.md), in the batch order that document's closure table sets.
|
||||
Follows the [04-fix-log.md](04-fix-log.md) pattern for the §§14–16 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| > 360` is `ERR_OUT_OF_BOUNDS` **before** normalization, `|d| == 360` is a full turn, `d == 0` draws nothing. `catmull-rom` is a **uniform cardinal spline** with the cubic Hermite equation and the tension form `m = tension · (p_next − p_prev)` written out, plus the open-duplication and closed-wrap index rules. A `closed` `bezier` spline closes with a **straight line**. Open-subpath fill closure, `point` stroke behavior, and `maxWidth` condensation (`maxWidth / w` about the align anchor) are all fixed; the text-metric device dependence is argued to need no new reproducibility exemption. | 17.9, 17.13 |
|
||||
| **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 Fisher–Yates 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](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.
|
||||
Reference in New Issue
Block a user