# Pickup handoff — #176 seam-floor lighting flicker = a RUNTIME draw z-fight (RenderDoc next) > ## ✅ OUTCOME (2026-07-06, the pickup session) — z-fight REFUTED; it was the light POOL tracking the camera; FIX SHIPPED > > RenderDoc was infeasible (it does not support `GL_ARB_bindless_texture` and hides > it → our mandatory-modern startup gate throws `NotSupportedException`; the AMD RX > 9070 XT rules out Nsight). The equivalent evidence came from the in-engine > `ACDREAM_PROBE_SEAMDRAW` probe (`[seam-blk]/[seam-cell]/[seam-snap]/[seam-ent]/ > [seam-mask]`): **one** shell instance per seam cell at the lifted z (suspect 1 > dead), **no** floor-coincident entity (suspect-class dead), **zero** portal depth > fans (suspect 3 dead) — but the corridor floor's applied LIGHT SET flipped > wholesale with flood composition. Root cause: the `c500912b` visible-cell scoping > was built on a wrong gloss — retail's `CEnvCell::visible_cell_table` is the > **RESIDENT-cell registry** (`add_visible_cell` 0x0052de40 dat-loads absent cells), > not the frame flood, and `insert_light` (0x0054d1b0) anchors the pool at > **`Render::player_pos`**. Fix: `BuildPointLightSnapshot(playerWorldPos)` — resident > collection, dynamics-first player-nearest cap; `RebuildScopedLights` deleted. > Live-verified: flood churned 8→41 cells over a full-circle turn while the floor's > set held the same 8 identities. Details: ISSUES #176, register AP-85, the > correction banner in `2026-07-06-a7-per-cell-lighting-pseudocode.md`. The document > below is kept as the historical elimination record — its DO-NOT-RETRY table > remains valid (all those channels stay dead). **Paste the companion prompt below into a fresh session.** Read this file, then `claude-memory/project_render_pipeline_digest.md` (top banner), then **ISSUES #176**. ## TL;DR — what #176 actually is now The Facility Hub corridor floor shows **hard-edged purple triangular facets that flicker as the camera turns**, at cell seams. After a long investigation (11 hypotheses), the domain is **pinned by evidence**: > **#176's residual is a RUNTIME draw-level depth-fight (z-fight) in the seam > FLOOR geometry — two draws the renderer emits at ~the same depth, one carrying > the purple portal light and one not, whose winner flips as the camera moves. > It is NOT lighting, NOT membership, NOT dat geometry.** The **wedge SHAPE itself is retail-faithful** (retail shows the same faceted purple — cdb-proven, below). The only defect is the **flicker**, and it is a render/draw problem. **The next step is a RenderDoc pixel-history capture** — do NOT throw more lighting fixes at it (two already did nothing). ## The elimination table (evidence — this is the DO-NOT-RETRY list) | Ruled OUT | How (hard evidence) | |---|---| | Lighting **brightness** | per-light cap on the dynamic branch (`mesh_modern.vert`) → **no visual change**, reverted | | Lighting **selection** | `SelectForCell` (all dynamic lights per cell, retail-exact) → **no visual change** | | Light-set **camera-cap churn** (the OLD "confirmed" #176 theory) | visible-cell scoping shipped (`c500912b`); probe-proven ~285 through-floor lights dropped/frame — symptom unchanged. **REFUTED.** | | **Membership / the "flap"** | `ACDREAM_PROBE_FLAP`: render cell = `0x8A020164` on **100%** of 188,732 frames across **526 distinct camera angles**, `res=None` always; `ACDREAM_PROBE_PVINPUT` flood is stable-per-angle (17/10/8…), never oscillates at a fixed view; 1 `[cell-transit]` (the spawn teleport) total | | **Dat-geometry z-fight** | `Issue176177DungeonSeamInspectionTests.CorridorNeighborhood_CoplanarOverlappingDrawnPolyPairs` seeded on the ACTUAL cell `0x8A020164` + neighbors → **zero** coplanar drawn pairs at the z=−6 corridor floor (only the benign same-cell z=−12 under-hall floor tiling in `0x011E`) | | A2C / alpha-hole see-through | corridor floor surface `0x08000377` is **fully opaque** (`alpha0Texels=0`, `transl=0.00`) | | Translucent under-surface blend | the one translucent colored surface `0x08000034` is `NoPos` (not drawn) | | Flat (per-face) normals | corridor floor uses **smooth per-vertex dat normals** (center `(0,0,1)`, corners tilted ~27° — retail-style edge smoothing), NOT flat (`CellVertexNormals_SmoothOrFaceted_Dump`) | ## What retail actually does (cdb-proven — the wedge is faithful) `tools/cdb/issue176-floor-light.cdb` traced live retail's `config_hardware_light` standing in the Hub (binary↔PDB MATCH, GUID `9e847e2f…`): - Retail applies **4 intensity-100 magenta portal lights** (`d3dIdx 3–6`, `color=(0.784,0,0.784)`, `falloff=6`) **+ the viewer fill** (`d3dIdx 1`, `intensity=2.25 falloff=10`) as **D3D hardware lights to EVERY Hub cell** (`0102,0103,0109,0111,0112,0115,0118,0119,011a,011e,011f,0121,0122,0123…`), every frame, stable. - So retail's cell floors ARE lit by the portals via per-vertex hardware T&L over the same smoothed fan-corner normals → **the same faceted purple wedges**. → the wedge SHAPE is retail-faithful; only the flicker is ours. The intensity-100 magenta light is the **portal** (2 portal weenies `0x000F4247`/`0x000F4248` in cells `0x8A020118`/`0x8A020119`, `dyn=1`). Confirmed via the `[light-detail] owner=/cell=/dyn=` probe fields. ## The residual bug — a runtime double-draw of the seam floor Since it's not dat geometry, the renderer is emitting the corridor floor (or a coincident surface) **twice at ~the same depth**. Prime suspects to check in RenderDoc (do NOT assume — capture and read): 1. **The `+0.02 m` shell render lift** (`PortalVisibilityBuilder.ShellDrawLiftZ`, register **AP-32**, the #130 saga). If the shell floor draws lifted while some other pass draws the same floor unlifted, 0.02 m z-fights at grazing angles. 2. **The A8 double-sided cell-shell stopgap** (**#178**, LIVE): `EnvCellRenderer.RenderModernMDIInternal` `if (cullMode == CullMode.Landblock) cullMode = CullMode.None;` — every cell shell draws two-sided. Retiring this is already an open issue; test whether it's implicated. 3. **Portal depth-writes** — `RetailPViewRenderer.DrawExitPortalMasks` / the interior-root depth-clear+seal path. Seals are supposed to fire only for `OtherCellId==0xFFFF` (a sealed dungeon draws zero), but VERIFY in the capture that no depth-mask lands on the floor-portal plane (the corridor floor IS a floor-portal poly to the under-room, surf `0x08000377`, z=−6). ## RenderDoc plan (the definitive next step) 1. Launch acdream under RenderDoc (`renderdoc` → capture `AcDream.App`, or inject). Standard `ACDREAM_LIVE` env from CLAUDE.md. 2. Stand in cell `0x8A020164` corridor, at an angle where the floor flickers. Capture a frame (F12). 3. In RenderDoc: **right-click a flickering floor pixel → "History"** (pixel history). It lists every draw that touched that pixel + its depth + pass/fail. 4. **Two draws at ~equal depth writing that pixel = the z-fight.** Identify which two passes they are (shell vs shell-lift vs portal-mask). Report the draw names + depths back — that names the fix. 5. Fix at the source (make the two draws not coincide: drop the redundant draw, or reconcile the lift), then re-gate. No band-aid depth-bias (that's a workaround — see CLAUDE.md). ## What's committed / the state - **`c500912b`** (in `main`): visible-cell light **scoping** — `LightSource.CellId` + `BuildPointLightSnapshot(camPos, visibleCells)` (retail `add_*_lights over visible_cell_table`) + the `[indoor-light]` probe. Retail-faithful; fixed the camera-eviction latent bug. Register **AP-85** corrected. Corrected the old handoff's imprecise "insert_light = per-cell" framing — the real model is visible-cell-scoped collection into a small player-nearest pool; see `docs/research/2026-07-06-a7-per-cell-lighting-pseudocode.md`. - **This commit**: `LightManager.SelectForCell` (all dynamic lights per EnvCell, retail `minimize_envcell_lighting`) wired into `EnvCellRenderer.GetCellLightSet`; the `[light-detail]` owner/cell/dyn probe; `CellVertexNormals_*` dump; `tools/cdb/issue176-floor-light.cdb`. **Retail-faithful, NOT the flicker fix.** - Suites green: Core 2599 + 2 skip. All on `main`. ## Apparatus (reuse, don't rebuild) - `ACDREAM_PROBE_FLAP=1` — per-frame `[flap-cam]` render cell + `[flap]` portal decisions (proved membership stable). - `ACDREAM_PROBE_PVINPUT=1` — per-frame `[pv-input] flood=N eye= yaw=`. - `ACDREAM_PROBE_LIGHT=1` — `[light]` + `[light-detail]` (now with owner/cell/dyn). - `ACDREAM_PROBE_INDOOR_LIGHT=1` — `[indoor-light]` scoped-pool SET composition. - `tools/cdb/issue176-floor-light.cdb` — retail light-setup trace. - `Issue176177DungeonSeamInspectionTests` — dat truth (coplanar sweep, floor surfaces, vertex normals); `CorridorNeighborhood_CoplanarOverlappingDrawnPolyPairs` seed = `{0164,0165,016E,017A}`+neighbors. ## Repro + launch protocol `dotnet build` green FIRST (close any running client — a client may still be up; the user manages lifecycle, do NOT kill it, ASK). Standard `ACDREAM_LIVE` PowerShell launch. Test char spawns in the Facility Hub; the flickering corridor is cell **`0x8A020164`** (player z≈−5.9). Turn back-and-forth across a seam to reproduce. Strip `\000` before grep (PowerShell Tee = UTF-16). ## Process lesson (paid for in full this session) **Get a screenshot AND a retail cdb trace EARLY for a render bug — before theorizing.** This session chased 11 inference-based hypotheses over many launches; the screenshot (faceted triangles) + the cdb trace (retail applies the same lights → faithful shape) would each have redirected the whole thing in one step. The project's own rules already say this; follow them. ## Also open - **#177** — dungeon stairs pop in/out (`0178/0182/0183`) — a SEPARATE portal-visibility miss, not lighting. Untouched this arc.