fix(portal): commit destination projection before reveal

Retail installs the destination CObjCell before hidden world simulation resumes. Rebucket the retained local-player projection inside the Place transaction so Hidden/UnHide scripts and particles settle behind the portal viewport instead of firing after reveal. Preserve the existing unloaded-cell scheduler gate and pin the ordering with a cross-landblock placement test.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-25 10:32:21 +02:00
parent 1a14812c44
commit 7aa9618136
4 changed files with 173 additions and 1 deletions

View file

@ -236,6 +236,21 @@ internal sealed class LocalPlayerTeleportPlacement : ILocalPlayerTeleportPlaceme
entity.SetPosition(controller.Position);
entity.ParentCellId = controller.CellId;
entity.Rotation = rotation;
// Retail CPhysicsObj::enter_world installs the object in its
// destination CObjCell before hidden scripts/particles resume.
// The accepted Position packet has already advanced FullCellId,
// but that wire fact alone does not move acdream's retained
// projection out of its source/pending GPU bucket. Commit both
// halves of the spatial move here, while portal space still owns
// the viewport, so CPhysicsObj::update_object's cell-gated tail
// can advance the Hidden/UnHide PES chain at retail's boundary.
if (!_liveEntities.RebucketLiveEntity(playerGuid, controller.CellId))
{
throw new InvalidOperationException(
$"Teleport Place could not commit local player 0x{playerGuid:X8} "
+ $"to destination cell 0x{controller.CellId:X8}.");
}
}
// Retail teleport_hook tail @ 0x00514ED0 clears the local target and