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>
11 KiB
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:
-
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 alsovestibule ↔ roomand 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. -
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 D14–16).
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 byClass::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/hasCommon/(ObjCell, EnvCell, LandCell, Position),Animation/and the transition/ sphere-path/cell logic. Authoritative C# reading offind_cell_list,change_cell,Transition,SpherePath.references/ACViewer/— MonoGame client that renders world + dungeons.Physics/Common/(EnvCell, ObjCell, CellArray) andRender/(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),:651, see the twoResolveWithTransition(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: NOdo_not_load_cellsprune today).src/AcDream.Core/Physics/TransitionTypes.cs—SpherePath(CheckCellId/CheckPos, only set atInitPath/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 ~:7150–7420).
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) — readsarg2->sphere_path.curr_cell, callschange_cellonly when it differs.CTransition::validate_transition@0x0050aa70(:272547) — advances:272608-272619); resets tosphere_path.curr_cell = check_cellon an accepted move (curr_cellon a block/slide (~:272593).CTransition::check_collisions@0x0050aa00(:272530);:272717) — callsCTransition::check_other_cells@0x0050ae50(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 (:308829-308867).*arg5), applies thedo_not_load_cellsprune (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),:432896),InitCell(CObjCell::find_visible_child_cell(~:311397). Find the full PVS/PView traversal, the EnvCell draw path, how exit portals /seen_outsidefeed render, terrain/sky gating.
4. Questions to answer (be comprehensive + cited)
A. Cell membership & transitions (physics)
- 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_transitionadvance →SetPositionInternal/change_cell. - Exactly how does
find_cell_listbuild the candidate cell array, and how does it pick the single containing cell (*arg5)? What is thedo_not_load_cellsprune — when is it set, what does it remove, and what stability does it buy? - 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_cellsemantics, or a combination? What guarantees a blocked/standing-still step does NOT change the cell? - How does a player transition indoor→outdoor (exit) and outdoor→indoor (enter)? Between interior cells? What do
CCellPortalvsCBldPortaldo, and how does the exit portal / outdoor landcell get added and chosen? - 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
- 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?
- 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?
- 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)
- 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)? - 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_outsidehandled in the render traversal? - How does retail seal interiors — cap ceilings, prevent the outdoor world from bleeding in, and clip entities/particles to the visible cells?
- How does retail decide to draw terrain + sky vs not, as a function of the current cell (indoor / outdoor / underground)?
- Is the render cell/visibility the SAME
curr_cell/cell-graph as physics, or separate? Trace whether render reads the physicscurr_celland traverses the shared cell graph, or maintains its own. (Central to acdream's decision.)
D. Synthesis for acdream (be concrete)
- 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?
- Specifically: should membership be advanced inside the transition sweep (port
validate_transition'scurr_celladvance + thedo_not_load_cellsprune + drop the staticResolveCellId, readingsphere_path.curr_celllikeSetPositionInternal)? Should the render obey the physicscurr_cell+ a single portal-visibility traversal? Justify from the decomp. - 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::methodFIRST; confirm addresses insymbols.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)orrepo/path/File.cs:LINE.