acdream/docs/research/2026-06-21-teleport-issues-handoff.md
Erik 59b4868408 docs: handoff — teleport issues cluster (#145 residual + retail teleport flow + FPS leak)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 18:11:37 +02:00

6.6 KiB
Raw Blame History

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 (438bb68403a338). 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 transitionplayer 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 → start with superpowers:brainstorming (the user is the retail oracle on look/feel). Research first: grep docs/research/named-retail/acclient_2013_pseudo_c.txt for the portal/teleport flow — the teleport animation, the loading hold, the PortalSpace exit condition, and the unified path for login/logout/death/portal. Extend, don't rebuild: acdream already has PlayerState.PortalSpace, TeleportArrivalController, and TeleportArrivalRules.Decide (src/AcDream.App/World/ + GameWindow); the flow should slot the anim + hold-until-loaded into that machinery.


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.