test #137: the seam shake reproduced offline — third mechanism characterized
The corridor gate FAILED with a changed symptom: shaking at cell seams (+ purple floor flashing there) instead of the dead stop. Deep-probe session (step-walk/push-back/indoor-bsp + full resolve capture) traced the complete chain; docs/ISSUES.md #137 carries it. Short form: - Corridor floors are double-faced portal slabs over under-rooms; the resting foot sphere lives within half a millimeter of three hit/straddle thresholds there. - Crossing a boundary, the foot penetrates the neighbor ramp slab by ~0.4mm, steps up onto it successfully (+0.6mm lift, stepped=True) — and the lifted check position is then LOST: the following pass runs at the unlifted height (the P2 stale-snapshot class; retail step_up 0x0050b6cc restores only on FAILURE). - The unlifted re-test grazes the under-room's ceiling (the slab underside) within the near-miss window, dispatches a neg-poly step-up with a DOWNWARD normal, whose nested step-down finds no walkable at exact tangency -> StepUpSlide -> slide_sphere opposing branch -> reversed-movement collision -> Collided -> revert. Every frame = shake. Apparatus committed: - Issue137CorridorSeamReplayTests: 3 deterministic offline repros (snapshot-exact west-boundary from the capture, east deep-straddle, the clean-run pin), currently Skip='#137 seam shake' pending the fix. Key: THREE portal-ring hydration (the under-room 0x8A020166 is ring-3; with fewer rings the flood can't add it and the bug vanishes) + live Setup step heights (0.6/1.5) + probe-buffer capture for line-diffing offline vs live traces. - Issue137CorridorSeamInspectionTests: portal-poly world spans (exposed the visual-vs-physics polygon-id conflation and the floor-portal topology), physics-BSP leaf membership walk, hit-normal candidate sweep (|align| both windings), downward-poly sweep. NEXT: read TransitionalInsert's attempt loop against retail 0x0050b6f0, find the restore that clobbers the successful step-up position, fix, un-skip. The purple seam flashing is expected to be the render exposing the same per-frame oscillation - re-check after the physics fix. Suites: Core 2553 / App 713 / UI 425 / Net 385, 0 failures (5 skips = 2 pre-existing + the 3 parked repros). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e8651b3819
commit
dbddad7a5e
4 changed files with 25131 additions and 4 deletions
|
|
@ -73,6 +73,22 @@ public class Issue137CorridorSeamReplayTests
|
|||
toLoad.Add(0x8A020000u | p.OtherCellId);
|
||||
}
|
||||
|
||||
// Expand three portal rings — the live collision cell array reaches
|
||||
// cells three hops out (0x8A020166, the under-ramp room whose ceiling
|
||||
// is the ramp slab's underside, is ring-3 in the 2026-07-06
|
||||
// seam-shake trace; with only two rings the offline flood can never
|
||||
// add it and the shake does not reproduce).
|
||||
for (int ring = 0; ring < 3; ring++)
|
||||
{
|
||||
foreach (var known in new System.Collections.Generic.List<uint>(toLoad))
|
||||
{
|
||||
var cell = dats.Get<EnvCell>(known);
|
||||
if (cell is null) continue;
|
||||
foreach (var p in cell.CellPortals)
|
||||
toLoad.Add(0x8A020000u | p.OtherCellId);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var cellId in toLoad)
|
||||
{
|
||||
var envCell = dats.Get<EnvCell>(cellId);
|
||||
|
|
@ -131,7 +147,145 @@ public class Issue137CorridorSeamReplayTests
|
|||
body: body,
|
||||
moverFlags: ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide);
|
||||
|
||||
[Fact]
|
||||
/// <summary>
|
||||
/// 2026-07-06 seam-shake repro, snapshot-exact (probe session
|
||||
/// launch-137-seam-probes.log + resolve-137-seam-capture.jsonl tick 4101,
|
||||
/// repeated ×46): running WEST across the x=75 boundary
|
||||
/// (0x8A02016E → 0x8A020165, the ramp cell) from (75.287, −40.035, −6)
|
||||
/// toward (74.685, −39.988, −6), the resolve blocks with the SYNTHETIC
|
||||
/// reversed-movement normal (0.997, −0.078, −0.002) and out==in — every
|
||||
/// frame — the "shaking at the seam" report.
|
||||
///
|
||||
/// <para>
|
||||
/// Probe-traced chain: the foot sphere (tangent to the floor) crosses
|
||||
/// onto 0165's ramp floor; the ramp slab is double-faced and the
|
||||
/// UNDERSIDE face (poly 0, n=(−0.03,0,−1)) grazes the sphere within the
|
||||
/// hit threshold → recorded as a foot near-miss → neg-poly step-up
|
||||
/// dispatch with a downward normal → the nested step-up's walkable probe
|
||||
/// rejects the exactly-tangent ramp floor ([walkable-nearest]
|
||||
/// gap=−0.0000 overlapsSphere=False) → StepUpSlide →
|
||||
/// slide_sphere(downward normal vs up-facing contact plane) → the
|
||||
/// opposing-normals branch → Collided → revert. Repeat.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Fact(Skip = "#137 seam shake — deterministic RED repro; the post-step-up check-pos restore clobbers the lifted position (P2 stale-snapshot class). Un-skip with the fix.")]
|
||||
public void SeamShake_WestBoundary_SnapshotExact_Advances()
|
||||
{
|
||||
var datDir = FindDatDir();
|
||||
if (datDir is null)
|
||||
{
|
||||
_out.WriteLine("SKIP: dat directory not found");
|
||||
return;
|
||||
}
|
||||
|
||||
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
||||
var engine = BuildCorridorEngine(dats);
|
||||
|
||||
// Body seeded EXACTLY from the capture's bodyBefore (tick 4101).
|
||||
var body = new PhysicsBody();
|
||||
body.ContactPlaneValid = true;
|
||||
body.ContactPlane = new Plane(Vector3.UnitZ, 6f);
|
||||
body.ContactPlaneCellId = SeamCellWest;
|
||||
body.TransientState |= TransientStateFlags.Contact | TransientStateFlags.OnWalkable;
|
||||
body.WalkablePolygonValid = true;
|
||||
body.WalkablePlane = new Plane(Vector3.UnitZ, 6f);
|
||||
body.WalkableUp = Vector3.UnitZ;
|
||||
body.WalkableVertices = new[]
|
||||
{
|
||||
new Vector3(75f, -38.33333f, -6f),
|
||||
new Vector3(75f, -41.66667f, -6f),
|
||||
new Vector3(78.33333f, -41.66667f, -6f),
|
||||
new Vector3(78.33333f, -38.33333f, -6f),
|
||||
};
|
||||
|
||||
var from = new Vector3(75.28674f, -40.03537f, -6f);
|
||||
var to = new Vector3(74.6854f, -39.988018f, -6f);
|
||||
|
||||
// Emit the same step-level probes the live session logged so the
|
||||
// offline trace can be line-diffed against launch-137-seam-probes.log
|
||||
// — the first divergent line names the state the replay is missing.
|
||||
var probeBuffer = new System.IO.StringWriter();
|
||||
var prevOut = Console.Out;
|
||||
ResolveResult r1;
|
||||
try
|
||||
{
|
||||
Console.SetOut(probeBuffer);
|
||||
PhysicsDiagnostics.ProbeStepWalkEnabled = true;
|
||||
PhysicsDiagnostics.ProbePushBackEnabled = true;
|
||||
PhysicsDiagnostics.ProbeIndoorBspEnabled = true;
|
||||
|
||||
r1 = engine.ResolveWithTransition(
|
||||
currentPos: from,
|
||||
targetPos: to,
|
||||
cellId: SeamCellWest,
|
||||
sphereRadius: 0.48f,
|
||||
sphereHeight: 1.2f,
|
||||
stepUpHeight: 0.6f, // live Setup values from the capture
|
||||
stepDownHeight: 1.5f,
|
||||
isOnGround: true,
|
||||
body: body,
|
||||
moverFlags: ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PhysicsDiagnostics.ProbeStepWalkEnabled = false;
|
||||
PhysicsDiagnostics.ProbePushBackEnabled = false;
|
||||
PhysicsDiagnostics.ProbeIndoorBspEnabled = false;
|
||||
Console.SetOut(prevOut);
|
||||
}
|
||||
_out.WriteLine(probeBuffer.ToString());
|
||||
|
||||
_out.WriteLine($"r1: ok={r1.Ok} out=({r1.Position.X:F3},{r1.Position.Y:F3},{r1.Position.Z:F3}) " +
|
||||
$"cell=0x{r1.CellId:X8} hit={r1.CollisionNormalValid} " +
|
||||
$"n=({r1.CollisionNormal.X:F2},{r1.CollisionNormal.Y:F2},{r1.CollisionNormal.Z:F2}) " +
|
||||
$"bodySliding={body.TransientState.HasFlag(TransientStateFlags.Sliding)}");
|
||||
|
||||
Assert.True(r1.Position.X < from.X - 0.3f,
|
||||
$"The westward boundary crossing onto the ramp must advance " +
|
||||
$"({from.X:F3} → {r1.Position.X:F3}, target {to.X:F3}); zero " +
|
||||
$"advance with the reversed-movement normal = the seam shake.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 2026-07-06 gate session repro (launch-137-corridor-gate.log): standing
|
||||
/// at (84.851, −39.764, −6.000) — the foot sphere already straddling the
|
||||
/// x=85 cell boundary by 0.33 m — the first move attempt toward
|
||||
/// (85.453, −39.782) blocked with the synthetic reversed-movement normal
|
||||
/// (−1.00, 0.03, −0.02), out==in, cp lost (cp=none), and repeated every
|
||||
/// frame (the "shaking at the seam" report). The deeper straddle start is
|
||||
/// what the original replay frame (84.638 → 85.253) didn't cover.
|
||||
/// </summary>
|
||||
[Fact(Skip = "#137 seam shake — deterministic RED repro; the post-step-up check-pos restore clobbers the lifted position (P2 stale-snapshot class). Un-skip with the fix.")]
|
||||
public void SeamCrossing_FromDeepStraddleStart_Advances()
|
||||
{
|
||||
var datDir = FindDatDir();
|
||||
if (datDir is null)
|
||||
{
|
||||
_out.WriteLine("SKIP: dat directory not found");
|
||||
return;
|
||||
}
|
||||
|
||||
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
||||
var engine = BuildCorridorEngine(dats);
|
||||
var body = GroundedBody();
|
||||
|
||||
var from = new Vector3(84.851f, -39.764f, -6.000f);
|
||||
var to = new Vector3(85.453f, -39.782f, -6.000f);
|
||||
|
||||
var r1 = Resolve(engine, body, from, to, SeamCellWest);
|
||||
_out.WriteLine($"r1: ok={r1.Ok} out=({r1.Position.X:F3},{r1.Position.Y:F3},{r1.Position.Z:F3}) " +
|
||||
$"cell=0x{r1.CellId:X8} hit={r1.CollisionNormalValid} " +
|
||||
$"n=({r1.CollisionNormal.X:F2},{r1.CollisionNormal.Y:F2},{r1.CollisionNormal.Z:F2}) " +
|
||||
$"bodySliding={body.TransientState.HasFlag(TransientStateFlags.Sliding)} " +
|
||||
$"bodyCpValid={body.ContactPlaneValid}");
|
||||
|
||||
Assert.True(r1.Position.X > from.X + 0.2f,
|
||||
$"The straddling-start seam crossing must advance " +
|
||||
$"({from.X:F3} → {r1.Position.X:F3}); zero advance with a " +
|
||||
$"reversed-movement normal = the 2026-07-06 seam shake.");
|
||||
}
|
||||
|
||||
[Fact(Skip = "#137 seam shake — deterministic RED repro; the post-step-up check-pos restore clobbers the lifted position (P2 stale-snapshot class). Un-skip with the fix.")]
|
||||
public void SeamCrossing_DoesNotPersistSyntheticSlidingNormal_AndRunContinues()
|
||||
{
|
||||
var datDir = FindDatDir();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue