feat(diag): Cluster A — extend [cell-cache] with AABB + bsphere + recursive poly count
The original Phase E [cell-cache] probe (fda6af7) only showed the BSP root
node's direct poly count, which was always 0 for non-trivial trees (internal
node root). Extending the probe to:
- Recursively walk the BSP tree and count total leaf polys
- Detect unmatched poly IDs (BSP leaves referencing IDs not in our resolved dict)
- Dump the BSP root bounding sphere (center + radius)
- Dump the cell's local AABB (min/max from poly vertices)
- Dump the cell's world origin (cellTransform * (0,0,0))
The extended data made the route-δ diagnosis definitive: Holtburg cells DO
have full physics polygons in their BSPs (e.g. 0xA9B40143 has 14 polys all
resolved, full Z range 0-2.8 m). The bug is upstream — AABB-based cell
containment is too tight to capture a standing player at most thresholds
between rooms, so the indoor cell-BSP branch fires only intermittently.
Retail uses portal traversal (CObjMaint::HandleObjectEnterCell + cell-side
portal data) which propagates CellId at door crossings. Our AABB-containment
shortcut is partial. This diagnostic stays in place as infrastructure for
the follow-up "Indoor portal-based cell tracking" phase.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fda6af7ad0
commit
1f11ba9b38
1 changed files with 41 additions and 1 deletions
|
|
@ -174,8 +174,48 @@ public sealed class PhysicsDataCache
|
|||
var root = cellStruct.PhysicsBSP?.Root;
|
||||
int bspRootPolyCount = root?.Polygons?.Count ?? 0;
|
||||
bool bspRootHasChildren = root?.PosNode is not null || root?.NegNode is not null;
|
||||
|
||||
// Recursive walk: count total leaf poly references + how many of
|
||||
// those poly IDs are absent from the resolved dict. If
|
||||
// bspTotalLeafPolys == 0 the BSP has no collidable polys at all.
|
||||
// If bspUnmatchedIds > 0 the BSP references IDs we didn't resolve
|
||||
// (data-deserialization quirk hypothesis).
|
||||
int bspTotalLeafPolys = 0;
|
||||
int bspUnmatchedIds = 0;
|
||||
if (root is not null)
|
||||
{
|
||||
var stack = new System.Collections.Generic.Stack<DatReaderWriter.Types.PhysicsBSPNode>();
|
||||
stack.Push(root);
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
var n = stack.Pop();
|
||||
if (n.Polygons is not null)
|
||||
{
|
||||
foreach (var pid in n.Polygons)
|
||||
{
|
||||
bspTotalLeafPolys++;
|
||||
if (!resolved.ContainsKey(pid)) bspUnmatchedIds++;
|
||||
}
|
||||
}
|
||||
if (n.PosNode is not null) stack.Push(n.PosNode);
|
||||
if (n.NegNode is not null) stack.Push(n.NegNode);
|
||||
}
|
||||
}
|
||||
|
||||
var bs = root?.BoundingSphere;
|
||||
string bsStr = bs is null
|
||||
? "bsphere=n/a"
|
||||
: System.FormattableString.Invariant(
|
||||
$"bsphere=({bs.Origin.X:F2},{bs.Origin.Y:F2},{bs.Origin.Z:F2}) r={bs.Radius:F2}");
|
||||
|
||||
// World origin = cellTransform * (0,0,0,1). Tells us where this cell
|
||||
// sits in world coordinates, so we can cross-check whether the
|
||||
// player's worldPos actually lies inside the AABB when transformed
|
||||
// back to local.
|
||||
var worldOrigin = Vector3.Transform(Vector3.Zero, worldTransform);
|
||||
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[cell-cache] envCellId=0x{envCellId:X8} physicsPolyCount={cellStruct.PhysicsPolygons?.Count ?? 0} resolvedCount={resolved.Count} bspRootPolyCount={bspRootPolyCount} bspRootHasChildren={bspRootHasChildren}"));
|
||||
$"[cell-cache] envCellId=0x{envCellId:X8} physicsPolyCount={cellStruct.PhysicsPolygons?.Count ?? 0} resolvedCount={resolved.Count} bspTotalLeafPolys={bspTotalLeafPolys} bspUnmatchedIds={bspUnmatchedIds} {bsStr} aabbMin=({aabbMin.X:F2},{aabbMin.Y:F2},{aabbMin.Z:F2}) aabbMax=({aabbMax.X:F2},{aabbMax.Y:F2},{aabbMax.Z:F2}) worldOrigin=({worldOrigin.X:F2},{worldOrigin.Y:F2},{worldOrigin.Z:F2})"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue