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) <noreply@anthropic.com>
7.7 KiB
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, branchclaude/thirsty-goldberg-51bb9b, HEAD774cb22. 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)
- See-through building walls from outside — standing outside a building you see into it through the walls (doors closed).
- 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 buildingModelId"shell" entities (WorldEntity.IsBuildingShell, created inLandblockLoader.cs:75-91fromLandBlockInfo.Buildings[].ModelId), ALL carry meshes, ALL land inpartition.Outdoor(they haveParentCellId==null;InteriorEntityPartitionline 47 → Outdoor;WbDrawDispatcher.EntityPassesVisibleCellGatereturnstruefor nullvisibleCellIds). So theModelIdexterior 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
ModelIdshells is NOT a solid building. Therefore theModelIdSetup is a partial frame, and the building's actual WALLS are the EnvCell shell geometry (ObjectMeshManager.PrepareCellStructMeshData, drawn byDrawEnvCellShells). 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 showedids=[node + ~12 building cells](≈13). So the flood membership swings between 1 and ~13 frame-to-frame.
The two residuals, explained
- 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.)
- 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 forSidesType==None(front twice reversed) andSidesType==Clockwise(neg surface). ACounterClockwisewall = 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:envCellsFloodedper 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==CounterClockwisewalls inPrepareCellStructMeshData, 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-polySidesTypedistribution 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, reverted774cb22): "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
ModelIdSetup 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 (
445e861Step B is the behaviour change; reverting it alone restores the pre-flip outdoor path — verify Step A5379f6eis inert without it). - Re-add the
[bshell]/envCellsFloodedprobe (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).