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

7 KiB
Raw Blame History

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 timeSetTerrainClip(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.