From ef2186147d38d0da467bfd4cfa6e23df149aefc6 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 7 Jun 2026 21:49:00 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20cutover=20flip=20shipped=20=E2=80=94=20?= =?UTF-8?q?see-through=20+=20oscillation=20DIAGNOSED=20(evidence-based=20h?= =?UTF-8?q?andoff)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flip killed the branch-toggle flap (one path, zero OutdoorRoot frames). It exposed two residuals now PROVEN via a live [bshell] probe, not guessed: (1) oscillation = the outdoor-node flood membership swings 1<->~13 building cells frame-to-frame, so the walls (EnvCell shells) blink; (2) see-through = EnvCell wall polys are single-sided for SidesType==CounterClockwise, so from outside you see their culled back. The ModelId building shells DO render (6/6 with mesh) but are a partial frame, not the walls — the skip-all-interiors experiment proved the walls are the EnvCell shells. Fixes identified (stabilise flood + build back faces) but not implemented; full do-not-retry list + open pre-flip-reconciliation question in the doc. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...flip-render-residuals-diagnosis-handoff.md | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 docs/research/2026-06-07-cutover-flip-render-residuals-diagnosis-handoff.md diff --git a/docs/research/2026-06-07-cutover-flip-render-residuals-diagnosis-handoff.md b/docs/research/2026-06-07-cutover-flip-render-residuals-diagnosis-handoff.md new file mode 100644 index 00000000..4e806859 --- /dev/null +++ b/docs/research/2026-06-07-cutover-flip-render-residuals-diagnosis-handoff.md @@ -0,0 +1,125 @@ +# Handoff — Cutover FLIP shipped; see-through + oscillation DIAGNOSED (evidence-based) — 2026-06-07 (PM) + +> **CANONICAL PICKUP for the render-unification residuals.** Worktree `thirsty-goldberg-51bb9b`, +> branch `claude/thirsty-goldberg-51bb9b`, HEAD `774cb22`. The cutover flip is SHIPPED (one render +> path, no branch-toggle flap). It exposed two residuals — **see-through building walls** and +> **oscillation** — whose root causes are now PROVEN with a live probe (not guessed). The fixes are +> identified but NOT yet implemented. Read §3 (diagnosis) and §5 (do-not-retry) before touching code. + +--- + +## 1. What shipped (committed, keep) + +The CUTOVER FLIP from `2026-06-07-render-unification-cutover-flip-handoff.md` landed: + +| Commit | What | +|---|---| +| `5379f6e` | Step A — `PortalVisibilityBuilder.Build` seeds a full-screen `OutsideView` when the root is the outdoor node (`LoadedCell.IsOutdoorNode`, set by `OutdoorCellNode.Build`). +2 UnifiedFloodTests, +2 flag assertions. | +| `445e861` | Step B — the flip: `GameWindow.cs:~7387` `clipRoot = viewerRoot ?? _outdoorNode`. Drops the `playerIndoorGate` gate. ONE path, no inside/outside branch. Preserves the `LiveDynamic` draw for the outdoor root. | +| `88caa0d` | depth-clear fix — `ClearDepthSlice = null` for the outdoor root (the full-screen depth clear was painting the cellar over the player; fixed). | +| `774cb22` | Revert of `0030dac` (the slot-0 skip — a FAILED fix, see §5). | + +**The flip's PRIMARY goal succeeded:** `[render-sig]` shows `branch=RetailPViewInside` every frame, +**zero `OutdoorRoot` frames** across a whole session. The two-branch-toggle flap is gone by +construction. Baselines: build green, App.Tests 216/0. + +--- + +## 2. The two residuals the flip exposed (user-observed) + +1. **See-through building walls from outside** — standing outside a building you see *into* it through + the walls (doors closed). +2. **Oscillation** — the interior/walls flicker between "showing nothing", "see-through", and "full + interior" frame-to-frame while standing still. + +--- + +## 3. ROOT CAUSE — proven by a live `[bshell]` probe (NOT guessed) + +A throttled probe in `RetailPViewRenderer.DrawInside` (now stripped; re-add from git history of this +doc's session if needed) logged, for the outdoor-node root on a loaded frame at the Holtburg cottage: + +``` +[bshell] total=6 withMesh=6 inOutdoorPartition=6 envCellsFlooded=1 outdoorEntities=637 +``` + +Interpretation (each number is decisive): + +- **`total=6 withMesh=6 inOutdoorPartition=6`** — there ARE 6 building `ModelId` "shell" entities + (`WorldEntity.IsBuildingShell`, created in `LandblockLoader.cs:75-91` from `LandBlockInfo.Buildings[].ModelId`), + ALL carry meshes, ALL land in `partition.Outdoor` (they have `ParentCellId==null`; + `InteriorEntityPartition` line 47 → Outdoor; `WbDrawDispatcher.EntityPassesVisibleCellGate` returns + `true` for null `visibleCellIds`). **So the `ModelId` exterior DOES render.** +- **BUT** the earlier "skip all interior shell draws for the outdoor root" experiment (uncommitted, + reverted) made the building **fully see-through** — i.e. drawing ONLY the `ModelId` shells is NOT a + solid building. **Therefore the `ModelId` Setup is a partial frame, and the building's actual WALLS + are the EnvCell shell geometry** (`ObjectMeshManager.PrepareCellStructMeshData`, drawn by + `DrawEnvCellShells`). +- **`envCellsFlooded=1`** — in this frame the outdoor-node flood reached **ZERO** building interior + cells (only the node itself). Earlier `[render-sig]` frames at the same spot showed `ids=[node + ~12 + building cells]` (≈13). **So the flood membership swings between 1 and ~13 frame-to-frame.** + +### The two residuals, explained +1. **Oscillation = flood instability gating the walls.** The flip made wall-drawing depend on the + portal flood reaching each building's interior cells. That flood is unstable (1 ↔ ~13), so the + EnvCell walls blink in and out. ("showing nothing" = flood=1, no interior; "full interior / + see-through" = flood reached the building.) +2. **See-through = single-sided EnvCell walls.** Even when the walls DO draw, the EnvCell wall polys + are single-sided for `SidesType==CounterClockwise` (interior-facing). `PrepareCellStructMeshData` + (ObjectMeshManager ~1299-1310) builds the back face only for `SidesType==None` (front twice + reversed) and `SidesType==Clockwise` (neg surface). A `CounterClockwise` wall = front face only → + from outside you see its culled back → see-through. + +--- + +## 4. Fix path (identified, NOT implemented) + +Two independent fixes, both needed: + +- **F1 — Stabilise the flood membership** so a building's interior cells are CONSISTENTLY in/out of + the visible set (no 1↔13 swing). This is the same metastability family as the indoor flicker. Likely + levers: ground the outdoor-node flood's building membership in the cell `stab_list`/PVS (stable, + precomputed) instead of the per-frame portal-side test + projection; or hysteresis on which buildings + are flooded. Probe to re-add: `envCellsFlooded` per frame (RLE it; it should be constant when standing + still). +- **F2 — Make the EnvCell walls solid from outside.** Either build the missing back faces for + `SidesType==CounterClockwise` walls in `PrepareCellStructMeshData`, or render those shells + double-sided (`CullMode.None`) when the viewer is outside the cell. Verify against retail: dump a real + Holtburg cell's wall-poly `SidesType` distribution first. + +**Open research question (reconcile before F2):** pre-flip the buildings looked SOLID from outside. +What drew the solid walls pre-flip — a global EnvCell-shell render, the `DrawPortal`/`BuildFromExterior` +look-in, or were the `ModelId` shells solid then? Find what the flip replaced. The old outdoor `else` +block (`GameWindow.cs:~7557-7663`, now dead-when-clipRoot-non-null but still present) is the place to +read. This answers whether F2 is "build back faces" or "restore a pre-flip draw". + +--- + +## 5. DO NOT RETRY (failed this session, with evidence) + +- **Slot-0 skip** (`0030dac`, reverted `774cb22`): "for the outdoor root, skip flooded cells whose + clip degenerated to no-clip slot 0." Made the oscillation WORSE — slot-0-ness flickers per frame, so + cells blinked. Wrong: the see-through is not the slot-0 fallback. +- **Skip-all-interiors experiment** (uncommitted, reverted): "outdoor root draws terrain + ModelId + exteriors only, no EnvCell shells." Made buildings FULLY see-through + flashing — proved the `ModelId` + Setup is not the walls (the walls are the EnvCell shells). Do not ship this. +- **Backface-culling-of-shells hypothesis** (never coded): plausible but the cull mode is already + data-driven (`poly.SidesType`); the real gap is single-sided geometry (no back face built), not a + cull-state bug. +- The subagent hypothesis "ModelId exterior occludes; interior overdraws it; fix = gate DrawEnvCellShells + off for the outdoor root" is **disproven** — that gate IS the skip-all-interiors experiment, which + removed the walls entirely. + +--- + +## 6. State + how to resume +- HEAD `774cb22`, tree clean, build green, App.Tests 216/0. The flip + depth-clear are committed; the + branch renders with the two residuals (see-through + oscillation). +- The flip is on this BRANCH only (main is unaffected). To get a stable client meanwhile, revert the + flip commits (`445e861` Step B is the behaviour change; reverting it alone restores the pre-flip + outdoor path — verify Step A `5379f6e` is inert without it). +- Re-add the `[bshell]` / `envCellsFlooded` probe (see this session's git reflog for the exact code) to + watch flood stability while working F1. +- Memory: `project_indoor_flap_rootcause` (update with this corrected diagnosis), + `reference_render_pipeline_state`, `feedback_render_downstream_of_membership` (the oscillation IS a + membership/flood-stability bug, per that note).