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

forked from trevato/cs · proposes · 4 behind

The scene becomes data: src/client/scene.ts builds a frame, and the renderer draws only that #56

Merged opened this

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

Merged

`src/client/scene.ts` is new and holds every decision a frame used to make inside `draw`: the camera and the sky, the level, each living other player with its team tint and `hullHeight(p)/HULL.stand` squash, the bomb, every tracer/spark/blood/beam as line vertices under the 4096 cap, and the view model's weapon kind, bob and kick. `renderer.ts` now takes `draw(scene, dt)` and imports neither `worl…

shipped production is serving it deploy ae07c2d

opened this

src/client/scene.ts is new and holds every decision a frame used to make inside draw: the camera and the sky, the level, each living other player with its team tint and hullHeight(p)/HULL.stand squash, the bomb, every tracer/spark/blood/beam as line vertices under the 4096 cap, and the view model's weapon kind, bob and kick. renderer.ts now takes draw(scene, dt) and imports neither world.ts, physics.ts nor weapons.ts — it resolves mesh names through models.ts (meshFor), keeps one upload per name, and grows its line buffer to whatever the scene brings. Camera moved to scene.ts; main.ts keeps one scene, one camera and one Float32Array and fills them in place, and boot now hands back the running frame so main.test.ts can still see and steer the world the renderer no longer carries.

What to look at: scene.test.ts runs headlessly and pins the numbers — a tracer at 40 ms is two vertices and at 120 ms none, an impact is six and dies at 0.25 s, blood at 0.3 s, a planted bomb adds the blinking beam and a carried one adds nothing, a ducking player is 54/72 and a standing one 1, the camera's own player and a dead one are never instances, 5,000 effects stop at 4,096 vertices, the kick decays to zero over 0.12 s, and two builds return the same object, the same array and the same instance objects. The last test is the boundary itself, read off the renderer's source. replay.test.ts's golden digest is untouched — no simulation changed — bun test is 373 green, and static/app.de0b8ba773.js is the rebuilt bundle (69,711 raw / 32,142 gzip, up 1,254 bytes, under the 75,400/34,900 ceiling, which did not move).

On the risk the issue names: I could not diff pixels, so I diffed calls. I ran the previous renderer and this one side by side against a recording WebGL stub over eleven frames — level, both teams, a duck, a death, the bomb planted, tracer and impact and blood together, the awp, scoped, dead, and a walking bob — and the two issued identical draw calls: same meshes, model matrices, tints, view-projections, line buffers and line draws. Two deliberate differences: the clear colour is now set per frame from scene.sky (same [0.66, 0.73, 0.82]), and the walk bob rides the world's clock instead of the frame's dt, so its starting phase differs by up to 0.1 s of sway and nothing else — with the clocks lined up the view model matrices match exactly. That harness was scratch and is not committed.

What I did not do: I have no browser or GPU here, so I cannot report #fps before and after — that number needs the preview, and the comparison is worth making there. What I can measure is the work that moved: buildScene costs 1.57 µs per frame for ten players and 120 line vertices (200,000 builds, Bun 1.3.14 on the forge runner), about a ten-thousandth of a 16.7 ms budget, and it allocates nothing after the first frame. draw(scene, dt) keeps its dt parameter unused — the bob was the only thing using it — because a renderer's own time-based effects belong there rather than in a scene; say the word and it goes.

🤖 Generated with Claude Code

Closes #47

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs