acdream/docs/research/2026-06-08-flap-rootcause-physics-rest-handoff.md
Erik d6aa526dd3 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>
2026-06-08 09:16:12 +02:00

8.1 KiB
Raw Permalink Blame History

Handoff — the indoor FLAP traced to a physics rest µm-jitter; prior diagnoses REFUTED — 2026-06-08

CANONICAL PICKUP for the indoor render flap. This session refuted the 2026-06-07 cutover-flip diagnosis AND an enqueue-once attempt, confirmed the real mechanism with primary evidence, and traced the root all the way down to a physics resting-position µm jitter. The fix is in physics (rest-stability), is teed up, and needs one more higher-precision trace to pin the exact cause before porting. Spec: docs/superpowers/specs/2026-06-08-portal-flood-membership-stability-design.md (its §4 enqueue-once design is REFUTED — see §3 here; its §6 physics contingency is now the active direction).


1. What the flap IS (confirmed, primary evidence)

At the Holtburg cottage doorway threshold, the portal-visibility flood set oscillates frame-to-frame (ids=[0170,0171,0172,0173,0174,0175][0170,0171], i.e. 6↔2 cells) from a stable viewer cell (root=0xA9B40170, outRoot=n). The deep 0172-0175 cluster pops in/out → textures "battle."

  • It is NOT see-through walls from outside (standing outside with the door closed is stable — user visual gate), NOT the outdoor node, NOT a root toggle, NOT nondeterminism.
  • PortalVisibilityBuilder.Build is a pure deterministic function (proved by PortalVisibilityBuilderTests.Build_IsDeterministic_*, passes). So the flip requires a varying input.
  • The high-precision [pv-input] probe (6 dp) shows the camera eye AND the player RenderPosition carry perpetual ~18 µm float jitter at rest (e.g. player Z 94.000000 ↔ 94.000008). At the threshold a grazing portal's clip is so knife-edge that this µm jitter flips its empty/non-empty result → the flood membership flips → the flap.

Mechanism chain: physics resting position blips ~µm → ComputeRenderPosition Lerp surfaces it as µm eye jitter → the portal-flood clip (clip-non-empty membership) is µm-sensitive at the grazing threshold portal → flips → flap. Retail is flap-free because its authoritative local position is bit-stable at rest (so its same clip-non-empty membership never crosses the boundary).

2. REFUTED — the 2026-06-07 cutover-flip diagnosis (do NOT act on its F1/F2)

docs/research/2026-06-07-cutover-flip-render-residuals-diagnosis-handoff.md is wrong on its load-bearing claims (primary evidence in this session):

  • "See-through from outside" — not reproduced (outside, door closed, is stable).
  • "Walls ARE the EnvCell shells; ModelId is a partial frame" — refuted: the cottage ModelId GfxObj 0x01000A2B is a full closed exterior (76 render polys, bbox 20×18×10.4 m, 46 outward-facing walls + roof — tools/A8CellAudit gfxobj 0x01000A2B). EnvCell shells are interior-facing. F2 (EnvCell back-faces) targets the wrong geometry.
  • "Oscillation = outdoor-node flood (1↔13)" — corrected: it is the indoor flood, stable root, 2↔6. F1 targeted the wrong root.
  • "branch=RetailPViewInside every frame proves the flap is gone" — tautological (post-flip clipRoot = viewerRoot ?? _outdoorNode is ~never null, so branch can't report OutdoorRoot).

3. REFUTED — enqueue-once traversal (TDD caught it)

Hypothesis: the flap is acdream's MaxReprocessPerCell re-enqueue drift; restore retail's enqueue-once (first-discovery only, no re-enqueue). Refuted: retail does NOT stop at first discovery — its AddViewToPortals growth branch calls AddToCell (decomp :433494), so a cell's later-grown view IS propagated (late slices reach exit portals). The existing test PortalVisibilityBuilderTests.Build_ViewGrowthAfterDoneCell_PropagatesNewSlicesToExit encodes exactly this retail behavior; enqueue-once broke it. The change + its test were reverted (tree clean, 27 portal tests green). The divergence is the re-clip DRIFT, not the propagation — and underneath, the flap is the µm input jitter (which removing the drift would only reduce, not eliminate; Build is deterministic so only a bit-stable INPUT guarantees no flap).

4. The root — physics resting position not bit-stable

PlayerMovementController.ComputeRenderPosition (line 810): Vector3.Lerp(_prevPhysicsPos, _currPhysicsPos, alpha). Lerp(a, a, t) == a exactly, so the µm RenderPosition jitter means _prev != _curr — the physics body's resting position blips ~µm between ticks. Retail's kill_velocity (OBJECTINFO::kill_velocity = set_velocity(0), decomp :274467) is called by validate_transition (:272567) on every grounded collision/slide with a valid contact plane, keeping rest bit-stable.

acdream rest path:

  • calc_acceleration (PhysicsBody.cs:191) zeroes gravity only when Contact && OnWalkable && !Sledding.
  • UpdatePhysicsInternal (PhysicsBody.cs:352) skips position integration when velocity <= 0.
  • Player flags set per tick in PlayerMovementController (1271-1301): Contact|OnWalkable only when resolveResult.IsOnGround && Velocity.Z <= 0; else cleared → gravity.
  • acdream's kill_velocity (PhysicsEngine.cs:837) is narrower than retail's — fires only on ObjectInfo.VelocityKilled (the airborne steep-roof/wall reset), NOT on every grounded contact.

So at a clean rest the position is bit-stable; the blip is an intermittent failure (a stray gravity tick / µm velocity residual / contact-plane not re-established). The [resolve] probe (3 dp) shows the body stable to mm at spawn rest (94.000 repeated) — confirming the blip is sub-mm, below that probe's precision — and shows groundedIn=True but walkable=False cp=none (no contact plane established at rest), a lead toward the Contact/contact-plane path.

5. NEXT STEPS (the physics rest-stability fix)

  1. Higher-precision physics rest trace (REQUIRED before fixing). The 3-dp [resolve] probe is too coarse. Add a 6-dp per-tick probe of the resting body: _body.Position, Velocity, Acceleration, TransientState (Contact/OnWalkable), resolveResult.IsOnGround, contact-plane valid. Launch, let the character sit at spawn (no input needed — autonomous), capture ~10 s, and find the tick where the position blips µm and which condition failed (gravity applied? velocity residual? resolve re-snap? Contact cleared?).
  2. Port the retail-faithful rest-stability fix for the pinned cause — most likely one of: (a) broaden kill_velocity to match retail's validate_transition (zero velocity on every grounded contact with a valid contact plane, :272567); (b) ensure the Contact flag / contact plane is re-established on the zero-distance rest sweep so calc_acceleration keeps gravity off; (c) a retail-faithful "supported body at rest is frozen" (skip integration/resolve when grounded + zero velocity + no movement input). TDD: a test asserting the resting body position is bit-stable across N ticks with no input.
  3. Visual gate at the cottage doorway threshold: hold still — the 2↔6 oscillation is gone (re-run [pv-input]/[render-sig], flood ids= constant at rest).

DO NOT RETRY: the overlap-predicate render band-aid (rejected by user — not retail); enqueue-once (refuted, §3); any render-side debounce/grace (forbidden).

6. Apparatus (committed this session) + state

  • Keep (real regression value): PortalVisibilityBuilderTests.Build_IsDeterministic_* (proves Build deterministic); tools/A8CellAudit gfxobj mode (dumps render geometry — used to refute the ModelId claim).
  • Diagnostic probes (env-gated, inert off; KEEP for the physics trace + flap visual gate, strip after the fix ships): [pv-input] (ACDREAM_PROBE_PVINPUT, 6-dp Build inputs + flood count, RenderingDiagnostics + GameWindow); the outRoot=/bshell= fields on [render-sig]; launch-pvinput.ps1, launch-bshell-probe.ps1, launch-resolve.ps1.
  • Tree: PortalVisibilityBuilder.cs reverted to the re-enqueue (no functional change shipped). Build green; App.Tests green (portal-visibility 27/27).
  • Memory to update: project_indoor_flap_rootcause (root is the physics rest µm-jitter, not the render diagnosis or enqueue-once).