diag(render): flap re-diagnosed as portal-flood re-clip DRIFT; physics + camera REFUTED

The 2026-06-08 AM "physics rest micro-jitter" diagnosis is refuted with primary
evidence (door-recheck 216K standstill records: 0 position re-snaps; player
byte-stable during the flap). Two adversarial verification sub-agents confirmed:

- Retail roots the render at the camera viewer_cell (swept from the player via
  SmartBox::update_viewer 0x453ce0; DrawInside(viewer_cell) 0x453aa0) and toggles
  DrawInside / LScape::draw -- so acdream's eye-cell rooting + inside/outside
  toggle are RETAIL-FAITHFUL. The locked-design "root at player cell" is wrong.
- The flap is render membership instability, eye-motion-driven: the visible-cell
  set oscillates (8<->3) as the eye sweeps monotonically. Root = the
  re-enqueue-on-growth DRIFT (PortalVisibilityBuilder.cs:322, MaxReprocessPerCell
  =16) re-clipping each grown cell every round -> sub-cm eye jitter flips membership.

Fix (spec, not yet implemented): verbatim port of retail's enqueue-once flood
(ConstructView + AddViewToPortals): enqueue once on first discovery, clip each
cell's portals once, union late growth in place (AddToCell) + draw-reorder
(FixCellList), never re-enqueue. Kills the drift; rooting/camera/seal untouched.

This commit lands VERIFIED GROUNDWORK + design only:
- spec: docs/superpowers/specs/2026-06-08-portal-flood-enqueue-once-port-design.md
- findings: docs/research/2026-06-08-flap-physics-diagnosis-REFUTED-its-render-membership.md
- [pv-input] probe gains rawPlayer + yaw (disambiguates the varying input)
- 4 GREEN physics rest-stability tests (prove rest is bit-stable -> flap not physics)
- apparatus: launch-flap-capture.ps1, analyze_flap_live.py, find_burst.py
- captured fixtures: tests/.../Fixtures/flap-doorway/0xA9B4017{0..5}.json

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-08 11:21:46 +02:00
parent d6aa526dd3
commit 6c3a96b26e
14 changed files with 8231 additions and 1 deletions

View file

@ -34,6 +34,97 @@ public class PlayerMovementControllerTests
Assert.Equal(96f, result.Position.Y, precision: 1);
}
// ── Indoor-flap root cause: resting-body bit-stability ────────────────────
//
// The indoor render "flap" (textures battling at the cottage doorway) is
// portal-flood membership instability. PortalVisibilityBuilder.Build is a
// proven-deterministic pure function, so the membership can only flip if its
// INPUT (the camera eye, derived from the player RenderPosition) varies.
// Live 6-dp capture (pvinput.log:54) shows the player RenderPosition carries
// a perpetual ~1-ULP flicker at rest (Z 94.000000 <-> 93.999992 — exactly one
// float mantissa step). ComputeRenderPosition is Vector3.Lerp(_prevPhysicsPos,
// _currPhysicsPos, alpha), and Lerp(a, a, t) == a exactly, so a jittering
// RenderPosition at rest means the physics body's resting Position is NOT
// bit-stable between ticks. Retail's authoritative local position is bit-stable
// at rest (validate_transition -> kill_velocity on every grounded contact), so
// retail never flaps.
//
// This test pins the physics-side invariant: a grounded body with no input
// must hold a byte-identical position across many frames. It PASSES — which
// is itself the evidence: the physics resting position is bit-stable, so the
// doorway flap is NOT a physics-rest jitter. See
// docs/research/2026-06-08-flap-physics-diagnosis-REFUTED-its-render-membership.md
// (the flap is render-side portal-flood membership instability at the grazing
// doorway portal under a sweeping camera eye). Kept as a regression guard.
[Fact]
public void Update_AtRestNoInput_RenderPositionBitStableAcrossManyFrames()
{
var engine = MakeFlatEngine();
var controller = new PlayerMovementController(engine);
var rest = new Vector3(96f, 96f, 50f);
controller.SetPosition(rest, 0x0001);
// Settle one frame so the resolver establishes its rest state, then
// capture the baseline the body must hold.
var settled = controller.Update(1f / 60f, new MovementInput());
Vector3 baselineRender = settled.RenderPosition;
Vector3 baselinePhysics = settled.Position;
// Hold still for ~10 s of 60 Hz frames (crosses MinQuantum every ~2
// frames, so the 30 Hz physics tick fires throughout — same cadence as
// live). Any deviation, even one ULP, is the flap's root cause.
float maxRenderDev = 0f;
float maxPhysicsDev = 0f;
for (int i = 0; i < 600; i++)
{
var r = controller.Update(1f / 60f, new MovementInput());
maxRenderDev = MathF.Max(maxRenderDev, (r.RenderPosition - baselineRender).Length());
maxPhysicsDev = MathF.Max(maxPhysicsDev, (r.Position - baselinePhysics).Length());
}
Assert.True(
maxRenderDev == 0f && maxPhysicsDev == 0f,
$"resting body drifted: render={maxRenderDev * 1e6f:F3} µm, " +
$"physics={maxPhysicsDev * 1e6f:F3} µm; expected byte-identical rest");
}
// After walking then releasing input, the body must SETTLE to a
// byte-identical resting position — not keep blipping a residual velocity.
// This models the live flap: the player walks to the cottage doorway and
// stops, and the eye then carries a ~1-ULP jitter that flips portal-flood
// membership. Flat-terrain variant: if even this drifts, the residual-after-
// motion path is the root and it is not indoor-specific.
[Fact]
public void Update_WalkThenStop_SettlesToBitStableRest()
{
var engine = MakeFlatEngine();
var controller = new PlayerMovementController(engine);
controller.SetPosition(new Vector3(96f, 96f, 50f), 0x0001);
controller.Yaw = 0f;
// Walk forward ~0.5 s, then release.
for (int i = 0; i < 30; i++)
controller.Update(1f / 60f, new MovementInput(Forward: true));
// Let velocity decay / state settle.
for (int i = 0; i < 30; i++)
controller.Update(1f / 60f, new MovementInput());
var settled = controller.Update(1f / 60f, new MovementInput());
Vector3 basePos = settled.Position;
Vector3 baseRender = settled.RenderPosition;
float maxPos = 0f, maxRender = 0f;
for (int i = 0; i < 600; i++)
{
var r = controller.Update(1f / 60f, new MovementInput());
maxPos = MathF.Max(maxPos, (r.Position - basePos).Length());
maxRender = MathF.Max(maxRender, (r.RenderPosition - baseRender).Length());
}
Assert.True(maxPos == 0f && maxRender == 0f,
$"post-walk rest drifted: pos={maxPos * 1e6f:F3} µm, render={maxRender * 1e6f:F3} µm");
}
[Fact]
public void Update_ForwardInput_MovesInFacingDirection()
{