feat(physics): re-enable indoor transitions with containment validation

Sprint 2 of the audit remediation plan.

Re-enables the outdoor→indoor portal transition in PhysicsEngine with
an added containment check: after detecting a portal plane crossing,
verify the target cell's floor polygon actually covers the candidate
position AND the floor Z is within step height of the player's Z.

This prevents the wall-bounce bug (where portal planes on upper
floors captured outdoor positions) while allowing genuine doorway
transitions. Without full CellBSP, the SampleFloorZ + Z-proximity
check is the best available approximation per the indoor transition
research (docs/research/acclient_indoor_transitions_pseudocode.md).

Source: ACE EnvCell.find_transit_cells validates via
sphere_intersects_cell in the target cell's local space. Our
SampleFloorZ + Z check is the equivalent without BSP.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-13 14:05:03 +02:00
parent 4782532c4b
commit a722c29759

View file

@ -187,7 +187,13 @@ public sealed class PhysicsEngine
if (crossedPortal is not null)
{
// Outdoor → Indoor: enter the OwnerCellId.
// Outdoor → Indoor: enter the OwnerCellId IF the target cell
// actually contains the candidate position. Without CellBSP,
// we verify by checking that SampleFloorZ returns non-null
// (position is within the cell's floor polygon bounds) AND the
// floor Z is close to the player's current Z (not a basement
// 30m below). This prevents the wall-bounce bug where portal
// planes on upper floors captured outdoor positions.
uint enterCellIndex = crossedPortal.Value.OwnerCellId & 0xFFFFu;
CellSurface? enterCell = null;
foreach (var c in physics.Cells)
@ -195,8 +201,24 @@ public sealed class PhysicsEngine
if ((c.CellId & 0xFFFFu) == enterCellIndex) { enterCell = c; break; }
}
float? enterFloorZ = enterCell?.SampleFloorZ(candidatePos.X, candidatePos.Y);
targetZ = enterFloorZ ?? terrainZ;
targetCellId = enterCellIndex;
// Validate: floor must exist AND be within step height of current Z.
// This rejects transitions to basements, upper floors, and cells
// whose floor polygon doesn't actually cover this position.
bool validTransition = enterFloorZ is not null
&& MathF.Abs(enterFloorZ.Value - currentPos.Z) < stepUpHeight + 2f;
if (validTransition)
{
targetZ = enterFloorZ!.Value;
targetCellId = enterCellIndex;
}
else
{
// Portal crossed but target cell doesn't contain us — stay outdoor.
targetZ = terrainZ;
targetCellId = physics.Terrain.ComputeOutdoorCellId(localCandX, localCandY);
}
}
else
{