fix(physics): #89 — sphere-overlap in CheckBuildingTransit closes login-inside-inn classification race

Outdoor→indoor entry path used PointInsideCellBsp (point-only) for the
building-portal containment test. When the player logs in INSIDE a
building and the foot-sphere center is just past the destination cell's
CellBSP boundary, the point-only check failed → CellId stuck as
outdoor → indoor BSP queries never ran → walls passable. User-reported
symptom: "logged in in the inn, at start ran through exterior walls,
ran back in and they block now."

Fix: swap PointInsideCellBsp for SphereIntersectsCellBsp (the radius-
aware port from #90). Promotes CellId to the interior cell the moment
ANY part of the foot-sphere crosses the destination cell boundary —
matches retail's CCellStruct::sphere_intersects_cell timing at
acclient_2013_pseudo_c.txt:317666 exactly.

The sphereRadius parameter was already plumbed through CheckBuildingTransit
per #89's documented "future upgrade" note from 2026-05-19 (which is
exactly today's symptom). Closes #89.

1147 + 8 baseline maintained.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-21 09:12:30 +02:00
parent c0d84057cb
commit 7ac8f544a7

View file

@ -155,25 +155,19 @@ public static class CellTransit
/// <c>BuildingObj::find_building_transit_cells</c> +
/// <c>EnvCell::check_building_transit</c>. For each portal of the
/// outdoor building, look up the destination interior cell and test
/// whether the sphere center is inside it via
/// <see cref="BSPQuery.PointInsideCellBsp"/>. If so, add the interior
/// cell to <paramref name="candidates"/>.
/// whether the sphere overlaps it via
/// <see cref="BSPQuery.SphereIntersectsCellBsp"/>. If so, add the
/// interior cell to <paramref name="candidates"/>.
///
/// <para>
/// <b>Retail divergence:</b> retail's <c>check_building_transit</c>
/// uses <c>CCellStruct::sphere_intersects_cell</c> (radius-aware
/// BSP-vs-sphere test) which fires the moment ANY part of the sphere
/// overlaps the destination cell. Our port uses
/// <see cref="BSPQuery.PointInsideCellBsp"/> (radius-less, tests only
/// the sphere CENTER). Practical effect: entry into a building fires
/// when the player's foot-sphere center crosses the destination cell
/// boundary — roughly <paramref name="sphereRadius"/> (~0.48m) DEEPER
/// into the doorway than retail. If visual verification at the cottage
/// door shows a noticeable "late entry" effect (player visually inside
/// the building before walls switch from outdoor-stab to indoor-cell),
/// port <c>sphere_intersects_cell</c> in a follow-up.
/// <paramref name="sphereRadius"/> is plumbed through for that future
/// upgrade; currently unused.
/// Issue #89 closed (2026-05-20): uses retail's radius-aware
/// <c>CCellStruct::sphere_intersects_cell</c>
/// (<c>acclient_2013_pseudo_c.txt:317666</c>) ported as
/// <see cref="BSPQuery.SphereIntersectsCellBsp"/>. Promotes CellId to
/// the interior cell the moment ANY part of the foot-sphere crosses
/// the cell boundary — matches retail entry timing exactly and
/// closes the login-inside-inn classification race where the player
/// would briefly be classified outdoor and walk through walls.
/// </para>
/// </summary>
public static void CheckBuildingTransit(
@ -198,13 +192,22 @@ public static class CellTransit
}
// Sphere center in the OTHER cell's local space.
// Issue #89 closed (2026-05-20): use radius-aware sphere-overlap
// (matches retail's CCellStruct::sphere_intersects_cell at
// acclient_2013_pseudo_c.txt:317666) instead of point-only. This
// promotes the player's CellId to the interior cell the moment
// ANY part of the foot-sphere crosses the cell boundary — the
// entry-side counterpart to issue #90's sticky-stay fix. Without
// it, login-inside-the-inn keeps the player classified outdoor
// until they walk further in (sphere center crosses), letting
// them run through exterior walls on the way out.
var localCenter = Vector3.Transform(worldSphereCenter, otherCell.InverseWorldTransform);
bool inside = BSPQuery.PointInsideCellBsp(otherCell.CellBSP.Root, localCenter);
bool inside = BSPQuery.SphereIntersectsCellBsp(otherCell.CellBSP.Root, localCenter, sphereRadius);
if (PhysicsDiagnostics.ProbeIndoorBspEnabled)
{
Console.WriteLine(System.FormattableString.Invariant(
$"[check-bldg] portal->0x{portal.OtherCellId:X8} wpos=({worldSphereCenter.X:F3},{worldSphereCenter.Y:F3},{worldSphereCenter.Z:F3}) lpos=({localCenter.X:F3},{localCenter.Y:F3},{localCenter.Z:F3}) inside={inside}"));
$"[check-bldg] portal->0x{portal.OtherCellId:X8} wpos=({worldSphereCenter.X:F3},{worldSphereCenter.Y:F3},{worldSphereCenter.Z:F3}) lpos=({localCenter.X:F3},{localCenter.Y:F3},{localCenter.Z:F3}) r={sphereRadius:F3} inside={inside}"));
}
if (inside)