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,408 @@
|
||||
# XZBT 0.1 Format Specification §§14–16 — review triage
|
||||
|
||||
Inputs: `01-grok-4.6.md`, `02-kimi-k3.md` (two outputs, same seven findings), `03-deepseek-v4-flash.md`.
|
||||
GLM review not received.
|
||||
|
||||
Every claim below was checked against `docs/XZBT_0-1_Format_Specification.md` as it stands on disk
|
||||
(1499 lines). Line numbers are that file's. Nothing in the spec was edited.
|
||||
|
||||
**Result: 13 distinct defects. All 21 reported findings are real** — none is a false positive —
|
||||
but only **4 of 13 were found by all three reviewers**, and **5 of 13 were found by exactly one**.
|
||||
Four proposed fixes are wrong or incomplete and are corrected below. Two defects that no reviewer
|
||||
found are added.
|
||||
|
||||
---
|
||||
|
||||
## Scorecard
|
||||
|
||||
| | Grok 4.6 | Kimi K3 | DeepSeek v4 Flash |
|
||||
| --- | :---: | :---: | :---: |
|
||||
| Findings reported | 6 | 7 | 8 |
|
||||
| Real | 6 | 7 | 8 |
|
||||
| Overstated | 0 | 0 | 1 (D8) |
|
||||
| Unique catches | 0 | 1 | 2 |
|
||||
| Of the 13 confirmed | 6 | 7 | 8 |
|
||||
| Proposed fix wrong/incomplete | 2 | 1 | 2 |
|
||||
|
||||
DeepSeek is the strongest pass: it is the only reviewer to unify the bus-gain defect (T1) instead of
|
||||
splitting it, and the only one to catch the eviction defect (T3) and the modulated-delay bound (T8).
|
||||
Kimi is the only one to catch the component-parameter defect (T2), which is the second-largest hole
|
||||
in the section. Grok found nothing the other two missed, and split T1 into two half-findings whose
|
||||
fixes contradict each other.
|
||||
|
||||
The overlap pattern is the argument for having run multiple models: **9 of 13 defects would have been
|
||||
missed by any single reviewer.**
|
||||
|
||||
---
|
||||
|
||||
## Tier 1 — Unimplementable: a required acceptance trace cannot pass as written
|
||||
|
||||
### T1. Bus gain claims automation and modulation with no authoring surface; no target in the document exposes both automation and override; trace 16.11.8 cannot be implemented
|
||||
|
||||
*Reported by: DeepSeek F1 (complete). Grok #4 and #5 are the same defect split in two. Kimi missed it.*
|
||||
|
||||
**Confirmed.** Three independent statements are orphaned:
|
||||
|
||||
- 8.1 (line 314) gives `audio.buses.<id>.gain` Automation = Yes and Additive modulation = Yes.
|
||||
15.17 (line 1230) and 16.2 (line 1330) repeat both claims.
|
||||
- Automation tracks live only in a graph object's `automation` array (14.3 line 606, 16.1 line 1265).
|
||||
Graph objects exist at exactly three places (lines 610–612); a bus is not one of them, and 15.17's
|
||||
bus table (line 1228) allows only `gain`, so an `automation` key on a bus is `ERR_UNKNOWN_FIELD`
|
||||
under the strict unknown-field policy.
|
||||
- 16.1 line 1286 restricts `target` to `<node-key>.<property>` "resolved in the same graph", and
|
||||
line 1293 restricts it to the 15.13 registry. A bus is not a node and bus gain is not in that
|
||||
registry (lines 1073–1085). **No legal `target` can name a bus.**
|
||||
- Modulation has the same problem: the only modulation syntax is a 15.12 route whose `to` is
|
||||
`<node-key>.<property>` in the same graph. **Nothing can modulate a bus gain either.**
|
||||
|
||||
Consequences:
|
||||
|
||||
1. 16.2's second consequence (line 1332, override masks automation, releases to the track's current
|
||||
value) describes a situation no legal document can produce. Node properties take automation
|
||||
(16.1) but are barred from override (14.4 line 631, `ERR_UNSUPPORTED_TARGET`); `parameters.<id>`
|
||||
and `state.<id>` take override but have Automation = No (8.1 lines 312–313); bus gain is the only
|
||||
row with both, and its automation is unauthorable. **No target exposes both stages.**
|
||||
2. Required trace **16.11.8** (line 1485) is therefore unimplementable, and Phase 3c cannot be
|
||||
accepted as written.
|
||||
|
||||
**Fix — all three reviewers get this wrong.**
|
||||
|
||||
- Grok #4 proposes keeping override-vs-automation "only for `audio.buses.<id>.gain`". That fails for
|
||||
the same reason: bus gain has no automation surface.
|
||||
- Grok #5 offers "add an `automation` array on the bus object, or strike automation from 16.2" and
|
||||
says not to leave both standing — correct as far as it goes, but it treats only the automation half
|
||||
and leaves the modulation half untouched.
|
||||
- DeepSeek F1 correctly identifies both halves and both options, then says **"Option (a) is the
|
||||
smaller change."** *That is wrong.* Deleting additive modulation from bus gain would orphan
|
||||
**§8.5 required trace 6** (line 409, "A target has legal additive modulation → Modulation follows
|
||||
the winning override; safety clamp runs last"), because bus gain is the **only** row in the entire
|
||||
8.1 table with Additive modulation = Yes. Option (a) does not shrink the problem; it moves it into
|
||||
§8, a contract §11 line 513 already marks **Complete (Rev 0.3 / GC3)**.
|
||||
§8.3 line 370 ("Underlying binding and **automation** stages continue to evaluate while masked")
|
||||
is orphaned by the same gap.
|
||||
|
||||
**Recommended fix:** option (b). Give `audio.buses.<id>.gain` a real authoring surface for automation
|
||||
and modulation, and make 16.1's target rule name it. Concretely: allow an `automation` array on the
|
||||
bus object with `target` fixed to `gain`, and add a bus-addressed modulation form. This keeps 8.1,
|
||||
8.3, 8.5, 15.17, 16.2 and traces 16.11.8 and 8.5-6 all consistent, and it is the only option that
|
||||
does not reopen a completed contract.
|
||||
|
||||
**Out of scope but worth recording:** §8.5 trace 6 and §8.3's masking sentence are *already*
|
||||
unimplementable today, independent of §§14–16. All three reviewers scoped to 14–16 and so could not
|
||||
see this. It should be logged against §8.
|
||||
|
||||
---
|
||||
|
||||
### T2. Component exposed parameters are legal modulation and automation targets with no defined effect; trace 15.19.4 cannot be implemented
|
||||
|
||||
*Reported by: Kimi #1 only. Grok and DeepSeek both missed it.*
|
||||
|
||||
**Confirmed.** 15.13 line 1085 lists `component` / `<exposed-parameter-id>` in the modulatable
|
||||
registry; 15.11 line 1045 repeats it; 16.1 line 1293 inherits it for automation. But the only path by
|
||||
which a component's internals read a parameter is `{ "ref": "inputs.<id>" }` in a node field
|
||||
(15.15 line 1166), and 14.4 line 629 resolves every node field **once** at instantiation, "constant
|
||||
for that node instance's lifetime". 15.11 line 1039 resolves the supplied `values` once as well.
|
||||
|
||||
So a modulation sum or automation curve applied to an exposed parameter changes a value that nothing
|
||||
ever reads again. Required trace **15.19.4** (line 1250, "an exposed parameter is a legal modulation
|
||||
target") cannot be satisfied without inventing behavior.
|
||||
|
||||
**Fix — Kimi offers two options and correctly says the first is smaller. It is also the only correct
|
||||
one, for a reason Kimi does not give.** Option two (make `{ "ref": "inputs.<id>" }` fields track the
|
||||
parameter live, as an exception to 14.4) would let a modulation route reach *any* internal node field
|
||||
through a parameter — including `reverb.decay`, `compressor.threshold`, `waveshaper.amount` and every
|
||||
other property 15.13 line 1087 deliberately excludes ("Merely being numeric does not grant modulation
|
||||
support"). That silently defeats the registry's whole purpose and breaks 15.11's encapsulation claim.
|
||||
|
||||
**Recommended fix:** delete the `component` row from the 15.13 registry (line 1085), delete the
|
||||
modulation-target sentence in 15.11 (line 1045), and drop the exposed-parameter clause from trace
|
||||
15.19.4.
|
||||
|
||||
---
|
||||
|
||||
### T3. 16.6 eviction step 3 frees no budget slot; the outcome is undefined and trace 16.11.10 is ambiguous
|
||||
|
||||
*Reported by: DeepSeek F2 only.*
|
||||
|
||||
**Confirmed.** Line 1406: "A voice counts against its ceiling from `CREATED` until `DISPOSED`."
|
||||
The eviction policy (lines 1408–1413) "stops at the first candidate":
|
||||
|
||||
1. Dispose the oldest `FINISHED` instance — frees a slot.
|
||||
2. Evict the oldest `RELEASING` instance by advancing its ramp to completion **and disposing it** —
|
||||
frees a slot.
|
||||
3. For a one-shot request only: evict the oldest `ACTIVE` one-shot **by starting its release** —
|
||||
moves it `ACTIVE` → `RELEASING`, which still counts (line 1406, reinforced by line 1372 and trace
|
||||
16.11.11). **Frees nothing.**
|
||||
4. Otherwise refuse.
|
||||
|
||||
With the ceiling full of `ACTIVE` one-shots, the policy stops at step 3 having freed no slot.
|
||||
Admitting the new instance puts the count at 65; refusing it contradicts having stopped before step 4.
|
||||
Neither is defined. Step 2's explicit "and disposing it" makes step 3's omission conspicuous rather
|
||||
than incidental.
|
||||
|
||||
A weak defense exists — line 1399 calls the ceilings "approximate" — but line 1406's counting rule and
|
||||
trace 16.11.11 both insist on exact accounting, so this does not resolve it.
|
||||
|
||||
**Fix — DeepSeek offers two options without ranking them; the first is correct.** Making step 3
|
||||
behave like step 2 (complete the release immediately and dispose) contradicts line 1415, "Eviction
|
||||
always releases; it never hard-stops an active voice" — an instant ramp on an audible voice *is* the
|
||||
click that 16.4's single-code-path design exists to prevent. Step 2 does it only to voices already
|
||||
decaying.
|
||||
|
||||
**Recommended fix:** state that an `ACTIVE` one-shot evicted under step 3 stops counting against the
|
||||
ceiling at the moment of eviction, its release running outside the budget, so the new instance is
|
||||
admitted and the ceiling is never exceeded. Add the matching exception to line 1406.
|
||||
|
||||
---
|
||||
|
||||
## Tier 2 — Real defects with clear fixes
|
||||
|
||||
### T4. `ERR_INVALID_RANGE_ORDER` is filed Semantic-only but the sample-hold rule needs instantiation-time values
|
||||
|
||||
*Reported by: all three (Grok #1, Kimi #2, DeepSeek F4). Unanimous, and unanimously right.*
|
||||
|
||||
**Confirmed.** 14.12 lines 815–816 make `min`/`max` full ValueSpecs in 14.4 scope — resolved once at
|
||||
instantiation. Line 819 makes inverted resolution `ERR_INVALID_RANGE_ORDER`. §7 line 286 files that
|
||||
code **Semantic** only, with a cause that says "after resolution" — the table concedes the problem in
|
||||
its own text. 14.5 line 645 states semantic validation checks only values that resolve to a literal
|
||||
and never opens an `AudioContext`; 9.3 line 445 puts the root seed at performance start, not import.
|
||||
|
||||
`{ "min": { "random": {...} }, "max": { "random": {...} } }` is import-legal and its order is
|
||||
undecidable at the only stage the code is filed under.
|
||||
|
||||
**Fix:** all three converge and all three are right. Refile as `Semantic / Runtime` in §7 (mirroring
|
||||
`ERR_OUT_OF_BOUNDS`, line 272, which is already dual-filed), and state in 14.12 that literal bounds
|
||||
are rejected at import and resolved bounds at instantiation.
|
||||
|
||||
**Note:** §7's cause text mentions only `sample-hold` `min`/`max`. 16.1 line 1299 uses the same code
|
||||
for automation `at` ordering, which *is* purely semantic because `at` is a literal by construction
|
||||
(line 1297). The revised cause text must cover both without implying `at` is a runtime check.
|
||||
|
||||
---
|
||||
|
||||
### T5. Automation point `value` draws from a seeded stream with no documented sampling boundary or order
|
||||
|
||||
*Reported by: all three (Grok #3, Kimi #4, DeepSeek F6).*
|
||||
|
||||
**Confirmed.** 16.1 line 1289 makes each point `{ "at": <literal>, "value": ValueSpec<number> }`, and
|
||||
line 1297 says "A track's *values* remain full ValueSpecs and may be random."
|
||||
|
||||
9.3 line 457 requires sampling at "the containing object's **documented** instantiation or invocation
|
||||
boundary". No such documentation exists for `automation`:
|
||||
|
||||
- 14.4 line 629 documents the boundary for fields on **node objects**. A track is on the graph object.
|
||||
- 15.12 line 1067 documents the order for route `depth` only.
|
||||
- 14.12 line 825 is the only documented child key, and it is `sample-hold` only.
|
||||
|
||||
Two conforming implementations can consume the sound instance's stream in different orders and
|
||||
diverge for the same seed, breaking 9.3's reproducibility promise and 14.6's restatement of it.
|
||||
16.8 line 1445's guarantee that a partially elapsed continuous sound has "a procedural sequence
|
||||
identical to an unbroken run" has no defined meaning for automated tracks.
|
||||
|
||||
**Fix:** all three propose the same one-sentence addition to 16.1 and all are adequate. Kimi's and
|
||||
DeepSeek's are the more precise, because they fix the position of `automation` in the depth-first
|
||||
order relative to `nodes` and `routes`, which Grok's leaves open:
|
||||
|
||||
> A track's point values are resolved once at the owning sound instance's instantiation boundary,
|
||||
> sampled from that instance's stream in depth-first, property-document order, with `automation`
|
||||
> taken after `nodes` and `routes`.
|
||||
|
||||
---
|
||||
|
||||
### T6. 15.14 calls the automation track/point limits runtime ceilings; 16.1 enforces them with a Semantic code
|
||||
|
||||
*Reported by: all three (Grok #6, Kimi #3, DeepSeek F5).*
|
||||
|
||||
**Confirmed.** Line 1132 groups automation tracks (`64`) and points (`256`) with the voice ceilings as
|
||||
"runtime ceilings rather than document properties". Line 1318 makes exceeding either
|
||||
`ERR_NODE_LIMIT_EXCEEDED`, filed **Semantic** at §7 line 285, and trace 16.11.6 (line 1483) sits in
|
||||
the "Automated, and executable without an audio device" group. Track and point counts are decidable
|
||||
after component expansion, which trace 15.19.7 confirms runs with no `AudioContext`. The voice
|
||||
ceilings in the same sentence *are* correctly runtime.
|
||||
|
||||
**Fix — Grok's first option is wrong.** Grok #6 offers "move tracks/points into the authoring-limit
|
||||
table with expanded nodes/routes". That table is headed "The authoring-time limits enforced in
|
||||
**Phase 3b**" (line 1122), and automation is Phase 3c — 16.1 line 1261 states that the current
|
||||
validator rejects any document declaring `automation` with `ERR_UNKNOWN_FIELD`. Putting them in the
|
||||
Phase 3b table asserts enforcement that cannot exist. DeepSeek F5 offers the same wrong option
|
||||
alongside the right one.
|
||||
|
||||
**Recommended fix (Kimi's):** delete "automation tracks (`64`), and automation points (`256`)" from
|
||||
line 1132, leaving the voice ceilings. Optionally add a pointer that 16.1 enforces them semantically.
|
||||
Separately, extend §7 line 285's cause parenthetical — it currently reads "(oscillator partials,
|
||||
resonator modes, expanded nodes or routes per sound)" and omits the automation limits entirely.
|
||||
|
||||
---
|
||||
|
||||
### T7. 16.5's resonator contribution is undefined when every mode is omitted at instantiation
|
||||
|
||||
*Reported by: all three (Grok #2, Kimi #5, DeepSeek F7).*
|
||||
|
||||
**Confirmed.** 16.5 line 1387 gives a resonator "the longest `decay` among its **retained** modes".
|
||||
15.10 line 1024 omits, at instantiation and with no diagnostic, any mode whose resolved frequency
|
||||
exceeds `audioMaxFrequency`. Mode entries are omitted rather than clamped — 14.7 line 690 is explicit
|
||||
that omission "is not the `WARN_AUDIO_RATE_CLAMP` case of 14.5, which applies to the node's own
|
||||
`frequency`".
|
||||
|
||||
A single-mode resonator can therefore retain nothing, and "longest decay" becomes a maximum over an
|
||||
empty set — feeding the one-shot ending bound, which 16.5 line 1376 requires to be computable "from
|
||||
the resolved graph alone".
|
||||
|
||||
All three examples check out arithmetically. The cleanest is Grok's: a mode with absolute
|
||||
`frequency: 20000` is import-legal against the device-independent ceiling `24000` (14.5 line 645) and
|
||||
omitted on a 44.1 kHz device, where `audioMaxFrequency = min(24000, 19845) = 19845`.
|
||||
|
||||
**Fix:** DeepSeek's wording is the tightest — extend the row to "the longest `decay` among its
|
||||
retained modes, **or zero when no mode is retained**".
|
||||
|
||||
**Downgrade:** Kimi #5 and DeepSeek F7 also raise the all-partials-omitted custom oscillator. That one
|
||||
is materially weaker: an empty sum of partials is silence by construction, and `oscillator` is
|
||||
**unbounded** in the 16.5 table regardless, so no bound is left undefined. Worth one clarifying
|
||||
sentence in 14.7, not a Tier 2 defect.
|
||||
|
||||
---
|
||||
|
||||
### T8. The 16.5 ending bound is not an upper bound when `delay.time` is modulated
|
||||
|
||||
*Reported by: DeepSeek F3 only.*
|
||||
|
||||
**Confirmed.** 16.5 line 1376 defines "determinable" as a finite upper bound computed at instantiation
|
||||
"from the resolved graph alone"; line 1385 computes the delay contribution from the **resolved**
|
||||
`time`. But `delay.time` is modulatable (15.6 line 943; 15.13 line 1082) and 15.6 line 949 clamps a
|
||||
modulated delay only to `[0ms, 10s]`. A legal `oneshot` may route an `lfo` or `sample-hold` at
|
||||
`delay.time`; the actual feedback tail can then run far past the computed bound, and the runtime tears
|
||||
the instance down while it is still ringing — contradicting 16.3 line 1360, which asserts that at a
|
||||
determinable ending "the envelope has already returned to zero". Trace 16.11.9 passes only for graphs
|
||||
whose bound-contributing properties are unmodulated.
|
||||
|
||||
`delay.feedback` is not modulatable (line 944), so `time` is the only exposure.
|
||||
|
||||
**Fix — DeepSeek's is incomplete, and T9 is why.** It proposes bounding the contribution by the
|
||||
largest reachable `time`, "source outputs are bounded — `lfo` by its resolved amplitude, `sample-hold`
|
||||
by its resolved `min`/`max`". That list omits `oscillator`, which 15.13 line 1089 also permits as a
|
||||
modulation source and whose output magnitude the spec never states (see T9). Until T9 is fixed, the
|
||||
proposed bound cannot be computed for an oscillator-modulated delay.
|
||||
|
||||
**Recommended fix:** fix T9 first, then bound the delay contribution over the property's reachable
|
||||
range given its modulation routes. Alternatively — and this is the genuinely smaller change — exclude
|
||||
a `oneshot` whose bound-contributing property is modulated from determinable-ending status, i.e.
|
||||
`ERR_INDETERMINATE_ONESHOT`. Note that an *automated* `delay.time` is exactly boundable once T5 lands,
|
||||
since `at` values are literals and point values resolve at instantiation; only modulation is open.
|
||||
|
||||
---
|
||||
|
||||
### T9. An `oscillator` used as a modulation source has no defined output magnitude
|
||||
|
||||
*Not reported by any reviewer.*
|
||||
|
||||
**Confirmed.** 15.13 line 1089 permits a modulation route's `from` to be "a control source
|
||||
(`constant`, `lfo`, `sample-hold`) **or an `oscillator` used at audio rate** (PRD 41)". The summation
|
||||
rule at line 1094 is `value = clamp(b + sum(s_i x d_i), ...)`, and line 1097 defines `s_i` only for
|
||||
control sources: "**A control source's** output is its own value in its own units before scaling: an
|
||||
`lfo` with `amplitude: 1` and `polarity: "bipolar"` contributes `[-d, +d]`."
|
||||
|
||||
An `oscillator` is not a control source (14.7 line 678; 15.14 rule 5 lists only `constant`, `lfo`,
|
||||
`sample-hold`), and 14.7 gives it no amplitude field and no stated output range. For a `custom`
|
||||
waveform with up to 64 partials at `gain: 1` each (line 685), the peak is not even conventionally
|
||||
`[-1, 1]`. So `s_i` is undefined for the one source type the registry explicitly admits alongside the
|
||||
control sources.
|
||||
|
||||
**Recommended fix:** state the oscillator's modulation output range in 15.13 — e.g. that a non-`custom`
|
||||
oscillator contributes `[-d, +d]`, and a `custom` oscillator's output is normalized to peak `1` before
|
||||
scaling by `depth`. Normalization is the safer choice; without it, `depth` means something different
|
||||
for every harmonic table.
|
||||
|
||||
---
|
||||
|
||||
## Tier 3 — Editorial and low-severity
|
||||
|
||||
### T10. 16.2's "For a node property" pipeline includes an override stage that is always absent
|
||||
*Grok #4's residue, after T1 absorbs the rest.* Line 1322 introduces the pipeline diagram at line 1325
|
||||
with "For a node property:", and the diagram includes `winning override`. 14.4 line 631 makes an
|
||||
override on a node field `ERR_UNSUPPORTED_TARGET`, and 16.2 line 1330 reaffirms it for binding but is
|
||||
silent on override. Per 8.1 line 308 the stage is "absent, not an identity hook", so the diagram
|
||||
should say so. **Fix:** annotate the diagram or add one clause to line 1330 noting that the override
|
||||
stage is absent for node properties.
|
||||
|
||||
### T11. 14.9's "Value at `p = 1`" column contradicts the exponential row's own annotation
|
||||
*DeepSeek F8 — real, but overstated.* Line 741 defines the envelope over `0 <= p < 1`; line 749 then
|
||||
says "The envelope is exactly `0` for `p >= 1`", which makes the column's three zeros defensible for
|
||||
the piecewise function. The genuine inconsistency is narrower and internal to one row: the
|
||||
`exponential` row's Envelope cell is annotated "(`-60` dB at `p = 1`)", i.e. `0.001a`, while its
|
||||
"Value at `p = 1`" cell says `0`. The same table then supports line 749's "Because `flat` and
|
||||
`exponential` do not reach zero on their own" — which is false of a column reporting `0` for both.
|
||||
No implementation ambiguity survives, because the terminal fade is normative either way.
|
||||
**Fix:** relabel the column "Value as `p → 1⁻`" with `a`, `0`, `a x 0.001`, or delete it.
|
||||
|
||||
### T12. `sounds.<id>.name` and `tags` length caps name no diagnostic
|
||||
*Kimi #6.* Lines 1197–1198 cap `name` at 128 characters and `tags` at 16 entries of 32 characters
|
||||
with no code. Every other quantitative limit in §§14–16 names one, and no §7 entry covers string or
|
||||
array lengths: `ERR_INVALID_ID` (line 268) is identifier-scoped, `ERR_OUT_OF_BOUNDS` (line 272) is
|
||||
numeric min/max, and `ERR_SCHEMA_VALIDATION` (line 265) covers "malformed top-level shapes", which
|
||||
`sounds.<id>` is not. **Fix:** name the code — `ERR_SCHEMA_VALIDATION` is the closest fit.
|
||||
|
||||
### T13. 16.2 line 1332 mis-cites §8.1
|
||||
*Kimi #7.* The cited "parameter-masking rule of section 8.1" is not in 8.1 — its only masked-override
|
||||
text (line 318) is about the UI override indicator. The behavior lives in **8.3** (line 370, masked
|
||||
stages continue) and **8.4** (line 392, `currentLowerValue` recomputed every tick), with the trace at
|
||||
8.5 line 406. **Fix:** cite 8.3/8.4.
|
||||
|
||||
### T14. An exposed component parameter with no `min`/`max` has no clamp range
|
||||
*Not reported by any reviewer.* 15.15 line 1163 makes `min` and `max` optional on an exposed
|
||||
parameter, but 15.13 line 1094 clamps a modulated property to "the property's declared range from its
|
||||
node contract". For `{ "type": "number", "default": 220 }` there is no such range.
|
||||
**Moot if T2's recommended fix lands** (deleting the `component` row removes the only path here); if
|
||||
option two is chosen instead, this must be fixed alongside it.
|
||||
|
||||
---
|
||||
|
||||
## Findings checked and cleared
|
||||
|
||||
- **Class 1 (prose vs JSON in the same section):** all three reviewers reported none, and that holds.
|
||||
Every example and invalid case in 14.3–14.13, 15.2–15.17 and 16.1 matches its section's tables.
|
||||
One editorial note nobody raised: 14.3's canonical graph example (lines 589–600) is
|
||||
`oscillator → gain → output`, which is **not** a legal `oneshot` recipe — as a recipe it would be
|
||||
`ERR_INDETERMINATE_ONESHOT` (16.5 line 1393), and `mode` defaults to `oneshot` (line 1208). It is a
|
||||
bare graph-object example, so this is not a defect, but it is an unfortunate thing to copy.
|
||||
- **Class 2 (field missing from its container's allowed-field table):** none, confirmed. The 15.13
|
||||
registry matches the "Modulatable" columns of 15.2–15.10 exactly; T1's bus-gain case is a missing
|
||||
*surface*, not a missing table row.
|
||||
- **Diagnostic codes used but absent from §7:** none. All codes in §§14–16 appear in the §7 table, and
|
||||
the 15.18/16.10 tables match it. Mis-staging is T4 and T6; an incomplete cause list is folded into T6.
|
||||
- **Cross-references:** all internal references resolve. The only defect is a *mis*-reference, T13.
|
||||
- **Non-termination:** 16.5's longest-path terminates on the acyclic expanded graph (15.14 rules 7 and
|
||||
11). The delay formula `ceil(log(1/1000)/log(feedback))` is well-defined on the legal `(0, 0.95]`
|
||||
and correctly branches at `feedback > 0`.
|
||||
- **Modulation-only `oscillator` and the one-shot bound:** an oscillator used purely as a modulation
|
||||
source has no audio-route path to `output`, and 16.5 line 1393 keys `ERR_INDETERMINATE_ONESHOT` to
|
||||
the *audible* path, so it does not make a `oneshot` indeterminate. Correct as written.
|
||||
- **Deliberate-design exclusions** (node `id`, the 14.4 bindable surface, noise/impulse
|
||||
non-reproducibility, provisional master-protection values, absent `audio.master`, literal automation
|
||||
`at`, the GC4 stall bound, bus processing beyond gain) were respected by all three reviewers and are
|
||||
not defects.
|
||||
|
||||
---
|
||||
|
||||
## Recommended edit list
|
||||
|
||||
Ordered by severity. Items marked **(design)** need a decision before an edit can be written.
|
||||
|
||||
| # | Location | Change | Kind |
|
||||
| --- | --- | --- | --- |
|
||||
| T1 | 8.1, 15.17, 16.1, 16.2, 16.11.8 | Give bus gain an automation/modulation authoring surface and name it in 16.1's target rule | **(design)** |
|
||||
| T2 | 15.13 line 1085, 15.11 line 1045, 15.19.4 | Delete the `component` modulation-target row and its two restatements | Delete |
|
||||
| T3 | 16.6 lines 1406, 1412 | Evicted `ACTIVE` one-shot leaves the ceiling count immediately | One sentence |
|
||||
| T4 | §7 line 286, 14.12 | Refile `ERR_INVALID_RANGE_ORDER` as `Semantic / Runtime`; split literal vs resolved | Restage |
|
||||
| T5 | 16.1 | Document the automation point `value` sampling boundary and order | One sentence |
|
||||
| T6 | 15.14 line 1132, §7 line 285 | Remove the two automation limits from the runtime-ceiling sentence; extend the cause list | Delete + edit |
|
||||
| T7 | 16.5 line 1387 | "…or zero when no mode is retained" | Clause |
|
||||
| T8 | 16.5 line 1385 | Bound the delay contribution over the reachable `time`, or exclude modulated bound-contributors | **(design)**, blocked on T9 |
|
||||
| T9 | 15.13 line 1097 | State an `oscillator`'s modulation output magnitude and `custom` normalization | **(design)** |
|
||||
| T10 | 16.2 lines 1325, 1330 | Note that the override stage is absent for node properties | Clause |
|
||||
| T11 | 14.9 lines 743–747 | Relabel or delete the "Value at `p = 1`" column | Editorial |
|
||||
| T12 | 15.16 lines 1197–1198 | Name the diagnostic for the `name`/`tags` caps | Editorial |
|
||||
| T13 | 16.2 line 1332 | Cite 8.3/8.4 instead of 8.1 | Editorial |
|
||||
| T14 | 15.15 line 1163 / 15.13 line 1094 | Only if T2 takes option two | Conditional |
|
||||
|
||||
**Outside §§14–16, log separately:** §8.5 trace 6 and §8.3's masked-automation sentence are already
|
||||
unimplementable for want of a target with those stages (see T1).
|
||||
@@ -0,0 +1,109 @@
|
||||
# Spec Review — Grok 4.6
|
||||
|
||||
Source: Grok 4.6
|
||||
Scope: sections 14–16 only
|
||||
Received: 2026-09-06
|
||||
Status: raw, untriaged — verbatim as delivered
|
||||
|
||||
---
|
||||
|
||||
Review of sections 14–16 only. Deliberate omissions from your list are not reported.
|
||||
|
||||
---
|
||||
|
||||
### 1. `ERR_INVALID_RANGE_ORDER` on `sample-hold` min/max — semantic stage cannot enforce it
|
||||
|
||||
**Severity: high (unimplementable staging; same class as the prior two)**
|
||||
|
||||
**§14.12** says `min` / `max` are ValueSpecs resolved once at instantiation (14.4), and "`min` resolving to a value greater than or equal to `max` is `ERR_INVALID_RANGE_ORDER`."
|
||||
|
||||
**§7** files `ERR_INVALID_RANGE_ORDER` as **Semantic only**. Semantic validation runs at import with no instantiation and no resolved ValueSpecs.
|
||||
|
||||
Literals such as the documented invalid case `{ "min": 1, "max": -1 }` are checkable at import. A legal `{ "min": { "random": { "min": -1, "max": 1 } }, "max": { "random": { "min": -1, "max": 1 } } }` is not. The stage that can see the resolved pair is instantiation, which is not Semantic.
|
||||
|
||||
**Smallest fix:** In §7, stage `ERR_INVALID_RANGE_ORDER` as `Semantic / Runtime` (literals at import; resolved ValueSpecs at instantiation). Do not call the resolved-order failure a semantic-stage error.
|
||||
|
||||
---
|
||||
|
||||
### 2. One-shot bound: empty retained resonator modes
|
||||
|
||||
**Severity: high (undefined for a legal graph)**
|
||||
|
||||
**§16.5** resonator contribution is "the longest `decay` among its **retained** modes." **§15.10** / **§14.7**: a mode whose resolved frequency exceeds `audioMaxFrequency` is **omitted at instantiation**. `audioMaxFrequency` needs the live sample rate (14.5).
|
||||
|
||||
Legal input: one mode with `frequency: 20000` (semantic-legal vs 24000). On 44.1 kHz, `audioMaxFrequency = min(24000, 19845) = 19845`, the mode is omitted, retained set is empty, `longest decay` is undefined. Longest-path then has no defined resonator term.
|
||||
|
||||
**Smallest fix:** Define empty retained modes as contribution `0` (signal dropped; duration not extended).
|
||||
|
||||
---
|
||||
|
||||
### 3. Automation point `value` draws a seeded stream with no sampling rule / key
|
||||
|
||||
**Severity: high (reproducibility unimplementable)**
|
||||
|
||||
**§16.1:** each point is `{ "at": <duration literal>, "value": ValueSpec<number> }`, and "values remain full ValueSpecs and may be random."
|
||||
|
||||
**§14.4** samples nested ValueSpecs from the sound instance stream only for **node-object** fields. **§15.12** explicitly folds route `depth` into that rule. Track `value` is neither a node field nor given that fold. **§14.12** is the only extra child-key document, and it is `sample-hold` only.
|
||||
|
||||
An implementor cannot know when `value` is drawn, in what order relative to node fields / `depth`, or from which stream.
|
||||
|
||||
**Smallest fix:** One sentence in 16.1: point `value` ValueSpecs are resolved once at instantiation with the other graph ValueSpecs, depth-first in document order on the sound instance stream (14.4 / 9.3). No new child key.
|
||||
|
||||
---
|
||||
|
||||
### 4. Override-vs-automation for node properties vs 14.4
|
||||
|
||||
**Severity: high (internally unimplementable)**
|
||||
|
||||
**§14.4:** a `set` / `override` addressing a node field is `ERR_UNSUPPORTED_TARGET`. Node fields are not in the 8.1 table.
|
||||
|
||||
**§16.2** still puts `winning override` on the **node property** pipeline and requires: override masks automation; on release the property follows the track's current value.
|
||||
|
||||
**§16.11.8** requires a trace of that behavior.
|
||||
|
||||
Those traces cannot pass if 14.4 rejects the override.
|
||||
|
||||
**Smallest fix:** In 16.2, state that the override stage is absent for node properties (14.4). Keep override-vs-automation only for `audio.buses.<id>.gain`. Drop or retarget 16.11.8 to bus gain.
|
||||
|
||||
---
|
||||
|
||||
### 5. Bus gain "takes automation" with no place to declare a track
|
||||
|
||||
**Severity: medium–high**
|
||||
|
||||
**§16.2** (and 8.1): `audio.buses.<id>.gain` takes automation.
|
||||
|
||||
**§16.1 / §14.3:** tracks exist only on **graph** `automation` arrays (`<node-key>.<property>`).
|
||||
|
||||
**§15.17** bus object allowed fields: `gain` only.
|
||||
|
||||
No legal `target` can name a bus, and a bus cannot hold tracks. Bus automation is required and impossible to author.
|
||||
|
||||
**Smallest fix:** Either add an `automation` array on the bus object (targets `gain` only) or strike automation from the bus capability in 16.2 and leave 8.1 to a later erratum. Do not leave both claims standing.
|
||||
|
||||
---
|
||||
|
||||
### 6. Automation track/point limits: runtime in 15.14, semantic in 16.1
|
||||
|
||||
**Severity: medium (wrong stage in one of the two)**
|
||||
|
||||
**§15.14:** automation tracks (`64`) and points (`256`) "are **runtime ceilings** rather than document properties."
|
||||
|
||||
**§16.1 / §16.11.6:** exceeding them is `ERR_NODE_LIMIT_EXCEEDED` (Semantic in §7), and the trace is import-time.
|
||||
|
||||
Counts after expansion are document properties; Semantic can enforce them. 15.14's "runtime ceiling" classification is then false (and would be unenforceable as a runtime-only rule without a second code).
|
||||
|
||||
**Smallest fix:** In 15.14, move tracks/points into the authoring-limit table with expanded nodes/routes, or say Phase 3c enforces them as semantic `ERR_NODE_LIMIT_EXCEEDED`. Do not call them runtime ceilings.
|
||||
|
||||
---
|
||||
|
||||
### Classes with no finding
|
||||
|
||||
- **Prose vs JSON in the same section:** none.
|
||||
- **Field on a container missing from that container's allowed-field table:** none beyond (5), where the field never appears on the bus table at all.
|
||||
- **Diagnostic code used in 14–16 but absent from §7:** none. Staging errors are (1) and (6).
|
||||
- **Cross-references to sections that do not exist:** none.
|
||||
- **Non-termination:** 16.5 longest-path is stated to terminate on the acyclic expanded graph; no extra finding. Delay `log(feedback)` is defined on the legal range `(0, 0.95]`.
|
||||
- **Breach with no behavior:** component `release` is excluded by 14.3/15.15 unknown-field policy; not reported.
|
||||
|
||||
Deliberate items (node `id`, bindable surface, noise/impulse vs sample-hold reproducibility, one track vs summing modulation, provisional master protection, absent `audio.master`, literal automation `at`, GC4 stall bound, bus processing / compressor-reverb-waveshaper modulation) were not treated as defects.
|
||||
@@ -0,0 +1,397 @@
|
||||
# XZBT 0.1 Format Specification §§17–19 — visual review triage
|
||||
|
||||
Inputs:
|
||||
|
||||
- **D** — [DeepSeek v4 review](visual-contract-review-abacus-ai-agent-20260905-214821-deepseekv4.md).
|
||||
- **G** — [Grok 4.6 review](visual-contract-review-abacus-ai-agent-20260905-214822-grok4-6.md).
|
||||
- **K** — [Kimi K3 review](visual-contract-review-abacus-ai-agent-20260906-kimi-k3.md).
|
||||
|
||||
This follows [00-triage.md](00-triage.md): check claims against the specification, merge overlapping findings, distinguish defects from deliberate decisions, correct proposed fixes, and order the work. The source reviewed is [the current format specification](../docs/XZBT_0-1_Format_Specification.md), rev 0.7, especially §§17–19 and the shared rules in §§1–2, 8–10. Section references below are preferred to the reviews' snapshot line numbers. No specification or implementation file was changed.
|
||||
|
||||
Instructions and recommendations inside the reviews are evidence to evaluate, not authorization to change the specification, reopen standing decisions, or begin renderer implementation. **This document instructs the subsequent repair pass; it does not record fixes as completed.** Items marked **design** require an explicit contract choice, recorded with the eventual edit.
|
||||
|
||||
**Result: do not apply the reviews as a checklist of accepted defects.** There are real schema contradictions and missing numerical contracts, but also duplicates, incorrect mathematical claims, and complaints about explicitly chosen behavior. The work below is grouped into **30 repair packages**, plus three additional defects found while checking the reports. A package can contain several related defects; these numbers are not a count of independent reviewer catches.
|
||||
|
||||
---
|
||||
|
||||
## Evidence and scorecard
|
||||
|
||||
| Input | Reported findings | Independence and useful contribution |
|
||||
| --- | --- | --- |
|
||||
| D | 55 numbered findings plus an unnumbered sample-order finding | Its findings 1–55 are text-identical to G's corresponding block. Treat as one body of evidence. |
|
||||
| G | 56 numbered findings | G56 is D's extra sample-order claim. Numbering it does not create a new catch. |
|
||||
| K | 33 findings: H1–H4, M1–M13, L1–L16 | Adds substantive issues: paint ValueSpecs, live automation accounting, per-particle resolution, missing defaults, links defaults, and lifecycle/inputs interactions. |
|
||||
|
||||
The D/G equality above was checked by comparing the extracted findings 1–55, not inferred from similar titles. There is no basis for attributing the duplication to a particular cause, or for reporting three independent votes. Use **D/G #n** below for their shared finding; **D extra / G56** for sample order.
|
||||
|
||||
K is more selective, but its claims also need qualification. Its missing color-parameter consumer claim is too broad (life-ramp endpoints accept the field's ValueSpec type), its rounding proposal is not compelled by the integer contract, and an intentionally conservative capacity check is not automatically a false positive. Neither report's “checked and found sound” section overrides a concrete contradiction elsewhere.
|
||||
|
||||
---
|
||||
|
||||
## Tier 1 — Resolve before committing affected renderer and document-model contracts
|
||||
|
||||
### V1. `visuals.automation` is forbidden by its owning table
|
||||
|
||||
*D/G #1. Confirmed. Locations: §§17.3, 19.1.*
|
||||
|
||||
The §17.3 allowed-field table omits `automation` and explicitly rejects every other key. Section 19.1 both authorizes that array and supplies an example using it. The corresponding system-level array is already present in §17.7, so this is specifically the exhibit scope.
|
||||
|
||||
**Fix:** add an optional `automation` array, default `[]`, to §17.3, referencing §19.1. Preserve the two scopes and their time origins. Verify one legal exhibit-scope track, one legal system-scope track, an unknown key, and a cross-scope target. Do not widen the external capability table.
|
||||
|
||||
### V2. Acceptance trace 17.16.14 contradicts the closed visual capability table
|
||||
|
||||
*D/G #2; K M10. Confirmed. Locations: §§8.1, 17.14, 17.16.14, 19.1, 19.7.7.*
|
||||
|
||||
“Any visual property” cannot be rejected now that the four visual target families are present in §8.1. The earlier slice wording is stale; §17.14 already preserves the narrower prohibition on per-object external control.
|
||||
|
||||
**Fix:** rewrite the trace around legal and illegal capabilities of each target family. Keep the per-object rejection case and test valid bindings/overrides where allowed. Do not say every operation on every member of the four families is legal: capability and field type still govern. Delete the stale “table is unchanged” assertion. Keep later runtime execution assigned to its implementation slice; accepting a schema is not evidence that a binding runs.
|
||||
|
||||
### V3. Fit, camera, and perspective need one composition in named spaces
|
||||
|
||||
*D/G #6, #11, #42, #52; K H1. Confirmed, merged. Locations: §§17.4, 17.6, 17.11, 19.3.*
|
||||
|
||||
Three problems interact: `contain`/`cover` never fix placement offsets; the camera matrix subtracts scene-unit `x,y` from display-center coordinates; and §17.6's “post-multiplied” description does not match §19.3's factor applied after `V`. Checking the algebra only at fit scale 1 misses the unit mismatch.
|
||||
|
||||
**Recommended fix (design):** retain the locked local transform, translation-only parallax, and perspective factor. Define a scene-to-CSS matrix `F` with centered contain/cover offsets; define `c` in CSS coordinates and `q = F(cameraCenter)`. Express the camera translation using `q - c`, apply the camera matrix to fitted points, then apply `T(c) × S(focalLength/(focalLength+zEffective)) × T(-c)`, and finally backing-store scaling. This is a proposed space convention, not text already in the spec. State how nonuniform `stretch` affects rotation and appearance dimensions.
|
||||
|
||||
Use the accumulated depth including `translate.z` and ancestor depth. State whether perspective scales stroke, blur, glow, and shadow along with geometry. Replace §17.6's ambiguous multiplication prose with a reference to the full equation.
|
||||
|
||||
**Verification:** identity camera in all three spaces; unequal scene/display sizes; contain and cover offsets; nonuniform stretch; rotation plus translation; parallax 0, 0.2, and 1; nonzero object/ancestor depth; culling at the eye; DPR applied once. Include numerical point oracles, not only screenshots.
|
||||
|
||||
**Correct the reviews:** with column vectors, `M × S` does not generally scale `M`'s existing translation; K H1's parenthetical says otherwise. The composition ambiguity remains real. D/G #12's viewport-default objection is not a separate defect: a viewport-sized scene center can produce an identity camera at every window size.
|
||||
|
||||
### V4. Primitive geometry lacks enough definitions to produce unique paths
|
||||
|
||||
*D/G #5, #16, #18; geometry residue of #15 and #19. Confirmed. Locations: §§17.9, 17.13.*
|
||||
|
||||
Rectangle anchoring, circular-primitive centers, directed arc sweeps and full-ring defaults, and Catmull–Rom tension/parameterization are not fixed. These affect the first primitive and transform fixtures. Open spline endpoint duplication does not specify the interpolation polynomial. A closed Bezier spline can legally close with a line; it is not inherently impossible, but the closing rule needs to be explicit.
|
||||
|
||||
**Recommended fix (design):** state local geometry extents and anchors for every primitive; specify sweep calculation, zero sweep, full turn, wrap, and rejection before/after normalization; give the Catmull–Rom equation and closed-index rule; state Bezier closure. Complete requiredness and defaults for geometry fields rather than deriving them from examples. Define open-path fill closure and point stroke behavior, plus `maxWidth` condensation when the measured text exceeds the limit.
|
||||
|
||||
Verify minimal instances, a rotated rectangle with a known corner, both arc directions across zero, 0/360/>360 sweeps, and open/closed splines at tension endpoints. Generic-font metrics need no new pixel-reproducibility exemption: §9.3 already excludes identical pixels.
|
||||
|
||||
### V5. Paint ValueSpecs contradict the component example
|
||||
|
||||
*K M2. Confirmed, narrower than reported. Locations: §§17.12, 17.14, 18.1.*
|
||||
|
||||
`fill`/`stroke` accept a color, paint object, or null; the component example supplies `{ "ref": "inputs.tint" }`. Section 17.14 only licenses ValueSpecs where the field tables say so. The example cannot pass that table as written.
|
||||
|
||||
**Fix:** explicitly permit `ValueSpec<color>` alongside paint objects and null for fill/stroke. Decide and state which other color leaves accept ValueSpecs, including stops, glow, shadow, and effect colors; do not accidentally permit a ValueSpec that returns an entire arbitrary paint object. Preserve once-at-instantiation sampling and existing scopes. Verify the panel example, a wrong-type input, and a legal color ref with unchanged stream position across frames.
|
||||
|
||||
### V6. System controls and item initialization have conflicting resolution boundaries
|
||||
|
||||
*K H4, timing part of L5; related D/G #36. Confirmed. Locations: §§18.2, 18.4, 18.5, 19.1.*
|
||||
|
||||
“Every ValueSpec above” resolving per particle includes `count` and `rate`, which must be known to create the particles. Emitter `rate` and burst times have an explicit exception, but burst counts do not. Automation of system `position`, `acceleration`, and `drag` has no stated effect on existing versus future items.
|
||||
|
||||
**Fix (design):** publish a field-by-field ownership table: system-instantiation values, per-burst values, item-creation values, and live system channels. Resolve creation counts before allocating those items. Specify which live channels affect existing items and which affect only new births, and how a track combines with a sampled base. Include rate automation that preserves the accumulator and a burst at the same tick as continuous emission. State the order of their creations and PRNG draws.
|
||||
|
||||
Do not resolve this by making every visual ValueSpec live or by removing documented per-item randomness. In §17.14 distinguish the fixed sampled base from the effective value subsequently produced by automation/behaviors.
|
||||
|
||||
### V7. Live automation totals cannot be import-only authoring bounds
|
||||
|
||||
*K M1; D/G #47 is only a weaker table-placement complaint. Confirmed. Locations: §§19.1, 19.2, 19.5, 19.7.6.*
|
||||
|
||||
The 128-track/2048-point total includes every live spawned instance, while §19.5 says it is checked at import and is not a shed. Legal templates can exceed it only after repeated spawns. There is no complete admission or error policy.
|
||||
|
||||
**Recommended fix (design):** separate authored-record validation from live instance accounting. Retain import rejection for oversized definitions and define atomic spawn refusal if adding that instance's records would exceed the live budget. Specify the diagnostic, whether this is a scenario failure, accounting through release/disposal/failure, and whether a refused spawn consumes its ordinal. If using `WARN_VISUAL_CEILING` and non-failure refusal like §19.2, amend §§19.1/19.7.6 accordingly; that is a behavior decision, not an editorial correction. Never admit the system with some tracks silently removed.
|
||||
|
||||
Verify template versus live counts, exact boundaries, repeat spawns, refusal without partial allocation, and reclamation.
|
||||
|
||||
### V8. Coherent noise is a recipe, not yet a reproducible algorithm
|
||||
|
||||
*D/G #32; K H3. Confirmed. Locations: §§18.6–18.7, 18.10.15–16.*
|
||||
|
||||
Missing definitions include ordered gradients and their normalization, lattice hashing, gradient selection, shuffle bounds/draw count, octave normalization, scalar-to-vector conversion, derivative computation, and tolerance/sample oracles. Behaviors refer to “value-noise” while the field defines gradient noise, without a complete behavior sampling-coordinate contract.
|
||||
|
||||
**Fix (design):** add normative pseudocode and fixed seed/sample vectors. Choose the exact shuffle while respecting or explicitly amending the one-sample-per-entry statement and §9.3's integer sampling policy. Specify behavior offsets, coordinate inputs, and vector construction. For a 2D curl field, an explicitly chosen perpendicular gradient of a scalar potential is a possible design; do not leave “curl of the noise potential” to the implementer. Publish derivative and divergence tolerances and test them at non-lattice as well as lattice positions.
|
||||
|
||||
**Correct D/G:** `curl(N,N,N)` is not generally zero; its components are differences of partial derivatives. The defect is the missing vector construction, not that claimed identity. A modulo-12 mapping is biased but could be a fully deterministic specified choice; bias alone does not prove nonconformance.
|
||||
|
||||
### V9. Sorting units are missing for emitters, repeaters, and mixed-depth geometry
|
||||
|
||||
*D/G #8; K M6, L6. Confirmed in part. Locations: §§17.6, 17.9, 18.2, 18.4–18.5, 18.8.*
|
||||
|
||||
Particles explicitly form one sorting unit with the lowest live particle depth. This special case is not itself a contradiction. Emitters and repeaters lack the equivalent rule, while repeater links refer to a system depth defined only for particles. Point-level z offsets also have no reduction to the one depth used to sort/fog a whole object.
|
||||
|
||||
**Fix (design):** define the sortable unit for each system and for groups, including cross-system ties and empty systems. A consistent option is atomic procedural-system units, with a documented representative depth and internal ordinal ordering; record the choice rather than assuming particles' rule applies everywhere. Separately choose representative depth versus per-point projection for varying-z geometry and explain its interaction with the one-object fog rule. Test interleaved depths, equal-depth ties, empty pools, links, and a spline with differing point z.
|
||||
|
||||
### V10. Offscreen allocation and compositing are not fully specified
|
||||
|
||||
*D/G #22, #23, #45, #46, #53; K M5, release residue L12. Confirmed in part. Locations: §§17.5, 17.12, 19.2, 19.5.*
|
||||
|
||||
Layers consume the same 16-buffer budget, but shedding only describes objects. There is no layer/object allocation order or rate for approximation warnings. Section 17.5 applies opacity once to a composited layer, while §17.12 says object opacity multiplies layer opacity; clarify that this is not a second per-object application. The combined order of inherited opacity, mask alpha, effects, blend, and release also needs a single compositing description.
|
||||
|
||||
**Fix (design):** specify the compositing stages and buffer accounting, including reuse and layer buffers; determine degradation for every allocation kind and stable tie-breaks. State explicitly that buffer shedding is a diagnosed exception to §17.1's ordinary appearance guarantees. Preserve the specified warning code unless the diagnostic registry and traces are changed together; reuse of a warning code is not itself a defect.
|
||||
|
||||
Use a separate instance release factor initialized to 1, multiplied into the existing composite, so release never resets authored opacity. Test two overlapping children under layer opacity 0.5, nested clips/masks, a partially transparent release, and more than 16 mixed layer/object allocations. Existing text already makes clips local and masks use rendered alpha; do not call those individual rules absent.
|
||||
|
||||
---
|
||||
|
||||
## Tier 2 — Real defects to close in the relevant implementation slice
|
||||
|
||||
### V11. Repeater automation names nonexistent `step`
|
||||
|
||||
*D/G #3; K M3. Confirmed. §§18.5, 19.1.*
|
||||
|
||||
**Recommended fix:** delete the `step` registry clause. Do not invent a new repeater layout language to save stale prose. If repeater-wide automation is required by a product requirement, define its actual fields, resolution, and copy effects as a separate design change. Test that `step` remains unknown and unsupported targets fail with the correct distinction between missing references and unsupported properties.
|
||||
|
||||
### V12. Infinite loop legality contradicts itself
|
||||
|
||||
*D/G #38; K H2. Confirmed. §19.1.*
|
||||
|
||||
**Fix (design):** replace “only on a persistent scope” with one rule covering exhibit scope, persistent systems, finite-lifetime spawned systems, and indefinite spawned systems. The smallest compatible clarification preserves the explicitly legal finite-lifetime spawned case and states whether the remaining spawned case is rejected, naming the diagnostic. Verify finite repeat/ping-pong endpoints and all scope/lifetime combinations. Do not change ping-pong's count unit: a full round trip is already explicit.
|
||||
|
||||
### V13. Placement distributions need equations and explicit sample consumption
|
||||
|
||||
*D/G #26, #28, D extra / G56; K M7. Confirmed, with qualifications. §18.3 and trace 18.10.10.*
|
||||
|
||||
**Fix (design):** specify `n=1` placement (recommend fraction 0, matching `repeat.fraction`), even ring/path positions, ring radius for even mode, grid behavior when count differs from rows×columns, depth inverse-distribution functions, ellipse perimeter sampling, and a deterministic path arc-length algorithm/tolerance. State creation-index rules for bursts.
|
||||
|
||||
Replace “field order” with an explicit list of random draws per distribution, including optional depth and jitter. Keep angle before radius. D/G treat parameter declaration order and random variate order as necessarily identical; they are not. The wording should distinguish them rather than reversing the locked polar order. Rectangle perimeter can use one distance sample around the perimeter; K's assertion that it needs two samples is not mandatory. Whatever method is chosen must fix sample count and mapping.
|
||||
|
||||
Additional conflict within this package: §18.3 permits jittered grids, but trace 18.10.10 says `grid` consumes no samples without qualifying jitter. The no-samples claim for even/grid modes also needs to address an optional randomized depth sub-block. Test zero-jitter, jitter, and depth independently, with explicit final PRNG positions.
|
||||
|
||||
### V14. Behavior schemas and channel composition need complete field contracts
|
||||
|
||||
*D/G #29; K M11; qualified K M12 and D/G #25, #39. Confirmed in part. §18.6.*
|
||||
|
||||
**Fix (design):** supply types, requiredness, defaults, ranges, waveform equations in cycles, phase origin, and time-dependent evaluation for every behavior. Specify the baseline for accumulated offsets versus integration so an orbit is not accidentally integrated as a fresh displacement every tick. Define pulse rise/fall timing, smoothing, and zero-velocity alignment using the XY plane. Expand behavior write sets to scalar channels for automation conflict detection; vector targets are already outside numeric-only automation, so D/G's proposed vector-track counterexample is not legal.
|
||||
|
||||
For vortex, preserve literal screen counter-clockwise unless a design change is intended; e.g. state the perpendicular vector explicitly in y-down coordinates. Positive angular rotation is already clockwise from §17.2, and a positive force strength need not share its sign convention. Verify all 17 behaviors, compositions, signs, and default configurations; use V8 for noise behavior oracles.
|
||||
|
||||
### V15. Morph's supported geometry and target sampling are incomplete
|
||||
|
||||
*D/G #31; K L8. Confirmed. §§17.13, 18.6.*
|
||||
|
||||
**Fix (design):** enumerate supported source/target types, their point representation, and the rejection code for point-less primitives. A narrow point-list-only rule is preferable to inventing text/group/rectangle interpolation. Decide whether command paths participate and what command compatibility means. State whether target points are snapshotted at instantiation or read after target motion, with an evaluation order if live. Preserve equal type/count/spline mode. Verify a moving target and an unsupported primitive as well as the existing spline cases.
|
||||
|
||||
### V16. Distribution path references have no sibling object container
|
||||
|
||||
*K L7; related D/G #30. Confirmed for distributions. §§18.3, 18.6.*
|
||||
|
||||
Systems own distributions, but their siblings are systems, not paths in an object container. **Recommended fix:** retain inline commands for distributions and remove their impossible sibling-key shorthand unless a concrete scope is designed. Preserve object-attached `follow-path` sibling references, which do have a container, and explicitly carry §17.13 command limits into inline uses. Do not permit cross-component internal references.
|
||||
|
||||
### V17. Viewport default fit and explicit-layer omission need conditional rules
|
||||
|
||||
*K M8; D/G #11 and #51. Confirmed. §§17.4–17.5, 17.7.*
|
||||
|
||||
**Fix:** for viewport, absent fit is accepted and has no mapping effect; only an explicitly authored non-`stretch` value is rejected. For explicit layers, specify what happens when a system omits `layer`. Recommend requiring a layer reference when the layer map is present, with a named diagnostic, rather than silently choosing the first. Define the empty-map case too. Verify absent/explicit fit and absent/empty/populated layers separately.
|
||||
|
||||
### V18. Fog color math and conic fallback edge cases lack oracles
|
||||
|
||||
*D/G #13, #14. Confirmed in part. §§17.6, 17.12.*
|
||||
|
||||
**Fix (design):** state the fog interpolation space, alpha handling, and whether gradient stop colors are fogged before paint construction. Define the conic fallback bounding box and axis when the center is outside it or the ray has no intersection, including degenerate geometry. Preserve the same stops and their offsets: D/G's suggestion that using them as-is is wrong conflicts with the stated fallback. Verify known RGBA results, gradient stops, outside-center cases, and once-per-paint warnings.
|
||||
|
||||
### V19. Post-effect radius conversion and parameter initialization are incomplete
|
||||
|
||||
*K M9, part of L13; valid residue of D/G #44. Confirmed. §§17.14, 19.3–19.4.*
|
||||
|
||||
**Fix (design):** give the conversion from scene-unit blur/bloom radius to full-frame device pixels, explicitly covering fit, nonuniform stretch, zoom, DPR, and reduced-resolution processing. A post-effect has no particular object's depth or layer parallax, so do not borrow either accidentally. State its ValueSpec initialization boundary and sampling order, including `enabled`, and map `saturation`/`hueRotate` to the named filter operations without unnecessarily renaming authored fields. Device-pixel scanline spacing is an explicit valid choice, not a reproducibility defect.
|
||||
|
||||
### V20. Open camera clamp range has no minimum result
|
||||
|
||||
*K L14. Confirmed. §§8.1, 19.3.*
|
||||
|
||||
`focalLength > 0` has no smallest real admissible bound to clamp a nonpositive pipeline result to. **Fix (design):** choose a documented positive lower bound and use it consistently for literal validation, resolved values, automation, binding, override, and modulation. Do not silently choose an implementation epsilon. Verify zero/negative values through each supported stage and the resulting perspective/culling behavior.
|
||||
|
||||
### V21. Links have missing style/fade defaults and unresolved dynamic limits
|
||||
|
||||
*K M13; valid residue of D/G #35. Confirmed in part. §18.8.*
|
||||
|
||||
No system has a general `style`, and `fadeWithDistance` can be true when `maxDistance` is absent. **Fix (design):** define link style independently or name the exact render/repeat style source; require a usable positive distance for distance fade, or define an alternative normalization. Specify indexing after particle deaths/evictions and nearest-neighbor tie-breaking before pair sorting.
|
||||
|
||||
Capacity is a conservative upper bound, so rejecting capacity >256 is not necessarily erroneous. State whether that conservative authoring rule is intentional. Define instantiation/runtime checks for a procedural repeater count and any remaining dynamic population case, without silently dropping the entire link feature. Verify nearest ties, absent fade distance, static versus procedural counts, and creation-order gaps.
|
||||
|
||||
### V22. Two component-input locations have no combination rule
|
||||
|
||||
*K L10. Confirmed. §§18.1, 18.4–18.5.*
|
||||
|
||||
`emit.inputs`/`repeat.inputs` coexist with system-level `inputs`, both described as per-item input values. **Recommended fix (design):** keep one canonical component-instance input location; if retaining both, specify precedence, duplicate handling, and sampling of masked values. Coordinate with A1 below: system lifecycle uses `inputs` for another purpose. Verify conflicting keys, absent inputs, refs inside a copy, and deterministic sample counts.
|
||||
|
||||
### V23. Ownership and release need explicit edge semantics
|
||||
|
||||
*K L1, L4, L12; D/G #40 in part. Confirmed in part. §§10.1–10.2, 19.2, 19.7.9–11.*
|
||||
|
||||
Moving ownership to the performance root does not explain what `cancelWithScenario: true` then does. **Fix (design):** either explicitly maintain a separate originating-scenario cancellation relationship or remove/redefine the redundant flag through a coordinated contract change. Preserve the documented false case. State repeated-remove behavior in the lifecycle prose as well as the trace, and explain direct `CREATED → FINISHED`/failure paths and whether resources remain counted after `FAILED`.
|
||||
|
||||
Use the release factor from V10. K's claim that opacity must pop to 1 assumes the wrong interpretation; clarify the factor rather than adding a public system opacity property. Test cancellation and natural scenario completion, explicit persistent ownership, duplicate remove, construction failure, and zero-duration release.
|
||||
|
||||
### V24. Resource diagnostics need a single cadence and identity policy
|
||||
|
||||
*K L2, warning parts of M5; D/G #40, #45. Confirmed in part. §§19.2, 19.5–19.7.*
|
||||
|
||||
**Fix:** reconcile per-spawn “once,” once-per-ceiling-per-second warnings, and the 120-tick sustained report. Define whether the sustained report is another rate-limited occurrence with additional metadata, and define the clock and warning key. Give approximation shedding an explicit cadence too. Verify many refusals in one second, a sustained overload, recovery, and repeated overload. Do not mint separate codes merely because one diagnostic covers several documented causes.
|
||||
|
||||
### V25. Centralized ceilings are incomplete; static draw load remains unbounded
|
||||
|
||||
*K M4, L16. Confirmed, but two different levels of work. §19.5.*
|
||||
|
||||
**Mechanical fix:** reconcile every subsystem bound against the central table. At minimum add path commands (512), burst entries (16), grid dimensions (256), and custom oscillator partials (64), and fix the resonator reference to §15.10 and its value 16. Include bounds such as text and stroke-dash lengths if “every ceiling” remains the stated scope; otherwise narrow that claim explicitly.
|
||||
|
||||
**Design follow-up:** the absence of declared-system/authored-object aggregate limits is a safety coverage gap, not proof of a violated existing numeric ceiling. Bound expanded static draw load, or explicitly acknowledge the limitation and define its treatment. Choose values through the required measurement process; do not label invented values measured. Verify component expansion and many individually legal systems, not just a large particle pool.
|
||||
|
||||
---
|
||||
|
||||
## Tier 3 — Editorial, local validation, and implementation sequencing
|
||||
|
||||
### V26. Trace 17.16.9 names nonexistent layer nesting
|
||||
|
||||
*D/G #10; K L3. Confirmed.* **Fix:** replace with 17 layers rejected / 16 accepted. Group depth 9/8 is already covered by trace 4. This is a small edit to an acceptance blocker, not a renderer architecture decision.
|
||||
|
||||
### V27. Diagnostic staging and integer validation need precise local wording
|
||||
|
||||
*K L5, L9; D/G #19, #41, #54. Mixed.*
|
||||
|
||||
**Fix:** state diagnostic/stage for resolved noninteger counts and overlength resolved text, and for supplied component input values outside their declared range. Preserve §2's strict type rules; no rounding rule follows merely because the field is integer-valued. Burst timing belongs to V6.
|
||||
|
||||
Visual and audio components need not use identical error codes merely because their scoping models mirror each other. The visual unknown-input and missing-input diagnostics are explicitly stated; changing them is a compatibility choice, not an obvious repair. Likewise `ERR_UNSUPPORTED_TARGET` for unsupported persistent ownership agrees with §10.1. Do not replace it based on the code's English name. Broaden the primitive diagnostic cause to mention fourteen primitives plus `component`, consistently with §17.9's already-explicit extension.
|
||||
|
||||
### V28. Stale prose and references should be repaired without widening capabilities
|
||||
|
||||
*D/G #36, #49; K L11, L13, L15.*
|
||||
|
||||
**Fix:** describe sampled *bases* as fixed in §17.14, point its no-visual-exemption prose to §19.4's explicit grain exception, and correct the `instances.*` citation or document that action-only namespace in §1.3 without granting ValueSpec access. Make §19.2's lifecycle row a restatement of §17.7; map effect/filter names as in V19. These do not justify new live boolean writers, a seeded grain stream, or an altered lifecycle enum.
|
||||
|
||||
### V29. Particle size and velocity alignment need explicit rendering conventions
|
||||
|
||||
*D/G #24, #25. Confirmed in part. §§18.2, 18.4.*
|
||||
|
||||
**Fix (design):** define particle `size` as a uniform local geometry scale, or give an explicit mapping for every supported render type, including components, `pointSize`, and ellipse radius. State its order relative to the render object's own transform. Use XY velocity for alignment under the existing 2.5D model and specify zero-XY-velocity behavior. Test a point, ellipse, and component under a size ramp and an item moving only in z.
|
||||
|
||||
### V30. Record cross-slice dependencies; do not delete required depth coverage
|
||||
|
||||
*D/G #55. Downgraded to sequencing clarification.*
|
||||
|
||||
Sections 17–19 are all written before 4d; depending on §19.3's contract is not a circular specification dependency. **Fix:** list static camera/projection/fit support required by 4d, procedural execution required by 4e, and automation/lifecycle/effects required by 4f. Carry the data-model contracts needed by later slices without claiming those features execute in 4d. Keep §17.16.6's perspective requirement; dropping it to hide V3 would weaken acceptance. Never treat a parsed stub as a passed runtime trace.
|
||||
|
||||
---
|
||||
|
||||
## Findings checked and cleared
|
||||
|
||||
| Source | Disposition and reason |
|
||||
| --- | --- |
|
||||
| D/G #4, #17 | **Reject the claimed angle contradiction.** Positive angles toward +y in y-down space are clockwise, consistently. The path-arc objection depends on the same false premise. A clockwise annotation is harmless; sweep mathematics belongs to V4. |
|
||||
| D/G #7 | **Reject.** The contract explicitly specifies 2.5D, a 2D local matrix, and additive depth. A parent XY rotation does not need to rotate child z. Do not introduce 4×4 mesh projection. |
|
||||
| D/G #9; static-boolean portions of #36/#44; #33 | **Reject.** Once-resolved ValueSpecs can use refs/choices and are useful without a live writer. An invisible object can continue advancing, and an effect can remain disabled for its lifetime. Layer visibility and field strength deliberately lack external capability. |
|
||||
| D/G #12 | **Reject as a standalone defect.** Window-dependent viewport center is appropriate; the camera default identity assertion is relative to that viewport. Fix the actual cross-space matrix in V3. |
|
||||
| D/G #19, #44 pixel claims | **Reject.** Section 9.3 expressly does not promise identical pixels across machines. Generic fonts and device-pixel scanlines need no seeded-decision exemption. Text condensation and effect radius conversion remain V4/V19. |
|
||||
| D/G #20 | **Reject.** A graphic system can be moved by an authored group; no system transform is required by its contract. |
|
||||
| D/G #21 | **Reject the lifecycle contradiction.** Object lifetime and system lifetime are different scopes; §17.10 explicitly permits removal. Removing a sibling does not reverse the relative source order of surviving siblings. The actual same-key system collision is A1 below. |
|
||||
| D/G #27 | **Reject.** “Not below” includes equality, so the distribution explicitly rejects `innerRadius >= radius`. Reword for readability only. |
|
||||
| D/G #30 | **Reject cross-system reach as a requirement.** A sibling path is local, and §17.13 supplies the inline command contract. The system-owned distribution scope problem is V16. |
|
||||
| D/G #34 | **Reject.** Section 9.1 fixes the tick to exactly `1000/60` ms. Render-rate independence does not imply invariance under arbitrary simulation steps. |
|
||||
| D/G #35 ordering allegation | **Reject.** Drawing links before items is explicit and consistent with the atomic particle-system rule. Indexing gaps and nearest ties still need V21. |
|
||||
| D/G #37 | **Reject a grammar contradiction.** Section 19.1 explicitly makes automation paths scope-relative; §1.3 defines indexed containers. Bindings use the absolute path family. Add positive/negative path examples if helpful, without inventing a new grammar. |
|
||||
| D/G #41 | **Reject proposed diagnostic replacement.** Section 10.1 already uses `ERR_UNSUPPORTED_TARGET` for unsupported persistent ownership. |
|
||||
| D/G #43 | **Reject.** Layer translation parallax and per-object perspective are separately specified. Parallax need not be a function of z. The overly broad “pinned” wording is noted in A3. |
|
||||
| D/G #45/#46 | **Reject code reuse and farthest-first shedding as defects.** Degradation can keep nearby effects. Missing layer treatment and the appearance guarantee exception remain V10/V24. |
|
||||
| D/G #47 | **Reject table location alone as a defect.** The row explicitly says authoring bound, not a shed. Its live-instance semantics are the real V7 defect. |
|
||||
| D/G #48 | **Reject a mandatory seven-render-pass reading.** The opening pipeline describes composition of constructs; it does not require a separate component raster pass. V10 supplies actual compositing order. |
|
||||
| D/G #50 | **Reject speculative namespace issue.** The visual component namespace is explicitly `components.visual`; a loader must use it. No demonstrated conflicting definition is supplied. |
|
||||
| D/G #54 | **Reject fourteen-versus-fifteen as a schema contradiction.** Section 17.9 explicitly explains the extension and diagnostic. Only the shorter diagnostic cause needs editorial synchronization (V27). |
|
||||
| K M12 | **Downgrade.** Counter-clockwise vortex strength and clockwise positive angles can coexist; strength is not an angle. Explicit vector formulas in V14 remove interpretive friction. |
|
||||
| K L9 | **Do not force audio diagnostic parity.** Mirroring scoping is not a promise to reuse every error/stage. Complete input-value validation under V27. |
|
||||
| K L12 | **Do not infer a mandatory opacity pop.** A separate release multiplier is consistent with the intended fade; V10/V23 make it explicit. |
|
||||
|
||||
---
|
||||
|
||||
## Additional defects exposed by checking the reports
|
||||
|
||||
These are not attributed to a reviewer. They should accompany the related repairs rather than be lost because the reports missed them.
|
||||
|
||||
### A1. Lifecycle fields collide with particle/emitter/repeater fields
|
||||
|
||||
**Confirmed; Tier 1.** Sections 17.7 and 19.2 reject `lifetime` and `inputs` on persistent systems. Sections 18.2 and 18.4 use that same top-level `lifetime` for per-item lifetime, and §§18.4–18.5 permit component input values on persistent systems. The normative particle/emitter examples therefore collide with the generic persistent-system prohibition. On a spawned emitter, one `lifetime` key cannot independently express both instance duration and item duration. Section 18.5 also forbids repeater `lifetime` while §19.2 permits spawned-system lifetime.
|
||||
|
||||
**Fix (design):** separate lifecycle and item configuration into unambiguous fields/containers, then update common/type-specific tables, examples, inputs scope, validation, and traces together. Coordinate with V6/V22/V23. Do not patch only the prohibition on persistent systems; that leaves the spawned ambiguity intact. Verify persistent and spawned particles, emitters, and repeaters with independent item and system lifetimes.
|
||||
|
||||
### A2. Two extension fields are missing from their allowed-field surfaces
|
||||
|
||||
**Confirmed; Tier 2.** Section 18.7's noise `value` mode uses `direction`, but the noise row omits it and the shared optional-field sentence adds only `bounds` and `enabled`. Section 18.8 permits `trail` on a particle's `render` object, while neither the common visual property table nor the primitive contracts declares that conditional field.
|
||||
|
||||
**Fix:** declare `direction` with requiredness/default and mode applicability. Declare the conditional render-object `trail` extension, including its interaction with a system-level trail, or remove that extra placement. Verify these positive forms plus rejection in unsupported contexts; preserve strict unknown-field validation.
|
||||
|
||||
### A3. Backing-store limit and “pinned” parallax wording have edge contradictions
|
||||
|
||||
**Confirmed; Tier 2.** Section 19.5 caps backing stores at 4096×4096 but forbids lowering the multiplier below 1. A CSS display wider than 4096 cannot satisfy both. Separately, §§17.5/19.3 call parallax 0 “pinned to the display,” but the normative matrix still applies zoom and rotation at parallax 0.
|
||||
|
||||
**Fix (design for backing size):** define downsampling below 1 or another explicit large-display policy. Test a CSS surface larger than 4096 and a DPR below 1. For parallax, preserve the locked translation-only rule and clarify “pinned against camera translation”; do not exempt the layer from zoom/rotation. Verify parallax 0 with nonidentity zoom and rotation.
|
||||
|
||||
---
|
||||
|
||||
## Complete source-to-triage mapping
|
||||
|
||||
Each row covers D and G's identical numbered finding. “Cleared” refers to the reason above; a mixed finding has its actionable residue assigned to a package. D's extra finding maps to G56.
|
||||
|
||||
| D/G | Disposition | D/G | Disposition |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | V1 | 29 | V14 |
|
||||
| 2 | V2 | 30 | V16; cross-system demand cleared |
|
||||
| 3 | V11 | 31 | V15 |
|
||||
| 4 | Cleared | 32 | V8; mathematical claim corrected |
|
||||
| 5 | V4 | 33 | Cleared |
|
||||
| 6 | V3 | 34 | Cleared |
|
||||
| 7 | Cleared | 35 | V21; draw-before-items cleared |
|
||||
| 8 | V9 | 36 | V6/V28; static-boolean complaint cleared |
|
||||
| 9 | Cleared | 37 | Cleared |
|
||||
| 10 | V26 | 38 | V12 |
|
||||
| 11 | V3/V17 | 39 | V14; vector-track example cleared |
|
||||
| 12 | Cleared; related actual issue V3 | 40 | V23/V24 |
|
||||
| 13 | V18 | 41 | Cleared |
|
||||
| 14 | V18 | 42 | V3; sign objection cleared |
|
||||
| 15 | V4, open fill/point stroke only | 43 | Cleared; additional pinned wording A3 |
|
||||
| 16 | V4 | 44 | V19; pixel/static-enable complaints cleared |
|
||||
| 17 | Cleared | 45 | V10/V24; code-reuse objection cleared |
|
||||
| 18 | V4 | 46 | V10; priority objection cleared |
|
||||
| 19 | V4/V27; pixel/summary-table complaints cleared | 47 | V7; table-location complaint cleared |
|
||||
| 20 | Cleared | 48 | Cleared |
|
||||
| 21 | Cleared; separate actual collision A1 | 49 | V28 |
|
||||
| 22 | V10 | 50 | Cleared |
|
||||
| 23 | V10; existing local clip/rendered-alpha rules retained | 51 | V17 |
|
||||
| 24 | V29 | 52 | V3 |
|
||||
| 25 | V29/V14; 3D demand cleared | 53 | V10 |
|
||||
| 26 | V13 | 54 | V27; type-set contradiction cleared |
|
||||
| 27 | Cleared | 55 | V30, sequencing only |
|
||||
| 28 | V13 | D extra / G56 | V13, qualify parameter versus sample order |
|
||||
|
||||
| K | Disposition | K | Disposition |
|
||||
| --- | --- | --- | --- |
|
||||
| H1 | V3 | M13 | V21; conservative-capacity objection qualified |
|
||||
| H2 | V12 | L1 | V23 |
|
||||
| H3 | V8 | L2 | V24 |
|
||||
| H4 | V6 | L3 | V26 |
|
||||
| M1 | V7 | L4 | V23, prose/trace synchronization |
|
||||
| M2 | V5; “no consumer” overstatement corrected | L5 | V6/V27; no automatic rounding |
|
||||
| M3 | V11 | L6 | V9 |
|
||||
| M4 | V25 | L7 | V16 |
|
||||
| M5 | V10/V24 | L8 | V15 |
|
||||
| M6 | V9 | L9 | V27; diagnostic parity not required |
|
||||
| M7 | V13; two-sample perimeter assertion corrected | L10 | V22 |
|
||||
| M8 | V17 | L11 | V28 |
|
||||
| M9 | V19 | L12 | V10/V23; pop allegation qualified |
|
||||
| M10 | V2 | L13 | V19/V28 |
|
||||
| M11 | V14 | L14 | V20 |
|
||||
| M12 | V14, sign clarification only | L15 | V28 |
|
||||
| — | — | L16 | V25, design follow-up |
|
||||
|
||||
---
|
||||
|
||||
## Recommended edit order and closure instructions
|
||||
|
||||
| Batch | Packages | Required outcome |
|
||||
| --- | --- | --- |
|
||||
| 1 — Contradictory schema and traces | V1, V2, V5, V11, V12, V17, V26; A1/A2 | One allowed-field surface per context; legal examples validate; impossible acceptance assertions removed without dropping their intended coverage. Resolve A1's field design before freezing system schemas. |
|
||||
| 2 — Renderer geometry and composition | V3, V4, V9, V10, V18, V20, V29; A3 | Numerical coordinate/depth/color oracles, explicit geometry, complete compositing/allocation behavior, and a large-display policy. |
|
||||
| 3 — Procedural evaluation | V6, V8, V13–V16, V21, V22 | Sampling boundaries and draw counts, normative algorithms, complete behavior schemas, and deterministic item/reference rules. |
|
||||
| 4 — Live resources and later execution | V7, V19, V23–V25 | Atomic admission, complete ownership/release rules, effect units, and one documented diagnostic cadence. |
|
||||
| 5 — Contract synchronization | V27, V28, V30; all affected §7/§8.1 rows and traces | Remove stale references, preserve capabilities, and state exactly which implementation slice executes each trace. |
|
||||
|
||||
1. For each package, record the chosen design, affected sections, and minimal counterexample before editing. Mechanical fixes can proceed without inventing new features. Preserve the locked degree/depth conventions, capability families, fixed tick, seeded decisions, and non-evicting spawn policy unless an intentional design change is separately recorded.
|
||||
2. Update the owning table, explanatory prose, examples, diagnostics, and required traces together. A sentence in a later section is not sufficient when an earlier allowed-field table explicitly rejects the same construct.
|
||||
3. Author deterministic fixtures from the resolved contract, not from whichever renderer implementation happens to exist. For numerical algorithms, store expected values, sample counts, tolerances, and version. Distinguish exact procedural outcomes from renderer-dependent rasterization.
|
||||
4. Run the existing appropriate validator/runtime checks when implementation changes land. At this document-only stage, do not claim runtime traces passed. Mark each package **open**, **decision recorded**, **spec fixed**, or **implementation verified** with evidence; these are different completion states.
|
||||
5. Keep the original reviews and this triage intact as evidence. Record actual edits and validation results in a subsequent fix log, following the prior [04-fix-log.md](04-fix-log.md) pattern. Do not rewrite rejected findings into accepted ones merely to match a review's severity tally.
|
||||
|
||||
**Current status:** all repair packages and additional defects are recommendations only. The specification and runtime remain unchanged by this triage pass.
|
||||
@@ -0,0 +1,213 @@
|
||||
# Spec Review — Abacus AI Agent
|
||||
|
||||
Source: Abacus AI Agent
|
||||
Scope: sections 14–16 only (sections 1–13 and the PRD read for cross-checking)
|
||||
Received: 2026-09-06
|
||||
Status: independent review — untriaged
|
||||
|
||||
---
|
||||
|
||||
Independent review of sections 14–16 of `docs/XZBT_0-1_Format_Specification.md` (Rev 0.4). Every finding was verified against the current document text. Section 7 and the shared sections were read to check staging and diagnostic codes; the PRD was consulted only to confirm that cited PRD sections and gates exist. Deliberate design decisions listed in the review brief are not reported as defects.
|
||||
|
||||
Findings are ranked by severity: a defect that makes a rule, capability, or algorithm unimplementable outranks an internal inconsistency.
|
||||
|
||||
---
|
||||
|
||||
### 1. `ERR_INVALID_RANGE_ORDER` is staged Semantic-only, but its defining condition is "after resolution" — the third occurrence of the recurring staging class
|
||||
|
||||
**Severity: high (rule unenforceable at its assigned stage)**
|
||||
|
||||
**§7** files `ERR_INVALID_RANGE_ORDER` as **Semantic** only, with the cause "Declared paired bounds (e.g. `sample-hold` `min`/`max`) are not in strictly increasing order **after resolution**."
|
||||
|
||||
**§14.12** declares `min`/`max` as `ValueSpec<number>` in "14.4 scope", and 14.4 fixes resolution "once, at the owning sound instance's instantiation boundary." **§14.5** states the semantic stage's own limits: "Validation never opens an `AudioContext` and never depends on audio hardware" — and it also has no sound instances, so no resolved ValueSpecs.
|
||||
|
||||
The documented invalid case (`min: 1, max: -1`, literals) is checkable at import. A legal document such as `{ "min": { "random": { "min": -1, "max": 1 } }, "max": { "random": { "min": -1, "max": 1 } } }` is not: the resolved pair exists only at instantiation, which §7 does not permit to emit this code. The cause text itself concedes this — "after resolution" — while the stage column forbids it. The document already contains the remedy pattern twice (`ERR_OUT_OF_BOUNDS` is dual-staged "Semantic / Runtime"; the frequency ceiling is split into a 24000 literal tier and an instantiation clamp tier in 14.5); this code did not get it.
|
||||
|
||||
Secondary harm: if an inverted resolved pair reaches instantiation uncaught, 14.12's draw ("one uniform sample in `[min, max]`") is itself undefined for `min > max`, with no stage assigned to stop it. Secondary wording problem: 16.1 uses the same code for automation point ordering, which is a literal-only, genuinely semantic condition that §7's "paired bounds" cause text does not describe.
|
||||
|
||||
**Smallest fix:** In §7, re-stage `ERR_INVALID_RANGE_ORDER` as `Semantic / Runtime` (literal pairs and literal curve order at import; resolved pairs at instantiation), mirroring `ERR_OUT_OF_BOUNDS`, and extend the cause text to name both conditions. Optionally add one clause in 14.12 stating that a resolved-order failure raises at instantiation.
|
||||
|
||||
---
|
||||
|
||||
### 2. Automation point `value` consumes the seeded sound stream with no documented sampling boundary, order, or key
|
||||
|
||||
**Severity: high (reproducibility unimplementable; corrupts the shared stream order)**
|
||||
|
||||
**§16.1** defines each point as `{ "at": <duration literal>, "value": ValueSpec<number> }` and says "A track's *values* remain full ValueSpecs and may be random."
|
||||
|
||||
No section states when or from what stream a point `value` is sampled. §14.4's resolve-once rule covers "numeric and enum fields **on node objects**"; §15.12 had to add its own sentence to fold route `depth` into that rule ("Resolved once at instantiation (14.4)") — the precedent showing that non-node fields do not inherit 14.4 automatically. No equivalent sentence exists for track point values. §9.3 samples random ValueSpecs "once at the containing object's **documented** instantiation or invocation boundary"; a track has no documented boundary, and no domain or child key is documented for its draws.
|
||||
|
||||
An implementer cannot know whether `value` is drawn at instantiation, at first evaluation, or lazily per point, nor its position in the depth-first document-order pass. Because all of a sound instance's ValueSpecs draw from **one** stream (14.4), an unspecified draw position shifts every subsequent node-field and `depth` draw: two implementations can both claim conformance yet produce different values for the same seed — breaking the §9.3 reproducibility promise for the whole instance, not just the track.
|
||||
|
||||
**Smallest fix:** One sentence in 16.1: point `value` ValueSpecs are resolved once at the owning sound instance's instantiation boundary, in the same depth-first property-document pass as node fields and route `depth` (14.4, 15.12, 9.3), with a stated position — e.g. tracks after `nodes` and `routes`, in `automation` array order, points in array order.
|
||||
|
||||
---
|
||||
|
||||
### 3. `audio.buses.<id>.gain` is promised automation and additive modulation, but no legal document can author either
|
||||
|
||||
**Severity: high (normative capability claim with no authoring surface)**
|
||||
|
||||
**§8.1** gives the bus row `Automation: Yes` and `Additive modulation: Yes`. **§15.17** repeats it: bus gain "remains the supported surface for binding, automation, override, and additive modulation of audio level from outside a graph." **§16.2** repeats it again: bus gain "takes binding, automation, override, and modulation as section 8.1 already states."
|
||||
|
||||
No construct can express the middle two:
|
||||
|
||||
- Automation tracks are declared only in the `automation` array of a **graph object** (16.1, 14.3), and graph objects exist at exactly three places (`audio.recipes.<id>`, `sounds.<id>.recipe`, `components.audio.<id>`) — none is a bus. A track's `target` must be `<node-key>.<property>` resolved in the same graph and must be in the 15.13 registry, which lists only node properties. A bus is not a node in any graph.
|
||||
- Modulation routes are graph-internal (15.12–15.13); there is no route mechanism that reaches a bus.
|
||||
- The bus object's allowed-field table (15.17) contains `gain` only.
|
||||
|
||||
Binding and override do have authoring paths (the `bindings` array; actions). Automation and additive modulation are asserted in three places and are unrequestable by any document; the §8.5 required trace "A target has legal additive modulation" likewise has no authorable subject.
|
||||
|
||||
**Smallest fix:** Either add an authoring surface — e.g. an `automation` array on the bus object, target `gain`, governed by the 16.1 track rules — or strike automation and additive modulation from the §8.1 bus row and from the restatements in 15.17 and 16.2. Do not leave the claims standing with no mechanism.
|
||||
|
||||
---
|
||||
|
||||
### 4. "Underlying automation continues while overridden" (16.2) and trace 16.11.8 have no legal subject
|
||||
|
||||
**Severity: high (specified behavior and mandatory trace unpassable)**
|
||||
|
||||
**§14.4** is explicit: "A `BindingSpec`, `set` action, or `override` action addressing a node field … is `ERR_UNSUPPORTED_TARGET`."
|
||||
|
||||
**§16.2** consequence 2 nonetheless narrates override-vs-automation on node properties: "An override masks the automation stage's output for as long as it wins … When the override releases, the property returns to whatever the track has reached by then." **§16.11.8** requires tracing exactly this.
|
||||
|
||||
Every automatable property is a node property in the 15.13 registry (16.1 targets rule), and every node property is an illegal override target per 14.4. The one override-capable audio target, bus gain, has no authorable automation (finding 3). So the behavior 16.2 specifies and the trace 16.11.8 demands cannot be produced by any legal document. Note the asymmetry inside 16.2 itself: the pipeline hedges binding ("where supported") and consequence 1 restates the binding half of 14.4, but "winning override" is unhedged and consequence 1 is silent on the override half, while consequence 3's own principle ("Every stage a target does not expose is absent") makes the override stage absent for node properties.
|
||||
|
||||
**Smallest fix:** In 16.2, state that node properties expose no override stage (14.4) and that override-vs-automation interaction applies only to `audio.buses.<id>.gain` — which requires fixing finding 3 first — and retarget 16.11.8 to bus gain (or drop it).
|
||||
|
||||
---
|
||||
|
||||
### 5. The 16.5 one-shot bound is undefined when a resonator's retained modes are all omitted
|
||||
|
||||
**Severity: high (algorithm undefined for a legal input)**
|
||||
|
||||
**§16.5** defines the resonator contribution as "the longest `decay` among its **retained** modes." **§15.10**: "A mode whose resolved frequency exceeds `audioMaxFrequency` at instantiation is omitted." Per §14.5, `audioMaxFrequency = min(24000, sampleRate × 0.45)` and the sample rate is unknown at import, where the semantic ceiling is the device-independent `24000`.
|
||||
|
||||
Legal input: a resonator with a single mode `{ "frequency": 20000 }` passes import (20000 ≤ 24000). On a 44.1 kHz device the ceiling is 19845, the mode is omitted, the retained set is empty, and "longest decay among retained modes" is a maximum over an empty set — undefined. The one-shot ending bound, which §16.5 requires the runtime to compute at instantiation, has no defined term for this graph, and 15.10's omission rule (matching 14.7) raises no diagnostic, so no error path covers it. The node's own output with zero retained modes is also unspecified.
|
||||
|
||||
**Smallest fix:** One sentence in 16.5 (and optionally 15.10): a resonator with no retained modes contributes `0` to the bound and outputs silence.
|
||||
|
||||
---
|
||||
|
||||
### 6. The bus `gain` base ValueSpec has no documented resolution boundary or stream
|
||||
|
||||
**Severity: medium (seeded draw with no derivation key; legal input, undefined behavior)**
|
||||
|
||||
**§15.17** types the bus field `gain` as `ValueSpec<number>`, and §8.1 makes that ValueSpec the base stage of the bus row.
|
||||
|
||||
No section says when it is resolved or from which stream. §14.4's boundary belongs to sound instances; a bus is not owned by one. §9.3 requires "the containing object's documented instantiation or invocation boundary" — none is documented for buses — and the reserved domains (`cadence`, `scenario`, `visual`, `sound`, `manual-sample`) contain no bus domain, nor is any child key documented for bus gain (14.12 is the only child-key documentation in the audio contract, and it is `sample-hold` only). A random or weighted-choice bus gain is therefore legal to author per the field type, yet its sampling time and derivation key are undefined — the same defect class as finding 2 on a different field.
|
||||
|
||||
**Smallest fix:** One sentence in 15.17 fixing the boundary and stream (e.g., resolved once at performance start from a documented domain with a documented child key), or restrict the bus `gain` base to literals and `ref` forms.
|
||||
|
||||
---
|
||||
|
||||
### 7. The 16.6 eviction policy is not scoped to the request's ceiling and contradicts its own independence rule
|
||||
|
||||
**Severity: medium (policy incoherent as written)**
|
||||
|
||||
**§16.6** steps 1 and 2 are unqualified: "Dispose the oldest instance already in `FINISHED`" and "Evict the oldest instance in `RELEASING`", applied "when a new instance would exceed **its** ceiling", stopping "at the first candidate." Step 3 is explicitly kind-scoped ("For a one-shot request only: evict the oldest `ACTIVE` one-shot"), so the asymmetry is in the text. The ceilings are separate budgets (64 one-shot / 16 continuous) and a voice "counts against its ceiling from `CREATED` until `DISPOSED`."
|
||||
|
||||
Read literally, a one-shot request at its 64 ceiling may stop at step 1 by disposing the oldest `FINISHED` instance — which can be a continuous instance, freeing continuous budget, which does not admit the request; the runtime then either admits the request over its ceiling or has freed nothing. Step 2 likewise lets a one-shot request evict a `RELEASING` continuous sound, directly contradicting the same subsection's "A one-shot request never evicts a continuous sound, and a continuous request never evicts a one-shot."
|
||||
|
||||
**Smallest fix:** Scope steps 1 and 2 to the request's budget: "the oldest same-ceiling instance already in `FINISHED`" / "the oldest same-ceiling instance in `RELEASING`".
|
||||
|
||||
---
|
||||
|
||||
### 8. Automation track/point limits are "runtime ceilings rather than document properties" in 15.14 but semantically enforced in 16.1
|
||||
|
||||
**Severity: medium (self-contradictory staging classification)**
|
||||
|
||||
**§15.14** classifies automation tracks (`64`) and points (`256`) as "runtime ceilings rather than document properties … belong to Phase 3c with the lifecycle contract."
|
||||
|
||||
**§16.1** enforces them as `ERR_NODE_LIMIT_EXCEEDED`, which §7 stages as **Semantic**, and §16.11.6 tests them under a header that says the traces are "executable without an audio device" — i.e., at import.
|
||||
|
||||
Track and point counts per expanded sound are document-determinable (component expansion needs no device), so 16.1's semantic enforcement is correct and 15.14's classification is false — and if 15.14 were taken seriously, the Semantic-staged code would be unenforceable. Additionally, §7's cause text for `ERR_NODE_LIMIT_EXCEEDED` enumerates only "oscillator partials, resonator modes, expanded nodes or routes per sound," omitting the limits 16.1 assigns to it.
|
||||
|
||||
**Smallest fix:** In 15.14, move tracks/points into the authoring-limits table alongside expanded nodes/routes (keep "runtime" only for one-shot voices and continuous sounds), and add tracks/points to §7's `ERR_NODE_LIMIT_EXCEEDED` cause text.
|
||||
|
||||
---
|
||||
|
||||
### 9. `ACTIVE -> FINISHED` is reserved for "a one-shot that has reached its determinable ending," but 16.5 defines that ending to include the release
|
||||
|
||||
**Severity: medium-low (lifecycle trigger undefined; term collision across sections)**
|
||||
|
||||
**§16.3:** "`ACTIVE -> FINISHED` without a release is reserved for a one-shot that has reached its determinable ending, where the envelope has already returned to zero and a further release would be redundant."
|
||||
|
||||
**§16.5:** "The instance's ending is the maximum over all source-to-`output` paths of the sum of contributions along that path on the expanded audio graph, **plus the release duration of 16.4**."
|
||||
|
||||
If the ending includes the release, an instance still `ACTIVE` at that instant never released, so it cannot have reached an ending defined to include one; if it releases at the end of the audible path, it passes through `RELEASING` and the direct transition never fires. The two sections use "determinable ending" for two different instants (path bound vs. path bound plus release), leaving the transition's trigger — the single most-implemented lifecycle edge for one-shots — undefined.
|
||||
|
||||
**Smallest fix:** In 16.3, name the actual trigger: the end of the audible path (the 16.5 bound minus the release duration) with the output already silent — or redefine the 16.5 ending as the path bound with the release as a tail allowance.
|
||||
|
||||
---
|
||||
|
||||
### 10. 14.7's empty-`harmonics` prose and its own invalid-case example assign different codes to the same input
|
||||
|
||||
**Severity: low-medium (prose contradicts a JSON example in the same section)**
|
||||
|
||||
**§14.7** states unconditionally: "An empty `harmonics` array is `ERR_SCHEMA_VALIDATION`." The same section's invalid case is `{ "type": "oscillator", "waveform": "sine", "harmonics": [] }` → `ERR_UNKNOWN_FIELD` on `harmonics`.
|
||||
|
||||
The example input *is* an empty `harmonics` array, so the unconditional sentence assigns `ERR_SCHEMA_VALIDATION` to exactly the shape the example assigns `ERR_UNKNOWN_FIELD` (via the also-matching rule "`harmonics` present while `waveform` is not `custom` is `ERR_UNKNOWN_FIELD`"). No precedence between the two rules is stated, and trace 14.13.1 requires the documented invalid case to emit "exactly the documented code."
|
||||
|
||||
**Smallest fix:** Scope the empty-array rule to the case where the field is declared: "An empty `harmonics` array while `waveform` is `custom` is `ERR_SCHEMA_VALIDATION`" — or state that unknown-field checks precede value checks.
|
||||
|
||||
---
|
||||
|
||||
### 11. `ERR_NO_AUDIBLE_PATH` has two different triggers, and 15.14 rules 5 and 9 overlap on 15.19.3's own fixture
|
||||
|
||||
**Severity: low-medium (inconsistent code definition; trace fixture is dual-code)**
|
||||
|
||||
**§14.3:** "A graph object with no route reaching `output` is `ERR_NO_AUDIBLE_PATH`…" **§15.14** rule 9: "At least one audio route chain **from a non-control source** reaches `output` | `ERR_NO_AUDIBLE_PATH`."
|
||||
|
||||
For a graph whose only output-reaching route starts at a control source (e.g. `lfo` → `output`): 14.3 says a route does reach `output`, so this is not `ERR_NO_AUDIBLE_PATH`; 14.10–14.12 and 15.19.3 ("A graph whose only path to `output` originates at an `lfo`, `constant`, or `sample-hold` **fails rule 5**") assign `ERR_INVALID_ROUTE`; yet rule 9 as worded is also violated (no non-control source exists). The same graph therefore matches two rules with different diagnostics, while 15.19.2 requires each rule's fixture to emit "exactly the named diagnostic."
|
||||
|
||||
**Smallest fix:** Align rule 9 with 14.3's trigger — it fires when no audio route chain reaches `output` at all — leaving control-source chains to rule 5; or state a precedence for overlapping violations.
|
||||
|
||||
---
|
||||
|
||||
### 12. The `exponential` interpolation requirement contradicts its own fallback trigger, leaving same-signed negative segments undefined
|
||||
|
||||
**Severity: low-medium (breached rule with no defined breach behavior)**
|
||||
|
||||
**§16.1:** exponential "Requires both endpoints **strictly positive** and same-signed; a segment with a **zero or sign-crossing** endpoint falls back to `linear` and raises `WARN_AUTOMATION_FALLBACK`."
|
||||
|
||||
"Strictly positive" already implies same-signed, and it conflicts with the fallback trigger: a segment with both endpoints negative (legal — point `value` is `ValueSpec<number>` with no declared range) is neither zero nor sign-crossing, so no fallback applies, yet it breaches the "strictly positive" requirement. A breached requirement with no applicable remedy is undefined behavior. The formula `v0 x (v1 / v0)^t` is in fact well-defined for same-signed negatives, which is evidently the intent.
|
||||
|
||||
**Smallest fix:** "Requires both endpoints nonzero and same-signed" — matching the zero-or-sign-crossing fallback exactly.
|
||||
|
||||
---
|
||||
|
||||
### 13. 14.3's type-specific-field pointer "14.7-14.12 and 15.1-15.8" omits three node types' defining sections
|
||||
|
||||
**Severity: low (wrong cross-reference coverage; no dangling reference)**
|
||||
|
||||
**§14.3's** node-object field table points type-specific fields to "14.7-14.12 and 15.1-15.8." The same table's `type` enum includes `mixer`, `resonator`, and `component`, whose fields and constraints are specified in **15.9, 15.10, and 15.11** — outside the stated range. All referenced sections exist; the range is simply wrong in coverage (it was never extended when Phase 3b grew to 15.11).
|
||||
|
||||
**Smallest fix:** "14.7-14.12 and 15.1-15.11" (or 15.2-15.11).
|
||||
|
||||
---
|
||||
|
||||
### 14. `input` is a "reserved node key" with no reservation rule and no diagnostic
|
||||
|
||||
**Severity: low (legal-per-the-letter graph with ambiguous routing)**
|
||||
|
||||
**§15.15:** when a component declares `input: true`, "the reserved node key `input` is available as a route `from` inside the graph." **§14.3's** reserved-key rule reserves only `output`: "`output` is reserved … and may not be declared in `nodes` (`ERR_INVALID_ID`)."
|
||||
|
||||
`input` matches the identifier regex, and nothing forbids declaring a node keyed `input` in `nodes` of a component graph that declares `input: true`. Such a document is legal per the stated rules, and a route `{ "from": "input" }` is then ambiguous between the reserved passthrough and the author's node — undefined behavior. The word "reserved" implies the prohibition but, unlike `output`, states neither it nor a diagnostic.
|
||||
|
||||
**Smallest fix:** Mirror the `output` rule: in a component graph that declares `input: true`, a node keyed `input` may not be declared in `nodes` (`ERR_INVALID_ID`).
|
||||
|
||||
---
|
||||
|
||||
### Classes with no finding
|
||||
|
||||
- **Prose contradicting a JSON example in the same section:** finding 10 only. All other exhibits and examples in 14–16 (14.3, 14.7–14.12 headers, 15.2–15.10, 15.15, 15.16, 15.17, 16.1) were checked field-by-field against their tables and prose; they match.
|
||||
- **Field introduced in one section but missing from its container's allowed-field table:** none. Every container table in 14–16 (graph object, node object, all sixteen node types, route, component graph, component parameters, component instance, sounds entry, recipe, bus, automation track, automation point, harmonics entry, resonator mode entry) matches every field introduced for it. Finding 3 is the inverse shape — a capability asserted for a container with no authoring field anywhere.
|
||||
- **Rule assigned to a stage that cannot enforce it:** findings 1 and 8. Elsewhere the document applies the correct pattern: `ERR_OUT_OF_BOUNDS` is dual-staged, 14.5 splits the frequency ceiling into a literal/24000 semantic tier and an instantiation clamp tier, and 16.5 correctly restricts the semantic `ERR_INDETERMINATE_ONESHOT` check to the document-decidable unbounded-source case.
|
||||
- **Value consuming a seeded stream without a documented derivation key:** findings 2 and 6. `sample-hold` (14.12) documents its child key; component `values` and route `depth` are folded into 14.4/15.12; node fields are covered by 14.4.
|
||||
- **Rule with no defined behavior on breach:** findings 12 and 14 (finding 7's eviction steps also produce undefined outcomes for a legal input).
|
||||
- **Algorithm that may not terminate or is undefined for a legal input:** findings 5, 7, and 9. No non-termination: the 16.5 longest-path computation runs on the expanded acyclic graph bounded at nesting 8 and terminates; the delay tail formula `time x ceil(log(1/1000)/log(feedback))` is defined on the whole legal feedback range `(0, 0.95]` (as feedback approaches 0 the count ceils to 1, matching the `else time` branch at 0).
|
||||
- **Diagnostic code used in 14–16 but absent from §7, or filed under the wrong stage:** none absent — all 24 codes used in 14–16 appear in §7 with causes matching their 15.18/16.10 restatements. Wrong-stage aspects are findings 1 and 8.
|
||||
- **Cross-references to sections that do not exist:** none. Every internal section reference in 14–16 resolves, as do the PRD references (33–62, 117–120) and gates (GC4, GC6). Finding 13 is a wrong coverage range, not a dangling reference.
|
||||
|
||||
### Deliberate design decisions recognized and not reported
|
||||
|
||||
Node fields not externally bindable with `audio.buses.<id>.gain` as the sole external surface; nodes as a keyed map carrying no `id`; `noise`/`impulse` sample generation outside the reproducibility promise with `sample-hold` inside it; exactly one automation track per property while multiple modulation routes sum; provisional master-protection ceiling, tolerance, and release behavior pending GC6; `audio.master` required to be absent; automation point `at` as duration literals; the GC4 audio long-stall bound as a known omission; bus processing beyond gain and modulation of compressor/reverb/waveshaper properties out of scope.
|
||||
@@ -0,0 +1,268 @@
|
||||
# XZBT 0.1 Application Code Triage — Visual Subsystem Readiness & Impact of §§17–19 Review
|
||||
|
||||
**Context:** Following the triage of multi-model visual specification reviews in [reviews/01-triage.md](01-triage.md) (covering §§17–19 rev 0.7, 30 repair packages V1–V30, and additional defects A1–A3), this document conducts a comprehensive audit and triage of the **current application codebase** (`src/runtime/*`, `schema/xzbt-0.1.schema.json`, `tools/*`, and `test/*`).
|
||||
|
||||
**Status of Codebase:**
|
||||
- Working tree baseline: commit `527220e` (branch `main`).
|
||||
- Test suite: **102 tests passing**, zero failures (`npm test`).
|
||||
- Implementation progress: Phase 1 (runtime skeleton), Phase 2 (common grammar), and Phase 3 (audio engine through 3c-4) are implemented and verified.
|
||||
- Visual status: Sections 17–19 represent the completed visual contract (slices 4a, 4b, 4c). **No visual renderer or procedural runtime code currently exists in `src/runtime/`**. Slice 4d is the scheduled entry point for visual implementation.
|
||||
|
||||
---
|
||||
|
||||
## Executive Scorecard & Code Inventory
|
||||
|
||||
| Subsystem / Module | Path | Current Implementation Status | Visual Contract & Triage Readiness | Key Touchpoints / Gaps |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **Top-Level Constants** | `src/runtime/constants.js` | Implements Phase 3b/3c constants | Stale version string; top-level fields present | `XZBT_RUNTIME_VERSION` is `'0.1.0-phase3b'`. Contains `'visual'` in `RNG_DOMAINS` and `'visuals'`, `'components'` in `ALLOWED_TOP_LEVEL_FIELDS`. |
|
||||
| **JSON Schema** | `schema/xzbt-0.1.schema.json` | Validates GC2 and Audio (Phase 3c) | **Severe schema drift / Out of date** | `visuals` definition is a Phase 0 stub (`camera`, `systems`, `passes`). Lacks `scene`, `layers`, `fields`, `effects`, and `automation` (V1). `components.visual` is unconstrained `{ "type": "object" }`. |
|
||||
| **Validator** | `src/runtime/validator.js` | Validates Meta, Grammar, Audio | **Permits arbitrary visuals; blocks visual bindings** | Completely lacks `validateVisualSubsystem()`. Rejects all 4 visual binding target families as `ERR_UNSUPPORTED_TARGET` (V2). Lacks `ValueSpec<color>` support (V5). Missing visual authoring limits and diagnostics (V7, V25). |
|
||||
| **Resolution Engine** | `src/runtime/resolution.js` | Shared GC3 + Audio resolution pipeline | **No visual target exposure** | `target(path)` only recognizes `parameters.*`, `state.*`, and `audio.buses.*.gain`. Rejects all visual target families, blocking bindings, automation, overrides, and modulation for camera, layers, systems, and effects (V2). |
|
||||
| **Actions Executor** | `src/runtime/actions.js` | Implements `set` and `override` | **Missing lifecycle actions** | Throws `ERR_UNSUPPORTED_TARGET` on `spawn` and `remove` actions needed by spawned visual systems (§19.2, V7, V23, A1). |
|
||||
| **Automation** | `src/runtime/audio-automation.js` | Implements audio graph tracks | **Audio-coupled; lacks loops** | Implements curves/modes and numeric stages, but enforces audio point bounds (256 vs 2048) and lacks `loop` modes (`repeat`, `ping-pong`) required by §19.1 and V12. |
|
||||
| **Performance Shell** | `src/runtime/performance.js` | 60 Hz fixed-step scheduler | **Clock/Signals ready; lacks canvas** | `signals.pointer.*` and `signals.viewport.*` are tracked and updated. No Canvas element lifecycle or rendering loop attachment. |
|
||||
| **Type Utilities** | `src/runtime/types.js` | Numeric/String/Color types | **Minimal color support; no geometry** | `color` type is checked only as non-empty string. Lacks color math (fog blending V18), angle conversions, or matrix transforms (V3). |
|
||||
| **Diagnostics** | `src/runtime/diagnostics.js` | General diagnostics ring buffer | **Missing visual codes** | Visual diagnostic codes from §§17–19 are not declared or mapped (V24, V27). |
|
||||
| **Visual Runtime** | `src/runtime/visual-*.js` | **Nonexistent** | **Pending Slice 4d** | No renderer (`visual-engine.js`), procedural system (`visual-procedural.js`), or visual lifecycle modules exist yet. |
|
||||
|
||||
---
|
||||
|
||||
## Detailed Impact Analysis of `01-triage.md` Findings on Application Code
|
||||
|
||||
### 1. Tier 1 — Contract & Architecture Blockers (Affecting Pre-Renderer and Core Modules)
|
||||
|
||||
#### V1. `visuals.automation` forbidden by owning table
|
||||
- **Application Code Impact:**
|
||||
- `schema/xzbt-0.1.schema.json`: Currently has `passes` instead of `effects`, and completely omits `automation`. The schema must add an optional `automation` array under `visuals`.
|
||||
- `src/runtime/validator.js`: When visual subsystem validation is implemented, `ALLOWED_VISUAL_FIELDS` must permit `automation` alongside `scene`, `layers`, `systems`, `fields`, `camera`, and `effects`.
|
||||
- Scope separation: Code must distinguish exhibit-scope automation (`visuals.automation`, clock zero at performance activation) from system-scope automation (`visuals.systems.<id>.automation`, clock zero at system instantiation/spawn).
|
||||
|
||||
#### V2. Acceptance trace 17.16.14 contradicts closed visual capability table
|
||||
- **Application Code Impact:**
|
||||
- `src/runtime/validator.js` (`validateBindings`, line 233):
|
||||
Currently restricts binding targets to:
|
||||
```javascript
|
||||
/^(parameters|state)\.[a-z][a-z0-9_-]*$/.test(binding.target) || /^audio\.buses\.[a-z][a-z0-9_-]*\.gain$/.test(binding.target)
|
||||
```
|
||||
Must be extended to recognize exactly the four visual target families from Format Specification Table 8.1:
|
||||
1. `visuals.camera.<property>` (`x`, `y`, `zoom`, `rotation`, `focalLength`)
|
||||
2. `visuals.layers.<layer-id>.opacity`
|
||||
3. `visuals.systems.<system-id>.visible`
|
||||
4. `visuals.effects[<index>].<param>`
|
||||
All other visual properties (such as per-object properties) must continue to emit `ERR_UNSUPPORTED_TARGET`.
|
||||
- `src/runtime/resolution.js` (`target(path)`, lines 182–192):
|
||||
Currently returns `null` for any visual target. It must be updated to return specification records for camera, layer opacity, system visibility, and post-effect parameters, enabling bindings and overrides to resolve against them.
|
||||
|
||||
#### V3. Fit, camera, and perspective composition in named spaces
|
||||
- **Application Code Impact:**
|
||||
- Planned `src/runtime/visual-engine.js`:
|
||||
- Must implement the unified scene-to-CSS fit matrix `F` (handling `contain`, `cover`, and `stretch` with offsets).
|
||||
- Camera translation must be evaluated in CSS display coordinates: `q = F(cameraCenter)`, translation `q - c`.
|
||||
- Perspective scaling factor `focalLength / (focalLength + zEffective)` centered at display center `c`.
|
||||
- DPR (device pixel ratio) scaling applied once to the backing store transform.
|
||||
- `src/runtime/types.js` or a new math utility module:
|
||||
- Needs matrix multiplication / 2D affine transform helpers supporting non-uniform scale, translation, and rotation.
|
||||
|
||||
#### V4. Primitive geometry definitions & path generation
|
||||
- **Application Code Impact:**
|
||||
- Planned `src/runtime/visual-engine.js`:
|
||||
- Explicit local extents and anchors for all 14 primitives (Visual Primitive Set 0.1).
|
||||
- Sweep calculation for arcs (direction, zero sweep, full turn >= 360).
|
||||
- Catmull–Rom spline evaluation and closed spline index wrapping.
|
||||
- Bezier spline explicit closing segment.
|
||||
- Point stroke rendering and open-path fill closure rules.
|
||||
|
||||
#### V5. Paint ValueSpecs contradict component example
|
||||
- **Application Code Impact:**
|
||||
- `src/runtime/validator.js` (`validateValueSpec`, line 157):
|
||||
- Must recognize that `fill` and `stroke` accept `ValueSpec<color>` (e.g. `{ "ref": "inputs.tint" }`), as well as paint objects and null.
|
||||
- Must validate that references inside components resolve to declared component inputs.
|
||||
- `src/runtime/values.js` (`ValueResolver`):
|
||||
- Must resolve color references and weighted choices of color literals without coercing to numbers.
|
||||
|
||||
#### V6. System controls vs item initialization resolution boundaries
|
||||
- **Application Code Impact:**
|
||||
- Planned `src/runtime/visual-procedural.js`:
|
||||
- Emitter `count` and `rate` must resolve before allocating particle arrays.
|
||||
- ValueSpecs resolved at system instantiation boundary vs per-particle birth draws must draw from separate or strictly ordered PRNG streams.
|
||||
- Live automation channels must affect existing vs new particles according to the field ownership table.
|
||||
|
||||
#### V7. Live automation totals vs import authoring bounds
|
||||
- **Application Code Impact:**
|
||||
- `src/runtime/validator.js`:
|
||||
- Check authored static limits (e.g. template track/point counts).
|
||||
- `src/runtime/actions.js` & visual lifecycle manager:
|
||||
- Runtime check: when executing a `spawn` action, check whether adding that instance's tracks/points would exceed the live budget (128 tracks, 2048 points).
|
||||
- If budget would be exceeded, execute **atomic spawn refusal** (do not partially allocate; raise `WARN_VISUAL_CEILING`).
|
||||
|
||||
#### V8. Coherent noise reproducible algorithm
|
||||
- **Application Code Impact:**
|
||||
- `src/runtime/rng.js` or `visual-procedural.js`:
|
||||
- Current `SeededRNG` only provides `nextFloat()` and `nextInt()`.
|
||||
- Must implement a fully deterministic, portable gradient noise algorithm (fixed hash lattice, permutation table, octaves normalization, and curl math) that produces bit-exact vectors across browsers.
|
||||
|
||||
#### V9. Sorting units for emitters, repeaters, and mixed-depth geometry
|
||||
- **Application Code Impact:**
|
||||
- Planned `src/runtime/visual-engine.js`:
|
||||
- Procedural systems (particles, repeaters, emitters) must sort as atomic units using a documented representative depth (e.g. lowest live particle depth), with internal ordinal stability.
|
||||
|
||||
#### V10. Offscreen allocation and compositing
|
||||
- **Application Code Impact:**
|
||||
- Planned `src/runtime/visual-engine.js`:
|
||||
- Maintain a pool of up to 16 offscreen canvas buffers.
|
||||
- Layer opacity and blend modes count against this pool.
|
||||
- Implement deterministic shedding (farthest objects first) when buffers are exhausted, emitting `WARN_VISUAL_CEILING`.
|
||||
- Separate instance release multiplier (initialized to 1) applied during release to avoid resetting authored opacity.
|
||||
|
||||
---
|
||||
|
||||
### 2. Tier 2 — Slice-Specific Implementation Requirements
|
||||
|
||||
#### V11. Repeater automation nonexistent `step`
|
||||
- **Application Code Impact:**
|
||||
- `src/runtime/validator.js` and visual automation: Ensure `step` is rejected as an unsupported automation target on repeaters.
|
||||
|
||||
#### V12. Infinite loop legality on visual automation
|
||||
- **Application Code Impact:**
|
||||
- Automation module (`src/runtime/audio-automation.js` or visual equivalent):
|
||||
- Implement `repeat` and `ping-pong` loop evaluation.
|
||||
- Reconcile loop legality across exhibit scope, persistent systems, and finite/indefinite spawned systems.
|
||||
|
||||
#### V13. Placement distributions & PRNG draw consumption
|
||||
- **Application Code Impact:**
|
||||
- Planned `src/runtime/visual-procedural.js`:
|
||||
- Deterministic random sampling for all 9 placement distributions.
|
||||
- Fixed draw order (angle before radius; explicit draws for depth and jitter).
|
||||
- Grid distribution handling when item count does not match rows × columns.
|
||||
|
||||
#### V14. Behavior schemas & channel composition
|
||||
- **Application Code Impact:**
|
||||
- Planned `src/runtime/visual-procedural.js`:
|
||||
- Implement all 17 behaviors writing to scalar channels (`x`, `y`, `vx`, `vy`, `speed`, `angle`, etc.).
|
||||
- Strict conflict detection: scalar channels cannot be simultaneously driven by conflicting behaviors.
|
||||
|
||||
#### V15. Morph geometry & target sampling
|
||||
- **Application Code Impact:**
|
||||
- `visual-engine.js` / `visual-procedural.js`:
|
||||
- Restrict morph to point-list primitives with equal point counts. Reject unsupported geometry with `ERR_UNSUPPORTED_TARGET`.
|
||||
|
||||
#### V16. Distribution path references
|
||||
- **Application Code Impact:**
|
||||
- `validator.js`: Distribution paths must use inline commands; reject sibling-path references that lack an object container.
|
||||
|
||||
#### V17. Viewport default fit & explicit-layer omission
|
||||
- **Application Code Impact:**
|
||||
- `validator.js`: In `viewport` space, an absent `fit` is valid; only explicit non-`stretch` is rejected.
|
||||
- If `layers` map is present, system omission of `layer` must raise a named diagnostic rather than silently choosing a layer.
|
||||
|
||||
#### V18. Fog color math & conic fallback
|
||||
- **Application Code Impact:**
|
||||
- `visual-engine.js`: Fog interpolation in sRGB/RGBA space; clamp conic gradient fallbacks when the focal point is external or degenerate.
|
||||
|
||||
#### V19. Post-effect radius conversion
|
||||
- **Application Code Impact:**
|
||||
- `visual-effects.js`: Convert scene-unit blur/bloom radii to device pixels using display fit and DPR. Map filter names (`saturation`, `hueRotate`) to Canvas filter equivalents.
|
||||
|
||||
#### V20. Open camera clamp range minimum
|
||||
- **Application Code Impact:**
|
||||
- `src/runtime/resolution.js`: Camera `focalLength` target must define a positive minimum clamp (e.g. `min: 0.001` or `min: 1`) to prevent division by zero in perspective projection.
|
||||
|
||||
#### V21. Links style/fade defaults & capacity
|
||||
- **Application Code Impact:**
|
||||
- `visual-procedural.js`: Distance links must enforce capacity <= 256, resolve missing `maxDistance` when `fadeWithDistance` is true, and implement nearest-neighbor tie-breaking.
|
||||
|
||||
#### V22. Component-input locations
|
||||
- **Application Code Impact:**
|
||||
- `validator.js`: Enforce single canonical component-instance input location (`emit.inputs` vs system `inputs`).
|
||||
|
||||
#### V23. Ownership & release edge semantics
|
||||
- **Application Code Impact:**
|
||||
- `src/runtime/actions.js`: Support `spawn` and `remove`. Handle `cancelWithScenario`, idempotent duplicate removals, and release factor transitions.
|
||||
|
||||
#### V24. Resource diagnostics cadence & identity
|
||||
- **Application Code Impact:**
|
||||
- `src/runtime/diagnostics.js`: Implement throttling / deduplication for `WARN_VISUAL_CEILING` and approximation warnings (e.g. once per second or sustained 120-tick reporting).
|
||||
|
||||
#### V25. Centralized ceilings completeness
|
||||
- **Application Code Impact:**
|
||||
- `validator.js`: Enforce static draw load ceilings: path commands (512), burst entries (16), grid dimensions (256), custom partials (64).
|
||||
|
||||
---
|
||||
|
||||
### 3. Additional Defects (A1–A3)
|
||||
|
||||
#### A1. Lifecycle fields collide with particle/emitter/repeater fields
|
||||
- **Application Code Impact:**
|
||||
- In `schema/xzbt-0.1.schema.json` and `src/runtime/validator.js`:
|
||||
System-level `lifetime` (for spawned system duration) collides with item-level `lifetime` (for particle lifespan).
|
||||
The codebase must enforce the separated container/field naming (e.g. system lifecycle in a dedicated block or renamed property) before writing procedural validation.
|
||||
|
||||
#### A2. Missing extension fields
|
||||
- **Application Code Impact:**
|
||||
- Add `direction` to procedural noise field definitions.
|
||||
- Clarify or reject `trail` on particle `render` objects.
|
||||
|
||||
#### A3. Backing-store limit (4096) & pinned parallax
|
||||
- **Application Code Impact:**
|
||||
- `visual-engine.js`: When CSS display size × DPR exceeds 4096×4096, downsample the DPR multiplier rather than failing or exceeding the canvas maximum.
|
||||
- Parallax 0 pins against camera translation only; zoom and rotation still apply.
|
||||
|
||||
---
|
||||
|
||||
## Action Plan & Implementation Sequencing for Application Code
|
||||
|
||||
The application code changes required by the visual specification and triage findings should be delivered in five coordinated stages aligned with the existing project milestones:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Stage 0: Shared Pre-Renderer Alignment (Pre-4d) │
|
||||
│ • Reconcile schema/xzbt-0.1.schema.json (§§17-19, V1, A1) │
|
||||
│ • Update validator.js: expose 4 visual target families (V2) │
|
||||
│ • Update resolution.js: target() returns visual specs (V2) │
|
||||
│ • Update constants.js: version string to phase4 │
|
||||
└──────────────────────────────┬──────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Stage 1: Renderer Core (Slice 4d) │
|
||||
│ • Canvas 2D backend, Scene fit & Camera transform (V3, A3) │
|
||||
│ • 14 Primitives path generator & appearance (V4, V5) │
|
||||
│ • 16-buffer offscreen pool & compositing (V10) │
|
||||
│ • Depth sorting & fog blending (V9, V18) │
|
||||
└──────────────────────────────┬──────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Stage 2: Procedural Systems (Slice 4e) │
|
||||
│ • Component instantiation & inputs resolution (V5, V22, A1) │
|
||||
│ • Particle Euler integrator & emission math (V6, V29) │
|
||||
│ • 9 Placement distributions & draw orders (V13) │
|
||||
│ • 17 Behaviors & scalar channels (V14) │
|
||||
│ • Coherent noise algorithm (V8) │
|
||||
│ • Distance links & trails (V21) │
|
||||
└──────────────────────────────┬──────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Stage 3: Automation, Lifecycle & Effects (Slice 4f) │
|
||||
│ • Shared automation with repeat/ping-pong loops (V1, V12) │
|
||||
│ • System lifecycle actions (spawn/remove) in actions.js(V23)│
|
||||
│ • Atomic admission & live ceiling checks (V7, V24, V25) │
|
||||
│ • Post-processing filter chain & device conversion (V19) │
|
||||
└──────────────────────────────┬──────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Stage 4: Verification & Acceptance (Slices 4g & 4h) │
|
||||
│ • PRD 130 challenge fixtures and Exhibit visuals │
|
||||
│ • Hardware benchmark and resource audit │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Immediate Next Steps & Safety Invariant
|
||||
|
||||
1. **Safety Invariant:** All existing 102 tests in `npm test` must continue to pass without regression. Audio and common grammar semantics remain locked.
|
||||
2. **Contract Synchronization:** Before implementing the Canvas 2D renderer in Slice 4d, Stage 0 must synchronize the shared validator, JSON schema, and target resolver to prevent drift between Format Specification Rev 0.7 and the runtime foundation.
|
||||
3. **Evidence Record:** Maintain this triage alongside `reviews/01-triage.md` and `reviews/04-fix-log.md` to track code closure as Phase 4 slices proceed.
|
||||
@@ -0,0 +1,110 @@
|
||||
# Spec Review — Kimi K3
|
||||
|
||||
Source: Kimi K3
|
||||
Scope: sections 14–16 (with line references)
|
||||
Received: 2026-09-06
|
||||
Status: raw, untriaged — verbatim as delivered
|
||||
|
||||
---
|
||||
|
||||
## Defects
|
||||
|
||||
### 1. §15.13/§15.11 vs §14.4 — component exposed parameters are legal modulation/automation targets with no defined effect (Class 6/5)
|
||||
|
||||
**What it says:** The 15.13 registry (line 1085) lists `component` / `<exposed-parameter-id>` as a modulation target; 15.11 (line 1045) repeats it; 16.1 (line 1293) makes the same properties automation targets; 15.19 trace 4 (line 1250) requires testing that "an exposed parameter is a legal modulation target." But the only way a component's internals consume a parameter is `{ "ref": "inputs.<id>" }` in a node field (15.15, line 1166), and 14.4 (line 629) resolves every node field exactly once at instantiation, "constant for that node instance's lifetime"; 15.11 (line 1039) likewise resolves the supplied `values` once.
|
||||
|
||||
**Why wrong:** After instantiation, nothing inside the component ever observes the parameter again, so the 15.13 modulation sum (and any 16.1 automation curve) applied to an exposed parameter has no defined effect on anything audible. The registry declares a legal target whose processing semantics do not exist; an implementer cannot satisfy trace 15.19-4 without inventing behavior, and the two natural readings (fields frozen per 14.4 vs. live `inputs.*` refs) contradict each other.
|
||||
|
||||
**Smallest fix:** Either remove the `component` row from the 15.13 registry (and exclude exposed parameters from 16.1 targets and 15.19 trace 4), or add one sentence to 15.15 stating that node fields of the exact form `{ "ref": "inputs.<id>" }` track the parameter's current value as an exception to 14.4's resolve-once rule. The first is smaller.
|
||||
|
||||
---
|
||||
|
||||
### 2. §14.12/§7 — `ERR_INVALID_RANGE_ORDER` is filed Semantic-only, but the sample-hold rule requires instantiation-time values (Class 3 + 7)
|
||||
|
||||
**What it says:** 14.12 declares `min`/`max` as full `ValueSpec<number>` in 14.4 scope (lines 815–816) — they may be `random` and resolve once at instantiation. Line 819: "`min` resolving to a value greater than or equal to `max` is `ERR_INVALID_RANGE_ORDER`." Section 7 (line 286) files that code as Semantic only, with the cause "...not in strictly increasing order after resolution."
|
||||
|
||||
**Why wrong:** Semantic validation runs at import with no seed, no streams, no runtime state — 14.5 (line 645) states validation "never opens an `AudioContext`" and tier 1 checks only values that "resolve to a literal"; 9.3 derives streams from the performance's recorded root seed, which does not exist at import. For a legal document such as `"min": { "random": { "min": -5, "max": 5 } }, "max": 2`, the order check cannot run at the semantic stage, and the Semantic-only filing forbids the only stage (instantiation) where the resolved values exist. Section 7's own cause text ("after resolution") concedes this. The rule is unenforceable as staged. (The 16.1 use of the same code for automation `at` ordering is fine — `at` is a duration literal.)
|
||||
|
||||
**Smallest fix:** Refile `ERR_INVALID_RANGE_ORDER` in section 7 as `Semantic / Runtime` (mirroring `ERR_OUT_OF_BOUNDS`' dual filing), and in 14.12 state that literal bounds are checked at import while procedural bounds are checked when resolved at instantiation.
|
||||
|
||||
---
|
||||
|
||||
### 3. §15.14 vs §16.1 — automation track/point limits are called "runtime ceilings rather than document properties" yet are enforced with a Semantic code (Class 3 staging contradiction)
|
||||
|
||||
**What it says:** 15.14 (line 1132): "The remaining PRD 58 limits — approximate one-shot voices (`64`), approximate continuous sounds (`16`), automation tracks (`64`), and automation points (`256`) — are runtime ceilings rather than document properties..." But 16.1 (line 1318): exceeding 64 tracks or 256 points "is `ERR_NODE_LIMIT_EXCEEDED`," which section 7 (line 285) files as Semantic, and 16.11 trace 6 (line 1483) requires `65`/`257` to fail and `64`/`256` to pass in the automated, no-audio-device group.
|
||||
|
||||
**Why wrong:** Automation tracks and points are authored in the document's `automation` arrays and are countable after static expansion (15.19 trace 7 confirms expansion runs with no `AudioContext`). They are document properties and are semantically enforced; 15.14's sentence misclassifies them, so an implementer following 15.14 would defer them to runtime and fail trace 16.11-6. (The voice ceilings in the same sentence are correctly runtime per 16.6.)
|
||||
|
||||
**Smallest fix:** Delete "automation tracks (`64`), and automation points (`256`)" from the 15.14 sentence. Optionally extend section 7's `ERR_NODE_LIMIT_EXCEEDED` cause parenthetical (line 285), which currently omits the automation-limits use introduced in 16.1.
|
||||
|
||||
---
|
||||
|
||||
### 4. §16.1 — automation point `value`s draw from the seeded stream with no documented sampling boundary or order (Class 4)
|
||||
|
||||
**What it says:** Line 1289 makes each point `{ "at": <duration literal>, "value": ValueSpec<number> }`; line 1297: "A track's values remain full ValueSpecs and may be random." Nothing states when they are sampled or in what stream order. 14.4 (line 629) documents resolve-once sampling only for node-object fields; 15.12 (line 1067) fixes the sampling order only for route `depth` values; 9.3 (line 457) requires "the containing object's documented instantiation or invocation boundary" and depth-first, property-document order — no documented boundary or order exists for the `automation` array. (The stream identity itself is inherited from the sound instance; what is missing is the boundary and the consumption order.)
|
||||
|
||||
**Why wrong:** Two conforming implementations may sample track values at different positions in the sound stream (before/after node fields, before/after route depths), producing different values for the same seed — silently breaking the 9.3 reproducibility promise that 14.6 explicitly claims for resolved-once audio values. It is also unstated whether a track's values are sampled once per sound instance at all.
|
||||
|
||||
**Smallest fix:** One sentence in 16.1 fixing boundary and order, e.g.: "A track's point values are resolved once at the owning sound instance's instantiation boundary, sampled from the sound instance's stream in depth-first, property-document order with `automation` taken after `nodes` and `routes`."
|
||||
|
||||
---
|
||||
|
||||
### 5. §15.10/§16.5 and §14.7 — undefined result when every mode or partial is omitted (Class 6 edge case)
|
||||
|
||||
**What it says:** 15.10 (line 1024) and 14.7 (line 690) omit over-ceiling modes/partials at instantiation and state omission "is a routine consequence of legal authoring and raises no diagnostic." 16.5 (line 1387) defines a resonator's contribution as "the longest `decay` among its retained modes."
|
||||
|
||||
**Why wrong:** A legal document can have all entries omitted (e.g. `fundamental: 20000` with `modes: [{ "ratio": 2 }]` on a 44.1 kHz device, where `audioMaxFrequency` = 19845). "Longest decay among retained modes" is then a maximum over an empty set — undefined — and it feeds the one-shot ending computation of 16.5. Likewise a `custom` oscillator with all partials omitted has no defined output.
|
||||
|
||||
**Smallest fix:** State that a resonator with no retained modes contributes zero (and is silent), and that a custom oscillator with all partials omitted produces silence.
|
||||
|
||||
---
|
||||
|
||||
### 6. §15.16 — `name`/`tags` length caps state no breach behavior (Class 5)
|
||||
|
||||
**What it says:** Lines 1197–1198: `name` "at most `128` characters"; `tags` "at most `16` entries of at most `32` characters." No diagnostic is named. Every other quantitative limit in §§14–16 names its code, and the in-section conventions don't cover string/array lengths (`ERR_TYPE_MISMATCH` covers bad enum tokens, `ERR_OUT_OF_BOUNDS` covers numeric ranges; section 7's `ERR_SCHEMA_VALIDATION` cause covers "malformed top-level shapes," which `sounds.<id>` is not).
|
||||
|
||||
**Smallest fix:** Name the diagnostic for exceeding these caps (e.g. `ERR_SCHEMA_VALIDATION`).
|
||||
|
||||
---
|
||||
|
||||
### 7. §16.2 — misattributed cross-reference (borderline Class 8)
|
||||
|
||||
Line 1332 cites "the parameter-masking rule of section 8.1" for "releasing an override returns toward the current stored value rather than an obsolete snapshot." Section 8.1 exists but contains no such rule — its only masked-override text (line 318) concerns the UI override indicator. The behavior is specified in 8.3 (line 370, masked stages continue) and 8.4 (line 392, `currentLowerValue` recomputed every tick).
|
||||
|
||||
**Smallest fix:** cite 8.3/8.4 instead of 8.1.
|
||||
|
||||
---
|
||||
|
||||
## Classes with no findings
|
||||
|
||||
* **Class 1** (prose contradicting a JSON example in the same section): none. I checked every example and invalid case in 14.3–14.13, 15.2–15.17, and 16.1 against its section's tables and rules (ranges, enums, defaults, named diagnostics); all match, including the subtle ones (14.7's empty-`harmonics` invalid case correctly maps to `ERR_UNKNOWN_FIELD`, not `ERR_SCHEMA_VALIDATION`; 15.16's `oneshot` example genuinely has a determinable ending under 16.5).
|
||||
* **Class 2** (field missing from its container's allowed-field table): none. `automation` is in 14.3's graph-object table; `parameters`/`input` in 15.15's; `mode`/`release` in 15.16's recipe table; all node fields, route fields (15.12), track fields (16.1), sound fields (15.16), and bus fields (15.17) are tabulated in their containers.
|
||||
* **Class 6**, beyond findings 1 and 5: checked and cleared — 16.5's longest-path termination (acyclic route graph + nesting ≤ 8), the delay tail formula's domain over `feedback ∈ [0, 0.95]`, the exponential-interpolation fallback, the 16.6 eviction order, and lifecycle transition totality (including the second-stop no-op).
|
||||
* **Class 7**, beyond finding 2: all 24 `ERR_`/`WARN_`/`INFO_` codes used in §§14–16 are present in the section 7 table (verified by mechanical extraction); the 15.18 and 16.10 "new codes" tables match section 7's entries in stage and wording. Only `ERR_INVALID_RANGE_ORDER` is mis-staged (finding 2), and `ERR_NODE_LIMIT_EXCEEDED`'s cause list omits its 16.1 automation use (folded into finding 3).
|
||||
* **Class 8**, beyond finding 7: all internal references (1.3, 4, 6.1, 6.2, 7, 8.1, 8.2, 9.3, 11, and all 14.x/15.x/16.x) resolve, and every PRD reference used in §§14–16 (33–62, 117–118, 120, 129) exists as a numbered PRD section.
|
||||
|
||||
---
|
||||
|
||||
# Second output — Kimi K3 (condensed restatement)
|
||||
|
||||
Same seven defects, delivered as a summary pass. Line numbers refer to `docs/XZBT_0-1_Format_Specification.md`. Verbatim as delivered.
|
||||
|
||||
---
|
||||
|
||||
Review of XZBT format spec sections 14–16 complete. Seven defects found, ranked by severity (line numbers refer to docs/XZBT_0-1_Format_Specification.md):
|
||||
|
||||
1. **§15.13/§15.11 vs §14.4** — component exposed parameters are legal modulation/automation targets with no defined effect (unimplementable). The 15.13 registry (line 1085) and 15.11 (line 1045) make `<component-node-key>.<parameter-id>` a modulation target, and 16.1 makes it an automation target. But the only internal consumption path is `{ "ref": "inputs.<id>" }` in a node field (15.15, line 1166), and 14.4 (line 629) freezes every node field at instantiation "for that node instance's lifetime." Nothing ever re-reads the parameter, so the modulation sum/automation curve has no defined audible effect, and acceptance trace 15.19-4 cannot be satisfied as written. Smallest fix: remove the `component` row from the 15.13 registry (and exclude exposed parameters in 16.1 and trace 15.19-4), or state in 15.15 that exact-form `inputs.*` ref fields track the parameter's current value as an exception to 14.4.
|
||||
|
||||
2. **§14.12/§7** — `ERR_INVALID_RANGE_ORDER` is filed Semantic-only but the sample-hold rule needs instantiation-time values. `min`/`max` are full ValueSpecs resolved once at instantiation (14.12 lines 815–816); the order rule (line 819) therefore cannot be checked at import for legal procedural bounds — semantic validation has no seed, streams, or runtime state (14.5 line 645; 9.3). Section 7 (line 286) files the code as Semantic only while its own cause says "after resolution." Smallest fix: refile as `Semantic / Runtime` in section 7 and state in 14.12 that literal bounds are checked at import, procedural bounds at instantiation. (The 16.1 use of the same code is fine — `at` is a literal.)
|
||||
|
||||
3. **§15.14 vs §16.1** — staging contradiction on automation limits. 15.14 (line 1132) calls automation tracks (64) and points (256) "runtime ceilings rather than document properties," but 16.1 (line 1318) enforces them with `ERR_NODE_LIMIT_EXCEEDED`, a Semantic code (section 7 line 285), and 16.11 trace 6 requires import-stage enforcement. Tracks/points are countable in the document after static expansion; the voice ceilings in the same sentence are correctly runtime. Smallest fix: delete the two automation limits from the 15.14 sentence; optionally extend `ERR_NODE_LIMIT_EXCEEDED`'s cause list in section 7 to mention automation.
|
||||
|
||||
4. **§16.1** — automation point values draw from the seeded stream with no documented sampling boundary or order. Points are `{ "at": literal, "value": ValueSpec }` and values "may be random" (lines 1289, 1297), but 14.4 documents resolve-once sampling only for node fields and 15.12 only fixes route-`depth` order; nothing fixes when or in what stream order track values sample (9.3 requires a documented boundary). Two conforming implementations can consume the stream in different orders and diverge for the same seed, breaking the 9.3/14.6 reproducibility promise. Smallest fix: one sentence in 16.1 fixing the instantiation boundary and the position of `automation` in the depth-first property-document sampling order.
|
||||
|
||||
5. **§15.10/§16.5 and §14.7** — undefined result when every mode/partial is omitted. A legal document can have all resonator modes omitted at instantiation (e.g. `fundamental: 20000`, `modes: [{ "ratio": 2 }]` on a 44.1 kHz device); 16.5's "longest `decay` among its retained modes" is then a max over an empty set, and a custom oscillator with all partials omitted has no defined output. Smallest fix: state both contribute zero / produce silence.
|
||||
|
||||
6. **§15.16** — `name` (128 chars) and `tags` (16 × 32 chars) caps name no diagnostic on breach, unlike every other quantitative limit in §§14–16; no in-section convention covers length caps. Smallest fix: name the code (e.g. `ERR_SCHEMA_VALIDATION`).
|
||||
|
||||
7. **§16.2 line 1332** — mis-citation: "the parameter-masking rule of section 8.1" is not in 8.1; the behavior lives in 8.3 (line 370) and 8.4 (line 392). Smallest fix: cite 8.3/8.4.
|
||||
|
||||
Classes with no findings: Class 1 (all in-section JSON examples match their prose, including invalid-case codes); Class 2 (every introduced field appears in its container's allowed-field table); Class 7 otherwise (all 24 codes used in §§14–16 verified present in the section 7 table; 15.18/16.10 match it); Class 8 otherwise (all internal section references and all cited PRD sections 33–62, 117–118, 120, 129 exist). Nothing in the excluded deliberate-design list was reported. No edits were made to the document.
|
||||
@@ -0,0 +1,135 @@
|
||||
# XZBT format specification sections 14–16 — defect review
|
||||
|
||||
Reviewer: independent pass over `docs/XZBT_0-1_Format_Specification.md` (commit `e5ed468`), sections 14–16 only.
|
||||
Scope: defects only — no rewrites, no features, no style commentary. Line numbers refer to the spec file at that commit.
|
||||
Not reviewed against any other review file; this analysis is standalone.
|
||||
|
||||
Ranking: findings that make the contract unimplementable or untestable outrank inconsistencies and under-specifications.
|
||||
|
||||
---
|
||||
|
||||
## F1 — No target supports both automation and override; 16.2's masking rule and acceptance trace 16.11.8 cannot be executed, and the bus-gain "automation" claim contradicts 16.1's own target rule
|
||||
|
||||
**Sections:** 16.1 (lines 1286, 1293), 16.2 (lines 1322–1332), 8.1 (line 314), 15.17 (line 1230), 14.4 (line 631), 16.11 trace 8 (line 1485).
|
||||
|
||||
**What the document says:**
|
||||
|
||||
- 16.2 gives the resolution pipeline "For a node property: `base ValueSpec -> binding (where supported) -> automation -> winning override -> modulation sum -> safety clamp -> engine parameter`" and states as a consequence: "An override masks the automation stage's output for as long as it wins… When the override releases, the property returns to whatever the track has reached by then."
|
||||
- 16.11 trace 8: "An override masking an automated property releases to the track's *current* value, not the value held when the override took hold."
|
||||
- 8.1, 15.17, and 16.2 all state that `audio.buses.<id>.gain` "takes binding, automation, override, and modulation."
|
||||
|
||||
**Why it is wrong:** The two capability sets never intersect on any property a document can actually author:
|
||||
|
||||
- Node properties support automation (16.1) but are barred from override: 14.4 — node fields are not added to the 8.1 table, and "an `override` action addressing a node field … is `ERR_UNSUPPORTED_TARGET`." 16.2 itself reaffirms this.
|
||||
- `parameters.<id>` and `state.<id>` support override but have Automation = No (8.1).
|
||||
- Bus gain is the only target with both Automation = Yes and Override = Yes (8.1), but 16.1 confines automation tracks to a graph object's `automation` array with `target` = `<node-key>.<property>`, "resolved in the same graph," restricted to "any property in the 15.13 modulatable registry, and no other." Buses are not nodes in any graph (they are top-level `audio.buses.<id>` objects whose only field is `gain`, 15.17), and bus gain is not in the 15.13 registry. No authoring surface for a bus-gain automation track exists anywhere in sections 14–16.
|
||||
|
||||
Consequences: (a) 16.2's masking rule describes a situation no legal document can produce, and (b) required acceptance trace 16.11.8 is unimplementable — the Phase 3c acceptance suite cannot pass as written. The same orphaning applies to bus-gain "additive modulation": the only modulation syntax in the document is graph-scoped routes of 15.12/15.13, whose `to` must be `<node-key>.<property>` in the same graph, so nothing can additively modulate a bus gain either, despite the 8.1 row and the 15.17/16.2 sentences.
|
||||
|
||||
**Smallest fix:** Pick one side and make the other consistent — either (a) delete "automation" (and, for the same reason, "additive modulation"/"modulation") from the bus-gain capability claims in 8.1, 15.17, and 16.2, and delete or re-scope 16.2's masking consequence and trace 8 to a target that actually exposes both stages — no such target exists, so the masking rule and its trace must be removed unless a target with both stages is added — or (b) give bus gain an actual automation authoring surface and make 16.1's target rule name it. Option (a) is the smaller change.
|
||||
|
||||
---
|
||||
|
||||
## F2 — 16.6 eviction step 3 cannot free a budget slot; the outcome for the incoming instance is undefined
|
||||
|
||||
**Section:** 16.6 (lines 1406–1415); also 16.3 (line 1345) and 16.11 trace 10 (line 1487).
|
||||
|
||||
**What the document says:** "A voice counts against its ceiling from `CREATED` until `DISPOSED`." The eviction policy is: "1. Dispose the oldest instance already in `FINISHED`. 2. Evict the oldest instance in `RELEASING` by advancing its release ramp to immediate completion and disposing it. 3. For a one-shot request only: evict the oldest `ACTIVE` one-shot by starting its release. 4. Otherwise refuse the new instance." The runtime "stops at the first candidate," and "Eviction always releases (16.4); it never hard-stops an active voice."
|
||||
|
||||
**Why it is wrong:** Steps 1 and 2 dispose an instance, so a slot frees immediately. Step 3 only *starts* the release of an `ACTIVE` voice, moving it to `RELEASING` — and a `RELEASING` voice still counts against the ceiling until `DISPOSED` (that is precisely why step 2 must advance the ramp to completion and dispose). So when the ceiling is full of `ACTIVE` one-shots (no `FINISHED`, no `RELEASING`), step 3 frees nothing, yet the policy stops there. Admitting the new instance then puts the count one over the ceiling (65 > 64), contradicting the "counts from `CREATED` until `DISPOSED`" rule; refusing it contradicts the policy having stopped at a candidate rather than reaching step 4. Neither outcome is defined. Acceptance trace 16.11.10 (exercise the eviction order at the one-shot ceiling) cannot be implemented unambiguously in exactly this state.
|
||||
|
||||
**Smallest fix:** One sentence defining the step-3 outcome, e.g. that an evicted `ACTIVE` one-shot is removed from the ceiling count immediately upon eviction (its release then runs without occupying budget), so the new instance is admitted and the ceiling is never exceeded; or make step 3 behave like step 2 (complete the release immediately and dispose). Either way the "counts until `DISPOSED`" sentence needs the matching exception or wording.
|
||||
|
||||
---
|
||||
|
||||
## F3 — 16.5's ending bound is not an upper bound when a bound-contributing property is modulated
|
||||
|
||||
**Sections:** 16.5 (lines 1376, 1385), 15.6 (lines 943, 949), 15.13 (line 1082), 16.3 (line 1360).
|
||||
|
||||
**What the document says:** "Determinable" means "the runtime can compute a finite upper bound on the instance's audible duration at instantiation, from the resolved graph alone, without observing output." The bound table gives `delay` the contribution `time x ceil(log(1/1000) / log(feedback))`. The instance is finished (torn down) at that bound plus the release duration.
|
||||
|
||||
**Why it is wrong:** `delay.time` is modulatable (15.13, in milliseconds) and is clamped live so it never leaves `[0ms, 10s]` (15.6). A legal `oneshot` can route an `lfo` or `sample-hold` to a delay's `time`; those sources vary over time and their future output is unknowable at instantiation. The 16.5 row computes the contribution from the *resolved* `time` only, but the actual delay time — and therefore the actual time the feedback tail takes to fall 60 dB — can be up to the 10 s clamp. The stated bound is then not an upper bound on audible duration: the runtime reaches the computed ending and tears the instance down while its tail is still ringing above the level the bound assumes, directly contradicting 16.3's claim that at the determinable ending "the envelope has already returned to zero." Trace 16.11.9's "bound matches the 16.5 table" only passes for graphs whose bound-contributing properties are not modulated.
|
||||
|
||||
**Smallest fix:** In 16.5, define the delay contribution over the largest delay `time` the property can reach given its modulation routes (source outputs are bounded — `lfo` by its resolved amplitude, `sample-hold` by its resolved `min`/`max` — and the property is clamped), or exclude a modulated bound-contributing property from determinable-ending status. One sentence either way; the first option preserves the most legal graphs.
|
||||
|
||||
---
|
||||
|
||||
## F4 — 14.12's `min`/`max` ordering check fires after resolution but its code is filed Semantic-only: recurrence of the known staging defect
|
||||
|
||||
**Sections:** 14.12 (lines 812–819), 14.4 (line 629), §7 (line 286).
|
||||
|
||||
**What the document says:** `min` and `max` are `ValueSpec<number>` fields "resolved once, at the owning sound instance's instantiation boundary" (14.4). 14.12: "`min` resolving to a value greater than or equal to `max` is `ERR_INVALID_RANGE_ORDER`." Section 7 lists `ERR_INVALID_RANGE_ORDER` with stage **Semantic** and cause "Declared paired bounds (e.g. `sample-hold` `min`/`max`) are not in strictly increasing order after resolution."
|
||||
|
||||
**Why it is wrong:** This is the same defect class that has already occurred twice in this document (the 14.5 `audioMaxFrequency` semantic-stage issue, and the automation-ordering rule fixed in `50fb72c`), and it is still present here. For literal bounds the check is import-decidable, but for procedural bounds the order exists only "after resolution," which happens at instantiation — no `AudioContext` and no instance exist at semantic validation, so a pair such as `min: {random:{-1,1}}, max: {random:{-1,1}}` that resolves inverted is undetectable at import, and the instantiation stage may not raise a code the section 7 table files as Semantic-only.
|
||||
|
||||
**Smallest fix:** File `ERR_INVALID_RANGE_ORDER` as "Semantic / Runtime" in section 7 (as `ERR_OUT_OF_BOUNDS` already is), with the cause text stating literals are rejected at import and resolved pairs at instantiation.
|
||||
|
||||
---
|
||||
|
||||
## F5 — 15.14 classifies the automation track/point limits as runtime ceilings, contradicting 16.1's import-time semantic enforcement
|
||||
|
||||
**Sections:** 15.14 (line 1132), 16.1 (line 1318), §7 (line 285), 16.11 trace 6 (line 1483).
|
||||
|
||||
**What the document says:** 15.14: "The remaining PRD 58 limits — approximate one-shot voices (`64`), approximate continuous sounds (`16`), automation tracks (`64`), and automation points (`256`) — are runtime ceilings rather than document properties and belong to Phase 3c with the lifecycle contract." 16.1: "At most `64` automation tracks and `256` total automation points per expanded sound (PRD 58). Exceeding either is `ERR_NODE_LIMIT_EXCEEDED`" — a Semantic-stage code — and 16.11 trace 6 requires `65` tracks / `257` points to be rejected (at import, like the other trace-6 checks).
|
||||
|
||||
**Why it is wrong:** Track and point counts are fully decidable at import after deterministic component expansion; unlike voice ceilings they depend on nothing device- or runtime-specific. 15.14's "runtime ceilings rather than document properties" therefore contradicts 16.1, §7's Semantic filing, and trace 6, and leaves the two normative statements irreconcilable for an implementer deciding where a 65-track document fails. (The same stale sentence also mislabels only the track/point limits — the voice ceilings genuinely are runtime ceilings.)
|
||||
|
||||
**Smallest fix:** In 15.14, delete "automation tracks (`64`), and automation points (`256`)" from the runtime-ceilings sentence and add the two rows to the 15.14 authoring-time limits table (or add a pointer saying they are enforced as `ERR_NODE_LIMIT_EXCEEDED` per 16.1).
|
||||
|
||||
---
|
||||
|
||||
## F6 — Automation point `value` sampling has no documented time, stream, or key
|
||||
|
||||
**Sections:** 16.1 (lines 1289, 1297), 14.4 (line 629), 14.6 (lines 652–658), 9.3 (line 457).
|
||||
|
||||
**What the document says:** Each point is `{ "at": <duration literal>, "value": ValueSpec<number> }`, and "A track's *values* remain full ValueSpecs and may be random; it is only the curve's shape in time that is authored, not sampled."
|
||||
|
||||
**Why it is wrong:** `random`/`choose` ValueSpecs consume a seeded stream (9.3), and the document carefully fixes the sampling boundary for every other audio consumer: node fields resolve once at the sound instance's instantiation boundary in depth-first, property-document order (14.4), route `depth` resolves at the same boundary in route order (15.12), and `sample-hold` draws are documented with a child key on its own stream (14.12, and 9.3's "documented child key" requirement). Automation point values are none of these: a track lives on the graph object, its point values are not node fields, and 16.1 says nothing about when they are sampled or from which stream/key, nor where the `automation` array sits in the 14.4 sampling order. An implementer cannot reproduce a run that uses random automation point values, and 16.8's promise that a partially elapsed continuous sound keeps "a procedural sequence identical to an unbroken run" has no defined meaning for automated tracks. This is exactly the "value consuming a seeded random stream without a documented derivation key" class.
|
||||
|
||||
**Smallest fix:** One sentence in 16.1 stating that point `value` ValueSpecs resolve once at the owning sound instance's instantiation boundary, from that instance's own stream, sampled in depth-first, property-document order together with the graph's node fields and route depths (or on a documented per-track child key like 14.12's).
|
||||
|
||||
---
|
||||
|
||||
## F7 — 16.5's resonator contribution is undefined for a legal graph whose modes are all omitted at instantiation
|
||||
|
||||
**Sections:** 16.5 (line 1387), 15.10 (lines 1020, 1024), 14.5 (lines 645–646).
|
||||
|
||||
**What the document says:** 16.5: a `resonator` contributes "the longest `decay` among its retained modes." 15.10: mode `frequency` may be authored up to `audioMaxFrequency`, and "A mode whose resolved frequency exceeds `audioMaxFrequency` at instantiation is omitted" with no diagnostic. 14.5: semantic checks use the device-independent ceiling `24000`; the device ceiling is `min(24000, sampleRate x 0.45)`.
|
||||
|
||||
**Why it is wrong:** On a 44.1 kHz device `audioMaxFrequency` is `19845` Hz; on 48 kHz it is `21600` Hz. A mode frequency in that gap (e.g. `20000` Hz or a `ratio x fundamental` product above the device ceiling) is import-legal but omitted at instantiation. A single-mode resonator then has zero retained modes, and "the longest decay among its retained modes" is undefined — so the one-shot ending bound, which must be "computable … from the resolved graph alone," has no defined value for a legal document on a legal device. (The node's output in that case is silence; only the bound row is left undefined.)
|
||||
|
||||
**Smallest fix:** Extend the row: "the longest `decay` among its retained modes, or zero when no mode is retained."
|
||||
|
||||
---
|
||||
|
||||
## F8 — 14.9's impulse-envelope table contradicts its own formulas and its own prose
|
||||
|
||||
**Section:** 14.9 (lines 741–749).
|
||||
|
||||
**What the document says:** The table lists, for each `decay` mode, the envelope and a column "Value at `p = 1`":
|
||||
|
||||
| `decay` | Envelope | Value at `p = 1` |
|
||||
| --- | --- | --- |
|
||||
| `flat` | `a` | `0` |
|
||||
| `linear` | `a x (1 - p)` | `0` |
|
||||
| `exponential` | `a x e^(-6.907755 x p)` (`-60` dB at `p = 1`) | `0` |
|
||||
|
||||
The prose immediately below reads: "Because `flat` and `exponential` do not reach zero on their own, the runtime applies a terminal linear fade to zero over the final `min(1ms, d x 0.1)` of the burst."
|
||||
|
||||
**Why it is wrong:** For `flat` the envelope is the constant `a`, so its value at `p = 1` is `a`, not `0`. For `exponential`, `a x e^(-6.907755)` is `a x 0.001` — as the row's own "`-60` dB at `p = 1`" annotation confirms — not `0`. Only `linear` reaches `0` at `p = 1`. The column contradicts both the envelope formulas in the same table and the sentence that follows it (which exists precisely because those two envelopes do not reach zero). Note also that `p = 1` is outside the stated domain `0 <= p < 1` of the envelope, so the column conflates the raw envelope with the post-fade value.
|
||||
|
||||
**Smallest fix:** Correct the two cells (e.g. `flat` → `a`; `exponential` → `a x e^(-6.907755)`), or delete the column entirely and keep the fade sentence as the definition at the burst end.
|
||||
|
||||
---
|
||||
|
||||
## Defect classes with no findings
|
||||
|
||||
- **Prose contradicting a JSON example in the same section:** none found. Every JSON example in 14–16 was checked against its field table and the section's rules and agrees with them (the 14.9 issue above is a table/formula/prose self-contradiction, not a JSON-example conflict).
|
||||
- **Field introduced in one section but missing from its container's allowed-field table:** none found. `automation` is in the 14.3 graph-object table; `mode`/`release` are in the 15.16 recipe table; `parameters`/`input` are in the 15.15 component table; the automation track fields (`target`, `mode`, `interpolation`, `points`) are all in the 16.1 table.
|
||||
- **Cross-references to sections that do not exist:** none found. All section references used in 14–16 (1.3, 4, 6.1/6.2, 7, 8.1/8.2, 9.3, 14.5, 15.13–15.17, 16.4, 16.8, and the "Phase 3c" deferrals) resolve to existing sections.
|
||||
- **Diagnostic code used but absent from the section 7 table:** none found. Every code used in 14–16 appears in §7, and the 15.18 and 16.10 tables duplicate §7 consistently. Wrong-stage filing is covered by F4 and F5.
|
||||
|
||||
## Findings deliberately not reported
|
||||
|
||||
- The provisional master-protection peak ceiling, tolerance, and release behavior (16.7), the GC4 audio long-stall bound (16.9), noise/impulse non-reproducibility (14.6), sample-hold's child-key documentation (14.12), and the `audio.master` absence (15.17) are all flagged in the text as deliberate and are consistent with the stated design decisions.
|
||||
- The `ERR_TYPE_MISMATCH` codes on out-of-set enum literals (e.g. 14.8 `color: "grey"`, 15.3 `mode: "comb"`) are internally consistent across the audio sections and defensible under section 2's definition of `enum` ("string constrained to an explicitly declared set of allowed tokens"), so no contradiction with §7 was established.
|
||||
@@ -0,0 +1,64 @@
|
||||
# XZBT 0.1 triage — fix log (what was applied, what remains)
|
||||
|
||||
Record of edits to `docs/XZBT_0-1_Format_Specification.md` (1499-line version, working
|
||||
tree = commit `e5ed468`; spec file itself unmodified in git status at time of writing).
|
||||
Consolidates the 13 defects of `00-triage.md`. Scope constraint honored: **nothing that
|
||||
overlaps the in-flight Phase 3c slice-2 implementation** (16.3 lifecycle, 16.4 release,
|
||||
16.5 endings, 16.6 eviction, disposal, traces 16.11.7/9/10/11, or the runtime/schema/test
|
||||
files that slice has open) was touched. Doc-text edits only; no code, schema, or test
|
||||
changes.
|
||||
|
||||
---
|
||||
|
||||
## Applied — 7 of 13 defects
|
||||
|
||||
| T | Location(s) | Change | Verification |
|
||||
| --- | --- | --- | --- |
|
||||
| T4 | §7 row `ERR_INVALID_RANGE_ORDER`; 14.12 | Refiled `Semantic / Runtime` with a cause covering both uses: literal paired bounds at import, resolved paired bounds at node instantiation, automation `at` ordering at import. 14.12 now states the literal/resolved split explicitly. 14.12's invalid-case example (`min: 1, max: -1`) remains an import case, consistent. | Code grounding: `audio-graph.js:186` emits the code only when both bounds are literal numbers, i.e. import stage. |
|
||||
| T5 | 16.1 "Points." | Added: "A track's point values are resolved once at the owning sound instance's instantiation boundary, sampled from that instance's stream in depth-first, property-document order, with `automation` taken after `nodes` and `routes`." Wording matches 14.4's and 9.3's own vocabulary; `automation` follows `nodes`/`routes` per the 14.3 graph-object field table. | None needed (forward-looking; automation is slice 3c-3). |
|
||||
| T6 | 15.14 (limits paragraph); §7 row `ERR_NODE_LIMIT_EXCEEDED` | Automation tracks/points removed from the runtime-ceiling enumeration (voice ceilings remain, with a 16.6 cross-reference) and pointed to 16.1's semantic enforcement instead. §7 cause extended: "...or automation tracks or points per expanded sound". | 16.10 and 15.18 tables do not restate either amended row; no drift. |
|
||||
| T10 | 16.2 | Clause added to the node-property paragraph: `override` actions on node fields are barred exactly like `BindingSpec` (14.4), so the `winning override` stage of the pipeline is always absent for node properties — "absent, not an identity hook (8.1)". | — |
|
||||
| T11 | 14.9 impulse envelope table | Column relabelled "Value at `p = 1`" → "Value as `p → 1⁻`" with corrected cells `a`, `0`, `a x 0.001` (exponential no longer self-contradicts its `-60 dB` annotation). | Rows checked against the piecewise definition (`0 <= p < 1`, exactly `0` for `p >= 1`). |
|
||||
| T12 | 15.16 sounds metadata | Sentence added: name > 128 chars, > 16 tags, or a tag > 32 chars is `ERR_SCHEMA_VALIDATION`. | Grounded: `schema/xzbt-0.1.schema.json` enforces `name` maxLength 128, `tags` maxItems 16, item maxLength 32 (Sound definition). |
|
||||
| T13 | 16.2 | Mis-citation fixed: "parameter-masking rule of section 8.1" → masked-override rules of **8.3** (underlying stages keep evaluating while masked) and **8.4** (release resolves against the current lower value recomputed without the releasing override). | 8.3 line 370 and 8.4 `currentLowerValue` text verified as the actual referents. |
|
||||
|
||||
Net diff: 12 insertions / 10 deletions in 9 regions. Nothing else in the file changed.
|
||||
|
||||
## Not applied — 6 of 13 defects, with reasons
|
||||
|
||||
| T | Reason |
|
||||
| --- | --- |
|
||||
| T1 (bus-gain automation/modulation surface; 8.1/15.17/16.1/16.2/16.11.8) | **(design)** — the triage's own edit list marks it decision-required before an edit can be written. Recommended option (b): give `audio.buses.<id>.gain` an authoring surface and name it in 16.1's target rule. Not in Gemini's slice (automation is 3c-3), so it can be picked up independently, but needs a decision first. |
|
||||
| T2 (delete `component` modulation-target row; 15.13/15.11/15.19.4) | Blocked on coordination, not on Gemini's slice per se: the registry row is mirrored in `audio-contract.js`, asserted by a green Phase 3b test (`test/phase3-audio.test.mjs:313`, trace 15.19.4), and 15.19.4 is an *accepted* contract trace. Deleting spec text alone would leave doc contradicting code+test. The runtime and test files are currently open by the slice-2 work; the delete (plus trace amendment and test/registry update) must land as one coordinated change, or be consciously deferred as a contract decision. |
|
||||
| T3 (16.6 eviction step 3 frees no slot) | **Gemini's slice** — 16.6 and trace 16.11.10 are being implemented and tested right now. A spec change here would invalidate the in-flight suite and evidence record. After slice 2 lands, reconcile: Gemini's `audio-engine.js` has already made *some* choice for step 3; check it against the triage's two options (recommended: evicted `ACTIVE` one-shot leaves the ceiling count at the moment of eviction, release running outside the budget, with the matching exception added to the 16.6 counting rule). |
|
||||
| T7 (16.5 resonator bound over empty retained-mode set) | **Gemini's slice** — 16.5 ending computation and trace 16.11.9. One-sentence fix ("...or zero when no mode is retained") ready to apply after slice 2 lands; check against Gemini's implementation choice for single-mode resonators on a 44.1 kHz device. |
|
||||
| T8 (16.5 delay bound under modulation) | **Gemini's slice** + **(design)** + blocked on T9. Alternative fix (exclude modulated bound-contributors from determinable-ending status via `ERR_INDETERMINATE_ONESHOT`) touches 16.5 semantics the slice is implementing. |
|
||||
| T9 (oscillator modulation output magnitude undefined; 15.13) | **(design)** — stating a range in the spec would be a behavioral contract change for accepted Phase 3b modulation code (custom-partial normalization) that no implementation performs; the registry and summation code live in files the slice has open. Needs a decision, then coordinated spec + contract-registry change. |
|
||||
| T14 (exposed parameter without `min`/`max` has no clamp range) | Conditional on T2 — moot unless T2's option two (live parameter tracking) is chosen. Remains closed while T2 is open. |
|
||||
|
||||
## Follow-ups recorded, not actioned
|
||||
|
||||
- **T4 implementation gap (pre-existing):** no instantiation-time range-order check exists.
|
||||
`audio-graph.js:186` is the only enforcement site and it guards on both bounds being
|
||||
literal numbers (`typeof low === 'number' && typeof high === 'number'`). A document with
|
||||
resolved (e.g. random) `sample-hold` bounds that invert at resolution is import-legal and
|
||||
currently unchecked at instantiation. The spec now says the check happens there; the
|
||||
runtime follow-up belongs to the phase-3a code owner (files open by Gemini's slice).
|
||||
- **§8 out-of-scope finding from the triage:** §8.5 trace 6 and §8.3's masked-automation
|
||||
sentence are already unimplementable for want of a target exposing both automation and
|
||||
override (T1's root cause, independent of §§14–16). Still to be logged against §8; T1
|
||||
must not be closed without addressing it.
|
||||
- **14.3 canonical example** (`oscillator → gain → output`) is not a legal `oneshot` recipe
|
||||
as-is (`ERR_INDETERMINATE_ONESHOT`); noted as editorial, not a defect. Unchanged.
|
||||
- **14.7 all-partials-omitted oscillator** deserves one clarifying sentence (Kimi #5 /
|
||||
DeepSeek F7 downgrade in the triage); not a Tier-2 defect. Not actioned.
|
||||
|
||||
## Housekeeping
|
||||
|
||||
- Edits are **uncommitted** in the working tree (12+/10−, 9 regions, spec only). Recommended:
|
||||
land as a dedicated `docs(audio): ...` commit before Gemini's slice-2 commit, per the
|
||||
project's contract-before-implementation convention, so the two changes never mix.
|
||||
- Verified: no stale "Value at `p = 1`" header remains; §7 rows appear once each; 15.18 and
|
||||
16.10 tables unaffected; all new cross-references (15.14 → 16.6/16.1; 16.2 → 8.1/8.3/8.4;
|
||||
14.12 → 14.4) resolve. No test suite was run — none is affected by doc-text edits, and the
|
||||
slice-2 suite was in flight.
|
||||
@@ -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.
|
||||
+486
@@ -0,0 +1,486 @@
|
||||
# Visual contract review — Abacus AI Agent
|
||||
|
||||
Independent adversarial read of XZBT Format Specification **rev 0.7**, sections **17–19**, before Phase 4 slice **4d** (renderer core). Existing files under `reviews/` were not opened.
|
||||
|
||||
Standing decisions were treated as locked. This pass checks whether the spec text actually states them and whether it contradicts itself elsewhere.
|
||||
|
||||
Severity:
|
||||
|
||||
- **Blocker** — 4d cannot implement a unique, testable behavior without guessing.
|
||||
- **Major** — two conforming 4d implementations can diverge, or a later slice will have to unwind 4d choices.
|
||||
- **Minor** — underspecified edge, wrong diagnostic, or local inconsistency that 4d can paper over with a default.
|
||||
|
||||
---
|
||||
|
||||
## Defects
|
||||
|
||||
### 1. `visuals.automation` is both required and forbidden
|
||||
|
||||
- **Severity:** Blocker
|
||||
- **Location:** §17.3 *The `visuals` block* vs §19.1 *Two declaration scopes*
|
||||
- **What's wrong:** §17.3 enumerates `scene`, `layers`, `systems`, `fields`, `camera`, `effects` and then: “any other property of `visuals` is `ERR_UNKNOWN_FIELD`.” §19.1 places an `automation` array on `visuals` itself (`visuals.automation`). Under the strict unknown-field policy that array is illegal at import.
|
||||
- **Why it matters for 4d:** The renderer core will parse `visuals`. If it follows §17.3 it rejects every exhibit that uses the §19.1 example. If it follows §19.1 it violates the §17.3 table. Slice 4d must know the legal key set before it writes a schema or a loader.
|
||||
|
||||
### 2. Trace 14 of §17.16 forbids the four §8.1 visual rows §19.1 adds
|
||||
|
||||
- **Severity:** Blocker
|
||||
- **Location:** §17.16 trace 14 vs §17.14 last paragraph vs §19.1 *The visual rows of the section 8.1 target-capability table*
|
||||
- **What's wrong:** Trace 14: “A binding, `set`, or `override` addressing **any** visual property is `ERR_UNSUPPORTED_TARGET`, and the section 8.1 table is **unchanged by this slice**.” §19.1 then adds four families (`visuals.camera.<field>`, `visuals.layers.<id>.opacity`, `visuals.systems.<id>.visible`, `visuals.effects[<index>].<param>`) that **are** binding/override targets. §17.14 already describes those four rows as if they exist. The acceptance gate for 4d therefore requires the opposite of the closed contract.
|
||||
- **Why it matters for 4d:** 4d is the first code that will implement (or stub) target resolution. Implementing trace 14 literally means rejecting camera/layer/system/effect bindings that 4f traces 7 require to succeed. The 4d gate must be restated as “any visual property **outside the four §19.1 rows**.”
|
||||
|
||||
### 3. Automatable registry names a repeater `step` block that does not exist
|
||||
|
||||
- **Severity:** Blocker
|
||||
- **Location:** §19.1 *The automatable registry* (“for `repeater`, the numeric fields of its `step` block”) vs §18.5 field table
|
||||
- **What's wrong:** §18.5 has `repeat`, `count`, `distribution`, `position`, `inputs`, `behaviors`, `fields`, `links`. There is no `step` field, no `step` block, and no definition of what “numeric fields of its `step` block” are. Repeaters also have no `rate` (explicitly `ERR_UNKNOWN_FIELD`).
|
||||
- **Why it matters for 4d:** Less for the first draw path than for the property-path resolver 4d will share with automation. A registry row with no corresponding schema is an unimplementable target. Either define `step` or delete the row.
|
||||
|
||||
### 4. Y-down scene vs “positive angles toward +y”
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.2 *Canonical units* (“`0` points along `+x`; positive angles turn toward `+y`”) vs §17.4 (`x` and `y` run `0` to `1` **from the top-left**; virtual origin at top-left)
|
||||
- **What's wrong:** Top-left origin with `y` increasing downward is a left-handed screen space. “Positive toward `+y`” is then **clockwise** on the display (standard canvas), not the mathematical CCW convention a reader of “toward +y” in a Cartesian plane will assume. The spec never says whether rotation is screen-clockwise or math-CCW, and never says whether `+y` in local object space is down.
|
||||
- **Why it matters for 4d:** Transform order `R × K × S` is useless if the sign of `R` is ambiguous. Trace 3 of §17.16 (“a known local point maps to the documented device point”) cannot be written until this is fixed. Canvas 2D `rotate` is clockwise for y-down; a naive `cos/sin` toward +y with y-down will mirror every exhibit.
|
||||
|
||||
### 5. Local origin of box primitives is unspecified
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.9 `rectangle`, `rounded-rectangle`, `ellipse`, `ring`, `arc`; §17.11 `origin` “in the object's own local space”; §17.13 text `align` “relative to the object's origin”
|
||||
- **What's wrong:** For `rectangle` / `rounded-rectangle`, is `position` the top-left of `size`, the center, or something else? For `ellipse` / `ring` / `arc`, is `position` the center (implied by `radius` but never stated)? For `point`, is the dot centered on the origin? Text has `align`/`baseline`, which implies the origin is an anchor, but boxes have no equivalent. Default `origin` is `{0,0}`, so if a rectangle’s local space has its corner at 0, rotation is about the corner; if centered, about the center. These produce different pixels.
|
||||
- **Why it matters for 4d:** First thing the primitive rasterizer needs. Two implementations will disagree on every rotated panel and every `contain`/`cover` fixture in trace 1/3.
|
||||
|
||||
### 6. Perspective factor vs camera matrix: two post-multiplies, no combined formula
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.6 *Perspective scaling* (“object's transform is post-multiplied by `focalLength / (focalLength + z)` about the camera's projection center”) vs §19.3 (“applied **after** `V`”)
|
||||
- **What's wrong:** §17.11 already post-multiplies `M_parent × M_local`. “Post-multiplied” in §17.6 does not say whether the factor is applied in local space, after `M_local`, after hierarchy, or after `V`. §19.3 says after `V`, about the **display** projection center. Applying a uniform scale about the display center to an already-view-transformed point is not the same as scaling the object about the camera look-at in scene space. `z` used in the factor is never named as **effective** `z` (`z_parent + z_local` from §17.11), though that is the only reading that matches hierarchy.
|
||||
- **Why it matters for 4d:** Trace 6 of §17.16 and trace 14 of §19.7 are the 4d/4f camera tests. Without a single matrix (or a worked numeric example: one point, one `z`, one `V`, one output), 4d will pick an order that 4f then has to break.
|
||||
|
||||
### 7. `translate.z` is excluded from `M_local` but perspective needs a 3D point
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.11 “The `z` component of `translate` adds to the object's `z` and … is **not** part of `M_local`.”
|
||||
- **What's wrong:** `M_local` is therefore a 2D affine matrix. Perspective uses `z`. Hierarchy adds `z`. Particle integration moves `p` in 3D. The spec never says whether parent `M` rotates/scales child `z` (it cannot, if `M` is 2D) or only adds it. A child with local `z` under a rotated group: is depth the scalar sum, or a transformed coordinate? §17.11 says sum, which means rotation never tilts depth — consistent with 2.5D, but then “perspective about projection center” on a 2D matrix is a uniform XY scale, not a projective transform. That should be stated as “uniform XY scale, no vanishing-point projection.”
|
||||
- **Why it matters for 4d:** Implementers will reach for a 4×4 perspective matrix. The contract wants a 2D scale. If 4d ships a real projection, every later fixture fails.
|
||||
|
||||
### 8. Depth sort unit is inconsistent (object vs system vs particle cloud)
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.6 “Within one layer, **objects** are drawn farthest first” vs §18.2 “Particles of one system draw as **one unit** … The system's own position in its layer's depth sort is its **lowest-`z` particle**” vs §17.8 document key order of `content`
|
||||
- **What's wrong:** Graphic systems contain many objects with independent `z`. Are those objects sorted **across systems** in the layer, or is each system an atomic band like particles? §17.6 says objects; §18.2 special-cases particles so they never interleave with unrelated objects. Emitters create full objects — §18.4 never says whether those objects join the layer object-sort or stay an atomic emitter band. Repeaters: same gap. A graphic system with `z=0` group and a child at `z=100` vs a sibling system at `z=50` has two legal draw orders.
|
||||
- **Why it matters for 4d:** Painter’s algorithm is the entire 2.5D model. 4d must pick a sort key. Wrong choice is a visual bug in every multi-system scene and is expensive to change.
|
||||
|
||||
### 9. Layer `visible` is a ValueSpec but is not automatable, bindable, or in §8.1 — and still “advances”
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.5 `visible` ValueSpec\<boolean\>, default `true`; §19.1 “`visuals.layers.<id>.visible` is deliberately **not** in the table”; §17.14 “every such field is resolved **once**, at its owning object's instantiation boundary”
|
||||
- **What's wrong:** If layer `visible` resolves once, a ValueSpec is pointless except `random`/`choose` at activation. If it is meant to change, there is no writer: not automation (boolean), not binding (absent from §8.1), not `set`/`override`. The field is therefore a once-at-import flag dressed as a ValueSpec. Meanwhile system `visible` **is** an §8.1 binding target and is explicitly **not** automatable. The two `visible` flags look alike in §17 and behave unlike in §19.
|
||||
- **Why it matters for 4d:** 4d will implement layer skip. It needs to know: evaluate once at activation, or subscribe to the pipeline? Implementing a live binding for layer `visible` would violate the locked “deliberately absent” decision; treating it as constant makes the ValueSpec type a lie.
|
||||
|
||||
### 10. §17.16 trace 9 cites “9 layer nesting levels” — layers do not nest
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.16 trace 9 vs §17.5 (layers composite in **document key order**, no parent field) vs §17.9 (group nesting 8)
|
||||
- **What's wrong:** Trace 9: “`9` layer nesting levels … are each `ERR_VISUAL_LIMIT_EXCEEDED`.” There is no layer nesting in §17.5. Group nesting is 8. Layer **count** is 16. The acceptance test for 4d is unrunnable as written.
|
||||
- **Why it matters for 4d:** 4d’s exit gate includes a test that cannot be authored. Likely intent: group nesting 9 / 8, and separately 17 layers.
|
||||
|
||||
### 11. Fit mapping is underspecified for `cover` crop and letterbox placement
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.4 `fit`; §17.16 trace 1
|
||||
- **What's wrong:** `contain` letterboxes “the remainder with `background`” but does not say the scene is **centered** in the display (vs aligned top-left). `cover` “cropping the overflow” does not say which region is kept (centered crop vs origin-aligned). `viewport` ignores `fit` except that non-`stretch` is an error — so the only legal `fit` under `viewport` is `stretch`, which is also ignored. Device-pixel-ratio (§19.5) is applied after fit; the spec never gives the composed scene→CSS→device transform.
|
||||
- **Why it matters for 4d:** Trace 1 is the first 4d test and requires “expected display point” including letterbox offsets. Without centering/crop rules those points are not determined.
|
||||
|
||||
### 12. Camera default “scene center” under `viewport` is not a scene quantity
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §19.3 “the display center for `viewport`” as default `x`,`y`; camera is in “scene units”
|
||||
- **What's wrong:** Viewport scene units **are** CSS pixels of the display, origin top-left, and the scene has no intrinsic size. Defaulting the camera to “display center” makes the default camera depend on the window size. Two machines, same exhibit, different default view. That contradicts “an exhibit that never mentions the camera” looking identical (§19.3’s own rationale) and contradicts reproducibility across display sizes.
|
||||
- **Why it matters for 4d:** Identity camera vs “centered on display” are different matrices as soon as the canvas is not the design size. 4d will bake this into every frame.
|
||||
|
||||
### 13. Conic-gradient fallback axis is not a unique segment
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.12 *Conic gradient fallback*
|
||||
- **What's wrong:** Fallback is “a `linear-gradient` with the same stops along the axis from `center` at `angle` to the paint's bounding-box edge.” A ray from `center` at `angle` intersects a bounding box at **one** point only if `center` is inside the box; if `center` is outside there may be two intersections or none. “Bounding-box edge” does not name which edge. Stop mapping from a 360° conic onto a 1D axis is lossy; the spec does not say whether offsets are used as-is (wrong) or the 0°–180° half is taken.
|
||||
- **Why it matters for 4d:** This is the **only** appearance fallback 4d must implement, and trace 10 requires a documented linear gradient. Without a unique segment, the warning can fire and the pixels still differ.
|
||||
|
||||
### 14. Fog blends “resolved fill, stroke, glow, and shadow colors” — gradients and alpha unspecified
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.6 *Depth fog*
|
||||
- **What's wrong:** Fog fraction is exact. What is “blended toward `color`”? For a gradient, every stop? The rasterized samples? Glow/shadow have their own colors; is the fog color’s alpha used? Premultiplied? `density * clamp(...)` as a lerp in sRGB or linear? Particle `color` ramps already interpolate sRGB; fog does not say. Objects with `fill: null` still have glow/shadow.
|
||||
- **Why it matters for 4d:** Trace 7 asks for blended colors at near/mid/far. Without a color space and a rule for paints, the trace has no oracle.
|
||||
|
||||
### 15. Stroke-only primitives vs `point` fill; `style.fill` on `line` vs table
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.9 `line` “`style.fill` on a `line` is `ERR_UNKNOWN_FIELD`” vs §17.12 *Stroke-only primitives* (`line`, `polyline`, `arc`, `bezier` — **not** `point`); `point` is “a filled dot”
|
||||
- **What's wrong:** `point` uses fill, not stroke, but is not listed as fill-only (`style.stroke` on a point?). `polyline` is stroke-only in §17.9 notes but the stroke-only ERR list in §17.12 omits nothing it listed — OK — yet `path`/`spline` can be open and still accept fill. Open path with fill: legal? Canvas fills open subpaths by closing them; the spec is silent.
|
||||
- **Why it matters for 4d:** Open-path fill is a classic renderer fork.
|
||||
|
||||
### 16. Arc sweep, direction, and “magnitude exceeds 360”
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.9 `arc` / `ring`; §17.2 angles
|
||||
- **What's wrong:** `startAngle`, `endAngle`, `direction` `clockwise` | `counter-clockwise`. Is the sweep the directed difference following `direction`, or the linear difference `end - start` with `direction` flipping the canvas arc flag? A sweep of 0: nothing, or a full ring (ring says “angles default to a full ring” — default values never given numerically)? `ERR_OUT_OF_BOUNDS` when magnitude exceeds 360: is that `|end-start|` or the directed sweep after wrapping? `end = start + 360` with clockwise: full circle or error?
|
||||
- **Why it matters for 4d:** Arc/ring are in Primitive Set 0.1; 4d must emit a unique path.
|
||||
|
||||
### 17. Path `arc` command vs SVG sweep/largeArc in y-down space
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.13 path `op: arc`
|
||||
- **What's wrong:** Endpoint-parameterized elliptical arc with `largeArc` and `sweep` booleans. SVG’s `sweep` is “positive angle” in y-down (clockwise). The visual contract’s positive angle is “toward +y.” If those disagree (defect 4), path arcs and `arc` primitives will rotate opposite ways.
|
||||
- **Why it matters for 4d:** Path vs primitive inconsistency will show up in Exhibit geometry immediately.
|
||||
|
||||
### 18. Spline Catmull-Rom: phantom points, closed form, and tension mapping
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.13 *Spline*
|
||||
- **What's wrong:** Open spline: “first and last points are duplicated as the phantom endpoints.” That is one CR parameterization (uniform, centripetal?). `tension` `0` to `1` default `0.5` is not mapped to a formula (Kochanek–Bartels? `tau = 1 - tension`?). Closed spline: no phantoms specified; standard is wrap indices. `bezier` mode `count = 3n+1` is for **open** cubics; a `closed` bezier spline with that count cannot close smoothly without extra points — interaction of `closed` + `bezier` is undefined.
|
||||
- **Why it matters for 4d:** Organic geometry and morph (equal point count) depend on a unique curve. 4d will pick a CR variant; 4e morph interpolation will bake it in.
|
||||
|
||||
### 19. Text: `maxWidth` “condensed horizontally” without a scale rule; missing `font` on the 17.9 geometry row
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.13 text; §17.9 `text` geometry fields omit `weight`, `italic`, `letterSpacing` that §17.13 adds
|
||||
- **What's wrong:** Condensing to `maxWidth` — uniform scale of glyphs? Tracking only? What if the run is already shorter? Overflow of 256 characters after resolution: diagnostic? Generic CSS families: metrics differ per platform, so text layout is **not** reproducible even though 17.14 claims procedural identity. That may be acceptable (like grain) but is not exempted.
|
||||
- **Why it matters for 4d:** 4d will measure text. Platform font metrics will fail any pixel fixture. The spec should either exempt text metrics from reproducibility or require a measurement rule (e.g. no pixel tests on text bounds).
|
||||
|
||||
### 20. Graphic system has no system-level transform/position; camera and particles do
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.7 / §17.8 vs §18.2 `position` on particles vs §19.3 camera
|
||||
- **What's wrong:** A `graphic` system’s objects sit in scene space with no system origin. Particles add a system `position` offset to the distribution. Authors cannot move a whole graphic system without grouping. Not a contradiction, but 4d must not invent a system transform for `graphic`.
|
||||
- **Why it matters for 4d:** Tempting to put a system matrix in the renderer. Spec does not allow it for `graphic`.
|
||||
|
||||
### 21. `lifetime` on a visual object vs system lifecycle vs particle lifetime
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.10 object `lifetime`; §17.7 / §19.2 system lifecycle; §18.2 particle `lifetime`
|
||||
- **What's wrong:** Object-level `lifetime` “removed from its container” has no state machine, no `release`, no ownership, no diagnostic, and is legal on persistent graphic content. Is that a 4d concern (remove from draw list on logical clock) or 4f? Removing a group’s child mid-run changes document key order used for equal-`z` ties. Spawned systems forbid lifecycle fields on persistent systems, but object `lifetime` remains legal on persistent content — a back door that looks like lifecycle.
|
||||
- **Why it matters for 4d:** The draw list is 4d’s. If objects can disappear without 19.2, 4d needs a removal path now.
|
||||
|
||||
### 22. Filters “after the object is drawn and before it composites into its layer”
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.12 *Filters* vs *Cost* (offscreen buffer)
|
||||
- **What's wrong:** CSS `filter` on a raster vs filter on vector before raster is different. Hue-rotate amount in degrees — range unset (unlike 0–4 / 0–1). Blend vs filter vs opacity vs layer opacity order: object opacity multiplies inherited and layer; blend requires offscreen; filters after draw. Glow/shadow vs filter order unset. `blur` style field vs `filters` vs post-effect `blur` — three blurs.
|
||||
- **Why it matters for 4d:** Offscreen pass graph is 4d. Ambiguous order → different pixels and different pass counts vs §19.5.
|
||||
|
||||
### 23. Clip inheritance and mask vs draw order
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.12 clipping / masking
|
||||
- **What's wrong:** Mask child “is not drawn; its rendered alpha multiplies the alpha of the group's remaining children.” Remaining children are still depth-sorted among themselves? Mask is rasterized untransformed? With the group’s transform? Does the mask include the mask child’s own style opacity? Clip intersected with ancestor — in which space after nested transforms?
|
||||
- **Why it matters for 4d:** Mask/clip are in traces 12. Need a unique raster definition.
|
||||
|
||||
### 24. Particle `size` ramp “multiplier on `render`'s own size” for `point` / `ellipse` / `component`
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §18.2 `size`
|
||||
- **What's wrong:** `point` has `pointSize`, not `size`. `ellipse` has `radius`. `component` has no size. Multiplying “size” is undefined for those. `rotation` at creation vs `angularVelocity` vs `align` on emitters.
|
||||
- **Why it matters for 4d:** Particle drawing is likely in 4d even if emission is 4e; the instance transform must be defined.
|
||||
|
||||
### 25. Particle / emitter motion in 3D vs 2D draw
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §18.2 integrator (`p`, `v`, `a` have z) vs §17.11 2D `M_local`
|
||||
- **What's wrong:** `p.z` updates; draw uses `z` for sort/fog/perspective scale only. Fine, but `align` “rotation … to its velocity direction” — 2D `atan2(vy,vx)` or 3D? Unspecified.
|
||||
- **Why it matters for 4d:** Emitter `align` is a 4d rotation if 4d draws emitted objects.
|
||||
|
||||
### 26. Index-driven `even` with `count === 1` and `i / (count - 1)`
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §18.3 `line` mode `even`; §18.5 `repeat.fraction` already special-cases `count === 1`
|
||||
- **What's wrong:** `i / (count - 1)` is division by zero when a burst or repeater has `count: 1`. Fraction documents the fix; distribution `even` does not.
|
||||
- **Why it matters for 4d:** Only if 4d implements distributions; still a landmine for 4e that the contract should close now.
|
||||
|
||||
### 27. Ring distribution: “not below `radius` is `ERR_INVALID_RANGE_ORDER`”
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §18.3 `ring` vs §17.9 ring primitive (`innerRadius` **must be less than** `radius`)
|
||||
- **What's wrong:** “not below `radius`” reads as `innerRadius >= radius` is the error, i.e. inner must be **below** radius — OK — but the wording is easy to invert. Primitive uses strict less-than; distribution does not say whether `innerRadius === radius` is a legal zero-width ring (perimeter) or an error. Primitive would error on equality.
|
||||
- **Why it matters for 4d:** Shared geometry helpers.
|
||||
|
||||
### 28. `grid` item count vs `repeater.count` / `columns * rows`
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §18.3 `grid`; §18.5 `count`
|
||||
- **What's wrong:** Grid is `columns × rows` cells. Repeater has an independent `count` up to 1024. If `count != columns * rows`, extra items wrap? Truncate? Error? Unspecified. `jitter` consumes samples even though “grid without jitter consumes no samples” — with jitter, order of x/y jitter vs depth sub-block is only “field order of its row,” and `jitter` is not ordered relative to `depth`.
|
||||
- **Why it matters for 4d/4e:** Placement fixtures (trace 10 of §18.10).
|
||||
|
||||
### 29. Behaviors: `oscillate` formula `phase / 360` with frequency in Hz
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §18.6 `oscillate` `center + amplitude * w(frequency * t + phase / 360)`
|
||||
- **What's wrong:** If `w` expects cycles, `frequency * t` is cycles and `phase/360` is cycles — OK. If `w` is `sin(2π · …)` that must be stated. `square`/`sawtooth` polarity (does square start high?) unset. `orbit` does not say whether it **sets** position or **adds** to it; composition says accumulate on position, which implies orbit is an offset — then `center` is absolute or relative?
|
||||
- **Why it matters for 4d:** 4d may stub behaviors; if it draws static poses only, less urgent. Still, `orbit` + `position` composition must be known before any motion lands.
|
||||
|
||||
### 30. `follow-path` `path` as sibling key vs component encapsulation
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §18.6 `follow-path`; §18.1 encapsulation
|
||||
- **What's wrong:** Sibling key inside a component is OK; from outside, `ERR_INVALID_REFERENCE`. Cross-system path follow: not mentioned. `path` as inline `commands` on a behavior duplicates 17.13 without vertex limits.
|
||||
- **Why it matters for 4d:** Low until 4e.
|
||||
|
||||
### 31. Morph “equal type, point count, and spline mode” vs rectangles / text / groups
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §18.6 *Morph compatibility* vs locked decision
|
||||
- **What's wrong:** Locked decision matches the spline rule. Morph of two `rectangle`s (no points) — same type, point count 0=0, no spline mode: legal lerp of `size`? Morph of `group`s? Morph of `text`? “Point count” for primitives without points is undefined. `path` command morph: equal command count or equal endpoint count?
|
||||
- **Why it matters for 4d:** Morph is 4e, but 4d may store geometry in a form that cannot lerp.
|
||||
|
||||
### 32. Noise: 12 edge-midpoint gradients of a cube, 256 permutation, Fisher–Yates
|
||||
|
||||
- **Severity:** Major (for 4e, but the algorithm is in-scope for this read)
|
||||
- **Location:** §18.7 *Coherent noise is normative*
|
||||
- **What's wrong:** Classic Perlin uses 12 **edge** vectors of a cube (the 12 midpoints of cube edges, yes). Permutation of 256 shuffled with one sample **per entry** — Fisher–Yates on 256 entries needs 256 samples if specified that way, but standard FY uses `i` from 255..1 with `j = floor(u * (i+1))`. The spec does not give the exact FY index formula or how a stream sample in `[0,1)` becomes `j`. Duplicate permutation (Perlin often repeats 0..255 twice to 512) unset. `curl` of a **scalar** 3D noise is zero if you take `∇ × (N, N, N)` or needs three offset noise fields; “curl of the noise potential” with one scalar is not a unique vector. This is the hardest reproducibility trap in §18.
|
||||
- **Why it matters for 4d:** If 4d does not implement fields, still: do not invent a noise helper in 4d that 4e must match. Spec should give a tabulated sample (trace 15 of §18.10 promises “documented sample values” that are not in the spec).
|
||||
- **Gap:** Trace 15 requires documented sample values; **the spec contains none**. That is a missing fixture, not just an algorithm gap.
|
||||
|
||||
### 33. `visuals.fields` vs §17.3 — fields are listed; OK. Field IDs vs system `fields` array order
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §18.7
|
||||
- **What's wrong:** Sum in system `fields` array order; addition commutative. Fine. `enabled` ValueSpec on a field — once at instantiation (17.14), so cannot be automated. Strength of a field is not in the automatable registry (locked). OK.
|
||||
|
||||
### 34. Trail `interval` default “one logical tick” — tick length is a runtime, not an exhibit constant
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §18.8 `interval` default; §9.1 logical tick
|
||||
- **What's wrong:** History sampled every tick by default means trail **shape depends on tick rate**, contradicting “a trail has the same shape at any **render** rate” (frame rate, not tick rate). Trace 17 of §18.10 only varies frame rate. If tick length differs across runtimes, trails diverge. Default should be a DurationSpec literal (e.g. the 9.1 tick) named explicitly.
|
||||
- **Why it matters for 4d:** If 4d allocates trail buffers, stride is unknown.
|
||||
|
||||
### 35. Links draw “before their system's items, at the system's own depth position”
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §18.8 vs §18.2 lowest-`z` particle as system sort key
|
||||
- **What's wrong:** Links at system depth vs particles sorted among themselves: links can draw in front of far particles of the same system. Maybe intended. `index` rule with `stride` on a changing particle pool — emitters cannot have links; particles can, and creation ordinals change under eviction. After oldest-first eviction, indices are not stable. Unspecified.
|
||||
- **Why it matters for 4d:** Draw order inside a system.
|
||||
|
||||
### 36. Automatable registry vs §8.1: emitter `rate` automatable but not bindable — registry allows it; graphic per-object properties automatable from **system** scope
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §19.1 registry “for a `graphic` system, any numeric `transform`, `style`, or geometry property of an object in its `content` tree”
|
||||
- **What's wrong:** Locked decision: automatable registry is **broader** than §8.1 — confirmed. But §17.14 said visual object fields resolve **once** and are constant for the object’s lifetime, with time variation from behaviors, automation, and external control. Automation writing `content.band.style.opacity` is therefore an exception to “constant for lifetime.” 4d must keep those fields **mutable** even though 17.14 sounds like they are baked. Also: `style.fill` as color is non-numeric and excluded; `strokeWidth` is included. `visible` on an **object** is boolean — not automatable; system `visible` is bindable only. Object `visible` has **no** writer except a once-resolved ValueSpec — same trap as layer `visible`.
|
||||
- **Why it matters for 4d:** 4d must not intern style as immutable GPU constants. Per-object opacity must remain a frame-time input for 4f automation.
|
||||
|
||||
### 37. Cross-scope `target` strings: relative paths vs `camera.zoom` vs `visuals.camera.zoom`
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §19.1 example `"target": "camera.zoom"`; §8.1 rows `visuals.camera.<field>`; §1.3 prefixes
|
||||
- **What's wrong:** Example targets are scope-relative (`camera.zoom`). §8.1 uses absolute `visuals.camera.zoom`. Bindings presumably need the absolute form. Automation in system scope “addressed relative to the system” — is `rate` or `content.hull.transform.rotation` the form? No ABNF. `effects[<index>]` vs `effects.0` — §1.3 says indexed form only.
|
||||
- **Why it matters for 4d:** Path parser is shared. Wrong grammar → every 4f track fails.
|
||||
|
||||
### 38. `loop` on visual tracks vs “audio track shape verbatim”
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §19.1 vs locked decision
|
||||
- **What's wrong:** Locked: audio shape **plus** `loop`. Text agrees. Ping-pong count = complete round trip: agrees. `infinite` loop on spawned system with finite lifetime is legal — OK. `infinite` “legal only on a persistent scope” in one sentence, then immediately allowed on spawned with finite lifetime — **contradiction in two consecutive sentences**.
|
||||
- **Why it matters for 4d:** Validation of `loop.count`.
|
||||
|
||||
### 39. `ERR_AUTOMATION_CONFLICT` for behavior + track on the same **object channel**
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §19.1 vs §18.6 composition
|
||||
- **What's wrong:** Locked decision matches. Unclear whether `oscillate` on `transform.scale.x` conflicts with a track on `transform.scale` (vector) or only `.x`. Unclear whether a system-level track on emitter `position.x` conflicts with a `drift` on each emitted item (different objects).
|
||||
- **Why it matters for 4d:** Conflict detection may live next to the property store 4d creates.
|
||||
|
||||
### 40. Lifecycle: `CREATED -> ACTIVE | FINISHED | FAILED` — when FINISHED from CREATED?
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §19.2 state table
|
||||
- **What's wrong:** No `SCHEDULED` (locked, OK). `CREATED -> FINISHED` is allowed but no cause (zero-lifetime spawn before first draw?). `release` default 0ms still one tick in `RELEASING` (locked, OK). Spawn at ceiling refused not evicted (locked, OK). `WARN_VISUAL_CEILING` once naming template — vs §19.5 “once per ceiling per second.” A refused spawn could warn every spawn or once per second; 19.2 says once (per event?), 19.5 says once per ceiling per second.
|
||||
- **Why it matters for 4d:** 4d may not spawn yet, but the warning rate is a shared diagnostic policy.
|
||||
|
||||
### 41. `cancelWithScenario: false` without persistent ownership is `ERR_UNSUPPORTED_TARGET`
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §19.2
|
||||
- **What's wrong:** That is a schema/semantic conflict, not an unsupported **target**. `ERR_SCHEMA_VALIDATION` or `ERR_UNKNOWN_FIELD` would fit; `ERR_UNSUPPORTED_TARGET` is the 8.1 capability miss. Wrong code will be wired into tests.
|
||||
- **Why it matters for 4d:** Low; 4f traces.
|
||||
|
||||
### 42. Camera matrix `R(-rotation)` vs object `R(rotation)`
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §19.3 `V = T(c) × S(zoom) × R(-rotation) × T(-c) × T(-p·Δ)`
|
||||
- **What's wrong:** Negating rotation is correct for a view matrix **if** object rotation uses the same convention. Combined with defect 4, the extra minus may double-correct. `T(-p * (x - c_x), …)` — camera `x,y` are scene-center defaults; `c` is **display** projection center after fit. Subtracting a scene coordinate from a display coordinate is a **unit/space mix** unless fit has already mapped them into one space. The formula never says whether `x,y` are converted to display pixels first.
|
||||
- **Why it matters for 4d:** This is the **normative camera matrix** 4d must implement. As written it mixes scene units and display units. Trace 13 of §19.7 cannot have a unique documented display point.
|
||||
|
||||
### 43. Parallax and perspective both claimed to affect “the layer”
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.6 parallax on camera translation; §19.3 perspective **per object** after `V`
|
||||
- **What's wrong:** Locked: parallax multiplies translation only — text agrees. Perspective is per-object `z`, so two objects on one layer at different `z` scale differently while sharing one `V`. A layer `parallax` does not change object `z`. OK if intended; worth stating that parallax is **not** a function of object `z` (only layer field).
|
||||
- **Why it matters for 4d:** Do not implement depth-based parallax.
|
||||
|
||||
### 44. Post-effects: `scanlines.spacing` in **device pixels** vs everything else in scene units
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §19.4 `scanlines.spacing`, `grain.scale` in device pixels; `blur.radius` scene units scaled by zoom
|
||||
- **What's wrong:** Reproducibility: scanline period changes with DPR and window size. Grain is exempt; scanlines are **not**. Two machines at different DPR get different line density. `speed` of scanlines has range “—” (unlimited?). `color-adjust.saturation` vs filter type `saturate` naming. Disabled effect “costs nothing that frame” vs pass budget counted how when `enabled` is a ValueSpec resolved once — cannot toggle unless automated; `enabled` is boolean so **not** automatable. So `enabled` is another once-only ValueSpec that cannot be bound (not in §8.1 except `.<param>` numeric). Cannot turn effects off at runtime despite the field.
|
||||
- **Why it matters for 4d:** Effect chain is after the renderer; 4d may still allocate the composited frame. Scanlines/grain in device pixels must be a conscious 4d/4f choice or exhibits are resolution-dependent without a warning.
|
||||
|
||||
### 45. `WARN_VISUAL_APPROXIMATION` used for three different events
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.1 / §17.12 conic fallback; §19.4 reduced-res blur/bloom and skipped unavailable effect; §19.5 shedding offscreen buffers also raises `WARN_VISUAL_APPROXIMATION` (not `WARN_VISUAL_CEILING`)
|
||||
- **What's wrong:** Buffer shedding is a ceiling event but uses the approximation warning. Authors cannot distinguish “GPU cannot conic” from “too many glow buffers.” Locked decision said reduced-res blur/bloom raises approximation once per effect instance — text agrees. Buffer shed using the same code is extra.
|
||||
- **Why it matters for 4d:** 4d implements glow/blur buffers and must pick a diagnostic.
|
||||
|
||||
### 46. §19.5 offscreen-buffer shed “farthest-`z` first” vs draw order farthest first
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §19.5 compositing buffers
|
||||
- **What's wrong:** Shed farthest-`z` first means distant fancy objects lose glow first (keep near effects). Opposite of painter priority. Fine if intended. “Draw the excess objects without their non-default blend/mask/blur/glow/filters” — dropping `mask` changes silhouette, which **is** a material substitution PRD 89 forbids, while the table claims neither kind of limit is silently transformed into something materially different.
|
||||
- **Why it matters for 4d:** Pass budget enforcement.
|
||||
|
||||
### 47. Authoring bound vs runtime ceiling: automation records listed in **both** tables
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §19.5 aggregate table “Automation records … Authoring bound (19.1), not a shed” **inside** the runtime-ceiling table
|
||||
- **What's wrong:** Mixes kinds. Spawned instances appear in both tables. Harmless but 4d/4f readers will implement a shed for automation that must not exist.
|
||||
- **Why it matters for 4d:** Don’t shed tracks.
|
||||
|
||||
### 48. §17.1 pipeline order vs actual data flow
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.1 “primitives → components → procedural systems → layers → camera → post effects → display”
|
||||
- **What's wrong:** Components and primitives are **inside** systems; systems **belong to** layers. Pipeline as a feed-forward list will mislead a 4d architecture (there is no “component pass” after primitives). Real order: tick systems → sort objects in layers → apply camera per layer → composite layers → effects.
|
||||
- **Why it matters for 4d:** Don’t build seven sequential passes matching that list.
|
||||
|
||||
### 49. §17.14 “Rendering consumes no procedural stream” vs grain vs wander offsets
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.14; §18.6 wander consumes stream **once at instantiation**; §19.4 grain exempt
|
||||
- **What's wrong:** Locked grain exemption is stated. 17.14’s “no visual analogue” of 14.6 is slightly overstated once grain exists. Not a 4d draw-loop sample bug if 4d respects 17.14.
|
||||
- **Why it matters for 4d:** Frame loop must not call the PRNG.
|
||||
|
||||
### 50. `components` root vs `components.visual`
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §1.1 `components`; §18.1 `components.visual.<id>`
|
||||
- **What's wrong:** Audio components live under the same `components` object (presumably `components.audio` from 15.x). 4d loader must not treat every `components` entry as visual. Out of scope to fully check 15.x; flag if 18.1 is the first mention of the `visual` subkey.
|
||||
- **Why it matters for 4d:** Schema.
|
||||
|
||||
### 51. Implicit layer vs `layer` field required-by-reference
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.5 “When `visuals.layers` is absent, one implicit layer … and a system's `layer` field is `ERR_INVALID_REFERENCE`.”
|
||||
- **What's wrong:** Clear: you may not name a layer if none were declared. Default `layer` on §17.7 is “implicit layer.” Consistent. If `layers` is present, omitting `layer` on a system — §17.7 default “implicit layer” which **does not exist**. So every system **must** set `layer` whenever `layers` is declared. Never stated as required-conditional.
|
||||
- **Why it matters for 4d:** Validation.
|
||||
|
||||
### 52. `cover`/`contain` and camera projection center
|
||||
|
||||
- **Severity:** Major (related to 11 and 42)
|
||||
- **Location:** §19.3 “projection center is the center of the **display** rectangle after `fit`”
|
||||
- **What's wrong:** Under `contain`, letterbox is background; projection center is display center, which may not be scene center on screen if letterboxing is not centered (defect 11). Under `cover`, cropped scene center may not match display center. Perspective about display center while scene origin is top-left is a specific choice that should have a numeric example.
|
||||
- **Why it matters for 4d:** Same as camera matrix.
|
||||
|
||||
### 53. Color alpha “multiplies the applicable opacity” vs fog vs layer
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.2 color; §17.12 opacity
|
||||
- **What's wrong:** `#rrggbbaa` × object opacity × group opacity × layer opacity × fog. Premultiply when, relative to blend `add`? Unspecified. Canvas 2D `globalAlpha` vs fillStyle alpha differ.
|
||||
- **Why it matters for 4d:** Every translucent primitive.
|
||||
|
||||
### 54. `ERR_INVALID_PRIMITIVE_TYPE` for fifteen types vs “Primitive Set 0.1 is fourteen”
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.9 vs §18.1 vs §17.15 table
|
||||
- **What's wrong:** Locked: fourteen closed; `component` is fifteenth object type. Text agrees. Diagnostic table §17.15 still says “not a member of Visual Primitive Set 0.1” without mentioning `component`. After 18.1, unknown type is “outside the fifteen.” 4d if implemented before 4e might reject `component` correctly as invalid primitive; 4e then accepts it. Trace 2 of 17.16 says unknown type is `ERR_INVALID_PRIMITIVE_TYPE` — `component` in a 4d-only renderer would fail that if exhibits use it.
|
||||
- **Why it matters for 4d:** 4d scope: parse `component` as a stub group or reject? Spec does not give a 4d subset.
|
||||
|
||||
### 55. Slice 4d is not given a subset of §17–19
|
||||
|
||||
- **Severity:** Blocker
|
||||
- **Location:** §19 opening “Slice 4d begins the renderer”; §17.16 traces 1–14 as 4d acceptance; traces 15–16 are 4h; §18.10 traces belong to 4e; §19.7 to 4f
|
||||
- **What's wrong:** 4d acceptance traces include perspective, fog, masks, conic fallback, bindings (trace 14), and “section 8.1 table unchanged.” They do **not** include camera matrix §19.3, yet §17.6 perspective is defined in terms of 19.3. 4d cannot implement trace 6 without 19.3, and 19.3 is a 4c/4f section. Circular implementation dependency.
|
||||
- **Why it matters for 4d:** The renderer core **must** implement a camera to pass 17.16.6, but camera is specified in 19.3 and tested in 19.7.13–14. 4d needs an explicit borrow of 19.3 or a reduced orthographic-only 4d gate.
|
||||
|
||||
---
|
||||
|
||||
## Standing decisions — compliance check
|
||||
|
||||
| Decision | Spec actually says it? | Contradicted elsewhere? |
|
||||
| --- | --- | --- |
|
||||
| Angles degrees, 0 along +x, positive toward +y | Yes, §17.2 | Yes — y-down top-left (§17.4) makes “toward +y” clockwise; not acknowledged |
|
||||
| Increasing z farther; sort greater z first; ties document key order | Yes, §17.6 | Partially — particle systems are atomic (§18.2); emitters/repeaters unspecified |
|
||||
| Perspective `focalLength / (focalLength + z)`; ortho: z for sort/parallax/fog only | Yes, §17.6, §19.3 | Formula vs `V` space mix (§19.3) |
|
||||
| Visual objects keyed, no `id` | Yes, §17.8 | No |
|
||||
| Transform order `T(pos+translate) × T(origin) × R × K × S × T(−origin)`; `M_parent × M_local` | Yes, §17.11 | `z` not in `M_local`; camera `V` separate |
|
||||
| ValueSpec once at instantiation; render consumes no stream | Yes, §17.14 | Automation mutates later (§19.1); grain exempt (§19.4) |
|
||||
| `layer` system property not object | Yes, §17.10 | No |
|
||||
| Depth fog per object, exact, no diagnostic | Yes, §17.6 | Color-space unspecified |
|
||||
| Conic only appearance fallback, warn once per paint | Yes, §17.12 | Fallback segment not unique |
|
||||
| `font` sans-serif/serif/monospace | Yes, §17.13 | No |
|
||||
| Live numeric text deferred | Yes, §17.13 | No |
|
||||
| Fourteen primitives closed; `component` fifteenth | Yes, §17.9, §18.1 | §17.15 cause text stale |
|
||||
| Component parameters number/boolean/string/color | Yes, §18.1 | No |
|
||||
| `inputs.*` / `repeat.*` construct-scoped | Yes, §18.1, §18.5 | No |
|
||||
| Expansion path is §9.3 key | Yes, §18.1 | No |
|
||||
| Integrator `v ← (v+a·dt)·(1−drag)^dt`; `p ← p+v·dt` | Yes, §18.2 | No |
|
||||
| Life ramps endpoints once | Yes, §18.2 | No |
|
||||
| Emission accumulator; `floor(rate·t)`; no reset | Yes, §18.4 | Particles say “Emission timing is 18.4” — OK |
|
||||
| `ERR_UNBOUNDED_EMISSION`; `count` alone legal | Yes, §18.2, §18.4 | No |
|
||||
| Pool oldest-first; aggregate §19.5 | Yes | No |
|
||||
| Index-driven + continuous rate = `ERR_INVALID_DISTRIBUTION` | Yes, §18.3 | No |
|
||||
| Sample order x,y,z; angle before radius | Yes, §18.3 | Polar `ring` field order in table is radius before angles — **sample** order vs **field** order clash |
|
||||
| Coherent noise 3D, 12 gradients, 256 perm, quintic, curl default | Yes, §18.7 | Curl of scalar potential not unique; no sample table |
|
||||
| Ribbons = `trail.mode`; `links` on emitter = `ERR_UNKNOWN_FIELD` | Yes, §18.8 | No |
|
||||
| Morph equal type, points, spline mode | Yes, §18.6 | Non-point primitives underspecified |
|
||||
| Exactly four §8.1 visual rows; no layer.visible | Yes, §19.1 | §17.16.14 and §17.3 contradict |
|
||||
| Two automation scopes; cross-scope `ERR_INVALID_REFERENCE` | Yes, §19.1 | `visuals.automation` vs unknown-field |
|
||||
| Track shape = §16.1 plus `loop`; ping-pong count = round trip | Yes | `infinite` only-persistent vs spawned-legal clash |
|
||||
| Automatable ⊃ bindable | Yes | Repeater `step` does not exist |
|
||||
| Behavior + track = `ERR_AUTOMATION_CONFLICT`; else pipeline then behaviors | Yes, §19.1 | Channel granularity fuzzy |
|
||||
| Lifecycle enum; fields on persistent = `ERR_UNKNOWN_FIELD`; spawn key `id#ordinal` | Yes, §19.2 | No |
|
||||
| Five states + FAILED; no SCHEDULED; release 0ms still one RELEASING tick | Yes | CREATED→FINISHED unexplained |
|
||||
| Spawn at ceiling refused, not evicted | Yes | Warn-once vs once-per-second |
|
||||
| Camera matrix normative; parallax translation only; projection authored once | Yes, §19.3 | Matrix mixes scene and display spaces |
|
||||
| Seven effects, array order; blur/bloom two passes; approx warn; skip not substitute | Yes, §19.4 | No |
|
||||
| Grain exempt, no stream | Yes | Scanlines also device-pixel but not exempt |
|
||||
| §19.5 centralized; values provisional | Yes | Automation row in runtime table |
|
||||
|
||||
**Sample-order clash (extra):** §18.3 “field order of its row” for `ring` is `center, radius, innerRadius, startAngle, endAngle, direction, mode`, but “polar forms angle before radius.” A fixture cannot satisfy both. **Severity: Major** for 4e; 4d should not implement distributions until this is fixed.
|
||||
|
||||
---
|
||||
|
||||
## What was checked and found sound
|
||||
|
||||
- Identifier regex and keyed `content`/`children` with no `id` field (§1.3, §17.8).
|
||||
- Strict unknown-field policy as a general rule (the `automation` hole is the exception, not the rule).
|
||||
- Three coordinate spaces named; `width`/`height` only for `virtual`; `viewport` rejects non-stretch `fit`.
|
||||
- Layer cap 16; group nest 8; polygon/polyline 512; spline 256; path commands 512; gradient stops 16; filters 4.
|
||||
- Transform composition string matches the locked order; hierarchy `M_parent × M_local` and `z_parent + z_local`.
|
||||
- Negative scale = mirror; scale 0 legal and draws nothing; skew magnitude ≥ 90 is `ERR_OUT_OF_BOUNDS`.
|
||||
- Safe blend set closed; stroke-only declared-fill vs inherited-fill distinction.
|
||||
- Path must start with `move`; zero-radius path arc is `ERR_OUT_OF_BOUNDS` not a line; bezier spline `3n+1`.
|
||||
- Fonts limited to three generic families; live numeric text explicitly deferred with rationale.
|
||||
- Component parameter types (four scalars) vs audio number-only; `inputs.*` not a §8.1 row.
|
||||
- Particle integrator formula and `dt`-correct drag; life-ramp-without-lifetime = `ERR_SCHEMA_VALIDATION`; exponential-through-zero reuses `WARN_AUTOMATION_FALLBACK`.
|
||||
- Unbounded emission diagnostic; initial `count` without lifetime legal; oldest-first eviction.
|
||||
- `links` on `emitter` = `ERR_UNKNOWN_FIELD`; ribbons as trail mode.
|
||||
- Four §8.1 rows only; `visuals.layers.<id>.visible` absent on purpose; system `visible` bindable not automatable.
|
||||
- Spawn refuse vs voice evict, with rationale; no `SCHEDULED` state; `0ms` release still enters `RELEASING`.
|
||||
- Projection not automatable; grain stream exemption and rationale.
|
||||
- Centralized ceiling table present; values labeled provisional pending 4h; runtimes may lower never raise.
|
||||
- Diagnostic reuse policy (`ERR_COMPONENT_RECURSION`, `ERR_AUTOMATION_CONFLICT` widened, etc.) is consistent in intent.
|
||||
|
||||
---
|
||||
|
||||
## 4d-specific recommendation (not a spec change, a sequencing note)
|
||||
|
||||
Do not start the renderer against §17.16 as a closed gate. Minimum contract fixes before 4d code:
|
||||
|
||||
1. Add `automation` to the §17.3 `visuals` key list.
|
||||
2. Rewrite §17.16.14 to match §19.1’s four rows.
|
||||
3. State Y-down + rotation sign in one sentence.
|
||||
4. State box/ellipse local origin.
|
||||
5. Give one numeric camera example (scene point → display point) that uses a single space for `V`.
|
||||
6. Borrow §19.3 into the 4d scope list, or drop perspective from 4d traces.
|
||||
7. Delete or define repeater `step`.
|
||||
8. Fix “layer nesting” in trace 9 to group nesting / layer count.
|
||||
|
||||
Until (3)–(5) land, two renderer cores can both “conform” and disagree on every rotated, zoomed, or letterboxed frame.
|
||||
+491
@@ -0,0 +1,491 @@
|
||||
# Visual contract review — Abacus AI Agent
|
||||
|
||||
Independent adversarial read of XZBT Format Specification **rev 0.7**, sections **17–19**, before Phase 4 slice **4d** (renderer core). Existing files under `reviews/` were not opened. Written **2026-09-05 21:48:22 UTC** as a new file so any earlier review of the same name is left untouched.
|
||||
|
||||
Standing decisions were treated as locked. This pass checks whether the spec text actually states them and whether it contradicts itself elsewhere.
|
||||
|
||||
Severity:
|
||||
|
||||
- **Blocker** — 4d cannot implement a unique, testable behavior without guessing.
|
||||
- **Major** — two conforming 4d implementations can diverge, or a later slice will have to unwind 4d choices.
|
||||
- **Minor** — underspecified edge, wrong diagnostic, or local inconsistency that 4d can paper over with a default.
|
||||
|
||||
---
|
||||
|
||||
## Defects
|
||||
|
||||
### 1. `visuals.automation` is both required and forbidden
|
||||
|
||||
- **Severity:** Blocker
|
||||
- **Location:** §17.3 *The `visuals` block* vs §19.1 *Two declaration scopes*
|
||||
- **What's wrong:** §17.3 enumerates `scene`, `layers`, `systems`, `fields`, `camera`, `effects` and then: “any other property of `visuals` is `ERR_UNKNOWN_FIELD`.” §19.1 places an `automation` array on `visuals` itself (`visuals.automation`). Under the strict unknown-field policy that array is illegal at import.
|
||||
- **Why it matters for 4d:** The renderer core will parse `visuals`. If it follows §17.3 it rejects every exhibit that uses the §19.1 example. If it follows §19.1 it violates the §17.3 table. Slice 4d must know the legal key set before it writes a schema or a loader.
|
||||
|
||||
### 2. Trace 14 of §17.16 forbids the four §8.1 visual rows §19.1 adds
|
||||
|
||||
- **Severity:** Blocker
|
||||
- **Location:** §17.16 trace 14 vs §17.14 last paragraph vs §19.1 *The visual rows of the section 8.1 target-capability table*
|
||||
- **What's wrong:** Trace 14: “A binding, `set`, or `override` addressing **any** visual property is `ERR_UNSUPPORTED_TARGET`, and the section 8.1 table is **unchanged by this slice**.” §19.1 then adds four families (`visuals.camera.<field>`, `visuals.layers.<id>.opacity`, `visuals.systems.<id>.visible`, `visuals.effects[<index>].<param>`) that **are** binding/override targets. §17.14 already describes those four rows as if they exist. The acceptance gate for 4d therefore requires the opposite of the closed contract.
|
||||
- **Why it matters for 4d:** 4d is the first code that will implement (or stub) target resolution. Implementing trace 14 literally means rejecting camera/layer/system/effect bindings that 4f traces 7 require to succeed. The 4d gate must be restated as “any visual property **outside the four §19.1 rows**.”
|
||||
|
||||
### 3. Automatable registry names a repeater `step` block that does not exist
|
||||
|
||||
- **Severity:** Blocker
|
||||
- **Location:** §19.1 *The automatable registry* (“for `repeater`, the numeric fields of its `step` block”) vs §18.5 field table
|
||||
- **What's wrong:** §18.5 has `repeat`, `count`, `distribution`, `position`, `inputs`, `behaviors`, `fields`, `links`. There is no `step` field, no `step` block, and no definition of what “numeric fields of its `step` block” are. Repeaters also have no `rate` (explicitly `ERR_UNKNOWN_FIELD`).
|
||||
- **Why it matters for 4d:** Less for the first draw path than for the property-path resolver 4d will share with automation. A registry row with no corresponding schema is an unimplementable target. Either define `step` or delete the row.
|
||||
|
||||
### 4. Y-down scene vs “positive angles toward +y”
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.2 *Canonical units* (“`0` points along `+x`; positive angles turn toward `+y`”) vs §17.4 (`x` and `y` run `0` to `1` **from the top-left**; virtual origin at top-left)
|
||||
- **What's wrong:** Top-left origin with `y` increasing downward is a left-handed screen space. “Positive toward `+y`” is then **clockwise** on the display (standard canvas), not the mathematical CCW convention a reader of “toward +y” in a Cartesian plane will assume. The spec never says whether rotation is screen-clockwise or math-CCW, and never says whether `+y` in local object space is down.
|
||||
- **Why it matters for 4d:** Transform order `R × K × S` is useless if the sign of `R` is ambiguous. Trace 3 of §17.16 (“a known local point maps to the documented device point”) cannot be written until this is fixed. Canvas 2D `rotate` is clockwise for y-down; a naive `cos/sin` toward +y with y-down will mirror every exhibit.
|
||||
|
||||
### 5. Local origin of box primitives is unspecified
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.9 `rectangle`, `rounded-rectangle`, `ellipse`, `ring`, `arc`; §17.11 `origin` “in the object's own local space”; §17.13 text `align` “relative to the object's origin”
|
||||
- **What's wrong:** For `rectangle` / `rounded-rectangle`, is `position` the top-left of `size`, the center, or something else? For `ellipse` / `ring` / `arc`, is `position` the center (implied by `radius` but never stated)? For `point`, is the dot centered on the origin? Text has `align`/`baseline`, which implies the origin is an anchor, but boxes have no equivalent. Default `origin` is `{0,0}`, so if a rectangle’s local space has its corner at 0, rotation is about the corner; if centered, about the center. These produce different pixels.
|
||||
- **Why it matters for 4d:** First thing the primitive rasterizer needs. Two implementations will disagree on every rotated panel and every `contain`/`cover` fixture in trace 1/3.
|
||||
|
||||
### 6. Perspective factor vs camera matrix: two post-multiplies, no combined formula
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.6 *Perspective scaling* (“object's transform is post-multiplied by `focalLength / (focalLength + z)` about the camera's projection center”) vs §19.3 (“applied **after** `V`”)
|
||||
- **What's wrong:** §17.11 already post-multiplies `M_parent × M_local`. “Post-multiplied” in §17.6 does not say whether the factor is applied in local space, after `M_local`, after hierarchy, or after `V`. §19.3 says after `V`, about the **display** projection center. Applying a uniform scale about the display center to an already-view-transformed point is not the same as scaling the object about the camera look-at in scene space. `z` used in the factor is never named as **effective** `z` (`z_parent + z_local` from §17.11), though that is the only reading that matches hierarchy.
|
||||
- **Why it matters for 4d:** Trace 6 of §17.16 and trace 14 of §19.7 are the 4d/4f camera tests. Without a single matrix (or a worked numeric example: one point, one `z`, one `V`, one output), 4d will pick an order that 4f then has to break.
|
||||
|
||||
### 7. `translate.z` is excluded from `M_local` but perspective needs a 3D point
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.11 “The `z` component of `translate` adds to the object's `z` and … is **not** part of `M_local`.”
|
||||
- **What's wrong:** `M_local` is therefore a 2D affine matrix. Perspective uses `z`. Hierarchy adds `z`. Particle integration moves `p` in 3D. The spec never says whether parent `M` rotates/scales child `z` (it cannot, if `M` is 2D) or only adds it. A child with local `z` under a rotated group: is depth the scalar sum, or a transformed coordinate? §17.11 says sum, which means rotation never tilts depth — consistent with 2.5D, but then “perspective about projection center” on a 2D matrix is a uniform XY scale, not a projective transform. That should be stated as “uniform XY scale, no vanishing-point projection.”
|
||||
- **Why it matters for 4d:** Implementers will reach for a 4×4 perspective matrix. The contract wants a 2D scale. If 4d ships a real projection, every later fixture fails.
|
||||
|
||||
### 8. Depth sort unit is inconsistent (object vs system vs particle cloud)
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.6 “Within one layer, **objects** are drawn farthest first” vs §18.2 “Particles of one system draw as **one unit** … The system's own position in its layer's depth sort is its **lowest-`z` particle**” vs §17.8 document key order of `content`
|
||||
- **What's wrong:** Graphic systems contain many objects with independent `z`. Are those objects sorted **across systems** in the layer, or is each system an atomic band like particles? §17.6 says objects; §18.2 special-cases particles so they never interleave with unrelated objects. Emitters create full objects — §18.4 never says whether those objects join the layer object-sort or stay an atomic emitter band. Repeaters: same gap. A graphic system with `z=0` group and a child at `z=100` vs a sibling system at `z=50` has two legal draw orders.
|
||||
- **Why it matters for 4d:** Painter’s algorithm is the entire 2.5D model. 4d must pick a sort key. Wrong choice is a visual bug in every multi-system scene and is expensive to change.
|
||||
|
||||
### 9. Layer `visible` is a ValueSpec but is not automatable, bindable, or in §8.1 — and still “advances”
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.5 `visible` ValueSpec\<boolean\>, default `true`; §19.1 “`visuals.layers.<id>.visible` is deliberately **not** in the table”; §17.14 “every such field is resolved **once**, at its owning object's instantiation boundary”
|
||||
- **What's wrong:** If layer `visible` resolves once, a ValueSpec is pointless except `random`/`choose` at activation. If it is meant to change, there is no writer: not automation (boolean), not binding (absent from §8.1), not `set`/`override`. The field is therefore a once-at-import flag dressed as a ValueSpec. Meanwhile system `visible` **is** an §8.1 binding target and is explicitly **not** automatable. The two `visible` flags look alike in §17 and behave unlike in §19.
|
||||
- **Why it matters for 4d:** 4d will implement layer skip. It needs to know: evaluate once at activation, or subscribe to the pipeline? Implementing a live binding for layer `visible` would violate the locked “deliberately absent” decision; treating it as constant makes the ValueSpec type a lie.
|
||||
|
||||
### 10. §17.16 trace 9 cites “9 layer nesting levels” — layers do not nest
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.16 trace 9 vs §17.5 (layers composite in **document key order**, no parent field) vs §17.9 (group nesting 8)
|
||||
- **What's wrong:** Trace 9: “`9` layer nesting levels … are each `ERR_VISUAL_LIMIT_EXCEEDED`.” There is no layer nesting in §17.5. Group nesting is 8. Layer **count** is 16. The acceptance test for 4d is unrunnable as written.
|
||||
- **Why it matters for 4d:** 4d’s exit gate includes a test that cannot be authored. Likely intent: group nesting 9 / 8, and separately 17 layers.
|
||||
|
||||
### 11. Fit mapping is underspecified for `cover` crop and letterbox placement
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.4 `fit`; §17.16 trace 1
|
||||
- **What's wrong:** `contain` letterboxes “the remainder with `background`” but does not say the scene is **centered** in the display (vs aligned top-left). `cover` “cropping the overflow” does not say which region is kept (centered crop vs origin-aligned). `viewport` ignores `fit` except that non-`stretch` is an error — so the only legal `fit` under `viewport` is `stretch`, which is also ignored. Device-pixel-ratio (§19.5) is applied after fit; the spec never gives the composed scene→CSS→device transform.
|
||||
- **Why it matters for 4d:** Trace 1 is the first 4d test and requires “expected display point” including letterbox offsets. Without centering/crop rules those points are not determined.
|
||||
|
||||
### 12. Camera default “scene center” under `viewport` is not a scene quantity
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §19.3 “the display center for `viewport`” as default `x`,`y`; camera is in “scene units”
|
||||
- **What's wrong:** Viewport scene units **are** CSS pixels of the display, origin top-left, and the scene has no intrinsic size. Defaulting the camera to “display center” makes the default camera depend on the window size. Two machines, same exhibit, different default view. That contradicts “an exhibit that never mentions the camera” looking identical (§19.3’s own rationale) and contradicts reproducibility across display sizes.
|
||||
- **Why it matters for 4d:** Identity camera vs “centered on display” are different matrices as soon as the canvas is not the design size. 4d will bake this into every frame.
|
||||
|
||||
### 13. Conic-gradient fallback axis is not a unique segment
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.12 *Conic gradient fallback*
|
||||
- **What's wrong:** Fallback is “a `linear-gradient` with the same stops along the axis from `center` at `angle` to the paint's bounding-box edge.” A ray from `center` at `angle` intersects a bounding box at **one** point only if `center` is inside the box; if `center` is outside there may be two intersections or none. “Bounding-box edge” does not name which edge. Stop mapping from a 360° conic onto a 1D axis is lossy; the spec does not say whether offsets are used as-is (wrong) or the 0°–180° half is taken.
|
||||
- **Why it matters for 4d:** This is the **only** appearance fallback 4d must implement, and trace 10 requires a documented linear gradient. Without a unique segment, the warning can fire and the pixels still differ.
|
||||
|
||||
### 14. Fog blends “resolved fill, stroke, glow, and shadow colors” — gradients and alpha unspecified
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.6 *Depth fog*
|
||||
- **What's wrong:** Fog fraction is exact. What is “blended toward `color`”? For a gradient, every stop? The rasterized samples? Glow/shadow have their own colors; is the fog color’s alpha used? Premultiplied? `density * clamp(...)` as a lerp in sRGB or linear? Particle `color` ramps already interpolate sRGB; fog does not say. Objects with `fill: null` still have glow/shadow.
|
||||
- **Why it matters for 4d:** Trace 7 asks for blended colors at near/mid/far. Without a color space and a rule for paints, the trace has no oracle.
|
||||
|
||||
### 15. Stroke-only primitives vs `point` fill; `style.fill` on `line` vs table
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.9 `line` “`style.fill` on a `line` is `ERR_UNKNOWN_FIELD`” vs §17.12 *Stroke-only primitives* (`line`, `polyline`, `arc`, `bezier` — **not** `point`); `point` is “a filled dot”
|
||||
- **What's wrong:** `point` uses fill, not stroke, but is not listed as fill-only (`style.stroke` on a point?). `polyline` is stroke-only in §17.9 notes but the stroke-only ERR list in §17.12 omits nothing it listed — OK — yet `path`/`spline` can be open and still accept fill. Open path with fill: legal? Canvas fills open subpaths by closing them; the spec is silent.
|
||||
- **Why it matters for 4d:** Open-path fill is a classic renderer fork.
|
||||
|
||||
### 16. Arc sweep, direction, and “magnitude exceeds 360”
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.9 `arc` / `ring`; §17.2 angles
|
||||
- **What's wrong:** `startAngle`, `endAngle`, `direction` `clockwise` | `counter-clockwise`. Is the sweep the directed difference following `direction`, or the linear difference `end - start` with `direction` flipping the canvas arc flag? A sweep of 0: nothing, or a full ring (ring says “angles default to a full ring” — default values never given numerically)? `ERR_OUT_OF_BOUNDS` when magnitude exceeds 360: is that `|end-start|` or the directed sweep after wrapping? `end = start + 360` with clockwise: full circle or error?
|
||||
- **Why it matters for 4d:** Arc/ring are in Primitive Set 0.1; 4d must emit a unique path.
|
||||
|
||||
### 17. Path `arc` command vs SVG sweep/largeArc in y-down space
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.13 path `op: arc`
|
||||
- **What's wrong:** Endpoint-parameterized elliptical arc with `largeArc` and `sweep` booleans. SVG’s `sweep` is “positive angle” in y-down (clockwise). The visual contract’s positive angle is “toward +y.” If those disagree (defect 4), path arcs and `arc` primitives will rotate opposite ways.
|
||||
- **Why it matters for 4d:** Path vs primitive inconsistency will show up in Exhibit geometry immediately.
|
||||
|
||||
### 18. Spline Catmull-Rom: phantom points, closed form, and tension mapping
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.13 *Spline*
|
||||
- **What's wrong:** Open spline: “first and last points are duplicated as the phantom endpoints.” That is one CR parameterization (uniform, centripetal?). `tension` `0` to `1` default `0.5` is not mapped to a formula (Kochanek–Bartels? `tau = 1 - tension`?). Closed spline: no phantoms specified; standard is wrap indices. `bezier` mode `count = 3n+1` is for **open** cubics; a `closed` bezier spline with that count cannot close smoothly without extra points — interaction of `closed` + `bezier` is undefined.
|
||||
- **Why it matters for 4d:** Organic geometry and morph (equal point count) depend on a unique curve. 4d will pick a CR variant; 4e morph interpolation will bake it in.
|
||||
|
||||
### 19. Text: `maxWidth` “condensed horizontally” without a scale rule; missing `font` on the 17.9 geometry row
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.13 text; §17.9 `text` geometry fields omit `weight`, `italic`, `letterSpacing` that §17.13 adds
|
||||
- **What's wrong:** Condensing to `maxWidth` — uniform scale of glyphs? Tracking only? What if the run is already shorter? Overflow of 256 characters after resolution: diagnostic? Generic CSS families: metrics differ per platform, so text layout is **not** reproducible even though 17.14 claims procedural identity. That may be acceptable (like grain) but is not exempted.
|
||||
- **Why it matters for 4d:** 4d will measure text. Platform font metrics will fail any pixel fixture. The spec should either exempt text metrics from reproducibility or require a measurement rule (e.g. no pixel tests on text bounds).
|
||||
|
||||
### 20. Graphic system has no system-level transform/position; camera and particles do
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.7 / §17.8 vs §18.2 `position` on particles vs §19.3 camera
|
||||
- **What's wrong:** A `graphic` system’s objects sit in scene space with no system origin. Particles add a system `position` offset to the distribution. Authors cannot move a whole graphic system without grouping. Not a contradiction, but 4d must not invent a system transform for `graphic`.
|
||||
- **Why it matters for 4d:** Tempting to put a system matrix in the renderer. Spec does not allow it for `graphic`.
|
||||
|
||||
### 21. `lifetime` on a visual object vs system lifecycle vs particle lifetime
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §17.10 object `lifetime`; §17.7 / §19.2 system lifecycle; §18.2 particle `lifetime`
|
||||
- **What's wrong:** Object-level `lifetime` “removed from its container” has no state machine, no `release`, no ownership, no diagnostic, and is legal on persistent graphic content. Is that a 4d concern (remove from draw list on logical clock) or 4f? Removing a group’s child mid-run changes document key order used for equal-`z` ties. Spawned systems forbid lifecycle fields on persistent systems, but object `lifetime` remains legal on persistent content — a back door that looks like lifecycle.
|
||||
- **Why it matters for 4d:** The draw list is 4d’s. If objects can disappear without 19.2, 4d needs a removal path now.
|
||||
|
||||
### 22. Filters “after the object is drawn and before it composites into its layer”
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.12 *Filters* vs *Cost* (offscreen buffer)
|
||||
- **What's wrong:** CSS `filter` on a raster vs filter on vector before raster is different. Hue-rotate amount in degrees — range unset (unlike 0–4 / 0–1). Blend vs filter vs opacity vs layer opacity order: object opacity multiplies inherited and layer; blend requires offscreen; filters after draw. Glow/shadow vs filter order unset. `blur` style field vs `filters` vs post-effect `blur` — three blurs.
|
||||
- **Why it matters for 4d:** Offscreen pass graph is 4d. Ambiguous order → different pixels and different pass counts vs §19.5.
|
||||
|
||||
### 23. Clip inheritance and mask vs draw order
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.12 clipping / masking
|
||||
- **What's wrong:** Mask child “is not drawn; its rendered alpha multiplies the alpha of the group's remaining children.” Remaining children are still depth-sorted among themselves? Mask is rasterized untransformed? With the group’s transform? Does the mask include the mask child’s own style opacity? Clip intersected with ancestor — in which space after nested transforms?
|
||||
- **Why it matters for 4d:** Mask/clip are in traces 12. Need a unique raster definition.
|
||||
|
||||
### 24. Particle `size` ramp “multiplier on `render`'s own size” for `point` / `ellipse` / `component`
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §18.2 `size`
|
||||
- **What's wrong:** `point` has `pointSize`, not `size`. `ellipse` has `radius`. `component` has no size. Multiplying “size” is undefined for those. `rotation` at creation vs `angularVelocity` vs `align` on emitters.
|
||||
- **Why it matters for 4d:** Particle drawing is likely in 4d even if emission is 4e; the instance transform must be defined.
|
||||
|
||||
### 25. Particle / emitter motion in 3D vs 2D draw
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §18.2 integrator (`p`, `v`, `a` have z) vs §17.11 2D `M_local`
|
||||
- **What's wrong:** `p.z` updates; draw uses `z` for sort/fog/perspective scale only. Fine, but `align` “rotation … to its velocity direction” — 2D `atan2(vy,vx)` or 3D? Unspecified.
|
||||
- **Why it matters for 4d:** Emitter `align` is a 4d rotation if 4d draws emitted objects.
|
||||
|
||||
### 26. Index-driven `even` with `count === 1` and `i / (count - 1)`
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §18.3 `line` mode `even`; §18.5 `repeat.fraction` already special-cases `count === 1`
|
||||
- **What's wrong:** `i / (count - 1)` is division by zero when a burst or repeater has `count: 1`. Fraction documents the fix; distribution `even` does not.
|
||||
- **Why it matters for 4d:** Only if 4d implements distributions; still a landmine for 4e that the contract should close now.
|
||||
|
||||
### 27. Ring distribution: “not below `radius` is `ERR_INVALID_RANGE_ORDER`”
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §18.3 `ring` vs §17.9 ring primitive (`innerRadius` **must be less than** `radius`)
|
||||
- **What's wrong:** “not below `radius`” reads as `innerRadius >= radius` is the error, i.e. inner must be **below** radius — OK — but the wording is easy to invert. Primitive uses strict less-than; distribution does not say whether `innerRadius === radius` is a legal zero-width ring (perimeter) or an error. Primitive would error on equality.
|
||||
- **Why it matters for 4d:** Shared geometry helpers.
|
||||
|
||||
### 28. `grid` item count vs `repeater.count` / `columns * rows`
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §18.3 `grid`; §18.5 `count`
|
||||
- **What's wrong:** Grid is `columns × rows` cells. Repeater has an independent `count` up to 1024. If `count != columns * rows`, extra items wrap? Truncate? Error? Unspecified. `jitter` consumes samples even though “grid without jitter consumes no samples” — with jitter, order of x/y jitter vs depth sub-block is only “field order of its row,” and `jitter` is not ordered relative to `depth`.
|
||||
- **Why it matters for 4d/4e:** Placement fixtures (trace 10 of §18.10).
|
||||
|
||||
### 29. Behaviors: `oscillate` formula `phase / 360` with frequency in Hz
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §18.6 `oscillate` `center + amplitude * w(frequency * t + phase / 360)`
|
||||
- **What's wrong:** If `w` expects cycles, `frequency * t` is cycles and `phase/360` is cycles — OK. If `w` is `sin(2π · …)` that must be stated. `square`/`sawtooth` polarity (does square start high?) unset. `orbit` does not say whether it **sets** position or **adds** to it; composition says accumulate on position, which implies orbit is an offset — then `center` is absolute or relative?
|
||||
- **Why it matters for 4d:** 4d may stub behaviors; if it draws static poses only, less urgent. Still, `orbit` + `position` composition must be known before any motion lands.
|
||||
|
||||
### 30. `follow-path` `path` as sibling key vs component encapsulation
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §18.6 `follow-path`; §18.1 encapsulation
|
||||
- **What's wrong:** Sibling key inside a component is OK; from outside, `ERR_INVALID_REFERENCE`. Cross-system path follow: not mentioned. `path` as inline `commands` on a behavior duplicates 17.13 without vertex limits.
|
||||
- **Why it matters for 4d:** Low until 4e.
|
||||
|
||||
### 31. Morph “equal type, point count, and spline mode” vs rectangles / text / groups
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §18.6 *Morph compatibility* vs locked decision
|
||||
- **What's wrong:** Locked decision matches the spline rule. Morph of two `rectangle`s (no points) — same type, point count 0=0, no spline mode: legal lerp of `size`? Morph of `group`s? Morph of `text`? “Point count” for primitives without points is undefined. `path` command morph: equal command count or equal endpoint count?
|
||||
- **Why it matters for 4d:** Morph is 4e, but 4d may store geometry in a form that cannot lerp.
|
||||
|
||||
### 32. Noise: 12 edge-midpoint gradients of a cube, 256 permutation, Fisher–Yates
|
||||
|
||||
- **Severity:** Major (for 4e, but the algorithm is in-scope for this read)
|
||||
- **Location:** §18.7 *Coherent noise is normative*
|
||||
- **What's wrong:** Classic Perlin uses 12 **edge** vectors of a cube (the 12 midpoints of cube edges, yes). Permutation of 256 shuffled with one sample **per entry** — Fisher–Yates on 256 entries needs 256 samples if specified that way, but standard FY uses `i` from 255..1 with `j = floor(u * (i+1))`. The spec does not give the exact FY index formula or how a stream sample in `[0,1)` becomes `j`. Duplicate permutation (Perlin often repeats 0..255 twice to 512) unset. `curl` of a **scalar** 3D noise is zero if you take `∇ × (N, N, N)` or needs three offset noise fields; “curl of the noise potential” with one scalar is not a unique vector. This is the hardest reproducibility trap in §18.
|
||||
- **Why it matters for 4d:** If 4d does not implement fields, still: do not invent a noise helper in 4d that 4e must match. Spec should give a tabulated sample (trace 15 of §18.10 promises “documented sample values” that are not in the spec).
|
||||
- **Gap:** Trace 15 requires documented sample values; **the spec contains none**. That is a missing fixture, not just an algorithm gap.
|
||||
|
||||
### 33. `visuals.fields` vs §17.3 — fields are listed; OK. Field IDs vs system `fields` array order
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §18.7
|
||||
- **What's wrong:** Sum in system `fields` array order; addition commutative. Fine. `enabled` ValueSpec on a field — once at instantiation (17.14), so cannot be automated. Strength of a field is not in the automatable registry (locked). OK.
|
||||
|
||||
### 34. Trail `interval` default “one logical tick” — tick length is a runtime, not an exhibit constant
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §18.8 `interval` default; §9.1 logical tick
|
||||
- **What's wrong:** History sampled every tick by default means trail **shape depends on tick rate**, contradicting “a trail has the same shape at any **render** rate” (frame rate, not tick rate). Trace 17 of §18.10 only varies frame rate. If tick length differs across runtimes, trails diverge. Default should be a DurationSpec literal (e.g. the 9.1 tick) named explicitly.
|
||||
- **Why it matters for 4d:** If 4d allocates trail buffers, stride is unknown.
|
||||
|
||||
### 35. Links draw “before their system's items, at the system's own depth position”
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §18.8 vs §18.2 lowest-`z` particle as system sort key
|
||||
- **What's wrong:** Links at system depth vs particles sorted among themselves: links can draw in front of far particles of the same system. Maybe intended. `index` rule with `stride` on a changing particle pool — emitters cannot have links; particles can, and creation ordinals change under eviction. After oldest-first eviction, indices are not stable. Unspecified.
|
||||
- **Why it matters for 4d:** Draw order inside a system.
|
||||
|
||||
### 36. Automatable registry vs §8.1: emitter `rate` automatable but not bindable — registry allows it; graphic per-object properties automatable from **system** scope
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §19.1 registry “for a `graphic` system, any numeric `transform`, `style`, or geometry property of an object in its `content` tree”
|
||||
- **What's wrong:** Locked decision: automatable registry is **broader** than §8.1 — confirmed. But §17.14 said visual object fields resolve **once** and are constant for the object’s lifetime, with time variation from behaviors, automation, and external control. Automation writing `content.band.style.opacity` is therefore an exception to “constant for lifetime.” 4d must keep those fields **mutable** even though 17.14 sounds like they are baked. Also: `style.fill` as color is non-numeric and excluded; `strokeWidth` is included. `visible` on an **object** is boolean — not automatable; system `visible` is bindable only. Object `visible` has **no** writer except a once-resolved ValueSpec — same trap as layer `visible`.
|
||||
- **Why it matters for 4d:** 4d must not intern style as immutable GPU constants. Per-object opacity must remain a frame-time input for 4f automation.
|
||||
|
||||
### 37. Cross-scope `target` strings: relative paths vs `camera.zoom` vs `visuals.camera.zoom`
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §19.1 example `"target": "camera.zoom"`; §8.1 rows `visuals.camera.<field>`; §1.3 prefixes
|
||||
- **What's wrong:** Example targets are scope-relative (`camera.zoom`). §8.1 uses absolute `visuals.camera.zoom`. Bindings presumably need the absolute form. Automation in system scope “addressed relative to the system” — is `rate` or `content.hull.transform.rotation` the form? No ABNF. `effects[<index>]` vs `effects.0` — §1.3 says indexed form only.
|
||||
- **Why it matters for 4d:** Path parser is shared. Wrong grammar → every 4f track fails.
|
||||
|
||||
### 38. `loop` on visual tracks vs “audio track shape verbatim”
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §19.1 vs locked decision
|
||||
- **What's wrong:** Locked: audio shape **plus** `loop`. Text agrees. Ping-pong count = complete round trip: agrees. `infinite` loop on spawned system with finite lifetime is legal — OK. `infinite` “legal only on a persistent scope” in one sentence, then immediately allowed on spawned with finite lifetime — **contradiction in two consecutive sentences**.
|
||||
- **Why it matters for 4d:** Validation of `loop.count`.
|
||||
|
||||
### 39. `ERR_AUTOMATION_CONFLICT` for behavior + track on the same **object channel**
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §19.1 vs §18.6 composition
|
||||
- **What's wrong:** Locked decision matches. Unclear whether `oscillate` on `transform.scale.x` conflicts with a track on `transform.scale` (vector) or only `.x`. Unclear whether a system-level track on emitter `position.x` conflicts with a `drift` on each emitted item (different objects).
|
||||
- **Why it matters for 4d:** Conflict detection may live next to the property store 4d creates.
|
||||
|
||||
### 40. Lifecycle: `CREATED -> ACTIVE | FINISHED | FAILED` — when FINISHED from CREATED?
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §19.2 state table
|
||||
- **What's wrong:** No `SCHEDULED` (locked, OK). `CREATED -> FINISHED` is allowed but no cause (zero-lifetime spawn before first draw?). `release` default 0ms still one tick in `RELEASING` (locked, OK). Spawn at ceiling refused not evicted (locked, OK). `WARN_VISUAL_CEILING` once naming template — vs §19.5 “once per ceiling per second.” A refused spawn could warn every spawn or once per second; 19.2 says once (per event?), 19.5 says once per ceiling per second.
|
||||
- **Why it matters for 4d:** 4d may not spawn yet, but the warning rate is a shared diagnostic policy.
|
||||
|
||||
### 41. `cancelWithScenario: false` without persistent ownership is `ERR_UNSUPPORTED_TARGET`
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §19.2
|
||||
- **What's wrong:** That is a schema/semantic conflict, not an unsupported **target**. `ERR_SCHEMA_VALIDATION` or `ERR_UNKNOWN_FIELD` would fit; `ERR_UNSUPPORTED_TARGET` is the 8.1 capability miss. Wrong code will be wired into tests.
|
||||
- **Why it matters for 4d:** Low; 4f traces.
|
||||
|
||||
### 42. Camera matrix `R(-rotation)` vs object `R(rotation)`
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §19.3 `V = T(c) × S(zoom) × R(-rotation) × T(-c) × T(-p·Δ)`
|
||||
- **What's wrong:** Negating rotation is correct for a view matrix **if** object rotation uses the same convention. Combined with defect 4, the extra minus may double-correct. `T(-p * (x - c_x), …)` — camera `x,y` are scene-center defaults; `c` is **display** projection center after fit. Subtracting a scene coordinate from a display coordinate is a **unit/space mix** unless fit has already mapped them into one space. The formula never says whether `x,y` are converted to display pixels first.
|
||||
- **Why it matters for 4d:** This is the **normative camera matrix** 4d must implement. As written it mixes scene units and display units. Trace 13 of §19.7 cannot have a unique documented display point.
|
||||
|
||||
### 43. Parallax and perspective both claimed to affect “the layer”
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.6 parallax on camera translation; §19.3 perspective **per object** after `V`
|
||||
- **What's wrong:** Locked: parallax multiplies translation only — text agrees. Perspective is per-object `z`, so two objects on one layer at different `z` scale differently while sharing one `V`. A layer `parallax` does not change object `z`. OK if intended; worth stating that parallax is **not** a function of object `z` (only layer field).
|
||||
- **Why it matters for 4d:** Do not implement depth-based parallax.
|
||||
|
||||
### 44. Post-effects: `scanlines.spacing` in **device pixels** vs everything else in scene units
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §19.4 `scanlines.spacing`, `grain.scale` in device pixels; `blur.radius` scene units scaled by zoom
|
||||
- **What's wrong:** Reproducibility: scanline period changes with DPR and window size. Grain is exempt; scanlines are **not**. Two machines at different DPR get different line density. `speed` of scanlines has range “—” (unlimited?). `color-adjust.saturation` vs filter type `saturate` naming. Disabled effect “costs nothing that frame” vs pass budget counted how when `enabled` is a ValueSpec resolved once — cannot toggle unless automated; `enabled` is boolean so **not** automatable. So `enabled` is another once-only ValueSpec that cannot be bound (not in §8.1 except `.<param>` numeric). Cannot turn effects off at runtime despite the field.
|
||||
- **Why it matters for 4d:** Effect chain is after the renderer; 4d may still allocate the composited frame. Scanlines/grain in device pixels must be a conscious 4d/4f choice or exhibits are resolution-dependent without a warning.
|
||||
|
||||
### 45. `WARN_VISUAL_APPROXIMATION` used for three different events
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.1 / §17.12 conic fallback; §19.4 reduced-res blur/bloom and skipped unavailable effect; §19.5 shedding offscreen buffers also raises `WARN_VISUAL_APPROXIMATION` (not `WARN_VISUAL_CEILING`)
|
||||
- **What's wrong:** Buffer shedding is a ceiling event but uses the approximation warning. Authors cannot distinguish “GPU cannot conic” from “too many glow buffers.” Locked decision said reduced-res blur/bloom raises approximation once per effect instance — text agrees. Buffer shed using the same code is extra.
|
||||
- **Why it matters for 4d:** 4d implements glow/blur buffers and must pick a diagnostic.
|
||||
|
||||
### 46. §19.5 offscreen-buffer shed “farthest-`z` first” vs draw order farthest first
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §19.5 compositing buffers
|
||||
- **What's wrong:** Shed farthest-`z` first means distant fancy objects lose glow first (keep near effects). Opposite of painter priority. Fine if intended. “Draw the excess objects without their non-default blend/mask/blur/glow/filters” — dropping `mask` changes silhouette, which **is** a material substitution PRD 89 forbids, while the table claims neither kind of limit is silently transformed into something materially different.
|
||||
- **Why it matters for 4d:** Pass budget enforcement.
|
||||
|
||||
### 47. Authoring bound vs runtime ceiling: automation records listed in **both** tables
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §19.5 aggregate table “Automation records … Authoring bound (19.1), not a shed” **inside** the runtime-ceiling table
|
||||
- **What's wrong:** Mixes kinds. Spawned instances appear in both tables. Harmless but 4d/4f readers will implement a shed for automation that must not exist.
|
||||
- **Why it matters for 4d:** Don’t shed tracks.
|
||||
|
||||
### 48. §17.1 pipeline order vs actual data flow
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.1 “primitives → components → procedural systems → layers → camera → post effects → display”
|
||||
- **What's wrong:** Components and primitives are **inside** systems; systems **belong to** layers. Pipeline as a feed-forward list will mislead a 4d architecture (there is no “component pass” after primitives). Real order: tick systems → sort objects in layers → apply camera per layer → composite layers → effects.
|
||||
- **Why it matters for 4d:** Don’t build seven sequential passes matching that list.
|
||||
|
||||
### 49. §17.14 “Rendering consumes no procedural stream” vs grain vs wander offsets
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.14; §18.6 wander consumes stream **once at instantiation**; §19.4 grain exempt
|
||||
- **What's wrong:** Locked grain exemption is stated. 17.14’s “no visual analogue” of 14.6 is slightly overstated once grain exists. Not a 4d draw-loop sample bug if 4d respects 17.14.
|
||||
- **Why it matters for 4d:** Frame loop must not call the PRNG.
|
||||
|
||||
### 50. `components` root vs `components.visual`
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §1.1 `components`; §18.1 `components.visual.<id>`
|
||||
- **What's wrong:** Audio components live under the same `components` object (presumably `components.audio` from 15.x). 4d loader must not treat every `components` entry as visual. Out of scope to fully check 15.x; flag if 18.1 is the first mention of the `visual` subkey.
|
||||
- **Why it matters for 4d:** Schema.
|
||||
|
||||
### 51. Implicit layer vs `layer` field required-by-reference
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.5 “When `visuals.layers` is absent, one implicit layer … and a system's `layer` field is `ERR_INVALID_REFERENCE`.”
|
||||
- **What's wrong:** Clear: you may not name a layer if none were declared. Default `layer` on §17.7 is “implicit layer.” Consistent. If `layers` is present, omitting `layer` on a system — §17.7 default “implicit layer” which **does not exist**. So every system **must** set `layer` whenever `layers` is declared. Never stated as required-conditional.
|
||||
- **Why it matters for 4d:** Validation.
|
||||
|
||||
### 52. `cover`/`contain` and camera projection center
|
||||
|
||||
- **Severity:** Major (related to 11 and 42)
|
||||
- **Location:** §19.3 “projection center is the center of the **display** rectangle after `fit`”
|
||||
- **What's wrong:** Under `contain`, letterbox is background; projection center is display center, which may not be scene center on screen if letterboxing is not centered (defect 11). Under `cover`, cropped scene center may not match display center. Perspective about display center while scene origin is top-left is a specific choice that should have a numeric example.
|
||||
- **Why it matters for 4d:** Same as camera matrix.
|
||||
|
||||
### 53. Color alpha “multiplies the applicable opacity” vs fog vs layer
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.2 color; §17.12 opacity
|
||||
- **What's wrong:** `#rrggbbaa` × object opacity × group opacity × layer opacity × fog. Premultiply when, relative to blend `add`? Unspecified. Canvas 2D `globalAlpha` vs fillStyle alpha differ.
|
||||
- **Why it matters for 4d:** Every translucent primitive.
|
||||
|
||||
### 54. `ERR_INVALID_PRIMITIVE_TYPE` for fifteen types vs “Primitive Set 0.1 is fourteen”
|
||||
|
||||
- **Severity:** Minor
|
||||
- **Location:** §17.9 vs §18.1 vs §17.15 table
|
||||
- **What's wrong:** Locked: fourteen closed; `component` is fifteenth object type. Text agrees. Diagnostic table §17.15 still says “not a member of Visual Primitive Set 0.1” without mentioning `component`. After 18.1, unknown type is “outside the fifteen.” 4d if implemented before 4e might reject `component` correctly as invalid primitive; 4e then accepts it. Trace 2 of 17.16 says unknown type is `ERR_INVALID_PRIMITIVE_TYPE` — `component` in a 4d-only renderer would fail that if exhibits use it.
|
||||
- **Why it matters for 4d:** 4d scope: parse `component` as a stub group or reject? Spec does not give a 4d subset.
|
||||
|
||||
### 55. Slice 4d is not given a subset of §17–19
|
||||
|
||||
- **Severity:** Blocker
|
||||
- **Location:** §19 opening “Slice 4d begins the renderer”; §17.16 traces 1–14 as 4d acceptance; traces 15–16 are 4h; §18.10 traces belong to 4e; §19.7 to 4f
|
||||
- **What's wrong:** 4d acceptance traces include perspective, fog, masks, conic fallback, bindings (trace 14), and “section 8.1 table unchanged.” They do **not** include camera matrix §19.3, yet §17.6 perspective is defined in terms of 19.3. 4d cannot implement trace 6 without 19.3, and 19.3 is a 4c/4f section. Circular implementation dependency.
|
||||
- **Why it matters for 4d:** The renderer core **must** implement a camera to pass 17.16.6, but camera is specified in 19.3 and tested in 19.7.13–14. 4d needs an explicit borrow of 19.3 or a reduced orthographic-only 4d gate.
|
||||
|
||||
### 56. Ring distribution sample order vs “angle before radius”
|
||||
|
||||
- **Severity:** Major
|
||||
- **Location:** §18.3 *Sample consumption* vs `ring` field table
|
||||
- **What's wrong:** Sample order is normative: “`x` before `y` before `z`, and for polar forms angle before radius.” The `ring` row lists `center, radius, innerRadius, startAngle, endAngle, direction, mode`. “Field order of its row” therefore samples radius before angle. A fixture cannot satisfy both sentences.
|
||||
- **Why it matters for 4d/4e:** Placement reproducibility.
|
||||
|
||||
---
|
||||
|
||||
## Standing decisions — compliance check
|
||||
|
||||
| Decision | Spec actually says it? | Contradicted elsewhere? |
|
||||
| --- | --- | --- |
|
||||
| Angles degrees, 0 along +x, positive toward +y | Yes, §17.2 | Yes — y-down top-left (§17.4) makes “toward +y” clockwise; not acknowledged |
|
||||
| Increasing z farther; sort greater z first; ties document key order | Yes, §17.6 | Partially — particle systems are atomic (§18.2); emitters/repeaters unspecified |
|
||||
| Perspective `focalLength / (focalLength + z)`; ortho: z for sort/parallax/fog only | Yes, §17.6, §19.3 | Formula vs `V` space mix (§19.3) |
|
||||
| Visual objects keyed, no `id` | Yes, §17.8 | No |
|
||||
| Transform order `T(pos+translate) × T(origin) × R × K × S × T(−origin)`; `M_parent × M_local` | Yes, §17.11 | `z` not in `M_local`; camera `V` separate |
|
||||
| ValueSpec once at instantiation; render consumes no stream | Yes, §17.14 | Automation mutates later (§19.1); grain exempt (§19.4) |
|
||||
| `layer` system property not object | Yes, §17.10 | No |
|
||||
| Depth fog per object, exact, no diagnostic | Yes, §17.6 | Color-space unspecified |
|
||||
| Conic only appearance fallback, warn once per paint | Yes, §17.12 | Fallback segment not unique |
|
||||
| `font` sans-serif/serif/monospace | Yes, §17.13 | No |
|
||||
| Live numeric text deferred | Yes, §17.13 | No |
|
||||
| Fourteen primitives closed; `component` fifteenth | Yes, §17.9, §18.1 | §17.15 cause text stale |
|
||||
| Component parameters number/boolean/string/color | Yes, §18.1 | No |
|
||||
| `inputs.*` / `repeat.*` construct-scoped | Yes, §18.1, §18.5 | No |
|
||||
| Expansion path is §9.3 key | Yes, §18.1 | No |
|
||||
| Integrator `v ← (v+a·dt)·(1−drag)^dt`; `p ← p+v·dt` | Yes, §18.2 | No |
|
||||
| Life ramps endpoints once | Yes, §18.2 | No |
|
||||
| Emission accumulator; `floor(rate·t)`; no reset | Yes, §18.4 | Particles say “Emission timing is 18.4” — OK |
|
||||
| `ERR_UNBOUNDED_EMISSION`; `count` alone legal | Yes, §18.2, §18.4 | No |
|
||||
| Pool oldest-first; aggregate §19.5 | Yes | No |
|
||||
| Index-driven + continuous rate = `ERR_INVALID_DISTRIBUTION` | Yes, §18.3 | No |
|
||||
| Sample order x,y,z; angle before radius | Yes, §18.3 | Polar `ring` field order in table is radius before angles |
|
||||
| Coherent noise 3D, 12 gradients, 256 perm, quintic, curl default | Yes, §18.7 | Curl of scalar potential not unique; no sample table |
|
||||
| Ribbons = `trail.mode`; `links` on emitter = `ERR_UNKNOWN_FIELD` | Yes, §18.8 | No |
|
||||
| Morph equal type, points, spline mode | Yes, §18.6 | Non-point primitives underspecified |
|
||||
| Exactly four §8.1 visual rows; no layer.visible | Yes, §19.1 | §17.16.14 and §17.3 contradict |
|
||||
| Two automation scopes; cross-scope `ERR_INVALID_REFERENCE` | Yes, §19.1 | `visuals.automation` vs unknown-field |
|
||||
| Track shape = §16.1 plus `loop`; ping-pong count = round trip | Yes | `infinite` only-persistent vs spawned-legal clash |
|
||||
| Automatable ⊃ bindable | Yes | Repeater `step` does not exist |
|
||||
| Behavior + track = `ERR_AUTOMATION_CONFLICT`; else pipeline then behaviors | Yes, §19.1 | Channel granularity fuzzy |
|
||||
| Lifecycle enum; fields on persistent = `ERR_UNKNOWN_FIELD`; spawn key `id#ordinal` | Yes, §19.2 | No |
|
||||
| Five states + FAILED; no SCHEDULED; release 0ms still one RELEASING tick | Yes | CREATED→FINISHED unexplained |
|
||||
| Spawn at ceiling refused, not evicted | Yes | Warn-once vs once-per-second |
|
||||
| Camera matrix normative; parallax translation only; projection authored once | Yes, §19.3 | Matrix mixes scene and display spaces |
|
||||
| Seven effects, array order; blur/bloom two passes; approx warn; skip not substitute | Yes, §19.4 | No |
|
||||
| Grain exempt, no stream | Yes | Scanlines also device-pixel but not exempt |
|
||||
| §19.5 centralized; values provisional | Yes | Automation row in runtime table |
|
||||
|
||||
---
|
||||
|
||||
## What was checked and found sound
|
||||
|
||||
- Identifier regex and keyed `content`/`children` with no `id` field (§1.3, §17.8).
|
||||
- Strict unknown-field policy as a general rule (the `automation` hole is the exception, not the rule).
|
||||
- Three coordinate spaces named; `width`/`height` only for `virtual`; `viewport` rejects non-stretch `fit`.
|
||||
- Layer cap 16; group nest 8; polygon/polyline 512; spline 256; path commands 512; gradient stops 16; filters 4.
|
||||
- Transform composition string matches the locked order; hierarchy `M_parent × M_local` and `z_parent + z_local`.
|
||||
- Negative scale = mirror; scale 0 legal and draws nothing; skew magnitude ≥ 90 is `ERR_OUT_OF_BOUNDS`.
|
||||
- Safe blend set closed; stroke-only declared-fill vs inherited-fill distinction.
|
||||
- Path must start with `move`; zero-radius path arc is `ERR_OUT_OF_BOUNDS` not a line; bezier spline `3n+1`.
|
||||
- Fonts limited to three generic families; live numeric text explicitly deferred with rationale.
|
||||
- Component parameter types (four scalars) vs audio number-only; `inputs.*` not a §8.1 row.
|
||||
- Particle integrator formula and `dt`-correct drag; life-ramp-without-lifetime = `ERR_SCHEMA_VALIDATION`; exponential-through-zero reuses `WARN_AUTOMATION_FALLBACK`.
|
||||
- Unbounded emission diagnostic; initial `count` without lifetime legal; oldest-first eviction.
|
||||
- `links` on `emitter` = `ERR_UNKNOWN_FIELD`; ribbons as trail mode.
|
||||
- Four §8.1 rows only; `visuals.layers.<id>.visible` absent on purpose; system `visible` bindable not automatable.
|
||||
- Spawn refuse vs voice evict, with rationale; no `SCHEDULED` state; `0ms` release still enters `RELEASING`.
|
||||
- Projection not automatable; grain stream exemption and rationale.
|
||||
- Centralized ceiling table present; values labeled provisional pending 4h; runtimes may lower never raise.
|
||||
- Diagnostic reuse policy (`ERR_COMPONENT_RECURSION`, `ERR_AUTOMATION_CONFLICT` widened, etc.) is consistent in intent.
|
||||
|
||||
---
|
||||
|
||||
## 4d-specific recommendation (not a spec change, a sequencing note)
|
||||
|
||||
Do not start the renderer against §17.16 as a closed gate. Minimum contract fixes before 4d code:
|
||||
|
||||
1. Add `automation` to the §17.3 `visuals` key list.
|
||||
2. Rewrite §17.16.14 to match §19.1’s four rows.
|
||||
3. State Y-down + rotation sign in one sentence.
|
||||
4. State box/ellipse local origin.
|
||||
5. Give one numeric camera example (scene point → display point) that uses a single space for `V`.
|
||||
6. Borrow §19.3 into the 4d scope list, or drop perspective from 4d traces.
|
||||
7. Delete or define repeater `step`.
|
||||
8. Fix “layer nesting” in trace 9 to group nesting / layer count.
|
||||
|
||||
Until (3)–(5) land, two renderer cores can both “conform” and disagree on every rotated, zoomed, or letterboxed frame.
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
# Visual Contract Review — §17–§19 (rev 0.7)
|
||||
|
||||
**Reviewer:** Abacus AI Agent (independent adversarial pass, pre–slice 4d)
|
||||
**Scope:** §17 (scene/primitives/transforms/appearance), §18 (components/procedural systems/behaviors/fields), §19 (automation/lifecycle/camera/effects/ceilings), plus the cross-referenced shared/audio sections (§1.3, §2, §4, §6, §7, §8, §9, §10, §14.4–14.7, §15.11/15.14/15.15, §16.1–16.7) used as consistency anchors.
|
||||
**Method:** from-scratch read; no other file in `reviews/` was opened before or during this pass.
|
||||
**Line numbers** refer to `docs/XZBT_0-1_Format_Specification.md` rev 0.7.
|
||||
|
||||
Result: **33 defects** (4 high, 13 medium, 16 low). All locked decisions were checked against the text; every one is present as claimed **except** "coherent noise is fully specified," which does not hold (H3).
|
||||
|
||||
---
|
||||
|
||||
## High severity
|
||||
|
||||
### H1. Perspective factor: §17.6 and §19.3 give two different, incompatible compositions
|
||||
|
||||
- **Location:** §17.6 "Perspective scaling" (line 1630) vs §19.3 (line 2507).
|
||||
- **What:** §17.6 says "an object's transform is **post-multiplied** by the uniform factor `focalLength / (focalLength + z)` about the camera's projection center." §19.3 says each object "additionally receives the uniform factor … about the projection center, exactly as 17.6 states, **applied after `V`**." Post-multiplying the object's local matrix by a scale applies the scale in *local* space (about the object's origin, also scaling its own translation); scaling after `V` about the projection center is a *display-space* operation. These produce different pixels whenever the object is off-center, and the projection center is not even expressible in local space, so the 17.6 phrasing is not realizable as written. "Exactly as 17.6 states" is false. A knock-on gap: §19.3 (line 2511) pins stroke/blur/glow/shadow scaling for camera *zoom* only; whether the perspective factor also scales stroke width is undecidable while the composition point is ambiguous.
|
||||
- **Why it matters for 4d:** the camera/depth path is core renderer work, and acceptance traces 17.16 #6 and 19.7 #14 both assert "the documented factor" — but the document specifies two. Whichever an implementer picks, a second conforming renderer can pick the other and both cite the spec.
|
||||
|
||||
### H2. `loop.count: "infinite"` is asserted legal and illegal in the same sentence
|
||||
|
||||
- **Location:** §19.1 "Loop modes" (line 2398).
|
||||
- **What:** "…it is legal **only on a persistent scope**, **and** an `infinite` loop on a spawned system with a finite `lifetime` (19.2) **is legal** and simply ends with the system." The second clause flatly contradicts the first. Remaining holes: an `infinite` loop on a spawned system *without* `lifetime` (which may run indefinitely until removed) is unaddressed, and no diagnostic is named for the illegal case (`ERR_SCHEMA_VALIDATION`? `ERR_VISUAL_LIMIT_EXCEEDED`?).
|
||||
- **Why it matters for 4d:** automation-track validation ships with the document model the renderer core builds; this rule cannot be implemented, and 19.7 trace 2 cannot be extended to spawned scopes, until the sentence is rewritten with a single rule and a named error.
|
||||
|
||||
### H3. Coherent noise is *not* fully specified, despite the locked decision and §18.7's own claim
|
||||
|
||||
- **Location:** §18.7 "Coherent noise is normative" (lines 2246–2254); §18.6 `wander`/`twinkle`/`point-wander` (lines 2185, 2189, 2187); 18.10 trace 15–16 (line 2332–2333).
|
||||
- **What:** The text fixes "3D gradient noise, twelve edge-midpoint gradients, 256-entry permutation shuffled from the field's own stream, quintic fade" — but omits everything that makes two implementations agree: (a) how the permutation table is **indexed** from a lattice coordinate (the hash composition, e.g. `perm[x+perm[y+perm[z]]]`, and any wrapping/doubling); (b) how the hash **selects** one of 12 gradients (256 is not divisible by 12 — `mod 12` biases four gradients); (c) the exact Fisher-Yates loop — "consuming one sample per entry" is 256 draws, which matches neither the standard backward (n−1 draws) nor forward variants, so the *table itself* differs across implementations; (d) the octave normalization — "normalized so the result stays within `[-1, 1]`" states a bound, not a formula (divide by `Σp^k`? by the analytic max?); (e) the derivative method for `curl`/`gradient` modes (analytic gradient of the faded noise vs finite differences, and at what epsilon) — trace 16 requires curl to be divergence-free "to within the documented tolerance," a tolerance documented nowhere. Separately, §18.6's `wander` and `twinkle` are driven by "a value-noise sample (18.7)" — but §18.7 defines **gradient** noise, a different function, and the sampling coordinates for behavior noise (a function of what? `t*rate` alone?) are never fixed.
|
||||
- **Why it matters for 4d:** reproducibility of identical decisions from identical seeds is the format's core promise (§9.3), and every field-, wander-, twinkle-, point-wander-, and noise-displace-driven fixture depends on this function being byte-exact. Renderer core decides where noise evaluation lives and how the `visual` stream is plumbed; if the function is not pinned before fixtures are written, 18.10 traces 15–16 are unwriteable.
|
||||
|
||||
### H4. §18.2's "every ValueSpec resolves once per particle" contradicts itself and §19.1's automation registry
|
||||
|
||||
- **Location:** §18.2 "Per-particle resolution boundary" (line 2027) vs the field table it covers (lines 2002–2025) and §19.1 registry (line 2408); parallel text §18.4 (line 2124).
|
||||
- **What:** "Every ValueSpec above resolves **once per particle, at that particle's creation**." The table above includes `count` and `rate` — which *drive* particle creation and cannot resolve per particle (circular), and `position`, `acceleration`, `drag` — which §19.1 then lists as **system-scope automatable** channels (`rate`, `position.x/y`, `acceleration.x/y/z`, `drag`). Automation is time-varying by definition; per-particle creation-time resolution is fixed by definition. The spec never says which wins: does automating `drag` rewrite live particles' drag each tick, or only re-base future creations? §18.4 carves `rate` and `burst[].at` out as emitter-level ("properties of the emitter, not of an item") but leaves `position`/`acceleration`/`drag` per item, so emitters inherit the same ambiguity. (The registry's omission of `velocity` while including `acceleration` suggests an intent — live channels vs creation channels — that is never stated.)
|
||||
- **Why it matters for 4d:** who owns these values — the system or the item — determines the runtime data layout and the per-tick evaluation order the renderer core is built around. Guessing wrong here is a structural rework in 4e/4f, not a patch.
|
||||
|
||||
---
|
||||
|
||||
## Medium severity
|
||||
|
||||
### M1. The 128-track/2048-point automation limit is runtime-dependent but classified as an import-time authoring bound
|
||||
|
||||
- **Location:** §19.1 "Limits" (line 2431); §19.5 two-kinds table (line 2563) and aggregate row (line 2582).
|
||||
- **What:** The limit counts "across both scopes and **across every live spawned instance**" — a quantity that only exists at runtime — yet §19.5 lists it as "Authoring bound (19.1), not a shed," and authoring bounds are defined as "checked at import; the exhibit is rejected." Nothing says what happens when a `spawn` action at runtime pushes the live count past the bound: spawn refused (with `WARN_VISUAL_CEILING`? as 19.2's instance ceiling does)? Action failure with `ERR_VISUAL_LIMIT_EXCEEDED`? Exhibit failure? It is also unspecified whether a spawned *template's* tracks count once at import.
|
||||
- **Why for 4d:** the import/runtime staging split is exactly what the validator half of renderer core encodes; 19.7 trace 6 asserts the numeric boundary but cannot be implemented without the runtime rule.
|
||||
|
||||
### M2. §17.12 types `fill`/`stroke` as literal-only, but §18.1's normative example uses a `ref` ValueSpec as `fill`
|
||||
|
||||
- **Location:** §17.12 table (lines 1766–1767) vs §18.1 example (line 1945: `"fill": { "ref": "inputs.tint" }`).
|
||||
- **What:** `fill` is "`color`, paint object, or `null`" — a `{"ref": …}` is none of those. §17.14 licenses ValueSpec "exactly where each field table says so," and 17.12's table does not say so. The same question is unanswered for gradient stop colors (can a stop be `{"ref": "inputs.tint"}`?). As written, the spec's first visual-component example is invalid, and with it the whole `color`-typed component-parameter mechanism (18.1, line 1964) has no legal consumer.
|
||||
- **Why for 4d:** paint parsing and style resolution are 4d core; the ValueSpec-vs-literal decision determines the shape of the entire style pipeline.
|
||||
|
||||
### M3. §19.1's registry automates a repeater `step` block that does not exist
|
||||
|
||||
- **Location:** §19.1 registry (line 2408) vs §18.5 field table (lines 2146–2155).
|
||||
- **What:** "for `repeater`, the numeric fields of its `step` block." §18.5 defines `repeat`, `count`, `distribution`, `position`, `inputs`, `behaviors`, `fields`, `links` — no `step`. The row is dead text (or a remnant of a dropped design), and with it the only repeater automation surface.
|
||||
- **Why for 4d:** target validation must assign `ERR_UNSUPPORTED_TARGET` vs `ERR_INVALID_REFERENCE` per the registry; a registry entry whose referent doesn't exist can't be classified, and 19.7 trace 4's expectations wobble with it.
|
||||
|
||||
### M4. §19.5's "centralized" ceiling table omits enforced ceilings and mis-cites one
|
||||
|
||||
- **Location:** §19.5 restated table (lines 2587–2614) vs §17.13 (line 1829: path commands 512), §18.2/§18.4 (lines 2008, 2107: burst entries 16), §18.3 (line 2076: grid columns/rows 256), §15.14 (line 1148: oscillator partials 64), §15.10 (line 1042: resonator modes 16).
|
||||
- **What:** The table claims "every ceiling the engine enforces appears here," but path commands (512), burst entries (16), and grid dimensions (256) are absent, as are custom oscillator partials (64) from the audio side. The "Resonator modes" row gives the value as "Per section 14" with "Fixed in: 15.10" — the value 16 lives in 15.10; "section 14" is a stale citation.
|
||||
- **Why for 4d:** 4d implements `path` rendering; an implementer coding limits from the central table — the table's stated purpose — will ship without the 512-command bound.
|
||||
|
||||
### M5. The offscreen-buffer ceiling's shed rule doesn't cover layer buffers, and its warning has no rate limit
|
||||
|
||||
- **Location:** §17.5 (line 1620) vs §19.5 aggregate table (line 2578).
|
||||
- **What:** Layers with non-default `opacity`/`blend` "require an offscreen compositing buffer" counted against the 19.5 budget of 16 — but the shed rule speaks only of "the excess **objects**" drawn "without their non-default `blend`, `mask`, `blur`, `glow`, or `filters`, farthest-`z` first." There is no rule for a *layer* whose buffer is shed (draw at opacity 1? drop the blend? in what order relative to object sheds?). The `WARN_VISUAL_APPROXIMATION` this shed raises also has no stated rate (per object? per frame? once ever?) — unlike every other diagnostic in the table, and unlike the conic-fallback and post-effect uses of the same code, which are explicitly "once per instance."
|
||||
- **Why for 4d:** the compositor's buffer allocator and degradation path are renderer core.
|
||||
|
||||
### M6. Emitted items and repeater copies have no draw-order rule in their layer
|
||||
|
||||
- **Location:** §18.2 "Drawing" (line 2059) — particles only; §18.4 and §18.5 have no drawing paragraph; §18.8 (line 2294) references "the system's own depth position (18.2)."
|
||||
- **What:** Particles get an explicit rule: drawn as one unit, system's sort key is its lowest-`z` particle, internal order `z` descending then creation ordinal. Nothing says whether an emitter's items or a repeater's copies draw as one unit (and at what `z`) or interleave per-item with `graphic` objects in the layer's depth sort. §18.8's link drawing rule cross-references a per-system depth position that only §18.2 defines, and only for particles — so links on a repeater (explicitly legal) have an undefined depth position.
|
||||
- **Why for 4d:** the layer sort and scene-graph structure are built in 4d; the unit-vs-interleave decision is architectural, not a detail.
|
||||
|
||||
### M7. §18.3 distribution math has holes that break its own "byte-identical" guarantee
|
||||
|
||||
- **Location:** §18.3 (lines 2067–2083); relatedly §18.6 `follow-path` (line 2186).
|
||||
- **What:** (a) `depth`'s `linear` and `exponential` curves have no formulas — "biasing items toward `near`" admits many functions (`u²`, `1−√(1−u)`, any exponential rate). (b) `ring`/`path` `even` modes have no placement formula at all ("distributes angle evenly by index" — `i/n` or `i/(n−1)`? measured from `startAngle`?); `line` `even` uses `i/(count−1)`, which divides by zero at `n = 1` — NaN, which §2 forbids outright (the `count = 1` guard exists for `repeat.fraction` in §18.5 but was not carried here). (c) The normative sample-consumption order covers `x,y,z` and polar forms but not select-then-place distributions: `rectangle` `perimeter` needs an edge-selection sample and an along-edge sample, in an unstated order. (d) `path` `random` is "uniform by arc length," and `follow-path` moves at scene units/second — both require an arc-length parameterization (flattening tolerance? adaptive quadrature?) that is never fixed, contradicting §18.3's stated goal that placements be "byte-identical across renderers."
|
||||
- **Why for 4d:** placement resolves at instantiation — a renderer-core code path — and 18.10 trace 10 demands documented positions from a fixed seed; without formulas there is nothing to document.
|
||||
|
||||
### M8. `fit`'s default collides with the `viewport` validation rule
|
||||
|
||||
- **Location:** §17.4 (lines 1591, 1600).
|
||||
- **What:** `fit` defaults to `contain`; for `viewport`, "`fit` is ignored and a `fit` other than `stretch` is `ERR_SCHEMA_VALIDATION`." Read literally, an absent `fit` resolves to `contain` — "a `fit` other than `stretch`" — so every viewport-space exhibit that doesn't explicitly write `fit: "stretch"` is invalid. If "ignored" is meant to cover absence, the default never engages and the sentence contradicts itself. Either way it needs an "explicitly authored" qualifier.
|
||||
- **Why for 4d:** scene/fit resolution is the subject of 17.16 trace 1 — the first acceptance trace of the slice.
|
||||
|
||||
### M9. Post-effect `blur`/`bloom` radii are in undefined units
|
||||
|
||||
- **Location:** §19.4 table (lines 2534, 2537).
|
||||
- **What:** `blur.radius` is "Scene units, scaled by camera `zoom`"; `bloom.radius` is "Scene units" without even the zoom note. But post-effects run on the composited *frame* (line 2515) — display space, after `fit` resolution, and after per-object perspective factors that make scene scale non-uniform across the frame. The scene→display conversion for the post chain (fit scale? coordinate space? which layer's parallax?) is never defined. `scanlines` and `grain` in the same table use device pixels, so the scene-unit choice is conspicuous.
|
||||
- **Why for 4d:** pass sizing and backing-store math for blur/bloom are set up by the renderer core; two renderers will pick different conversions and both will be "conforming."
|
||||
|
||||
### M10. §17.16 trace 14 contradicts §8.1/§19.1 (and §19.7 trace 7) as of rev 0.7
|
||||
|
||||
- **Location:** §17.16 trace 14 (line 1910) vs §8.1 rows (lines 329–332), §19.1 (lines 2416–2427), §19.7 trace 7 (line 2645).
|
||||
- **What:** Trace 14: "A binding, `set`, or `override` addressing **any visual property** is `ERR_UNSUPPORTED_TARGET`." That was true when 4a landed; it is false now — `visuals.camera.zoom` et al. are legal binding targets, and 19.7 trace 7 requires them to *work*. §19.1 (line 2425) carefully preserves §17.14 and 18.10 trace 19 ("survive this slice unchanged") but overlooks trace 14's blanket wording, which was not scoped to per-object properties.
|
||||
- **Why for 4d:** 17.16 *is* the 4d acceptance suite; as written it fails the document model 4d is required to build.
|
||||
|
||||
### M11. §18.6 behavior field contracts are incomplete against §12's template, and `oscillate`'s waveforms are undefined
|
||||
|
||||
- **Location:** §18.6 table (lines 2179–2197); §12 (line 542).
|
||||
- **What:** §12 requires every construct to record required/optional fields, ranges, and defaults. The behavior table lists field names only: `oscillate` has no stated default for `center` (0? the authored base?), no requiredness for `amplitude`/`frequency`, no `phase` default; `pulse` never defines how `duty` splits between rise and fall, nor its `curve` default; `wander`, `twinkle`, `noise-displace` give no ranges or defaults (`noise-displace`'s `scale`/`speed`/`octaves`/`persistence` presumably borrow 18.7's — unstated); `rotate.speed`, `orbit.radius`/`speed`/`phase`, `follow-path.offset`, `attract`/`repel.strength`, `morph.curve`, `drift.damping` likewise. And the waveform functions themselves — `triangle`, `square`, `sawtooth` polarity, duty, and phase origin in `w(frequency·t + phase/360)` — are never defined, so even with defaults the behavior is not reproducible across renderers.
|
||||
- **Why for 4d:** the document parser and object model ship behavior schemas with renderer core; every missing default is a guess two implementers will make differently.
|
||||
|
||||
### M12. `vortex` (and `orbit`) direction is ambiguous in the y-down scene space
|
||||
|
||||
- **Location:** §18.7 (line 2239), §18.6 `orbit` (line 2184) vs §17.2 (line 1543) and §17.4 (lines 1599–1601).
|
||||
- **What:** All three coordinate spaces put +y *downward*; §17.2's positive angles turn toward +y, i.e. clockwise *on screen*. `vortex` is "perpendicular to the outward ray, **counter-clockwise** for positive `strength`" without saying whether that is display-visual counter-clockwise or the 17.2 convention — in a y-down space those are opposite vectors. `orbit` never states the direction of positive `speed` at all.
|
||||
- **Why for 4d:** sign conventions get baked into the field evaluator and behavior math; a flipped sign passes every automated trace that isn't written and fails the visual acceptance that is.
|
||||
|
||||
### M13. `links` block: dangling `style` default, undefined fade case, and an over-broad static check
|
||||
|
||||
- **Location:** §18.8 links table (lines 2281–2296).
|
||||
- **What:** (a) `style` defaults to "the system's style" — no system type has a `style` field (§17.7, §18.2, §18.4, §18.5 define none); the default has no referent. (b) `fadeWithDistance` fades "from full at distance `0` to zero at `maxDistance`," but for rule `nearest` `maxDistance` is optional — fading with it absent is undefined. (c) The 256-population validation uses `particles.capacity` as the static proxy, which rejects systems whose live count can never approach it (e.g. `capacity: 400`, `count: 10`, no `rate`/`burst` → rejected at import); the false positive is unacknowledged. And when `repeater.count` is a non-literal ValueSpec, the "statically known" precondition silently fails and the instantiation-time diagnostic is unstated.
|
||||
- **Why for 4d:** links validation and default-style resolution land in the renderer core's system model.
|
||||
|
||||
---
|
||||
|
||||
## Low severity
|
||||
|
||||
- **L1 — §19.2 vs §10.1 (lines 2444, 2476, 483):** `cancelWithScenario` semantics are hollow. With `ownership: "persistent"` the instance transfers to the performance root, so scenario cleanup would not touch it regardless; meaning is given only to `false`. Either the default `true` lets a scenario cancel a root-owned instance (making the transfer hollow) or the flag is redundant. 19.7 trace 11 tests a distinction the text never defines. *(4d relevance: lifecycle flags ship in the system object model.)*
|
||||
- **L2 — §19.5 (lines 2564, 2583) and §19.2 (line 2480):** `WARN_VISUAL_CEILING` has two unreconciled triggers — "once per ceiling per second" on any shed, and a separate "120 consecutive ticks" degradation report using the same code; and 19.2's spawn refusal "raises once" without saying whether the 1/sec rate limit applies. *(Renderer core emits these.)*
|
||||
- **L3 — §17.16 trace 9 (line 1905):** "`9` layer nesting levels" — layers don't nest; presumably group nesting (already trace 4) or the 16-layer count was meant. *(Corrupts the 4d acceptance suite.)*
|
||||
- **L4 — §19.7 trace 9 (line 2647) vs §19.2:** the trace requires "a second `remove` on a `DISPOSED` instance is a no-op"; §19.2 never states this (§16.3 states it for audio). A trace without normative basis.
|
||||
- **L5 — §18.2/§18.4/§18.5 `count` fields (lines 2006, 2107, 2149):** integer-typed ValueSpecs with no rounding rule when they resolve non-integer (contrast §8.1's integer-target rounding and §13.2's half-away-from-zero); `burst[].count`'s resolution *timing* (emitter instantiation vs burst tick) is also unstated — §18.4 (line 2124) carves out `burst[].at` only.
|
||||
- **L6 — §17.9 (line 1704) vs §17.6/§19.3:** per-point `z` "offsets the object's z for depth purposes" but the object "is sorted and drawn as one unit" — with points at differing `z`, which `z` drives the single perspective factor and fog fraction? `z_parent + z_local` (17.11) covers groups, not point offsets. A spline spanning z=0..100 needs one number; the spec doesn't say which.
|
||||
- **L7 — §18.3 `path` distribution (line 2075):** the sibling-key form ("the key of a sibling `path`/`spline`/`polyline`/`polygon` object in the same container") has no possible referent — distributions live on systems, and systems have no sibling object containers (`render`/`emit`/`repeat` are singular). Dead syntax, or a missing referent such as a graphic system's `content`.
|
||||
- **L8 — §18.6 `morph` (lines 2197, 2214):** the operand set is undefined for point-less primitives — compatibility keys on point count, which is meaningless for `rectangle`/`ellipse`/`arc`/`ring`/`text` and for `path` (commands, not points); whether morph there is `ERR_INVALID_BEHAVIOR_TARGET` or interpolates `size`/`radius` is unstated. "Reads the target's resolved points" is also ambiguous when the target's points are moving under `point-wander`.
|
||||
- **L9 — §18.1 (lines 1966, 1976) vs §15.11 (line 1059):** visual component diagnostics diverge from the audio contract they claim to mirror: undeclared `inputs` key → `ERR_INVALID_REFERENCE` (visual) vs `ERR_UNKNOWN_FIELD` (audio); missing required parameter → `ERR_INVALID_REFERENCE` *at instantiation* (visual) vs import-time `ERR_SCHEMA_VALIDATION` (audio); supplied-value-out-of-range is specified for audio (`ERR_OUT_OF_BOUNDS`) and unstated for visual. The instantiation staging also means a persistent system passes import and fails at activation — a new failure mode the strict-validation posture elsewhere avoids.
|
||||
- **L10 — §18.4/§18.5 (lines 2117, 2152) vs §18.1 (line 1974):** dual `inputs` paths — the `component` object carries its own `inputs`, and the emitter/repeater carries a separate system-level `inputs` "only when `emit`/`repeat` is a `component` object," both sampled per item/copy, with no merge or conflict rule when both are present.
|
||||
- **L11 — §19.2 (line 2450) vs §1.3 (line 58):** "the `instances.*` runtime namespace section 1.3 reserves" — §1.3's prefix list omits `instances.*`; only §8.1/§8.3 mention it. Dangling cross-reference; the reservation 19.2 relies on doesn't exist where it says it does.
|
||||
- **L12 — §19.2 (line 2472):** release "ramps the instance's composited opacity linearly from `1` to `0`" — systems have no `opacity` property (§17.7), so where the release factor multiplies the compositing stack is undefined, and "from 1" reads as absolute where audio's 16.4 says "from its current value"; the literal reading pops a 0.5-opacity instance to full before fading.
|
||||
- **L13 — §19.4 (line 2532) vs §17.12 (line 1799):** `color-adjust` claims "the 17.12 filter semantics" but renames the parameters (`saturation` vs `saturate`, `hueRotate` vs `hue-rotate`) — an unforced inconsistency for implementers mapping one onto the other. §19.4 also never states the resolution boundary for effect parameters (presumably activation; §17.14 covers object fields).
|
||||
- **L14 — §8.1 (line 329) vs §19.3 (line 2493):** the camera row's safety clamp is "the per-field range of 19.3," but `focalLength`'s range is the open interval "above `0`" — automation or binding driving it ≤ 0 has no finite bound to clamp *to*.
|
||||
- **L15 — §19.2 (line 2439) vs §17.7 (line 1654):** the `lifecycle` row claims it is "Added to the 17.7 system field table by this section," but 17.7 already contains it in rev 0.7. Stale double-definition; harmless, but it signals the two tables can drift.
|
||||
- **L16 — §19.5 (lines 2557, 2620) and §17.7–17.9:** no ceiling bounds declared systems per exhibit or authored objects per system, while governance "never sheds an authored persistent object or a declared system." A document can author unbounded per-frame draw load with no runtime recourse (audio bounds nodes per sound and voices; visuals bounds neither objects per system nor systems per exhibit). Deliberate or not, the gap is unacknowledged in a table that claims completeness.
|
||||
|
||||
---
|
||||
|
||||
## Checked and found sound
|
||||
|
||||
Verified directly against the text (presence, internal consistency, and cross-references), no defect found:
|
||||
|
||||
- **All locked §17 decisions present and consistent:** degrees/0-+x/positive-toward-+y (17.2, applied to arc/ring/skew/conic); z sign, greater-z-first sort with document-key-order ties and stability (17.6); perspective factor formula and `z <= -focalLength` culling with no diagnostic (17.6, 19.3, traces 17.16#6 / 19.7#14 mutually consistent); orthographic z behavior; keyed objects with no `id` field (17.8); transform order `T(position+translate) × T(origin) × R × K × S × T(−origin)` and `M_parent × M_local`, `z_parent + z_local` (17.11); once-at-instantiation ValueSpec resolution and no procedural stream in rendering (17.14, 9.3); `layer` system-only with `ERR_UNKNOWN_FIELD` on objects (17.10); fog formula exact, per object, no diagnostic (17.6); conic fallback the sole appearance fallback, warn once per paint instance (17.12); three-font `font` enum (17.13); live readouts deliberately deferred (17.13).
|
||||
- **All locked §18 decisions present:** fourteen primitives + `component` as fifteenth object type (17.9, 18.1); component parameter types number/boolean/string/color vs audio's number-only (18.1, 15.15); `inputs.*`/`repeat.*` construct scope with `ERR_INVALID_REFERENCE` and no §8.1 capability (18.1, 18.5); expansion path as the §9.3 stable key (18.1); the normative integrator `v ← (v + a·dt)·(1−drag)^dt; p ← p + v·dt` (18.2); life ramps interpolating once-resolved endpoints (18.2); fractional accumulator, cumulative `floor(rate·t)`, no reset on automated rate change (18.4); `ERR_UNBOUNDED_EMISSION` with `count`-alone legal (18.2/18.4); oldest-first eviction (18.2/18.4); `ERR_INVALID_DISTRIBUTION` for index-driven placement on continuous emission (18.3); normative sample order x,y,z / angle-before-radius (18.3, as far as it goes — see M7); noise parameters (3D, 12 gradients, 256-entry permutation, quintic fade) present in text (completeness defect H3 notwithstanding); `curl` default (18.7); ribbon as `trail.mode` (18.8); `links` as `ERR_UNKNOWN_FIELD` on emitters (18.8); morph compatibility rule (18.6).
|
||||
- **All locked §19 decisions present:** exactly four system-level §8.1 rows, verified in the §8.1 table itself (lines 329–332) with `visuals.layers.<id>.visible` deliberately absent (19.1); two automation scopes with spawn-relative time origin and cross-scope `ERR_INVALID_REFERENCE` (19.1); 16.1 track shape plus `loop`, ping-pong count = full round trip (19.1); automatable registry broader than the §8.1 rows (19.1); behavior/track `ERR_AUTOMATION_CONFLICT` with pipeline-then-behaviors array-order composition (19.1); explicit `lifecycle` enum and `ERR_UNKNOWN_FIELD` on persistent systems (19.2, 17.7); spawn stream key `<system-id>#<spawn-ordinal>` (19.2); five states plus `FAILED`, no `SCHEDULED`, with a stated reason (19.2); `release` default `0ms` with one `RELEASING` tick (19.2); spawn-at-ceiling refusal, never eviction (19.2); normative camera matrix with parallax on translation only (19.3 — algebra spot-checked: reduces to `T(c)·S·R·T(−x,−y)` at p=1 and to one-fifth camera translation at p=0.2, matching trace 13); `projection` authored once (19.3); seven effects in normative array order, blur/bloom two passes and the rest one, 4-entry/8-pass arithmetic checks out (19.4); reduced-resolution and unavailable-effect warnings once per effect instance, never substituted (19.4); `grain` the sole reproducibility exemption, consuming no stream, with a correct justification against §9.3/§14.6 precedent (19.4); §19.5 centralization with provisional values and the Phase-6 gap for scenario/timeline ceilings explicitly named.
|
||||
- **Diagnostic-code parity:** every code introduced by 17.15 (5), 18.9 (7), and 19.6 (2) is present in §7 with matching stage and cause; §7's `ERR_AUTOMATION_CONFLICT` text is already widened to the visual scope exactly as 19.6 claims; §19.6's "two, and no more" holds.
|
||||
- **§19.5 restated values** match their subsystem sources for every row checked (16 layers, 8 group nesting, 512 vertices, 256 spline points, 16 stops, 4 filters, 8 component nesting, 4096/512 capacities, 1024 repeater count, 8 behaviors, 8/4 fields, 128 trail, 1024 maxLinks, 4 effects, 32 radius, 64 spawned, 64/16 voices, 64/256 audio automation, 1024 dispatch units, 16 event depth, 256 hook actions).
|
||||
- **§16.1 → §19.1 track-shape reuse:** field-for-field match (target/mode/interpolation/points, duration-literal `at`, strictly-increasing rule, hold-first/hold-last), with `loop` the only addition, as claimed.
|
||||
- **State machine:** the visual machine is the audio machine minus `SCHEDULED`, transitions otherwise parallel, and the reasoning (no second clock) is stated; cleanup deadline interplay with §10.2 (shorter of authored release and 5s, `WARN_CLEANUP_FORCED`) is consistent.
|
||||
- **17.16 / 18.10 / 19.7 trace lists** are internally consistent with their sections everywhere except the flagged items (M10, L3, L4).
|
||||
Reference in New Issue
Block a user