acdream/docs/research/2026-06-02-retail-cell-render-research-prompt.md
Erik 840c1b6442 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>
2026-06-02 13:58:51 +02:00

11 KiB
Raw Blame History

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.csResolveCellId (: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.csFindCellList/FindCellSet/BuildCellSetAndPickContaining, FindTransitCellsSphere, AddAllOutsideCells, CheckBuildingTransit (note: NO do_not_load_cells prune today).
  • src/AcDream.Core/Physics/TransitionTypes.csSpherePath (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_listvalidate_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

  1. 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?
  2. 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?
  3. 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)

  1. 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)?
  2. 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?
  3. How does retail seal interiors — cap ceilings, prevent the outdoor world from bleeding in, and clip entities/particles to the visible cells?
  4. How does retail decide to draw terrain + sky vs not, as a function of the current cell (indoor / outdoor / underground)?
  5. 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)

  1. 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?
  2. 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.
  3. 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.