acdream/docs/research/2026-06-07-indoor-render-session-handoff.md
Erik 1405dd8e90 feat(render): indoor render WORKS — terminating portal flood + every-cell seal + look-in FPS
Checkpoint of the unified retail-faithful indoor render. The two-week HANG/grey is fixed and the
interior seals (live-verified by the user). Commits the session render-rewrite foundation together
with the fixes that made it functional.

- HANG fix: PortalVisibilityBuilder.Build portal flood did not terminate (the faithful ProjectToClip
  near-side clip drifts per round, defeating the CellView dedup; the BFS had no bound after U.2a removed
  MaxReprocessPerCell). Fix = drift-tolerant snapped/canonical CellView.Add dedup (PortalView.cs) plus
  restored MaxReprocessPerCell=16 bounded re-enqueue (PortalVisibilityBuilder.cs). Re-enqueue is kept
  (load-bearing for late-slice propagation, Build_ViewGrowthAfterDoneCell_PropagatesNewSlicesToExit);
  only its count is capped. CellViewDedupTests added.
- Seal (DrawCells Task 2): RetailPViewRenderer.DrawEnvCellShells draws EVERY visible cell via
  IndoorDrawPlan.ShellPass (was gated on the ClipFrameAssembler slot filter, leaving slot-less cells grey).
- Look-in FPS: GameWindow exterior look-in candidates limited to the player landblock +-1 (was all ~81
  loaded LBs iterated every outdoor frame). No behaviour change (far cells were >48m, already culled).

Remaining dominant issue = the FLAP at transitions: viewer-cell metastability (render roots at the
camera-eye cell, which oscillates outdoor-indoor as the 3rd-person boom drifts across the doorway,
confirmed in render-sig). SEPARATE fix, NOT the DrawCells port. Full handoff + flap fix plan + tracked
follow-ups (#78 terrain, look-in-from-inside, look-in FPS, L-spotlight):
docs/research/2026-06-07-indoor-render-session-handoff.md.

Baselines: build 0 err; App.Tests 210/210; Core.Tests 1331 pass / 4 fail (pre-existing) / 1 skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 10:14:43 +02:00

12 KiB
Raw Permalink Blame History

Indoor Render — Session Handoff: HANG fixed + interior SEALS; the FLAP is next — 2026-06-07

Worktree thirsty-goldberg-51bb9b, branch claude/thirsty-goldberg-51bb9b. PowerShell on Windows; launch logs UTF-16; build before launch; acceptance is the user's eyes. Live ACE 127.0.0.1:9000, testaccount/testpassword, char +Acdream (spawns near the Holtburg / "Arcanum" cottage — landblock 0xA9B4, cottage cells 0xA9B4016F0175). Do NOT branch/worktree, push, or git stash/gc.

TL;DR

The two-week indoor-render HANG is FIXED and the interior SEALS (walls/floor/ceiling draw, textured) — both committed this session and live-verified by the user ("Ok now it runs!"). A structured live test pinned the remaining dominant visible issue, the FLAP at transitions, as viewer-cell metastability: the render roots at the camera-eye cell, which oscillates outdoor↔indoor as the 3rd-person boom drifts across the doorway plane. The flap is a SEPARATE, already-designed fix — it is NOT the verbatim DrawCells port; finishing the port will not fix it. Next session: fix the flap (camera-boom stability + viewer-cell dead-zone). Tracked follow-ups: #78 terrain gating, look-in-from-inside sealing, look-in FPS, L-spotlight.

What shipped this session (committed — see git log on this branch)

1. The HANG fix (the blocker)

Indoor frames froze (AppHangB1; not a crash — captured the spinning managed stack via a dotnet-stack hang-watcher). Root cause: PortalVisibilityBuilder.Build's portal-visibility flood did not terminate for real cottage geometry. Two layers, two fixes (both kept):

  • A — drift-tolerant CellView.Add dedup (src/AcDream.App/Rendering/PortalView.cs). The flood re-queues a cell every time its view GROWS; growth only stops when the dedup recognises a re-clipped region as a duplicate. The faithful ProjectToClip near-side clip drifts per round, so the old exact index-by-index match (eps 1e-4) never caught the near-duplicate → unbounded growth → O(n²) CPU-spin in CellView.Add. Fix: key each polygon by its vertices snapped to a 1e-3 NDC grid, consecutive-dedup'd, canonically rotated to a lex-min start → finite key space → convergence. Tests: tests/AcDream.App.Tests/Rendering/CellViewDedupTests.cs (3).
  • B — bounded re-enqueue (src/AcDream.App/Rendering/PortalVisibilityBuilder.cs). A alone did not fully converge (the spin relocated to ScreenPolygonClip.ClipByEdge — bounded loops — inside the still-non-terminating BFS). Restored the MaxReprocessPerCell = 16 hard cap that Phase U.2a deleted ("fixpoint termination" left the loop with NO bound). Kept the re-enqueue — it is load-bearing for late-slice propagation (Build_ViewGrowthAfterDoneCell_PropagatesNewSlicesToExit). Pure enqueue-once was tried and broke that test, so re-enqueue is kept and merely bounded.
  • Deep diagnosis + the reassessment that led to B: docs/research/2026-06-06-indoor-render-hang-rootcause.md.
  • Verified: clean exit (255→0); runs indoors with no freeze; the indoor flood converges in ~1 round/cell at normal positions (measured 35 pops/frame, 1 view-poly/cell). The cap only bites at the metastable doorway.

2. The SEAL (verbatim DrawCells port — Task 2)

RetailPViewRenderer.DrawEnvCellShells now iterates IndoorDrawPlan.ShellPass(pvFrame)every visible cell's shell draws (was gated on ClipFrameAssembler's slot filter → cells without a slot were silently dropped → grey clear-color void). Verified: interior seals + textured. (Task 1 IndoorDrawPlan + its test committed earlier as bff1955.)

3. Look-in FPS

GameWindow exterior-look-in candidate cells limited to the player's landblock ±1 (was all ~81 loaded landblocks iterated every outdoor frame just to discard them via the 48 m seed cutoff). Provably no behavior change (excluded cells are >48 m, already culled). Outdoor FPS improved but still ~110 fps / ~9 ms (was ~200)DrawPortal still draws ~12 building interiors/frame (see follow-up).

Baselines (must hold at next session start)

  • dotnet build -c Debug 0 errors.
  • App.Tests 210/210 (205 baseline + IndoorDrawPlanTests 2 + CellViewDedupTests 3).
  • Core.Tests 1331 pass / 4 fail / 1 skip — the 4 are pre-existing Physics door/step-up, unrelated.

Structured live test — findings (Holtburg/Arcanum cottage, 2026-06-07)

User walked a 6-step protocol (inside-still → camera-pan → doorway-threshold → just-outside → looking-at-cottage → cellar) and reported 8 behaviours; ACDREAM_PROBE_FLAP [render-sig] correlated each.

# Observed Cause Bucket
2,3,6,8 walls briefly transparent / window+entrance "covered by the world background" / abrupt "teleport" through the doorway — all at transitions (camera crossing a threshold) THE FLAP viewer-cell stability (NEXT)
1 outdoor grass covers the cellar-entrance hole (steady, looking in from outside) outdoor terrain not gated over the indoor floor opening #78 terrain gating
7 from inside, a building seen through the doorway has transparent walls (world-bg shows); pops back when you step outside look-out shows other buildings unsealed look-in/look-out completeness
5 spotlight blobs on textures from the ceiling lamp (always been there) point-light artifact L-spotlight (separate)
FPS inside very high; outside 110 fps / ~9 ms (was ~200) DrawPortal draws ~12 interiors/frame look-in cost
4 cellar transitions stable vertical transition doesn't cross the outdoor boundary

The FLAP — pinned (render-sig evidence)

[render-sig] over the doorway shows the render branch + the cell it roots at flip-flopping while the player cell stays inside:

50×  branch=OutdoorRoot        viewer=0xA9B40031 (outdoor)  player=0xA9B40171 (indoor)  gate=in
16×  branch=RetailPViewInside  viewer=0xA9B40170 (indoor)   player=0xA9B40171           gate=in
113× branch=RetailPViewInside  viewer=0xA9B40171 (indoor)   player=0xA9B40171           gate=in
     ... oscillates 0x0031 ↔ 0x0170 ↔ 0x0171 frame-to-frame ...

Mechanism: the render roots at the viewer (camera-eye) cell (clipRoot = viewerRoot, Phase W "one viewpoint"). The 3rd-person boom drifts the eye across the doorway plane; acdream re-resolves the viewer cell fresh each frame with no hysteresis → it flips between outdoor 0x0031 and indoor 0x0170/0x0171 → the render flips OutdoorRootRetailPViewInside → the indoor seal drops (walls transparent, outdoor world/grass shows) then re-seals → flapping. This is exactly the 2026-06-05 viewer-cell-flicker diagnosis, now confirmed against the live render branch.

Per docs/research/2026-06-05-viewer-cell-flicker-rootcause-and-fix-plan-handoff.md, 3 retail-faithful parts:

  1. Viewer-cell dead-zone (do this first) — ±0.2 mm cell hysteresis so a sub-mm eye drift can't flip the cell (PhysicsCameraCollisionProbe.SweepEye; retail point_inside_cell_bsp 0x53c1f0). Highest leverage — likely kills most of the flap on its own.
  2. Camera-boom stability — stop the boom drifting at rest (RetailChaseCamera.UpdateCamera; retail UpdateCamera 0x456660).
  3. w-space (w=0) portal clip — close-portal projection degeneracy (PortalProjection / PortalVisibilityBuilder; retail GetClip 0x5a4320 / polyClipFinish 0x6b6d00). Lower priority.

Apparatus ready: ACDREAM_PROBE_FLAP emits [render-sig] (branch/viewer/player/gate per frame), [flap], [flap-cam], [flap-sweep] — light enough to launch with (the heavy ACDREAM_PROBE_SHELL firehose is what previously caused an I/O stall; avoid it).

Tracked follow-ups (logged; not yet fixed)

  • #78 terrain gating — outdoor terrain (grass) draws over the indoor cellar-entrance hole (and likely other indoor floors). Decomp anchor CEnvCell::find_visible_child_cell (acclient_2013_pseudo_c.txt:311397).
  • Look-in-from-inside — buildings seen through your door/window from inside render unsealed (transparent walls); the look-out pass doesn't draw other buildings' shells. DrawCells port Task 5/7 territory (or R2 "outside-looking-in").
  • Look-in FPSDrawPortal draws ~12 building interiors every outdoor frame (~110 vs ~200 fps). Optimize: only look into buildings whose exit portals are frustum-visible; skip when no door is in view.
  • L-spotlight — ceiling-lamp point light makes spotlight blobs on textures. Pre-existing, separate.

verbatim DrawCells port — remaining tasks (deferred)

Plan: docs/superpowers/plans/2026-06-06-verbatim-retail-indoor-render-port.md. Task 1 + Task 2 done. Task 3 (objects no-clip) is effectively already satisfied (objects draw membership-gated with no clip; no half-characters observed). Tasks 48 (per-slice trim, look-out, delete ClipFrameAssembler, look-in, final) are cleanup with no current visible payoff — the seal works and there is no visible bleed (the "glitches between cells" were the FLAP, not bleed). Task 4 (trim) is intricate (its per-slice _clipFrame.Reset() is coupled with the landscape/particle passes that still read clipAssembly slots) and risks re-slicing the working seal — do it carefully, fresh, and only when clean architecture is the priority.

DO NOT re-litigate

  • The HANG fix (A drift-dedup + B bounded re-enqueue) is correct + verified. Do NOT try pure enqueue-once — it breaks Build_ViewGrowthAfterDoneCell_PropagatesNewSlicesToExit (late-slice propagation needs the re-enqueue; the cap, not removal, is the termination guarantee).
  • The grey was the drawableCells / ClipFrameAssembler slot filter; Task 2 fixed it. The clip math is faithful — do not "harden the w-clip".
  • The FLAP is NOT the DrawCells port. It is viewer-cell metastability (camera/membership). Tasks 48 will NOT fix it.
  • The render roots at the VIEWER (camera-eye) cell intentionally (Phase W "one viewpoint"). The flap fix is to STABILISE the viewer cell (dead-zone + boom), NOT to re-root at the player cell (superseded).

Copy-paste pickup prompt (next session)

Pick up the indoor-render work in worktree thirsty-goldberg-51bb9b (branch
claude/thirsty-goldberg-51bb9b). PowerShell; launch logs UTF-16; build before launch; acceptance is
the user's eyes. Do NOT branch/worktree, push, git stash/gc, or revert the dirty tree.

Read first: docs/research/2026-06-07-indoor-render-session-handoff.md (state, what shipped, the FLAP
diagnosis, do-not-relitigate). Then docs/research/2026-06-05-viewer-cell-flicker-rootcause-and-fix-plan-handoff.md
(the flap fix plan).

Confirm baselines: build 0 errors; App.Tests 210/210; Core.Tests 1331 pass / 4 fail (pre-existing) / 1 skip.

The indoor HANG is fixed and the interior SEALS (shipped + committed last session). The remaining
dominant visible issue is the FLAP at transitions — viewer-cell metastability: the render roots at the
camera-eye cell, which oscillates outdoor↔indoor as the 3rd-person boom drifts across the doorway (no
hysteresis), confirmed in [render-sig]. FIX THE FLAP, starting with the viewer-cell dead-zone
(PhysicsCameraCollisionProbe.SweepEye; retail point_inside_cell_bsp 0x53c1f0), then camera-boom
stability (RetailChaseCamera.UpdateCamera; retail UpdateCamera 0x456660). Launch with ACDREAM_PROBE_FLAP
only (NOT ACDREAM_PROBE_SHELL — it stalls on I/O). Gate on the user's eyes at the cottage doorway.

Do NOT: retry pure enqueue-once (breaks late-slice propagation); re-root render at the player cell
(viewer-cell rooting is intentional); finish DrawCells port Tasks 4-8 expecting it to fix the flap (it
won't). Tracked follow-ups (not the flap): #78 terrain gating (grass over cellar hole), look-in-from-
inside sealing, look-in FPS (DrawPortal ~12 interiors/frame), L-spotlight.