acdream/docs/research/2026-06-09-flap-outdoor-fullworld-building-flood-merge-handoff.md
Erik d877e4329a docs: §4 outdoor full-world flap — onset pinned to building-flood merge (handoff)
Evidence-chain handoff for the outdoor flap investigation: frame-exact
onset (pv-input flood 1->5 + the gl-state doorway-box fingerprint, same
frame), the full probe exoneration chain (camera matrix NaN-free at
6 dp, eye above terrain, cross-frame GL leak refuted, full-screen quad
planes can't cull, MergeNearbyBuildingFloods doesn't touch OutsideView,
ClipFrame capacity clean), the two surviving kill-mechanism suspects
(per-instance clip-slot routing under outdoor roots / terrain UBO
content at draw time), the decisive [clip-route] probe spec, the
user-validated repro protocol, and the probe-semantics gotchas.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 08:19:54 +02:00

105 lines
7 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 — §4 outdoor FULL-WORLD flap: onset pinned to the building-flood merge
**Date:** 2026-06-09 (late evening). **Branch:** `claude/thirsty-goldberg-51bb9b`, HEAD `fafe5d6`.
**Status:** trigger pinned frame-exact; kill mechanism NOT yet pinned — one purpose-built
probe (or RenderDoc) decides it. Read this top-to-bottom before touching code.
---
## 0. TL;DR
1. **The user-visible bug:** standing/running OUTDOORS at specific spots, the WHOLE world
(terrain + buildings + entities + the player model + sky) drops to the fog-tinted clear
color. It strobes at onset then HOLDS; rotating the camera pops it back; walking
forward through the trigger zone reproduces it; sidestep/backwards through the same
zone does not. Confirmed distinct from #106 (membership healthy throughout).
2. **Frame-exact onset (the day's key result):** the flap begins at EXACTLY the frame the
nearby cottage's per-building flood merges into the outdoor root —
`[pv-input] flood=1 → flood=5` with player/yaw frozen and only the camera boom settling
(~3 cm/frame). Same frame, the `[gl-state]` tripwire shows the leftover scissor box
flip from full-screen to a **drifting 9×21 px doorway footprint** (the cottage doorway
projected to screen, moving with the eye's micro-settle). Onset evidence:
`flap-glstate-capture.log` (gl-state frame 4977 = log line 5006); the same transition
with full render-sig fields: `flap-residual-capture.log` (38 such transitions, e.g.
frame 1745→1746: ONLY `ids=`/`draw=` gain a cell — every other field identical,
`out=10529` instances submitted in BOTH frames).
3. **Massive exoneration chain (all probe-verified, do not re-tread):** membership/root/
viewer cell stable; `res=None`; camera view-projection matrix sane and NaN-free
(11,767 frames, 6 dp); eye 1.62.1 m ABOVE terrain (buried-eye refuted); flood/
outPolys/outSlices/outMode constant; full-screen quad clip planes mathematically
cannot cull; `MergeNearbyBuildingFloods` does NOT touch OutsideView; cross-frame GL
state leak refuted (`[gl-state]` shows scissor test OFF + sane depth/blend/cull/vp/fbo
entering every frame, `err=0x0`); `ClipFrameAssembler` slices carry their own plane
arrays (slot repacking can't swap planes per se); `ClipFrame.AppendSlot/UploadShared`
have dynamic capacity + full re-upload (no overflow).
4. **What remains (the kill mechanism, one of):**
a. **Per-instance clip-slot routing** (`WbDrawDispatcher.SetClipRouting` +
`ResolveEntitySlot`, binding=3 slot SSBO): the landscape slice installs routing
UNCONDITIONALLY (`RetailPViewRenderer.cs:215`) even for OUTDOOR roots, while the
U.4 contract (`WbDrawDispatcher.cs:309-331`) says outdoor frames should
`ClearClipRouting`. When the flood merges, `CellIdToSlot` gains cells and slot
indices REPACK (cells pack before the outside view in the assembler) — if any
instance's slot resolution or the slot SSBO content goes stale/wrong, the world's
instances clip against the cottage's doorway planes (or CULL).
b. **Terrain/sky UBO content at draw time**`SetTerrainClip(slice.Planes)` should
hold the full-screen planes; if the merge path overwrites it with doorway planes
(or count) before terrain samples it, terrain + sky die together.
c. Something in the per-slice draw orchestration (`DrawLandscapeThroughOutsideView`,
`RetailPViewRenderer.cs:208-230`) that behaves differently when cell slices exist.
5. **Why this matters beyond the spot:** the same merge boundary is crossed every time
you run past cottages (the original "parts of the screen flash while running") and at
cottage enter/exit — this is very likely THE remaining §4 visible flap, with the
edge-on doorway grey (2a) and corner seal (2b) as siblings in the same family.
## 1. The decisive next probe (do this first)
Add a `[clip-route]` print-on-change probe (gate: reuse `ACDREAM_PROBE_GLSTATE` or a new
var) emitting, per frame:
- In `RetailPViewRenderer.DrawLandscapeThroughOutsideView`: `slice.Slot`, `slice.Planes`
values (all 48 vec4s), `clipAssembly.CellIdToSlot` contents, and the first 16 bytes of
`_clipFrame`'s terrain bytes (the count + first plane) as uploaded.
- In `WbDrawDispatcher.Draw` (when routing active): a histogram of `ResolveEntitySlot`
outcomes — instances per slot index + CULL count.
One repro run (the user triggers the flap, holds 3 s, rotates, closes) then diff the
held-flap frames vs healthy frames. Whichever of (a)/(b) shows wrong values is the bug.
If BOTH look correct → RenderDoc frame capture during the flap (the GPU truth).
## 2. Repro protocol (user-validated, fast)
Spot: Holtburg south slope, player ≈ (167169, 31..37) world frame (A9B4 anchor), the
slope SE of town with the A9B3 cottage. Walk FORWARD downhill through the zone → strobe →
hold. Rotate camera → recovers. `eyeAbove` stays ~+2 m (do not chase buried-eye).
Launch with `ACDREAM_PROBE_PVINPUT=1` + `ACDREAM_PROBE_GLSTATE=1` (+ the new probe);
AVOID `ACDREAM_PROBE_FLAP` for visual judgment runs (timing skew — render digest landmine).
## 3. Evidence inventory (this session's captures, worktree root, untracked)
| File | What it holds |
|---|---|
| `flap-residual-capture.log` | Full flap probes; 38 flood-merge transitions w/ render-sig field diffs; the held-flap [flap-cam]/[flap] stability blocks |
| `flap-pvinput-capture2.log` | 11,767 pv-input frames; NaN-free matrix proof; held blocks at 6 dp |
| `flap-eyeterr-capture.log` | eyeAbove (terrain-burial refutation) |
| `flap-glstate-capture.log` | GL-state tripwire; frame-exact onset marker (frame 4977) + doorway-box fingerprint |
## 4. DO-NOT-RETRY additions from this session (full chain in §0.3)
- Camera matrix NaN / degenerate orientation — REFUTED (6 dp capture).
- Eye buried in terrain — REFUTED (`eyeAbove` +1.6..2.1 m).
- Cross-frame GL scissor/depth/blend leak — REFUTED (`[gl-state]` stable + scis=0 entering frames).
- Full-screen outdoor quad plane collapse / winding — impossible (static CCW NDC quad).
- `MergeNearbyBuildingFloods` contaminating OutsideView — code-verified NOT (explicitly skipped).
- `ClipFrame` slot-capacity overflow — dynamic capacity, full re-upload, code-verified.
- The earlier "stale render anchor explains the running distortion" attribution — PARTIAL
only; #106 fixed the anchor, this flap persists (correction noted in the #105/#106 docs).
## 5. Probe-semantics gotchas (cost an hour today)
- `[render-sig]`'s `terrain=` prints a STALE pre-DrawInside local (always `Skip` when a
clipRoot exists); the REAL assembler mode is `[flap-cam]`'s `terrain=` and render-sig's
`outMode=`. Don't diff the wrong field.
- `[render-sig]` prints on signature change only; the EYE coords are part of the
signature → a settling boom spams lines with nothing else changing.
- `Tee-Object` writes UTF-16LE; Python analyzers must BOM-detect (`b'\xff\xfe'`).
- The first pv-input run produced 0 lines because the client was closed pre-entry —
check `auto-entered player mode` exists before analyzing.