docs(render): Unified Cell Graph pivot — evidence model + Stage 1 spec
Pixel-grounded investigation concluded the indoor 'world from below' is a cell-MEMBERSHIP disagreement between render-side CellVisibility and physics-side ResolveCellId, not any single draw gate (terrain has one gated draw path; it leaks only on render null-root frames). Decision with user: full migration onto one retail CObjCell graph across physics+collision+render+streaming, staged in 5 verify-each cycles. This lands the evidence model + the Stage 1 (ObjCell scaffold) design. No code yet. - docs/research/2026-06-02-render-cell-membership-evidence.md (the why, from pixels) - docs/superpowers/specs/2026-06-02-unified-cell-graph-stage1-design.md (Stage 1) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1d7d8b1de4
commit
e8c7164ad9
2 changed files with 347 additions and 0 deletions
120
docs/research/2026-06-02-render-cell-membership-evidence.md
Normal file
120
docs/research/2026-06-02-render-cell-membership-evidence.md
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
# Render Cell-Membership — evidence model (2026-06-02)
|
||||
|
||||
> Canonical "why" for the **Unified Cell Graph (CObjCell)** pivot. This is the
|
||||
> pixel-grounded investigation that ended the week-long indoor-render saga.
|
||||
> Companion design spec: `docs/superpowers/specs/2026-06-02-unified-cell-graph-stage1-design.md`.
|
||||
> Predecessors: `docs/research/2026-05-31-render-architecture-reset-handoff.md`,
|
||||
> `docs/research/2026-06-01-render-reset-session2-handoff.md`.
|
||||
|
||||
## 0. One-line conclusion
|
||||
|
||||
The render pipeline maintains its **own** indoor-cell system (`CellVisibility` +
|
||||
`PortalVisibilityBuilder` + 3 gates) that is **separate from, and out of sync with,
|
||||
physics cell tracking**. When the render side's answer is "outdoor/null" — which
|
||||
happens on spawn-in, on within-building flicker, and on building entry — terrain
|
||||
draws ungated and you see the **world from below**. Point-fixing any one gate cannot
|
||||
win because the *visibility input* is unreliable and disagrees with physics. The fix
|
||||
is to unify onto **one cell membership** all systems share (the retail `CObjCell`
|
||||
model), executed as a staged migration (see spec).
|
||||
|
||||
## 1. Symptom
|
||||
|
||||
Standing inside the Holtburg cottage (room or cellar) the interior does not seal:
|
||||
the full outdoor world (terrain hills, water, scenery) renders and the player
|
||||
appears to float in it ("world from below"). The exterior renders correctly.
|
||||
|
||||
## 2. Method
|
||||
|
||||
Live client against local ACE, four launches, character `+Acdream`. Existing
|
||||
baseline probes only — **no code added** (investigation was report-only):
|
||||
`ACDREAM_PROBE_SHELL` (`[shell]`), `ACDREAM_PROBE_VIS` (`[vis]`),
|
||||
`ACDREAM_PROBE_FLAP` (`[flap]`/`[flap-cam]`), `ACDREAM_PROBE_ENVCELL`,
|
||||
`ACDREAM_PROBE_CELL` (`[cell-transit]`). Screenshots via PowerShell
|
||||
`CopyFromScreen` on the `AcDream.App` window → PNG → read.
|
||||
|
||||
**Gotcha (recorded so it isn't re-hit):** `Tee-Object` writes the log as **UTF-16LE**.
|
||||
GNU `grep` (Bash) silently matches nothing on it; the ripgrep-based Grep tool and
|
||||
PowerShell `Get-Content` decode it correctly. Early "0 probes" Bash reads were false
|
||||
negatives — always read these logs with ripgrep/PowerShell.
|
||||
|
||||
## 3. Findings (per waypoint)
|
||||
|
||||
1. **Exterior — correct.** Opaque textured cottage walls, building sitting on dirt,
|
||||
no holes. No indoor probes fire (outdoor root). The outdoor case is sound and the
|
||||
migration must preserve it.
|
||||
2. **Room, entered from outside (run 1) — indoor path NEVER engaged.**
|
||||
`[vis]=[shell]=[envcells]=0`. The "interior" we saw was the building-shell GfxObj +
|
||||
interior statics drawn by the *outdoor* entity pass; dark void where nothing covered.
|
||||
⇒ **outdoor→indoor building entry did not transit the player into the cell.**
|
||||
3. **Cellar, walked in (run 1) — indoor path engaged but leaks.**
|
||||
`[vis] root=0xA9B40174 cells=3 ids=[0174,0175,0171] outside(polys=0,planes=0)` —
|
||||
visibility computed correctly (windowless ⇒ empty OutsideView). `[shell]` shows all
|
||||
three shells render fine (`gfx=1 tr=0 zh=0`). `[flap-cam] terrain=Skip outVisible=False`
|
||||
— terrain *decision* is correct. Yet the screenshot is the full outdoor world.
|
||||
4. **First-person test (user-confirmed) — terrain shows even with the eye inside the
|
||||
cell.** ⇒ the leak is **not** the 3rd-person camera; it's the gate/root.
|
||||
5. **Spawn directly into the cellar (run 3) — the cleanest proof.** Only one
|
||||
`[cell-transit] 0x0 → 0xA9B40174 reason=teleport`. **Physics knew the indoor cell
|
||||
instantly.** Render fired **zero** `[vis]/[shell]` while standing still ⇒ world from
|
||||
below. ⇒ **render and physics disagree: physics indoor, render outdoor.**
|
||||
6. **Move from the cellar (run 3) — render re-engages on motion.** `[cell-transit]`
|
||||
flickers `0174↔0175↔0171` (`reason=resolver`); `[vis]`/`[shell]` start firing.
|
||||
`[vis] root=0xA9B40174 cells=1` vs `root=0xA9B40175 cells=3` — the portal walk from
|
||||
`0174` reaches **only itself** (incomplete traversal); the root flickers, so the
|
||||
sealed set flickers 1↔3 and gaps show.
|
||||
7. **Room from the cellar side (run 3).** The cottage interior is **one connected
|
||||
EnvCell group: `0171`(room/Z94) ↔ `0175`(stairs/Z93) ↔ `0174`(cellar/Z90)**; the
|
||||
cell flickers among the three while standing still. Render engaged (came up through
|
||||
the graph) yet still showed terrain at room level.
|
||||
|
||||
## 4. Causal model
|
||||
|
||||
- **Terrain has exactly one draw path** (`_terrain.Draw`, `GameWindow.cs:7415/7420`),
|
||||
both call sites behind the `TerrainClipMode` gate; **no second terrain/water/skybox
|
||||
source exists** (code-confirmed). So terrain can only appear when `terrainClipMode
|
||||
!= Skip`, which happens when the render **root resolves outdoor** (`clipRoot==null`
|
||||
→ the `else` branch at `GameWindow.cs:7352` → default `Planes` → ungated terrain).
|
||||
- **Two independent cell resolvers.** Render root = `CellVisibility.ComputeVisibility
|
||||
(visRootPos)` → `FindCameraCell` (AABB `PointInCell`, render-side registry).
|
||||
Physics = `PhysicsEngine.ResolveCellId` → `PlayerMovementController.CellId`
|
||||
(BSP-based, separate registry). They disagree.
|
||||
- **Render registration lags/misses.** Render cells enter `CellVisibility` via
|
||||
`_pendingCells` → render-thread `AddCell` (`GameWindow.cs:5749`), a *different
|
||||
lifecycle* from physics' worker-thread `CacheCellStruct`. On spawn-in the render
|
||||
side has nothing registered yet ⇒ `clipRoot==null` ⇒ terrain.
|
||||
- **The `[flap-cam]` probe can't see the failure frames** — it only logs inside
|
||||
`if (clipRoot is not null)`. The `null`-root frames (where terrain draws) are
|
||||
invisible to it, which is why prior log-archaeology missed this.
|
||||
- **Two failure variants, one disease:** building **entry** (outdoor→indoor) leaves
|
||||
you outdoor (finding 2); **within-building** the membership flickers and the portal
|
||||
walk is incomplete (findings 6–7). Both are "cell membership recomputed from
|
||||
geometry, unreliable, and disagreeing across systems."
|
||||
|
||||
## 5. Verdict → the pivot
|
||||
|
||||
Unify onto **one cell membership** every system shares — the retail `CObjCell` model
|
||||
(one `curr_cell`, `GetVisible` magnitude dispatch, portal graph). Scope decided with
|
||||
the user (2026-06-02): **full migration** across physics + collision + render +
|
||||
streaming, freeze lifted as needed, executed as a 5-stage chain that keeps the client
|
||||
runnable + visually verified at each step. Design: the companion Stage 1 spec.
|
||||
|
||||
## 6. Disproven / do-not-repeat (this session + predecessors)
|
||||
|
||||
- Camera/eye as the root cause (FP test: terrain shows with eye inside the cell).
|
||||
- A second terrain renderer (code-confirmed there is none).
|
||||
- "Render-gate consolidation only" as sufficient (the broken input is the *membership*,
|
||||
upstream of the gate).
|
||||
- Earlier disproven: H1 PVS grounding, H2 PortalSide, cull mode, shell
|
||||
geometry/texture missing, zoom-confound, the screen-space stencil-mask rule
|
||||
(breaks outdoors), flag-based per-entity gate routing (= patchwork).
|
||||
|
||||
## 7. Grounding research (full reports archived in session transcript)
|
||||
|
||||
Two research agents produced the retail structure + acdream inventory that ground the
|
||||
design; their key facts are folded into the spec's "Retail grounding" and "acdream
|
||||
current state" sections with citations. Decomp anchors:
|
||||
`CObjCell` (`acclient.h:30915`), `CEnvCell` (`:32072`), `CLandCell` (`:31886`),
|
||||
`CSortCell` (`:31880`), `CCellPortal` (`:32300`), `CBldPortal` (`:32094`),
|
||||
`CellStruct` (`:32275`); `GetVisible` (`pseudo_c:308209`), `change_cell` (`:281192`),
|
||||
`find_cell_list` (`:308742`), `ConstructView` (`:433750`/`:433827`),
|
||||
`InitCell` (`:432896`), `find_visible_child_cell` (`:311397`).
|
||||
Loading…
Add table
Add a link
Reference in a new issue