docs(render): Phase W (rev) — 4-model research + transition-membership/PView design

Four independent decomp studies (Opus 4.8 x2, Sonnet 4.6, external Codex)
converge: retail carries the cell through the collision sweep (validate_transition
advances curr_cell only on an accepted move, reverts on a block) and commits it in
SetPositionInternal — it never re-derives membership from a static resting position.
acdream already ports the sweep machinery (sp.CurCellId/CheckCellId, ValidateTransition,
CheckOtherCells) but ResolveWithTransition discards the swept cell and re-derives
statically via ResolveCellId (PhysicsEngine.cs:909/928) — the root of the
0170<->0031 doorway/cellar ping-pong. The do_not_load_cells prune is secondary
(static/cross-cell lists), not the anti-flicker; W2b was doubly misplaced and is reverted.

Render: one PView::ConstructView portal traversal over the same cell graph, rooted at
the physics current cell; seen_outside (not a dungeon flag) gates landscape; the outside
draws through exit portals clipped to the doorway (no blue-hole, no stencil split).
Dungeons/interiors share the machinery; "underground" is emergent.

Design doc lays out the staged, evidence-first rewrite (Stage 0 diagnostic ->
Stage 1 transition-owned membership [visual gate] -> Stage 2 CELLARRAY/prune parity ->
Stages 3-5 render root + PView seal + entity clip). Adds the shared research prompt and
all four study reports as the grounding record.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-02 13:58:51 +02:00
parent 2acd8f9e1d
commit 840c1b6442
6 changed files with 3608 additions and 0 deletions

View file

@ -0,0 +1,129 @@
# Research task — Retail AC: cell transitions, underground/dungeons, and seamless inside/outside rendering
> **Shared prompt for a multi-model study (2026-06-02).** The same prompt is run on
> several models (Opus 4.6/4.7/4.8, Sonnet 4.6, and an external model) so we can compare
> independent reads before committing to an architecture. Study the **source** (retail
> decomp + reference repos) and **cite everything** — do not guess. Depth + citations
> matter far more than brevity.
## 0. Why this study exists (context)
**acdream** is a modern C# .NET port of the retail Asheron's Call client (Sept 2013 EoR
build). The rule: *the code is modern, the behavior is retail.* Every AC-specific algorithm
is ported faithfully from the named retail decomp.
We are at an architecture decision and want ground truth before choosing. Two coupled
problems:
1. **Cell-membership flicker (physics).** The player's "current cell" ping-pongs at
boundaries — at a near-static position the cell flips e.g. `0xA9B40170` (indoor cottage
vestibule) ↔ `0xA9B40031` (outdoor landcell) every physics tick, and also
`vestibule ↔ room` and inside the cellar. Root finding so far: acdream runs retail's
collision *sweep* but then **discards the swept cell** and **re-derives the cell from the
final static position every tick** (`PhysicsEngine.ResolveCellId`), which flips as the
collision push-back jitters the end position ±~8 cm across the boundary.
2. **Non-seamless indoor render.** Standing inside a cottage/cellar the interior does not
seal: the ceiling isn't capped, the doorway opening shows the blue clear-color instead of
the real outside (no sky / no rain visible through the door), entities/particles bleed
through walls, and at the threshold the view strobes between "indoor (incomplete)" and
"outdoor." acdream's render maintains its **own** cell/visibility system separate from
physics; we believe retail renders inside+outside seamlessly through a single
portal-visibility traversal.
We have a candidate fix direction ("track the cell through the transition sweep like
retail's `validate_transition` + `change_cell`, drop the static re-derive, add the
`do_not_load_cells` prune, and make the render obey one portal-visibility traversal"), but
we want a **solid, decomp-grounded understanding of how retail ACTUALLY does all of this**
before we commit — patches/guesses in this exact area have failed ~10× historically.
## 1. Your deliverable
A comprehensive, decomp-cited markdown report. For **every** non-trivial claim, cite the
retail function **name + address** (from the decomp) or the reference **file:line** you
verified it from. Include short pseudocode where it clarifies control flow. End with a
concrete "Recommended acdream architecture" section (questions D1416).
Write your report to the output path given to you (e.g.
`docs/research/2026-06-02-retail-cell-render-study-<model>.md`).
## 2. Sources to study
**Primary oracle — the retail decomp (study this first and most):**
- `docs/research/named-retail/acclient_2013_pseudo_c.txt` — 1.4 M lines of named pseudo-C.
Grep by `Class::method` (e.g. `CTransition::validate_transition`).
- `docs/research/named-retail/acclient.h` — verbatim retail struct definitions.
- `docs/research/named-retail/symbols.json` — name ↔ address index (grep by name or addr).
**Reference repos (cross-check at least two per topic; the intersection is usually truth):**
- `references/ACE/` — server-side C# physics port. `Source/ACE.Server/Physics/` has
`Common/` (ObjCell, EnvCell, LandCell, Position), `Animation/` and the transition/
sphere-path/cell logic. Authoritative C# reading of `find_cell_list`, `change_cell`,
`Transition`, `SpherePath`.
- `references/ACViewer/` — MonoGame client that renders world + dungeons. `Physics/Common/`
(EnvCell, ObjCell, CellArray) and `Render/` (how cells/portals are drawn).
- `references/WorldBuilder/` — the render base acdream extracted from. EnvCell/portal/
visibility/scenery managers; how it draws interiors + the (flat-stencil) inside/outside
split it uses.
- `references/Chorizite.ACProtocol/`, `references/AC2D/`, `references/holtburger/` — use as
relevant (struct field order, simpler client confirmations).
**acdream's current code (so your synthesis is actionable, not abstract):**
- `src/AcDream.Core/Physics/PhysicsEngine.cs``ResolveCellId` (~:272), `ResolveWithTransition` (~:651, see the two `ResolveCellId(sp.GlobalSphere[0].Origin,…)` calls at ~:909/:928 that discard the swept cell).
- `src/AcDream.Core/Physics/CellTransit.cs``FindCellList`/`FindCellSet`/`BuildCellSetAndPickContaining`, `FindTransitCellsSphere`, `AddAllOutsideCells`, `CheckBuildingTransit` (note: NO `do_not_load_cells` prune today).
- `src/AcDream.Core/Physics/TransitionTypes.cs``SpherePath` (`CheckCellId`/`CheckPos`, only set at `InitPath`/reset — NOT advanced through the sweep), `Transition` (`FindEnvCollisions`, `CheckOtherCells`).
- `src/AcDream.Core/World/Cells/` — the W1 unified cell graph (`ObjCell`/`EnvCell`/`LandCell`/`CellGraph`/`CellPortal`).
- `src/AcDream.App/Rendering/CellVisibility.cs` + `PortalVisibilityBuilder.cs` + `GameWindow.cs` (terrain/shell/entity draw gates ~:71507420).
## 3. Decomp anchors (verified starting points — confirm and expand; find more yourself)
Physics / cell tracking:
- `CPhysicsObj::change_cell` @ `0x00513390` (pseudo_c ~:281192) — the leave/enter setter.
- `CPhysicsObj::SetPositionInternal` @ `0x00515330` (~:283399) — reads `arg2->sphere_path.curr_cell`, calls `change_cell` only when it differs.
- `CTransition::validate_transition` @ `0x0050aa70` (~:272547) — advances `sphere_path.curr_cell = check_cell` on an accepted move (~:272608-272619); resets to `curr_cell` on a block/slide (~:272593).
- `CTransition::check_collisions` @ `0x0050aa00` (~:272530); `CTransition::check_other_cells` @ `0x0050ae50` (~:272717) — calls `find_cell_list`.
- `CTransition::transitional_insert` @ `0x0050b6f0` (~:273137) — the sweep stepper.
- `CObjCell::find_cell_list` @ `0x0052b4e0` (~:308742) — builds the cell array, picks the containing cell (`*arg5`), applies the `do_not_load_cells` prune (~:308829-308867).
- `CObjCell::GetVisible` (pseudo_c ~:308209) magnitude dispatch; `CEnvCell::GetVisible`, `CLandCell::GetVisible`, `CLandCell::add_all_outside_cells`, `CObjCell::point_in_cell` (vtable +0x84), `CEnvCell::find_transit_cells`.
Cell structs (acclient.h): `CObjCell` (:30915), `CEnvCell` (:32072), `CLandCell` (:31886),
`CSortCell` (:31880), `CCellPortal` (:32300), `CBldPortal` (:32094), `CellStruct` (:32275),
plus `SPHEREPATH`, `CELLARRAY`, `Position`.
Rendering / visibility (verify addresses; these are from prior notes):
- `ConstructView` (~:433750 / :433827), `InitCell` (~:432896),
`CObjCell::find_visible_child_cell` (~:311397). Find the full PVS/PView traversal,
the EnvCell draw path, how exit portals / `seen_outside` feed render, terrain/sky gating.
## 4. Questions to answer (be comprehensive + cited)
### A. Cell membership & transitions (physics)
1. How does retail represent and store "the cell I'm in" (`curr_cell`)? When/where is it updated? Trace the full chain: per-step sweep → `find_cell_list``validate_transition` advance → `SetPositionInternal`/`change_cell`.
2. Exactly how does `find_cell_list` build the candidate cell array, and how does it pick the single containing cell (`*arg5`)? What is the `do_not_load_cells` prune — when is it set, what does it remove, and what stability does it buy?
3. **Precisely how does retail avoid cell flicker** at a doorway / indoor↔outdoor / room↔room? Is it directed portal-crossing, swept-path containment with accept-on-move, the prune, `point_in_cell` semantics, or a combination? What guarantees a blocked/standing-still step does NOT change the cell?
4. How does a player transition indoor→outdoor (exit) and outdoor→indoor (enter)? Between interior cells? What do `CCellPortal` vs `CBldPortal` do, and how does the exit portal / outdoor landcell get added and chosen?
5. Is the cell ARRAY (for collision) the same mechanism as `curr_cell` (membership), or two? How do they relate within one transition?
### B. Underground / dungeons
6. How are dungeons represented in the dats and at runtime — EnvCell graph, portals, the absence of terrain? How does this differ from building interiors (cottage/inn) which sit ON a landblock with terrain?
7. How does the player move through a dungeon: cell tracking, dungeon cell loading/streaming, and how the engine knows there is no sky/terrain to draw?
8. Is there an explicit "underground" flag/state (e.g. on `Position`/landblock/cell), or is "underground" simply "current cell is an EnvCell with no outdoor reachability"? Cite the flag/field if it exists.
### C. Rendering inside and outside (the seamless seal)
9. Trace retail's render visibility: how does it build the visible set in ONE pass (`ConstructView`/`InitCell`/`find_visible_child_cell`/PVS/PView)? What does it output (visible cell list, per-portal clip regions/frustum)?
10. How does retail draw the OUTSIDE seen through a doorway/window from inside (sky, rain, terrain, exterior buildings) so there is **no blue clear-color hole**? How are exit portals / `seen_outside` handled in the render traversal?
11. How does retail seal interiors — cap ceilings, prevent the outdoor world from bleeding in, and clip entities/particles to the visible cells?
12. How does retail decide to draw terrain + sky vs not, as a function of the current cell (indoor / outdoor / underground)?
13. **Is the render cell/visibility the SAME `curr_cell`/cell-graph as physics, or separate?** Trace whether render reads the physics `curr_cell` and traverses the shared cell graph, or maintains its own. (Central to acdream's decision.)
### D. Synthesis for acdream (be concrete)
14. Given acdream re-derives membership statically per tick (instead of tracking it through the sweep) and renders with a separate cell system, what is the **retail-faithful target architecture** we should port?
15. Specifically: should membership be advanced inside the transition sweep (port `validate_transition`'s `curr_cell` advance + the `do_not_load_cells` prune + drop the static `ResolveCellId`, reading `sphere_path.curr_cell` like `SetPositionInternal`)? Should the render obey the physics `curr_cell` + a single portal-visibility traversal? Justify from the decomp.
16. List the **must-port functions** (with decomp addresses), the integration order, the main risks (esp. anything touching the collision sweep where acdream has a history of bugs), and what conformance tests would prove faithfulness.
## 5. Method reminders
- Grep the named decomp by `Class::method` FIRST; confirm addresses in `symbols.json`.
- The decomp is ground truth; ACE/ACViewer/WorldBuilder are interpretation aids — when they
disagree, the decomp wins, but note the disagreement.
- Distinguish what you VERIFIED in source from what you INFER. Flag inferences.
- Cite: `function_name @ 0xADDR (pseudo_c:LINE)` or `repo/path/File.cs:LINE`.