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