_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.