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

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using AcDream.App.Input;
using AcDream.Core.Physics;
using DatReaderWriter.Enums;
using DatReaderWriter.Types;
@ -136,6 +137,137 @@ public class CellarUpTrajectoryReplayTests : IDisposable
// Tests
// ───────────────────────────────────────────────────────────────
/// <summary>
/// Indoor-flap root cause (2026-06-08). A body resting on the cellar
/// floor with ZERO requested motion must hold a byte-identical position
/// across many ticks — retail's authoritative local position is bit-stable
/// at rest (validate_transition → kill_velocity + SetWalkable on every
/// grounded contact, decomp :272567/:274467).
///
/// <para>
/// 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, from the player RenderPosition) varies.
/// RenderPosition = Lerp(_prevPhysicsPos, _currPhysicsPos), and Lerp(a,a,t)==a,
/// so a jittering eye at rest means the physics body's resting Position is not
/// bit-stable. Flat LandCell terrain rest IS bit-stable
/// (<see cref="AcDream.Core.Tests.Input.PlayerMovementControllerTests"/>.
/// Update_AtRestNoInput_RenderPositionBitStableAcrossManyFrames passes); the
/// instability is the INDOOR path — the floor-touch is classified
/// walkable=False (no walkable-polygon anchor), so each tick re-fires a
/// step-down probe whose re-found Z is not bit-stable.
/// </para>
///
/// PASSES — the indoor resting body is bit-stable even with the
/// grounded/cp=none contradictory state present. This is evidence (with the
/// flat-terrain variant) that the doorway flap is NOT a physics-rest jitter;
/// it is render-side portal-flood membership instability under a sweeping eye.
/// See docs/research/2026-06-08-flap-physics-diagnosis-REFUTED-its-render-membership.md.
/// The diagnostic log (on any future regression) names the failing per-tick
/// condition. Kept as a regression guard.
/// </summary>
[Fact]
public void IndoorCellarFloor_AtRestZeroOffset_BodyPositionBitStable()
{
var (engine, _) = BuildEngineWithCellarFixtures();
// Body seeded exactly at its natural resting pose on the cellar floor,
// WITH the walkable-polygon + contact-plane anchor (BuildInitialBody) —
// i.e. the most-favourable starting state. If even this drifts, the rest
// path fails to PERSIST the anchor.
var body = BuildInitialBody();
var rest = body.Position;
uint cell = CellarId;
bool grounded = true;
var log = new List<string>();
float maxDrift = 0f;
for (int tick = 1; tick <= 200; tick++)
{
// ZERO requested motion: currentPos == targetPos == rest pose.
var result = engine.ResolveWithTransition(
currentPos: body.Position,
targetPos: body.Position,
cellId: cell,
sphereRadius: SphereRadius,
sphereHeight: SphereHeight,
stepUpHeight: StepUpHeight,
stepDownHeight: StepDownHeight,
isOnGround: grounded,
body: body,
moverFlags: ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide,
movingEntityId: 0);
body.Position = result.Position;
cell = result.CellId;
grounded = result.IsOnGround;
float drift = (body.Position - rest).Length();
maxDrift = MathF.Max(maxDrift, drift);
if (tick <= 6 || drift > 0f)
{
log.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture,
"tick{0,3}: pos=({1:F7},{2:F7},{3:F7}) drift={4:F3}µm grounded={5} " +
"walkable={6} cpV={7} ts=0x{8:X}",
tick, body.Position.X, body.Position.Y, body.Position.Z,
drift * 1e6f, grounded, body.WalkablePolygonValid,
body.ContactPlaneValid, (uint)body.TransientState));
}
}
Assert.True(maxDrift == 0f,
$"cellar-floor rest drifted {maxDrift * 1e6f:F3} µm (expected byte-identical):\n "
+ string.Join("\n ", log.Take(24)));
}
/// <summary>
/// Indoor-flap investigation (2026-06-08) — the FULL production loop. Drives
/// <see cref="PlayerMovementController"/> (integration + flag logic + velocity,
/// not just the resolver) on the indoor cellar engine with NO input. PASSES —
/// the RenderPosition the camera reads is byte-identical at rest, confirming
/// the flap is not produced by the indoor controller rest loop. Kept as a
/// regression guard. See
/// docs/research/2026-06-08-flap-physics-diagnosis-REFUTED-its-render-membership.md.
/// </summary>
[Fact]
public void IndoorCell_FullController_AtRestNoInput_RenderPositionBitStable()
{
var (engine, _) = BuildEngineWithCellarFixtures();
var controller = new PlayerMovementController(engine);
controller.SetPosition(InitialSphereWorld, CellarId);
var settled = controller.Update(1f / 60f, new MovementInput());
var basePos = settled.Position;
var baseRender = settled.RenderPosition;
var log = new List<string>();
float maxPos = 0f, maxRender = 0f;
for (int i = 1; i <= 600; i++)
{
var r = controller.Update(1f / 60f, new MovementInput());
float dp = (r.Position - basePos).Length();
float dr = (r.RenderPosition - baseRender).Length();
maxPos = MathF.Max(maxPos, dp);
maxRender = MathF.Max(maxRender, dr);
if (i <= 4 || dp > 0f || dr > 0f)
{
log.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture,
"f{0,3}: pos=({1:F7},{2:F7},{3:F7}) render=({4:F7},{5:F7},{6:F7}) " +
"grounded={7} cell=0x{8:X8}",
i, r.Position.X, r.Position.Y, r.Position.Z,
r.RenderPosition.X, r.RenderPosition.Y, r.RenderPosition.Z,
r.IsOnGround, r.CellId));
}
}
Assert.True(maxPos == 0f && maxRender == 0f,
$"indoor controller rest drifted: pos={maxPos * 1e6f:F3} µm, "
+ $"render={maxRender * 1e6f:F3} µm (expected byte-identical):\n "
+ string.Join("\n ", log.Take(24)));
}
/// <summary>
/// Confirms the harness compiles, the engine runs the simulation,
/// and a trajectory comes back with the expected number of points.