fix(test): correct geometric pin test for door slab Z math
The Geometric_DoorSlabZRange_AbovePlayerSphereTop test was computing slabWorldZBottom as (entity.Z + partFrame.Z) — assuming the slab's local Z=0 was its bottom. Actually checking the dat shows the slab's PhysicsPolygons local AABB is min=(-0.954, -0.134, -1.236) max=(0.971, 0.127, 1.255) — the slab's local origin is at its GEOMETRIC CENTER, not the bottom. With partFrame.Z=1.275 lifting the origin, the slab world Z is actually [94.139, 96.630], not [95.375, 97.865]. Corrected test now computes both slabLocalZMin and slabLocalZMax from the polygon vertices and asserts the opposite (correct) geometric fact: the slab IS at sphere height — overlap from Z=94.139 to Z=95.20 (1.061 m of vertical overlap with the player's sphere). The slab is NOT a lintel that misses the sphere; it should collide. Test renamed: Geometric_DoorSlabZRange_AbovePlayerSphereTop → Geometric_DoorSlabAtSphereHeight_OverlapsInZ. Handoff doc 2026-05-25-door-bug-partial-fix-shipped.md updated with the corrected analysis. The "next investigation candidates" list now points toward cdb attach to retail as the highest-ROI option, since the BSP collision IS active at sphere height but production still shows asymmetric walkthrough behavior. The bug is in either the GetNearbyObjects coverage at primary-cell boundaries, the BSP polygon partial-overlap handling, or missing cell-BSP collision for cottage doorway walls. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c27fded61e
commit
85a164f4a8
2 changed files with 125 additions and 129 deletions
|
|
@ -300,9 +300,9 @@ public class DoorBugTrajectoryReplayTests
|
|||
/// </para>
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Geometric finding (2026-05-25 evening) — pins the door geometry
|
||||
/// math that explains why the "inside-out walkthrough" persists
|
||||
/// after the cell-visibility fix.
|
||||
/// Geometric pin (2026-05-25 evening, CORRECTED) — pins where the
|
||||
/// cottage door's BSP slab actually lives in world space relative
|
||||
/// to the player's sphere.
|
||||
///
|
||||
/// <para>
|
||||
/// The cottage door Setup 0x020019FF has:
|
||||
|
|
@ -319,58 +319,55 @@ public class DoorBugTrajectoryReplayTests
|
|||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// With entity at (132.6, 17.1, 94.1) (the captured Holtburg cottage
|
||||
/// door spawn position):
|
||||
/// AABB measured: <c>min=(-0.954,-0.134,-1.236) max=(0.971,0.127,1.255)</c>.
|
||||
/// The slab's local origin is at the slab's GEOMETRIC CENTER (each
|
||||
/// axis is roughly symmetric around 0). With partFrame.Z = +1.275
|
||||
/// lifting the local origin up from the entity, the slab's world
|
||||
/// extents are:
|
||||
/// </para>
|
||||
///
|
||||
/// <list type="bullet">
|
||||
/// <item>Cylinder world Z range: [94.118, 94.318] — touches the
|
||||
/// ground (sphere foot Z=94 to top Z=95.20 overlaps cyl Z up to 94.318).</item>
|
||||
/// <item>Slab world Z range: [95.375, 97.865]. <b>The slab's BOTTOM
|
||||
/// (95.375 m) is 0.175 m ABOVE the player's sphere top
|
||||
/// (95.20 m).</b> The slab NEVER intersects the player's
|
||||
/// body sphere vertically.</item>
|
||||
/// <item>X: [131.635, 133.560] (1.925 m wide; after 180° entity Z rot)</item>
|
||||
/// <item>Y: [16.848, 17.109] (0.261 m thick)</item>
|
||||
/// <item><b>Z: [94.139, 96.630]</b> (2.491 m tall, bottom JUST above floor)</item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para>
|
||||
/// Player sphere (radius 0.48, height 1.20) at floor Z=94 extends
|
||||
/// Z=[94, 95.20]. Slab bottom (94.139) is BELOW sphere top (95.20)
|
||||
/// by 1.061 m. <b>The slab DOES overlap the sphere in Z</b> over
|
||||
/// world Z range [94.139, 95.20]. The slab is at sphere height,
|
||||
/// not above it.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Implication: the door's only effective collision against a player
|
||||
/// at floor level is the 0.10 m radius foot cylinder. The 1.93 m wide
|
||||
/// slab is collision-irrelevant at sphere height — it's a LINTEL
|
||||
/// (the door frame above), not a leaf collision. The user-reported
|
||||
/// "off-center walkthrough" is the sphere walking AROUND the
|
||||
/// 0.10 m foot cylinder (sphere reach 0.48 + cyl 0.10 = 0.58 m;
|
||||
/// any sphere center >0.58 m from cylinder center passes freely).
|
||||
/// The "body partially intersects door" is the rendered character model
|
||||
/// occupying volume the door visual fills, but no collision body to
|
||||
/// stop it because the slab is too high.
|
||||
/// The foot cylinder (r=0.10, h=0.20) sits at world Z [94.118, 94.318]
|
||||
/// — barely above the floor, well within the sphere's foot region.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Next session: this is a DAT or registration issue, not a BSP query
|
||||
/// bug. Options:
|
||||
/// <list type="number">
|
||||
/// <item>Verify retail actually USES this geometry — maybe the door
|
||||
/// relies on the cottage CELL'S walls (cell 0x0150's
|
||||
/// PhysicsPolygons) to enclose the doorway, and the door's
|
||||
/// only collision is the foot cylinder + a leaf shape we're
|
||||
/// missing.</item>
|
||||
/// <item>Inspect parts 1+2 (the door LEAVES, GfxObj 0x010044B6) to
|
||||
/// confirm they're truly visual-only or if we missed a
|
||||
/// physics shape.</item>
|
||||
/// <item>cdb attach to retail at a Holtburg cottage door — set a
|
||||
/// breakpoint on CTransition::FindObjCollisions for the door
|
||||
/// entity and inspect what shapes retail tests against.</item>
|
||||
/// </list>
|
||||
/// Both shapes are at collision-able height. So the post-fix
|
||||
/// inside-out walkthrough at off-center positions is NOT explained
|
||||
/// by the slab being above the sphere. The bug must be in the BSP
|
||||
/// polygon-level collision response, OR in how the multi-cell
|
||||
/// portal-reachable cells produce the shapes list for a player on
|
||||
/// the indoor side of the doorway. Next session investigation:
|
||||
/// add a focused test that replays the captured inside-out
|
||||
/// walkthrough tick with the door registered at its FAITHFUL
|
||||
/// production transform (180° entity rot + dat-loaded partFrame)
|
||||
/// and shows what BSPQuery.FindCollisions actually does at that
|
||||
/// tick.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Geometric_DoorSlabZRange_AbovePlayerSphereTop()
|
||||
public void Geometric_DoorSlabAtSphereHeight_OverlapsInZ()
|
||||
{
|
||||
var datDir = ResolveDatDir();
|
||||
if (datDir is null) return;
|
||||
|
||||
// Load the door setup + part 0's PlacementFrame.
|
||||
DatReaderWriter.Types.Frame partFrame;
|
||||
float slabLocalZMax;
|
||||
float slabLocalZMin = float.MaxValue;
|
||||
float slabLocalZMax = float.MinValue;
|
||||
using (var dats = new DatCollection(datDir, DatAccessType.Read))
|
||||
{
|
||||
var setup = dats.Get<DatReaderWriter.DBObjs.Setup>(0x020019FFu)!;
|
||||
|
|
@ -380,38 +377,37 @@ public class DoorBugTrajectoryReplayTests
|
|||
|
||||
var gfx = dats.Get<DatReaderWriter.DBObjs.GfxObj>(DoorGfxObjId)!;
|
||||
Assert.NotNull(gfx.PhysicsPolygons);
|
||||
// Compute local AABB Z max from vertex array.
|
||||
slabLocalZMax = float.MinValue;
|
||||
// Walk every physics polygon vertex to find local Z extents.
|
||||
foreach (var poly in gfx.PhysicsPolygons.Values)
|
||||
{
|
||||
foreach (ushort vid in poly.VertexIds)
|
||||
{
|
||||
if (gfx.VertexArray.Vertices.TryGetValue(vid, out var sv))
|
||||
if (sv.Origin.Z > slabLocalZMax) slabLocalZMax = sv.Origin.Z;
|
||||
if (!gfx.VertexArray.Vertices.TryGetValue(vid, out var sv)) continue;
|
||||
if (sv.Origin.Z < slabLocalZMin) slabLocalZMin = sv.Origin.Z;
|
||||
if (sv.Origin.Z > slabLocalZMax) slabLocalZMax = sv.Origin.Z;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The door's entity is placed at world Z=94.1 in Holtburg (per
|
||||
// captured spawn log). Slab local Z=0 origin offsets to:
|
||||
float slabWorldZBottom = DoorSpawnPos.Z + partFrame.Origin.Z;
|
||||
float slabWorldZTop = slabWorldZBottom + slabLocalZMax;
|
||||
// Slab local origin shifted up by partFrame.Z. Slab world Z extents:
|
||||
float partWorldZ = DoorSpawnPos.Z + partFrame.Origin.Z;
|
||||
float slabWorldZBottom = partWorldZ + slabLocalZMin;
|
||||
float slabWorldZTop = partWorldZ + slabLocalZMax;
|
||||
|
||||
// The player has sphereHeight=1.20, sphereRadius=0.48. Sphere top
|
||||
// in world = foot.Z + height - radius + radius = foot.Z + height
|
||||
// (the top of the head sphere centered at foot.Z + height - radius).
|
||||
const float SphereHeight = 1.20f;
|
||||
const float PlayerFootZ = 94f; // standard Holtburg floor
|
||||
float sphereTopZ = PlayerFootZ + SphereHeight;
|
||||
const float SphereHeight = 1.20f;
|
||||
const float PlayerFootZ = 94f;
|
||||
float sphereTopZ = PlayerFootZ + SphereHeight;
|
||||
|
||||
// The crucial assertion: slab bottom is above sphere top.
|
||||
Assert.True(slabWorldZBottom > sphereTopZ,
|
||||
$"Door slab bottom ({slabWorldZBottom:F3}) should be ABOVE " +
|
||||
$"player sphere top ({sphereTopZ:F3}). Gap = " +
|
||||
$"{slabWorldZBottom - sphereTopZ:F3} m. This pins the geometric " +
|
||||
$"fact that the slab does not collide with the player at floor " +
|
||||
$"level — only the foot cylinder does. The inside-out 'walkthrough' " +
|
||||
$"is the sphere passing around the cylinder, not through the slab.");
|
||||
// The slab IS at sphere height — bottom should be below sphere top.
|
||||
Assert.True(slabWorldZBottom < sphereTopZ,
|
||||
$"Door slab bottom ({slabWorldZBottom:F3}) should be BELOW " +
|
||||
$"player sphere top ({sphereTopZ:F3}). Slab Z range = " +
|
||||
$"[{slabWorldZBottom:F3}, {slabWorldZTop:F3}]. Player sphere Z = " +
|
||||
$"[{PlayerFootZ:F3}, {sphereTopZ:F3}]. The slab IS at " +
|
||||
$"sphere height (overlap from {MathF.Max(slabWorldZBottom, PlayerFootZ):F3} " +
|
||||
$"to {MathF.Min(slabWorldZTop, sphereTopZ):F3}). So the inside-out " +
|
||||
$"walkthrough is NOT caused by the slab being above the sphere — " +
|
||||
$"the bug must be in BSP polygon-level collision response.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue