From aa7c0b5c3177978453b7c56d3065a0821df2e622 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 25 Jun 2026 12:20:54 +0200 Subject: [PATCH] =?UTF-8?q?fix(physics):=20#150=20=E2=80=94=20skip=20ether?= =?UTF-8?q?eal=20targets=20in=20the=20step-down=20pass=20(open=20doors=20f?= =?UTF-8?q?ully=20passable)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An OPEN door (ETHEREAL_PS 0x4 set on Use) still stopped the player with a residual threshold block after the collision sweep, despite swinging open visually. The resolver runs two collision passes per step: the main sweep and a step-down (foot-sphere "is there floor?") sub-pass. acdream tested the ethereal door in BOTH; the main pass cleared it (BSPQuery Path 1 + the Layer-2 override) but the step-down pass had no escape, leaving a Collided result at the sill -- the "can-sized cylinder on the ground threshold". Retail's CPhysicsObj::FindObjCollisions (pc:276795-276806) SKIPS an ethereal target when sphere_path.step_down != 0 -- it only tests it in the main pass. So an open door is fully passable everywhere; the swung panel's position is irrelevant (ethereal = no collision; the swing animation is purely visual). Port that branch verbatim: an ethereal-for-this-test target (target state & 0x4, OR mover-ethereal vs a non-static target) is `continue`d when sp.StepDown is set. Live-verified (user confirmed): the door now blocks 0x while open (0x1000C), still blocks while closed (0x10008); pre-fix it blocked 217x while open. Core suite green (1595/0). (An earlier "animate the door collision" theory was wrong and dropped -- if collision tracked the swung panel you'd bump the panel in its open position, which retail does not do. The user caught it.) Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/ISSUES.md | 21 +++++++++++++ src/AcDream.Core/Physics/TransitionTypes.cs | 35 +++++++++++++++------ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/docs/ISSUES.md b/docs/ISSUES.md index e5fe597b..83fa0294 100644 --- a/docs/ISSUES.md +++ b/docs/ISSUES.md @@ -5482,6 +5482,27 @@ outdoors at the angle that previously erased it. # Recently closed +## #150 — Open doors still blocked at the threshold (ethereal target not skipped in the step-down pass) + +**Status:** DONE (2026-06-25) — after the retail collision sweep, an OPEN door +(ETHEREAL_PS 0x4 set on Use) still stopped the player with a small residual block at +the sill ("can-sized cylinder on the ground threshold"), even though the door swings +open visually. Root: the resolver runs two collision passes per step — the main +sweep and a step-down (foot-sphere "is there floor?") sub-pass. acdream tested the +ethereal door in BOTH; the main pass cleared it (Layer-1 Path-1 + the Layer-2 +override) but the **step-down pass had no escape** and its `Collided` result survived +as the threshold block. Retail's `CPhysicsObj::FindObjCollisions` (pc:276795-276806) +SKIPS an ethereal target entirely when `sphere_path.step_down != 0` — it only tests +it in the main pass — so an open door is fully passable everywhere (the swung panel's +position is irrelevant; ethereal = no collision, the animation is purely visual). +Fix: port that exact branch — `if ((state & 0x4) || (mover.Ethereal && (state & 0x1) +== 0)) && sp.StepDown → continue`. `TransitionTypes.cs` per-object loop. Live-verified +(user "Yes it works"; the door now blocks 0× while open [0x1000C], still blocks while +closed [0x10008]; pre-fix it blocked 217× while open). Core suite green (1595/0). The +earlier "animate the door collision" theory was WRONG and dropped — the user correctly +noted that if collision followed the swung panel you'd bump the panel in its open +position, which retail does not do. Likely also advances the door half of #137. + ## #149 — BSP-less landblock statics (torches/braziers/lamp-posts) were walk-through **Status:** DONE (2026-06-25, `4cf6eeb`) — town props placed as landblock stabs diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs index 7b0aeafb..a633efd4 100644 --- a/src/AcDream.Core/Physics/TransitionTypes.cs +++ b/src/AcDream.Core/Physics/TransitionTypes.cs @@ -2599,15 +2599,32 @@ public sealed class Transition if (CollisionExemption.ShouldSkip(obj.State, obj.Flags, ObjectInfo.State)) continue; - // Task 3 (2026-06-24): set obstruction_ethereal on SpherePath before - // the shape dispatch. Retail: CPhysicsObj::FindObjCollisions pc:276806 / - // 0x0050f0c9 — `ebx->sphere_path.obstruction_ethereal = var_c` where - // var_c=1 when the target has ETHEREAL_PS (0x4). The flag is consumed by - // BSPQuery Path 1 (BSPTREE::find_collisions pc:323742) to route through - // sphere_intersects_solid instead of the blocking paths, making the player - // passable through the open door's BSP (no solid leaf at the opening). - // Cleared after each per-object test below (retail pc:276989 / 0x0050f31e). - sp.ObstructionEthereal = (obj.State & 0x4u) != 0; + // Retail CPhysicsObj::FindObjCollisions ethereal branch + // (pc:276795-276806 / 0x0050f0a2-0x0050f0c9). A target counts as + // "ethereal for this test" when it has ETHEREAL_PS (0x4) OR the mover + // is ethereal against a NON-static target ((state & 1) == 0): + // + // if ((state & 4) || (mover.ethereal && (state & 1) == 0)) { + // var_c = 1; + // if (sphere_path.step_down == 0) goto do_test; // main pass + // // step_down != 0 → fall through = SKIP this target entirely + // } else { var_c = 0; do_test: obstruction_ethereal = var_c; ...test... } + // + // The step-down SKIP (pc:276799 — no `goto` when step_down != 0) is what + // makes an OPEN DOOR fully passable: the main pass tests it with + // obstruction_ethereal=1 (BSPQuery Path 1 + the Layer-2 override below + // force it to OK), and the foot-sphere step-down sub-pass NEVER TESTS IT + // at all. acdream previously tested ethereal targets in BOTH passes with + // no step-down escape, so the step-down pass left a residual threshold + // block ("can" at the open door's sill) that retail does not have + // (#137 / live capture 2026-06-25: 217 blocks all while door state=0x1000C). + // obstruction_ethereal (= retail var_c) is consumed by BSPQuery Path 1 + // (BSPTREE::find_collisions pc:323742); cleared after each test (pc:276989). + bool etherealForTest = (obj.State & 0x4u) != 0 + || (ObjectInfo.Ethereal && (obj.State & 0x1u) == 0); + if (etherealForTest && sp.StepDown) + continue; // retail pc:276799 — ethereal target not tested in step-down + sp.ObstructionEthereal = etherealForTest; // L.2a slice 3 (2026-05-12): snapshot collision-normal state so // we can tell whether THIS object's BSP/CylSphere test produced a