From f8cda7e86d8273533a0791150353368c7fed83f9 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 22 Jun 2026 10:00:23 +0200 Subject: [PATCH] docs(teleport): foundation-investigation handoff for a fresh-look session 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) --- .../2026-06-21-teleport-foundation-handoff.md | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 docs/research/2026-06-21-teleport-foundation-handoff.md diff --git a/docs/research/2026-06-21-teleport-foundation-handoff.md b/docs/research/2026-06-21-teleport-foundation-handoff.md new file mode 100644 index 00000000..13478573 --- /dev/null +++ b/docs/research/2026-06-21-teleport-foundation-handoff.md @@ -0,0 +1,176 @@ +# 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`, reverting `ad8c24e..c880973` back to `00ef47e`). 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-45` returns `InsideDungeon=false` for a teleport hold; + `StreamingController.cs` ~130-163 Tick dispatch, ~279-297 `ExitDungeonExpand`, ~161-163 `DrainAndApply` + runs unconditionally). So the *command* path is not obviously gated on `InWorld`. +- **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 **`_datLock` starvation**: ACE + floods CreateObjects on arrival; `WorldSession.Tick` drains them in a tight loop + (`src/AcDream.Core.Net/WorldSession.cs` ~598-607, no yield), each under `lock(_datLock)` + (`GameWindow.cs` ~2679-2682), and the streaming worker also needs `_datLock` to build a landblock + (`BuildLandblockForStreaming` — confirm the exact site; the investigation cited a `_datLock` acquire 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.** +- `AddLandblock` registers 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.WriteLine`s 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: +1. **Why is the teleport destination not resident fast?** Confirm/refute the `_datLock` starvation + hypothesis: instrument (lightly) the streamer worker's load completions and the `_datLock` hold 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? +2. **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? +3. **What leaks** (Work item C) across teleport round-trips? Capture landblock + entity counts across N + teleports; find what grows unbounded. +4. **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 on + `position_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 `PortalSpace` input-freeze.** Today entering a teleport freezes input (`OnTeleportStarted` sets + `PlayerState.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): + ```powershell + $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.log + ``` + Build green FIRST (`dotnet 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 the `live: teleport started` → `teleport complete` gap and `[snap] ... NO-LANDBLOCK + (lbs=0)`. +- **Diagnostics:** `ACDREAM_PROBE_RESOLVE=1` exists but is FPS-toxic + confounding — prefer a *new, light* + probe (e.g. log streamer load-completions + `_datLock` hold-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 `Release` build is far faster. Build Release if you + want a real frame-rate read. + +## Clean baseline — what NOT to touch +- `dd2eb8b` is 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.md` with the new symptoms (lost-collision-after-teleport; char-stops-at-portal) as part of + the investigation.