From 5240d654dfd2f4c18c3b201b41f3f3a7e8991afb Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 25 May 2026 19:51:45 +0200 Subject: [PATCH] =?UTF-8?q?fix(physics):=20#101=20=E2=80=94=20suppress=20m?= =?UTF-8?q?esh-aabb-fallback=20for=20phantom=20GfxObj=20stabs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 10 stair-step cyls (entities 0x40B5008A..0x40B50095 in Holtburg cells 0xA9B40159/A) are synthesized by the mesh-aabb-fallback path from the visual mesh AABB of GfxObj 0x0100081A — which has HasPhysics=False and no PhysicsBSP. Retail's CPartArray::InitParts emits no collision in this case; acdream now matches that by consulting PhysicsDataCache.IsPhantomGfxObjSource (added in the previous commit) and skipping synthesis when the predicate fires. The actual staircase collision is on entity 0x40B50089 (GfxObj 0x01000C16, hasPhys=True, BSP radius 2.645m) — same staircase BSP that retail uses. After this fix, only that BSP fires; the phantoms are gone. Visual verification pending (next step in plan); the BSP dump from ACDREAM_DUMP_GFXOBJS=0x01000C16 will confirm whether 0x01000C16 has walkable inclined polys for the climb to actually land. If not, a follow-up issue is needed; the cyl phantom is closed either way. Also updates PhysicsDataCache.cs XML doc line reference from 6116 to 6127 (drifted by the 11-line isPhantomGfxObj block inserted above the guarded if). Refs docs/research/2026-05-25-a6-stairs-cyl-retail-investigation.md. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.App/Rendering/GameWindow.cs | 12 ++++++++++++ src/AcDream.Core/Physics/PhysicsDataCache.cs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 6227452..e148d74 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -6100,6 +6100,17 @@ public sealed class GameWindow : IDisposable } } + // Issue #101 (2026-05-25): retail-faithful phantom check for + // GfxObj-only entity sources. Retail's CPartArray::InitParts + // emits NO collision shapes when the source GfxObj has + // HasPhysics=False / null PhysicsBSP.Root. Our mesh-aabb-fallback + // below previously synthesized a clamped [0.30, 1.50]m cylinder + // from the visual mesh AABB — that's the source of the 10 + // phantom 0.80m cyls that block the Holtburg upper-floor + // staircase (issue #101). Suppress synthesis for these. + // Spec: docs/research/2026-05-25-a6-stairs-cyl-retail-investigation.md. + bool isPhantomGfxObj = _physicsDataCache.IsPhantomGfxObjSource(entity.SourceGfxObjOrSetupId); + // VISUAL mesh-bounds collision: for SCENERY entities (IDs with // 0x80000000 bit set, indicating procedurally-placed scenery), // ALWAYS compute a cylinder from the world-space mesh AABB. @@ -6114,6 +6125,7 @@ public sealed class GameWindow : IDisposable // L-fix3: skip entirely when the Setup is phantom — retail // decorative meshes have no collision data on purpose. if (!isPhantomSetup + && !isPhantomGfxObj && !_isLandblockStab && (_isOutdoorMesh || (entityBsp == 0 && entityCyl == 0)) && entity.MeshRefs.Count > 0) diff --git a/src/AcDream.Core/Physics/PhysicsDataCache.cs b/src/AcDream.Core/Physics/PhysicsDataCache.cs index fbe8a2d..c7b65a5 100644 --- a/src/AcDream.Core/Physics/PhysicsDataCache.cs +++ b/src/AcDream.Core/Physics/PhysicsDataCache.cs @@ -370,7 +370,7 @@ public sealed class PhysicsDataCache /// short-circuited at the early-return on line 45/46. Retail's /// CPartArray::InitParts emits NO collision shapes for /// these — acdream's mesh-aabb-fallback synthesis at - /// GameWindow.cs:6116 must do the same. + /// GameWindow.cs:6127 must do the same. /// public bool IsPhantomGfxObjSource(uint sourceId) {