Measure first, because the obvious suspect was innocent
Line-of-sight checks were costing 38.5% of a tick. The game runs at a fixed 60Hz, so a tick is 16.6ms and there is no borrowing from the next one.
The obvious suspect: rays travelling too far across a dense map, testing geometry all the way. The obvious fix: make them stop earlier.
The obvious suspect was innocent.
5m ray 3.10us
80m ray 6.20us
A sixteen-fold increase in distance for less than double the cost. That is nearly flat, which means the early-out was already working correctly and the distance was not the problem. Almost all of that 3.10us was being spent before the ray went anywhere.
Had I skipped the measurement and optimised the walk, I would have spent a day making the cheap half of the function faster.
Cause one: recomputing constants
Every single ray summed the lengths of 600 meshes and allocated a vector of offsets, purely to build a key for tracking which triangles it had already tested.
Those offsets never change. They are a property of the world, not of the ray. Computing them once when the spatial index is built rather than 720 times per tick: 6.20 → 5.47us.
That is the flat floor from the measurement above, removed. It was never about distance.
Cause two: nine bins per step
The world is divided into a 2m grid, and each ray needs to visit the grid cells it passes through. The old code sampled the line every half-bin, and at each sample visited that cell plus all eight neighbours.
The neighbours were not paranoia. A point sample can land in one cell while the line itself clips the corner of the next, so sampling alone genuinely misses geometry. The nine-cell ring was a correct fix for a real bug.
It is also nine visits per step, roughly 360 cells for an 80m ray, with heavy overlap between consecutive steps.
The replacement is a DDA, the Amanatides-Woo grid traversal, run in 2D across the ground plane. Instead of sampling points and hoping, it steps from one grid boundary to the next and enumerates exactly the cells the line crosses, each one once, with no ring and no overlap.
5.47 → 2.28us. Combined with the first fix, 2.7x faster overall, and sight drops from 38.5% of a tick to 12.1%.
Proving it rather than arguing it
Here correctness stops being obvious, and the subtlety is worth stating.
A DDA is exact for the line. But triangles are registered in every cell their bounding box touches, which is a larger area than the triangle itself. So a cell the line merely clips may legitimately contain a triangle the ray hits. An exact line traversal can therefore still miss real geometry if you reason only about the line.
Rather than argue this through, it is asserted. The test compares the fast path against "does any triangle in the entire world intersect this segment" across 150+ rays (axis-aligned, diagonal, short, long, and starting inside geometry) and requires the sample to contain both blocked and unblocked outcomes. A test where everything is blocked would agree perfectly and prove nothing.
That test also filled a real gap. This particular function is the path every sight check takes, and the only query that does not route through the shared traversal code, so the existing equivalence tests, which all went through that shared path, had never touched it.
The budget
audio occlusion 200 rays 0.56ms 3.4%
visibility gate 400 rays 1.12ms 6.7%
bot sight 120 rays 0.34ms 2.0%
---------------------------
all three 720 rays 2.02ms 12.1%
The visibility gate is the expensive one, and it is not optional. It is what decides which players you are told about at all: the difference between an anti-cheat that detects a wallhack and an architecture where the wallhack has nothing to draw.
An experiment that lost
Recorded rather than deleted, because a negative result stops the next person retrying it:
Baking a one-cell margin into the index, so queries could visit a single cell instead of a neighbour ring, made rays slightly faster (5.28 to 4.86us) and made collision contacts nearly three times slower, 0.54 to 1.54us, because every cell then held its neighbours' triangles too.
Movement runs far more often than sight. The trade is the wrong way round.