diff --git a/src/AcDream.Core/Selection/WorldPicker.cs b/src/AcDream.Core/Selection/WorldPicker.cs
index a0da64c..1c49b70 100644
--- a/src/AcDream.Core/Selection/WorldPicker.cs
+++ b/src/AcDream.Core/Selection/WorldPicker.cs
@@ -60,6 +60,13 @@ public static class WorldPicker
/// using a fixed 5m sphere radius. Returns the
/// of the closest hit within , or null on miss.
///
+ ///
+ /// World-space ray direction. Must be normalized — the geometric
+ /// ray-sphere formula simplifies a = dot(direction, direction) to
+ /// 1; non-unit input produces an undocumented t-scale that
+ /// makes maxDistance compare against ray-parameter units instead
+ /// of world meters.
+ ///
///
/// Entities with ServerGuid == 0 (atlas-tier scenery, dat-hydrated
/// statics) are skipped — they have no server-side identity and can't be
@@ -94,8 +101,13 @@ public static class WorldPicker
float d = b * b - c;
if (d < 0f) continue;
- float t = -b - MathF.Sqrt(d);
- if (t < 0f) continue; // ray points away or origin inside
+ // Two intersection roots: t_near = -b - sqrt(d), t_far = -b + sqrt(d).
+ // If t_near < 0 the ray origin is INSIDE the sphere; fall through
+ // to t_far so the entity is still pickable at point-blank range.
+ float sqrtD = MathF.Sqrt(d);
+ float t = -b - sqrtD;
+ if (t < 0f) t = -b + sqrtD; // origin inside sphere -> use far exit
+ if (t < 0f) continue; // both roots negative -> sphere entirely behind ray
if (t >= maxDistance) continue;
if (t < bestT)
{
diff --git a/tests/AcDream.Core.Tests/Selection/WorldPickerTests.cs b/tests/AcDream.Core.Tests/Selection/WorldPickerTests.cs
index 20550db..ac0bc5a 100644
--- a/tests/AcDream.Core.Tests/Selection/WorldPickerTests.cs
+++ b/tests/AcDream.Core.Tests/Selection/WorldPickerTests.cs
@@ -149,4 +149,21 @@ public class WorldPickerTests
Assert.Null(result);
}
+
+ [Fact]
+ public void Pick_RayOriginInsideEntitySphere_StillReturnsServerGuid()
+ {
+ // Player ~3m from a door -> camera near-plane sits INSIDE the door's
+ // 5m bounding sphere. Naive t_near < 0 guard would skip; correct
+ // behavior is to fall through to t_far (the sphere exit point).
+ var entity = MakeEntity(0xABCDu, new Vector3(0, 0, -3));
+
+ var result = WorldPicker.Pick(
+ origin: Vector3.Zero,
+ direction: -Vector3.UnitZ,
+ candidates: new[] { entity },
+ skipServerGuid: 0u);
+
+ Assert.Equal(0xABCDu, result);
+ }
}