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 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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue