docs(render): calibrated indoor-flap handoff — MEASURED vs HYPOTHESIS vs OPEN

Separates what is measured (eye smooth + 1um at rest -> not jitter) from the leading-but-unproven hypothesis (clip edge-on) and the NOT-ruled-out alternative (camera position: retail eye collided/head-on 93%, ours floats edge-on). The one 'clean' pass had ratio 4.2x back-and-forth, so the flood claim is indicated not proven. Lists the verify-first steps before any R-A2b fix. Counters this session's pattern of overclaiming then refuting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-09 08:16:51 +02:00
parent 8c78f1f07a
commit 8f879bd7d9

View file

@ -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 Q2Q4) ≈ **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 <pid> -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.