From e44d24cec687d820d66d5a6c1e4afa31d30f7185 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 29 Apr 2026 19:28:30 +0200 Subject: [PATCH] =?UTF-8?q?fix(physics):=20L.2.3i=20=E2=80=94=20use=20Floo?= =?UTF-8?q?rZ=20(not=20LandingZ)=20for=20OnWalkable=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two parallel research agents converged on this bug. acdream's ValidateTransition was setting OnWalkable based on `Normal.Z >= LandingZ` (0.087, ~85° permissive) instead of `Normal.Z >= FloorZ` (0.664, ~49° strict). Effect: a 60° roof slope (normal.Z = 0.5) was being marked OnWalkable, letting the player walk freely up surfaces retail blocks. Per retail PhysicsObj::is_valid_walkable (acclient_2013_pseudo_c.txt:277180-277193) and ACE PhysicsObj.cs:2861, the canonical "walkable" predicate is FloorZ. LandingZ is the more permissive threshold used only in airborne→ground transitions (Path 6 Collide handler) where we want to accept a brief landing before the next frame's strict FloorZ check rejects the surface and CliffSlide kicks in. Three sites fixed: 1. Step-down branch's `zVal` initial value (was unconditional LandingZ; now `oi.GetWalkableZ()` returns FloorZ when OnWalkable, LandingZ otherwise — matches retail's transitional_insert step-down OK branch at acclient_2013_pseudo_c.txt:273258-273265). 2. ValidateTransition's live-contact OnWalkable test (LandingZ → FloorZ). 3. ValidateTransition's LastKnown-fallback OnWalkable test (LandingZ → FloorZ). After this commit: - Walking horizontally INTO a 60° slope: step-up's WalkableAllowance is FloorZ (when OnWalkable), find_walkable rejects the slope's polygon, step-up fails, StepUpSlide. Player blocked from climbing. - Jumping ONTO a 60° roof: Path 6 still uses LandingZ (correct, we want to land), so the player lands. Next frame: ValidateTransition sees Normal.Z=0.5 < FloorZ → OnWalkable cleared. Player is Contact but not OnWalkable. Currently this leaves them STUCK on the roof (no CliffSlide yet to push them off). That's still better than walking up the roof. Full slide-off-roof + edge-slide-along-balcony behaviors require porting CliffSlide + PrecipiceSlide + adding Walkable polygon reference — that's Phase L.4 (~12-20h, sketched out by both research agents). This commit unblocks the worst of the steep-walk-up behavior while the bigger port is being designed. Test count 825/825 still pass. Build clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.Core/Physics/TransitionTypes.cs | 22 ++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs index cc6944a..6494789 100644 --- a/src/AcDream.Core/Physics/TransitionTypes.cs +++ b/src/AcDream.Core/Physics/TransitionTypes.cs @@ -623,7 +623,15 @@ public sealed class Transition if (!ci.ContactPlaneValid && oi.Contact && !sp.StepDown && sp.CheckCellId != 0 && oi.StepDown) { - float zVal = PhysicsGlobals.LandingZ; + // L.2.3i (2026-04-29): retail uses FloorZ when OnWalkable, + // LandingZ when not. acdream was unconditionally LandingZ — + // which let the step-down probe accept steep polygons + // (~85° permissive instead of ~49° strict) as the player's + // new contact, contributing to the "walks up steep roofs" + // bug. Per CTransition::transitional_insert step-down OK + // branch (acclient_2013_pseudo_c.txt:273258-273265) and + // ACE Transition.cs:849-856. + float zVal = oi.GetWalkableZ(); float stepDownHeight = oi.StepDownHeight; sp.WalkableAllowance = zVal; sp.SaveCheckPos(); @@ -1638,7 +1646,14 @@ public sealed class Transition ci.LastKnownContactPlaneIsWater = ci.ContactPlaneIsWater; oi.State |= ObjectInfoState.Contact; - if (ci.ContactPlane.Normal.Z >= PhysicsGlobals.LandingZ) + // L.2.3i (2026-04-29): use FloorZ (~49°) NOT LandingZ (~85°) + // for the OnWalkable test. The previous LandingZ check was + // far too permissive — a 60° roof (normal.Z=0.5) was being + // marked OnWalkable, letting the player walk up steep slopes + // they shouldn't reach. Retail's PhysicsObj::is_valid_walkable + // uses FloorZ unconditionally (acclient_2013_pseudo_c.txt:277180-277193, + // ACE PhysicsObj.cs:2861). + if (ci.ContactPlane.Normal.Z >= PhysicsGlobals.FloorZ) oi.State |= ObjectInfoState.OnWalkable; else oi.State &= ~ObjectInfoState.OnWalkable; @@ -1651,7 +1666,8 @@ public sealed class Transition // last-known plane. Without this, every wall bump dropped the // player into the falling animation for one frame. oi.State |= ObjectInfoState.Contact; - if (ci.LastKnownContactPlane.Normal.Z >= PhysicsGlobals.LandingZ) + // L.2.3i: same FloorZ correction as the live-contact branch. + if (ci.LastKnownContactPlane.Normal.Z >= PhysicsGlobals.FloorZ) oi.State |= ObjectInfoState.OnWalkable; else oi.State &= ~ObjectInfoState.OnWalkable;