T6 (BR-7) C4: straddle-only outside-add (A6.P5 widening DELETED) + #90 stickiness removed

The two remaining flagged workarounds retired, per the BR-7 plan +
the WF1 [MEDIUM] correction (re-gate, do NOT delete the outside-add):

1. A6.P5 hasExitPortal topology widening DELETED. Outdoor cells enter the
   collision cell array ONLY on the retail straddle gate - |dist| <
   radius + F_EPSILON against an exterior portal plane
   (CEnvCell::find_transit_cells Ghidra 0x0052c820, gate 0052c9d6,
   live-binary verified) - the same flag that already gated the
   membership pick (#112 rider). The widening existed so outdoor-
   registered doors stayed findable from indoor cells under the old flat
   registry query; with per-cell shadow lists the door is found in the
   straddle-admitted outdoor cell's own list (tick-13558 pin holds).
   The hasExitPortal out-param + plumbing deleted from
   FindTransitCellsSphere; the AddAllOutsideCells call in
   BuildCellSetAndPickContaining re-gated on exitOutsideStraddle
   (once-per-walk = retail CELLARRAY.added_outside).

2. #90 ResolveCellId sphere-overlap stickiness REMOVED (the 4ca3596
   workaround, deferred-to-A6.P4 in the physics digest). It was dead
   code: the method's only caller is FindEnvCollisions' cache-null TEST
   fallback, and the indoor branch (where the stickiness lived) required
   a non-null DataCache. Production membership flows exclusively through
   the collide-then-pick advance whose ordered-array hysteresis (current
   cell at index 0, interior-wins-break) is the retail mechanism the
   workaround approximated. ResolveCellId reduced to the bare
   prefix-preserving outdoor re-derive, documented test-only.

Test updates (pins of the deleted behaviors inverted to retail):
- A6P5_BuildCellSetFromIndoorStart_ReachesDoorOutdoorCell (asserted the
  topology widening verbatim) -> DeepInteriorSphere_NoStraddle_
  AddsNoOutdoorCells: a deep-interior sphere admits NO outdoor cells.
- A6P5_BuildCellSetFromAlcove... -> AlcoveSphere_StraddlesExitPortal_
  ReachesDoorOutdoorCell (the captured alcove position genuinely
  straddles - the retail-positive half).
- Issue112MembershipTests straddle pin + the second-sphere straddle test
  updated to the single-flag signature.

Suites: Core 1416/0/2, App 225, UI 420, Net 294 - green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-11 14:44:49 +02:00
parent dbfbf8506c
commit ca4b482f8b
5 changed files with 101 additions and 205 deletions

View file

@ -58,20 +58,6 @@ public static class CellTransit
float sphereRadius,
ICollection<uint> candidates,
out bool exitOutside)
=> FindTransitCellsSphere(
cache, currentCell, currentCellId, worldSphereCenter, sphereRadius,
candidates, out exitOutside, out _);
/// <inheritdoc cref="FindTransitCellsSphere(PhysicsDataCache, CellPhysics, uint, IReadOnlyList{Sphere}, int, ICollection{uint}, out bool, out bool)"/>
public static void FindTransitCellsSphere(
PhysicsDataCache cache,
CellPhysics currentCell,
uint currentCellId,
Vector3 worldSphereCenter,
float sphereRadius,
ICollection<uint> candidates,
out bool exitOutside,
out bool hasExitPortal)
{
var spheres = new[]
{
@ -84,7 +70,7 @@ public static class CellTransit
FindTransitCellsSphere(
cache, currentCell, currentCellId,
spheres, spheres.Length, candidates, out exitOutside, out hasExitPortal);
spheres, spheres.Length, candidates, out exitOutside);
}
/// <summary>
@ -98,12 +84,9 @@ public static class CellTransit
/// This is the only condition under which retail's
/// <c>CEnvCell::find_transit_cells</c> calls <c>add_all_outside_cells</c>
/// (acclient.exe 0052c8e5-0052c92d straddle test, 0052c9d2-0052c9f0 gate;
/// pseudo-C :310070-310120). Drives the membership pick's outdoor branch.</param>
/// <param name="hasExitPortal">Topology-only: this cell has at least one
/// exterior (0xFFFF) portal, regardless of sphere position. NOT retail —
/// kept for the A6.P5 collision cell-set widening (outdoor-registered door
/// entities must stay findable from indoor cells until the A6.P4 per-cell
/// shadow architecture ships; see the exterior-portal branch comment).</param>
/// pseudo-C :310070-310120). Drives the membership pick's outdoor branch
/// AND (BR-7 C4, retail-faithfully) the collision cell-set outside-add —
/// the former <c>hasExitPortal</c> topology widening is deleted.</param>
public static void FindTransitCellsSphere(
PhysicsDataCache cache,
CellPhysics currentCell,
@ -111,11 +94,9 @@ public static class CellTransit
IReadOnlyList<Sphere> worldSpheres,
int numSpheres,
ICollection<uint> candidates,
out bool exitOutside,
out bool hasExitPortal)
out bool exitOutside)
{
exitOutside = false;
hasExitPortal = false;
uint lbPrefix = currentCellId & 0xFFFF0000u;
int sphereCount = EffectiveSphereCount(worldSpheres, numSpheres);
@ -129,8 +110,6 @@ public static class CellTransit
if (portal.OtherCellId == 0xFFFF)
{
hasExitPortal = true;
// #112 rider (2026-06-10): retail straddle gate, RESTORED and
// verified against the LIVE 2013 binary (cdb attach, function
// 0052c820; x87 decode at 0052c8e5-0052c92d):
@ -144,19 +123,17 @@ public static class CellTransit
// portal_side / exact_match in this branch — BN's pseudo-C
// invented those (feedback_bn_decomp_field_names).
//
// History: this gate existed pre-A6.P5 and was removed
// 2026-05-25 citing the CALLER (find_cell_list :308775-:308785
// walks every array cell unconditionally — true, but each
// callee still applies its own straddle gate). The A6.P5
// symptom it fixed (outdoor-registered cottage DOORS invisible
// to collision from indoor cells) is really the missing
// per-cell shadow_object_list (#99/A6.P4): retail finds those
// doors via shadow lists, not via outdoor transit cells. Until
// A6.P4 ships, BuildCellSetAndPickContaining keeps widening the
// COLLISION cell set on hasExitPortal — but the membership PICK
// gates its outdoor branch on this retail flag, which is what
// keeps deep-interior containment gaps on curr_cell (retail
// keep-curr) instead of demoting to outdoor (#112).
// History: this gate existed pre-A6.P5, was removed 2026-05-25
// citing the CALLER (find_cell_list :308775-:308785 walks every
// array cell unconditionally — true, but each callee still
// applies its own straddle gate), and was restored for the
// membership PICK by the #112 rider. BR-7 / A6.P4 C4
// (2026-06-11) finished the story: the per-cell shadow
// architecture made the A6.P5 hasExitPortal topology widening
// unnecessary (doors are found in the straddle-admitted outdoor
// cell's own list), so this flag now gates BOTH the pick's
// outdoor branch AND the collision cell-set outside-add —
// pure retail.
if (!exitOutside)
{
for (int i = 0; i < sphereCount; i++)
@ -540,7 +517,7 @@ public static class CellTransit
FindTransitCellsSphere(
cache, cell, cellId, worldSpheres, sphereCount,
candidates, out bool exitStraddle, out _);
candidates, out bool exitStraddle);
if (exitStraddle && !outdoorAdded)
{
@ -787,7 +764,7 @@ public static class CellTransit
FindTransitCellsSphere(
cache, cell, cellId, worldSpheres, sphereCount,
candidates, out bool exitOutsideStraddle, out bool hasExitPortal);
candidates, out bool exitOutsideStraddle);
// #112 rider (2026-06-10): the retail straddle flag (live-binary
// verified — see FindTransitCellsSphere) gates the PICK's outdoor
@ -795,16 +772,21 @@ public static class CellTransit
// when a path sphere straddles an exterior portal plane.
outdoorPickAllowed |= exitOutsideStraddle;
// A6.P5 (kept, NARROWED to the collision cell SET): the first
// exit-portal cell triggers the outdoor neighbourhood add once, by
// TOPOLOGY — wider than retail. This keeps outdoor-registered door
// entities findable from indoor cells (the 2026-05-25 door capture)
// until #99/A6.P4 ships per-cell shadow lists; the pick no longer
// consumes these cells unless the retail flag fired, so membership
// matches retail in both regimes. Appended AFTER the interior cells,
// matching retail order (add_all_outside_cells at the end,
// pseudo_c:310120) — interior-wins is preserved.
if (hasExitPortal && !outdoorAdded)
// BR-7 / A6.P4 C4 (2026-06-11): outdoor cells enter the array
// on the retail STRADDLE gate — |dist| < radius + F_EPSILON
// against an exterior portal plane (CEnvCell::find_transit_cells
// 0x0052c820; gate at 0052c9d6) — replacing the A6.P5
// hasExitPortal TOPOLOGY widening. The widening existed to keep
// outdoor-registered doors findable from indoor cells under the
// old flat query; with per-cell shadow lists the door is found
// in the straddle-admitted outdoor cell's OWN list (and the
// straddle fires whenever a sphere is genuinely at the
// threshold — the tick-13558 door pin proves the admission).
// Appended AFTER the interior cells, matching retail order
// (add_all_outside_cells at the end, pseudo_c:310120) —
// interior-wins is preserved. Once-per-walk via outdoorAdded =
// retail CELLARRAY.added_outside (0x00533630).
if (exitOutsideStraddle && !outdoorAdded)
{
AddAllOutsideCells(worldSpheres, sphereCount, currentCellId, blockOrigin, candidates);
outdoorAdded = true;