Closes the M1.5 "doors don't block in production" bug (alongside the
foundation fix at 3b7dc46). Server-spawned entities (doors, NPCs,
chests, items) now register one ShadowEntry per collision shape —
matching retail's CPhysicsObj-with-CPartArray model
(acclient_2013_pseudo_c.txt:286236) — instead of one Cylinder
approximation per entity.
Before:
RegisterLiveEntityCollision picked ONE shape via a CylSphere → Radius
→ Sphere cascade, registered as a single Cylinder. Doors got a
14 cm × 20 cm cylinder from setup.Radius — far too narrow to span
the doorway gap. Players could walk through closed doors.
After:
- ShadowShapeBuilder.FromSetup emits N shapes:
• one Cylinder per CylSphere
• one Cylinder per Sphere (only when no CylSpheres — retail
convention)
• one BSP shape per Part with a non-null PhysicsBSP
- Caller substitutes the real BoundingSphere.Radius from
PhysicsDataCache for BSP shapes (pure builder's 2.0 placeholder
is tightened to the actual cached value).
- setup.Radius fallback preserved: if the builder produces zero
shapes but Radius > 0, register a Radius-based Cylinder so simple
decorative props don't silently lose collision.
- ShadowObjects.RegisterMultiPart adds N rows, all sharing
entity.Id so the existing UpdatePhysicsState (ETHEREAL flip on
door Use) propagates to every part without changes.
Door 0x020019FF (Holtburg cottage) now registers:
- Cylinder r=0.10 h=0.20 (from the single Sphere)
- BSP from Part 0 = GfxObj 0x010044B5, the 6-face 1.925 m × 0.261 m
× 2.490 m two-sided slab confirmed by
DoorSetupGfxObjInspectionTests
Parts 1 + 2 (GfxObj 0x010044B6, the visual leaves) are visual-only
in the dat by retail design and correctly skipped.
Test impact: 53/53 pass in the shape / registry / door /
cellar-replay scope. App-layer 41/41 pass.
Visual verification needed: launch the client, walk into a closed
Holtburg cottage door from outside (dead center AND ~50 cm
off-center), then walk into it from inside. Door should block all
three approaches. Use the door (click + Use) → door swings open →
walking through passes (ETHEREAL flip via existing SetState path).
Foundation fix dependency:
3b7dc46 fix(phys): GetNearbyObjects dedup-by-entityId silently
drops multi-part shadows
Without 3b7dc46 in place, the BSP shape registered here would be
dropped by GetNearbyObjects's dedup. They land together.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>