test(phys): door setup + GfxObj dat-inspection — Hypothesis A falsified
Read-only deterministic test that opens the real client dat and
dumps Setup 0x020019FF + every GfxObj id it references. Bypasses
PhysicsDataCache's four early-return filters so we see WHAT is in
the dat, not what got into the cache. Skips gracefully when the
dat directory isn't present (keeps CI green).
Result reframes the prior session's investigation:
GfxObj 0x010044B5 (part 0 of the door) DOES have a full door-slab
PhysicsBSP — 6 two-sided (SidesType=Landblock) polygons forming a
1.925m × 0.261m × 2.490m collision volume at frame[0] offset
(-0.006, 0.125, 1.275). Bounding sphere radius 1.975. HasPhysics
flag set. So the handoff's Hypothesis A ("0x010044B5 has no
collision-bearing polygons, only visual") is FALSE.
GfxObj 0x010044B6 (parts 1 + 2, the swinging leaves) IS visual-only
by retail design — HasPhysics clear, PhysicsBSP null, 0 PhysicsPolygons,
but 87 visual Polygons. Our ShadowShapeBuilder skipping these matches
retail's CPhysicsPart::find_obj_collisions short-circuit on
physics_bsp==0 (acclient_2013_pseudo_c.txt:275051) — not a bug.
So the door collision bug is in INTEGRATION, not data. The Task 7
experiment last session registered 0x010044B5's BSP but got zero
[resolve-bldg] attributions. With the data confirmed good, the
next apparatus is a deterministic harness that hydrates 0x010044B5
from a dat dump, registers it via RegisterMultiPart, and sweeps a
player sphere into the door to confirm whether BSP collision fires
in isolation.
Pickup prompt + full reading in
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
c89df8e4c0
commit
e1d94d7094
2 changed files with 479 additions and 0 deletions
258
docs/research/2026-05-24-door-dat-inspection-findings.md
Normal file
258
docs/research/2026-05-24-door-dat-inspection-findings.md
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
# Door collision dat inspection — findings
|
||||
|
||||
**Date:** 2026-05-24 (evening, continuation of door collision investigation)
|
||||
**Branch:** `claude/strange-albattani-3fc83c`
|
||||
**Status:** Evidence gathered. Hypothesis A from
|
||||
[`2026-05-24-door-collision-session-handoff.md`](2026-05-24-door-collision-session-handoff.md) **FALSIFIED**.
|
||||
|
||||
---
|
||||
|
||||
## TL;DR
|
||||
|
||||
A deterministic, read-only dat-inspection test
|
||||
([`DoorSetupGfxObjInspectionTests.cs`](../../tests/AcDream.Core.Tests/Physics/DoorSetupGfxObjInspectionTests.cs))
|
||||
opens the real client dat and prints the raw state of door Setup
|
||||
`0x020019FF` + its three referenced GfxObjs.
|
||||
|
||||
**Result — Hypothesis A is wrong.** Part 0 (`0x010044B5`) has a complete
|
||||
1.925 m × 0.261 m × 2.490 m door-sized collision volume in the dat. Six
|
||||
two-sided (`SidesType=Landblock`) physics polygons form the closed door
|
||||
slab. Bounding sphere radius 1.975 m. Setup `Flags=HasPhysicsBSP`.
|
||||
|
||||
Parts 1, 2 (`0x010044B6`) **are** visual-only by design — `HasPhysics`
|
||||
flag is clear, `PhysicsBSP` is null, `PhysicsPolygons.Count = 0`. **This
|
||||
matches retail's `CPhysicsPart::find_obj_collisions`**
|
||||
([`acclient_2013_pseudo_c.txt:275051`](../research/named-retail/acclient_2013_pseudo_c.txt)),
|
||||
which explicitly short-circuits when `physics_bsp == 0`. So retail also
|
||||
runs no collision against `0x010044B6` — and our skip-on-null-BSP
|
||||
behavior is retail-faithful, not a bug.
|
||||
|
||||
**This rewrites the "next-session approach" recommendation in the prior
|
||||
handoff.** The handoff said "if 0x010044B5's BSP has zero floor-touching
|
||||
polys → Hypothesis A confirmed → pivot strategy." The BSP has six
|
||||
collision polygons forming the whole door slab. The pivot is not needed;
|
||||
we need to figure out why our integration of `0x010044B5`'s BSP didn't
|
||||
fire during the Task 7 experiment.
|
||||
|
||||
---
|
||||
|
||||
## Raw dump (verbatim from the test)
|
||||
|
||||
```
|
||||
=== Setup 0x020019FF ===
|
||||
Flags = HasParent, AllowFreeHeading, HasPhysicsBSP (0x0000000D)
|
||||
Radius = 0.1414
|
||||
Height = 0.2000
|
||||
StepUp = 0.0900
|
||||
StepDown = 0.0900
|
||||
CylSpheres = 0
|
||||
Spheres = 1
|
||||
[0] r=0.1000 origin=(0.000,0.000,0.018)
|
||||
Parts = 3
|
||||
[0] gfxObj=0x010044B5
|
||||
[1] gfxObj=0x010044B6
|
||||
[2] gfxObj=0x010044B6
|
||||
PlacementFrames = 1
|
||||
[Default] frameCount=3
|
||||
frame[0] pos=(-0.006,0.125,1.275) rot=(0.000,0.000,0.000,1.000)
|
||||
frame[1] pos=(0.710,0.000,1.210) rot=(0.000,0.000,0.000,1.000)
|
||||
frame[2] pos=(0.710,0.247,1.210) rot=(0.000,0.000,1.000,0.000)
|
||||
|
||||
=== GfxObj 0x010044B5 === (the door slab — has physics)
|
||||
Flags = HasPhysics, HasDrawing, HasDIDDegrade (0x0000000B)
|
||||
HasPhysics = True
|
||||
VertexArray = non-null, 8 vertices
|
||||
PhysicsPolygons = 6 polys
|
||||
PhysicsBSP = non-null
|
||||
PhysicsBSP.Root = non-null
|
||||
Root.Type = BPnN
|
||||
Root.BoundingSphere = (-0.390,-0.056,-0.150) r=1.975
|
||||
BSP tree total polys (including children) = 6
|
||||
PhysicsPolygon AABB sweep (first 6 polys):
|
||||
[0x0000] nVerts=4 sides=Landblock min=(-0.954,-0.134,-1.236) max=(0.971,0.127,-1.236) # bottom face
|
||||
[0x0001] nVerts=4 sides=Landblock min=(-0.954,-0.134,-1.236) max=(-0.954,0.127,1.255) # left face
|
||||
[0x0002] nVerts=4 sides=Landblock min=(-0.954,-0.134, 1.255) max=(0.971,0.127,1.255) # top face
|
||||
[0x0003] nVerts=4 sides=Landblock min=( 0.971,-0.134,-1.236) max=(0.971,0.127,1.255) # right face
|
||||
[0x0004] nVerts=4 sides=Landblock min=(-0.954,-0.134,-1.236) max=(0.971,-0.134,1.255) # front face
|
||||
[0x0005] nVerts=4 sides=Landblock min=(-0.954, 0.127,-1.236) max=(0.971,0.127,1.255) # back face
|
||||
PhysicsPolygons combined AABB: min=(-0.954,-0.134,-1.236) max=(0.971,0.127,1.255)
|
||||
size=(1.925, 0.261, 2.490)
|
||||
|
||||
=== GfxObj 0x010044B6 === (the leaves — visual-only by design)
|
||||
Flags = HasDrawing, HasDIDDegrade (0x0000000A)
|
||||
HasPhysics = False
|
||||
VertexArray = non-null, 40 vertices
|
||||
PhysicsPolygons = 0 polys
|
||||
PhysicsBSP = NULL
|
||||
Polygons (visual) = 87 polys
|
||||
DrawingBSP = non-null
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What this means
|
||||
|
||||
### The data is right
|
||||
|
||||
Part 0's BSP is a six-faced thin slab oriented as a vertical door:
|
||||
1.925 m wide × 0.261 m thin × 2.490 m tall. Placed at frame[0] offset
|
||||
`(-0.006, 0.125, 1.275)`, it occupies entity-local Z ∈ `[0.039, 2.530]` —
|
||||
a standard door height. All six faces are
|
||||
`SidesType=Landblock` (two-sided collision — catches a sphere
|
||||
approaching from either side).
|
||||
|
||||
This is exactly what retail's collision system uses to block doors.
|
||||
No mystery, no missing data, no need to fall back to a wider Cylinder
|
||||
approximation.
|
||||
|
||||
### The leaves are correctly visual-only
|
||||
|
||||
`0x010044B6` is the swinging door leaf (used twice — left + right
|
||||
panels). It has no physics by retail design. Our `ShadowShapeBuilder`
|
||||
skipping these parts matches both the dat and retail's
|
||||
`CPhysicsPart::find_obj_collisions`.
|
||||
|
||||
### So the bug is in integration, not data
|
||||
|
||||
The previous session's Task 7 experiment registered `0x010044B5`'s BSP
|
||||
correctly (we saw `type=BSP gfxObj=0x010044B5 radius=2.000
|
||||
localPos=(-0.006,0.125,1.275)` in the per-shape registration), yet got
|
||||
**zero `[resolve-bldg]` attributions** during live play. With the data
|
||||
now confirmed good, that gap must be in:
|
||||
|
||||
1. **The BSP collision dispatch never enters for the door entry** —
|
||||
`TransitionTypes.cs:2257` silently `continue`s when
|
||||
`engine.DataCache.GetGfxObj(obj.GfxObjId)?.BSP?.Root is null`. If the
|
||||
GfxObj wasn't cached at collision time (race with renderer load), the
|
||||
entry is invisibly skipped. **No log distinguishes this from
|
||||
"queried-and-no-hit."**
|
||||
|
||||
2. **Broadphase placeholder radius** — Task 2's `ShadowShapeBuilder`
|
||||
uses `bspRadius = 2f` as a stand-in pending a Task 5/6 caller
|
||||
replacement. The real dat value is `1.975` — close enough not to be
|
||||
the blocker, but the placeholder convention means callers MUST
|
||||
substitute the real BS radius from `cache.GetGfxObj(gfxId).BoundingSphere.Radius`
|
||||
before registering. If a future caller forgets, the broadphase will
|
||||
still mostly work but won't be tight.
|
||||
|
||||
3. **The broadphase center is the part's FRAME origin, not the BSP's
|
||||
bounding-sphere center.** Frame origin = `(-0.006, 0.125, 1.275)`;
|
||||
BS center in part-local = `(-0.390, -0.056, -0.150)`. Distance:
|
||||
1.48 m. The 2.0 m broadphase radius nominally covers the BS sphere
|
||||
(radius 1.975) from the frame origin only on the side closest to the
|
||||
BS center. For approaches on the opposite side, the broadphase
|
||||
sphere extends 2.0 m + 1.48 m = 3.48 m from the BS center — wider
|
||||
than needed, but never too tight in the door case. Still, a more
|
||||
faithful encoding centers the broadphase on the part's BS center +
|
||||
frame offset, with radius = BS radius.
|
||||
|
||||
4. **BSPQuery against `SidesType=Landblock` polys** — `BSPQuery.cs`
|
||||
pass-through-copies `SidesType` (line 2277) but doesn't filter on
|
||||
it. We have not yet verified that `Landblock`-typed polys actually
|
||||
produce collision hits in our query pipeline against a thin-slab
|
||||
geometry. (Note: indoor cells use `SidesType=Single`-typed cell-floor
|
||||
polys and those work — the cellar replay tests pin that. But Doors'
|
||||
`Landblock` polys may behave differently — particularly w.r.t.
|
||||
two-sided collision.)
|
||||
|
||||
5. **Entity rotation at the doorway** — Holtburg cottage doors face
|
||||
non-cardinal directions. The entity's world rotation
|
||||
`entity_rot` composes with `frame[0].Rotation` (identity for part 0)
|
||||
to produce `obj.Rotation = entity_rot`. The sphere
|
||||
transform `inv(entity_rot) * (sphere_world − obj.Position)` is
|
||||
sensitive to that rotation. If we register with identity (forgetting
|
||||
to plumb the spawn's rotation through), the BSP polys will be
|
||||
oriented "into the world" wrong — passing tests that approach from
|
||||
the wrong axis.
|
||||
|
||||
---
|
||||
|
||||
## Recommended next step
|
||||
|
||||
The handoff's "DO NOT speculate-and-fix again" rule still applies. The
|
||||
right next move is **apparatus-first**, not another implementation
|
||||
attempt:
|
||||
|
||||
**Write a focused unit test** that:
|
||||
|
||||
1. Loads the real `0x010044B5` PhysicsBSP from the dat via the
|
||||
inspection test's pattern (or use `GfxObjDumpSerializer.Hydrate`
|
||||
for a deterministic fixture).
|
||||
2. Constructs a synthetic door entity at a known world position
|
||||
`(132.6, 17.1, 94.08)` with a known rotation (try identity AND a
|
||||
~90° Z rotation to cover both axes).
|
||||
3. Sweeps a player sphere at the door from each of the four
|
||||
compass directions, at off-center positions (50 cm off-center)
|
||||
AND dead center.
|
||||
4. Calls `Transition.FindObjCollisions` / `ResolveWithTransition`
|
||||
directly (apparatus path mirrors the live one).
|
||||
5. Asserts:
|
||||
- Dead-center approach → `Collided` / `Adjusted` / `Slid`
|
||||
with `CollideObjectGuids` containing the door entity.
|
||||
- 50 cm off-center approach → same.
|
||||
- From inside walking out → same.
|
||||
|
||||
If the test fails: we have a deterministic reproduction of the live
|
||||
bug in <500 ms, and we can fix the integration with confidence.
|
||||
If the test passes: the door bug is elsewhere (cell registration,
|
||||
spawn-time race, etc.).
|
||||
|
||||
This is the next apparatus the previous session was building toward
|
||||
when it ran out of cycles. With the data question now closed by the
|
||||
dat inspection, it's the highest-information next move.
|
||||
|
||||
---
|
||||
|
||||
## What's in the tree right now
|
||||
|
||||
```
|
||||
$ git status --short
|
||||
?? docs/research/2026-05-24-door-dat-inspection-findings.md
|
||||
?? tests/AcDream.Core.Tests/Physics/DoorSetupGfxObjInspectionTests.cs
|
||||
[+ untracked launch logs from prior sessions]
|
||||
```
|
||||
|
||||
Build green; existing tests still pass; new test runs in 34 ms and
|
||||
produces the dump above.
|
||||
|
||||
---
|
||||
|
||||
## Pickup prompt for next session
|
||||
|
||||
```
|
||||
Door collision dat inspection (2026-05-24 evening) FALSIFIED
|
||||
Hypothesis A. Part 0 (0x010044B5) has a full door-slab BSP in the
|
||||
dat — 6 Landblock-typed polys forming a 1.925 m × 0.261 m × 2.490 m
|
||||
collision volume. Parts 1, 2 (0x010044B6) are visual-only by retail
|
||||
design (HasPhysics flag clear). Retail and acdream both skip those
|
||||
in CPhysicsPart::find_obj_collisions — that's not a bug.
|
||||
|
||||
Read docs/research/2026-05-24-door-dat-inspection-findings.md
|
||||
|
||||
State both altitudes:
|
||||
Currently working toward: M1.5 — Indoor world feels right
|
||||
Current phase: A6.P4 — door collision investigation continues.
|
||||
Per-part BSP infrastructure (Tasks 1-6) ships
|
||||
already; data is confirmed good in the dat; need
|
||||
to determine WHY our integration of 0x010044B5's
|
||||
BSP didn't fire collisions during the Task 7
|
||||
experiment.
|
||||
|
||||
Next moves (in order):
|
||||
1. Write CellarUpTrajectoryReplay-style apparatus test that
|
||||
loads 0x010044B5's PhysicsBSP from a dat dump, registers a
|
||||
synthetic door via RegisterMultiPart, and sweeps a player
|
||||
sphere at it. Confirm BSP collision fires (or doesn't) in
|
||||
isolation.
|
||||
2. If the test passes → bug is in live registration (likely
|
||||
cell scoping, entity rotation, or race with renderer
|
||||
loading). Investigate live cell membership for door
|
||||
entities.
|
||||
3. If the test fails → bug is in BSPQuery.FindCollisions
|
||||
against thin-slab Landblock-typed polys. Investigate the
|
||||
6-path dispatcher for that case.
|
||||
|
||||
DO NOT re-attempt Task 7 (per-part BSP wiring in
|
||||
RegisterLiveEntityCollision) until the apparatus test confirms
|
||||
the BSP works in isolation. Tasks 1-6 stay; they're correct.
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue