diff --git a/.labyricorn/devlog/closing-the-visual-contract/contents.lr b/.labyricorn/devlog/closing-the-visual-contract/contents.lr new file mode 100644 index 0000000..13ec0a5 --- /dev/null +++ b/.labyricorn/devlog/closing-the-visual-contract/contents.lr @@ -0,0 +1,71 @@ +_model: devlog-entry +--- +schema_version: 1 +--- +title: Closing the visual contract +--- +date: 2026-09-06 +--- +author: Labyricorn +--- +summary: Section 19 finishes what sections 17 and 18 started. Visual automation, the spawned-system lifecycle, the camera, seven post-effects, and one centralized table holding every ceiling in the engine. The visual contract now has no unresolved forward reference and still no renderer. +--- +tags: Visuals, Contracts, Limits, Specification, Phase 4 +--- +source_commit: d3cd688a3c21e9d40cecf1ff82efd41a923ece0c +--- +body: + +The [third visual contract section](https://git.labyricorn.com/Labyricorn/XZBT/commit/d3cd688a3c21e9d40cecf1ff82efd41a923ece0c) is the one that had to decide things the first two were allowed to defer. Sections 17 and 18 could describe a scene and the systems that populate it while leaving three questions open, marked in the text as forward references to a section that did not exist yet. Section 19 is that section, and the forward references are gone. + +## The question that was actually hard + +Which visual properties can be controlled from outside the visual subsystem? + +XZBT resolves every controllable value through one shared pipeline: a base value, then a binding, then automation, then a winning override, then modulation, then a safety clamp. A table in section 8 lists every target that pipeline accepts. Adding a row to that table is not a small thing. It is plumbing the renderer has to carry everywhere, state the override stack has to mask and release correctly, and a promise a later revision cannot withdraw. + +The answer is four rows, and all four are system-level: the camera, a layer's opacity, a system's visibility, and a post-effect's parameters. Not one per-object property. You cannot bind a state value to the opacity of a particular rectangle inside a particular system. + +That sounds restrictive until you look at what the reference exhibits need. The instrument display of Exhibit D is state-driven geometry *inside* a system, and automation and behaviors reach it without any external capability at all. The narrow surface is not a limit on what the format can express. It is the smallest surface that expresses it, and it can be widened later without breaking anything. The reverse is not true. + +The general rule underneath comes from the audio contract: a mechanism declared inside a subsystem never widens that subsystem's external surface. An emitter's emission rate is automatable and is still not bindable, and the trace written for section 18 that asserts this still passes. + +## Automation, and where a track's clock starts + +Visual automation reuses the audio track shape field for field — the same three modes, the same four curves, the same fallback when an exponential segment crosses zero. It adds exactly one field, `loop`, because looping is the only thing the visual requirement asks for that the audio requirement did not. + +The interesting decision is that a track can be declared in two places. One at the top of the visual block, measured from activation, reaching the scene, layers, camera, and effects. One inside a system, measured from that system's own instantiation. The second exists for spawned systems: a track written against spawn-relative time behaves identically whether the spawn happens at four seconds or at four minutes. + +`ping-pong` counts complete round trips rather than one-way passes, so a finite loop count always ends where it began. + +## Spawned systems, and a policy that deliberately contradicts the audio one + +A visual system is persistent or spawned, set by an explicit field rather than inferred from which other fields are present. A spawned system is a template: validated at import, drawn only when an action instantiates it. + +When the runtime hits its ceiling of live spawned systems, it refuses the new one. It does not evict an old one. The audio subsystem does the opposite — at the voice ceiling it evicts the oldest voice and starts its release. Both are right for their subsystem. An evicted voice fades out over milliseconds and is forgiven. A visual system evicted mid-scene disappears in front of the viewer. + +Visual release also defaults to immediate, where audio release defaults to fifty milliseconds. An audio release exists to prevent a click, which is a defect. A visual pop is merely abrupt, and is sometimes exactly what an author wants. An author who wants a fade declares one. + +## One table for every ceiling + +The requirement is one sentence long: limits should be centralized rather than scattered through individual subsystems. Honouring it took a table and a distinction. + +The distinction is between an authoring bound and a runtime ceiling. An authoring bound is a number the author wrote — a particle capacity of ten thousand, a fifth post-effect. Those reject the exhibit at import, loudly, because the author should learn the number is wrong. A runtime ceiling is an aggregate the document never named — total live particles across every system, total link segments in a tick. Those shed work and keep running, because a legal exhibit on a small machine is not an error. + +Every shedding rule is written down and deterministic: oldest particle from the largest system, the tail of an already-specified link ordering, farthest objects first when compositing buffers run out. A degraded frame should be the same degraded frame everywhere. + +The table then restates every ceiling first fixed anywhere else in the specification — voices, dispatch units, layers, group nesting, spline points, trail lengths, and the rest — so that a runtime reads limits from one place. Two entries in the requirement's list have no value yet, because the scenario contract has not been written. The gap is named in the table rather than omitted, so the table stays a center instead of a snapshot. + +## The one honest exemption + +Film grain is a per-pixel noise effect. Sampling it from the exhibit's seeded stream would make the stream position depend on resolution and frame rate, which would break reproducibility for everything else in order to protect it for the one thing that does not need it. + +So grain is exempt, explicitly, in the specification text. It consumes no seeded samples and two runs of one seed are not guaranteed pixel-identical while it is enabled. Reproducibility in XZBT has always promised identical decisions rather than identical pixels; grain drives no decision. Writing the exemption down is better than letting someone discover it. + +## What is not claimed + +Every number in the runtime ceiling table is an estimate. Their relationship to the benchmark at the end of Phase 4 is the same one the audio protection thresholds have to their own measurement: the shape is fixed now, the numbers are confirmed or replaced by a real machine. Until that benchmark runs, no evidence record may describe them as measured. + +There is still no renderer. No renderer module, no visual definitions in the schema, no visual fixture, nothing drawn from any build. The full test suite passes at 102 tests and every one of them is about something else. Three documentation slices in a row is an unusual amount of writing to do before the first pixel, and it is the same order that saved the audio subsystem a rewrite. + +The next slice writes code. The one decision still open is whether these three sections get the multi-model review that found thirteen defects in the audio contract — nine of which a single reviewer would have missed — before that happens. It is no longer a question that can be left pending, because there is nothing left to defer it behind.