10 KiB
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 1–3 + 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 4–6 (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 marches0xC98C → 0xC9FE(+2 lbY/tick), Z free-falls 22→−19; outbound wire sends the marched cellC9FE0031+ compensating garbagelocalY=−21684→ ACEMOVEMENT SPEED/failed transition→ player stuck.- Same session, mid-block unstreamed arrival
0x977B000C(Y=73.8) → Z free-fall only, no march. - Hub
0x00070133VALIDATED (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):
- Camera floats to the destination on teleport (disliked).
- Teleport takes too long / feels off vs retail.
- 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); beginBeginTeleportAnimation(218888); endEndTeleportAnimation(218994). - The world-load HOLD is
TAS_TUNNEL(3), gated onSmartBox::teleport_in_progress(pc 90815) =(player != 0) && (position_update_complete == 0);position_update_completeflips 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::SetLockUIsets bit0x1000000inoptions2_(pc 486713) for the whole animation; cleared atTAS_OFF. - Unification: death + logout share the flow via
logOffRequested; login enters viaRecvNotice_BeginEnterWorld; portal starts directly atTAS_TUNNEL(skips the fade-out). Sounds:Sound_UI_EnterPortal(0x6A) /ExitPortal(0x6B). Exit callbackSendLoginCompleteNotification(pc 367516). - Missing from decomp: exact
TELEPORT_ANIM_FADE_TIME/MIN_CONTINUE_TIMEconstants; the DDD code that setsposition_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):
- NO animation / visual cover. During the hold the camera is frozen at the OLD spot, then
PlaceTeleportArrivalsnaps 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. - 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 abranch=NO-LANDBLOCKworld → 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. yawstill updates during the hold (MouseLooknot frozen, gotcha) — lock it too.- Login / logout / death NOT unified. Login uses inline auto-entry guards (
:1036-1065) that DUPLICATE teleport's readiness (:5419-5439) + recenter (:2874-2881vs:5364-5370). Logout does not exist; death only feedsChat.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 — the0xC98Cstuck-at-−21684run),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/(grepclass::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 SPEEDvalidation — thefailed transitionrejects).
Suggested order (brainstorm to confirm)
- B first (retail teleport flow) — it's the keystone and likely subsumes A. Brainstorm → spec → build.
- 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.
- C (FPS leak) — independent; can run in parallel as its own investigation.
- (Background) Slices 4–6 — the invisible Option-B completeness, unrelated to this cluster.