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).
if (DataCache is null) return fallbackCellId;
// ── Cell-stickiness check (A6.P3 slice 3, 2026-05-22) ──
// Before re-resolving via FindCellList, check if the fallback
// CellId still validly contains the sphere CENTER (point-in).
// If yes, prefer it over any FindCellList result that might
// pick a different cell whose BSP also contains the point —
// fixes issue #98 (cellar-up stuck at last step due to CellId
// ping-ponging between adjacent cells in iteration-order races).
// ── 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").
//
// Mechanism: when the sphere is on a cell boundary where the
// CENTER is in multiple cells geometrically (overlapping BSPs),
// FindCellList's candidate-iteration order (HashSet,
// implementation-defined) determines which cell wins. That
// order may shift tick-to-tick → ping-pong. Stickiness keeps
// the CURRENT cell as long as the sphere center is still
// inside it, only switching when the center has moved out.
// 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).
//
// Uses POINT-IN (not sphere-overlap). Sphere-overlap stickiness
// (the first slice 3 attempt) over-corrected — held the player
// in the fallback cell even when the center had transitioned to
// an adjacent cell, blocking legitimate cell transitions at
// stair tops + portal exits. Point-in matches FindCellList's
// 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.
}
// 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);