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

119 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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).