The G.3a visual gate ran a real PlayerTeleport into the 0x0007 dungeon. The core hold+place worked (grounded on the dungeon floor, no ocean) and Bug A (landblock- prefix mis-stamp) is fixed (2ce5e5c). But the gate proved #95 (portal-graph visibility blowup, ~9.1M instances/frame) is LIVE under the current pipeline — my plan's "likely superseded / conditional G.3b" premise was wrong. Spec §2.5/§3.2 + ISSUES #133/#95 updated: G.3b (grab_visible_cells stab_list bounding) is REQUIRED, needs its own grounding/brainstorm. Also noted: the render-only hydration decouple was reverted (e7058ca) for making the player invisible at Holtburg. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
26 KiB
Phase G.3 — Dungeon Support (Design Spec)
Status: APPROVED design (brainstorm 2026-06-13). Next:
writing-plans. Milestone: M1.5 ("Indoor world feels right"). G.3 is the remaining M1.5 exit-gate. M2 (CombatMath) stays deferred until this lands. Issue: #133 (teleport-into-dungeon snaps to ocean) + #95 (dungeon portal-graph visibility blowup — re-assessed below). Supersedes the §12 port-plan ofdocs/research/deepdives/r09-dungeon-portal-space.md: most of R9's "new types" (EnvCell loader/renderer/physics, PortalVisibility BFS, multi-cell transit) already shipped and power the building/cellar demo. r09 stays the retail contract reference for the wire formats, the EnvCell/CellPortal layout, and the recall taxonomy.
0. TL;DR
Dungeons don't work because of one timing+placement gap on one code path,
not a terrain-less-pipeline rewrite. A dungeon landblock (e.g. 0x0125, the
Holtburg-area meeting hall) is a flat-terrain landblock (LandBlock
present, all-zero heights) + 71 EnvCells + no buildings — it already streams,
renders, and collides through the existing pipeline. The teleport-arrival
handler snaps the player before that landblock has streamed in, so Resolve
falls back to the resident Holtburg blocks and lands the player in ocean.
The fix is retail's own shape: hold the player in portal space until the
destination cell is hydrated, then place into the EnvCell — reusing the
#107/#111 login machinery — and then layer retail's portal-tunnel visual
(TeleportAnimState) on top. We ship it in four installments, gated by one
visual acceptance test.
1. Corrected root cause (verified)
1.1 The "terrain-less landblock" framing is WRONG (dat-verified)
A prior research pass assumed dungeon landblocks have no LandBlock record, so
LandblockLoader.Load returns null and the whole streaming/render/physics
pipeline needs terrain-less support. A direct dat probe
(DungeonLandblockDatProbeTests, committed) refutes that:
0x0125 (dungeon): LandBlock 0x0125FFFF PRESENT, Height[81] allZero=True (flat)
LandBlockInfo: NumCells=71, Buildings=0, Objects=0
EnvCells 0x0100.. present (the 71 dungeon rooms)
0xA9B4 (Holtburg): LandBlock PRESENT, heights non-zero; NumCells=123, Buildings=12, Objects=114
A dungeon landblock is a flat-terrain landblock (lowest/"ocean" terrain
height index) plus its EnvCells, no buildings/objects. LandblockLoader.Load
returns a valid flat landblock; the terrain mesh builds a flat plane;
PhysicsEngine.AddLandblock gets a valid flat TerrainSurface. The existing
pipeline already streams a dungeon landblock. This matches ACE's IsDungeon
(all heights 0 + NumCells > 0 + no buildings — Landblock.cs:575) and the
single-landblock rule (Player_Tick.cs:548-560 forbids moving between dungeon
landblocks without a teleport — so "multi-landblock dungeon LOD" is moot).
1.2 The real blocker: teleport TIMING + PLACEMENT
OnLivePositionUpdated (src/AcDream.App/Rendering/GameWindow.cs:4877-4961)
detects teleport arrival as any player position update while in PortalSpace
(correct, per #107), then unconditionally:
- Recenters streaming to the destination landblock (
_liveCenterX/Y,:4908-4925). - Immediately calls
_physicsEngine.Resolve(destPos, destCell, …)to snap the player (:4927-4931) — before the destination landblock has streamed in. - Snaps entity + controller (
:4935-4939), exits PortalSpace (:4950), sendsLoginComplete(:4953-4959).
Because the dungeon landblock isn't resident yet, Resolve can't find the
destination cell, falls back to an outdoor scan against the still-resident
Holtburg landblocks, and snaps to 0xA9B3000E (Holtburg's south edge — local
(30,−60) maps into the block south of the A9B4 spawn). Streaming then shifts
the frame out from under the player → they slide south into ocean. ACE logs the
matching failed transition for +Acdream from 0x01250126 … to 0xA9B0000E …
chain (captured in launch-dungeon-diag.log).
There is no hold-until-hydration on the teleport-arrival path. The #107
login path directly above it (GameWindow.cs:1010-1024) HAS exactly this gate;
the teleport path doesn't.
2. Grounded seam facts (the design rests on these)
All five verified against current code this session (high confidence).
2.1 Teleport-arrival + PortalSpace FSM
OnTeleportStarted(GameWindow.cs:~4971-4976) — onPlayerTeleport (0xF751)sets_playerController.State = PlayerState.PortalSpace, freezing movement.PlayerMovementController.Update(PlayerMovementController.cs:840-854) returns a zero-movement result whileState == PortalSpace— PortalSpace already doubles as the input-freeze. It can equally serve as the hydration-wait gate.- Exit is only via the arrival detection in
OnLivePositionUpdated(:4880). No timeout, no cell-hydration gate today.
2.2 #107/#111 login machinery (directly reusable)
PhysicsEngine.IsSpawnCellReady(cellId)(PhysicsEngine.cs:468-472): outdoor (cellId & 0xFFFF < 0x0100) → always ready; indoor →DataCache.GetCellStruct(cellId) is not null(the cell's physics BSP has hydrated).IsSpawnClaimUnhydratable(claim)(GameWindow.cs:11728-11748): fetches the datLandBlockInfoat(lb & 0xFFFF0000) | 0xFFFE; a claim whose low word is>= 0x0100 + NumCells(orNumCells==0) can never hydrate → reject fast (distinguishes a bogus claim from a not-yet-streamed one).- #107 login hold (
GameWindow.cs:1010-1024):isSpawnGroundReadywaits for terrain AND (claim outdoor ORIsSpawnCellReadyORIsSpawnClaimUnhydratable). No timeout today (login can afford to wait forever; teleport cannot — see §5). - #111 validated-claim placement (
PhysicsEngine.cs:626-646): whensnapDiag (zero-delta) && adjustedFound && indoor, place viaWalkableFloorZNearest(:383-406) — projects Z onto the claim cell's own physics walkable polygons (normal.Z >= PhysicsGlobals.FloorZ, 0.6642), cell-local, nearest to the reference Z. Returnsnullif the cell isn't hydrated → falls through to the legacybestCellscan (the ocean bug). - The teleport-arrival Resolve call is already the same shape as login entry. The gate only needs to sit in front of it; no change to Resolve or WalkableFloorZNearest. (Both already key on the full prefixed cell id + indoor/outdoor.)
2.3 Streaming far recenter (works as-is)
StreamingRegion.RecenterTo(StreamingRegion.cs:180-283) recomputes the near/far Chebyshev window from scratch around the new center — a 42 km jump is treated identically to a 1-step move. No incremental-movement assumption.- Drain:
StreamingControllerapplies ≤MaxCompletionsPerFrame(default 4) results/frame;ApplyLoadedTerrainLocked(GameWindow.cs:5941-6150) does GPU upload + cell-visibility registration + AABB +PhysicsEngine.AddLandblock+ EnvCell/portal registration. Estimate: ~7-8 frames (~120-130 ms) to hydrate a 5×5 near window; physics ready +1-2 frames. - Recenter keeps the old neighborhood until hysteresis unload (NearRadius+2 demote, FarRadius+2 unload), so the player isn't instantly stranded.
- New code needed: reuse the #107 login-gate terrain-ready signal
_physicsEngine.SampleTerrainZ(x,y) is not null(non-null once the destination terrain landblock has applied) — no separate "landblock applied" query is required. Plus dest-coord validation (reject out-of-world coords — a malformed portal dest would otherwise leave the player in an invisible, unloadable landblock).
2.4 EnvCell hydration coupling (latent landmine — decouple)
- In
BuildInteriorEntitiesForStreaming(GameWindow.cs:5564-5651), bothBuildLoadedCell(the portal-visibility node) and_physicsDataCache.CacheCellStruct(the physics BSP) sit inside the render guardif (cellSubMeshes.Count > 0)(:5602). A cell whose render mesh is empty (CellMesh.Buildreturns nothing — e.g. all-untextured/Stippling.NoPospolys) silently gets no visibility node and no collision, even if it has walkable physics polygons.CellTransit.FindTransitCellsSpherethenGetCellStruct → null → continue(silently skips it) → fall-through-floor. - A normal dungeon room has textured walls → non-empty submeshes → the guard
passes, so this is probably not the meeting-hall blocker — but it is a real
correctness landmine for any geometry-less collision cell, and decoupling is
cheap and retail-correct (physics/visibility do not depend on visible geometry).
Fix: gate
CacheCellStructoncellStruct.PhysicsBSP != nullandBuildLoadedCelloncellStruct != null, independent of the render submesh count. (CacheCellStructalready early-returns on null BSP internally —PhysicsDataCache.cs:172— so moving it out is safe.)
2.5 #95 — dungeon portal-graph visibility blowup (RE-ASSESSED: likely superseded)
- ISSUES.md #95 (
888-913): on a 2026-05-21 A6.P1 scen5 (Town Network hub) trace,visibleCellsper cell exploded to 135-145 with spurious cells from landblocks0x020A/0x0408(other dungeons). Its "Files" point at the WBEnvCellRenderManager/VisibilityManager+ the Streaming cell-cache. - That code path was DELETED by the T1-T6 render rewrite (2026-06-11) (T4:
"per-frame ACME BFS deleted… InteriorRenderer/DrawPortal deleted"). The current
flood,
PortalVisibilityBuilder.Build, (a) confines neighbors to the camera cell's landblock (lbMask = cameraCell.CellId & 0xFFFF0000,:131) and (b) has enqueue-once termination (queuedHashSet,:165— "at most N cells are ever processed"). Since AC dungeons are single-landblock, that confinement is correct, and the cross-landblock 135-cell blowup structurally cannot reproduce: a single-landblock flood visits ≤NumCellsdistinct cells (71 for the meeting hall). - Verdict (pre-gate, 2026-06-13 AM): #95's evidence is stale, from a deleted path; the current pipeline looked bounded. Treated #95 as likely superseded.
- ⚠️ GATE CORRECTION (2026-06-13 PM — #95 is CONFIRMED LIVE): the G.3a visual
gate ran a real
PlayerTeleportinto the0x0007dungeon (Town Network). The core hold+place worked (player grounded on the dungeon floor, z=0 — no ocean), but WB-DIAG exploded to entSeen=6.5M / instances=9.1M / drawsIssued=590K per frame (vs. 3345 / 4667 at Holtburg), with a flood of[mesh-miss] 0x000100xxxxinterior re-requests → the dungeon renders as "thin air." #95 reproduces under the current Option-A pipeline. The "bounded flood" reasoning was wrong for the0x0007dungeon (the grounding agent's "still live" verdict was correct; this doc over-discounted it). G.3b is now REQUIRED, not conditional (§3.2). The retail-faithful fix shape stands: portCEnvCell::grab_visible_cells(:311878) stab_list bounding — aseen_outside==0cell walks ONLY itsstab_list.
3. The plan (Approach C — phased full-G.3)
Each installment lands a complete retail behavior (the BR-2 half-port lesson). The visual gate sits as early as possible, right after the core.
3.1 G.3a — Core teleport-into-dungeon (the blocker)
Goal: teleporting into the meeting-hall dungeon lands the player standing in
the dungeon cell, on the floor, with walls blocking — no ocean, no ACE
failed transition spam.
New component — TeleportArrivalController (src/AcDream.App/World/):
- Owns a small phase:
Idle / Holding / Placing, plus_pendingArrival(destPos, destCellId, deadline). - Lives outside
GameWindow(Code Structure Rule 1: no new feature bodies in the god-object).GameWindow.OnLivePositionUpdatedhands the arrival to it and calls its per-frameTick;GameWindowkeeps only the wiring. - Unit-testable in isolation (no GL, fake readiness predicate + fake Resolve).
Control flow (replaces the unconditional snap at GameWindow.cs:4927-4950):
- On arrival update in PortalSpace: validate
destCellId's landblock coords are in-world; recenter streaming + prioritize-load the dest landblock (existing path); stash_pendingArrival; enterHolding. Re-sendLoginCompleteimmediately (holtburger-conformant —messages.rs:434; do not wait for assets to send it). - Each frame in
Holding, evaluate the readiness predicate:IsSpawnClaimUnhydratable(destCell)→ impossible claim: stop holding, place via the safety-net demote (loud log), exit PortalSpace.now > deadline(timeout, ~10 s) → force-snap via safety-net demote + loud log, exit PortalSpace. (See §5 — failure-surfacing, not symptom-masking.)SampleTerrainZ(destPos) != null && (outdoor || IsSpawnCellReady(destCell))→ ready: go to 3.- else stay frozen, retry next frame.
Placing: call the existingResolve(destPos, destCell, Vector3.Zero, …). Because the cell is now hydrated, Resolve takes the #111 validated-claim branch →WalkableFloorZNearestgrounds the player on the EnvCell floor. Snap entity- controller (existing
:4935-4939code), exit PortalSpace, resume input.
- controller (existing
Readiness predicate — reuse the #107 login triplet (no new query). The
hold gates on exactly the three checks the login auto-entry gate already uses
(GameWindow.cs:1010-1024), evaluated against the teleport's (destPos, destCell) instead of the spawn claim: SampleTerrainZ(destPos.X, destPos.Y) is not null (destination terrain applied) ∧ (outdoor cell OR
IsSpawnCellReady(destCell)); IsSpawnClaimUnhydratable(destCell) short-circuits
an impossible claim to immediate placement. This reuses proven, validated code
rather than introducing a parallel "landblock applied" query.
Dest-coord validation: in OnLivePositionUpdated, reject a destination whose
(lbX, lbY) is out of the world grid before recenter; log + abort the teleport
hold rather than recenter to a phantom block.
Hydration decouple (§2.4): move BuildLoadedCell + CacheCellStruct out of
the cellSubMeshes.Count > 0 guard in BuildInteriorEntitiesForStreaming. Gate
each on its own non-null precondition.
Acceptance (G.3a): the visual gate in §6. This gate also empirically settles #95 (does the flood blow up?) and the hydration coupling (does collision work?).
3.2 G.3b — #95 visibility bounding (REQUIRED — gate-confirmed 2026-06-13)
The G.3a gate confirmed the blowup (9.1M instances/frame in 0x0007), so this
is the next blocker, not a conditional follow-up. The dungeon will not render
until the portal-visibility flood is bounded to the dungeon's own cell adjacency.
Fix: port retail CEnvCell::grab_visible_cells (:311878) — a cell with
seen_outside == 0 loads ZERO terrain and walks ONLY its stab_list of adjacent
EnvCells; the portal graph is bounded by the dungeon's own cell adjacency, never a
radius / never the whole resident cell set. This is a render-pipeline change in
PortalVisibilityBuilder (the flap-/DO-NOT-RETRY-sensitive area) and needs its own
grounding + brainstorm before implementation (verify the dat carries the stab_list
and acdream's EnvCell loader parses it; confirm the seen_outside flag is read;
decide how it composes with the outdoor-root look-in floods). NOT a wing-it
inline fix.
Open question surfaced at the gate (possible Bug C): even with Bug A fixed
(placement keeps the dungeon prefix, 2ce5e5c), the dungeon's negative-local-Y
coordinate frame may cause the per-tick membership/landblock resolution to drift
(the ACE movement pre-validation failed spam). Re-gate after Bug A to see if it
persists; if so, fold the dungeon-coordinate membership handling into G.3b's
grounding (it is plausibly the same seen_outside / cross-landblock root as #95).
3.3 G.3c — Portal-tunnel loading visual (faithful TeleportAnimState)
Goal: the retail portal-space transition, ported faithfully (user decision 2026-06-13). Reconciles the older r09 §6 ("there is no loading screen") with the named-retail decomp where this FSM actually lives.
Oracle: gmSmartBoxUI::BeginTeleportAnimation (004d6300, named-retail line
218888) + the per-frame FSM (219405-219774). States:
TAS_WORLD_FADE_OUT → TAS_TUNNEL_FADE_IN → TAS_TUNNEL / TAS_TUNNEL_CONTINUE → TAS_TUNNEL_FADE_OUT → TAS_WORLD_FADE_IN → (off). m_pPortalSpace is a
UIElement_Viewport rendering the tunnel scene (creature-mode objects +
DISTANT_LIGHT + smartbox FOV; SetVisible(1) on enter, SetVisible(0) on the
TAS_TUNNEL_FADE_OUT → TAS_WORLD_FADE_IN edge at 219742-219747).
Key architectural unification: the TAS_TUNNEL/TAS_TUNNEL_CONTINUE hold
state's exit gates on the same readiness predicate as G.3a — retail's loading
visual and the hold-until-hydration gate are one mechanism (the tunnel is the
visual form of the hold). G.3a ships the bare PortalSpace freeze; G.3c wraps it
in the tunnel viewport + the fade FSM, exit-gated identically.
Port workflow: grep-named → decompile BeginTeleportAnimation + the FSM →
pseudocode (durations, fade math, viewport scene construction) → port → test.
Detail deferred to the G.3c implementation phase; this spec fixes the design
(states, transitions, the readiness-gated hold) + the oracle pointers.
3.4 G.3d — Recall game-actions
Outbound zero-payload game-action builders (r09 §7.1): TeleToLifestone 0x0063, TeleToHouse 0x0262, TeleToMansion 0x0278, TeleToMarketPlace 0x028D,
RecallAllegianceHometown 0x02AB, TeleToPkArena 0x0027. The client only sends
the request; the server validates, plays the recall animation, then drives the
same PlayerTeleport → UpdatePosition arrival flow.
Value: (1) doubles as the easy test lever for G.3a/G.3c — /ls triggers a
teleport with no portal-click choreography; (2) completes the recall UX (keybinds
exist; the wire sends + return handling did not). Wire through the existing
command bus.
4. Data flow (the teleport happy path)
1. PlayerTeleport(0xF751) → OnTeleportStarted: enter PortalSpace, freeze input
[G.3c: BeginTeleportAnimation(TAS_WORLD_FADE_OUT)]
2. fake UpdatePosition(destCell) → validate dest coords → recenter streaming to dest lb
→ prioritize-load dest lb → re-send LoginComplete
3. HOLD (TeleportArrivalController.Tick, each frame in PortalSpace):
ready = SampleTerrainZ(destPos) != null && (outdoor || IsSpawnCellReady(destCell))
- not ready → stay frozen, retry [G.3c: tunnel holds in TAS_TUNNEL/_CONTINUE]
- impossible → IsSpawnClaimUnhydratable → safety-net demote + loud log
- timeout → force-snap + loud log + leave PortalSpace
4. READY → Resolve(destPos, destCell) → #111 validated-claim branch
→ WalkableFloorZNearest places on the EnvCell floor
→ SetPosition(entity + controller) → exit PortalSpace, resume input
[G.3c: TAS_TUNNEL_FADE_OUT → TAS_WORLD_FADE_IN → off]
(ACE server send-order, for reference — Player_Location.Teleport:686:
PlayerTeleport(seq) → fake UpdatePosition (start client load) →
DoTeleportPhysicsStateChanges (hidden / ignoreCollisions) → real
UpdatePosition → OnTeleportComplete after CreateWorldObjectsCompleted.)
5. Error handling
| Failure | Handling | No-workaround rationale |
|---|---|---|
Impossible / poisoned claim (cell id ∉ [0x0100, 0x0100+NumCells), or no struct + no surface) |
IsSpawnClaimUnhydratable → safety-net demote (PhysicsEngine.Resolve head, :536-570) + loud log; never hold forever |
Reuses the validated #107/#111 reject; no new masking |
| Dest LB fails to stream (worker crash / corrupt dat / OOB coords) | Timeout ceiling (~10 s) → force-snap + loud log + leave PortalSpace | Surfaces the failure (visible bad placement + log), does not freeze the client or silence the cause; gets a divergence-register row |
| Mid-hold entity-rescue race | Already serialized by _datLock during recenter (verified, seam-3) |
No change |
The timeout is the one judgment call: holding forever on a never-hydrating landblock would soft-lock the client. The chosen behavior fails loudly and visibly (force-snap + log), which is the opposite of a symptom-masking grace period — it makes a broken teleport obvious rather than hiding it. It is recorded as a deliberate adaptation (retail loads synchronously; async streaming has no direct analog).
6. Testing & acceptance
6.1 Headless / unit
TeleportArrivalControllerFSM:Idle → Holding → Placinghappy path; impossible-claim immediate reject; timeout force-snap; ready-predicate gating (fakeIsLandblockApplied/IsSpawnCellReady).- Hydration-decouple test: a geometry-less EnvCell (empty render mesh, non-empty
physics BSP) still gets
CacheCellStruct+BuildLoadedCell. TeleportFlowTests: fakePlayerTeleport+UpdatePositionwire → controller phase transitions + input-gate flips.DungeonLandblockDatProbeTests(exists): pins0x0125= flat + 71 cells.- G.3c:
TeleportAnimStateFSM transition test (state sequence + the readiness-gatedTAS_TUNNELhold-exit). - G.3d: recall-builder byte tests (opcode + empty payload, per builder).
6.2 Visual gate (the acceptance test — after G.3a)
Teleport into the meeting-hall dungeon via the portal:
- Player stands in the dungeon cell, on the floor (not ocean, not falling).
- The dungeon renders; navigate 3–5 rooms; walls block movement.
- No ocean / no ACE
failed transitionspam. - (Implicitly) the portal flood does not blow up (#95 check) and collision works in every room (hydration-coupling check).
ACDREAM_PROBE_CELL=1 + ACDREAM_PROBE_VIEWER=1 + ACDREAM_WB_DIAG=1 + the
always-on [snap] / live: teleport lines capture the chain (the
launch-dungeon-diag.log protocol from this session).
6.3 Per-installment build/test gates
Each installment: dotnet build green + dotnet test green
(App / Core / UI / Net suites) before it's "done"; G.3a additionally requires the
visual gate.
7. Retail divergence register impact
- G.3a timeout force-snap → NEW row (adaptation: async streaming hold has no
synchronous-retail analog; retail loads the cell set synchronously before
SetPositionInternal). - Hydration decouple → NO row (bug fix retiring an incidental render↔physics coupling; restores retail-correct independence).
- G.3c → only a row if a faithful asset can't be reproduced (e.g. the tunnel viewport scene) and a documented courtesy substitute is shipped.
- #95 close-as-superseded (if G.3b not triggered) → ISSUES.md note only.
8. Component boundaries (what each unit does / depends on)
| Unit | Location | Does | Depends on |
|---|---|---|---|
TeleportArrivalController |
AcDream.App/World/ |
Owns the Idle/Holding/Placing phase + _pendingArrival; decides hold-vs-place each frame |
readiness predicate (injected), Resolve (injected), PortalSpace state |
| readiness predicate | PhysicsEngine (reused #107 triplet) |
SampleTerrainZ(pos) ∧ (outdoor ∨ IsSpawnCellReady(cell)); IsSpawnClaimUnhydratable(cell) |
DataCache, dat LandBlockInfo |
| hydration decouple | GameWindow.BuildInteriorEntitiesForStreaming |
BuildLoadedCell + CacheCellStruct gated on cellStruct/BSP, not render mesh |
cellStruct, PhysicsBSP |
TeleportAnimState FSM (G.3c) |
AcDream.App UI/render |
Portal-tunnel fade FSM; hold-exit gated on the readiness predicate | m_pPortalSpace viewport, the readiness predicate |
| recall builders (G.3d) | AcDream.Core/Network/Actions |
Zero-payload outbound game actions | command bus |
AcDream.Core gains no GL/window dependency. The controller + FSM live in
AcDream.App; the readiness predicate's physics half lives in AcDream.Core
(pure), its streaming half in AcDream.App.
9. References cited
- Current code (verified this session):
GameWindow.cs4877-4961 (arrival), ~4971-4976 (OnTeleportStarted), 1010-1024 (#107 login gate), 11728-11748 (IsSpawnClaimUnhydratable), 5564-5651 (EnvCell hydration guard), 5941-6150 (ApplyLoadedTerrainLocked);PhysicsEngine.cs468-472 (IsSpawnCellReady), 626-646 (#111 validated claim), 383-406 (WalkableFloorZNearest), 536-570 (Resolve safety net);StreamingRegion.cs180-283 (RecenterTo);StreamingController.cs120-149 (drain);PortalVisibilityBuilder.cs131 (lbMask), 165 (enqueue-once);CellTransit.cs515-516 (null-skip);PhysicsDataCache.cs172 (null-BSP early-return). - Decomp (named-retail):
BeginTeleportAnimation004d6300(line 218888) + theTeleportAnimStateFSM 219405-219774;m_pPortalSpaceviewport 218829/219363;CEnvCell::grab_visible_cells:311878(G.3b stab_list). - holtburger:
messages.rs:434(client re-sendsLoginCompleteon teleport). - ACE:
Player_Location.Teleport:686(send order);Landblock.cs:575(IsDungeon);Player_Tick.cs:548-560(single-landblock dungeons); recall handlers +Portal.ActOnUse/AdjustDungeon. - r09 deepdive:
docs/research/deepdives/r09-dungeon-portal-space.md(EnvCell / CellPortal wire layout, recall taxonomy, the retail contract). - Issues: #133, #95.
- Digests (DO-NOT-RETRY tables apply):
project_render_pipeline_digest,project_physics_collision_digest.
10. Open questions (resolved here; revisit only if the gate disagrees)
- Loading visual now or later? Faithful
TeleportAnimStatein G.3c (user decision). Unified with the G.3a hold (the tunnel IS the hold's visual). - Hold timeout/failure? Reject impossible claims instantly
(
IsSpawnClaimUnhydratable); hold plausible-but-slow with a ~10 s ceiling; on timeout force-snap + loud log (fail visibly, never freeze). - Big-jump streaming? Verified to work (Chebyshev recenter). Add only
dest-coord validation; the readiness gate reuses
SampleTerrainZ(no new streaming query). - EnvCell placement vs flat terrain? The #111
WalkableFloorZNearestEnvCell path (identical to the cellar path that already works); the flat terrain renders below. The gate guarantees the cell is hydrated before Resolve runs. - (New, deferred to G.3b/implementation) Does the dat carry a parsed
stab_listforgrab_visible_cellsbounding? Only matters if the gate shows the #95 blowup.