From 4cbfe0a5f8cf60c19f926596db1c014894ef3fe4 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 29 Apr 2026 19:13:56 +0200 Subject: [PATCH] =?UTF-8?q?fix(physics):=20L.2.3h=20=E2=80=94=20skip=20Pla?= =?UTF-8?q?cement=20in=20step-down=20contact-recovery=20branch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Live-test bug: player getting "super stuck" near walls without touching them. Diagnostic showed 0 step-up calls, so the issue wasn't in DoStepUp. Root cause: my subagent's L.2.1 commit added a Placement validation inside DoStepDown to prevent step-up-through-walls. That check is right for DoStepUp's call (the original use case). But DoStepDown is ALSO called from TransitionalInsert's contact-recovery branch when the per- sub-step contact plane is briefly lost (e.g., right after a wall-slide nudges the sphere slightly upward). For that "maintain contact during normal movement" use, the Placement check is over-strict. Wall-slide can leave the sphere with sub-EPSILON overlap of the wall's BSP solid; SphereIntersectsSolid returns Collided inside Placement; DoStepDown returns false; my L.2.3e then escalates that to TransitionState.Collided in the outer loop; ValidateTransition reverts the position to CurPos every frame. Result: player stuck near the wall without ever touching it. Fix: add a `bool runPlacement = true` parameter to DoStepDown. - DoStepUp passes the default (Placement runs — protects step-up). - TransitionalInsert's contact-recovery branch passes false (Placement skipped — accepts whatever walkable surface is found within reach). This preserves L.2.3e's edge-block (genuine edges return Collided because no walkable is found, not because Placement rejected) while unbreaking normal-walking-near-walls. ACE Transition.cs:731-741 runs Placement unconditionally, but ACE's pre-step-down state machine is cleaner — acdream's residual wall-slide artifacts make Placement misfire here. Test count 825/825 still pass. Build clean. Live verification needed: walk near a wall, should no longer get stuck. Walk off a tall (>1.5m) balcony, should still edge-block. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.Core/Physics/TransitionTypes.cs | 33 ++++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs index 6f73771..cc6944a 100644 --- a/src/AcDream.Core/Physics/TransitionTypes.cs +++ b/src/AcDream.Core/Physics/TransitionTypes.cs @@ -630,9 +630,17 @@ public sealed class Transition float radsum = sp.GlobalSphere[0].Radius * 2f; + // L.2.3h (2026-04-29): pass runPlacement=false. This + // branch's job is to maintain ground contact during normal + // movement (e.g., walking over small bumps or near walls). + // The Placement check inside DoStepDown is too strict for + // this use — minor wall overlap from a prior wall-slide + // would fail Placement and trigger the L.2.3e edge-block, + // leaving the player stuck near walls. DoStepUp still runs + // Placement for the step-UP-through-walls protection. if (radsum >= stepDownHeight) { - if (DoStepDown(stepDownHeight, zVal, engine)) + if (DoStepDown(stepDownHeight, zVal, engine, runPlacement: false)) { sp.WalkableValid = false; return TransitionState.OK; @@ -641,8 +649,8 @@ public sealed class Transition else { stepDownHeight *= 0.5f; - if (DoStepDown(stepDownHeight, zVal, engine) - || DoStepDown(stepDownHeight, zVal, engine)) + if (DoStepDown(stepDownHeight, zVal, engine, runPlacement: false) + || DoStepDown(stepDownHeight, zVal, engine, runPlacement: false)) { sp.WalkableValid = false; return TransitionState.OK; @@ -1314,7 +1322,8 @@ public sealed class Transition /// Ported from pseudocode section 5 (StepDown). /// ACE: Transition.StepDown(float stepDownHeight, float zVal). /// - private bool DoStepDown(float stepDownHeight, float walkableZ, PhysicsEngine engine) + private bool DoStepDown(float stepDownHeight, float walkableZ, PhysicsEngine engine, + bool runPlacement = true) { var sp = SpherePath; @@ -1348,6 +1357,22 @@ public sealed class Transition && CollisionInfo.ContactPlaneValid && CollisionInfo.ContactPlane.Normal.Z >= walkableZ) { + // L.2.3h (2026-04-29): Placement validation is for the + // DoStepUp use case (prevents climbing through walls by + // stepping up onto ground beyond a tall wall). For the + // "maintain contact during normal movement" use case (called + // from TransitionalInsert's contact-recovery branch), the + // Placement check is over-strict — slight wall overlap from + // a prior wall-slide makes Placement reject, then the caller + // returns Collided (L.2.3e) and the player gets stuck near + // walls without ever touching them. + // + // ACE Transition.cs:731-741 runs Placement here unconditionally, + // but ACE's pre-step-down state is cleaner — we have residual + // wall-slide artifacts that make Placement misfire. + if (!runPlacement) + return true; + // Placement validation: can we actually stand here? var savedInsert = sp.InsertType; sp.InsertType = InsertType.Placement;