A .map() Inside a Raycast Is an Allocation Per Bullet
Dead Signal's decal placement rebuilt a visibility array every ray. Five rays a shot, a closure and a fresh array each. One Uint8Array sized at build fixed it.
Dead Signal places combat decals on the drawn level, not on the bullet grid. The grid stops a round at a cell face; what the eye sees there may be 35 cm further in, or missing, or round. So there is a SurfaceIndex: the merged static level batches copied into a quarter-cell triangle grid once per map, plus a list of opaque occluders like door leaves, window boards and machines that a decal must never sit behind. Every query honours visibility, so Low quality never gets a decal on a mesh it does not draw.
That visibility check is where the waste was. rayLevel, the function that walks the grid for one ray, started with this.meshes.map((m, i) => this.visible(m) && ...). A fresh boolean array and a closure, per ray. A shot is five rays. Add the fit() test that confirms the whole decal quad sits on real geometry, which runs more rays, and a shotgun burst was allocating a pile of small arrays in the exact frames that matter most.
None of this showed as a slow function. It showed as GC. Short-lived arrays are cheap right up until the collector decides it is time, and the collector likes to decide that in the middle of a fight.
The replacement is a Uint8Array called meshVis, sized once when the index is built, filled with a plain for loop at the top of rayLevel. Zero allocation per ray. The loop body is the same test, it just writes a 1 or a 0 instead of returning a boolean to a closure.
The same commit removed a .slice() from fits(). It called occludersNear, which returns a shared scratch list, and copied it defensively before iterating. The copy was there in case something inside the loop re-entered occludersNear and overwrote the list. I read the loop. It calls ray(), and ray() never calls occludersNear. The slice was protecting against a path that does not exist, at the cost of an array per fit.
The raycast itself had already had its big fix earlier: three's Mesh.raycast tests every triangle once a ray touches the geometry's single bounding sphere, and the merged wall-buy gun mesh is 49k triangles spread over the whole map. Every ray walked all of it, about 4 ms each. Now that mesh carries per-chunk boxes of 384 triangles and only the crossed chunks go through the stock test.
The lesson from the small commit, not the big one: when a function runs per ray or per frame, a .map() is not a style choice. It is an allocation. Read the hot path for anything that returns a new thing, then ask whether the defensive copy is defending against something real.