using System.Numerics;
using AcDream.App.Input;
using AcDream.App.Net;
using AcDream.App.Physics;
using AcDream.App.Rendering;
using AcDream.App.Update;
using AcDream.App.World;
using AcDream.Core.Net.Messages;
using AcDream.Core.Audio;
using AcDream.Core.Physics;
using AcDream.Core.Rendering;
using AcDream.Core.World;
using AcDream.Runtime;
using AcDream.Runtime.Physics;
using AcDream.Runtime.Session;
using AcDream.Runtime.World;
namespace AcDream.App.Streaming;
internal interface ILocalPlayerTeleportNetworkSink
{
void OnTeleportStarted(uint sequence);
void OfferDestination(
RuntimeTeleportDestination destination,
bool teleportTimestampAdvanced);
///
/// Enter-world round (2026-08-17): the graphical host's local-player
/// first-entry conductor completed its canonical initial placement.
/// This is the login analogue of retail's
/// SmartBox::UseTime @ 0x00455410 setting
/// position_update_complete = 1 — the fact that lets the login
/// portal-space presentation leave its Tunnel hold. LoginComplete
/// (0xA1) itself now rides the presentation's own
/// edge, matching
/// retail's send at the WorldFadeIn end
/// (gmSmartBoxUI::UseTime @ 0x004D745D →
/// CPlayerSystem::SendLoginCompleteNotification @ 0x00562E90).
///
void OnLocalPlayerFirstEntryCompleted();
///
/// Enter-click round (2026-08-17): arm the login wormhole PRESENTATION at
/// the character-select Enter click (and its direct-connect /
/// enter-after-create equivalents), BEFORE the EnterWorld server
/// round-trip. This is a REGISTERED user-directed deviation from retail:
/// retail shows the empty pre-player gameplay screen (black behind the
/// UI) from CPlayerSystem::LogOnCharacter @ 0x0055F890 /
/// CM_Login::SendNotice_BeginEnterWorld @ 0x006AD810 until
/// CreatePlayer flips SmartBox::teleport_in_progress @ 0x00451C20
/// and gmSmartBoxUI::UseTime @ 0x004D6EAB begins TAS_TUNNEL. The
/// user prefers the tunnel to cover that whole wait — see the divergence
/// register row added with this method. Invoked from the ONE host edge
/// every entry route shares: ILiveSessionLifecycleHost
/// .ApplySelectedCharacter, which runs immediately before the
/// EnterWorld wire send on all three routes.
///
void ArmLoginTunnel();
///
/// Logout round (2026-08-17): the confirmed exit-to-character-select
/// click — retail's gmGamePlayUI::UseTime @ 0x004EA454 →
/// CPlayerSystem::LogOffCharacter(0) drain, forwarded through
/// this construction-order bridge so the retained UI (built before the
/// controller) can reach the one wormhole owner. Refusals are logged by
/// the controller; the grounded gate runs upstream in
/// RetailUiRuntime (the transient_state & CONTACT
/// branch @ 0x004EA445).
///
void RequestLogout();
void ResetSession();
void ResetGenerationPresentation();
}
///
/// Construction-order bridge for the inbound session. It carries no teleport
/// state: after binding, every packet and reset is forwarded to the one
/// owner.
///
internal sealed class DeferredLocalPlayerTeleportNetworkSink
: ILocalPlayerTeleportNetworkSink
{
private ILocalPlayerTeleportNetworkSink? _inner;
public void Bind(ILocalPlayerTeleportNetworkSink inner)
{
ArgumentNullException.ThrowIfNull(inner);
if (Interlocked.CompareExchange(ref _inner, inner, null) is not null)
throw new InvalidOperationException("The local teleport sink is already bound.");
}
public IDisposable BindOwned(ILocalPlayerTeleportNetworkSink inner)
{
Bind(inner);
return new Binding(this, inner);
}
private void Unbind(ILocalPlayerTeleportNetworkSink expected)
{
_ = Interlocked.CompareExchange(ref _inner, null, expected);
}
public void OnTeleportStarted(uint sequence) => Required().OnTeleportStarted(sequence);
public void OfferDestination(
RuntimeTeleportDestination destination,
bool teleportTimestampAdvanced) =>
Required().OfferDestination(destination, teleportTimestampAdvanced);
public void OnLocalPlayerFirstEntryCompleted() =>
Required().OnLocalPlayerFirstEntryCompleted();
public void ArmLoginTunnel() => Required().ArmLoginTunnel();
public void RequestLogout() => Required().RequestLogout();
public void ResetSession() => Required().ResetSession();
public void ResetGenerationPresentation() =>
Required().ResetGenerationPresentation();
private ILocalPlayerTeleportNetworkSink Required() =>
_inner ?? throw new InvalidOperationException(
"The local teleport sink was used before composition completed.");
private sealed class Binding : IDisposable
{
private DeferredLocalPlayerTeleportNetworkSink? _owner;
private readonly ILocalPlayerTeleportNetworkSink _expected;
public Binding(
DeferredLocalPlayerTeleportNetworkSink owner,
ILocalPlayerTeleportNetworkSink expected)
{
_owner = owner;
_expected = expected;
}
public void Dispose() =>
Interlocked.Exchange(ref _owner, null)?.Unbind(_expected);
}
}
internal interface ILocalPlayerTeleportInputLifetime
{
void EndMouseLook();
}
internal interface ILocalPlayerTeleportModeOperations
{
PlayerMovementController? Controller { get; }
Matrix4x4 Projection { get; }
bool TryEnterPortalSpace();
///
/// Enter-world round (2026-08-17): the LOGIN arm's portal-space entry.
/// Unlike — whose F751 callers run when
/// player-mode presentation is already attached (or deliberately absent,
/// e.g. fly mode) — the login tunnel begins BEFORE any player-mode entry
/// has happened, so this operation must also perform the same
/// presentation attach the post-reveal auto-entry used to do (chase
/// camera, shadow, animation sinks) before flipping the controller into
/// portal space. Refuses (retryable) until the Runtime first-entry
/// conductor has published the movement controller.
///
bool TryEnterPortalSpaceForLogin();
void EnterWorld();
}
internal interface ILocalPlayerTeleportAuthority
{
bool IsFreshStart(ushort sequence);
}
///
/// Enter-click round (2026-08-17): the armed pre-reveal login tunnel's read
/// of the Runtime character-selection lifecycle (a typed seam, not a stored
/// delegate — frame-phase owners hold no delegate fields per the GameWindow
/// decomposition invariant). Resolved per call against the live owner.
///
internal interface ILocalPlayerLoginLifecycleSource
{
RuntimeCharacterSelectionLifecycle SelectionLifecycle { get; }
}
/// Production adapter over the canonical GameRuntime owner.
internal sealed class RuntimeLoginLifecycleSource
: ILocalPlayerLoginLifecycleSource
{
private readonly GameRuntime _runtime;
public RuntimeLoginLifecycleSource(GameRuntime runtime) =>
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
public RuntimeCharacterSelectionLifecycle SelectionLifecycle =>
_runtime.CharacterSelection.Snapshot.Lifecycle;
}
///
/// Logout round (2026-08-17): the logout pump's typed seams — the wire/session
/// transaction pair on the canonical Runtime session owner, the local-player
/// PK fact, and the retail side effects of the request itself (chat line +
/// command-interpreter disable). See CPlayerSystem::RequestLogOff
/// @ 0x00562DD0 for the retail body these mirror.
///
internal interface ILocalPlayerLogoutOperations
{
///
/// Retail ACCWeenieObject::IsPlayerKiller @ 0x0058C910: PWD
/// bitfield 0x20 (PK) or 0x2000000 (PKLite). Drives the
/// +20 s logoff hold (RequestLogOff @ 0x00562E4E-0x00562E67).
///
bool IsLocalPlayerKiller { get; }
///
/// Retail CPlayerSystem::LogOffCharacter(0) @ 0x00563520 +
/// RequestLogOff @ 0x00562DD0: options flush, "Logging off..."
/// chat line (type 0, AddTextToScroll @ 0x00562DF2), the 0xF653
/// wire send, and the command-interpreter disable
/// (HandleLogOff @ 0x006B3330).
///
bool BeginCharacterLogOff();
/// The server's opcode-only 0xF653 echo has landed.
bool IsCharacterLogOffConfirmed { get; }
///
/// The return-to-character-select session transaction
/// ().
///
bool CompleteCharacterLogOff();
}
/// Production adapter over the canonical Runtime owners.
internal sealed class RuntimeLocalPlayerLogoutOperations
: ILocalPlayerLogoutOperations
{
private readonly GameRuntime _runtime;
private readonly RuntimeLocalPlayerMovementState _movement;
private readonly ILiveWorldSessionSource _session;
private readonly AcDream.Core.Items.ClientObjectTable _objects;
private readonly ILocalPlayerIdentitySource _identity;
public RuntimeLocalPlayerLogoutOperations(
GameRuntime runtime,
RuntimeLocalPlayerMovementState movement,
ILiveWorldSessionSource session,
AcDream.Core.Items.ClientObjectTable objects,
ILocalPlayerIdentitySource identity)
{
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
_movement = movement ?? throw new ArgumentNullException(nameof(movement));
_session = session ?? throw new ArgumentNullException(nameof(session));
_objects = objects ?? throw new ArgumentNullException(nameof(objects));
_identity = identity ?? throw new ArgumentNullException(nameof(identity));
}
public bool IsLocalPlayerKiller
{
get
{
uint bitfield = _objects.Get(_identity.ServerGuid)
?.PublicWeenieBitfield ?? 0u;
// IsPlayerKiller @ 0x0058C910: (bitfield & 0x20) | (bitfield &
// 0x2000000) — the PK and PKLite PWD bits.
return (bitfield & 0x20u) != 0u || (bitfield & 0x2000000u) != 0u;
}
}
public bool BeginCharacterLogOff()
{
// The Runtime command runs retail's SaveToServer-first ordering
// (the pre-logoff flush) then sends 0xF653.
if (!_runtime.Session.BeginCharacterLogOff(_runtime.Generation)
.Accepted)
{
return false;
}
// Retail RequestLogOff's own side effects, in its order: the chat
// line (@ 0x00562DF2, AddTextToScroll(str, 0, 1, 0)) and the
// command-interpreter disable (@ 0x00562E6D).
_runtime.CommunicationOwner.AddText(
"Logging off...",
AcDream.Core.Chat.RetailLogTextType.Default);
_movement.DisableCommandInterpreter();
return true;
}
public bool IsCharacterLogOffConfirmed =>
_session.CurrentSession?.IsCharacterLogOffConfirmed == true;
public bool CompleteCharacterLogOff() =>
_runtime.Session.CompleteCharacterLogOff(_runtime.Generation)
.Accepted;
}
internal sealed class LiveLocalPlayerTeleportAuthority
: ILocalPlayerTeleportAuthority
{
private readonly LiveEntityRuntime _liveEntities;
private readonly ILocalPlayerIdentitySource _identity;
public LiveLocalPlayerTeleportAuthority(
LiveEntityRuntime liveEntities,
ILocalPlayerIdentitySource identity)
{
_liveEntities = liveEntities ?? throw new ArgumentNullException(nameof(liveEntities));
_identity = identity ?? throw new ArgumentNullException(nameof(identity));
}
public bool IsFreshStart(ushort sequence) =>
_liveEntities.IsFreshTeleportStart(_identity.ServerGuid, sequence);
}
internal interface ILocalPlayerTeleportStreamingOperations
{
int CenterX { get; }
int CenterY { get; }
bool IsRecenterPending { get; }
bool BeginRecenter(int x, int y, bool isSealedDungeon);
bool ResetRecenter(bool sessionEnding);
bool IsSealedDungeon(uint cellId);
}
internal sealed class LocalPlayerTeleportStreamingOperations
: ILocalPlayerTeleportStreamingOperations
{
private readonly LiveWorldOriginState _origin;
private readonly StreamingOriginRecenterCoordinator _recenter;
private readonly StreamingController _streaming;
private readonly ISealedDungeonCellClassifier _sealedDungeonCells;
public LocalPlayerTeleportStreamingOperations(
LiveWorldOriginState origin,
StreamingOriginRecenterCoordinator recenter,
StreamingController streaming,
ISealedDungeonCellClassifier sealedDungeonCells)
{
_origin = origin ?? throw new ArgumentNullException(nameof(origin));
_recenter = recenter ?? throw new ArgumentNullException(nameof(recenter));
_streaming = streaming ?? throw new ArgumentNullException(nameof(streaming));
_sealedDungeonCells = sealedDungeonCells
?? throw new ArgumentNullException(nameof(sealedDungeonCells));
}
public int CenterX => _origin.CenterX;
public int CenterY => _origin.CenterY;
public bool IsRecenterPending => _recenter.IsPending;
public bool BeginRecenter(int x, int y, bool isSealedDungeon) =>
_recenter.Begin(x, y, isSealedDungeon);
public bool ResetRecenter(bool sessionEnding) =>
_recenter.Reset(sessionEnding);
public bool IsSealedDungeon(uint cellId) =>
_sealedDungeonCells.IsSealedDungeon(cellId);
}
internal interface ILocalPlayerTeleportPlacement
{
void Place(Quaternion rotation);
}
///
/// C4 route 3: acknowledges the local player's deferred portal arrival —
/// the canonical placement itself now runs through
///
/// (a portal arm sharing route 2's Runtime SetPosition owner), retiring the
/// duplicate Resolve/SetPosition authority this class used to own (D1;
/// docs/research/2026-08-04-c4-route-3-contract.md D-T4). This class runs
/// AFTER that commit succeeds. A10 review fix (2026-08-05): the render
/// pose write and rebucket this method performs
/// are REDUNDANT repeats of a mutation the canonical Place receipt already
/// made — RuntimePlacementPresentationSink.TryApply →
/// LiveEntityRuntime.TryApplyRuntimePlacementPlace already calls
/// entity.SetPosition/sets Rotation/ParentCellId and
/// rebuckets, synchronously, before TryPublishPlace's OWN snapshot
/// even runs (proof obligation P2's ordering). This method's writes are
/// therefore harmless-but-duplicate, not the render entity's ONLY mover as
/// an earlier revision of this comment claimed; kept because they cost
/// nothing extra and this is also where the retail teleport_hook tail's
/// remaining local-player-visible actions run (target-watcher
/// notification, camera reset, spatial reconcile).
/// is the retained accepted destination's wire
/// orientation — the resolved body orientation was already committed
/// identically by CommitCanonical (retail's teleport branch does not
/// independently reorient the mover), so re-deriving it here would only add
/// a second copy of the same source of truth.
///
internal sealed class LocalPlayerTeleportPlacement : ILocalPlayerTeleportPlacement
{
private readonly LiveEntityRuntime _liveEntities;
private readonly ILocalPlayerIdentitySource _identity;
private readonly IRuntimeLocalPlayerControllerSource _controller;
private readonly ILocalPlayerPhysicsHostSource _host;
private readonly ChaseCameraInputState _cameras;
private readonly ILiveSpatialReconcilePhase _spatial;
public LocalPlayerTeleportPlacement(
LiveEntityRuntime liveEntities,
ILocalPlayerIdentitySource identity,
IRuntimeLocalPlayerControllerSource controller,
ILocalPlayerPhysicsHostSource host,
ChaseCameraInputState cameras,
ILiveSpatialReconcilePhase spatial)
{
_liveEntities = liveEntities ?? throw new ArgumentNullException(nameof(liveEntities));
_identity = identity ?? throw new ArgumentNullException(nameof(identity));
_controller = controller ?? throw new ArgumentNullException(nameof(controller));
_host = host ?? throw new ArgumentNullException(nameof(host));
_cameras = cameras ?? throw new ArgumentNullException(nameof(cameras));
_spatial = spatial ?? throw new ArgumentNullException(nameof(spatial));
}
public void Place(Quaternion rotation)
{
PlayerMovementController controller = _controller.Controller
?? throw new InvalidOperationException(
"Teleport Place ran without the local player controller.");
uint playerGuid = _identity.ServerGuid;
if (_liveEntities.TryGetWorldEntity(
playerGuid,
out WorldEntity? entity))
{
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 canonical commit has already advanced FullCellId, but that
// 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
// notifies every watcher that this object teleported. The body's
// constraint leash re-arm and orientation are already the canonical
// commit's job (RuntimeAcceptedPositionDriveController
// .ReconcileAndAcknowledgePortal -> PlayerMovementController
// .CommitCanonicalTeleportFrame), so this suffix only acknowledges
// the result into presentation.
_host.Host?.NotifyTeleported();
_cameras.Legacy?.Update(controller.Position, controller.Yaw);
_cameras.Retail?.ResetViewerToPlayer(controller.Position, controller.Yaw);
_spatial.Reconcile();
PhysicsDiagnostics.LogTeleport(
"PLACED",
controller.CellId,
"readiness=complete");
Console.WriteLine(
$"live: teleport materialized - snapped to {controller.Position} "
+ $"cell=0x{controller.CellId:X8}");
}
}
internal interface ILocalPlayerTeleportSession
{
void SendLoginComplete();
}
internal sealed class LocalPlayerTeleportSession : ILocalPlayerTeleportSession
{
private readonly ILiveWorldSessionSource _session;
public LocalPlayerTeleportSession(ILiveWorldSessionSource session) =>
_session = session ?? throw new ArgumentNullException(nameof(session));
public void SendLoginComplete() =>
_session.CurrentSession?.SendGameAction(
GameActionLoginComplete.Build());
}
internal interface ILocalPlayerTeleportPresentation : IDisposable
{
bool IsPortalViewportVisible { get; }
int CurrentTunnelFrame { get; }
void Begin(Matrix4x4 projection);
///
/// Logout round (2026-08-17): the wormhole run in REVERSE ORDER —
/// retail's BeginTeleportAnimation(TAS_WORLD_FADE_OUT)
/// @ 0x004D6E83. The sequencer enters at WorldFadeOut (the world
/// stays drawn while the view plane pulls in), then TunnelFadeIn →
/// Tunnel; the tunnel scene itself plays the SAME forward 40 fps
/// animation (set_sequence_animation @ 0x004D6F70 runs
/// identically for every tunnel-family entry — nothing renders
/// backwards), and the enter cue plays at this begin
/// (Sound_UI_EnterPortal @ 0x004D638E, unconditional). No exit
/// cue ever plays on logout: the character-select swap preempts
/// retail's TunnelContinue/FadeOut tail.
///
void BeginLogout(Matrix4x4 projection);
(TeleportAnimSnapshot Snapshot, IReadOnlyList Events)
Tick(float deltaSeconds, bool worldReady);
void TickTunnel(float deltaSeconds);
void PlayEnterCue();
void PlayExitCue();
void EnterTunnel();
void ExitTunnel();
void SetWaitCue(bool visible);
void Reset();
Matrix4x4 ApplyViewPlane(Matrix4x4 projection);
ICamera ApplyViewPlane(ICamera camera);
void DrawPortalViewport(int width, int height, Matrix4x4 projection);
}
internal sealed class LocalPlayerTeleportPresentation
: ILocalPlayerTeleportPresentation
{
private readonly TeleportAnimSequencer _animation = new();
private readonly TeleportViewPlaneController _viewPlane = new();
private readonly PortalTunnelPresentation _tunnel;
public LocalPlayerTeleportPresentation(PortalTunnelPresentation tunnel) =>
_tunnel = tunnel ?? throw new ArgumentNullException(nameof(tunnel));
public bool IsPortalViewportVisible => _tunnel.IsVisible;
public int CurrentTunnelFrame => _tunnel.CurrentAnimationFrame;
public void Begin(Matrix4x4 projection)
{
_viewPlane.Begin(projection);
_animation.Begin(TeleportEntryKind.Portal);
}
public void BeginLogout(Matrix4x4 projection)
{
_viewPlane.Begin(projection);
_animation.Begin(TeleportEntryKind.Logout);
}
public (TeleportAnimSnapshot Snapshot, IReadOnlyList Events)
Tick(float deltaSeconds, bool worldReady)
{
var (snapshot, events) = _animation.Tick(
deltaSeconds,
worldReady,
CurrentTunnelFrame);
_viewPlane.Update(snapshot);
return (snapshot, events);
}
public void TickTunnel(float deltaSeconds) => _tunnel.Tick(deltaSeconds);
///
/// Interface-sound sink for the portal cues. Retail plays these from the
/// teleport-animation boundary, not from the tunnel renderer:
/// PlaySoundFromCenter(Sound_UI_EnterPortal, GetUISoundTable()) at
/// 0x004D638E inside gmSmartBoxUI::BeginTeleportAnimation, and
/// Sound_UI_ExitPortal at 0x004D7405. Assigned at composition;
/// null (and therefore silent) when audio is unavailable.
///
public Action? UiSoundSink { get; set; }
///
/// Sound_UI_EnterPortal, at retail's moment: the START of the
/// teleport animation (gmSmartBoxUI::BeginTeleportAnimation @
/// 0x004D638E), which the sequencer marks as
/// TeleportAnimEvent.PlayEnterSound — NOT when the tunnel viewport
/// first appears, which is a TunnelFadeIn later.
///
public void PlayEnterCue() => UiSoundSink?.Invoke(SoundId.UI_EnterPortal);
///
/// Sound_UI_ExitPortal @ 0x004D7405, at the
/// TunnelFadeOut to WorldFadeIn edge — the sequencer's
/// TeleportAnimEvent.PlayExitSound, the same tick the world viewport
/// is revealed.
///
public void PlayExitCue() => UiSoundSink?.Invoke(SoundId.UI_ExitPortal);
public void EnterTunnel() => _tunnel.Enter();
public void ExitTunnel() => _tunnel.Exit();
public void SetWaitCue(bool visible) => _tunnel.SetWaitCue(visible);
public void Reset()
{
_animation.Reset();
_viewPlane.Reset();
_tunnel.Exit();
}
public Matrix4x4 ApplyViewPlane(Matrix4x4 projection) =>
_viewPlane.Apply(projection);
public ICamera ApplyViewPlane(ICamera camera) => _viewPlane.ApplyTo(camera);
public void DrawPortalViewport(int width, int height, Matrix4x4 projection) =>
_tunnel.Draw(width, height, ApplyViewPlane(projection));
public void Dispose() => _tunnel.Dispose();
}
///
/// Graphical-host orchestrator for Runtime's local F751/Position-correlated
/// portal lifetime. Runtime owns sequence/destination/reveal authority; this
/// update-thread adapter owns portal viewport activation, render-space aim,
/// readiness preparation, placement callbacks, and LoginComplete presentation.
///
internal sealed class LocalPlayerTeleportController
: ILocalPlayerTeleportFramePhase,
ILocalPlayerTeleportNetworkSink,
AcDream.App.Interaction.ISelectionViewPlaneSource,
IDisposable
{
private readonly RuntimeWorldTransitState _transit;
private readonly ILocalPlayerTeleportAuthority _authority;
private readonly ILocalPlayerTeleportInputLifetime _input;
private readonly ILocalPlayerTeleportModeOperations _mode;
private readonly ILocalPlayerTeleportStreamingOperations _streaming;
private readonly WorldRevealCoordinator _worldReveal;
private readonly ILocalPlayerTeleportPlacement _placement;
private readonly ILocalPlayerTeleportSession _session;
private readonly ILocalPlayerTeleportPresentation _presentation;
private readonly RuntimeAcceptedPositionDriveController _acceptedPositionDrive;
private uint _pendingCell;
private Quaternion _pendingRotation = Quaternion.Identity;
private long _pendingRevealGeneration;
private RuntimeTeleportDestination _pendingDestination;
private bool _hasPendingDestination;
///
/// A1 review fix (2026-08-05): true once the canonical Runtime commit
/// for THIS teleport lifetime has actually happened. See the class doc
/// on for why this exists.
///
private bool _placementCommitted;
///
/// A1 review fix: true while a DeferredCell park is outstanding
/// for the local player's one possible pending operation. See
/// .
///
private bool _awaitingDeferredWake;
private float _holdSeconds;
private long _lifetimeGeneration;
private bool _disposed;
///
/// Enter-world round (2026-08-17): the login reveal generation this
/// controller's presentation currently owns (0 = none). Retail runs the
/// SAME wormhole machine at initial login as at an F751 teleport —
/// SmartBox::teleport_in_progress @ 0x00451C20 returns 1 whenever
/// the SmartBox has a player whose position_update_complete is
/// still 0, which is true the moment the login CreatePlayer lands, and
/// gmSmartBoxUI::UseTime @ 0x004D6EAB edge-detects that flag into
/// BeginTeleportAnimation(TAS_TUNNEL) @ 0x004D6EC9 (which plays
/// Sound_UI_EnterPortal @ 0x004D638E) with no F751 involved.
/// acdream's canonical equivalent of that condition is Runtime's login
/// reveal (,
/// begun on the first accepted local-player position) — so this arm keys
/// the same presentation off that Runtime-owned lifecycle instead of a
/// teleport start.
///
private long _loginRevealGeneration;
private bool _loginPresentationActive;
///
/// Enter-click round (2026-08-17): true while the login tunnel is armed
/// PRE-REVEAL — from the character-select Enter click (host edge
/// ApplySelectedCharacter, shared by direct connect, roster Enter,
/// and enter-after-create) until the Runtime login reveal adopts the
/// running presentation, or the enter transaction falls back to character
/// select (rejected EnterWorld), or a session/teleport reset withdraws
/// it. Registered deviation from retail's pre-CreatePlayer black — see
/// .
///
private bool _loginTunnelArmed;
///
/// Latched by — the
/// first-entry conductor's canonical initial placement committed. The
/// login pump's worldReady requires it, so the sequencer cannot
/// leave its Tunnel hold (and therefore cannot reach
/// ) before the same
/// placement contract that previously gated the immediate LoginComplete
/// send. Session-scoped: cleared only by session-level resets.
///
private bool _loginPlacementCompleted;
private float _loginHoldSeconds;
///
/// Enter-click round (2026-08-17): resolved PER CALL (never captured —
/// claude-memory/feedback_resolve_deferred_funcs_per_call.md). The armed
/// pre-reveal tunnel projects the Runtime character-selection lifecycle:
/// EnteringWorld/InWorld keep it armed; a regression to
/// AwaitingSelection (rejected EnterWorld —
/// LiveSessionController.EnterHighlightedCore's
/// ReturnToSelection) disarms it so the user is not left staring
/// at a tunnel on the character-select screen.
///
private readonly ILocalPlayerLoginLifecycleSource _loginLifecycle;
///
/// Logout round (2026-08-17): the logout pump's Runtime seams — see
/// . The pump itself is the
/// third arm of retail's ONE wormhole machine
/// (gmSmartBoxUI::UseTime @ 0x004D6E30 drives login, teleport,
/// and logout from the same function).
///
private readonly ILocalPlayerLogoutOperations _logout;
public LocalPlayerTeleportController(
ILocalPlayerTeleportAuthority authority,
ILocalPlayerTeleportInputLifetime input,
ILocalPlayerTeleportModeOperations mode,
ILocalPlayerTeleportStreamingOperations streaming,
RuntimeWorldTransitState transit,
WorldRevealCoordinator worldReveal,
ILocalPlayerTeleportPlacement placement,
ILocalPlayerTeleportSession session,
ILocalPlayerTeleportPresentation presentation,
RuntimeAcceptedPositionDriveController acceptedPositionDrive,
ILocalPlayerLoginLifecycleSource loginLifecycle,
ILocalPlayerLogoutOperations logout)
{
_authority = authority ?? throw new ArgumentNullException(nameof(authority));
_input = input ?? throw new ArgumentNullException(nameof(input));
_mode = mode ?? throw new ArgumentNullException(nameof(mode));
_streaming = streaming ?? throw new ArgumentNullException(nameof(streaming));
_transit = transit ?? throw new ArgumentNullException(nameof(transit));
_worldReveal = worldReveal ?? throw new ArgumentNullException(nameof(worldReveal));
_placement = placement ?? throw new ArgumentNullException(nameof(placement));
_session = session ?? throw new ArgumentNullException(nameof(session));
_presentation = presentation ?? throw new ArgumentNullException(nameof(presentation));
_acceptedPositionDrive = acceptedPositionDrive
?? throw new ArgumentNullException(nameof(acceptedPositionDrive));
_loginLifecycle = loginLifecycle
?? throw new ArgumentNullException(nameof(loginLifecycle));
_logout = logout ?? throw new ArgumentNullException(nameof(logout));
}
public bool IsActive => _transit.IsTeleportActive;
public bool IsPortalViewportVisible => _presentation.IsPortalViewportVisible;
///
/// The destination the portal viewport is currently holding for. The
/// render frame's reveal-preparation arm
/// ()
/// keys composite-texture preparation and readiness evaluation off this
/// cell; the login presentation must report its own destination here
/// because entering portal space flips ChaseModeEverEntered, which
/// retires the fallback "waiting for login" cell source that used to keep
/// preparation running pre-entry.
///
public uint ActiveDestinationCell
{
get
{
if (_transit.IsTeleportActive)
return _pendingCell;
if (_loginPresentationActive)
{
RuntimePortalSnapshot snapshot = _transit.Snapshot;
if (snapshot.Kind == RuntimePortalKind.Login
&& snapshot.Generation == _loginRevealGeneration)
{
return snapshot.Readiness.DestinationCell;
}
}
return 0u;
}
}
public void OnTeleportStarted(uint sequence)
{
ThrowIfDisposed();
// Logout round (2026-08-17): a logoff in flight owns the wormhole;
// the character is leaving the world and no F751 may supersede the
// logout presentation (ACE does not teleport a logging-off player —
// Player.LogOut sets IsBusy/IsLoggingOut before any motion runs).
if (_transit.IsLogoutActive)
{
Console.WriteLine(
$"live: teleport start ignored during logout (seq={sequence})");
return;
}
ushort teleportSequence = (ushort)sequence;
if (!_authority.IsFreshStart(teleportSequence)
|| !_transit.CanQueueTeleportStart(teleportSequence))
{
return;
}
long observedGeneration = _lifetimeGeneration;
_input.EndMouseLook();
if (_lifetimeGeneration != observedGeneration)
return;
long resetGeneration = ResetTransit(clearSession: false);
if (_lifetimeGeneration != resetGeneration)
return;
if (!_transit.TryQueueTeleportStart(teleportSequence))
return;
TryActivatePendingPresentation();
Console.WriteLine($"live: teleport queued (seq={sequence})");
}
public void OfferDestination(
RuntimeTeleportDestination destination,
bool teleportTimestampAdvanced)
{
ThrowIfDisposed();
_transit.OfferTeleportDestination(
destination,
teleportTimestampAdvanced);
TryAimAcceptedDestination();
}
public void OnLocalPlayerFirstEntryCompleted()
{
ThrowIfDisposed();
_loginPlacementCompleted = true;
}
///
/// Enter-click round (2026-08-17): begin the login tunnel PRESENTATION at
/// the Enter click, before the EnterWorld server round-trip (registered
/// user-directed deviation — retail presents black here; see
/// ).
///
///
/// The enter cue plays HERE, at the click: retail's rule is "cue at the
/// animation begin" (gmSmartBoxUI::BeginTeleportAnimation plays
/// Sound_UI_EnterPortal unconditionally at 0x004D638E), and
/// this deviation moves the animation begin to the click — so the cue
/// moves with it, keeping cue-and-tunnel continuous instead of splitting
/// them across the round-trip.
///
///
///
/// The first sequencer tick is consumed SYNCHRONOUSLY (dt = 0) rather
/// than left to the frame pump: the Enter command that follows this call
/// blocks the update thread for the whole ServerReady round-trip
/// (WorldSession.EnterWorldCore), so a deferred first tick would
/// leave the frame black for exactly the wait this deviation exists to
/// cover. The render thread draws the tunnel scene independently every
/// frame once it is visible.
///
///
public void ArmLoginTunnel()
{
ThrowIfDisposed();
if (_loginTunnelArmed
|| _loginPresentationActive
|| _transit.IsTeleportActive
|| _transit.HasPendingTeleportStart)
{
return;
}
long generation = _lifetimeGeneration;
_presentation.Begin(_mode.Projection);
if (_lifetimeGeneration != generation)
return;
var (_, events) = _presentation.Tick(0f, worldReady: false);
if (_lifetimeGeneration != generation)
return;
if (!ProcessArmedLoginTunnelEvents(events, generation))
return;
_loginTunnelArmed = true;
_loginHoldSeconds = 0f;
Console.WriteLine("live: login tunnel armed at enter click");
}
///
/// The armed pre-reveal tunnel's event consumer — only the two
/// begin-edge events can occur while worldReady is pinned false
/// (the sequencer holds in Tunnel); anything else is ignored. Returns
/// false when a nested callback retired this lifetime.
///
private bool ProcessArmedLoginTunnelEvents(
IReadOnlyList events,
long generation)
{
foreach (TeleportAnimEvent teleportEvent in events)
{
switch (teleportEvent)
{
case TeleportAnimEvent.PlayEnterSound:
Console.WriteLine(
"live: login portal-space enter cue "
+ "(Sound_UI_EnterPortal)");
_presentation.PlayEnterCue();
if (_lifetimeGeneration != generation)
return false;
break;
case TeleportAnimEvent.EnterTunnel:
_presentation.EnterTunnel();
if (_lifetimeGeneration != generation)
return false;
break;
default:
break;
}
}
return true;
}
// ── Logout round (2026-08-17): the logout arm of retail's ONE wormhole
// machine. Retail derivation:
// gmGamePlayUI::UseTime @ 0x004EA3A0 — confirmed Yes drains into
// CPlayerSystem::LogOffCharacter(0) when the player is grounded
// (transient_state & CONTACT — the upstream RetailUiRuntime gate).
// CPlayerSystem::LogOffCharacter @ 0x00563520 — SaveToServer first.
// CPlayerSystem::RequestLogOff @ 0x00562DD0 — "Logging off..." chat,
// 0xF653 send, logOffRequestTime = now + 3.0 (+20.0 PK),
// CommandInterpreter::HandleLogOff @ 0x006B3330 → Disable. The
// SERVER then broadcasts the LogOut motion (ACE Player.cs:596 →
// SendMotionAsCommands), which plays on the local player through
// the ordinary inbound movement funnel during this hold.
// gmSmartBoxUI::UseTime @ 0x004D6E64 — hold elapsed →
// BeginTeleportAnimation(TAS_WORLD_FADE_OUT) (enter cue) →
// TunnelFadeIn → Tunnel.
// Inbound 0xF653 echo (dispatch case @ 0x0055C963) →
// CPlayerSystem::ExecuteLogOff @ 0x0055D780 — world teardown with
// the logon connection kept; the fresh CharacterList in the same
// server batch re-shows character management
// (gmGamePlayUI::Update @ 0x004E9CD0 → QueueUIMode(0x1000000a)).
// No exit cue: the swap preempts the TunnelContinue/FadeOut tail.
/// The sink-forwarded UI entry — see
/// .
public void RequestLogout()
{
if (!TryRequestLogout())
Console.WriteLine("live: character logoff request refused");
}
///
/// The confirmed exit-to-character-select click. Returns false when a
/// logout, teleport, or login presentation already owns the machine or
/// the wire request refused.
///
public bool TryRequestLogout()
{
ThrowIfDisposed();
if (_transit.IsLogoutActive
|| _transit.IsTeleportActive
|| _transit.HasPendingTeleportStart
|| _loginPresentationActive
|| _loginTunnelArmed)
{
return false;
}
if (!_transit.TryBeginLogoutRequest(_logout.IsLocalPlayerKiller))
return false;
long generation = _lifetimeGeneration;
if (!_logout.BeginCharacterLogOff())
{
// Nothing went on the wire — roll the request back rather than
// running a wormhole for a logoff the server never heard.
if (_lifetimeGeneration == generation)
_transit.CancelLogoutRequest();
return false;
}
if (_lifetimeGeneration != generation)
return true;
// Retail's Disable() also ends any mouse-driven turning; the same
// input-lifetime call every teleport start already makes.
_input.EndMouseLook();
Console.WriteLine("live: character logoff requested");
return true;
}
///
/// Per-frame logout pump — the third arm of the wormhole machine (see
/// the derivation block above). Confirmation is polled every tick in
/// every pre-confirmed stage; on the Confirmed edge the handoff runs
/// IMMEDIATELY, exactly like retail's ExecuteLogOff-on-echo — the
/// character-select swap tears down whatever presentation state exists
/// (normally the held tunnel; on a fast confirmation, less).
///
private void TickLogout(float deltaSeconds)
{
long generation = _lifetimeGeneration;
if (_transit.LogoutStage is RuntimeLogoutStage.Requested
or RuntimeLogoutStage.PresentationActive
&& _logout.IsCharacterLogOffConfirmed)
{
_transit.AcknowledgeLogoutConfirmed();
}
switch (_transit.LogoutStage)
{
case RuntimeLogoutStage.Requested:
// The 3 s (23 s PK) hold: the server-broadcast LogOut
// motion is playing on the player in-world.
if (_transit.AdvanceLogoutHold(deltaSeconds))
{
_presentation.BeginLogout(_mode.Projection);
if (_lifetimeGeneration != generation)
return;
PumpLogoutPresentation(0f, generation);
}
return;
case RuntimeLogoutStage.PresentationActive:
PumpLogoutPresentation(deltaSeconds, generation);
return;
case RuntimeLogoutStage.Confirmed:
CompleteLogoutHandoff(generation);
return;
default:
return;
}
}
private void PumpLogoutPresentation(float deltaSeconds, long generation)
{
var (_, events) = _presentation.Tick(deltaSeconds, worldReady: false);
if (_lifetimeGeneration != generation)
return;
foreach (TeleportAnimEvent teleportEvent in events)
{
switch (teleportEvent)
{
case TeleportAnimEvent.PlayEnterSound:
// Sound_UI_EnterPortal @ 0x004D638E — unconditional at
// BeginTeleportAnimation, INCLUDING the logout's
// TAS_WORLD_FADE_OUT entry.
Console.WriteLine(
"live: logout portal-space enter cue "
+ "(Sound_UI_EnterPortal)");
_presentation.PlayEnterCue();
if (_lifetimeGeneration != generation)
return;
break;
case TeleportAnimEvent.EnterTunnel:
_presentation.EnterTunnel();
if (_lifetimeGeneration != generation)
return;
break;
default:
// worldReady is pinned false: Place / PlayExitSound /
// FireLoginComplete cannot fire (the sequencer holds in
// Tunnel), matching retail's preempted logout tail.
break;
}
}
_presentation.TickTunnel(deltaSeconds);
}
private void CompleteLogoutHandoff(long generation)
{
if (!_transit.CompleteLogout() || _lifetimeGeneration != generation)
return;
Console.WriteLine(
"live: logout confirmed — returning to character select");
if (_logout.CompleteCharacterLogOff())
{
// The transaction's world reset already ran this controller's
// ResetGenerationPresentation (retiring the tunnel) and the
// fresh selection state re-shows the character-management
// screen — retail's QueueUIMode(0x1000000a) analogue.
return;
}
// The transaction refused or degraded to a full stop. If a reset
// reached this controller the lifetime moved and everything is
// already clean; otherwise retire the presentation here so a
// refused transaction can never leave a stranded tunnel over a
// still-running world.
if (_lifetimeGeneration == generation)
{
Console.Error.WriteLine(
"live: return-to-character-select refused — retiring the "
+ "logout presentation");
_presentation.Reset();
}
}
public void Tick(float deltaSeconds)
{
ThrowIfDisposed();
// Logout round (2026-08-17): an active logout owns the whole
// wormhole machine, exactly as retail's one teleportInProgress flag
// does (SetTeleportInProgress(1) at the logout begin,
// gmSmartBoxUI::UseTime @ 0x004D6E8C). Teleport starts are refused
// while it runs (OnTeleportStarted's own guard).
if (_transit.IsLogoutActive)
{
TickLogout(deltaSeconds);
return;
}
TryActivatePendingPresentation();
TryAimAcceptedDestination();
if (!_transit.IsTeleportActive)
{
TickLoginPresentation(deltaSeconds);
return;
}
long generation = _lifetimeGeneration;
ushort sequence = _transit.ActiveTeleportSequence;
if (!_mode.TryEnterPortalSpace()
|| !IsCurrentLifetime(generation, sequence)
|| _mode.Controller is null)
{
return;
}
bool haveDestination = _pendingCell != 0u;
bool originReady = !_streaming.IsRecenterPending;
bool dataReady = haveDestination
&& originReady
&& _worldReveal.Evaluate(_pendingCell).IsReady;
if (!IsCurrentLifetime(generation, sequence))
return;
// A1 review fix (2026-08-05, retail/architecture review): the
// sequencer's Tunnel -> TunnelContinue transition
// (TeleportAnimSequencer.cs:134-141) is unconditional and
// irreversible the instant it observes `worldReady` true; by the
// time a failed placement is discovered the stream has already left
// Tunnel with no path back, and TeleportAnimSequencer itself is
// untouched (stop condition 2 forbids sequencer timing changes). So
// the boolean fed into the sequencer must never mean "the data is
// ready" alone - it must mean "the canonical Runtime commit has
// ALREADY happened", checked/attempted fresh every tick via
// TryAdvancePortalCommit. This makes D-T5 row 2's "the NEXT Tick
// re-attempts the Place edge" real: the sequencer simply never
// leaves Tunnel while the commit keeps refusing, and by the time it
// finally does leave Tunnel and fire Place, TryAdvancePortalCommit
// has already made the Runtime side succeed - the Place-event
// handler below only ever runs the presentation suffix.
bool placementReady = dataReady && TryAdvancePortalCommit(sequence);
if (!IsCurrentLifetime(generation, sequence))
return;
if (haveDestination && !placementReady)
_holdSeconds += deltaSeconds;
_presentation.SetWaitCue(
haveDestination
&& !placementReady
&& _worldReveal.ObserveWait(
TimeSpan.FromSeconds(_holdSeconds)));
var (_, events) = _presentation.Tick(deltaSeconds, placementReady);
if (!IsCurrentLifetime(generation, sequence))
return;
foreach (TeleportAnimEvent teleportEvent in events)
{
switch (teleportEvent)
{
case TeleportAnimEvent.Place:
// TryAdvancePortalCommit above is the only path that
// makes `placementReady` (and, with the REAL sequencer,
// this event) true, so the canonical Runtime commit has
// ALREADY succeeded by construction on that path - this
// only runs the presentation suffix (D-T4). The
// _placementCommitted re-check stays defensive: it is
// the exact same shape of guard IsCurrentLifetime below
// already applies to every other step of this case, for
// a transit that goes stale between the gate above and
// this line being reached.
if (!_placementCommitted)
return;
// B7 review fix (2026-08-05): re-derived, not assumed -
// if the reveal was cancelled/superseded in the window
// between the commit above and this event being
// processed, ObserveMaterialized below would refuse but
// Place/the presentation suffix would already have run
// against a reveal that is no longer current. Same
// check TryExecuteCanonicalPortalPlacementCore itself
// gates on; idempotent to repeat here.
if (!_worldReveal.CanPlacePortalDestination(
_pendingRevealGeneration, sequence, _pendingCell))
{
return;
}
_placement.Place(_pendingRotation);
if (!IsCurrentLifetime(generation, sequence))
return;
_worldReveal.ObserveMaterialized(
_pendingRevealGeneration,
sequence,
_pendingCell);
if (!IsCurrentLifetime(generation, sequence))
return;
break;
case TeleportAnimEvent.PlayEnterSound:
// Retail plays the enter cue as the animation BEGINS, before
// the tunnel is visible. The sequencer has always emitted
// this event; nothing consumed it until Campaign A.
_presentation.PlayEnterCue();
if (!IsCurrentLifetime(generation, sequence))
return;
break;
case TeleportAnimEvent.EnterTunnel:
_presentation.EnterTunnel();
if (!IsCurrentLifetime(generation, sequence))
return;
break;
case TeleportAnimEvent.PlayExitSound:
// gmSmartBoxUI::UseTime @ 0x004D6E30 releases destination
// cell blocking at the exact portal/world viewport swap.
// LoginComplete remains one WorldFadeIn second later.
_worldReveal.RevealWorldViewport();
if (!IsCurrentLifetime(generation, sequence))
return;
_presentation.PlayExitCue();
if (!IsCurrentLifetime(generation, sequence))
return;
_presentation.ExitTunnel();
if (!IsCurrentLifetime(generation, sequence))
return;
break;
case TeleportAnimEvent.FireLoginComplete:
_mode.EnterWorld();
if (!IsCurrentLifetime(generation, sequence))
return;
_session.SendLoginComplete();
if (!IsCurrentLifetime(generation, sequence))
return;
_worldReveal.Complete();
if (!IsCurrentLifetime(generation, sequence))
return;
ResetTransit(clearSession: false);
return;
default:
// ClientUISystem sound-table enum playback remains outside
// this already-accepted presentation extraction.
break;
}
}
_presentation.TickTunnel(deltaSeconds);
}
///
/// A1 review fix (2026-08-05): the one gate that decides whether the
/// anim sequencer is allowed to see worldReady=true. Returns
/// ONLY once the canonical Runtime commit for
/// THIS teleport lifetime has actually happened — never speculatively,
/// never optimistically. Three states:
///
///
/// - Already committed
/// () — returns
/// immediately, every subsequent Tick.
/// - A DeferredCell park is outstanding
/// () — polls
///
/// only to decide whether to re-attempt the Runtime call: Runtime's own
/// Begin would just refuse a second overlapping attempt with
/// Contention while a park is outstanding (the drive tracks at
/// most one pending operation for the local player), so retrying blind
/// would only add noise. B1 review fix (2026-08-05): once
/// PendingCount returns to 0 the park is DONE, but "done" is not
/// "committed" — the drive's own doc names a merge-time Forget
/// (an ordinary ACE broadcast arriving mid-park) as the EXPECTED way a
/// park resolves without committing, and A2's own abandon branches are
/// a second way. The OLD code inferred commit from the empty slot
/// alone; this now asks
/// ,
/// which the drive latches ONLY inside a REAL
/// ReconcileAndAcknowledgePortal call, keyed to this exact
/// reveal generation/sequence. A "no" here is NOT a failure — it just
/// means the park ended without placing, so
/// clears and the method falls through to a fresh attempt below,
/// safely (nothing is pending anymore).
/// - Neither — attempt the canonical placement fresh
/// this tick. Committed latches
/// ; DeferredCell latches
/// ; every other status (Contention,
/// Rejected, NotApplicable, or the transit no longer owning this
/// reveal) is the D-T5 refusal shape — nothing mutates, and the SAME
/// predicate retries automatically on the NEXT Tick, which is what
/// makes D-T5 row 2's "the next Tick re-attempts the Place edge"
/// mechanism real without ever touching
/// .
///
///
private bool TryAdvancePortalCommit(ushort sequence)
{
if (_placementCommitted)
return true;
if (_awaitingDeferredWake)
{
if (_acceptedPositionDrive.PendingCount != 0)
return false;
_awaitingDeferredWake = false;
if (_acceptedPositionDrive.TryConsumePortalCommit(
_pendingRevealGeneration, sequence))
{
_placementCommitted = true;
return true;
}
// The park ended without placing (Forgotten, or abandoned by
// A2's re-validation). Fall through to the fresh-attempt path
// below in this SAME call — nothing is pending, so it is safe.
}
if (!_worldReveal.CanPlacePortalDestination(
_pendingRevealGeneration,
sequence,
_pendingCell))
{
PhysicsDiagnostics.LogTeleport(
"REFUSED", _pendingCell, "cause=stale-reveal");
// R8 residual fix (2026-08-05): this refusal previously only
// logged through PhysicsDiagnostics.LogTeleport, gated by the
// DIFFERENT ACDREAM_PROBE_TELEPORT flag — invisible under
// ACDREAM_PROBE_LOCAL_TELEPORT, the gate the rest of this
// route's arrival/commit lines use. No placement was attempted,
// so there is no resolved cell/leash/autorun fact to report.
PhysicsDiagnostics.LogLocalTeleportArrival(
cause: "stale-reveal",
placementStatus: "Refused",
portalGeneration: _pendingRevealGeneration,
teleportSequence: sequence,
destinationCell: _pendingCell,
resolvedCell: 0u,
hookTailRan: false,
leashArmed: false,
autorunCancelled: false);
return false;
}
RuntimeAcceptedPositionExecutionStatus status =
TryExecuteCanonicalPortalPlacementCore(sequence);
switch (status)
{
case RuntimeAcceptedPositionExecutionStatus.Committed:
_placementCommitted = true;
return true;
case RuntimeAcceptedPositionExecutionStatus.DeferredCell:
_awaitingDeferredWake = true;
return false;
default:
return false;
}
}
///
/// C4 route 3 (D-T1/D-T2): builds the producer's
/// from live transit facts
/// and drives the canonical Runtime portal arm. No new
/// exposure is needed — the host
/// token is RE-DERIVED through the transit owner's idempotent
/// TryRegisterHostProjection (the same generation+cell returns
/// the token already
/// registered at Aim time; a stale generation, wrong cell, cancelled, or
/// completed reveal refuses), which makes a superseded token unobtainable
/// by construction.
///
///
/// The destination itself is — the
/// value captured at Aim time — and NOT a
/// fresh _transit.TryGetAcceptedTeleportDestination read.
/// atomically
/// CONSUMES the transit's one accepted-destination slot the instant Aim
/// claims the reveal generation (it clears
/// _hasAcceptedDestination so a stale destination can never be
/// re-claimed by a later portal) — by Place time that slot is already
/// empty, so re-querying it here always fails. This mirrors why
/// //
/// are themselves Aim-time
/// snapshots rather than live transit reads.
///
///
///
/// A9 review fix (2026-08-05): is
/// 's OWN
/// , not the
/// transit's separately-tracked ActiveTeleportSequence the caller
/// otherwise threads through — one source for the fact this method's
/// authority carries, asserted equal to the caller's copy so the two
/// can never silently diverge.
///
///
private RuntimeAcceptedPositionExecutionStatus
TryExecuteCanonicalPortalPlacementCore(ushort sequence)
{
System.Diagnostics.Debug.Assert(
!_hasPendingDestination
|| _pendingDestination.TeleportSequence == sequence,
"The transit's active sequence and the Aim-time destination's "
+ "own sequence must never diverge (A9).");
if (!_hasPendingDestination
|| !_transit.TryRegisterHostProjection(
_pendingRevealGeneration,
_pendingCell,
out RuntimeWorldHostProjectionToken hostToken))
{
PhysicsDiagnostics.LogTeleport(
"REFUSED", _pendingCell, "cause=host-token-unavailable");
// R8 residual fix (2026-08-05): same rationale as the
// stale-reveal refusal above — route through
// LogLocalTeleportArrival too, so ACDREAM_PROBE_LOCAL_TELEPORT
// alone is enough to see every App-side refusal cause.
PhysicsDiagnostics.LogLocalTeleportArrival(
cause: "host-token-unavailable",
placementStatus: "Refused",
portalGeneration: _pendingRevealGeneration,
teleportSequence: sequence,
destinationCell: _pendingCell,
resolvedCell: 0u,
hookTailRan: false,
leashArmed: false,
autorunCancelled: false);
return RuntimeAcceptedPositionExecutionStatus.Rejected;
}
RuntimeTeleportDestination destination = _pendingDestination;
var portal = new RuntimePortalPlacementAuthority(
Present: true,
RevealGeneration: _pendingRevealGeneration,
TeleportSequence: destination.TeleportSequence,
Projection: hostToken);
return _acceptedPositionDrive.TryExecuteAcceptedPortalArrival(
destination,
portal);
}
public void ResetSession()
{
ThrowIfDisposed();
ResetTransit(
clearSession: true,
resetCanonicalTransit: true);
}
public void ResetGenerationPresentation()
{
ThrowIfDisposed();
ResetTransit(
clearSession: true,
resetCanonicalTransit: false);
}
public Matrix4x4 ApplyViewPlane(Matrix4x4 projection) =>
_presentation.ApplyViewPlane(projection);
public ICamera ApplyViewPlane(ICamera camera) =>
_presentation.ApplyViewPlane(camera);
public void DrawPortalViewport(int width, int height, Matrix4x4 projection) =>
_presentation.DrawPortalViewport(width, height, projection);
private void TryActivatePendingPresentation()
{
if (!_transit.HasPendingTeleportStart)
return;
long generation = _lifetimeGeneration;
if (!_mode.TryEnterPortalSpace()
|| _lifetimeGeneration != generation
|| !_transit.HasPendingTeleportStart)
{
return;
}
_holdSeconds = 0f;
_presentation.Begin(_mode.Projection);
if (_lifetimeGeneration != generation
|| !_transit.HasPendingTeleportStart)
{
return;
}
if (!_transit.ActivateQueuedTeleport())
return;
TryAimAcceptedDestination();
Console.WriteLine(
$"live: teleport presentation started "
+ $"(seq={_transit.ActiveTeleportSequence})");
}
///
/// Enter-world round (2026-08-17): the login half of retail's ONE
/// wormhole machine. Retail begins the identical TAS_TUNNEL animation for
/// initial login and for F751 teleports from the same
/// gmSmartBoxUI::UseTime @ 0x004D6EAB flag edge —
/// SmartBox::teleport_in_progress @ 0x00451C20 goes high the
/// moment the login player object exists with
/// position_update_complete == 0, no F751 required. acdream's
/// canonical login edge is Runtime's login reveal generation
/// (, begun on the
/// first accepted local-player position on every entry route: direct
/// auto-select, character-select Enter, and enter-after-create).
///
///
/// Activation retries every Tick until
///
/// succeeds — the same retry shape the F751 arm uses — which requires the
/// Runtime first-entry conductor's published movement controller.
/// Entering portal space cancels the player-mode auto-entry (its first
/// statement), so this arm owns the reveal's
/// EnterWorld/LoginComplete/Complete suffix exactly like the teleport
/// pump owns its own; the update-frame order (teleport phase before
/// auto-entry) guarantees this claim happens before auto-entry could
/// fire.
///
///
private void TryActivateLoginPresentation()
{
if (_transit.HasPendingTeleportStart || _transit.IsTeleportActive)
return;
RuntimePortalSnapshot snapshot = _transit.Snapshot;
if (snapshot.Kind != RuntimePortalKind.Login
|| snapshot.Generation == 0
|| snapshot.Completed
|| snapshot.Cancelled
|| _loginRevealGeneration == snapshot.Generation)
{
return;
}
// Quiet pre-gate: until the first-entry conductor PUBLISHES the
// movement controller, portal-space entry cannot succeed (and the
// full entry path would log a refusal every tick). Retry silently.
if (_mode.Controller is not { CanExecuteLiveMovement: true })
return;
long generation = _lifetimeGeneration;
if (!_mode.TryEnterPortalSpaceForLogin()
|| _lifetimeGeneration != generation
|| _mode.Controller is null)
{
return;
}
// Re-read after the mode entry: TryEnterPortalSpaceForLogin can run
// arbitrary presentation attach work.
snapshot = _transit.Snapshot;
if (snapshot.Kind != RuntimePortalKind.Login
|| snapshot.Generation == 0
|| snapshot.Completed
|| snapshot.Cancelled)
{
return;
}
// Enter-click round (2026-08-17): a click-armed tunnel is ADOPTED,
// not restarted — the presentation is already running (sequencer in
// its Tunnel hold, tunnel scene visible, enter cue already played at
// the click), so re-Begin here would restart the sequencer and
// double-fire the begin-edge events. The hold clock also carries
// over: the user's wait began at the click.
bool adoptedArmedTunnel = _loginTunnelArmed;
_loginTunnelArmed = false;
_loginRevealGeneration = snapshot.Generation;
_loginPresentationActive = true;
if (!adoptedArmedTunnel)
{
_loginHoldSeconds = 0f;
_presentation.Begin(_mode.Projection);
}
Console.WriteLine(
$"live: login portal-space presentation started "
+ $"(gen={snapshot.Generation} "
+ $"cell=0x{snapshot.Readiness.DestinationCell:X8} "
+ $"adoptedArmedTunnel={(adoptedArmedTunnel ? 1 : 0)})");
}
///
/// Per-frame pump for the login presentation — the login mirror of the
/// teleport pump in . Differences, each anchored in
/// retail: there is no Place edge to drive (the first-entry conductor
/// committed the canonical placement before this presentation could
/// begin — retail's SmartBox::UseTime @ 0x00455483 likewise only
/// flips position_update_complete, it does not place), and
/// LoginComplete rides
/// at the WorldFadeIn end (gmSmartBoxUI::UseTime @ 0x004D745D →
/// CPlayerSystem::SendLoginCompleteNotification @ 0x00562E90)
/// instead of at raw first placement.
///
private void TickLoginPresentation(float deltaSeconds)
{
TryActivateLoginPresentation();
RuntimePortalSnapshot snapshot = _transit.Snapshot;
bool revealActive = snapshot.Kind == RuntimePortalKind.Login
&& snapshot.Generation != 0
&& !snapshot.Completed
&& !snapshot.Cancelled;
if (!revealActive || _loginRevealGeneration != snapshot.Generation)
{
if (_loginPresentationActive)
{
// The reveal this presentation was serving ended underneath
// it (cancel, supersession, or session reset that did not
// route through this controller's own reset). Drop the claim
// and retire the visuals; a successor reveal re-activates
// through TryActivateLoginPresentation above.
_loginRevealGeneration = 0;
_loginPresentationActive = false;
_loginTunnelArmed = false;
_loginHoldSeconds = 0f;
_presentation.Reset();
}
else if (_loginTunnelArmed)
{
// Enter-click round (2026-08-17): the pre-reveal armed
// window — from the Enter click until the Runtime login
// reveal begins (CreatePlayer + first accepted position) and
// the activation above claims it. Keeps the tunnel animating
// across the server round-trip; disarms if the enter
// transaction fell back to character select.
TickArmedLoginTunnel(deltaSeconds);
}
return;
}
if (!_loginPresentationActive)
return;
long generation = _lifetimeGeneration;
long revealGeneration = snapshot.Generation;
uint destinationCell = snapshot.Readiness.DestinationCell;
bool originReady = !_streaming.IsRecenterPending;
bool worldReady = _loginPlacementCompleted
&& originReady
&& _worldReveal.Evaluate(destinationCell).IsReady;
if (!IsCurrentLoginLifetime(generation, revealGeneration))
return;
if (!worldReady)
_loginHoldSeconds += deltaSeconds;
_presentation.SetWaitCue(
!worldReady
&& _worldReveal.ObserveWait(
TimeSpan.FromSeconds(_loginHoldSeconds)));
var (_, events) = _presentation.Tick(deltaSeconds, worldReady);
if (!IsCurrentLoginLifetime(generation, revealGeneration))
return;
foreach (TeleportAnimEvent teleportEvent in events)
{
switch (teleportEvent)
{
case TeleportAnimEvent.PlayEnterSound:
// Sound_UI_EnterPortal as the animation begins —
// BeginTeleportAnimation @ 0x004D638E, identical for the
// login entry. Logged (once per login) as the audio-start
// evidence line for connected gates.
Console.WriteLine(
"live: login portal-space enter cue "
+ "(Sound_UI_EnterPortal)");
_presentation.PlayEnterCue();
if (!IsCurrentLoginLifetime(generation, revealGeneration))
return;
break;
case TeleportAnimEvent.EnterTunnel:
_presentation.EnterTunnel();
if (!IsCurrentLoginLifetime(generation, revealGeneration))
return;
break;
case TeleportAnimEvent.Place:
// No login PLACEMENT: the canonical initial placement is
// the first-entry conductor's, already committed (the
// worldReady latch above requires it). Retail's login
// analogue only flips position_update_complete
// (SmartBox::UseTime @ 0x00455483) — but that same
// blocking-ends edge is where retail RESUMES
// CObjectMaint/CPhysics (@ 0x00455410), while the tunnel
// is still in front. Acknowledge the login
// materialization/simulation release here, the exact
// login mirror of the teleport pump's
// ObserveMaterialized — without it the world generation
// stayed unavailable until Complete, so the whole
// WorldFadeIn second presented an EMPTY world frame (the
// 2026-08-17 gate's exit-edge void).
_worldReveal.ObserveLoginMaterialized(revealGeneration);
if (!IsCurrentLoginLifetime(generation, revealGeneration))
return;
break;
case TeleportAnimEvent.PlayExitSound:
// Release destination cell blocking at the exact
// portal/world viewport swap — same edge as the teleport
// pump (gmSmartBoxUI::UseTime @ 0x004D6E30), with
// Sound_UI_ExitPortal @ 0x004D7405.
_worldReveal.RevealWorldViewport();
if (!IsCurrentLoginLifetime(generation, revealGeneration))
return;
_presentation.PlayExitCue();
if (!IsCurrentLoginLifetime(generation, revealGeneration))
return;
_presentation.ExitTunnel();
if (!IsCurrentLoginLifetime(generation, revealGeneration))
return;
break;
case TeleportAnimEvent.FireLoginComplete:
_mode.EnterWorld();
if (!IsCurrentLoginLifetime(generation, revealGeneration))
return;
_session.SendLoginComplete();
if (!IsCurrentLoginLifetime(generation, revealGeneration))
return;
_worldReveal.Complete();
_loginRevealGeneration = 0;
_loginPresentationActive = false;
_loginHoldSeconds = 0f;
Console.WriteLine(
"live: login portal-space presentation complete");
return;
default:
break;
}
}
_presentation.TickTunnel(deltaSeconds);
}
///
/// Enter-click round (2026-08-17): the armed pre-reveal pump. The
/// sequencer holds in its Tunnel state (worldReady pinned false — no
/// reveal exists to be ready), the tunnel scene animates, and the hold
/// clock accumulates from the click. Disarms when the Runtime
/// character-selection lifecycle regresses out of the enter transaction
/// (rejected EnterWorld → AwaitingSelection, or a session teardown →
/// Inactive/Connecting): the character-select screen is in front again
/// and retail shows no tunnel there.
///
private void TickArmedLoginTunnel(float deltaSeconds)
{
RuntimeCharacterSelectionLifecycle lifecycle =
_loginLifecycle.SelectionLifecycle;
if (lifecycle is not (
RuntimeCharacterSelectionLifecycle.EnteringWorld
or RuntimeCharacterSelectionLifecycle.InWorld))
{
_loginTunnelArmed = false;
_loginHoldSeconds = 0f;
_presentation.Reset();
Console.WriteLine(
$"live: login tunnel disarmed (lifecycle={lifecycle})");
return;
}
long generation = _lifetimeGeneration;
_loginHoldSeconds += deltaSeconds;
var (_, events) = _presentation.Tick(deltaSeconds, worldReady: false);
if (_lifetimeGeneration != generation || !_loginTunnelArmed)
return;
if (!ProcessArmedLoginTunnelEvents(events, generation))
return;
_presentation.TickTunnel(deltaSeconds);
}
///
/// The login pump's currency check — the login mirror of
/// : same controller
/// lifetime, still no active teleport (an F751 supersedes the login
/// presentation), and the transit snapshot still carries the exact
/// claimed login reveal generation.
///
private bool IsCurrentLoginLifetime(
long lifetimeGeneration,
long revealGeneration) =>
_lifetimeGeneration == lifetimeGeneration
&& !_transit.IsTeleportActive
&& _loginRevealGeneration == revealGeneration
&& _transit.Snapshot.Generation == revealGeneration;
private void TryAimAcceptedDestination()
{
if (!_transit.TryGetAcceptedTeleportDestination(
out RuntimeTeleportDestination destination))
{
return;
}
long generation = _lifetimeGeneration;
ushort sequence = _transit.ActiveTeleportSequence;
PlayerMovementController? controller = _mode.Controller;
if (controller is null)
{
if (!_mode.TryEnterPortalSpace()
|| !IsCurrentLifetime(generation, sequence))
{
return;
}
controller = _mode.Controller;
if (controller is null)
return;
}
if (!AimDestination(destination, controller, generation, sequence))
return;
}
private bool AimDestination(
RuntimeTeleportDestination destination,
PlayerMovementController controller,
long generation,
ushort sequence)
{
Position position = destination.Position;
int landblockX = (int)((position.ObjCellId >> 24) & 0xFFu);
int landblockY = (int)((position.ObjCellId >> 16) & 0xFFu);
uint streamingOriginLandblockId = StreamingRegion.EncodeLandblockId(
_streaming.CenterX,
_streaming.CenterY);
var origin = new Vector3(
(landblockX - _streaming.CenterX) * 192f,
(landblockY - _streaming.CenterY) * 192f,
0f);
Vector3 translated = position.Frame.Origin + origin;
TeleportLandblockTransition transition = TeleportLandblockTransition.Classify(
controller.CellId,
position.ObjCellId,
streamingOriginLandblockId);
int oldX = (int)((transition.SourceLandblockId >> 24) & 0xFFu);
int oldY = (int)((transition.SourceLandblockId >> 16) & 0xFFu);
Console.WriteLine(
$"live: teleport arrival - old lb=({oldX},{oldY}) "
+ $"new lb=({landblockX},{landblockY}) "
+ $"dist={Vector3.Distance(translated, controller.Position):F1}");
// Retail SmartBox enters blocking_for_cells before old-world object,
// physics, landscape, and ambient owners may advance again. Begin the
// reveal generation before a recenter can start detaching that world.
if (!_worldReveal.TryBeginPortal(
sequence,
position.ObjCellId,
out long revealGeneration))
{
return false;
}
_pendingRevealGeneration = revealGeneration;
if (!IsCurrentLifetime(generation, sequence))
return false;
if (transition.ChangesStreamingCenter)
{
bool isSealedDungeon = _streaming.IsSealedDungeon(
position.ObjCellId);
if (!IsCurrentLifetime(generation, sequence))
return false;
_streaming.BeginRecenter(
landblockX,
landblockY,
isSealedDungeon);
if (!IsCurrentLifetime(generation, sequence))
return false;
}
// C4 route 3: the App-frame-translated `translated`/`worldPosition`
// vector is no longer carried past this point — the canonical
// portal arm resolves the placement through Runtime's OWN world
// frame (resolveWorldOffsetFromRuntimeFrame: true), using the
// cell-local `destination` Position captured HERE rather than an
// App-translated snapshot (trap T4). It must be captured here and
// NOT re-read from the transit at the Place edge:
// _worldReveal.TryBeginPortal (above) drives
// RuntimeWorldTransitState.TryBeginPortalReveal, which atomically
// CONSUMES the transit's one accepted-destination slot the instant
// it claims this reveal generation — a later
// TryGetAcceptedTeleportDestination call always finds it empty.
_pendingRotation = position.Frame.Orientation;
_pendingCell = position.ObjCellId;
_pendingDestination = destination;
_hasPendingDestination = true;
_holdSeconds = 0f;
PhysicsDiagnostics.LogTeleport(
"AIM",
position.ObjCellId,
$"seq={destination.TeleportSequence} lb={landblockX},{landblockY} "
+ $"indoor={((position.ObjCellId & 0xFFFFu) >= 0x0100u)} "
+ $"playerCross={transition.CrossesLandblock} "
+ $"centerChange={transition.ChangesStreamingCenter}");
return true;
}
private long ResetTransit(
bool clearSession,
bool resetCanonicalTransit = false)
{
long generation = checked(++_lifetimeGeneration);
_pendingCell = 0u;
_pendingRotation = Quaternion.Identity;
_pendingRevealGeneration = 0;
_pendingDestination = default;
_hasPendingDestination = false;
_placementCommitted = false;
_awaitingDeferredWake = false;
_holdSeconds = 0f;
// Enter-world round (2026-08-17): an F751 arriving mid-login-tunnel
// (clearSession: false) withdraws the login presentation's claim —
// the portal pump supersedes it and owns the single LoginComplete,
// exactly as retail's one teleportInProgress flag stays high across
// both and sends once at the eventual WorldFadeIn end. The
// placement-completed latch is a session fact: it survives the
// teleport-scoped reset and clears only with the session.
_loginRevealGeneration = 0;
_loginPresentationActive = false;
_loginTunnelArmed = false;
_loginHoldSeconds = 0f;
if (clearSession)
_loginPlacementCompleted = false;
_streaming.ResetRecenter(clearSession);
if (_lifetimeGeneration != generation)
return generation;
if (clearSession)
{
if (resetCanonicalTransit)
_worldReveal.ResetSession();
else
_worldReveal.ResetHostSession();
}
else
{
_transit.EndTeleport();
_worldReveal.Cancel();
}
if (_lifetimeGeneration != generation)
return generation;
_presentation.Reset();
if (_lifetimeGeneration != generation)
return generation;
return generation;
}
private bool IsCurrentLifetime(long generation, ushort sequence) =>
_lifetimeGeneration == generation
&& _transit.IsTeleportActive
&& _transit.ActiveTeleportSequence == sequence;
private void ThrowIfDisposed() =>
ObjectDisposedException.ThrowIf(_disposed, this);
public void Dispose()
{
if (_disposed)
return;
_presentation.Dispose();
_disposed = true;
}
}