fix(physics): L.4-cliffslide-priority — steep ContactPlane check before OnWalkable gate
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) <noreply@anthropic.com>
This commit is contained in:
parent
52e257d8d7
commit
a48883af2d
1 changed files with 29 additions and 0 deletions
|
|
@ -810,6 +810,35 @@ public sealed class Transition
|
||||||
var ci = CollisionInfo;
|
var ci = CollisionInfo;
|
||||||
var oi = ObjectInfo;
|
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
|
// Retail lets non-EdgeSlide movers continue over the boundary. Player
|
||||||
// movement carries EdgeSlide, so the local avatar takes the slide path.
|
// movement carries EdgeSlide, so the local avatar takes the slide path.
|
||||||
if (!oi.OnWalkable || !oi.EdgeSlide)
|
if (!oi.OnWalkable || !oi.EdgeSlide)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue