fix(physics): Phase 2 — pass foot-sphere center to ResolveCellId

Visual test of Phase 2 portal traversal showed walls still didn't
block from inside buildings. Diagnosis: ResolveCellId was being
called with sp.CheckPos (entity reference, at the feet — world
Z=terrain) instead of sp.GlobalSphere[0].Origin (foot sphere center,
~0.5m above terrain). Combined with the +0.02f Z-bump on cached
cell origins (for render z-fight prevention), the test position
landed at cell-local Z=-0.02 — just below the cell floor — and
PointInsideCellBsp correctly reported "outside" for every cell.
CheckBuildingTransit never added candidates; player CellId stayed
outdoor; indoor cell-BSP collision branch never fired; walls didn't
block.

Retail's check_building_transit uses sphere.Center (the sphere CENTER,
not the entity reference) per the pseudocode at
docs/research/acclient_indoor_transitions_pseudocode.md:222-238.

Three call sites updated (PhysicsEngine x2 inside ResolveWithTransition;
TransitionTypes inside Transition.FindEnvCollisions).

Also adds a [check-bldg] diagnostic line to CheckBuildingTransit (gated
on the existing ACDREAM_PROBE_INDOOR_BSP flag) so future verification
captures show per-portal inside/outside results without needing
another diagnostic flag.

Spec: docs/superpowers/specs/2026-05-19-indoor-portal-cell-tracking-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-19 18:54:10 +02:00
parent 702b30a63e
commit 3ffe1e44f6
3 changed files with 22 additions and 5 deletions

View file

@ -186,11 +186,28 @@ public static class CellTransit
foreach (var portal in building.Portals)
{
var otherCell = cache.GetCellStruct(portal.OtherCellId);
if (otherCell?.CellBSP?.Root is null) continue;
if (otherCell?.CellBSP?.Root is null)
{
if (PhysicsDiagnostics.ProbeIndoorBspEnabled)
{
string reason = otherCell is null ? "cell not cached" : "CellBSP null";
Console.WriteLine(System.FormattableString.Invariant(
$"[check-bldg] portal->0x{portal.OtherCellId:X8} skipped: {reason}"));
}
continue;
}
// Sphere center in the OTHER cell's local space.
var localCenter = Vector3.Transform(worldSphereCenter, otherCell.InverseWorldTransform);
if (BSPQuery.PointInsideCellBsp(otherCell.CellBSP.Root, localCenter))
bool inside = BSPQuery.PointInsideCellBsp(otherCell.CellBSP.Root, localCenter);
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}"));
}
if (inside)
{
candidates.Add(portal.OtherCellId);
}

View file

@ -769,7 +769,7 @@ public sealed class PhysicsEngine
return new ResolveResult(
sp.CheckPos,
ResolveCellId(sp.CheckPos, sphereRadius, sp.CheckCellId),
ResolveCellId(sp.GlobalSphere[0].Origin, sphereRadius, sp.CheckCellId),
onGround,
collisionNormalValid,
collisionNormal);
@ -787,7 +787,7 @@ public sealed class PhysicsEngine
uint partialCellId = sp.CheckCellId != 0 ? sp.CheckCellId : cellId;
return new ResolveResult(
sp.CheckPos,
ResolveCellId(sp.CheckPos, sphereRadius, partialCellId),
ResolveCellId(sp.GlobalSphere[0].Origin, sphereRadius, partialCellId),
partialOnGround,
collisionNormalValid,
collisionNormal);

View file

@ -1181,7 +1181,7 @@ public sealed class Transition
Vector3 footCenter = sp.GlobalSphere[0].Origin;
float sphereRadius = sp.GlobalSphere[0].Radius;
uint resolvedOutdoorCellId = engine.ResolveCellId(sp.CheckPos, sphereRadius, sp.CheckCellId);
uint resolvedOutdoorCellId = engine.ResolveCellId(sp.GlobalSphere[0].Origin, sphereRadius, sp.CheckCellId);
if (resolvedOutdoorCellId != sp.CheckCellId)
sp.SetCheckPos(sp.CheckPos, resolvedOutdoorCellId);