feat(core): Phase W — faithful find_cell_list membership (interior-wins pick + swept determination, drop static :1947)
Change A (TransitionTypes.FindEnvCollisions:~1947): replace the unconditional static ResolveCellId re-derive with the SWEPT find_cell_list pick via CellTransit.FindCellSet. When DataCache is available (always in production), the swept pick runs and resolves the containing cell from the portal-graph candidate set. When DataCache is null (test engines without a cell registry), the old ResolveCellId fallback is preserved to keep PhysicsEngineTests green. Change B (CellTransit.BuildCellSetAndPickContaining): replace the containment loop that silently skipped all outdoor candidates (CellBSP=null) with the retail CObjCell::find_cell_list interior-wins pick (pseudo_c:308788-308819): interior EnvCells win first; if no interior cell contains the center, fall to the outdoor XY-grid column (CLandCell::point_in_cell equivalent). This is the missing half of find_cell_list that caused the 0xA9B40170↔0xA9B40031 doorway cell-strobe — the swept pick previously always returned currentCellId for outdoor candidates, letting the static re-derive at :1947 strobe on every tick from a different result. DoorwayMembershipReplayTests: two facts, loads doorway-capture.jsonl (364K records, strobing live run), filters to Y∈[15.5,17.5] seam zone (57 records), verifies FindCellSet produces exactly 1 transition (enter indoor → stay outdoors) with zero A→B→A ping-pong across the full window. Second test verifies outdoor-seed records round-trip correctly via the XY-grid formula. Both pass. LiveCompare_FirstCap_FixClosesCottageFloorCap: still passes (issue #98 gate intact). Full Core suite: 15 failures (within documented flaky baseline of 14–19; all 15 are pre-existing static-leak/document-the-bug tests, zero new regressions in cell/transit/BSP/physics classes). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ed00719cf4
commit
59f3a1380d
3 changed files with 386 additions and 10 deletions
|
|
@ -519,22 +519,36 @@ public static class CellTransit
|
|||
PhysicsDiagnostics.LogCellSetBuild(currentCellId, worldSphereCenter, candidates);
|
||||
}
|
||||
|
||||
// Containment test: for each candidate, transform worldSphereCenter to
|
||||
// local and test PointInsideCellBsp.
|
||||
// Retail CObjCell::find_cell_list containing-cell pick (pseudo_c:308788-308819):
|
||||
// INTERIOR-WINS — the first EnvCell whose point_in_cell (BSP) contains the sphere center
|
||||
// wins and stops the search. Only if no interior cell contains it do we fall to the
|
||||
// outdoor landcell (CLandCell::point_in_cell = the XY-column the sphere is over). This is
|
||||
// the half of find_cell_list acdream had not ported (it skipped outdoor candidates), and
|
||||
// it is what lets the SWEPT pick transition indoor<->outdoor without a static re-derive.
|
||||
uint lbPrefix = currentCellId & 0xFFFF0000u;
|
||||
foreach (uint candId in candidates)
|
||||
{
|
||||
if ((candId & 0xFFFFu) < 0x0100u) continue; // interior pass only
|
||||
var cand = cache.GetCellStruct(candId);
|
||||
if (cand?.CellBSP?.Root is null) continue;
|
||||
|
||||
var local = Vector3.Transform(worldSphereCenter, cand.InverseWorldTransform);
|
||||
if (BSPQuery.PointInsideCellBsp(cand.CellBSP.Root, local))
|
||||
return candId; // interior-wins, stop
|
||||
}
|
||||
// No interior cell contains the center → outdoor landcell point_in_cell (XY-column).
|
||||
// worldSphereCenter is landblock-local (A6.P4 convention); the cell index mirrors
|
||||
// AddAllOutsideCells' grid math (CellSize=24, low = gx*8 + gy + 1).
|
||||
{
|
||||
int gx = (int)(worldSphereCenter.X / 24f);
|
||||
int gy = (int)(worldSphereCenter.Y / 24f);
|
||||
if (gx >= 0 && gx < 8 && gy >= 0 && gy < 8)
|
||||
{
|
||||
return candId;
|
||||
uint outdoorId = lbPrefix | (uint)(gx * 8 + gy + 1);
|
||||
if (candidates.Contains(outdoorId))
|
||||
return outdoorId;
|
||||
}
|
||||
}
|
||||
|
||||
// No cell contained the sphere center. Stay in the input cell.
|
||||
return currentCellId;
|
||||
return currentCellId; // nothing contained the center — stay
|
||||
}
|
||||
|
||||
private static int EffectiveSphereCount(IReadOnlyList<Sphere> worldSpheres, int numSpheres)
|
||||
|
|
|
|||
|
|
@ -1944,9 +1944,28 @@ public sealed class Transition
|
|||
Vector3 footCenter = sp.GlobalSphere[0].Origin;
|
||||
float sphereRadius = sp.GlobalSphere[0].Radius;
|
||||
|
||||
uint resolvedOutdoorCellId = engine.ResolveCellId(sp.GlobalSphere[0].Origin, sphereRadius, sp.CheckCellId);
|
||||
if (resolvedOutdoorCellId != sp.CheckCellId)
|
||||
sp.SetCheckPos(sp.CheckPos, resolvedOutdoorCellId);
|
||||
// Phase W: cell membership comes from the SWEPT find_cell_list pick (retail
|
||||
// CObjCell::find_cell_list), not a static re-derive. FindCellSet builds candidates anchored
|
||||
// to the current cell (interior neighbors + outside cells via the exit portal + building
|
||||
// re-entry cells) and picks the containing cell interior-wins. The commit to sp.CurCellId
|
||||
// is gated by ValidateTransition (accept-on-move), so a push-back can't flip the cell.
|
||||
//
|
||||
// DataCache-null fallback: PhysicsEngineTests use engines without a DataCache (no cell
|
||||
// registry). FindCellSet requires a cache, so we fall back to the old ResolveCellId
|
||||
// outdoor re-derive in that case. In production DataCache is always set.
|
||||
if (engine.DataCache is not null)
|
||||
{
|
||||
uint sweptCellId = CellTransit.FindCellSet(
|
||||
engine.DataCache, sp.GlobalSphere, sp.NumSphere, sp.CheckCellId, out _);
|
||||
if (sweptCellId != sp.CheckCellId)
|
||||
sp.SetCheckPos(sp.CheckPos, sweptCellId);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint resolvedOutdoorCellId = engine.ResolveCellId(sp.GlobalSphere[0].Origin, sphereRadius, sp.CheckCellId);
|
||||
if (resolvedOutdoorCellId != sp.CheckCellId)
|
||||
sp.SetCheckPos(sp.CheckPos, resolvedOutdoorCellId);
|
||||
}
|
||||
|
||||
// ── Indoor cell BSP collision ────────────────────────────────────
|
||||
// If the player is in an indoor cell (low 16 bits >= 0x0100),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue