fix(physics): use horizontal distance for cylinder broad-phase

The broad-phase rejection was using 3D distance for cylinder objects,
which includes the Z offset between player feet and cylinder base.
Trees have their origin at the base (Z=ground) while the player
sphere is at chest height (Z=ground+~2.5m). The 3D distance exceeded
the combined radius, causing the collision test to be skipped entirely.

Fix: use horizontal (XY) distance for cylinder broad-phase since
the vertical extent is checked separately in the cylinder test.
Also increase broad-phase margin from 1m to 2m.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-14 12:49:04 +02:00
parent efd6a06c7d
commit 4d335c74da

View file

@ -657,8 +657,14 @@ public sealed class Transition
foreach (var obj in _nearbyObjs)
{
// Broad-phase: can the moving sphere reach this object?
float distToCurr = Vector3.Distance(currPos, obj.Position);
float maxReach = sphereRadius + obj.Radius + movement.Length() + 1f;
// Use horizontal distance for cylinders (Z extent is checked separately).
Vector3 deltaToCurr = currPos - obj.Position;
float distToCurr;
if (obj.CollisionType == ShadowCollisionType.Cylinder)
distToCurr = MathF.Sqrt(deltaToCurr.X * deltaToCurr.X + deltaToCurr.Y * deltaToCurr.Y);
else
distToCurr = deltaToCurr.Length();
float maxReach = sphereRadius + obj.Radius + movement.Length() + 2f;
if (distToCurr > maxReach)
continue;
@ -755,14 +761,13 @@ public sealed class Transition
if (bestT >= float.MaxValue)
{
// Debug: log nearby objects that didn't trigger collision
if (_debugMissCounter++ % 120 == 0)
if (_debugMissCounter++ % 120 == 0 && _nearbyObjs.Count > 0)
{
foreach (var obj in _nearbyObjs)
{
float d = Vector3.Distance(checkPos, obj.Position);
if (d < 3f)
Console.WriteLine($"NEAR: obj {obj.EntityId:X8} gfx={obj.GfxObjId:X8} type={obj.CollisionType} dist={d:F2} r={obj.Radius:F2} cylH={obj.CylHeight:F2} pos={obj.Position}");
float hd = MathF.Sqrt((checkPos.X-obj.Position.X)*(checkPos.X-obj.Position.X)+(checkPos.Y-obj.Position.Y)*(checkPos.Y-obj.Position.Y));
Console.WriteLine($"MISS: eid={obj.EntityId:X8} gfx={obj.GfxObjId:X8} type={obj.CollisionType} dist={d:F2} hdist={hd:F2} r={obj.Radius:F2} cylH={obj.CylHeight:F1} playerZ={checkPos.Z:F1} objZ={obj.Position.Z:F1} player=({checkPos.X:F1},{checkPos.Y:F1}) obj=({obj.Position.X:F1},{obj.Position.Y:F1})");
}
}
return TransitionState.OK;