using System.Collections.Generic; using AcDream.Core.Physics; using DatReaderWriter.Enums; using DatReaderWriter.Types; using Xunit; namespace AcDream.Core.Tests.Physics; /// /// Issue #101 (2026-05-25) — phantom-stair fix. Retail's /// CPartArray::InitParts emits collision shapes only from /// Setup-level CylSpheres/Spheres or per-Part /// PhysicsBSP. There is NO synthesis from visual mesh AABB. /// Acdream's mesh-aabb-fallback path at /// GameWindow.cs:6127 previously fired for ANY entity that /// reached it with entityBsp==0 && entityCyl==0, /// including GfxObj-only stabs whose GfxObj has /// HasPhysics=False. This produced the 10 phantom 0.80 m /// cylinders that block the Holtburg upper-floor staircase (see /// docs/research/2026-05-25-a6-stairs-cyl-retail-investigation.md). /// /// /// captures the /// retail rule as a predicate: "the entity's source is a GfxObj /// (high byte 0x01) AND the cache has no /// entry for it." When this returns true, the caller suppresses the /// fallback synthesis. /// /// public class PhysicsDataCachePhantomSourceTests { [Fact] public void IsPhantomGfxObjSource_SetupHighByte_ReturnsFalse() { // Setup source (high byte 0x02) is never the GfxObj-phantom case. // The existing isPhantomSetup check at GameWindow.cs:6090 handles // the Setup-side phantom. This predicate is scoped to GfxObj only. var cache = new PhysicsDataCache(); Assert.False(cache.IsPhantomGfxObjSource(0x020019FFu)); // door setup Assert.False(cache.IsPhantomGfxObjSource(0x02000266u)); // some setup } [Fact] public void IsPhantomGfxObjSource_GfxObjUncached_ReturnsTrue() { // GfxObj source (high byte 0x01) with NO cached GfxObjPhysics // = the phantom case. The stair-step GfxObj 0x0100081A from // issue #101's broken-stairs capture has HasPhysics=False and // does not enter the cache. Acdream should treat it as phantom. var cache = new PhysicsDataCache(); Assert.True(cache.IsPhantomGfxObjSource(0x0100081Au)); } [Fact] public void IsPhantomGfxObjSource_GfxObjCached_ReturnsFalse() { // GfxObj source (high byte 0x01) WITH a cached GfxObjPhysics // (i.e. the GfxObj's HasPhysics flag was set and its PhysicsBSP // root is non-null) is NOT phantom. The staircase BSP entity // 0x40B50089 from issue #101's capture, backed by GfxObj // 0x01000C16 with hasPhys=True, is this case. var cache = new PhysicsDataCache(); var leaf = new PhysicsBSPNode { Type = BSPNodeType.Leaf }; var fakePhysics = new GfxObjPhysics { BSP = new PhysicsBSPTree { Root = leaf }, PhysicsPolygons = new Dictionary(), Vertices = new VertexArray(), Resolved = new Dictionary(), }; cache.RegisterGfxObjForTest(0x01000C16u, fakePhysics); Assert.False(cache.IsPhantomGfxObjSource(0x01000C16u)); } }