acdream/docs/research/2026-06-21-teleport-issues-handoff.md
2026-06-21 18:24:08 +02:00

136 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Handoff — teleport issues cluster (2026-06-21)
**Branch:** `claude/thirsty-goldberg-51bb9b` — committed, NOT merged ("merge later").
**Account:** `notan` / `MittSnus81!``+Je`. Portal via the Town Network hub (`0x0007`) to far towns.
---
## What shipped this session (context)
#145 **cell-relative physics frame (Option B)**, Slices 13 + 7 (`438bb68`→`403a338`). The carried-anchor
fix closes the far-town cascade **for a STREAMED-terrain arrival** — live-verified across ~10 far-town
landblocks over 2 sessions, zero march. Design + plan:
`docs/superpowers/specs/2026-06-21-145-cell-relative-physics-frame-design.md` + the matching plan.
Adversarial review passed (2 flagged issues were verified false positives). Slices **46** (contact-plane /
walkable cell-relative + `_liveCenter` fully render-only — invisible architectural completeness) remain.
---
## Work item A — #145 RESIDUAL (reopened): unstreamed-arrival-near-edge cascade
**Trigger:** a teleport that arrives onto a **NOT-YET-STREAMED** landblock **near an edge** → the cascade
recurs (+ a Z free-fall). **Evidence** (`launch5.log`):
- `[snap] claim=0xC98C0028 pos=(113.666,190.259,22.010) branch=NO-LANDBLOCK (lbs=0) -> verbatim` — arrival
local Y=190.3, **1.7 m from the 192 edge**, destination not streamed → cell marches `0xC98C → 0xC9FE`
(+2 lbY/tick), Z free-falls 22→19; outbound wire sends the marched cell `C9FE0031` + compensating garbage
`localY=21684` → ACE `MOVEMENT SPEED` / `failed transition`**player stuck**.
- Same session, **mid-block** unstreamed arrival `0x977B000C` (Y=73.8) → Z free-fall only, **no march**.
- Hub `0x00070133` **VALIDATED** (cells resident) → fine.
**Root (HYPOTHESIS — not apparatus-confirmed):** outdoor teleport places immediately but the destination
streams a few ticks later (`streaming: dungeon EXIT-expand -> (201,140)` logs just after placement). During
the gap the resolve runs against an empty world; at an edge the player crosses into the **unstreamed
neighbour**, where the carried anchor (which computes to `(0,0,0)` at the recentered center) gives no
protection. **Same root as the Z free-fall** (`#135`/`#138` placed-before-streamed gap).
**⚠️ DO NOT guess-patch** (original #145 burned 5 attempts). **Apparatus first:** add a diagnostic that logs
the **anchor value + the engagement-guard state** in `PhysicsEngine.ResolveWithTransition` at the crossing
tick (behind a probe flag), reproduce a `0xC98C`-style arrival once, and confirm whether the anchor
**disengages** (CellId/CellPosition landblock divergence) or the **body is repositioned into a marched frame
first**. *Then* the fix is a known quantity.
**Likely fix:** a **streaming-gap HOLD** — freeze the per-tick resolve (no integration/gravity, no membership
march) while the player's landblock is unloaded, until it streams in (a few ticks). The async-streaming
equivalent of retail's synchronous load; would fix **both** the cascade and the Z plunge.
**Note:** this may be **subsumed by Work item B** — the retail teleport flow holds until the world loads, so
there's no unstreamed gap to begin with. Decide A-vs-B ordering at brainstorm.
---
## Work item B — KEYSTONE FEATURE: the retail teleport flow
**Symptoms (user-reported; all ONE root):**
1. Camera **floats** to the destination on teleport (disliked).
2. Teleport **takes too long** / feels off vs retail.
3. Player **input is not locked** during teleport.
**Retail behaviour (user = oracle, treat as axiom):** teleport plays an **animation** (portal effect), holds
input + camera, **waits for the world to load**, then exits/places the player. Used for **ALL** teleportation:
**login, logout, death, portal.**
**Insight:** acdream places **immediately** (raw placement → the floating camera + the unstreamed gap). A
faithful retail teleport flow (anim + input/camera lock + **hold-until-loaded** + single exit point) fixes the
float, the timing, the input-lock, **and very likely Work item A.** This is the keystone.
**This is a FEATURE → resume `superpowers:brainstorming`** (user is the retail oracle on look/feel). The
3-agent research (workflow `wf_61501a7a-1d4`) is DONE — findings below. **Extend acdream's machinery, don't
rebuild.**
### RETAIL flow (decomp-verified) — `gmSmartBoxUI` teleport **animation state machine**
`TeleportAnimState` enum (`acclient.h:6871`): the client plays a **fade + portal-tunnel animation that COVERS
the world-load wait** — used by ALL of login / logout / death / portal:
```
TAS_OFF(0) → WORLD_FADE_OUT(1, ~1s) → TUNNEL_FADE_IN(2, ~1s) → TUNNEL(3 = HOLD for world load)
→ TUNNEL_CONTINUE(4, min ~2s) → TUNNEL_FADE_OUT(5, ~1s) → WORLD_FADE_IN(6, ~1s) → OFF
```
- State machine: `gmSmartBoxUI::UseTime` (pc 219400); begin `BeginTeleportAnimation` (218888); end `EndTeleportAnimation` (218994).
- **The world-load HOLD** is `TAS_TUNNEL(3)`, gated on `SmartBox::teleport_in_progress` (pc 90815) = `(player != 0) && (position_update_complete == 0)`; `position_update_complete` flips to 1 when the **DDD interrogation / position update** completes → animation advances. **This is the "exit when the world has loaded" the user wants.**
- **Input lock:** `PlayerModule::SetLockUI` sets bit `0x1000000` in `options2_` (pc 486713) for the whole animation; cleared at `TAS_OFF`.
- **Unification:** **death + logout share** the flow via `logOffRequested`; **login** enters via `RecvNotice_BeginEnterWorld`; **portal** starts directly at `TAS_TUNNEL` (skips the fade-out). Sounds: `Sound_UI_EnterPortal`(0x6A) / `ExitPortal`(0x6B). Exit callback `SendLoginCompleteNotification` (pc 367516).
- Missing from decomp: exact `TELEPORT_ANIM_FADE_TIME` / `MIN_CONTINUE_TIME` constants; the DDD code that sets `position_update_complete`.
### acdream TODAY — what exists, what's missing
acdream ALREADY has the *skeleton*: `PlayerState.PortalSpace` **input lock** (`PlayerMovementController.cs:840-854`)
+ a **hold-until-readiness** controller (`TeleportArrivalController.cs:56-105`; `GameWindow.OnTeleportStarted`
:5487, `OnLivePositionUpdated`/`BeginArrival` :5394, `PlaceTeleportArrival` :5443-5478, readiness :5419-5439).
**The gaps (the actual work):**
1. **NO animation / visual cover.** During the hold the camera is frozen at the OLD spot, then `PlaceTeleportArrival` **snaps** both cameras (`:5464-5469`, no fade/lerp). That snap + the world streaming in *is* the "floating" the user dislikes. → **Build a client teleport-anim state machine (TAS-shaped: fade-out → tunnel/hold → fade-in) that covers the wait.**
2. **Readiness gate is too weak → this is the #145 residual.** Outdoor readiness gates on `SampleTerrainZ != null` (`:5436`) — terrain heightmap can sample BEFORE the collision **landblock cells** load, so the player is placed onto a `branch=NO-LANDBLOCK` world → the unstreamed-arrival cascade + Z free-fall (Work item A). → **Gate on the full landblock being loaded** (`teleport_in_progress`-equivalent). **Fixing this likely closes Work item A.**
3. **`yaw` still updates during the hold** (`MouseLook` not frozen, gotcha) — lock it too.
4. **Login / logout / death NOT unified.** Login uses inline auto-entry guards (`:1036-1065`) that DUPLICATE teleport's readiness (`:5419-5439`) + recenter (`:2874-2881` vs `:5364-5370`). **Logout does not exist; death only feeds `Chat.OnPlayerKilled` (`:2569`), no game effect.** → Route all four through one arrival/anim controller; de-dupe readiness + recenter.
### Design direction (for the brainstorm)
Build a **client-side teleport animation + arrival controller** (port the TAS state machine shape) layered on
the existing PortalSpace lock + TeleportArrivalController hold: (a) a fade/tunnel **visual cover** over the
world-load wait (kills the float); (b) **strengthen the readiness gate** to the full landblock (kills the #145
residual + the Z free-fall); (c) **unify** login/logout/death/portal entry points + de-dupe the readiness/recenter
logic; (d) tune timing so it doesn't feel longer than retail (the current 600-frame/10s timeout is the worst case).
---
## Work item C — FPS leak after teleports (perf bug, SEPARATE)
**Symptom:** FPS drops low after a couple of teleports (cumulative).
**Hypothesis:** a streaming/entity **leak** — landblocks or render/physics entities not freed on teleport,
accumulating across round-trips (candidates: dungeon collapse/expand not cleaning up; `RehydrateServerEntities`
/ `_lastSpawnByGuid` accumulating; `GpuWorldState` entities not pruned). **NEXT:** capture FPS + landblock /
entity counts across N teleports (`ACDREAM_DUMP_LIVE_SPAWNS=1` + streaming logs + the DebugPanel counts), find
what grows unbounded. Independent of A and B.
---
## Pointers / apparatus
- **Logs (worktree root):** `launch5.log` (#145 residual capture — the `0xC98C` stuck-at-`21684` run),
`launch4.log` (Z free-fall + a 3-far-town round-trip), `launch3`/`launch2` (clean streamed-arrival runs).
- **#145 record:** `docs/ISSUES.md` #145 (reopened, with the trigger table) + the physics digest banner
(`claude-memory/project_physics_collision_digest.md`).
- **Probes (no rebuild):** `ACDREAM_PROBE_CELL=1` (`[cell-transit]`), `ACDREAM_PROBE_RESOLVE=1`,
`ACDREAM_CAPTURE_RESOLVE=<path>`, `ACDREAM_DUMP_LIVE_SPAWNS=1`.
- **Decomp oracle:** `docs/research/named-retail/` (grep `class::method`); for the teleport flow, look at the
portal/teleport + DDD-loading + animation paths.
- **Wire cross-check:** `references/holtburger` (client teleport flow) + `references/ACE` (server teleport /
`MOVEMENT SPEED` validation — the `failed transition` rejects).
## Suggested order (brainstorm to confirm)
1. **B first** (retail teleport flow) — it's the keystone and likely subsumes **A**. Brainstorm → spec → build.
2. Re-test **A**: if the hold-until-loaded flow eliminates the unstreamed gap, the #145 residual is gone; if a
narrow edge case survives, apply the diagnostic + targeted hold.
3. **C** (FPS leak) — independent; can run in parallel as its own investigation.
4. (Background) Slices **46** — the invisible Option-B completeness, unrelated to this cluster.