diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index d5ee444..b2bd967 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -7034,11 +7034,22 @@ public sealed class GameWindow : IDisposable _playerController.StepDownHeight = (playerSetup is not null && playerSetup.StepDownHeight > 0f) ? playerSetup.StepDownHeight : 0.4f; + // L.2.3f (2026-04-29): diagnostic — confirm what the actual + // values from the player's Setup dat are. Retail's spec says ~0.4 m + // for humans, but we want to verify rather than guess. If the + // dat-derived value is large (e.g. 1.5 m+) it explains why the + // player can mount steep roofs via the step-up scan reach. + Console.WriteLine( + $"physics: player step heights — StepUp={_playerController.StepUpHeight:F3} m " + + $"(Setup.StepUpHeight={(playerSetup?.StepUpHeight ?? 0f):F3}), " + + $"StepDown={_playerController.StepDownHeight:F3} m " + + $"(Setup.StepDownHeight={(playerSetup?.StepDownHeight ?? 0f):F3})"); } else { _playerController.StepUpHeight = 0.4f; _playerController.StepDownHeight = 0.4f; + Console.WriteLine($"physics: player step heights — defaulting to 0.4 m (no setup dat)"); } int plbX = _liveCenterX + (int)MathF.Floor(playerEntity.Position.X / 192f); int plbY = _liveCenterY + (int)MathF.Floor(playerEntity.Position.Y / 192f); diff --git a/src/AcDream.Core/Physics/BSPQuery.cs b/src/AcDream.Core/Physics/BSPQuery.cs index eff0222..a0947d0 100644 --- a/src/AcDream.Core/Physics/BSPQuery.cs +++ b/src/AcDream.Core/Physics/BSPQuery.cs @@ -1106,7 +1106,7 @@ public static class BSPQuery if (transition.DoStepUp(collisionNormal, engine!)) return TransitionState.OK; - return transition.SpherePath.StepUpSlide(transition.CollisionInfo); + return transition.SpherePath.StepUpSlide(transition); } // ------------------------------------------------------------------------- diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs index bd34857..2b5834c 100644 --- a/src/AcDream.Core/Physics/TransitionTypes.cs +++ b/src/AcDream.Core/Physics/TransitionTypes.cs @@ -248,15 +248,29 @@ public sealed class SpherePath /// /// Slide fallback when step-up fails. Clears the contact-plane state that - /// caused the step-up attempt and issues a slide along StepUpNormal. - /// ACE: SpherePath.StepUpSlide (ACE SpherePath.cs:309-317). + /// caused the step-up attempt and runs the full sphere-slide computation + /// to actually move the sphere along the wall. + /// + /// + /// L.2.3d (2026-04-29): the previous version only set + /// as a flag; it never applied a slide offset. The user observed "running + /// close to the wall now I stop" — the sphere stayed pinned at the wall + /// and the slide normal got overwritten by ValidateTransition's + /// default-to-UnitZ branch. ACE actually computes the slide offset and + /// applies it to via Sphere.SlideSphere; + /// we delegate to which does + /// the same thing. + /// + /// + /// ACE: SpherePath.StepUpSlide + Sphere.SlideSphere + /// (SpherePath.cs:309-317, Sphere.cs:558-604). /// - public TransitionState StepUpSlide(CollisionInfo collisions) + public TransitionState StepUpSlide(Transition transition) { - collisions.ContactPlaneValid = false; - collisions.ContactPlaneIsWater = false; - collisions.SetSlidingNormal(StepUpNormal); - return TransitionState.Slid; + var ci = transition.CollisionInfo; + ci.ContactPlaneValid = false; + ci.ContactPlaneIsWater = false; + return transition.SlideSphereInternal(StepUpNormal, GlobalCurrCenter[0].Origin); } /// @@ -635,9 +649,20 @@ public sealed class Transition } } - // Step-down failed: stay at current position. + // L.2.3e (2026-04-29): step-down failed — the move would put + // the player off an edge with no walkable surface within reach. + // Retail's EdgeSlide (ACE Transition.cs:268-320) maps this to + // SetEdgeSlide(true, true, OK) which restores CheckPos to the + // saved (post-step) position — but our outer ValidateTransition + // accepts CheckPos as the new CurPos, defeating the intent. + // + // Returning Collided here makes ValidateTransition revert to + // CurPos (pre-step) — the always-on retail "stop at edge" + // behavior. Confirmed against ACE Transition.cs:317 where + // EdgeSlide returns Collided when no walkable surface is + // found and the EdgeSlide flag is unset (player default). sp.RestoreCheckPos(); - return TransitionState.OK; + return TransitionState.Collided; } return TransitionState.OK; @@ -1082,6 +1107,14 @@ public sealed class Transition /// normal variant). ACE: Sphere.SlideSphere(Transition, ref Vector3, Vector3). /// Decompiled: FUN_00538180. /// + /// + /// L.2.3d: exposed as internal so + /// can apply the same slide computation ACE's Sphere.SlideSphere uses + /// for failed step-up. Mirror of ACE Sphere.cs:558-604 (Plane variant). + /// + internal TransitionState SlideSphereInternal(Vector3 collisionNormal, Vector3 currPos) + => SlideSphere(collisionNormal, currPos); + private TransitionState SlideSphere(Vector3 collisionNormal, Vector3 currPos) { var sp = SpherePath; @@ -1356,6 +1389,23 @@ public sealed class Transition var ci = CollisionInfo; var oi = ObjectInfo; + // L.2.3f (2026-04-29): diagnostic for steep-roof bug. Log the + // collision normal Z so we can confirm whether the polygon being + // stepped up onto is "walkable" (normal.Z >= FloorZ ≈ 0.66) or + // not (≈ 0.087 LandingZ when not OnWalkable). If we see steep + // normals being accepted, the issue is in the find_walkable + // threshold rather than the StepUpHeight reach. + if (Environment.GetEnvironmentVariable("ACDREAM_DUMP_STEPUP") == "1") + { + float floor = PhysicsGlobals.FloorZ; + string verdict = collisionNormal.Z >= floor ? "WALKABLE" : "STEEP"; + Console.WriteLine( + $"stepup: normal=({collisionNormal.X:F3},{collisionNormal.Y:F3},{collisionNormal.Z:F3}) " + + $"|Z|={collisionNormal.Z:F3} vs FloorZ={floor:F3} → {verdict}, " + + $"OnWalkable={oi.State.HasFlag(ObjectInfoState.OnWalkable)}, " + + $"StepUpHeight={oi.StepUpHeight:F3}"); + } + // L.2.3c (2026-04-29): capture the existing contact plane BEFORE // clearing it. On step-up failure (too-tall wall) we restore it so // the mover stays grounded — without this, walking into a wall