Captures the full teleport-flow journey (Slice 1 kept, Slice 2 hold built +
reverted), the root-cause findings (the IsLandblockLoaded key bug + the real
foundation problem: destination doesn't stream fast/complete during teleport,
likely _datLock starvation from the CreateObject flood), the open foundation
issues (#138 slow/incomplete streaming, lost-collision-after-teleport, FPS
leak Work-C, the PortalSpace freeze-vs-run-through question), and the clean
baseline (dd2eb8b). Written as evidence + open questions, not conclusions, so
a fresh session can re-examine the whole approach.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
12 KiB
Handoff — teleport foundation (fresh-look investigation) — 2026-06-21
Branch: claude/thirsty-goldberg-51bb9b — committed, NOT merged.
Clean baseline commit: dd2eb8b (the Slice 2 revert). Build + tests green here.
Account for repro: notan / MittSnus81! → +Je. The character is currently left at a far
outdoor town (landblock ~125,100 = 0x7D64); portal via the Town Network hub (0x0007, an indoor cell)
to reach far towns.
Read this with fresh eyes. The previous session built a "hold the player until the destination loads" teleport feature, hit a wall, and reverted it. This doc gives you the evidence and the open questions — NOT a prescription. You are explicitly invited to question every assumption below, including whether a "hold" is the right model at all. Investigate first (read-only), report, then brainstorm the fix with the user. No band-aids (the user's words: "feels shaky and bandaid").
TL;DR
Teleporting in acdream feels bad: long transitions, the character STOPS at the portal instead of
running through, FPS sags after portaling, and a building lost its collision after a death→lifestone
teleport. The previous session traced the "long transition" to a real root: the destination
landblock does not stream in fast (or completely) during/around a teleport (the #138 streaming gap),
likely amplified by _datLock contention from ACE's CreateObject flood on arrival. A "hold until
loaded" gate just made every teleport wait ~10 s for a load that wasn't happening — so it was reverted.
The real work is the foundation: make teleport-destination streaming fast AND complete, and fix the
post-teleport FPS leak. Then (and only then) a retail-style visual cover is polish, not a crutch.
What was built, and what was reverted
The original goal was Work item B: the retail teleport flow (fade → portal-tunnel → hold-until-loaded → place; covers login/logout/death/portal). Design + plan are committed and still valid as aspiration:
- Spec:
docs/superpowers/specs/2026-06-21-retail-teleport-flow-design.md - Plan:
docs/superpowers/plans/2026-06-21-retail-teleport-flow.md(22 TDD tasks, 5 slices) - Prior research:
docs/research/2026-06-21-teleport-issues-handoff.md(the TAS state-machine decomp).
Shipped, then status:
- Slice 1 —
TeleportAnimSequencer(pure 7-state TAS machine,src/AcDream.Core/World/): KEPT. Dormant, unwired, fully unit-tested (29 tests). Harmless. Reusable later. - Slice 2 — the readiness-gate hold (outdoor teleport holds until the physics landblock is loaded):
REVERTED (
dd2eb8b, revertingad8c24e..c880973back to00ef47e). Outdoor teleports now place immediately again (fast). See "Why reverted" below. - Slices 3–5 (the TAS animation/controller, login/logout/death unification): PARKED (never started).
Why Slice 2 was reverted (the key learning)
Slice 2 gated outdoor arrival on "is the destination physics landblock resident?" and held otherwise.
Live testing showed every outdoor teleport froze ~10 s then force-placed, because the destination
landblock never became resident during the hold (lbs=0 the whole time). The hold was band-aiding a
foundation problem (slow/incomplete streaming) instead of fixing it — and it didn't even prevent the
#145 edge cascade it was designed for (it force-snapped onto NO-LANDBLOCK after the timeout regardless).
Root-cause findings so far (verify, don't trust)
A 3-agent read-only investigation (workflow wf_8b67a9d1-35c, all high-confidence) + manual
source-verification found two distinct problems behind the 10 s freeze:
1. A real (now-reverted) bug: IsLandblockLoaded key mismatch
The Slice 2 gate queried IsLandblockLoaded(destCell & 0xFFFF0000) (e.g. 0x7D640000), but streaming
stores landblocks under the EncodeLandblockId form — low 16 bits = 0xFFFF (e.g. 0x7D64FFFF)
(src/AcDream.App/Streaming/StreamingRegion.cs:98-99). A raw ContainsKey never matched → the gate was
permanently NotReady. This was fixed (c880973, normalize the key + the regression test the
original missed) then reverted along with the rest of Slice 2. If a future hold is reintroduced, the
predicate must normalize the key (the fix is preserved in history at c880973).
2. The actual foundation problem: the destination doesn't stream in fast/complete
Even with the key bug fixed, lbs=0 (zero landblocks resident) for the entire hold — the destination
genuinely was not loaded. Evidence + leads:
- The streaming machinery is commanded correctly: on a dungeon-OUT teleport the gate logs
streaming: dungeon EXIT-expand -> (Lx,Ly)and the observer is correctly pinned to the destination during the hold (src/AcDream.App/Rendering/GameWindow.cs~7441-7459 PortalSpace observer-pin;DungeonStreamingGate.cs:44-45returnsInsideDungeon=falsefor a teleport hold;StreamingController.cs~130-163 Tick dispatch, ~279-297ExitDungeonExpand, ~161-163DrainAndApplyruns unconditionally). So the command path is not obviously gated onInWorld. - Yet nothing loads during the hold, and the landblock appears the instant the player flips to
InWorld. The leading hypothesis (MEDIUM confidence — NOT proven) is_datLockstarvation: ACE floods CreateObjects on arrival;WorldSession.Tickdrains them in a tight loop (src/AcDream.Core.Net/WorldSession.cs~598-607, no yield), each underlock(_datLock)(GameWindow.cs~2679-2682), and the streaming worker also needs_datLockto build a landblock (BuildLandblockForStreaming— confirm the exact site; the investigation cited a_datLockacquire on the build path). So the render-thread CreateObject flood may block the background streamer for the whole arrival window. This is the #1 thing to confirm or refute. AddLandblockregisters terrain + cells + portals atomically (PhysicsEngine.cs~66-74), and the spatial lookups (SampleTerrainZ,TryGetLandblockContext) find landblocks by world-XY bounds, not by dictionary key — which is why a force-placed player grounds correctly the instant the LB appears.
3. The lost-collision symptom (death→lifestone → building has no collision)
Not yet root-caused. Hypothesis: the destination loaded incompletely — terrain present but the
building/cell collision (ShadowObjects / the per-cell shadow list) not registered. See
memory/feedback_retail_per_cell_shadow_list.md (retail collision = per-cell list with portal-aware
registration). Likely the same root as #2 (incomplete async load), but verify independently — it may
be a distinct registration-order bug.
4. FPS sag after portaling (Work item C)
Separate known issue (a cumulative leak). Candidates from the prior research: streaming/entity state not
freed on teleport round-trips, _lastSpawnByGuid / GpuWorldState accumulation, dungeon collapse/expand
not cleaning up. The _datLock contention also contributes to per-frame hitching during the flood.
Note: ACDREAM_PROBE_RESOLVE=1 is itself a massive FPS killer (it Console.WriteLines every physics
resolve — a 561k-line log). Do NOT measure FPS with it on; it confounds everything.
The investigation to run (the new session's job)
Root-cause, read-only, then report + brainstorm. Core questions:
- Why is the teleport destination not resident fast? Confirm/refute the
_datLockstarvation hypothesis: instrument (lightly) the streamer worker's load completions and the_datLockhold time during an arrival, and see whether the worker is blocked by the CreateObject flood. Is the async load→apply pipeline gated, starved, or just slow? Does the dungeon collapse→EXIT-expand transition add latency? - Why does collision get dropped after a teleport (the lost-building-collision)? Trace where cell / ShadowObject collision is registered during streaming and what order vs terrain; is it skipped or raced on a teleport arrival?
- What leaks (Work item C) across teleport round-trips? Capture landblock + entity counts across N teleports; find what grows unbounded.
- What does retail actually do? The decomp (
docs/research/named-retail/) + the prior TAS research show retail plays a tunnel animation that covers a synchronous-feeling load, gated onposition_update_complete(the DDD interrogation). Retail's load is fast within the tunnel. So the question isn't "hold vs no-hold" in the abstract — it's "how do we make the load fast + complete," after which either a short hold-with-cover OR place-immediately becomes viable.
Open DESIGN questions to reconsider with fresh eyes (don't assume the prior answers)
- Hold vs place-immediately. The reverted baseline places immediately (fast, but risks pop-in / the #145 edge cascade on a not-yet-streamed edge arrival). Retail holds behind a tunnel anim but loads fast. Which model fits once streaming is fast? (AC2D, a reference, sends movement keys + trusts server Z and does no client-side hold — worth weighing.)
- The
PortalSpaceinput-freeze. Today entering a teleport freezes input (OnTeleportStartedsetsPlayerState.PortalSpace), so the character STOPS at the portal instead of running through. The user explicitly wants "run through the portal." Is freezing input on portal-enter even correct? What does retail do (keep momentum? freeze?)? This is independent of streaming and may be its own fix. - The #145 edge cascade. With place-immediately restored, a teleport that arrives near a landblock EDGE onto an unstreamed neighbour can still march the cell label + free-fall Z (the original #145 residual). The hold never actually fixed this. The real fix is fast/complete streaming so the arrival lands on a loaded world; failing that, the cell-relative physics frame (#145 Slices 1–3, already shipped) limits it.
Apparatus / how to reproduce + diagnose
- Launch (PowerShell; client lifecycle is the USER's — launch with plain
dotnet run, do NOT pre-close clients; if a rebuild is locked by a running client, ASK):
Build green FIRST ($env:ACDREAM_DAT_DIR="$env:USERPROFILE\Documents\Asheron's Call"; $env:ACDREAM_LIVE="1" $env:ACDREAM_TEST_HOST="127.0.0.1"; $env:ACDREAM_TEST_PORT="9000" $env:ACDREAM_TEST_USER="notan"; $env:ACDREAM_TEST_PASS="MittSnus81!" dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug 2>&1 | Tee-Object launch.logdotnet build src\AcDream.App\AcDream.App.csproj -c Debug); launch uses--no-build. - Repro the bad teleport: in-world, portal via the hub (
0x0007) to a far town, or recall to lifestone. Watch thelive: teleport started→teleport completegap and[snap] ... NO-LANDBLOCK (lbs=0). - Diagnostics:
ACDREAM_PROBE_RESOLVE=1exists but is FPS-toxic + confounding — prefer a new, light probe (e.g. log streamer load-completions +_datLockhold-time during an arrival). The prior apparatus logs (launch-gate-retest*.log,launch-clean-noprobe*.log) are in the worktree root but transient — the evidence above is the durable summary. - It's a Debug build — FPS is not representative; a
Releasebuild is far faster. Build Release if you want a real frame-rate read.
Clean baseline — what NOT to touch
dd2eb8bis the clean baseline: outdoor teleports place immediately, Slice 1 sequencer dormant.- Do NOT re-introduce the Slice 2 hold without fixing the streaming foundation first (that's the band-aid we removed).
- Frozen/shipped phases per the milestones doc remain off-limits.
Key references
- Render/physics digests:
claude-memory/project_render_pipeline_digest.md,claude-memory/project_physics_collision_digest.md(DO-NOT-RETRY tables). - Streaming refs:
memory/reference_two_tier_streaming.md,memory/reference_indoor_cell_tracking.md. memory/feedback_apparatus_for_physics_bugs.md,memory/feedback_retail_per_cell_shadow_list.md,memory/feedback_verify_subagent_claims_against_source.md(read cited code yourself; agents fabricate).- ISSUES: #138 (teleport-OUT EXPAND gap), #145 (edge cascade), Work item C (FPS leak) — update
docs/ISSUES.mdwith the new symptoms (lost-collision-after-teleport; char-stops-at-portal) as part of the investigation.