Split The code is public; the app answers only to the crew.
Bullet holes that stay: a fixed ring of decals, drawn in one call #144
Check · Review · Land · Live done — Landed and live in production.
Pushed. `bun run check` is green (577 tests, 40 client test files).
shipped merged
Sign in to join this conversation.
new above
offline
Pushed.
bun run checkis green (577 tests, 40 client test files).---
Shooting a wall now leaves something behind. Every round that hits the level marks the surface it hit — turned by the normal the impact effect already carried, lifted half a unit off the wall so it cannot z-fight — and the mark stays for the round instead of for a quarter of a second.
src/client/decals.tsis the ring, and it is the only new concept: 64 slots allocated once at start-up, the oldest overwritten when it is full, each a fan of six triangles about a dark centre. The marks are drawn multiplied into the wall behind them (DST_COLOR, ZERO), so a vertex colour of 1 is a wall untouched and the fade is nothing but the centre colour walking back up to white over 25 seconds of the clock, never a count of frames. That needs a position and a colour, which is exactly what the line program already takes — so all 64 holes are onedrawArraysof triangles, inside the pass the tracers were already in: one more draw, no third program, no texture, no per-decal draw and nothing allocated per shot. The frame's draw rule inrenderer.test.tsgained adecalsterm and the worst round the game can build is now 60 draws against its budget of 80, still two programs switched four times.The renderer takes each impact once, on the tick it was stamped (an impact effect lives two seconds and a frame meets it 120 times), and drops the ring when
match.roundmoves or the clock runs backwards — a fresh round starts on clean walls, which is the round-restart test. Only the level is marked:world.ts's impact effect now says whether the trace reported a surface, so blood and the five sparks a grenade throws into open air mark nothing.What I chose not to do:
models.ts:198's unit quad is untouched and there is no instance buffer — instancing a textured quad would have meant a third program and a per-decal attribute stream for something that is 1152 vertices in total, and the fan carries its own falloff in vertex colours for free. And nothing culls decals per frame: at a low render scale the pass is fragment work like everything else, so it shrinks with the buffer, and off-screen it is one draw whose triangles are clipped — the only guard worth having is the one that is there, which skips the draw and the upload entirely when the ring is empty or everything in it has faded.In the preview: spray a wall on the T side of mid and walk away — the marks are still there when you come back, they overlap into a patch where you held the trigger, they darken a crate on both faces a round punched through, and they are gone the moment the next round's freeze time starts.
🤖 Generated with Claude Code
Closes #109