fix(phys): A6.P5 — unconditional outdoor expansion in CellTransit BFS
Retail's CObjCell::find_cell_list at acclient_2013_pseudo_c.txt:308742- 308869 walks vtable[0x80] on every cell in the array and adds portal- reachable cells unconditionally — without testing each portal plane against the sphere. Our exit-portal branch in FindTransitCellsSphere gated outdoor inclusion on sphere-plane overlap (exitOutside fired only when the sphere physically straddled the exit portal plane). That gate produced the cottage-door over-penetration bug verified in A6P5_BuildCellSetFromIndoorStart_ReachesDoorOutdoorCell: BFS from indoor cell 0xA9B4013F expanded to 0xA9B40150 (which has an exit portal) but the sphere — in 0xA9B4013F's volume — wasn't at 0xA9B40150's exit portal plane, so exitOutside stayed false and the door's outdoor cell 0xA9B40029 wasn't added to the cellSet. The cell-crossing tick's collision query missed the door and the sphere committed 0.27 m INTO the slab. Fix: exit portals contribute exitOutside=true by topology (OtherCellId == 0xFFFFu), not by sphere overlap. AddAllOutsideCells is deduped to once per BFS so the radial pattern is added exactly once when any BFS-visited cell has an exit portal. Conformance: A6P5_BuildCellSetFromIndoorStart_ReachesDoorOutdoorCell now passes. A6P5_BuildCellSetFromAlcove_AlsoReachesDoorOutdoorCell (regression guard for the previously-sometimes-working case) stays green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2a890e6bde
commit
3b1ae83931
1 changed files with 38 additions and 18 deletions
|
|
@ -94,22 +94,33 @@ public static class CellTransit
|
|||
|
||||
if (portal.OtherCellId == 0xFFFF)
|
||||
{
|
||||
// Exit portal. Any path sphere straddling the plane triggers
|
||||
// the outdoor cell expansion.
|
||||
for (int i = 0; i < sphereCount; i++)
|
||||
{
|
||||
var sphere = worldSpheres[i];
|
||||
float rad = sphere.Radius + EPSILON;
|
||||
var localCenter = Vector3.Transform(
|
||||
sphere.Origin, currentCell.InverseWorldTransform);
|
||||
float dist = Vector3.Dot(localCenter, poly.Plane.Normal) + poly.Plane.D;
|
||||
bool hit = dist > -rad && dist < rad;
|
||||
if (hit)
|
||||
{
|
||||
exitOutside = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// A6.P5 (2026-05-25): exit portals add outdoor cells
|
||||
// UNCONDITIONALLY, by topology — not by sphere-plane overlap.
|
||||
//
|
||||
// Retail's CObjCell::find_cell_list (acclient_2013_pseudo_c.txt
|
||||
// :308742-308869) walks vtable[0x80] on every cell already in
|
||||
// the array and adds reachable cells without testing the
|
||||
// sphere against each portal plane. The straddle check we
|
||||
// had here gated outdoor inclusion on the sphere physically
|
||||
// overlapping the EXIT portal — which fails to fire when:
|
||||
// a) the sphere is in a SIBLING indoor cell that BFS-
|
||||
// expanded to this one (sphere is geographically near
|
||||
// the doorway region, just not at THIS cell's exit
|
||||
// portal plane); OR
|
||||
// b) the per-tick target moves the sphere across the
|
||||
// portal plane on one tick but not the next, producing
|
||||
// intermittent visibility from the same position.
|
||||
//
|
||||
// Pre-fix bug: cottage doors at outdoor cells were invisible
|
||||
// from indoor cells during cell-crossing substeps (live
|
||||
// capture 2026-05-25; over-penetration test in
|
||||
// CellTransitTests.A6P5_BuildCellSetFromIndoorStart_...).
|
||||
//
|
||||
// Post-fix: any cell visited by BFS that has at least one
|
||||
// exit portal contributes exitOutside=true regardless of
|
||||
// sphere position. AddAllOutsideCells fires once per BFS
|
||||
// (deduped in BuildCellSetAndPickContaining).
|
||||
exitOutside = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -441,6 +452,7 @@ public static class CellTransit
|
|||
pending.Enqueue(currentCellId);
|
||||
visited.Add(currentCellId);
|
||||
int maxIterations = 16; // hard cap; portal graphs are small
|
||||
bool outdoorAdded = false;
|
||||
while (pending.Count > 0 && maxIterations-- > 0)
|
||||
{
|
||||
uint cellId = pending.Dequeue();
|
||||
|
|
@ -461,10 +473,18 @@ public static class CellTransit
|
|||
}
|
||||
}
|
||||
|
||||
if (exitOutside)
|
||||
// A6.P5 (2026-05-25): any BFS-visited cell with an exit
|
||||
// portal triggers the outdoor-neighbourhood add — matches
|
||||
// retail's CObjCell::find_cell_list at
|
||||
// acclient_2013_pseudo_c.txt:308742-308869 which expands
|
||||
// portal-reachable cells unconditionally via vtable[0x80].
|
||||
// Dedupe to once per BFS — the radial pattern depends only
|
||||
// on the seed cell + sphere XY, so repeated calls would
|
||||
// be no-ops with extra HashSet overhead.
|
||||
if (exitOutside && !outdoorAdded)
|
||||
{
|
||||
// Add neighbour outdoor cells too.
|
||||
AddAllOutsideCells(worldSpheres, sphereCount, currentCellId, candidates);
|
||||
outdoorAdded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue