GEO-RUNTIME / SPRINT 1THE SLICE3 SEPT 20269 / 9 DONE-WHEN

The whole character
is 2,320 bytes.

Not a mesh, not a model file — a program. It declares its own ceiling in a 64-byte header, and a WebAssembly VM reads its bytes every frame to build 5,787 vertices straight into the layout the GPU wants. Ten times faster than the engine it was ported from, and vertex-for-vertex identical to it.

The crash-test dummy rendered at three angles — three-quarter, front and side — each at a different moment in its pose plan, with the arms in different positions.
One program. Three angles, three moments in the plan. The arms are in different places because the VM evaluates the pose — the plan, the Bézier ease between beats, the merge onto each part — before it emits a single vertex. JavaScript resolves nothing.
The program2,320bytes · 9 parts
It expands to305 KiB135× · never stored
geo_build0.191ms · 0.205 worst
vs GeoV, same mesh10.2×1.960 ms → 0.191
What executes

The header states the ceiling before the body is read

A depth-bounded program has a maximum cost you can compute without running it. That is the whole architecture in one sentence: known ceiling → arena sized before execution → no allocation, no memory growth → a typed-array view that can never detach. Bounded and zero-copy are the same constraint approached from opposite ends.

THE PROGRAM — 2,320 BYTES header 64 B parts · 9 × 96 B 864 B pose channels · 3 × 9 × 48 B 1,296 B plan · 4 × 24 96 B max_verts = 5 solids×27² + 4 limbs×(17×21) + 2 hands×(21×17) = 5,787 from the header's COUNTS alone — no value in the file can move it ONE FRAME geo_build(t) ease · lerp · merge 0.191 ms the arena pos3 uv2 nrm3 exactly 5,787 verts Float32Array view NO COPY 0.000 ms gl.bufferData 11 spans, 11 colours 0.352 ms WebGL2 0.048 ms submit The bytes are interpreted every frame. There is no decoded instruction cache — “the binary is the runtime” is a weaker claim if the binary is really a serialisation format for something else.
Correctness first

Identical to the engine it replaces

A fast engine that draws a different character is not a faster engine. So before any timing: the VM and GeoV's own gcBuildForm run in the same JavaScript context, on the same document, at fourteen times along the plan — every key, the mid-segments, the loop seam, past the end, and a negative time.

comparedGeoVthe VMresult
vertices, every frame5,7875,787identical
index buffer, element by element31,80031,8000 mismatches
group spans and colours11110 mismatches
worst |Δvertex|, every float of every frame8.196 × 10⁻⁷6.9 f32 ULP

The first run was 2.58 × 10⁻⁶. Widening the pose arithmetic from f32 to f64 cut it 3.1×; the ~6.4 × 10⁻⁷ that remains is the format storing pose values as f32, which is a stated cost rather than a bug. f32 in the file is a decision. f32 in the arithmetic is sloppiness.

The bound

Four thousand programs nobody wrote

The claim is narrow and total: for every input the VM accepts, execution fits the ceiling the header declared. Rejection is always allowed. What is never allowed is accepting a program and then exceeding the bound — because a clamped arena keeps rendering something plausible, so nobody would notice.

mutationacceptedtriedwhat it attacks
poisoned floats342444NaN, ±∞ and 10³⁰ through real geometry
random byte flips294445anything, anywhere
offset rewrites47444sections pointing outside the file
255× resolutions38445the multiply that computes the ceiling
header scramble15445every declared count at once
giant counts · lying ceiling · truncation · pure noise01,777all rejected, by design

736 mutated programs were accepted and executed at eight times each. Zero traps. Zero ceiling violations. Zero arena overflows. Zero memory growth. The 3,264 rejections carry their reason: 1,601 for a section falling outside the binary, 1,260 for a ceiling that exceeds the arena or contradicts the header's own counts.

And the fuzz found a real hole. usize is 32-bit on wasm32, so a header claiming 65,535 solids at 255×255 wraps a plain multiply onto a small ceiling — which then passes the arena check. A bounded VM talked out of its bound by arithmetic. Fixed with saturating maths, and it was invisible until four thousand programs went looking.

⭐ The strongest line in that table is the first one. Poisoned floats are accepted and cannot move the bound, because the ceiling is derived from the header's counts — never from the data.

The instruction set

Unify by restriction, enforced at the compiler

The existing .geo grammar is not bounded — it has subroutines, program-switching, neighbour propagation, and a tick that counts forever while depth stays capped. v0 does not adopt those and then guard against them. There is no encoding for them at all.

absent from v0why it breaks the bound
CALL · PROG · PLURALITYsubroutines and program-switching are unbounded recursion
tick>=N · tick%P=Rdepth is capped; time is not
EMIT · signal · neighbour readspropagation has no static step bound
any jump, any backward branchcontrol flow is “walk the parts once, in order”

The compiler refuses what the ISA cannot express, and names the key. A compiler that quietly drops a pose channel emits a binary that renders a different character and reports success — which is the exact fail-open shape this project keeps meeting. Restriction only means something if something enforces it at the boundary.

The done-when

Nine of nine

✓The dummy renders at three angles, posedfrom one program, at three times in the plan
✓Beats the 1.93 ms the plan namedworst frame 0.205 ms — 9.4× under it
✓Every control passes elementFromPoint7 controls × 3 window sizes
✓And a real drag moves each slider and its readouta hit test proves reachable, not usable
✓Every frame lands exactly on the declared ceiling5,787 / 5,787 — tight, not padded
✓The arena never overflowedacross the plan and the fuzz
✓Linear memory never grew257 pages, start to finish
✓No view detached · no page errorsthe bridge alarm stayed silent
The one that failed first

The law was written down that morning. It did not help.

GeoV has a documented law: a control is not done until elementFromPoint at its own centre returns it. It exists because a depth slider shipped unclickable for months, visible and on screen the whole time, covered by the next panel — and every clipping test passed it, because nothing was clipped.

rig-time
301×18 · hit = FALSE · covered by: nothing
rig-yaw
301×18 · hit = FALSE · covered by: nothing
rig-el
301×18 · hit = FALSE · covered by: nothing

New repo. New HUD. A CSS comment citing the old failure by name, three lines above the controls that had it. “Covered by nothing” means the centre point is outside the viewport — the panel was one long scrolling column and the sliders sat below the fold at the default window size. The buttons above them passed.

Worse: the automation's drag helper scrolls the element into view before clicking, so one slider appeared to work. The drag test alone would have shipped it.

The fix is the layout, not the test. Readouts scroll; controls are pinned and cannot scroll out of reach at any height. And the harness now probes every control at three window sizes, because reachable at one size is not reachable. Writing a law down does not install it. Only the check does.

Not done

What Sprint 2 inherits, and what is still owed

  • The bounds are declared, not derived. The header states them and four thousand programs confirm they hold — but noodle_nv = 20 is still a number a person typed. Morton order as the program counter is not in v0.
  • Plates have never run through this path. The leaf primitive is written and the crash-test dummy has none, so hair and hems remain untested in the old measurement and the new engine alike.
  • The draw path uses one colour per part. The VM emits both; GeoV's split fill belongs to the plate layer, which is not in this sprint.
  • One dialect only. .geoi and .geov do not appear here, deliberately — proving the dialect idea on one format is the point, and doing four at once is how this dies.
  • Raster numbers are software. No GPU in the bench container. Ratios travel; absolute milliseconds do not.