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
|
|
@ -142,27 +142,50 @@ the corner at (133.5, 17.10) — should encounter the cottage wall
|
|||
and be stopped. If our engine handles the corner transition
|
||||
incorrectly, sphere slides past.
|
||||
|
||||
## What's next (revised)
|
||||
## What's next (revised AGAIN — corner test PASSED, bug is state-related)
|
||||
|
||||
**Investigate sphere-vs-corner collision behavior** at the alcove
|
||||
east wall → cottage north wall meeting point at world (133.5, 17.10).
|
||||
**Corner-slide hypothesis: FALSIFIED.** `CornerSlide_AlcoveEastToCottageNorth_ShouldBlock`
|
||||
test runs cottage GfxObj + cell 0x0150 BSP both registered. Places
|
||||
sphere at (132.95, 16.8, 94) inside alcove near east wall. Walks +Y
|
||||
50 times at 0.05 m/tick. **Sphere stays put at (132.95, 16.8) for all
|
||||
50 ticks with cn=(0.71, -0.71, 0)** — the corner normal between
|
||||
alcove east wall and cottage north wall. **The corner handling works
|
||||
correctly in the harness.**
|
||||
|
||||
Apparatus to write:
|
||||
- Load cottage GfxObj + cell 0x0150 BSP into harness
|
||||
- Place sphere at (133.0, 16.8, 94) (inside alcove, near east wall)
|
||||
- Walk sphere +Y in small increments
|
||||
- Expected: sphere stops at Y=17.10-0.48 = 16.62 if east wall blocks
|
||||
OR: sphere slides along corner staying in alcove
|
||||
- Captured: sphere ends up at (133.655, 17.59) — sliding past corner
|
||||
So production's walkthrough is **a STATE difference**, not a geometric
|
||||
or collision-detection bug. The harness's sphere can't reach
|
||||
X=133.655 inside the cottage geometry. Production's sphere does
|
||||
reach it somehow.
|
||||
|
||||
If harness reproduces "sphere slides past corner", the bug is in
|
||||
the engine's corner-handling. Read BSPQuery for the sphere-vs-edge
|
||||
case. Retail oracle: CTransition::find_obj_collisions corner
|
||||
handling at acclient_2013_pseudo_c.txt.
|
||||
Differences between harness and production:
|
||||
- Harness uses identity walkable polygon (big quad). Production uses
|
||||
real cell walkable polys (small, with edges).
|
||||
- Harness has stub landblock terrain at Z=-1000. Production has real
|
||||
terrain.
|
||||
- Harness uses fresh body each tick. Production has accumulated state
|
||||
from many prior ticks (velocity, contact plane history, etc.).
|
||||
- Harness uses sphereRadius=0.48 + sphereHeight=1.20 exactly. Production
|
||||
matches but might have different stepUp / stepDown.
|
||||
|
||||
If harness BLOCKS at corner (no sliding past), the bug is something
|
||||
else — maybe cell 0x0150 BSP isn't being queried in production from
|
||||
some sphere position.
|
||||
**Next-session apparatus**: replay the EXACT captured tick 2586's body
|
||||
state through the corner-blocking test setup. Tick 2586 was where
|
||||
sphere went from indoor cell 0x0150 to outdoor cell 0x0029 at
|
||||
PrevPy=17.586, Py=17.586 (no Y motion) with X=134.022 (way past alcove
|
||||
east wall). That tick is the smoking-gun "how did sphere get to X=134
|
||||
inside alcove" event. Load its body state into the harness, replay
|
||||
the call, see what the engine reports about getting to that position.
|
||||
|
||||
If the harness blocks (sphere can't reach X=134), then production has
|
||||
state we're not capturing — probably accumulated push/depenetration
|
||||
across many earlier ticks. If the harness reproduces sphere at X=134,
|
||||
the bug is in the specific body state at that moment.
|
||||
|
||||
The cleanest path forward is **cdb attach to retail** as the original
|
||||
handoff recommended. Inspect what retail does FRAME-BY-FRAME at the
|
||||
same doorway approach. If retail walks the user inside cottage at
|
||||
off-center approach EXACTLY like we do — the bug isn't a bug, and
|
||||
we should accept the behavior. If retail blocks cleanly — diff
|
||||
retail's body state evolution vs ours to find the divergence.
|
||||
|
||||
## OLD (superseded) "what's next" candidates
|
||||
|
||||
|
|
|
|||
|
|
@ -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