Public: anyone can read this; the crew can change it.

forked from trevato/cs · proposes · 4 behind

The range is drawn in three dimensions: the room, the plates and the tracer through the engine’s renderer #76

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

The range is drawn: `src/range/scene.ts` builds the engine's `Scene` from a `Run`, a view and a trail — the room as the first instance, one plate instance per standing target at its own place and sized to its own box, the last shot as two line vertices for 80 ms, and the range's own dark indoor sky — and `src/range/main.ts` hands `createRenderer` the atlas `paint.ts` paints and the two meshes `lev…

shipped production is serving it deploy ae07c2d

opened this

The range is drawn: src/range/scene.ts builds the engine's Scene from a Run, a view and a trail — the room as the first instance, one plate instance per standing target at its own place and sized to its own box, the last shot as two line vertices for 80 ms, and the range's own dark indoor sky — and src/range/main.ts hands createRenderer the atlas paint.ts paints and the two meshes level.ts builds, then draws that scene every frame with the readout's panel still on the HUD canvas above it. It imports nothing from src/client/. A browser with no WebGL2 keeps the game it had: createRenderer returns null, the scene canvas takes a 2D context instead, drawHorizon draws the plates as blips, the panel's last line says which of the two you are looking at, and the page says so in prose.

The gap the issue existed to find, named: the engine gained exactly one thing, Instance.size in src/engine/renderer.ts — a uniform scale, default 1, applied before the vertical squash that was already there. The renderer could place one mesh but not resize it, so a plate could only ever be drawn at the half-width its mesh was built at; modelMatrix already took three scales, so this exposes what the math did and nothing more. It is neither game's: the shooter writes no size and its bundle grew 18 bytes. renderer.test.ts covers it, and GUIDE.md's description of an Instance and its seam table follow.

The pin is unmoved. src/range/pin.ts and pin.test.ts were not touched and RANGE is still 852637bcfcc0106c9b3e569e52a19ac9d91f1adffab8afc7d0a0e96e1817f4ed — drawing the game did not change the game.

What I measured, and on what. On the forge's box with Bun: one frame of the whole entry — loop, controls, the run's ticks, buildRangeScene, every GL call and the HUD's 2D — is 0.021 ms at a 360 px phone's stage (720 × 540 device pixels, DPR clamped to 2), against the 16.7 ms a 60 fps frame has; buildRangeScene alone is 158 ns with three plates up and allocates nothing (a test asserts the pool and the line buffer are the same objects frame after frame). A frame is 30 GL calls, 3–4 drawElements and one drawArrays; the whole world is 84 triangles and a 48 KB atlas of three 64² layers, uploaded once. Boot — painting the atlas, building both meshes, uploading them — is about 30 ms once. /range answers in 0.7–3.4 ms. I could not run a real GPU browser in this sandbox, so the 60 fps claim is a CPU and draw-call budget, not a reading off a phone; the page prints its own smoothed frame time in the corner for anyone checking on hardware. The stage CSS is untouched, so 360 px wide with no horizontal scroll and 44 px controls are as they were.

bun test is green (446 tests). static/range.81ebd406a7.js is rebuilt and committed; its ceiling is raised from 12,800/5,800 to 26,700/11,600 for a measured 24,214 raw and 10,500 gzip — the +11.7 KB is the engine's renderer, the room and plate meshes and the three-layer atlas now reaching the bundle, which is the price of the range being drawn at all. bundle.test.ts boots the committed file down both paths, so a mangled name in the renderer's seam fails bun test rather than the preview.

What I chose not to do: no movement (the eye still stands at the firing line — that waits on the engine's physics), no view model, no sound, and no try around createRenderer — a shader that fails to compile still throws, as it does for the shooter, because that is a bug and not a browser.

To try it: open /range, capture the mouse, press <kbd>Enter</kbd> and shoot down the lane — the room, the plates fading as their lifetimes run out, and a tracer on every shot. To see the fallback, disable WebGL2 in the browser: the flat horizon comes back and the panel's last line reads no WebGL2 here · the 2D horizon instead.

🤖 Generated with Claude Code

Closes #54

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs