From 4d335c74da7f0ce915d3a02be03e8b02f61623bd Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 14 Apr 2026 12:49:04 +0200 Subject: [PATCH] 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) --- src/AcDream.Core/Physics/TransitionTypes.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs index 7eb99fb..ad6d9e7 100644 --- a/src/AcDream.Core/Physics/TransitionTypes.cs +++ b/src/AcDream.Core/Physics/TransitionTypes.cs @@ -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;