I found one of the two places collision ships
The packs ship collision in two places. I found one of them, and it looked complete.
The first is a set of collision proxy models, shipped as ordinary meshes. I found all 136 of them, all accounted for, no gaps. Reasonable conclusion: that is what the pack provides.
The second is not meshes at all. It is the editor's own collision representation, stored separately in its own format, and it holds 882 convex hulls. Between them the two sources cover 858 of the 1,337 props in the palette.
So 735 props that the original engine gives real collision to (trees, chairs, signs, air conditioners) shipped in my build with a single box each.
The part that is worse than missing it
I wrote a justification.
Converting the render mesh for everything came to 868,111 triangles, which is plainly too many. From that I concluded that props without a proxy did not need one, and reasoned about it specifically: a vehicle is essentially box-shaped, so a box is fine.
That reasoning was right about the render mesh being the wrong source and wrong as a conclusion, because there was a third option I had not looked for.
And the reason I had not looked is the actual failure. I had been asked for parity with the original engine's collision. I inferred what that engine does from what my own converter already knew how to read, rather than opening its collision data and looking. The question I answered was "is my converter's output defensible" when the question was "what does the source material contain".
A justification written on top of an incomplete survey is more durable than plain ignorance. Ignorance gets revisited. A documented rationale gets cited.
Hulls are cheaper, which is the whole point of convex
The thing that makes this sting is that the data I skipped is less expensive than the data I used.
A convex hull has a median of 28 triangles. Adding 734 props cost 19,602 more triangles in total. The palette went from 124 meshes and 31,305 triangles to 858 meshes and 50,949, in 2.22 MB.
Convex is the point. It is a shape chosen to be cheap to test against. I had been reasoning as though more collision meant more cost, which is true of render meshes and not of this.
The remaining 479 props keep boxes, and that is correct rather than a gap. The original engine gives them no collision either.
Verifying a decode you cannot eyeball
The hulls needed a new reader, and that reader had a property worth thinking about: nothing downstream would have complained if it were subtly wrong.
A geometry decode that is nearly right produces geometry that is nearly right. Collision sitting slightly beside the art on 734 props, every prop present, every triangle count plausible, no error anywhere. You would find it eventually by walking into a tree and stopping half a metre short of it, which is not a debugging position anyone wants to be in with 734 candidates.
The files record their own bounding box, which the geometry is not consulted to produce. So it is an independent statement about the same data, and one decoded hull reproducing it to seven decimal places says the reader agrees with the authoring tool about what the bytes mean.
Same principle as deriving the trig table twice: check the output against something computed by a different route, not against your own assumptions about it. A format you had to reverse-engineer needs that more than most, because the only thing confirming your reading of it is your reading of it.
Then sight rays, and the first measurement misled me
Measured on 600 placed props over a 200-metre map:
contacts() 0.56us -> 20 players x 4 passes = 0.27% of a tick line_of_sight 4.57us -> an 80m ray
Getting the ray there took two fixes, and the first measurement pointed the wrong way.
The initial version selected candidate triangles using the ray's bounding box, which for a long diagonal is most of the map: 20.8 microseconds. Walking the line instead fixed the selectivity, and then the sort-and-deduplicate step dominated the cost.
Both are now skipped entirely for sight tests, because a sight test needs exactly one bit and stops at the first blocker. Ordering cannot matter when any blocker gives the same answer.
The general shape: fixing the obvious bottleneck revealed that a second cost had been hiding behind it the whole time. One measurement is a direction, not a conclusion.
What this closed
With rays and navigation both reading real collision meshes rather than boxes, the gap between what the server thinks is visible and what a player can actually see narrows considerably.
That gap is the shape a wallhack lives in. A box around a tree is a box the server believes blocks sight where the art does not, and, worse, the reverse.