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