diff --git a/src/AcDream.App/Input/PlayerMovementController.cs b/src/AcDream.App/Input/PlayerMovementController.cs
index 8084231..2a832d0 100644
--- a/src/AcDream.App/Input/PlayerMovementController.cs
+++ b/src/AcDream.App/Input/PlayerMovementController.cs
@@ -97,11 +97,25 @@ public sealed class PlayerMovementController
///
/// Maximum Z increase per movement step before the move is rejected.
- /// AC's default StepUpHeight for human characters is ~2 units.
- /// Using 5 for the MVP to be forgiving — prevents walking up vertical
- /// walls but allows stairs, ramps, and terrain slopes.
+ /// Retail's step_up_height for human characters is ~0.4 m (hip-
+ /// level). Setting this too high lets the player teleport up small
+ /// buildings via the step-up scan finding any walkable polygon within
+ /// reach (Bug 3 in L.2.3 testing — walking into a steep slope mounted
+ /// the building's flat top instead of sliding off the slope).
+ /// Authoritative source is the player's Setup.StepUpHeight set
+ /// in GameWindow.cs at world-entry time.
///
- public float StepUpHeight { get; set; } = 5.0f;
+ public float StepUpHeight { get; set; } = 0.4f;
+
+ ///
+ /// L.2.3a (2026-04-29): how far below the foot the step-down probe
+ /// reaches when transitioning between surfaces. Retail's
+ /// step_down_height for human characters is ~0.4 m. With the
+ /// previous 4 cm hardcoded value, walking off the top of a stair onto
+ /// the ground 25 cm below produced a one-frame contact-plane gap — the
+ /// animation system briefly flickered to falling.
+ ///
+ public float StepDownHeight { get; set; } = 0.4f;
///
/// Current portal-space state. Set to PortalSpace when the server sends
@@ -411,7 +425,7 @@ public sealed class PlayerMovementController
sphereRadius: 0.48f, // human player radius from Setup
sphereHeight: 1.2f, // human player height from Setup
stepUpHeight: StepUpHeight,
- stepDownHeight: 0.04f, // retail default
+ stepDownHeight: StepDownHeight, // L.2.3a: from Setup.StepDownHeight
isOnGround: _body.OnWalkable,
body: _body, // persist ContactPlane across frames for slope tracking
// Commit C 2026-04-29 — local player is always IsPlayer.
diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs
index ba3c978..d5ee444 100644
--- a/src/AcDream.App/Rendering/GameWindow.cs
+++ b/src/AcDream.App/Rendering/GameWindow.cs
@@ -5755,8 +5755,8 @@ public sealed class GameWindow : IDisposable
preIntegratePos, postIntegratePos, rm.CellId,
sphereRadius: 0.48f,
sphereHeight: 1.2f,
- stepUpHeight: 2.0f, // retail default for unknown remotes
- stepDownHeight: 0.04f, // PhysicsGlobals.DefaultStepHeight
+ stepUpHeight: 0.4f, // L.2.3a: retail human-scale, was 2.0f
+ stepDownHeight: 0.4f, // L.2.3a: retail human-scale, was 0.04f
// K-fix9 (2026-04-26): mirror the K-fix7 gate —
// airborne remotes must NOT pre-seed the
// ContactPlane, otherwise AdjustOffset's snap-to-plane
@@ -7016,7 +7016,13 @@ public sealed class GameWindow : IDisposable
_playerController.SetCharacterSkills(_lastSeenRunSkill, _lastSeenJumpSkill);
Console.WriteLine($"live: {loggingTag} — applied server skills run={_lastSeenRunSkill} jump={_lastSeenJumpSkill}");
}
- // Read the real step height from the player's Setup dat.
+ // Read the real step heights from the player's Setup dat.
+ // L.2.3a (2026-04-29): retail's Setup.StepUpHeight for humans is
+ // ~0.4 m, NOT 2 m. With 2 m fallback the step-up scan reached
+ // small-building roofs and teleported the player onto them. Same
+ // for StepDownHeight — was hardcoded 0.04 m, causing stair-top
+ // contact-plane gaps. Both now come from Setup with retail-realistic
+ // 0.4 m fallbacks.
if (_dats is not null && (playerEntity.SourceGfxObjOrSetupId & 0xFF000000u) == 0x02000000u)
{
var playerSetup = _dats.Get(playerEntity.SourceGfxObjOrSetupId);
@@ -7024,11 +7030,15 @@ public sealed class GameWindow : IDisposable
_physicsDataCache.CacheSetup(playerEntity.SourceGfxObjOrSetupId, playerSetup);
_playerController.StepUpHeight = (playerSetup is not null && playerSetup.StepUpHeight > 0f)
? playerSetup.StepUpHeight
- : 2f;
+ : 0.4f;
+ _playerController.StepDownHeight = (playerSetup is not null && playerSetup.StepDownHeight > 0f)
+ ? playerSetup.StepDownHeight
+ : 0.4f;
}
else
{
- _playerController.StepUpHeight = 2f;
+ _playerController.StepUpHeight = 0.4f;
+ _playerController.StepDownHeight = 0.4f;
}
int plbX = _liveCenterX + (int)MathF.Floor(playerEntity.Position.X / 192f);
int plbY = _liveCenterY + (int)MathF.Floor(playerEntity.Position.Y / 192f);