acdream/docs/research/2026-07-08-186-connector-grey-flap-handoff.md
Erik 4dea66b633 docs(#186): close — render InsideSide from dat PortalSide bit (fix 8257b9ba); retail trace overturned PICK + FLOOD-epsilon hypotheses
The live retail cdb trace decided it: retail roots at the connector 0118 at the
grey pose (NOT the PICK fork) AND still draws the player room 0116 from that root,
because retail's InitCell side test reads the dat PortalSide bit where acdream's
render path reconstructed the interior side from the cell AABB centroid (mis-sides
a thin connector). ISSUES #186 -> CLOSED; handoff gets a RESOLVED banner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 15:07:46 +02:00

12 KiB
Raw Blame History

Handoff: #186 — indoor GREY flap at a top-floor connecting room (new house type)

RESOLVED 2026-07-08 · fix 8257b9ba · live gate PASSED. The retail cdb trace (§3, run via tools/cdb/issue186-connector-decider.cdb) OVERTURNED both hypotheses below. Retail roots at the connector 0118 at the grey pose (same as acdream — so NOT the PICK fork) AND still DRAWS the player room 0116 from that root — because retail's PView::InitCell side test reads the dat PortalSide bit, while acdream's render path reconstructed the portal's interior side from the cell AABB centroid, which mis-sides a THIN connector cell (0118, 5 render polys → centroid on the wrong side of the 0118→0116 doorway → back-portal cull → grey). Fix = derive InsideSide from the dat bit in GameWindow.BuildLoadedCell, matching retail + acdream's own physics (CellTransit.cs:190). Surgical (dat diagnostic: agrees with the old centroid on every portal but the one #186 broke). The §2/§3 "acdream drops the room" read was right; the "acdream over-switches the root / flood-epsilon" mechanism was WRONG — it was the InsideSide source, not the pick or the epsilon. Everything below is the pre-fix investigation, kept for the trail.

Date: 2026-07-08 · Status: ROOT CAUSE NARROWED (report-only) — fix NOT started. RESOLVED (see banner). It lives in the FROZEN portal-flood / viewer-cell render code (the doorway-FLAP family that cost weeks), so it needs a retail cdb trace to pin the last fork BEFORE any code, then a careful change. Milestone: M1.5.

Read these first, in order:

  1. This handoff.
  2. claude-memory/project_render_pipeline_digest.md — the render/doorway-FLAP SSOT + DO-NOT-RETRY table ("root at VIEWER cell"; the deleted EyeInsidePortalOpening rescue; PortalSideEpsilon root-lag).
  3. docs/architecture/retail-divergence-register.md rows AD-17 (≤8 clip-plane / union-AABB scissor), AD-20 (camera-sweep fallback seeds eye cell from PLAYER cell), AD-21 (null-clipRoot outdoor fallback).
  4. docs/ISSUES.md #186.

0. Symptom (user, live)

Top floor of a house type not tried before; a connecting opening/room between two parts of the house. Passing through → a brief GREY flap (the framebuffer clear color). Stopping at the precise spot → the WHOLE screen is grey and stays; turning the camera clears it, turning back → grey again (camera-DIRECTION dependent, player stationary). A jump is NOT involved (that's #185). Retail is SEAMLESS at the same spot (user-confirmed) → this is a real acdream bug, not inherent.

1. The cells + the grey pose (ACDREAM_PROBE_FLAP capture)

Landblock 0xF682. Three cells, all envId 0x0365, cell-struct origin (132,84,62):

cell render polys physics polys portals role
0xF6820116 15 12 0117,0118,0113 player's room (closed)
0xF6820117 10 6 0118,0116,0115,0119 next room (closed)
0xF6820118 5 3 0117,0116 thin connector (root when grey)

Grey-pose facts (from [flap]/[flap-cam]/[flap-sweep], 88,249 frames; the grey frames are vis=1 root=0xF6820118, ~6,725 frames):

  • [flap-cam]: root=viewerCell=0xF6820118 playerCell=0xF6820116 eyeInRoot=Y playerInRoot=n terrain=Skip outVisible=False eye=(134.81,83.59,69.07) player=(137.30,83.32,66.82).
  • [flap] grey line: root=0118 | p0->0x0117 D=0.14 TRV proj=4 clip=0 | p1->0x0116 D=0.18 CULL proj=5 clip=4 || outPolys=0 vis=1. (Dominant D to 0117 ≈ 0.14; ALWAYS positive 0.000.50 → eye is genuinely INSIDE 0118, never across the plane.)
  • [flap-sweep]: ok=True resolved=Y pulledIn=-0.00 (the chase boom reaches FULL distance, NO collision pull-in, seating the eye in the connector).

Read of the geometry: the eye sits in the sparse connector 0118, in the doorway threshold, looking BACK toward the player's room 0116. p1->0116 is ON-screen (proj=5 clip=4) but side-CULLED (eye ~0.180.31 on 0116's side of the p1 plane — a back-portal). p0->0117 is genuinely OFF-screen (clip=0 with proj≥3 = off-screen per the builder's own probe doc, PortalVisibilityBuilder.cs:818-821). So no neighbour floods, only the 5-poly connector draws, and its shell doesn't cover the forward view → the framebuffer clear color (fog, GameWindow.cs:8968) shows through = GREY. Turning re-aims off the back-portal → a room floods → picture.

2. What is CONFIRMED / RULED OUT (workflow wf_362bbeda-933, 4 tracers + synthesis, + my own re-checks)

  • NOT null viewer-root / AD-21 — the render root is a valid indoor cell on 88,248/88,249 frames (one lone [outdoor-node] root=OUT). (This refutes THE FIRST hypothesis in this investigation.)
  • NOT viewer-cell root-lag / AD-20eyeInRoot=Y at every grey frame: the eye is geometrically INSIDE 0118's BSP. The root is the fresh eye-derived swept curr_cell (PhysicsCameraCollisionProbe.cs:92), NOT a stale player-seeded fallback. (Refutes the workflow synthesis's own leading "31 cm gap" theory — the eye is NOT across the boundary; I checked the eyeInRoot flag directly.)
  • NOT the flood-admission being unfaithful — the empty-clip cull (PortalVisibilityBuilder.cs:320-324) and the back-portal side-CULL (:292-298, CameraOnInteriorSide :857-863) are faithful ports: retail PView::ClipPortals gates neighbour processing on if (ecx_8 != 0) (pc 005a5660) and AddViewToPortals on view_count != 0 (pc 005a5357). #177 already proved acdream's flood is retail-identical.
  • NOT the color clear (the tempting "3-line fix" — DEAD END, do not revisit). Retail's DrawCells (0x005a4840, decomp line 432709) has its only Clear INSIDE if (outside_view.view_count > 0) (line 432715), right AFTER LScape::draw (the landscape draw, 432719). The Clear is Clear(4, &RGBAColor_Black, 1.0f) (0x820fc0 = RGBAColor_Black) — but clearing COLOR after drawing the landscape would erase it, so that 4 flag is depth/stencil, not color. So retail's color clear is NOT gated here, and matching "don't color-clear a sealed interior" would be WRONG. (This is why I did the cdb prep — it killed a wrong fix before shipping.)
  • It IS a real acdream bug (retail seamless) in the doorway-FLAP flood/pick family: acdream drops the room the camera looks back at (0116) from the connector root; retail keeps it drawn.

3. THE ONE OPEN FORK → the retail cdb trace (do this FIRST in the new session)

Retail is seamless, so retail either (A) roots at the player's room 0116, not the connector (a viewer-cell PICK divergence — acdream over-switches to 0118), or (B) roots at 0118 but its flood still admits 0116 (a FLOOD divergence — retail keeps the back-portal / uses an on-screen sliver acdream drops). Static decomp cannot decide; observe retail.

Toolchain (CLAUDE.md "Retail debugger toolchain"; binary CONFIRMED pairs with refs/acclient.pdb via py tools/pdb-extract/check_exe_pdb.py "C:/Turbine/Asheron's Call/acclient.exe" → MATCH).

  • User: launch retail, connect to local ACE, walk to the SAME connecting room, and HOLD the grey pose (so most captured frames are it).
  • bp acclient!SmartBox::update_viewer (0x00453ce0) → dump viewer_cell (a CObjCell*, acclient.h:35194) and its cell id. AND/OR bp acclient!PView::DrawCells (0x005a4840) → dump cell_draw_num + cell_draw_list.data[] ids (acclient.h:45939-45940) — the drawn cell set.
  • Decide: retail viewer_cell == 0116PICK fix (acdream's viewer-cell pick/hysteresis over-retains the connector — CellTransit.cs:862-916 BuildCellSetAndPickContaining, or the camera boom coasting the eye into the connector at pulledIn=-0.00 where retail's PathClipped viewer_sphere sweep hard-stops in 0116). retail viewer_cell == 0118 but cell_draw_list0116FLOOD fix (retail admits the back-portal from the connector; acdream's side-CULL / the deleted EyeInsidePortalOpening rescue is the site).
  • cdb watchouts (CLAUDE.md): qd ONLY at a top-level threshold, never inside a conditional bp action (strands cdb → kills retail); dt acclient!Class @ecx field for reads (@@c++ is broken here); .sympath <local> (no srv*, the ; splits); spaced .if () lets qd detach.

4. Apparatus (re-create in the new session — temp files won't persist)

  • Flap capture: launch acdream with ACDREAM_PROBE_FLAP=1 (canonical live cmd, CLAUDE.md). Emits per frame: [flap] (root + per-portal proj/clip/side, PortalVisibilityBuilder.cs:790), [flap-cam] (root/viewerCell/playerCell/eyeInRoot/playerInRoot/terrain/outVisible, GameWindow.cs:11142-11152), [flap-sweep] (ok/resolved/pulledIn/viewerCell, PhysicsCameraCollisionProbe.cs:74-87), [outdoor-node] (root=IN/OUT). ⚠️ heavy (per-frame); output goes to the raw background-task .output, not the Tee'd log. Have the user HOLD the grey pose + turn to clear + turn back a couple times.
  • Offline cell geometry dump: tests/AcDream.Core.Tests/Physics/Issue186ConnectorCellGeometryInspectionTests.cs (Dump_ConnectorCells_ShellAndCollision) loads 0116/0117/0118 from the dat and prints render/physics poly counts + normal-axis buckets. No live launch needed. (That's how the §1 table was produced.)
  • Decomp workflow: wf_362bbeda-933 (this session) — the 4-tracer + synthesis analysis; results in its journal under the session's subagents/workflows/ dir.

5. DO-NOT-RETRY (each already refuted or a known trap)

  • Do NOT re-open the null-root / AD-20 / AD-21 angle — root is valid, eyeInRoot=Y.
  • Do NOT implement the color-clear gating — retail's gated Clear is depth/stencil (post-LScape::draw); matching it would add non-retail stale-content.
  • Do NOT re-add the deleted EyeInsidePortalOpening rescue (PortalVisibilityBuilder.cs:314-319) blindly — it caused the 0171↔0173 flood-cycle churn (the ORIGINAL doorway flap).
  • Do NOT widen PortalSideEpsilon (:38-56) — a documented root-lag band, NOT retail (F_EPSILON=0.0002); BR-4 refuted tightening/widening without eye-exact tracking.
  • Do NOT "fix" the flood by propagating an empty/edge-on portal — the empty-clip cull is retail-faithful.
  • The sparse connector geometry (0118 = 5 render / 3 physics polys) is FAITHFUL to the dat (retail draws the same). It is context, not the bug — the bug is that 0116 isn't drawn, not that 0118 is thin.

6. Key code sites

  • Frame-top clear (unconditional color+depth+stencil to fog): GameWindow.cs:8968-8983. NOT the fix.
  • Viewer-cell resolution: GameWindow.cs:9137-9148 (viewerCellId = _retailChaseCamera.ViewerCellId).
  • Camera boom sweep: PhysicsCameraCollisionProbe.cs (SweepEye, PathClipped, pulledIn, r.CellId=curr_cell).
  • Viewer-cell PICK: CellTransit.cs:862-916 (BuildCellSetAndPickContaining, center-based point_in_cell, current-cell-first hysteresis, port of find_cell_list pseudo_c:308788-308825).
  • Portal FLOOD: PortalVisibilityBuilder.cs (Build; side-test CameraOnInteriorSide :857-863; empty-clip cull :320-324; PortalSideEpsilon :38-56; the off-screen-vs-sliver note :818-821).

7. Retail decomp anchors

  • PView::DrawCells 0x005a4840 (decomp line 432709): Clear gated on outside_view.view_count>0 (432715), Clear at 432732 (depth/stencil, post-LScape::draw); cells drawn unconditionally after (432815+).
  • SmartBox::update_viewer 0x00453ce0: viewer_cell = sphere_path.curr_cell (the render root = swept cell).
  • PView::ClipPortals empty-clip gate if(ecx_8!=0) pc 005a5660; AddViewToPortals view_count!=0 pc 005a5357.
  • PView::InitCell side test decomp:432962. RGBAColor_Black @ 0x820fc0.

8. Acceptance

  • No grey flap passing through OR stopping in the connecting room at any camera angle; matches retail's seamless.
  • Regression: the outside→inside doorway + other multi-room houses + the #177/#181 render-flap suites unchanged.

9. Context: this session also FIXED #185 (unrelated, DONE + gated)

The outdoor-stairs "invisible wall" (07c5b832) — a shadow part-id uint32 overflow dropping stair collision. Unrelated to #186 (that was collision/registration; #186 is render/visibility). See docs/superpowers/specs/2026-07-08-185-outdoor-stairs-fix-design.md.