fix(physics): #101 — suppress mesh-aabb-fallback for phantom GfxObj stabs

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) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-25 19:51:45 +02:00
parent f6305b1e3c
commit 5240d654df
2 changed files with 13 additions and 1 deletions

View file

@ -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)

View file

@ -370,7 +370,7 @@ public sealed class PhysicsDataCache
/// short-circuited at the early-return on line 45/46. Retail's
/// <c>CPartArray::InitParts</c> emits NO collision shapes for
/// these — acdream's <c>mesh-aabb-fallback</c> synthesis at
/// <c>GameWindow.cs:6116</c> must do the same.
/// <c>GameWindow.cs:6127</c> must do the same.
/// </summary>
public bool IsPhantomGfxObjSource(uint sourceId)
{