test(phys): A6.P4 door — corner-slide hypothesis falsified, bug is state-related
CornerSlide_AlcoveEastToCottageNorth_ShouldBlock test: - Registers cottage GfxObj 0x01000A2B (contains north exterior walls) - Registers cell 0xA9B40150 BSP via dat-direct load (alcove walls) - Places sphere at (132.95, 16.8, 94) inside alcove near east wall - Walks sphere +Y 50 times at walk speed (0.05 m/tick) Result: sphere STAYS at (132.95, 16.8) for all 50 ticks with collision normal cn=(0.71, -0.71, 0) — the average of alcove east wall normal and cottage north wall normal at their meeting corner. The corner handling works correctly in the harness. So production's inside-out walkthrough is NOT a geometric or BSP collision-detection bug. The geometry exists, the collision detection fires symmetrically at corners. The discrepancy must be a STATE difference between harness and production: - Real walkable polygons with edges (harness uses big quad) - Real terrain (harness uses Z=-1000 stub) - Accumulated body state across many prior ticks (harness uses fresh) - Possibly cell ping-pong between 0x0150 and 0x0029 in production Cottage GfxObj wall polygons at the doorway area confirmed: - North exterior wall east of doorway: polys 0x0032, 0x0033 X=[133.5, 136.3], Y=17.10, Z=[94, 97], normal +Y - North exterior wall west of doorway: polys 0x0030, 0x0031, 0x0034, 0x0035 (X<131.6 various ranges) - Lintel polys above doorway: 0x0037, 0x0038, 0x003A, 0x003B at Z>96.5 Next-session moves (per handoff): 1. Replay captured tick 2586 (where sphere went from cell 0x0150 to 0x0029 at X=134.022, way past alcove east wall). Inspect engine behavior at exactly that tick's body state. 2. cdb attach to retail at Holtburg cottage doorway — verify whether retail also lets sphere walk through at off-center, OR blocks cleanly. If retail also allows walkthrough, this might be retail-faithful behavior we should accept. Updated handoff: docs/research/2026-05-25-door-bug-inside-out-geometry-gap.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fe29db5691
commit
a657ca946c
2 changed files with 179 additions and 17 deletions
|
|
@ -441,6 +441,145 @@ public class DoorBugTrajectoryReplayTests
|
|||
$"wall is now blocking as expected — the #98 gate fix landed.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A6.P4 corner-slide hypothesis (2026-05-25 late) — reproduces the
|
||||
/// inside-out walkthrough at unit-test speed. Builds engine with
|
||||
/// BOTH cottage GfxObj 0x01000A2B (which contains the north exterior
|
||||
/// wall east of doorway at X=[133.5, 136.3], Y=17.10) AND cell
|
||||
/// 0xA9B40150's BSP (alcove east wall at X=133.5, Y=[16.5, 17.1]).
|
||||
/// Sphere starts inside alcove sliding against east wall, then
|
||||
/// walks NORTH. If harness slides sphere past the corner at
|
||||
/// (133.5, 17.10) to end up at X > 133.5 Y > 17.10, bug reproduced.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void CornerSlide_AlcoveEastToCottageNorth_ShouldBlock()
|
||||
{
|
||||
var datDir = ResolveDatDir();
|
||||
if (datDir is null) return;
|
||||
|
||||
var (engine, cache) = BuildFaithfulDoorEngine(datDir);
|
||||
|
||||
// 1. Register cottage GfxObj (contains the north exterior wall).
|
||||
const uint CottageGfxId = 0x01000A2Bu;
|
||||
const uint CottageEntityId = 0x00A9B479u;
|
||||
var cottageFixturePath = Path.Combine(SolutionRoot(),
|
||||
"tests", "AcDream.Core.Tests", "Fixtures", "issue98",
|
||||
"0x01000A2B.gfxobj.json");
|
||||
var cottageDump = GfxObjDumpSerializer.Read(cottageFixturePath);
|
||||
var cottagePhysics = GfxObjDumpSerializer.Hydrate(cottageDump);
|
||||
cache.RegisterGfxObjForTest(CottageGfxId, cottagePhysics);
|
||||
|
||||
engine.ShadowObjects.Register(
|
||||
entityId: CottageEntityId,
|
||||
gfxObjId: CottageGfxId,
|
||||
worldPos: new Vector3(130.5f, 11.5f, 94.0f),
|
||||
rotation: Quaternion.CreateFromAxisAngle(Vector3.UnitZ, MathF.PI),
|
||||
radius: cottagePhysics.BoundingSphere?.Radius ?? 14f,
|
||||
worldOffsetX: 0f,
|
||||
worldOffsetY: 0f,
|
||||
landblockId: DoorLandblockId,
|
||||
collisionType: ShadowCollisionType.BSP,
|
||||
scale: 1.0f,
|
||||
cellScope: 0u);
|
||||
|
||||
// 2. Load cell 0xA9B40150 BSP into cache (the alcove walls).
|
||||
const uint AlcoveCellId = 0xA9B40150u;
|
||||
using (var dats = new DatCollection(datDir, DatAccessType.Read))
|
||||
{
|
||||
var envCell = dats.Get<DatReaderWriter.DBObjs.EnvCell>(AlcoveCellId);
|
||||
Assert.NotNull(envCell);
|
||||
var environment = dats.Get<DatReaderWriter.DBObjs.Environment>(
|
||||
0x0D000000u | envCell!.EnvironmentId);
|
||||
Assert.NotNull(environment);
|
||||
Assert.True(environment!.Cells.TryGetValue(envCell.CellStructure, out var cellStruct));
|
||||
|
||||
var cellOriginWorld = envCell.Position.Origin;
|
||||
var cellTransform =
|
||||
Matrix4x4.CreateFromQuaternion(envCell.Position.Orientation) *
|
||||
Matrix4x4.CreateTranslation(cellOriginWorld);
|
||||
cache.CacheCellStruct(AlcoveCellId, envCell, cellStruct!, cellTransform);
|
||||
}
|
||||
Assert.NotNull(cache.GetCellStruct(AlcoveCellId));
|
||||
|
||||
// 3. Sphere setup: inside alcove, near east wall.
|
||||
// Alcove east wall at world X=133.5, Y=[16.5, 17.1]. Sphere at
|
||||
// X=132.95 (sphere east edge 133.43 just west of wall), Y=16.8
|
||||
// (inside alcove Y range).
|
||||
var currentPos = new Vector3(132.95f, 16.8f, 94f);
|
||||
|
||||
// Walk sphere in +Y direction (toward cottage exterior north wall).
|
||||
// Repeat several ticks with small steps to mimic walk-speed motion.
|
||||
Vector3 pos = currentPos;
|
||||
uint cellId = AlcoveCellId;
|
||||
bool isOnGround = true;
|
||||
|
||||
var body = new PhysicsBody
|
||||
{
|
||||
Position = pos,
|
||||
Orientation = Quaternion.Identity,
|
||||
ContactPlaneValid = true,
|
||||
ContactPlane = new System.Numerics.Plane(0f, 0f, 1f, -94f),
|
||||
ContactPlaneCellId = cellId,
|
||||
WalkablePolygonValid = true,
|
||||
WalkablePlane = new System.Numerics.Plane(0f, 0f, 1f, -94f),
|
||||
WalkableVertices = new[]
|
||||
{
|
||||
new Vector3(120f, 10f, 94f),
|
||||
new Vector3(145f, 10f, 94f),
|
||||
new Vector3(145f, 30f, 94f),
|
||||
new Vector3(120f, 30f, 94f),
|
||||
},
|
||||
WalkableUp = Vector3.UnitZ,
|
||||
TransientState = TransientStateFlags.Contact | TransientStateFlags.OnWalkable,
|
||||
};
|
||||
|
||||
Console.WriteLine($"Start: pos=({pos.X:F3},{pos.Y:F3},{pos.Z:F3}) cell=0x{cellId:X8}");
|
||||
for (int t = 1; t <= 50; t++)
|
||||
{
|
||||
var target = pos + new Vector3(0f, 0.05f, 0f); // walk speed
|
||||
var result = engine.ResolveWithTransition(
|
||||
currentPos: pos,
|
||||
targetPos: target,
|
||||
cellId: cellId,
|
||||
sphereRadius: 0.48f,
|
||||
sphereHeight: 1.20f,
|
||||
stepUpHeight: 0.60f,
|
||||
stepDownHeight: 1.5f,
|
||||
isOnGround: isOnGround,
|
||||
body: body,
|
||||
moverFlags: ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide,
|
||||
movingEntityId: DoorEntityId + 1);
|
||||
|
||||
pos = result.Position;
|
||||
cellId = result.CellId;
|
||||
isOnGround = result.IsOnGround;
|
||||
body.Position = pos;
|
||||
|
||||
if (t % 5 == 0 || result.CollisionNormalValid)
|
||||
{
|
||||
Console.WriteLine(string.Format(System.Globalization.CultureInfo.InvariantCulture,
|
||||
"t={0,2} pos=({1:F3},{2:F3},{3:F3}) cell=0x{4:X8} cnValid={5} cn=({6:F2},{7:F2},{8:F2})",
|
||||
t, pos.X, pos.Y, pos.Z, cellId,
|
||||
result.CollisionNormalValid,
|
||||
result.CollisionNormal.X, result.CollisionNormal.Y, result.CollisionNormal.Z));
|
||||
}
|
||||
|
||||
// Stop if sphere has clearly walked through the wall.
|
||||
if (pos.Y > 18f) break;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Final pos: ({pos.X:F3},{pos.Y:F3},{pos.Z:F3}) cell=0x{cellId:X8}");
|
||||
|
||||
// Document expected: sphere should stop at sphere center Y =
|
||||
// 17.10 - 0.48 = 16.62 (cottage north wall + sphere reach).
|
||||
// Bug: sphere slides past corner and exits north.
|
||||
Assert.True(pos.Y < 17.20f,
|
||||
$"BUG REPRODUCTION: sphere walked from inside alcove to Y={pos.Y:F3} " +
|
||||
$"(past cottage north wall at Y=17.10). Cottage wall should have blocked " +
|
||||
$"sphere at Y ≈ 16.62 (wall - sphere reach). If this assertion FAILS, " +
|
||||
$"the corner handling at (X=133.5, Y=17.10) is letting sphere slide past.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Diagnostic: dump cottage GfxObj 0x01000A2B polygons near world
|
||||
/// position (133.655, 17.59, 94.5) — the sphere position where the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue