A "perfect" shot only landed 43% of the time — at 30 fps
A bullet doesn't move in a straight line between two rendered frames. On a slow computer, that gap is exactly where a mathematically correct shot can go missing.
For a while, a shot that was dead-center — right lead, right drop, right wind correction, everything the solver said should connect — would still sometimes just not register. Not often enough to be obvious, and only on slower hardware. That combination is exactly what makes a bug like this dangerous: it hides.
Where it hid
The original hit test asked one question, once per rendered frame: is the bullet's current position touching the target right now? At 60 frames a second that question gets asked every 16 milliseconds, which is often enough that it rarely mattered. At 30 frames a second — a plausible frame rate on modest hardware, or on a phone — the same question is only asked every 33 milliseconds, and a bullet does not wait politely between questions.
If a target is narrower than the distance the bullet travels in a single frame, the bullet can be cleanly on one side of it on frame N and cleanly past it on frame N+1 — and never once occupy a point in space where the game happened to check. The aim was correct. The physics were correct. The test was just asking the wrong question: a single point in time, when the thing that actually needs checking is everything the bullet passed through to get there.
Two fixes, not one
- One integrator, one step. The reticle's drop marks, the ballistic-hold assist, every bot, and the automated tests all now call the exact same fixed 120 Hz stepper. Nothing in the game is allowed to keep its own approximation of where a bullet is.
- Every hit test is swept, not a point check. Instead of "is the bullet touching the target right now", the question became "did the segment from where the bullet just was to where it is now cross the target" — checked against boxes, cylinders and spheres depending on what's in the way. That's the change that actually closes the gap; the fixed step alone only makes the gap smaller, not zero.
A bot that couldn't hit anything
Re-testing the physics surfaced a second, unrelated bug in the same neighbourhood: the Tower Duel bot's own wind compensation had drifted out of sync with the real solver. It was adding a correction roughly a hundred times too strong, in a formula that also had the drop compensation's sign flipped. Across 8,000 simulated duels, that bot recorded zero hits.
Neither bug was a flaw in the ballistics themselves — the physics were fine in both cases. Both were a system quietly using its own approximation instead of the one real answer everyone else was using. The fix for both was the same instinct: stop letting anything re-derive its own version of the truth. A test now checks the solver and the fixed-step integrator agree to within half a unit, every run, so the two can't drift apart again without being noticed.