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:
parent
dbfbf8506c
commit
ca4b482f8b
5 changed files with 101 additions and 205 deletions
|
|
@ -290,84 +290,38 @@ public sealed class PhysicsEngine
|
|||
cg.CurrCell = cell;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TEST-ONLY outdoor cell re-derive. The single caller is
|
||||
/// <c>Transition.FindEnvCollisions</c>'s cache-null fallback
|
||||
/// (PhysicsEngineTests run engines without a <see cref="DataCache"/>,
|
||||
/// so <see cref="CellTransit.FindCellSet"/> is unavailable). Production
|
||||
/// membership flows exclusively through the collide-then-pick advance
|
||||
/// (<c>RunCheckOtherCellsAndAdvance</c> → <c>FindCellSet</c>).
|
||||
///
|
||||
/// <para>
|
||||
/// BR-7 / A6.P4 C4 (2026-06-11): the former indoor branch — including
|
||||
/// the #90 sphere-overlap stickiness workaround (4ca3596) and the
|
||||
/// building-transit promotion — was DEAD CODE on this path (it required
|
||||
/// a non-null DataCache; the only caller guarantees null) and is
|
||||
/// removed. #90's doorway ping-pong concern is owned by the retail
|
||||
/// ordered-pick hysteresis (current cell at array index 0,
|
||||
/// interior-wins-break; CellTransit.BuildCellSetAndPickContaining) —
|
||||
/// the workaround is retired, closing the digest's deferred-removal
|
||||
/// item.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>Preserves the L.2e prefix-preservation fix (always apply the
|
||||
/// matched landblock's high-16 prefix even when
|
||||
/// <paramref name="fallbackCellId"/> arrived bare-low-byte).</para>
|
||||
/// </summary>
|
||||
internal uint ResolveCellId(Vector3 worldPos, float sphereRadius, uint fallbackCellId)
|
||||
{
|
||||
if (fallbackCellId == 0) return 0;
|
||||
|
||||
uint fallbackLow = fallbackCellId & 0xFFFFu;
|
||||
// Indoor fallback ids pass through unchanged — identical to the old
|
||||
// dead path's `DataCache is null → return fallbackCellId` outcome.
|
||||
if ((fallbackCellId & 0xFFFFu) >= 0x0100u) return fallbackCellId;
|
||||
|
||||
if (fallbackLow >= 0x0100u)
|
||||
{
|
||||
// Indoor branch needs DataCache to look up cells; outdoor uses
|
||||
// _landblocks (no DataCache dependency).
|
||||
if (DataCache is null) return fallbackCellId;
|
||||
|
||||
// ── Cell-stickiness REVERTED (A6.P3 slice 3 v3, 2026-05-22) ──
|
||||
// Slice 3 v1 (sphere-overlap, 8898166) over-corrected — held
|
||||
// player in cellar even when transitioning out at the ramp top.
|
||||
// Slice 3 v2 (point-in, 3e140cf) closed the ping-pong at the
|
||||
// inn doorway (data confirmed) BUT prevented the player from
|
||||
// reaching the top of the cellar ramp (the stuck spot
|
||||
// transitioned from "ping-pong at top" to "never reach top").
|
||||
//
|
||||
// Reverting to no-stickiness for now. The ping-pong at the inn
|
||||
// doorway returns but is a lesser evil than blocking cellar-up
|
||||
// entirely. Issue #98 cellar-up has a deeper bug that needs
|
||||
// separate investigation (BSP step-physics or AdjustOffset
|
||||
// slope-projection at the cottage main floor boundary).
|
||||
//
|
||||
// Slice 3 work remains valuable as research evidence; the fix
|
||||
// shape was wrong. Issue #90 stays as workaround until a
|
||||
// better stickiness mechanism is designed (probably needs to
|
||||
// be GATED by some "near cell boundary" check rather than
|
||||
// applied unconditionally).
|
||||
|
||||
// Fallback cell no longer valid → re-resolve via portal-graph BFS.
|
||||
uint indoorResult = CellTransit.FindCellList(DataCache, worldPos, sphereRadius, fallbackCellId);
|
||||
|
||||
// ISSUES #83 / Phase A1.7 (2026-05-21): verify the indoor result
|
||||
// actually contains the player. CellTransit.FindCellList falls back
|
||||
// to currentCellId when no candidate cell's CellBSP contains the
|
||||
// sphere center — but this happens even when the player has walked
|
||||
// OUTSIDE the entire portal-connected indoor cell graph (e.g.,
|
||||
// exited through an unblocked wall or doorway gap). In that state
|
||||
// the player's CellId is stuck on an indoor cell whose BSP is
|
||||
// far away, every indoor-bsp query returns OK (NodeIntersects
|
||||
// fails at root), and no walls block.
|
||||
//
|
||||
// If the resolved indoor cell's BSP does NOT contain the sphere
|
||||
// center, fall through to the outdoor cell resolution below — it
|
||||
// will compute the correct landcell from the terrain grid and
|
||||
// optionally re-enter an indoor cell via CheckBuildingTransit.
|
||||
var indoorCell = DataCache.GetCellStruct(indoorResult);
|
||||
if (indoorCell?.CellBSP?.Root is null)
|
||||
return indoorResult; // render root (CurrCell) set by the player's UpdateCellId // Can't verify (no CellBSP); trust FindCellList.
|
||||
|
||||
// Issue #90 fix (2026-05-20): use SPHERE-overlap instead of POINT-in
|
||||
// for the indoor verification. The previous point-only check caused
|
||||
// a per-frame ping-pong at the inn doorway: indoor BSP push-back
|
||||
// moved the sphere CENTER a few cm outside the indoor CellBSP
|
||||
// volume → point-only check returned false → fell through to outdoor
|
||||
// → next tick re-promoted to indoor → wall hit → push-back →
|
||||
// outdoor → repeat. Net visual behavior: "walls walk through"
|
||||
// because outdoor ticks bypass indoor BSP entirely. With sphere-
|
||||
// overlap, the player stays classified indoor as long as ANY part
|
||||
// of the foot sphere still overlaps the indoor cell volume.
|
||||
//
|
||||
// Retail oracle: CCellStruct::sphere_intersects_cell at
|
||||
// acclient_2013_pseudo_c.txt:317666 →
|
||||
// BSPTREE::sphere_intersects_cell_bsp at :323267.
|
||||
var localCenter = Vector3.Transform(worldPos, indoorCell.InverseWorldTransform);
|
||||
if (BSPQuery.SphereIntersectsCellBsp(indoorCell.CellBSP.Root, localCenter, sphereRadius))
|
||||
return indoorResult; // render root (CurrCell) set by the player's UpdateCellId
|
||||
|
||||
// Fall through to outdoor resolution: player has FULLY left the
|
||||
// indoor portal-connected graph (sphere no longer overlaps).
|
||||
}
|
||||
|
||||
// Outdoor seed: use terrain grid to compute the prefixed cell id.
|
||||
// Preserves the L.2e prefix-preservation fix (always apply the matched
|
||||
// landblock's high-16 prefix even when fallbackCellId arrived bare-low-byte).
|
||||
foreach (var kvp in _landblocks)
|
||||
{
|
||||
var lb = kvp.Value;
|
||||
|
|
@ -376,29 +330,7 @@ public sealed class PhysicsEngine
|
|||
if (localX >= 0f && localX < 192f && localY >= 0f && localY < 192f)
|
||||
{
|
||||
uint lowCellId = lb.Terrain.ComputeOutdoorCellId(localX, localY);
|
||||
uint outdoorCellId = (kvp.Key & 0xFFFF0000u) | lowCellId;
|
||||
|
||||
// Outdoor→indoor entry: if this landcell has a cached building,
|
||||
// check whether the sphere has crossed into one of its interior
|
||||
// EnvCells via the building's portals.
|
||||
if (DataCache is not null)
|
||||
{
|
||||
var building = DataCache.GetBuilding(outdoorCellId);
|
||||
if (building is not null)
|
||||
{
|
||||
var candidates = new System.Collections.Generic.HashSet<uint>();
|
||||
CellTransit.CheckBuildingTransit(
|
||||
DataCache, building, worldPos, sphereRadius, candidates);
|
||||
if (candidates.Count > 0)
|
||||
{
|
||||
// First candidate wins — building portal containment is
|
||||
// mutually exclusive in retail (one interior cell per portal).
|
||||
foreach (var c in candidates) return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return outdoorCellId; // render root (CurrCell) set by the player's UpdateCellId
|
||||
return (kvp.Key & 0xFFFF0000u) | lowCellId;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue