fix #59: tighten WorldPicker radius from 5 m to 0.7 m

User-observed bug: 'I selected a retail player once, now I cant select
anything else.' Cause: the 5 m fixed pick sphere covered most of the
visible area around an entity, so once the cursor was anywhere near an
NPC or player, every subsequent click resolved to that same NPC/player
instead of the actual cursor target.

0.7 m roughly matches the actual hitbox radius of humanoid bodies and
most pickable items. Clicking on the entity's silhouette still hits;
clicking next to it or through it to a closer target now correctly
picks the closer target.

Existing 9 WorldPicker unit tests all pass — they tested geometric
behaviour at picked test radii, not the literal 5 m constant.

Follow-ups (deferred to a future picker phase):
  - Per-itemType radius (tighter for tapers, looser for chests).
  - Priority sorting at equal hit-distance (items beat NPCs).
  - Ray-vs-actual-mesh test instead of bounding sphere.

Together with B.7's target indicator (corner triangles, c7e5f9f /
4bc95ec) this gives the user both 'I can hit what I'm aiming at'
AND 'I can see what I just hit' — fixes the over-pick at the source
plus surfaces it visually when it does still happen.
This commit is contained in:
Erik 2026-05-15 07:04:34 +02:00
parent 4bc95eca01
commit 5e29773e92

View file

@ -57,7 +57,7 @@ public static class WorldPicker
/// <summary> /// <summary>
/// Ray-sphere intersection against each candidate's <see cref="WorldEntity.Position"/> /// Ray-sphere intersection against each candidate's <see cref="WorldEntity.Position"/>
/// using a fixed 5m sphere radius. Returns the <see cref="WorldEntity.ServerGuid"/> /// using a fixed 0.7 m sphere radius. Returns the <see cref="WorldEntity.ServerGuid"/>
/// of the closest hit within <paramref name="maxDistance"/>, or null on miss. /// of the closest hit within <paramref name="maxDistance"/>, or null on miss.
/// </summary> /// </summary>
/// <param name="direction"> /// <param name="direction">
@ -68,10 +68,22 @@ public static class WorldPicker
/// of world meters. /// of world meters.
/// </param> /// </param>
/// <remarks> /// <remarks>
/// <para>
/// Entities with <c>ServerGuid == 0</c> (atlas-tier scenery, dat-hydrated /// Entities with <c>ServerGuid == 0</c> (atlas-tier scenery, dat-hydrated
/// statics) are skipped — they have no server-side identity and can't be /// statics) are skipped — they have no server-side identity and can't be
/// the target of a Use packet. The player's own guid is skipped via /// the target of a Use packet. The player's own guid is skipped via
/// <paramref name="skipServerGuid"/>. /// <paramref name="skipServerGuid"/>.
/// </para>
/// <para>
/// <b>Radius history (Issue #59).</b> Started at 5 m as a forgiving default;
/// in practice this over-picked massively — any cursor anywhere near an
/// NPC selected the NPC instead of a nearby item, and "click empty
/// ground to deselect" was nearly impossible. Tightened to 0.7 m on
/// 2026-05-15 to roughly match the actual hitbox radius of humanoids +
/// most items. A future refinement is per-itemType radius (smaller for
/// tapers, bigger for shop chests) or priority sorting (items beat
/// NPCs at equal hit-distance).
/// </para>
/// </remarks> /// </remarks>
public static uint? Pick( public static uint? Pick(
Vector3 origin, Vector3 direction, Vector3 origin, Vector3 direction,
@ -79,7 +91,7 @@ public static class WorldPicker
uint skipServerGuid, uint skipServerGuid,
float maxDistance = 50f) float maxDistance = 50f)
{ {
const float Radius = 5f; const float Radius = 0.7f;
const float Radius2 = Radius * Radius; const float Radius2 = Radius * Radius;
if (direction.LengthSquared() < 1e-10f) return null; if (direction.LengthSquared() < 1e-10f) return null;