From 0935a315bfa78d63ddd1ff1627e0009c340b59ab Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 4 Jun 2026 10:28:59 +0200 Subject: [PATCH] fix(p2): port retail slide_sphere for head near-miss (cellar wall-slide) Replace the A6.P4 Collided shortcut in transitional_insert's neg_step_up==0 branch with the faithful CSphere::slide_sphere. Retail (acclient_2013_pseudo_c.txt:273350-273351, CTransition::transitional_insert): neg_step_up==0 (HEAD-sphere near-miss) -> slide_sphere -> continue the insert loop; neg_step_up==1 (foot) -> step_up_slide. The acdream foot branch already did that; only the head branch took the shortcut (SetCollisionNormal + return Collided = dead hard-stop). The slide itself is the existing SlideSphereInternal (Sphere.SlideSphere port): it strips the into-wall component and keeps the tangential crease (collisionNormal x contactPlane.N). Surfaced by the B1 near-miss-gate fix (abbd761): once the grounded mover climbs onto the cottage floor, its head sphere brushes the cellar stairwell walls and the old hard-stop wedged it (2026-06-04 live capture: 274 (0,-1,0) + 78 (1,0,0) hits, out==current, dead oscillation). Post-fix capture shows 96 hit-and-advanced frames (the body slides along the walls). Visual-verified 2026-06-04: closed cottage door still BLOCKS (no walkthrough -- drifts sideways along it, retail-faithful); cellar ascent now works (was always stuck). An intermittent corner-wedge (slide into the -Y/+X wall corner) remains -- separate finer issue, under investigation. Core 1310 pass / 4 fail (pre-existing: 3 door documents-the-bug + D4 airborne). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/AcDream.Core/Physics/TransitionTypes.cs | 37 +++++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs index a2f5a5ce..9879e624 100644 --- a/src/AcDream.Core/Physics/TransitionTypes.cs +++ b/src/AcDream.Core/Physics/TransitionTypes.cs @@ -1070,18 +1070,33 @@ public sealed class Transition } else { - // Retail CSphere::slide_sphere — the full retail version - // adjusts sphere position via add_offset_to_check_pos + - // returns Adjusted (on success) or Collided (degenerate). - // Our simpler response: record the collision normal + - // return Collided. The outer engine sees Collided and - // does NOT advance the sphere position — block achieved. + // Retail neg_step_up==0 (HEAD-sphere near-miss) → CSphere::slide_sphere, + // then the insert loop continues (re-tests at the slid position): + // CTransition::transitional_insert, acclient_2013_pseudo_c.txt:273350-273351 + // if (sphere_path.neg_step_up == 0) + // edi = CSphere::slide_sphere(global_sphere, sphere_path, + // collision_info, neg_collision_normal, + // global_curr_center); // - // A6.P4 door inside-out fix (2026-05-25): user-visible - // blocking is the goal (retail behavior); the full - // slide-position adjustment can be a later iteration. - ci.SetCollisionNormal(sp.NegCollisionNormal); - return TransitionState.Collided; + // Earlier (A6.P4, 2026-05-25) this branch returned Collided as a + // simplification so closed doors would block. But that hard stop + // ALSO wedged a grounded mover whose HEAD sphere brushed a wall + // while moving along it — e.g. exiting the Holtburg cottage cellar: + // the body reached the cottage floor (Z=94) but oscillated against + // the stairwell walls with no slide (2026-06-04 live capture, 16k + // frames: 274 (0,-1,0) + 78 (1,0,0) hits, out==current). The + // faithful slide_sphere slides tangentially along the wall (crease = + // collisionNormal × contactPlane.Normal), which un-wedges the cellar + // AND still blocks a closed door — the into-door (+Y) component is + // removed and only the tangential X slide survives, so there is no + // walkthrough. + var slideRes = SlideSphereInternal( + sp.NegCollisionNormal, sp.GlobalCurrCenter[0].Origin); + if (slideRes == TransitionState.Collided) + return TransitionState.Collided; // degenerate slide → hard stop + // Slid / Adjusted / OK → re-test at the (slid) CheckPos, mirroring + // retail's insert-loop continuation after slide_sphere. + continue; } }