fix(phys): GetNearbyObjects dedup-by-entityId silently drops multi-part shadows
Apparatus test (DoorCollisionApparatusTests) loads door GfxObj 0x010044B5 from the real dat, builds the door entity's shape list via ShadowShapeBuilder, registers via RegisterMultiPart, and sweeps a player sphere into the door from three angles. Pre-fix: all three assertions fail — the sphere walks straight through. The [cyl-test] probe fires every tick (the small Sphere shape is queried) but no [resolve-bldg] — the per-Part BSP entry is never reached. Root cause: ShadowObjectRegistry.GetNearbyObjects deduplicates on entry.EntityId via HashSet<uint>. Pre-RegisterMultiPart each entity had exactly one shadow row, so dedup-by-entityId correctly suppressed multi-cell duplication. After Task 4's RegisterMultiPart introduced multi-shape rows (1 Sphere + 1 per-Part-BSP for doors; potentially more for creatures + items), the dedup silently drops everything after the first. ShadowShapeBuilder emits Sphere shapes before Part-BSPs, so the Sphere wins and the BSP is dropped — exactly the "Task 7 produced zero [resolve-bldg] hits" finding from the 2026-05-24 evening handoff. Fix: dedup on the full ShadowEntry. record-struct equality compares all fields (EntityId, GfxObjId, Position, Rotation, Radius, CollisionType, CylHeight, Scale, State, Flags, LocalPosition, LocalRotation). Distinct shapes of the same entity are not equal and make it through; the same shape registered in multiple cells (its fields identical across calls) dedups exactly as before. Apparatus verification post-fix: all 4 tests pass. - Dead-center front approach: BLOCKED at Y=11.5 normal=(0,-1,0). - 50 cm off-center: BLOCKED at Y=11.5 normal=(0,-1,0). - Back approach from inside: BLOCKED at Y=12.8 normal=(0,+1,0). - Diagnostic dump: BSP fires at tick 5. What this fix DOES NOT do: switch live RegisterLiveEntityCollision to use ShadowShapeBuilder + RegisterMultiPart. That's Task 7 of the original plan, still reverted. With this foundation fix in place, Task 7 should now actually deliver door blocking in production. Test impact: 44/44 in the shape/registry/door scope pass. The broader Physics suite shows the pre-existing PhysicsResolveCapture static-state flakiness documented in CLAUDE.md — 6 baseline failures without my new tests, 10 with them (4 extra are my apparatus tests' IsPlayer-flag resolves getting captured by a concurrent Capture-test race). Independent of this fix; verified by isolating each test class. Findings + apparatus reasoning: docs/research/2026-05-24-door-dat-inspection-findings.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e1d94d7094
commit
3b7dc46219
2 changed files with 343 additions and 3 deletions
|
|
@ -434,7 +434,20 @@ public sealed class ShadowObjectRegistry
|
|||
uint primaryCellId = 0u)
|
||||
{
|
||||
results.Clear();
|
||||
var seen = new HashSet<uint>();
|
||||
// A6.P4 door fix (2026-05-24): dedup on the full ShadowEntry rather
|
||||
// than entity id. Pre-RegisterMultiPart each entity had exactly one
|
||||
// shadow, so dedup-by-entityId correctly suppressed multi-cell
|
||||
// duplication. With multi-part entities (a door has 1 Sphere + 1
|
||||
// per-Part-BSP = 2 entries with the same EntityId; creatures can
|
||||
// have more), an entityId dedup silently dropped every shape after
|
||||
// the first — the door's BSP slab never reached BSPQuery in the
|
||||
// 2026-05-24 apparatus reproduction. ShadowEntry's record-struct
|
||||
// equality compares all fields (incl. GfxObjId, LocalPosition,
|
||||
// CollisionType) so distinct shapes of the same entity make it
|
||||
// through, while a single shape registered across multiple cells
|
||||
// (its position + radius equal across calls) deduplicates exactly
|
||||
// as before.
|
||||
var seen = new HashSet<ShadowEntry>();
|
||||
|
||||
// Cells reachable from the sphere's primary cell via the portal graph
|
||||
// (output of CellTransit.FindCellSet). This set holds the primary
|
||||
|
|
@ -450,7 +463,7 @@ public sealed class ShadowObjectRegistry
|
|||
if (!_cells.TryGetValue(cellId, out var list)) continue;
|
||||
foreach (var entry in list)
|
||||
{
|
||||
if (seen.Add(entry.EntityId))
|
||||
if (seen.Add(entry))
|
||||
results.Add(entry);
|
||||
}
|
||||
}
|
||||
|
|
@ -503,7 +516,7 @@ public sealed class ShadowObjectRegistry
|
|||
|
||||
foreach (var entry in list)
|
||||
{
|
||||
if (seen.Add(entry.EntityId))
|
||||
if (seen.Add(entry))
|
||||
results.Add(entry);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue