#112 ROOT CAUSE: outdoor-seed pick lacked retail's growing-array walk - threshold tick-skip became absorbing
The instrumented capture (cottage-112-capture1.log) + dat replay pinned the transparent-cottage mechanism end to end: 1. The A9B3 cottage's entry cell 0x104 is a 0.22 m-wide THRESHOLD band (x 184.68->184.46 at y~82). A running player (~13-16 cm/tick at 30 Hz) can cross it BETWEEN two physics ticks - the tick where the centre is inside 0x104 never happens. 2. Our outdoor-seed branch ran CheckBuildingTransit over a landcell snapshot and STOPPED - building-admitted entry cells were never expanded. The tick after the skip (centre in 0x100, a deep room not building-portal-adjacent) found no containing candidate -> the pick kept the outdoor landcell FOREVER (absorbing): the user walked the whole interior classified outdoor (render faithfully drew an outdoor frame = transparent walls), promoting only on touching portal-adjacent 0x102's own volume minutes later (captured: 0xA9B3003C -> 0xA9B30102 with no transitions in between). 3. Retail cannot strand: CObjCell::find_cell_list (0x0052b4e0) runs ONE growing-array walk for EVERY seed (0052b576-0052b5ab, cells[i]->find_transit_cells vtable dispatch over the GROWING array) - the landcell's building bridge admits 0x104 (the foot sphere still overlaps the band one tick after the skip) and the walk expands 0x104's portals to 0x100 where containment wins. Recovery fires one tick after any skip. Fix: BuildCellSetAndPickContaining now runs retail's single growing walk for both seeds with per-cell-type dispatch (landcells -> CLandCell::find_transit_cells 0x00533800 -> CSortCell 0x00534060 -> check_building_transit 0x0052c5d0; envcells -> FindTransitCellsSphere with the straddle gate + once-per-walk outside add). The old indoor branch behavior is preserved (seed at index 0, hysteresis, straddle- gated outdoor pick); the outdoor branch gains the expansion + the indoor branch gains the retail landcell bridge dispatch for straddle-admitted landcells. Pins (dat-backed, Issue112MembershipTests): tick-skip recovery one tick past the threshold (RED pre-fix); run-speed entry replay across tick phases never strands outdoor; threshold-gap outdoor-seed keeps outdoor (over-fix guard); entry-walk replay diagnostic prints the full promotion chain (0x3C -> 0x104 -> 0x100 -> 0x103 -> 0x100 -> 0x102). Suites: App 246+1skip / Core 1438+2skip / UI 420 / Net 294. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
756ea61e30
commit
be03146e30
2 changed files with 229 additions and 54 deletions
|
|
@ -740,72 +740,85 @@ public static class CellTransit
|
|||
// sphere straddled an exterior portal plane during the BFS (set below).
|
||||
bool outdoorPickAllowed = currentLow < 0x0100u;
|
||||
|
||||
// SEED (retail CObjCell::find_cell_list 0052b535-0052b56c): an indoor id
|
||||
// adds exactly the current cell at INDEX 0 (the current-cell-first pick
|
||||
// hysteresis that stops the flap); an outdoor id adds every landcell the
|
||||
// path spheres overlap (add_all_outside_cells, 0052b53f — which also
|
||||
// sets CELLARRAY.added_outside, hence outdoorAdded starts true there).
|
||||
bool outdoorAdded;
|
||||
if (currentLow >= 0x0100u)
|
||||
{
|
||||
// Indoor seed: the CURRENT cell is added at INDEX 0 (retail
|
||||
// CObjCell::find_cell_list add_cell @ pseudo_c:308766). Index 0 is what
|
||||
// makes the pick current-cell-first — the hysteresis that stops the flap.
|
||||
var currentCell = cache.GetCellStruct(currentCellId);
|
||||
if (currentCell is null) return currentCellId;
|
||||
candidates.Add(currentCellId);
|
||||
|
||||
// EXPAND — a single forward walk over the GROWING array, mirroring
|
||||
// retail's `for (i=0; i<num_cells; i++) cells[i].find_transit_cells(...)`
|
||||
// loop (pseudo_c:308775-308785). FindTransitCellsSphere APPENDS portal
|
||||
// neighbours (and, on an exit portal, the outdoor landcells) to the same
|
||||
// array; CellArray.Add dedups, so the walk terminates when no new cell is
|
||||
// appended. Read OrderedIds[i] by index because the list grows under us.
|
||||
bool outdoorAdded = false;
|
||||
for (int i = 0; i < candidates.Count; i++)
|
||||
{
|
||||
uint cellId = candidates.OrderedIds[i];
|
||||
var cell = cache.GetCellStruct(cellId);
|
||||
if (cell is null) continue;
|
||||
|
||||
FindTransitCellsSphere(
|
||||
cache, cell, cellId, worldSpheres, sphereCount,
|
||||
candidates, out bool exitOutsideStraddle);
|
||||
|
||||
// #112 rider (2026-06-10): the retail straddle flag (live-binary
|
||||
// verified — see FindTransitCellsSphere) gates the PICK's outdoor
|
||||
// branch below. Retail only ever has outdoor cells in this array
|
||||
// when a path sphere straddles an exterior portal plane.
|
||||
outdoorPickAllowed |= exitOutsideStraddle;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
outdoorAdded = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Outdoor seed: expand neighbour landcells (added first), then check each
|
||||
// for a building stab whose portals cross into an interior EnvCell.
|
||||
// (Stage 2 will make building entry intrinsic and remove CheckBuildingTransit.)
|
||||
AddAllOutsideCells(worldSpheres, sphereCount, currentCellId, blockOrigin, candidates);
|
||||
outdoorAdded = true;
|
||||
}
|
||||
|
||||
var landcellSnapshot = new List<uint>(candidates.OrderedIds);
|
||||
foreach (uint landcellId in landcellSnapshot)
|
||||
// THE WALK — ONE forward pass over the GROWING array for EVERY seed,
|
||||
// mirroring retail's `for (i=0; i<num_cells; i++)
|
||||
// cells[i]->find_transit_cells(...)` vtable dispatch (pseudo_c:
|
||||
// 308775-308785 / 0052b576-0052b5ab). CellArray.Add dedups, so the walk
|
||||
// terminates when no new cell is appended; read OrderedIds[i] by index
|
||||
// because the list grows under us.
|
||||
//
|
||||
// #112 ROOT CAUSE (2026-06-12, cottage-112-capture1.log): the outdoor
|
||||
// seed used to run CheckBuildingTransit over a landcell SNAPSHOT and
|
||||
// stop — building-admitted entry cells were never expanded, so a player
|
||||
// whose centre stood in a DEEP room (not building-portal-adjacent)
|
||||
// could never be promoted from an outdoor seed: the pick kept the
|
||||
// outdoor landcell while they walked the cottage interior (transparent
|
||||
// interior; promotion fired only on touching portal-adjacent 0x102's
|
||||
// own volume). Retail's single growing walk expands the admitted entry
|
||||
// cells to the deeper rooms the spheres overlap — ported below.
|
||||
for (int i = 0; i < candidates.Count; i++)
|
||||
{
|
||||
uint cellId = candidates.OrderedIds[i];
|
||||
|
||||
if ((cellId & 0xFFFFu) < 0x0100u)
|
||||
{
|
||||
var building = cache.GetBuilding(landcellId);
|
||||
// Landcell dispatch — CLandCell::find_transit_cells (0x00533800)
|
||||
// → CSortCell::find_transit_cells (0x00534060, this->building)
|
||||
// → CBuildingObj::find_building_transit_cells (0x006b5230)
|
||||
// → CEnvCell::check_building_transit (0x0052c5d0): the building
|
||||
// bridge admits the building's portal-adjacent ENTRY cells into
|
||||
// the same growing array; the walk then expands them via the
|
||||
// envcell dispatch below.
|
||||
var building = cache.GetBuilding(cellId);
|
||||
if (building is null) continue;
|
||||
CheckBuildingTransit(cache, building, worldSphereCenter, sphereRadius, candidates);
|
||||
CheckBuildingTransit(cache, building, worldSpheres, sphereCount, candidates, out _);
|
||||
continue;
|
||||
}
|
||||
|
||||
var cell = cache.GetCellStruct(cellId);
|
||||
if (cell is null) continue;
|
||||
|
||||
FindTransitCellsSphere(
|
||||
cache, cell, cellId, worldSpheres, sphereCount,
|
||||
candidates, out bool exitOutsideStraddle);
|
||||
|
||||
// #112 rider (2026-06-10): the retail straddle flag (live-binary
|
||||
// verified — see FindTransitCellsSphere) gates the PICK's outdoor
|
||||
// branch below. Retail only ever has outdoor cells in this array
|
||||
// when a path sphere straddles an exterior portal plane.
|
||||
outdoorPickAllowed |= exitOutsideStraddle;
|
||||
|
||||
// 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. 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue