diff --git a/docs/research/2026-06-08-indoor-flap-edgeon-vs-camera-position-handoff.md b/docs/research/2026-06-08-indoor-flap-edgeon-vs-camera-position-handoff.md new file mode 100644 index 00000000..dfec716a --- /dev/null +++ b/docs/research/2026-06-08-indoor-flap-edgeon-vs-camera-position-handoff.md @@ -0,0 +1,204 @@ +# HANDOFF — Indoor crossing flap: MEASURED vs HYPOTHESIS vs OPEN (read before ANY code) + +**Date:** 2026-06-08 (late). **Branch:** `claude/thirsty-goldberg-51bb9b`. +**HEAD:** the `docs(render): plan status` commit (after `c7069cf`). Working tree clean. + +--- + +## ⚠️ 0. CALIBRATION NOTE — read this first + +This session's diagnosis of the indoor flap **shifted four times** as measurements came in: +`per-building` → `camera-jitter (R-A4)` → `camera swept/published eye` → `flood edge-on`. **Each +"confident" claim was refuted by the NEXT measurement, in the same session.** The author (me) also +stated a final scoreboard ("conclusively pinned / camera ruled out") with **more confidence than the +data earns.** + +So this handoff is structured to **prevent the next session from inheriting an overclaim:** +- §2 = **MEASURED** (high confidence, with the data + file refs). +- §3 = **LEADING HYPOTHESIS** (medium — explicitly NOT isolated). +- §4 = **ALTERNATIVE NOT RULED OUT** (a real competing explanation I waved away). +- §5 = **WHAT TO VERIFY FIRST** — do this BEFORE building anything. Do not trust §3 until §5 confirms it. +- §6 = **DO-NOT** (genuinely refuted, high confidence — safe to not re-try). + +**The honest one-liner:** the flap is a **motion-time** grey-flash at openings; eye-jitter-at-rest is +ruled out (measured); but whether the cause is the **clip math at edge-on** (§3) or the **camera not +pulling the eye in like retail** (§4) is **NOT yet disambiguated** — the one "clean" pass wasn't clean +enough (it had back-and-forth). Disambiguate (§5) before choosing the fix. + +--- + +## 1. What SHIPPED this session (all green, visual-confirmed by the user) + +| Commit | What | Verified | +|---|---|---| +| `7fe9809` | R-A1 — canonicalize outdoor root on `LoadedCell.IsOutdoorNode` (behavior-preserving) | tests | +| `c62663d` | R-A2 — per-building floods (`ConstructViewBuilding`; `OutdoorCellNode` portal-less; per-building merge in `RetailPViewRenderer.DrawInside`) | **user: outside-looking-in flap GONE** | +| `2ec189c` | R-A2 seam fix — flood null-`BuildingId` cells (don't drop) | **user: missing textures GONE** | +| `3c178b2` | `tools/cdb/flap-cam-measure.cdb` — retail eye + CameraManager capture | apparatus | +| `c7069cf` | `[flap-sweep]` probe: F6 `in=`(desiredEye) / `out=`(eye) | apparatus | +| `docs…` | plan-doc STATUS note (R-A4 ruled out) | docs | + +Test baseline: App `~Rendering` **207**, Core `PlayerMovementControllerTests` **14**. Build green. +Plan: `docs/superpowers/plans/2026-06-08-full-retail-render-port-option-a.md`. + +**The user's remaining symptom (verbatim):** *"grey background color flapping entrances between rooms +and cellar entrance and when going outside."* I.e., the clear/background color flashes through openings +during MOTION; at rest it does not flap. + +--- + +## 2. MEASURED FACTS (high confidence — data + file refs) + +### 2.1 acdream eye at REST ≈ 1 µm stable (NOT a jitter problem) +`flap-cam-measure.log` (acdream run, user held completely still, 768 samples). Last 150 frames: +`in` (desiredEye) Y range **0.000 mm** (1 distinct value); `out` (eye) Y range **0.001 mm** (2 distinct); +`pulledIn=0`. → The convergence snap (`RetailChaseCamera.ApplyConvergenceSnap`, `SnapEpsilon 0.0004`) +holds; there is **no at-rest jitter**. (acdream at rest is actually steadier than retail's settled eye.) + +### 2.2 The earlier "~1.3 mm jitter at rest" was MOTION misread as rest +`launch-flap-pin2.log` `[pv-input]` showed ~1.3 mm; that was during the crossing. The F2-precision print +rounded sub-cm walking to "d=0", which I mis-analyzed as a static eye. **The flap is motion-time only.** + +### 2.3 On a (mostly) one-way pass: eye SMOOTH, but visible-cell count OSCILLATES +`launch-camprobe.log`, the clean-pass tail (~25.7k sweep frames): +- **Eye:** 3D path = 34.1 m, net = 8.2 m (**ratio 4.2×**), direction reversals **X=3, Y=18** (i.e. few + large direction changes — the motion is smooth, not jittery). +- **Visible cells (`[flap] vis=`):** oscillated **414×** between **1 and 6**; **648 `clip=0`** events. + Distribution `1:4084 2:6300 3:3860 4:10634 5:439 6:427`. Roots: `0171` (room) 21396, `0031` (outdoor) + 4084, others tiny. +- **⚠️ CAVEAT (why this is NOT isolated):** ratio 4.2× means the pass was **not** purely one-way — it had + back-and-forth + indoor↔outdoor. The 414 oscillations therefore **mix** (a) indoor-flood oscillation + (root `0171`, vis 2↔6) AND (b) indoor↔outdoor **root swaps** (`0171`↔`0031`, the "going outside" + grey; the 4084 vis=1 frames are root `0031` = legitimately just the outdoor node). These were **not + separated.** So "smooth eye + oscillating vis ⇒ flood bug" is **indicated but not proven** — some of + the oscillation is the back-and-forth and the root-swap. + +### 2.4 Retail eye at the cottage doorway: COLLIDED 93%, settled jitter ~tens of µm +`flap-cam-measure.log` (retail, `acclient.exe`, PDB **MATCH** `refs/acclient.pdb`), 768 samples: +- **`pub != sought` 716/768 = 93% COLLIDED** — retail's boom hits the cottage; the eye is pulled toward + the player (a more **head-on** view of the doorway, not floating 3 m back). +- Settled jitter (quarters Q2–Q4) ≈ **93 µm**; Q1's 1.4 mm was the user walking up (settling motion), + verified by the quarter split. +- Offsets: `CameraManager.t_stiffness@+0x08`, `r_stiffness@+0x0c`, `viewer_offset@+0x48`; + `SmartBox.camera_manager@+0xa0`. acdream's damping already matches retail's formula + (`alpha = clamp(stiffness·dt·10,0,1)`, stiffness 0.45) — `RetailChaseCamera.ComputeDampingAlpha`. + +--- + +## 3. LEADING HYPOTHESIS (medium confidence — NOT isolated; see §2.3 caveat) + +The flood/clip's **"is the room behind this opening visible?"** decision is **non-monotonic near a +doorway's EDGE-ON angle.** The portal projects to an on-screen area that hovers at ~zero (coin-on-edge); +small smooth eye steps flip the clip 0↔nonzero → the cell behind drops → grey flash. This matches the +"grey at room↔room / cellar entrances." Mechanism candidates inside this hypothesis: `ProjectToClip` / +`ClipToRegion` collapsing at edge-on (`PortalProjection.cs`), and/or the band-aids `MaxReprocessPerCell` +(D4) + `EyeInsidePortalOpening` (D5) re-processing differently frame-to-frame during motion. + +--- + +## 4. ALTERNATIVE NOT RULED OUT (a real competing explanation) + +**The edge-on viewing might be a CONSEQUENCE of the camera not pulling the eye in like retail.** §2.4: +retail's eye is **collided 93% (head-on)**; acdream's `[flap-sweep]` showed it **uncollided much more +often (floats free, 3 m back → edge-on view of the doorway)**. A doorway viewed edge-on is exactly when +the clip collapses. So the root cause could be **camera collision / eye position** (acdream's spring-arm +not pulling in where retail's does), NOT the clip math. +- **What IS ruled out:** eye-jitter / wobble / at-rest instability (§2.1, §2.3 — smooth + 1 µm). +- **What is NOT ruled out:** eye **position** (collision pull-in). These are different. I conflated them + in the scoreboard. Disambiguate in §5.2. +- Separately, the **"going outside" grey** is the root `0171↔0031` wholesale swap (§2.3) — likely a + distinct sub-issue (indoor↔outdoor root transition), not the same as the room↔room edge-on flap. + +--- + +## 5. VERIFY FIRST — before building ANY fix (this is the anti-overclaim gate) + +1. **Isolate pure indoor-flood oscillation.** Do a **genuinely one-way, slow** walk through **ONE + interior doorway** (room→room), **no going outside, no back-and-forth, camera fixed**. Capture + `ACDREAM_PROBE_FLAP=1`. Confirm whether `vis` (root `0171` only) oscillates 2↔6 for a **monotonic** + eye (check `[flap-sweep] out=` is monotonic — ratio ≈ 1, ~0 reversals). **If it does NOT oscillate + when the eye is truly monotonic → the flap was the back-and-forth / root-swap, not a flood bug** (then + §3 is wrong). The clean pass in §2.3 had ratio 4.2× — not clean enough to conclude. +2. **Disambiguate clip-math (§3) vs camera-position (§4).** At the SAME interior doorway, measure + acdream's eye-to-doorway angle vs retail's (collided) — is acdream viewing it edge-on where retail + views it head-on (because retail's eye is pulled in)? Add the doorway-plane signed distance + the + collide state to `[flap-sweep]`. If acdream floats edge-on where retail pulls in → the fix is the + **camera collision**, not the flood. +3. **Read retail's edge-on clip handling (the fix ORACLE — do NOT guess the fix without this):** + `PView::GetClip` (`0x5a4320`, decomp ~`:432344`), `PView::ClipPortals` (`0x5a5520`, `:433572`), + `ACRender::polyClipFinish` (the w=0 clip, `:702749`). Questions: does retail's clip collapse to zero + at edge-on like ours, or stay robust? Does retail keep a flooded cell once added? Cross-check + `PortalProjection.cs` (our port — handoff says it's faithful; verify at edge-on specifically). + +--- + +## 6. DO-NOT RETRY (genuinely refuted this session — high confidence) + +- **Camera eye-JITTER / R-A4 stiffness change.** Eye is smooth (3/18 reversals) + 1 µm at rest + (§2.1, §2.3). acdream's damping already matches retail's stiffness 0.45. Do not "reduce jitter." +- **RenderPosition rest-snap** (`cd974b2`, reverted). The eye isn't the jitter source; at rest it's 1 µm. +- **"1.3 mm jitter at rest"** — it was MOTION (§2.2). +- **A robust-MEMBERSHIP scheme retail lacks.** Retail's flood is clip-driven too — `PView::add_views` + (`0x5a5210`) only pre-pushes view slices onto the stab_list cells via `curr_view_push`; it does NOT + ground `cell_draw_list` membership. So the robustness must live in the **clip's edge-on behavior** + (§3) or the **camera position** (§4), NOT a stab_list membership-grounding. +- **Two-pipe inside/outside split; bounded-propagation/churn; byte-stable eye** — all refuted earlier + (see the 2026-06-08 OPTION-A handoff §7). + +--- + +## 7. The fix (R-A2b) — GATED on §5 + +Do NOT design until §5 disambiguates. Two branches: +- **If §5.1 + §5.3 show the clip collapses at edge-on (clip-math):** port retail's edge-on clip + robustness into `PortalProjection` / `PortalVisibilityBuilder`; conformance-test "smooth monotonic eye + through a doorway ⇒ monotonic vis (no oscillation)"; visual-gate. +- **If §5.2 shows acdream's eye floats edge-on where retail pulls in (camera-position):** the fix is the + camera collision (`PhysicsCameraCollisionProbe.SweepEye` / `RetailChaseCamera`) — make the eye pull in + like retail (93% collided at the doorway). This is the camera **position**, distinct from the + ruled-out eye-jitter. +- The "going outside" grey (§4, root `0171↔0031` swap) is likely a separate, smaller task. + +--- + +## 8. Apparatus (reuse — don't rebuild) + +- **acdream probes** (gated in `AcDream.Core.Rendering.RenderingDiagnostics`): + - `ACDREAM_PROBE_FLAP=1` → `[flap]` (per-portal `D`/side-test `TRV/CULL`/`clip=` vertex count, `vis=`), + `[flap-sweep]` (camera sweep: `pulledIn`, and **`in=`(desiredEye) / `out=`(eye) at F6** — added + `c7069cf`), `[flap-cam]`. HEAVY (per-frame per-portal) — short captures only. + - `ACDREAM_PROBE_PVINPUT=1` → `[pv-input]` (eye/player/rawPlayer F6 + flood count). Lighter. +- **cdb on retail:** `tools/cdb/flap-cam-measure.cdb` (eye origin per frame `pub`+`sought` raw float + hex + `CameraManager`/`SmartBox` type dumps). Attach: `Get-Process acclient` → pid; + `cdb.exe -p -cf tools\cdb\flap-cam-measure.cdb`. Exit code **5 is expected** (clean detach). + PDB `refs/acclient.pdb` — verify with `py tools/pdb-extract/check_exe_pdb.py "C:/Turbine/Asheron's Call/acclient.exe"` (MATCH). +- **Logs (UNTRACKED, large — gitignore/delete):** `launch-camprobe.log`, `launch-flap-pin2.log`, + `flap-cam-measure.log`, `launch-ra2.log`. +- **Launch:** CLAUDE.md "Running the client" env block + `$env:ACDREAM_PROBE_FLAP="1"`. +- **Build-while-client-running gotcha:** the running client locks the DLLs → build fails MSB3027. Close + the client (graceful `CloseMainWindow`) before building. + +--- + +## 9. Exact pickup (next session) + +1. Read this doc top-to-bottom, then memory `project_indoor_flap_rootcause` (2026-06-08 late CORRECTION). +2. `git log --oneline -8` (HEAD = the plan-status docs commit). `dotnet build` green; App `~Rendering` + 207 / Core `PlayerMovementControllerTests` 14 green. +3. **Do §5 FIRST** (isolate indoor-flood vs camera-position vs root-swap). **Do NOT write a fix until §5 + tells you which of §3 / §4 is real.** This is the whole point of the calibration note. +4. Then §5.3 (retail clip oracle) → design the gated fix (§7) → conformance-test PRE-gate → visual-gate. +5. Strip the throwaway probes after the visual gate; update memory + the plan + milestones. + +--- + +## 10. One-paragraph version (for when you're tired) + +Outside-flap and seams are FIXED and confirmed. The indoor flap is a motion-time grey-flash at openings. +We MEASURED that it's not eye-jitter (eye is smooth + 1 µm at rest). We did NOT prove it's the clip math: +the one "clean" pass had back-and-forth (ratio 4.2×) so the 414 oscillations mix flood-edge-on, +back-and-forth, and indoor↔outdoor root-swaps. And we did NOT rule out the camera *position* (retail's +eye is pulled-in/head-on 93% at the doorway; ours floats edge-on). So: isolate a truly one-way single +interior-doorway pass, compare acdream's vs retail's eye angle at that doorway, and read retail's +edge-on clip code — THEN choose between the clip-math fix and the camera-position fix. Don't pick one +until those three are done.