refactor(update): cut over the frame orchestrator
Reduce GameWindow.OnUpdate to one typed orchestration call and remove transitive window callbacks from teardown, liveness, teleport placement, live presentation, auto-entry, and entity packet routing. Preserve the frozen retail object/network order while making the production owner graph explicit and testable.
This commit is contained in:
parent
947c61d2d7
commit
e91f310279
18 changed files with 864 additions and 348 deletions
|
|
@ -134,7 +134,6 @@ public sealed class GameWindow : IDisposable
|
|||
private AcDream.App.Streaming.StreamingController? _streamingController;
|
||||
private AcDream.App.Streaming.StreamingOriginRecenterCoordinator?
|
||||
_streamingOriginRecenter;
|
||||
private AcDream.App.Update.IStreamingFramePhase _streamingFrame = null!;
|
||||
private AcDream.App.Streaming.WorldRevealCoordinator? _worldReveal;
|
||||
private readonly AcDream.App.Streaming.DeferredLocalPlayerTeleportNetworkSink
|
||||
_localPlayerTeleportSink = new();
|
||||
|
|
@ -173,10 +172,7 @@ public sealed class GameWindow : IDisposable
|
|||
private readonly AcDream.App.Input.MovementTruthDiagnosticController
|
||||
_movementTruthDiagnostics;
|
||||
private readonly AcDream.App.Input.LocalPlayerOutboundController _localPlayerOutbound;
|
||||
private AcDream.App.Update.ICameraFramePhase _cameraFrame = null!;
|
||||
private AcDream.App.World.RetailLiveFrameCoordinator _liveFrameCoordinator = null!;
|
||||
private AcDream.App.Update.LiveSpatialPresentationReconciler
|
||||
_liveSpatialReconciler = null!;
|
||||
private AcDream.App.Update.UpdateFrameOrchestrator _updateFrameOrchestrator = null!;
|
||||
private readonly AcDream.App.Update.UpdateFrameClock _updateFrameClock = new();
|
||||
private AcDream.App.Physics.RemoteTeleportController? _remoteTeleportController;
|
||||
// Step 7 projectile presentation. The controller owns no identity map;
|
||||
|
|
@ -186,6 +182,7 @@ public sealed class GameWindow : IDisposable
|
|||
_liveEntityProjectionWithdrawal;
|
||||
private AcDream.App.World.LiveEntityHydrationController? _liveEntityHydration;
|
||||
private AcDream.App.Physics.LiveEntityNetworkUpdateController? _liveEntityNetworkUpdates;
|
||||
private AcDream.App.Net.LiveEntitySessionController _liveEntitySessionEvents = null!;
|
||||
private readonly AcDream.App.Physics.DeferredLiveEntityMotionRuntimeBindings
|
||||
_liveEntityMotionBindings = new();
|
||||
|
||||
|
|
@ -2170,36 +2167,32 @@ public sealed class GameWindow : IDisposable
|
|||
_entityEffects = entityEffects;
|
||||
entityEffects.DiagnosticSink = message =>
|
||||
Console.Error.WriteLine($"vfx: {message}");
|
||||
var partArrayLifecycle =
|
||||
new AcDream.App.Rendering.LiveEntityPartArrayLifecycle(
|
||||
_animatedEntities);
|
||||
_liveEntityPresentation = new AcDream.App.World.LiveEntityPresentationController(
|
||||
_liveEntities,
|
||||
_physicsEngine.ShadowObjects,
|
||||
entityEffects.PlayTypedFromHiddenTransition,
|
||||
_equippedChildRenderer.SetDirectChildrenNoDraw,
|
||||
_liveEntityMotionBindings.ClearTargetForHiddenEntity,
|
||||
() => (_liveCenterX, _liveCenterY),
|
||||
handlePartArrayEnterWorld: localEntityId =>
|
||||
{
|
||||
if (_animatedEntities.TryGetValue(localEntityId, out var animated))
|
||||
animated.Sequencer?.Manager.HandleEnterWorld();
|
||||
});
|
||||
_liveWorldOrigin.GetCenter,
|
||||
handlePartArrayEnterWorld: partArrayLifecycle.HandleEnterWorld);
|
||||
var remoteShadowPlacement =
|
||||
new AcDream.App.Physics.RemoteShadowPlacementSynchronizer(
|
||||
_remotePhysicsUpdater,
|
||||
_liveWorldOrigin);
|
||||
var remoteTeleportPresentation =
|
||||
new AcDream.App.Physics.RemoteTeleportPlacementPresentation(
|
||||
_liveEntityPresentation);
|
||||
_remoteTeleportController = new AcDream.App.Physics.RemoteTeleportController(
|
||||
_physicsEngine,
|
||||
_liveEntities,
|
||||
_liveEntityMotionBindings.GetSetupCylinder,
|
||||
CellLocalForSeed,
|
||||
(entity, remote, cellId) => _remotePhysicsUpdater.SyncRemoteShadowToBody(
|
||||
entity.Id,
|
||||
remote,
|
||||
_liveCenterX,
|
||||
_liveCenterY,
|
||||
cellId),
|
||||
(guid, generation, deferShadowRestore) =>
|
||||
_liveEntityPresentation.CompleteAuthoritativePlacement(
|
||||
guid,
|
||||
generation,
|
||||
deferShadowRestore),
|
||||
(guid, generation) =>
|
||||
_liveEntityPresentation.BeginAuthoritativePlacement(guid, generation));
|
||||
_liveWorldOrigin.CellLocalForSeed,
|
||||
remoteShadowPlacement.Sync,
|
||||
remoteTeleportPresentation.Complete,
|
||||
remoteTeleportPresentation.Begin);
|
||||
_equippedChildRenderer.ProjectionPoseReady += guid =>
|
||||
_liveEntityLights.OnAttachedPoseReady(guid);
|
||||
_hookRouter.Register(entityEffects);
|
||||
|
|
@ -2484,9 +2477,14 @@ public sealed class GameWindow : IDisposable
|
|||
_streamingController,
|
||||
_worldState,
|
||||
_worldReveal,
|
||||
() => _playerServerGuid,
|
||||
_localPlayerIdentity,
|
||||
sealedDungeonCells,
|
||||
Console.WriteLine);
|
||||
_liveSessionController = new AcDream.App.Net.LiveSessionController();
|
||||
var localPhysicsTimestamps =
|
||||
new AcDream.App.World.LiveSessionLocalPhysicsTimestampPublisher(
|
||||
_localPlayerIdentity,
|
||||
_liveSessionController);
|
||||
var liveEntityTeardown =
|
||||
new AcDream.App.World.LiveEntityRuntimeTeardownController(
|
||||
_liveEntities,
|
||||
|
|
@ -2503,8 +2501,15 @@ public sealed class GameWindow : IDisposable
|
|||
_physicsEngine.ShadowObjects,
|
||||
_liveEntityLights!,
|
||||
_classificationCache,
|
||||
() => _playerServerGuid);
|
||||
_localPlayerIdentity);
|
||||
liveEntityComponentLifecycle.Bind(liveEntityTeardown);
|
||||
var liveEntityDeletion =
|
||||
new AcDream.App.World.LiveEntityDeletionController(
|
||||
_liveEntities,
|
||||
Objects,
|
||||
liveEntityTeardown,
|
||||
_localPlayerIdentity,
|
||||
_options.DumpLiveSpawns ? Console.WriteLine : null);
|
||||
_liveEntityHydration = new AcDream.App.World.LiveEntityHydrationController(
|
||||
_liveEntities,
|
||||
Objects,
|
||||
|
|
@ -2518,9 +2523,9 @@ public sealed class GameWindow : IDisposable
|
|||
_liveEntityPresentation!),
|
||||
originCoordinator,
|
||||
networkUpdateBridge,
|
||||
liveEntityTeardown,
|
||||
PublishLocalPhysicsTimestamps,
|
||||
() => _playerServerGuid,
|
||||
localPhysicsTimestamps,
|
||||
_localPlayerIdentity,
|
||||
liveEntityDeletion,
|
||||
_options.DumpLiveSpawns ? Console.WriteLine : null);
|
||||
_liveEntityNetworkUpdates =
|
||||
new AcDream.App.Physics.LiveEntityNetworkUpdateController(
|
||||
|
|
@ -2546,18 +2551,24 @@ public sealed class GameWindow : IDisposable
|
|||
_combatTargetController,
|
||||
_liveWorldOrigin,
|
||||
_localPlayerTeleportSink,
|
||||
() => _playerController,
|
||||
_playerControllerSlot,
|
||||
_localPlayerOutbound,
|
||||
() => _playerHost,
|
||||
() => _playerServerGuid,
|
||||
_playerHostSlot,
|
||||
_localPlayerIdentity,
|
||||
_updateFrameClock,
|
||||
() => LiveSession,
|
||||
PublishLocalPhysicsTimestamps,
|
||||
_liveSessionController,
|
||||
localPhysicsTimestamps.Publish,
|
||||
_movementTruthDiagnostics);
|
||||
_liveEntityLiveness = new AcDream.App.World.LiveEntityLivenessController(
|
||||
_liveEntities,
|
||||
() => _playerServerGuid,
|
||||
candidate => _liveEntityHydration.OnPrune(candidate));
|
||||
_localPlayerIdentity,
|
||||
liveEntityDeletion);
|
||||
_liveEntitySessionEvents = new AcDream.App.Net.LiveEntitySessionController(
|
||||
_inboundEntityEvents,
|
||||
_liveEntityHydration,
|
||||
_liveEntityNetworkUpdates,
|
||||
_localPlayerTeleportSink,
|
||||
_entityEffects!);
|
||||
parentAcceptance!.Bind(_liveEntityHydration.TryAcceptParentForProjection);
|
||||
networkUpdateBridge.Bind(_liveEntityNetworkUpdates);
|
||||
_equippedChildRenderer.EntityReady += candidate =>
|
||||
|
|
@ -2573,7 +2584,6 @@ public sealed class GameWindow : IDisposable
|
|||
// CreateObject messages into _worldGameState as they arrive. Entirely
|
||||
// gated behind ACDREAM_LIVE=1 so the default run path is unchanged.
|
||||
_liveWorldOrigin.SetPlaceholder(centerX, centerY);
|
||||
_liveSessionController = new AcDream.App.Net.LiveSessionController();
|
||||
_combatAttackOperations.Bind(
|
||||
new AcDream.App.Combat.LiveCombatAttackOperations(
|
||||
Combat,
|
||||
|
|
@ -2609,7 +2619,7 @@ public sealed class GameWindow : IDisposable
|
|||
mouseLookController,
|
||||
new AcDream.App.Input.CombatAttackInputFrameAdapter(
|
||||
_combatAttackController!));
|
||||
_streamingFrame = new AcDream.App.Streaming.StreamingFrameController(
|
||||
var streamingFrame = new AcDream.App.Streaming.StreamingFrameController(
|
||||
_options.LiveMode,
|
||||
_localPlayerMode,
|
||||
_playerControllerSlot,
|
||||
|
|
@ -2638,7 +2648,7 @@ public sealed class GameWindow : IDisposable
|
|||
new AcDream.App.Update.SettingsParticleRangeSource(
|
||||
_settingsVm,
|
||||
_persistedDisplay.ParticleRange));
|
||||
_liveSpatialReconciler =
|
||||
var liveSpatialReconciler =
|
||||
new AcDream.App.Update.LiveSpatialPresentationReconciler(
|
||||
_entityEffects!,
|
||||
_equippedChildRenderer!,
|
||||
|
|
@ -2721,16 +2731,15 @@ public sealed class GameWindow : IDisposable
|
|||
_localPlayerSkills,
|
||||
_viewportAspect);
|
||||
_playerModeAutoEntry = new AcDream.App.Input.PlayerModeAutoEntry(
|
||||
isLiveInWorld: () => _liveSessionController.IsInWorld,
|
||||
isPlayerEntityPresent: () =>
|
||||
_liveEntities.MaterializedWorldEntities.ContainsKey(
|
||||
_playerServerGuid),
|
||||
isPlayerControllerReady: () => true,
|
||||
// Retail SmartBox::UseTime @ 0x00455410 completes player
|
||||
// position only after the destination cell load clears.
|
||||
isWorldReady: LoginWorldReady,
|
||||
enterPlayerMode: _playerModeController.EnterFromAutoEntry,
|
||||
isPlayerModeActive: () => _localPlayerMode.IsPlayerMode);
|
||||
new AcDream.App.Input.LivePlayerModeAutoEntryContext(
|
||||
_liveSessionController,
|
||||
_liveEntities,
|
||||
_localPlayerIdentity,
|
||||
_worldReveal
|
||||
?? throw new InvalidOperationException(
|
||||
"World reveal was not composed before player auto-entry."),
|
||||
_localPlayerMode,
|
||||
_playerModeController));
|
||||
_playerModeController.BindAutoEntry(_playerModeAutoEntry);
|
||||
var localTeleportPresentation =
|
||||
new AcDream.App.Streaming.LocalPlayerTeleportPresentation(
|
||||
|
|
@ -2758,7 +2767,7 @@ public sealed class GameWindow : IDisposable
|
|||
_playerHostSlot,
|
||||
_chaseCameraInput,
|
||||
_liveWorldOrigin,
|
||||
_liveSpatialReconciler),
|
||||
liveSpatialReconciler),
|
||||
new AcDream.App.Streaming.LocalPlayerTeleportSession(
|
||||
_liveSessionController),
|
||||
localTeleportPresentation);
|
||||
|
|
@ -2767,25 +2776,41 @@ public sealed class GameWindow : IDisposable
|
|||
// remains only as the staged-startup fallback used by shutdown when
|
||||
// composition fails before the transfer.
|
||||
_portalTunnel = null;
|
||||
_liveFrameCoordinator = new AcDream.App.World.RetailLiveFrameCoordinator(
|
||||
var liveFrameCoordinator = new AcDream.App.World.RetailLiveFrameCoordinator(
|
||||
liveObjectFrame,
|
||||
_worldState,
|
||||
_liveSessionController,
|
||||
localPlayerFrame,
|
||||
_liveSpatialReconciler);
|
||||
_cameraFrame = new AcDream.App.Rendering.CameraFrameController(
|
||||
liveSpatialReconciler);
|
||||
var cameraFrame = new AcDream.App.Rendering.CameraFrameController(
|
||||
_cameraController!,
|
||||
_inputCapture,
|
||||
_cameraInput,
|
||||
localPlayerFrameRuntime,
|
||||
_chaseCameraInput,
|
||||
localPlayerFrame,
|
||||
_liveSpatialReconciler,
|
||||
liveSpatialReconciler,
|
||||
new AcDream.App.Combat.CombatCameraTargetSource(
|
||||
_gameplaySettings,
|
||||
Combat,
|
||||
_selection,
|
||||
_worldSelectionQuery!));
|
||||
_updateFrameOrchestrator = new AcDream.App.Update.UpdateFrameOrchestrator(
|
||||
new AcDream.App.Update.LiveEntityTeardownFramePhase(
|
||||
_liveEntities),
|
||||
new AcDream.App.Update.ConsoleUpdateFrameFailureSink(),
|
||||
_updateFrameClock,
|
||||
new AcDream.App.Update.PhysicsScriptClockPublisher(_scriptRunner!),
|
||||
streamingFrame,
|
||||
_gameplayInputFrame,
|
||||
liveFrameCoordinator,
|
||||
new AcDream.App.Update.LiveEntityLivenessFramePhase(
|
||||
_liveEntityLiveness,
|
||||
new AcDream.App.Update.StopwatchClientMonotonicTimeSource()),
|
||||
_localPlayerTeleport,
|
||||
new AcDream.App.Update.PlayerModeAutoEntryFramePhase(
|
||||
_playerModeAutoEntry),
|
||||
cameraFrame);
|
||||
AcDream.App.Net.LiveSessionStartResult liveStart =
|
||||
_liveSessionController.Start(
|
||||
_options,
|
||||
|
|
@ -2958,7 +2983,7 @@ public sealed class GameWindow : IDisposable
|
|||
{
|
||||
events = new AcDream.App.Net.LiveSessionEventRouter(
|
||||
session,
|
||||
CreateLiveEntitySessionSink(),
|
||||
_liveEntitySessionEvents.CreateSink(),
|
||||
new AcDream.App.Net.LiveEnvironmentSessionSink(
|
||||
OnEnvironChanged,
|
||||
ticks =>
|
||||
|
|
@ -2987,44 +3012,6 @@ public sealed class GameWindow : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private AcDream.App.Net.LiveEntitySessionSink CreateLiveEntitySessionSink() => new(
|
||||
Spawned: spawn => _inboundEntityEvents.Run(
|
||||
_liveEntityHydration!,
|
||||
spawn,
|
||||
static (hydration, value) => hydration.OnCreate(value)),
|
||||
Deleted: deletion => _inboundEntityEvents.Run(
|
||||
_liveEntityHydration!, deletion,
|
||||
static (hydration, value) => hydration.OnDelete(value)),
|
||||
PickedUp: pickup => _inboundEntityEvents.Run(
|
||||
_liveEntityHydration!, pickup,
|
||||
static (hydration, value) => hydration.OnPickup(value)),
|
||||
MotionUpdated: motion => _inboundEntityEvents.Run(
|
||||
_liveEntityNetworkUpdates!, motion,
|
||||
static (updates, value) => updates.OnMotion(value)),
|
||||
PositionUpdated: position => _inboundEntityEvents.Run(
|
||||
_liveEntityNetworkUpdates!, position,
|
||||
static (updates, value) => updates.OnPosition(value)),
|
||||
VectorUpdated: vector => _inboundEntityEvents.Run(
|
||||
_liveEntityNetworkUpdates!, vector,
|
||||
static (updates, value) => updates.OnVector(value)),
|
||||
StateUpdated: state => _inboundEntityEvents.Run(
|
||||
_liveEntityNetworkUpdates!, state,
|
||||
static (updates, value) => updates.OnState(value)),
|
||||
ParentUpdated: parent => _inboundEntityEvents.Run(
|
||||
_liveEntityHydration!, parent,
|
||||
static (hydration, value) => hydration.OnParent(value)),
|
||||
TeleportStarted: teleport => _inboundEntityEvents.Run(
|
||||
_localPlayerTeleportSink,
|
||||
teleport,
|
||||
static (sink, value) => sink.OnTeleportStarted(value)),
|
||||
AppearanceUpdated: appearance => _inboundEntityEvents.Run(
|
||||
_liveEntityHydration!, appearance,
|
||||
static (hydration, value) => hydration.OnAppearance(value)),
|
||||
PlayPhysicsScript: script => _inboundEntityEvents.Run(
|
||||
this, script, static (window, value) => window.OnPlayScriptReceived(value)),
|
||||
PlayPhysicsScriptType: message => _inboundEntityEvents.Run(
|
||||
this, message, static (window, value) => window._entityEffects?.HandleTyped(value)));
|
||||
|
||||
private AcDream.App.Net.LiveInventorySessionBindings
|
||||
CreateLiveInventorySessionBindings() => new(
|
||||
Objects,
|
||||
|
|
@ -3276,47 +3263,6 @@ public sealed class GameWindow : IDisposable
|
|||
doll.MeshRefs = reposed;
|
||||
}
|
||||
|
||||
private void PublishLocalPhysicsTimestamps(
|
||||
uint guid,
|
||||
AcDream.App.World.AcceptedPhysicsTimestamps timestamps)
|
||||
{
|
||||
AcDream.Core.Net.WorldSession? session = LiveSession;
|
||||
if (guid != _playerServerGuid || session is null) return;
|
||||
session.PublishAcceptedLocalPhysicsTimestamps(
|
||||
timestamps.Instance,
|
||||
timestamps.ServerControlledMove,
|
||||
timestamps.Teleport,
|
||||
timestamps.ForcePosition);
|
||||
}
|
||||
|
||||
// #145: the LANDBLOCK-relative (cell-local) position used to SEED the player
|
||||
// body's cell-relative CellPosition. This is the ONE place the streaming center
|
||||
// (_liveCenter) is allowed to touch the physics frame — at the placement seam,
|
||||
// converting the render-frame world position into the wire's (cell, local). After
|
||||
// seeding, physics carries (cell, local) forward without ever reading _liveCenter.
|
||||
private System.Numerics.Vector3 CellLocalForSeed(System.Numerics.Vector3 worldPos, uint cellId)
|
||||
{
|
||||
int lbX = (int)((cellId >> 24) & 0xFFu);
|
||||
int lbY = (int)((cellId >> 16) & 0xFFu);
|
||||
var origin = new System.Numerics.Vector3(
|
||||
(lbX - _liveCenterX) * 192f,
|
||||
(lbY - _liveCenterY) * 192f,
|
||||
0f);
|
||||
return worldPos - origin;
|
||||
}
|
||||
|
||||
private bool LoginWorldReady()
|
||||
{
|
||||
return TryGetLoginWorldCell(out uint cell)
|
||||
&& EvaluateWorldRevealReadiness(cell).IsReady;
|
||||
}
|
||||
|
||||
private AcDream.App.Streaming.WorldRevealReadinessSnapshot EvaluateWorldRevealReadiness(
|
||||
uint destinationCell)
|
||||
{
|
||||
return _worldReveal?.Evaluate(destinationCell) ?? default;
|
||||
}
|
||||
|
||||
private bool TryGetLoginWorldCell(out uint cell)
|
||||
{
|
||||
cell = 0;
|
||||
|
|
@ -3329,20 +3275,6 @@ public sealed class GameWindow : IDisposable
|
|||
return cell != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-sent direct PhysicsScript (F754). EntityEffectController owns
|
||||
/// GUID translation and pre-materialization queueing.
|
||||
///
|
||||
/// <para>
|
||||
/// F754 remains a direct PhysicsScript DID. It is never resolved through a
|
||||
/// typed PhysicsScriptTable.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
private void OnPlayScriptReceived(AcDream.Core.Net.Messages.PlayPhysicsScript message)
|
||||
{
|
||||
_entityEffects?.HandleDirect(message);
|
||||
}
|
||||
|
||||
private void UpdateSkyPes(
|
||||
float dayFraction,
|
||||
AcDream.Core.World.DayGroupData? dayGroup,
|
||||
|
|
@ -3512,76 +3444,10 @@ public sealed class GameWindow : IDisposable
|
|||
}
|
||||
private void OnUpdate(double dt)
|
||||
{
|
||||
using var _updStage = _frameProfiler.BeginStage(AcDream.App.Diagnostics.FrameStage.Update);
|
||||
|
||||
// A resource callback can request deletion while the presentation
|
||||
// owner is inside a synchronous resume/suspend transition. The first
|
||||
// unregister leaves a canonical teardown tombstone; drain it only now,
|
||||
// after that callback stack has unwound on the same update thread.
|
||||
try
|
||||
{
|
||||
_liveEntityHydration?.RetryPendingTeardowns();
|
||||
}
|
||||
catch (AggregateException error)
|
||||
{
|
||||
// The tombstone retains every unfinished owner step. Report this
|
||||
// attempt, but keep the frame advancing so the next update can
|
||||
// retry after transient renderer/plugin teardown failures.
|
||||
Console.Error.WriteLine($"[live-entity-teardown] {error}");
|
||||
}
|
||||
|
||||
AcDream.App.Update.UpdateFrameTiming frameTiming = _updateFrameClock.Advance(
|
||||
using var _updStage = _frameProfiler.BeginStage(
|
||||
AcDream.App.Diagnostics.FrameStage.Update);
|
||||
_updateFrameOrchestrator.Tick(
|
||||
new AcDream.App.Update.UpdateFrameInput(dt));
|
||||
float frameDelta = frameTiming.SimulationDeltaSecondsSingle;
|
||||
|
||||
// Retail ScriptManager::AddScriptInternal (0x0051B310) stamps
|
||||
// Timer::cur_time at the packet/default-script call site. Publish this
|
||||
// update frame's clock before streaming and network dispatch, then
|
||||
// reuse the same timestamp for animation hooks and the later drain.
|
||||
_scriptRunner?.PublishTime(frameTiming.ScriptTime);
|
||||
|
||||
// Streaming publishes before inbound CreateObject dispatch so newly
|
||||
// accepted live projections can enter a resident bucket this frame.
|
||||
_streamingFrame.Tick();
|
||||
|
||||
// Input callbacks feed the current object tick. Packets already read
|
||||
// by Core.Net remain queued until the retail object/physics phase has
|
||||
// consumed that input and published its final pose.
|
||||
_gameplayInputFrame!.Tick(frameTiming);
|
||||
|
||||
// Drain pending live-session traffic AFTER streaming so any incoming
|
||||
// CreateObject events find their landblock already loaded in
|
||||
// GpuWorldState. Non-blocking — returns immediately if no datagrams
|
||||
// are in the kernel buffer. Fires EntitySpawned events synchronously.
|
||||
// Step 2: routed through the controller; functionally identical.
|
||||
// Retail SmartBox::UseTime (0x00455410) advances CObjectMaint and
|
||||
// CPhysics before it drains the inbound event queue. Keep the complete
|
||||
// live-object phase on that side of the barrier too. In particular,
|
||||
// the recall action retires before ACE's Hidden SetState freezes its
|
||||
// PartArray at the teleport boundary.
|
||||
_liveFrameCoordinator.Tick(frameDelta);
|
||||
_liveEntityLiveness?.Tick(ClientTimerNow());
|
||||
|
||||
// Retail teleport transit runs after streaming and inbound state so a
|
||||
// newly resident destination can materialize in this same frame. The
|
||||
// focused owner also converges an F751 that arrived before the local
|
||||
// player projection/controller became available.
|
||||
_localPlayerTeleport!.Tick(frameDelta);
|
||||
// Phase K.1a — tick the input dispatcher so Hold-type bindings
|
||||
// re-fire while their chord is held. K.1b adds the subscribers
|
||||
// that actually consume the events.
|
||||
// Phase D.5.3a — advance the selected-object overlay flash (0.25s green pulse
|
||||
// on selection, then revert). No-op when nothing is flashing.
|
||||
// Retained panel-local timers advance through RetailUiRuntime.Tick on the draw seam.
|
||||
|
||||
// Phase K.2 — auto-enter player mode at login. The guard
|
||||
// returns true on the firing tick (one-shot); subsequent ticks
|
||||
// are no-ops. Skipped offline (no active session → IsLiveInWorld
|
||||
// predicate stays false). Cancelled by manual fly-toggle in
|
||||
// OnInputAction (Ctrl+Tab) or DebugPanel.
|
||||
_playerModeAutoEntry?.TryEnter();
|
||||
|
||||
_cameraFrame.Tick(frameTiming);
|
||||
}
|
||||
|
||||
private void OnCameraModeChanged(bool _modeBool)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue