fix(phys): A6.P3 slice 3 v3 — REVERT stickiness; hypothesis-test cellar-up

Slice 3 v2 (point-in stickiness) closed the cell-resolver ping-pong
(data confirmed: scen4_cottage_cellar_slice3v2 capture shows 1 cell-
transit vs 20+ pre-fix). BUT user verification revealed: cellar-up
symptom transitioned from "stuck-at-top-ping-pong" (pre-slice-3) to
"never-reach-top-stuck-in-cellar" (post-slice-3). Stickiness was
holding player in cellar cell so aggressively that the legitimate
transition to the cottage main floor cell at the ramp top never
fired.

Reverting the stickiness check entirely. Trade-off:
- Inn doorway ping-pong returns (existed pre-slice-3; lesser evil)
- Player can again reach the top of the cellar ramp (per pre-slice-3
  user observation)
- Issue #98 cellar-up remains open — but with sharper diagnosis: it's
  not the cell resolver at all, it's deeper (BSP step-physics or
  AdjustOffset slope-projection at the cottage main floor boundary,
  per slice 4 polydump trace showing repeated push-back on the
  46-degree ramp polygon)

The slice 3 stickiness premise was correct but the implementation
shape was wrong. A future attempt needs either:
- A "near boundary" gate (only stick when sphere is deep inside cell)
- A retail-faithful per-cell hysteresis matching CObjCell::find_cell_list
  Position-variant (acclient_2013_pseudo_c.txt:308742-308783) more
  exactly than point-in
- OR address the underlying BSP step-physics bug first; then ping-pong
  may not even need a stickiness fix

Test suite: 1148 + 8 (baseline maintained).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-22 12:43:58 +02:00
parent 319847289e
commit 8bd311759e

View file

@ -262,46 +262,25 @@ public sealed class PhysicsEngine
// _landblocks (no DataCache dependency). // _landblocks (no DataCache dependency).
if (DataCache is null) return fallbackCellId; if (DataCache is null) return fallbackCellId;
// ── Cell-stickiness check (A6.P3 slice 3, 2026-05-22) ── // ── Cell-stickiness REVERTED (A6.P3 slice 3 v3, 2026-05-22) ──
// Before re-resolving via FindCellList, check if the fallback // Slice 3 v1 (sphere-overlap, 8898166) over-corrected — held
// CellId still validly contains the sphere CENTER (point-in). // player in cellar even when transitioning out at the ramp top.
// If yes, prefer it over any FindCellList result that might // Slice 3 v2 (point-in, 3e140cf) closed the ping-pong at the
// pick a different cell whose BSP also contains the point — // inn doorway (data confirmed) BUT prevented the player from
// fixes issue #98 (cellar-up stuck at last step due to CellId // reaching the top of the cellar ramp (the stuck spot
// ping-ponging between adjacent cells in iteration-order races). // transitioned from "ping-pong at top" to "never reach top").
// //
// Mechanism: when the sphere is on a cell boundary where the // Reverting to no-stickiness for now. The ping-pong at the inn
// CENTER is in multiple cells geometrically (overlapping BSPs), // doorway returns but is a lesser evil than blocking cellar-up
// FindCellList's candidate-iteration order (HashSet, // entirely. Issue #98 cellar-up has a deeper bug that needs
// implementation-defined) determines which cell wins. That // separate investigation (BSP step-physics or AdjustOffset
// order may shift tick-to-tick → ping-pong. Stickiness keeps // slope-projection at the cottage main floor boundary).
// the CURRENT cell as long as the sphere center is still
// inside it, only switching when the center has moved out.
// //
// Uses POINT-IN (not sphere-overlap). Sphere-overlap stickiness // Slice 3 work remains valuable as research evidence; the fix
// (the first slice 3 attempt) over-corrected — held the player // shape was wrong. Issue #90 stays as workaround until a
// in the fallback cell even when the center had transitioned to // better stickiness mechanism is designed (probably needs to
// an adjacent cell, blocking legitimate cell transitions at // be GATED by some "near cell boundary" check rather than
// stair tops + portal exits. Point-in matches FindCellList's // applied unconditionally).
// own semantics for "which cell are you in."
//
// Retail oracle: cell-array hysteresis pattern from
// CObjCell::find_cell_list Position-variant at
// acclient_2013_pseudo_c.txt:308742-308783.
//
// Likely closes/obsoletes:
// - #98 (cellar ascent stuck at last step) — direct target
// - #97 (phantom collisions + fall-through on 2nd floor) —
// same instability family hypothesized
// - #90 (sphere-overlap workaround below) — superseded;
// can be removed after visual verification (A6.P4)
var fallbackCell = DataCache.GetCellStruct(fallbackCellId);
if (fallbackCell?.CellBSP?.Root is not null)
{
var fallbackLocal = Vector3.Transform(worldPos, fallbackCell.InverseWorldTransform);
if (BSPQuery.PointInsideCellBsp(fallbackCell.CellBSP.Root, fallbackLocal))
return fallbackCellId; // center still inside; stick.
}
// Fallback cell no longer valid → re-resolve via portal-graph BFS. // Fallback cell no longer valid → re-resolve via portal-graph BFS.
uint indoorResult = CellTransit.FindCellList(DataCache, worldPos, sphereRadius, fallbackCellId); uint indoorResult = CellTransit.FindCellList(DataCache, worldPos, sphereRadius, fallbackCellId);