From 88981669fe3067580976f9b19245c9005ff3910d Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 22 May 2026 12:01:28 +0200 Subject: [PATCH] =?UTF-8?q?fix(phys):=20A6.P3=20slice=203=20=E2=80=94=20ce?= =?UTF-8?q?ll-resolver=20stickiness=20for=20ping-pong=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes A6.P2 Finding 3 (cell-resolver instability) + issue #98 (cellar ascent stuck at last step) + likely closes #97 (phantom collisions + fall-through on 2nd floor; same instability family). Adds a cell-stickiness check at the top of ResolveCellId's indoor branch: before re-resolving via FindCellList, check if the fallback (previous-tick) CellId's BSP still validly contains the sphere. If yes, return fallbackCellId immediately — preserves cell membership when the sphere is at a boundary where multiple cells overlap. The bug: at cell boundaries (cellar last step, indoor doorways, between two adjacent indoor cells), the sphere overlaps multiple cells geometrically. FindCellList's candidate-iteration order (HashSet, implementation-defined) determines which cell wins. That order may shift tick-to-tick → CellId ping-pong → AdjustOffset operates against a different cell's geometry each tick → player can't accumulate forward motion → stuck. Evidence: scen3_inn_2nd_floor_slice2v2 capture shows the ping-pong chain at the cellar boundary: 0xA9B4014B → 0xA9B4014A → 0xA9B4013F → 0xA9B4014A → 0xA9B4014B (Z stable ~96.4; CellId oscillates every tick; reason=resolver) Retail oracle: cell-array hysteresis pattern from CObjCell::find_cell_list Position-variant at acclient_2013_pseudo_c.txt:308742-308783. Retail preserves cell membership when sphere is close to (but slightly past) cell boundaries. Implementation: 9 lines added (sphere-overlap check against fallbackCellId's CellBSP before falling through to FindCellList). Existing #90 workaround at line 299-300 (post-FindCellList sphere- overlap check) is now redundant in the common case but kept for safety; deferred to A6.P4 removal after visual verification. Test suite: 1148 pass + 8 pre-existing fail (baseline maintained). Visual verification: pending — user happy-test will confirm cellar- up walk succeeds + no ping-pong in cell-transit log. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.Core/Physics/PhysicsEngine.cs | 39 +++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/AcDream.Core/Physics/PhysicsEngine.cs b/src/AcDream.Core/Physics/PhysicsEngine.cs index aaf5a48..be3f76c 100644 --- a/src/AcDream.Core/Physics/PhysicsEngine.cs +++ b/src/AcDream.Core/Physics/PhysicsEngine.cs @@ -261,6 +261,45 @@ public sealed class PhysicsEngine // Indoor branch needs DataCache to look up cells; outdoor uses // _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. If yes, prefer it + // over any FindCellList result that might pick a different + // overlapping cell — fixes issue #98 (cellar-up stuck at last + // step due to CellId ping-ponging between adjacent cells whose + // BSPs all overlap the sphere). + // + // Mechanism: when the sphere is on a cell boundary where it + // overlaps multiple cells, FindCellList's candidate-iteration + // order (HashSet, implementation-defined) determines which + // cell wins. That order may shift tick-to-tick → ping-pong. + // Stickiness ensures we keep the CURRENT cell as long as the + // sphere still validly overlaps it, only switching when the + // sphere has truly left. + // + // Retail oracle: cell-array hysteresis pattern from + // CObjCell::find_cell_list Position-variant at + // acclient_2013_pseudo_c.txt:308742-308783. Retail's cell + // resolution preserves cell membership when the sphere is + // close to (but slightly past) cell boundaries. + // + // 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 stickiness workaround in this same + // function below) — superseded; can be removed after + // visual verification (deferred to A6.P4) + var fallbackCell = DataCache.GetCellStruct(fallbackCellId); + if (fallbackCell?.CellBSP?.Root is not null) + { + var fallbackLocal = Vector3.Transform(worldPos, fallbackCell.InverseWorldTransform); + if (BSPQuery.SphereIntersectsCellBsp(fallbackCell.CellBSP.Root, fallbackLocal, sphereRadius)) + return fallbackCellId; // sphere still overlaps; stick. + } + + // 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