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
48 lines
4.3 KiB
Markdown
48 lines
4.3 KiB
Markdown
_model: devlog-entry
|
|
---
|
|
schema_version: 1
|
|
---
|
|
title: Frame plans before pixels
|
|
---
|
|
date: 2026-09-06
|
|
---
|
|
author: Labyricorn
|
|
---
|
|
summary: The first visual code that draws anything. The renderer is split in two so that a frame can be checked numerically without a display — an engine that emits a frame plan in device pixels, and a backend that turns a plan into drawing calls. Implementing it corrected two rules in the contract.
|
|
---
|
|
tags: Visuals, Renderer, Testing, Runtime, Phase 4
|
|
---
|
|
source_commit: 0af58da89dd6095fa9ca3ed546a2e48d7c7971b7
|
|
---
|
|
body:
|
|
|
|
Slice 4d is [the first visual code that draws](https://git.labyricorn.com/Labyricorn/XZBT/commit/0af58da89dd6095fa9ca3ed546a2e48d7c7971b7). Sections 17.4 through 17.14 of the contract are implemented, and the standalone artifact now has a stage canvas that renders a declared scene.
|
|
|
|
## Why the renderer is two pieces
|
|
|
|
The required traces of 17.16 ask things like: this scene point lands at this display point; this object's perspective factor is this number; this object's fog fraction is that one; these objects sort in this order. None of those questions can be answered by looking at a canvas. A canvas has pixels. The answers live one step earlier.
|
|
|
|
So `visual-engine.js` resolves an exhibit's objects once at their instantiation boundary, composes the scene-to-device chain, sorts by depth, applies fog and the compositing order, and emits a **frame plan** in device pixels. `visual-canvas2d.js` turns a plan into drawing calls and does nothing else. The split is not tidiness — it is what makes the traces runnable without a display, which is what those traces require. A frame plan carries the numeric oracles; the backend carries the pixels.
|
|
|
|
## Exactness as a contract property
|
|
|
|
Colors are parsed into exact components, and a color outside the four documented forms is `ERR_TYPE_MISMATCH` rather than a best-effort interpretation. That strictness has a specific payoff: per-object fog is arithmetic on those components, so two conforming renderers must produce the same fogged color. A permissive parser would make fog renderer-dependent, which is a thing you cannot un-promise later.
|
|
|
|
The depth rules are similarly narrow. Sortable units order farthest-first with the documented tie-break; a point's own `z` projects that point without making it a sortable unit; an object at or behind the eye is culled with no diagnostic at all, because that is a scene an author composed, not an error.
|
|
|
|
## Two rules the implementation corrected
|
|
|
|
Writing the code found two defects in the prose I had written.
|
|
|
|
The first is buffer accounting. Section 17.12 originally counted compositing buffers as *concurrently live*, which sounded right and was wrong: group nesting is already capped at 8, so that reading made 19.5's farthest-first shedding rule nearly unreachable. It is now **16 allocations per frame** — the per-frame "pass budget" reading that 17.5 had been using since it first appeared. The shedding rule now actually engages, and the sixteen grants go nearest-first so refusals fall on the far content, each one diagnosed under the 19.5 cadence rather than dropped in silence.
|
|
|
|
The second is arc sweep. The prose always described `350°` to `10°` clockwise as a 20-degree sweep, but the rule as written normalized the wrong quantity and produced 340 degrees the long way round. It now normalizes the *signed* delta, and the bound is checked before normalization rather than after.
|
|
|
|
Both corrections came from implementing the section rather than from re-reading it. That is an argument for writing the contract first *and* for not treating it as finished until something has been built against it.
|
|
|
|
## What this slice does not do
|
|
|
|
`visual-validation.js` gains the whole object tree — fifteen object types and their declared fields, no `id` and no `layer` on an object, a mask only on a group and only naming its own child, path legality, spline modes and point counts, gradient stop order, and every authoring limit of 19.5 that applies to a drawn object.
|
|
|
|
Twenty-two new tests cover traces 1 through 13 of 17.16 plus buffer shedding and the backend's call order; the suite goes from 131 to 153. No procedural system runs, no automation track advances, and no post-effect executes — 4e and 4f own those. Traces 15 and 16 stay user-observed, because what they check is whether it looks right, and nothing automated can answer that.
|