fix(physics): R1 membership — current-cell-first hysteresis in find_cell_list pick
The flap R1 exposed is a cell-membership ping-pong: the find_cell_list containing- cell pick (CellTransit.BuildCellSetAndPickContaining) iterated an UNORDERED HashSet and returned the first interior cell whose BSP contains the sphere center, with no preference for the current cell. Retail CObjCell::find_cell_list adds the current cell at index 0 (add_cell, pc:308766) and iterates current-first with interior-wins- break (pc:308791-308819) — you STAY in your current cell until the center genuinely leaves it. acdream's HashSet dropped that ordering; once the candidate set churns at a boundary the enumeration can surface a neighbour before the current cell → the ping-pong. Restore the explicit, deterministic current-cell-first test (retail's index-0 hysteresis). + a two-direction regression guard (current cell wins the straddle). Diagnosed from the existing [cell-transit] walk log (no new probing): room flips are the pick non-determinism; stairs flips additionally show the foot Z oscillating ~0.2m/tick (a separate stairs-physics residual, #98 family, to verify after this). The 2 DoorBugTrajectoryReplay failures are PRE-EXISTING (verified: they fail without this change too) — 2 of the handoff's '3 door-collision apparatus / A6.P5'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
58822fed96
commit
5ca2f448d4
2 changed files with 63 additions and 0 deletions
|
|
@ -519,6 +519,25 @@ public static class CellTransit
|
|||
PhysicsDiagnostics.LogCellSetBuild(currentCellId, worldSphereCenter, candidates);
|
||||
}
|
||||
|
||||
// Retail CObjCell::find_cell_list checks the CURRENT cell FIRST — it adds it at index 0
|
||||
// (add_cell, pseudo_c:308766) and the pick loop iterates from index 0 with interior-wins-break
|
||||
// (pseudo_c:308791-308819). So if the sphere center is still inside the current cell, it wins
|
||||
// and the search stops: you STAY in your current cell until the center genuinely leaves it,
|
||||
// never flipping to an overlapping neighbour. acdream's unordered HashSet candidate set dropped
|
||||
// that ordering — once the set churns at a boundary the enumeration can surface a neighbour
|
||||
// before the current cell, producing the membership ping-pong (the R1 flap). Restore the
|
||||
// explicit, deterministic current-cell-first test (the retail hysteresis):
|
||||
if (currentLow >= 0x0100u)
|
||||
{
|
||||
var curCell = cache.GetCellStruct(currentCellId);
|
||||
if (curCell?.CellBSP?.Root is not null)
|
||||
{
|
||||
var localCur = Vector3.Transform(worldSphereCenter, curCell.InverseWorldTransform);
|
||||
if (BSPQuery.PointInsideCellBsp(curCell.CellBSP.Root, localCur))
|
||||
return currentCellId; // still inside the current cell → stay (retail index-0)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue