From a48883af2d597da36669ada8054841991374df7c Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 30 Apr 2026 10:29:30 +0200 Subject: [PATCH] =?UTF-8?q?fix(physics):=20L.4-cliffslide-priority=20?= =?UTF-8?q?=E2=80=94=20steep=20ContactPlane=20check=20before=20OnWalkable?= =?UTF-8?q?=20gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User-reported: "still don't slide down steep roofs" after the previous trigger-gate fix (52e257d). Traced through the EdgeSlide dispatcher: the gate IS firing now, but ValidateTransition's L.2.3i FloorZ test clears OnWalkable as soon as the player is on a steep surface. So EdgeSlideAfterStepDownFailed enters Branch 1 (`!OnWalkable → restore + OK`) and stops the player BEFORE Branch 2's steep-ContactPlane CliffSlide can fire. Re-order: check the steep-ContactPlane condition FIRST, before the Branch 1 OnWalkable gate. If the surface is too steep AND we have a contact plane on it AND the EdgeSlide flag is set, run CliffSlide regardless of OnWalkable state. The cross-product deflection plus gravity produces continuous downhill drift, frame after frame. Branch 1's "stop at edge" still fires for the original case it was meant for: walked off into thin air with no contact plane at all. That should still stop (or fall normally) rather than CliffSlide against nothing. Tests: 1491 still pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.Core/Physics/TransitionTypes.cs | 29 +++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs index afc7607..ce4bb07 100644 --- a/src/AcDream.Core/Physics/TransitionTypes.cs +++ b/src/AcDream.Core/Physics/TransitionTypes.cs @@ -810,6 +810,35 @@ public sealed class Transition var ci = CollisionInfo; var oi = ObjectInfo; + // L.4-cliffslide-priority (2026-04-30): the steep-ContactPlane check + // moved BEFORE the OnWalkable/EdgeSlide gate. + // + // Why: by the time this dispatch runs on subsequent frames (player + // standing on a steep slope), ValidateTransition's L.2.3i FloorZ + // test has already CLEARED OnWalkable (steep slope → not a walkable + // surface). The original Branch 1 (`!OnWalkable → restore + OK`) + // therefore fires every frame, stopping the player dead — exactly + // the "stay on the roof" symptom the user reported. + // + // Re-ordering: if the surface is too steep AND we have a contact + // plane on it, run CliffSlide regardless of OnWalkable. The + // cross(currentNormal, lastKnownNormal) deflection plus gravity + // produces visible downhill drift each frame. + // + // Branch 1 (the !OnWalkable stop) still fires when we DON'T have + // a contact plane — the original "walked off into thin air" + // case, which should still stop or fall normally rather than + // CliffSlide on nothing. + if (ci.ContactPlaneValid && ci.ContactPlane.Normal.Z < zVal && oi.EdgeSlide) + { + var cliffPlane = ci.ContactPlane; + sp.ClearWalkable(); + sp.RestoreCheckPos(); + ci.ContactPlaneValid = false; + ci.ContactPlaneIsWater = false; + return CliffSlide(cliffPlane); + } + // Retail lets non-EdgeSlide movers continue over the boundary. Player // movement carries EdgeSlide, so the local avatar takes the slide path. if (!oi.OnWalkable || !oi.EdgeSlide)