fix(physics): Slice 1 — delete render-mesh-AABB synthetic collision; DAT-only shape authority

Retail oracle: CPartArray::InitParts@0x00517F40, CGfxObj::Serialize@0x00534970 (physics_bsp
gated on serialized-flags bit-0), CPhysicsPart::find_obj_collisions@0x0050D8D0 (returns OK /
passable when physics_bsp==null). Render-mesh bounds never enter collision.

Changes:
- GameWindow.cs: delete the ~200-line VISUAL mesh-bounds collision block (the
  isPhantomSetup / isPhantomGfxObj locals + the if-block computing worldMin/worldMax
  AABB + the ShadowObjects.Register call that capped and registered the synthetic
  cylinder). Also removes dead counter variables scHaveBounds/scRegistered/scNoBounds/
  scTooThin; trims the ProbeBuildingEnabled summary line accordingly.
- PhysicsDataCache.cs: delete IsPhantomGfxObjSource (the predicate that only existed
  to fence the mesh-AABB synthesis; the "phantom" concept is now the default — no DAT
  shape means no registration, verbatim with retail).
- PhysicsDataCachePhantomSourceTests.cs: deleted (tested the removed method).
- ShadowShapeBuilderShapeSourceTests.cs: new guard test — a Setup with parts but
  hasPhysicsBsp=false and no CylSpheres/Spheres yields an empty shape list, locking
  the DAT-only rule in the builder.
- retail-divergence-register.md: AP-2 row deleted (divergence retired).

Objects with no DAT physics shape (no CylSpheres, no Spheres, no part with a
PhysicsBSP) now register no collision shape and are passable, verbatim with retail.
Objects with real DAT shapes (BSP parts, CylSpheres) are unaffected.

dotnet build green, 22/22 tests passing (ShadowShapeBuilderShapeSourceTests +
CellarUpTrajectoryReplay + CornerFlood + Issue147ArwicBuildings replay harnesses).

Visual gate pending: walk Holtburg + open world; objects that become passable must
match retail (DAT has no physics shape — trees with real CylSpheres still solid).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-24 18:57:46 +02:00
parent 4f26067755
commit 79dee342f2
5 changed files with 44 additions and 297 deletions

View file

@ -384,24 +384,6 @@ public sealed class PhysicsDataCache
public GfxObjPhysics? GetGfxObj(uint id) => _gfxObj.TryGetValue(id, out var p) ? p : null;
/// <summary>
/// Issue #101 (2026-05-25): retail-faithful phantom check for
/// GfxObj-only entity sources. Returns true when the entity's
/// <c>SourceGfxObjOrSetupId</c> is a GfxObj (high byte
/// <c>0x01</c>) AND has no cached <see cref="GfxObjPhysics"/> —
/// meaning the underlying GfxObj had <c>HasPhysics=False</c> or
/// a null <c>PhysicsBSP.Root</c>, so <see cref="CacheGfxObj"/>
/// 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:6127</c> must do the same.
/// </summary>
public bool IsPhantomGfxObjSource(uint sourceId)
{
if ((sourceId & 0xFF000000u) != 0x01000000u) return false;
return GetGfxObj(sourceId)?.BSP?.Root is null;
}
public SetupPhysics? GetSetup(uint id) => _setup.TryGetValue(id, out var p) ? p : null;
public CellPhysics? GetCellStruct(uint id) => _cellStruct.TryGetValue(id, out var p) ? p : null;
public int GfxObjCount => _gfxObj.Count;