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

160 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 `OutdoorRoot``RetailPViewInside` → 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.
## RECOMMENDED NEXT WORK — fix the FLAP (separate, already-designed)
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 FPS** — `DrawPortal` 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.
```