diag(render/physics): flap root-caused to physics rest µm-jitter; refute prior diagnoses

Apparatus + handoff for the indoor flap. Confirmed (primary evidence): the flap is the
portal-flood clip being µm-sensitive at the threshold, driven by a ~1-8µm jitter in the
player RenderPosition (physics resting position not bit-stable; Lerp surfaces it). REFUTES
the 2026-06-07 see-through/EnvCell/outdoor-node diagnosis (ModelId GfxObj 0x01000A2B IS the
solid exterior) AND an enqueue-once attempt (retail propagates late slices via AddToCell;
the existing PropagatesNewSlicesToExit test caught it; reverted). Adds: Build determinism
test, A8CellAudit gfxobj dump, [pv-input] 6dp probe + [render-sig] outRoot/bshell fields.
No functional fix shipped. Next: higher-precision physics rest trace -> port retail
kill_velocity/contact rest-stability. Canonical: docs/research/2026-06-08-flap-rootcause-physics-rest-handoff.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-08 09:16:12 +02:00
parent d0b65c4170
commit d6aa526dd3
6 changed files with 300 additions and 1 deletions

View file

@ -58,6 +58,38 @@ public class PortalVisibilityBuilderTests
$"OutsideView width {outsideWidth} should be a sliver, far less than full window {windowOnlyWidth}");
}
[Fact]
public void Build_IsDeterministic_IdenticalInputsGiveIdenticalVisibleSet()
{
// Flap root-cause apparatus (2026-06-07): the live threshold flap shows OrderedVisibleCells
// flipping 2<->6 at an eye+player identical to cm. Build is a pure static function with
// all-fresh per-call state, so identical inputs MUST yield an identical visible set. If this
// FAILS, the flap is a determinism bug INSIDE Build; if it PASSES (expected), the live flip is
// sub-cm INPUT jitter and the fix must make membership robust, not Build deterministic.
// Exercises the re-enqueue fixpoint via a diamond: 0x0004 is reached from BOTH 0x0002 and 0x0003.
var cam = Cell(0x0001,
new CellPortalInfo(0x0002, 0, 0, 0),
new CellPortalInfo(0x0003, 1, 0, 0));
cam.PortalPolygons.Add(QuadX(-0.6f, -0.05f, -3f)); // left -> 0002
cam.PortalPolygons.Add(QuadX(0.05f, 0.6f, -3f)); // right -> 0003
var left = Cell(0x0002, new CellPortalInfo(0x0004, 0, 0, 0));
left.PortalPolygons.Add(QuadX(-0.6f, -0.05f, -6f));
var right = Cell(0x0003, new CellPortalInfo(0x0004, 0, 0, 0));
right.PortalPolygons.Add(QuadX(0.05f, 0.6f, -6f));
var back = Cell(0x0004, new CellPortalInfo(0xFFFF, 0, 0, 0));
back.PortalPolygons.Add(QuadX(-0.6f, 0.6f, -9f));
var all = new Dictionary<uint, LoadedCell>
{ [0x0001] = cam, [0x0002] = left, [0x0003] = right, [0x0004] = back };
var a = Build(cam, all);
var b = Build(cam, all);
Assert.Equal(a.OrderedVisibleCells, b.OrderedVisibleCells);
Assert.Equal(
a.CellViews.Keys.OrderBy(k => k).ToArray(),
b.CellViews.Keys.OrderBy(k => k).ToArray());
}
[Fact]
public void Build_EyeStandingInInteriorPortal_FloodsNeighbour()
{