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; } }