fix(streaming): #145 — teleport re-use via server-authoritative placement
Portals only worked once per session: teleporting OUT of a dungeon
mis-rooted the player into the SOURCE dungeon's coordinate frame, so every
move was sent dungeon-framed and ACE rejected it ("failed transition") —
the player couldn't move, never reached a portal, and the world wouldn't
re-render (only skybox).
Root cause: acdream's streaming-relative frame recenters on teleport, but
resident physics landblocks keep their load-time world-offset. After
recentering onto the outdoor destination, the collapsed source dungeon
(offset 0,0 as the prior center) and the destination (offset 0,0 as the
new center) overlap, and the Z-agnostic outdoor cell-snap returns the
dungeon for both the arrival placement and every per-frame resolve.
Fix (server-authoritative teleport placement):
- Drop the stale source center landblock from physics at the teleport
recenter (GameWindow.OnLivePositionUpdated) so the resolve falls through
to the server position (Resolve NO-LANDBLOCK verbatim) until the
destination streams in.
- Place outdoor teleports immediately (TeleportArrivalRules) — holding is
futile because streaming does not progress during a PortalSpace hold.
- Clear a dangling CellGraph.CurrCell when its landblock is removed
(PhysicsEngine.RemoveLandblock) — otherwise the dungeon-streaming gate
keeps streaming collapsed onto the gone dungeon (only skybox renders).
Keeps DungeonStreamingGate (gate suppression during the hold). Indoor
(dungeon-entry) placement is unchanged (cell-keyed, IsSpawnCellReady).
User-verified: in->out->re-enter works repeatedly, no ACE errors, world
renders. Remaining facets (server objects + own avatar not rendering after
a teleport-out) are entity render/lifecycle — split to #138.
Registers AP-36 + AD-2 updated. New: DungeonStreamingGate (+4 tests),
TeleportArrivalRules (+4 tests). Build + 2727 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f6a576af8e
commit
a15bd3b56d
9 changed files with 290 additions and 32 deletions
|
|
@ -48,18 +48,25 @@ Copy this block when adding a new issue:
|
|||
|
||||
## #145 — Portals only work once per session (can't run in, run out, re-enter)
|
||||
|
||||
**Status:** OPEN
|
||||
**Status:** CORE FIXED + user-verified 2026-06-20 (in→out→re-enter repeatedly works; no ACE errors; world renders). Remaining facets (server objects + own-avatar not rendering after a teleport-out) split to **#138** — they are entity render/lifecycle, a different subsystem from the cell-rooting bug this issue was.
|
||||
**Severity:** HIGH (blocks repeated portal/dungeon travel — user, 2026-06-20)
|
||||
**Filed:** 2026-06-20
|
||||
**Component:** net/streaming — teleport (0xF751) + PortalSpace + arrival
|
||||
**Component:** net/streaming — teleport (0xF751) + PortalSpace + arrival + cell rooting
|
||||
|
||||
**Description (user, 2026-06-20):** A portal can only be used ONCE per session. The first teleport works (run into a portal); a subsequent portal use (run out again, or re-enter) does not. Goal: run into a portal, run out again, repeatedly — portals usable any number of times in one session.
|
||||
**Description (user, 2026-06-20):** A portal can only be used ONCE per session. The first teleport works; a subsequent portal use (run out, or re-enter) does not. Goal: run in, run out, repeatedly.
|
||||
|
||||
**Root cause / status (UNVERIFIED):** Likely a stuck teleport-state issue — after the first 0xF751 PlayerTeleport (PortalSpace hold → `TeleportArrivalController` places → back to InWorld), some state isn't reset for the second teleport: candidates are PortalSpace not cleared, the arrival `Phase` stuck at `Holding`, a cell-membership/streaming gap, or the dungeon collapse↔expand state (#133/#135 machinery). Likely overlaps **#138** (teleport OUT of a dungeon loads incompletely) — may be the same root or a sibling. **First step:** instrument a SECOND teleport and find WHERE it stalls (does the 0xF751 arrive? does PortalSpace toggle? does `TeleportArrivalController.Tick` place? does the streaming window re-expand?).
|
||||
**Root cause (CONFIRMED — code trace + live cdb-style [phys-lb] probe):** the teleport OUT of a dungeon mis-rooted the player into the SOURCE dungeon's coordinate frame, desyncing all movement from ACE. acdream uses a streaming-RELATIVE coordinate frame (positions relative to `_liveCenterX/Y`) and recenters on teleport, but **resident physics landblocks keep their load-time world-offset**. After recentering onto the outdoor destination, the collapsed source dungeon (loaded at offset (0,0) when it was the center) and the destination (also the new center → offset (0,0)) **overlap**, and the Z-agnostic outdoor cell-snap (`AdjustPosition`, iterating `_physicsEngine._landblocks`) returns the dungeon — for the arrival placement AND every per-frame resolve — so the player was rooted at a dungeon cell (`0x00070019`) at Holtburg's position. acdream then sent dungeon-frame positions; ACE (which knows the player is at Holtburg) rejected every one (`WARN: failed transition … to 0x0007…`), so the player couldn't move, never reached a portal, and ACE never re-broadcast the Holtburg objects. Teleport IN works because its placement is cell-keyed (indoor `FindVisibleChildCell` validates the specific claimed cell) and it pre-collapses; the OUTDOOR resolve is the grid-snap, which the overlap fools.
|
||||
|
||||
**Files:** `WorldSession.cs:999` (0xF751 handler); `PlayerMovementController.cs:75/840` (`PlayerState.PortalSpace`); `TeleportArrivalController.cs` (`Phase` Idle/Holding, the placement flip); `GameWindow.cs:4903/5332/5362` (PortalSpace observer + arrival placement); streaming collapse↔expand (see #138).
|
||||
**Fix (2026-06-20) — server-authoritative teleport placement (user-approved approach):**
|
||||
1. **Drop the stale source center landblock from physics at the teleport recenter** (`GameWindow.OnLivePositionUpdated`, `differentLandblock` branch → `_physicsEngine.RemoveLandblock(EncodeLandblockId(oldCenter))`). Only the offset-(0,0) center collides with the destination-local position, so removing it alone clears the overlap; the arrival + per-frame resolve then fall through to the server position (`PhysicsEngine.Resolve` NO-LANDBLOCK verbatim, `:605`) until the destination streams in.
|
||||
2. **Place outdoor teleports immediately** (`TeleportArrivalRules.Decide` — outdoor → Ready). Holding is futile: streaming does NOT progress while the player is held in PortalSpace (the destination only loads once placement flips to InWorld). Indoor unchanged (`IsSpawnCellReady` hold). Gate suppression during the hold kept (`DungeonStreamingGate`).
|
||||
3. **Clear a dangling `CellGraph.CurrCell` when its landblock is removed** (`PhysicsEngine.RemoveLandblock`). Without this, dropping the dungeon left CurrCell pointing at the orphaned dungeon cell; the dungeon-streaming gate (keyed on CurrCell) kept streaming collapsed onto the gone landblock, so the destination never streamed → only skybox. Clearing it lets the gate read "not in a dungeon" → `ExitDungeonExpand` → destination streams in → CurrCell re-acquires.
|
||||
|
||||
**Acceptance:** run into a portal, run out, re-enter — repeatedly in one session, each time placing + streaming correctly.
|
||||
Tests: `DungeonStreamingGateTests` (4), `TeleportArrivalRulesTests` (4). Registers AP-36 + AD-2. Build + 2727 tests green. Edge logs added to `EnterDungeonCollapse`/`ExitDungeonExpand`.
|
||||
|
||||
**Files:** `GameWindow.cs` `OnLivePositionUpdated` (drop stale center) + `TeleportArrivalReadiness`; `src/AcDream.App/World/TeleportArrivalController.cs` (`TeleportArrivalRules`); `src/AcDream.App/Streaming/DungeonStreamingGate.cs`; `src/AcDream.Core/Physics/PhysicsEngine.cs` (`RemoveLandblock` CurrCell clear + NO-LANDBLOCK verbatim).
|
||||
|
||||
**Acceptance:** run into a portal, run out, re-enter — repeatedly in one session, each time placing + streaming correctly. Verify the dungeon→outdoor exit completes promptly (log `teleport complete`, not `teleport HOLD gave up … force-snapping`) with `streaming: dungeon EXIT-expand`, the outdoor world fully streamed, collision working. Also re-checks **#138**.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -211,13 +218,15 @@ exit; (c) the position desync = the player controller / streaming observer disag
|
|||
post-exit world position (the avatar moves in one frame, the streaming/camera in another).
|
||||
Pairs with #135 (`712f17f`/`2c92375`) — same collapse machinery; the EXIT path is the gap.
|
||||
|
||||
**Files:** `src/AcDream.App/Streaming/StreamingController.cs` (`ExitDungeonExpand`, the
|
||||
collapse/expand hysteresis), `src/AcDream.App/Rendering/GameWindow.cs` (`OnLivePositionUpdated`
|
||||
teleport recenter ~4912, the streaming Tick gate ~6890, the PortalSpace observer branch),
|
||||
`TeleportArrivalController`. Cross-check the post-exit shadow-object/collision registration.
|
||||
**UPDATE 2026-06-20 — the position-desync HALF is FIXED via #145; the remaining half is narrowed + RE-SCOPED to entity render/lifecycle:** #145 fixed the cell-rooting (the player was rooted into the source dungeon's frame on teleport-out → ACE rejected all movement → "avatar moves but position doesn't track"). With that fixed (server-authoritative placement + drop-stale-center + CurrCell clear), the outdoor TERRAIN now streams + renders and movement is accepted. What REMAINS, observed in the #145 verification run:
|
||||
- **(A) Server-spawned objects don't render after teleport-back.** NOT a re-broadcast gap — the `ACDREAM_DUMP_LIVE_SPAWNS` trace shows ACE DOES re-send `CreateObject` for all Holtburg weenies on return (Doors, NPCs "Agent of the Arcanum"/"Wedding Planner", "Holtburg Meeting Hall Portal", chests, …) and acdream receives + processes them (`OnLiveEntitySpawnedLocked` → `AppendLiveEntity`). They just don't appear. So the bug is downstream: entity render/storage during the teleport streaming churn — candidates: the re-added live entities are dropped by a subsequent `GpuWorldState.AddLandblock` record-replace for the same landblock, OR the per-instance render data (`WbEntitySpawnAdapter.OnCreate`) isn't built/registered, OR a render-root/visibility gate while `CurrCell` re-acquires.
|
||||
- **(B) Own avatar stops rendering after a couple of round-trips.** The player entity (persistent, rescued+re-injected via `GpuWorldState.DrainRescued`→`AppendLiveEntity` at `GameWindow` ~:7421) is lost/duplicated across repeated rescue/re-inject cycles. Cumulative (first trip OK, later trips vanish).
|
||||
|
||||
**Acceptance:** portal out of the 0x0007 dungeon → full outdoor world streams (trees/scenery
|
||||
present), collision works, and the player position tracks correctly (no avatar-vs-camera desync).
|
||||
Both are the **entity-lifecycle/render path across a teleport**, NOT the streaming collapse/expand (which now works — terrain streams). Start here: instrument `GpuWorldState` (AppendLiveEntity / AddLandblock / RemoveLandblock / DrainRescued) to trace one guid (a Door + the player 0x5000000B) across an in→out cycle and find where it leaves the rendered set.
|
||||
|
||||
**Files:** `src/AcDream.App/Streaming/GpuWorldState.cs` (`AddLandblock` pending-merge vs record-replace, `AppendLiveEntity`, `RemoveLandblock`/rescue, `DrainRescued`, `RelocateEntity`), `src/AcDream.App/Rendering/GameWindow.cs` (`OnLiveEntitySpawnedLocked` ~:2696, rescued re-inject ~:7421), the per-instance render adapter (`WbEntitySpawnAdapter`). The streaming collapse/expand (`StreamingController`) is no longer the suspect.
|
||||
|
||||
**Acceptance:** portal out of the 0x0007 dungeon → full outdoor world streams (trees/scenery present), **server objects (doors/NPCs/portals) render**, **own avatar renders across repeated round-trips**, collision works, position tracks (no avatar-vs-camera desync).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue