refactor(app): compose session and player startup
Move streaming, live-session, hydration, local-player, combat, and teleport construction behind the typed Phase-7 boundary. Add exact-owner runtime bindings and focused spawn-claim classification so partial startup rolls back without retaining old session targets while preserving the accepted construction and frame dependencies. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
3573da12e1
commit
7771c07fb6
31 changed files with 1759 additions and 532 deletions
|
|
@ -21,7 +21,8 @@ public sealed class GameWindow :
|
|||
IGameWindowSettingsDevToolsPublication,
|
||||
IGameWindowWorldRenderPublication,
|
||||
IGameWindowInteractionRetainedUiPublication,
|
||||
IGameWindowLivePresentationPublication
|
||||
IGameWindowLivePresentationPublication,
|
||||
IGameWindowSessionPlayerPublication
|
||||
{
|
||||
private static double ClientTimerNow() =>
|
||||
System.Diagnostics.Stopwatch.GetTimestamp()
|
||||
|
|
@ -99,6 +100,7 @@ public sealed class GameWindow :
|
|||
private AcDream.App.Rendering.RenderFrameDiagnosticsController?
|
||||
_renderFrameDiagnostics;
|
||||
private LivePresentationRuntimeBindings? _livePresentationBindings;
|
||||
private SessionPlayerRuntimeBindings? _sessionPlayerBindings;
|
||||
private AcDream.App.Diagnostics.FrameScreenshotController? _frameScreenshots;
|
||||
private AcDream.App.Diagnostics.WorldLifecycleAutomationController? _worldLifecycleAutomation;
|
||||
private AcDream.App.Rendering.GpuFrameFlightController? _gpuFrameFlights;
|
||||
|
|
@ -123,22 +125,14 @@ public sealed class GameWindow :
|
|||
private AcDream.App.Streaming.StreamingOriginRecenterCoordinator?
|
||||
_streamingOriginRecenter;
|
||||
private AcDream.App.Streaming.WorldRevealCoordinator? _worldReveal;
|
||||
private AcDream.App.Streaming.DatSpawnClaimHydrationClassifier?
|
||||
_spawnClaimHydration;
|
||||
private readonly AcDream.App.Streaming.DeferredLocalPlayerTeleportNetworkSink
|
||||
_localPlayerTeleportSink = new();
|
||||
private AcDream.App.Streaming.LocalPlayerTeleportController?
|
||||
_localPlayerTeleport;
|
||||
private readonly AcDream.App.Rendering.WorldRenderRangeState _renderRange =
|
||||
new(nearRadius: 4, farRadius: 12);
|
||||
private int _nearRadius
|
||||
{
|
||||
get => _renderRange.NearRadius;
|
||||
set => _renderRange.NearRadius = value;
|
||||
}
|
||||
private int _farRadius
|
||||
{
|
||||
get => _renderRange.FarRadius;
|
||||
set => _renderRange.FarRadius = value;
|
||||
}
|
||||
// Phase B.3: physics engine — populated from the streaming pipeline.
|
||||
private readonly AcDream.Core.Physics.PhysicsEngine _physicsEngine = new();
|
||||
|
||||
|
|
@ -170,7 +164,7 @@ public sealed class GameWindow :
|
|||
_liveEntityProjectionWithdrawal;
|
||||
private AcDream.App.World.LiveEntityHydrationController? _liveEntityHydration;
|
||||
private AcDream.App.Physics.LiveEntityNetworkUpdateController? _liveEntityNetworkUpdates;
|
||||
private AcDream.App.Net.LiveEntitySessionController _liveEntitySessionEvents = null!;
|
||||
private AcDream.App.Net.LiveEntitySessionController? _liveEntitySessionEvents;
|
||||
private readonly AcDream.App.Physics.DeferredLiveEntityMotionRuntimeBindings
|
||||
_liveEntityMotionBindings = new();
|
||||
|
||||
|
|
@ -996,6 +990,51 @@ public sealed class GameWindow :
|
|||
_livePresentationBindings = result.RuntimeBindings;
|
||||
}
|
||||
|
||||
void IGameWindowSessionPlayerPublication.PublishSessionPlayer(
|
||||
SessionPlayerResult result)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(result);
|
||||
if (_streamer is not null
|
||||
|| _streamingController is not null
|
||||
|| _streamingOriginRecenter is not null
|
||||
|| _worldReveal is not null
|
||||
|| _spawnClaimHydration is not null
|
||||
|| _liveSessionController is not null
|
||||
|| _liveEntityHydration is not null
|
||||
|| _liveEntityNetworkUpdates is not null
|
||||
|| _liveEntityLiveness is not null
|
||||
|| _liveEntitySessionEvents is not null
|
||||
|| _gameplayInputFrame is not null
|
||||
|| _localPlayerAnimation is not null
|
||||
|| _localPlayerShadowSynchronizer is not null
|
||||
|| _playerModeController is not null
|
||||
|| _playerModeAutoEntry is not null
|
||||
|| _localPlayerTeleport is not null
|
||||
|| _sessionPlayerBindings is not null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The GameWindow composition shell already owns session/player state.");
|
||||
}
|
||||
|
||||
_streamer = result.Streamer;
|
||||
_streamingController = result.Streaming;
|
||||
_streamingOriginRecenter = result.StreamingOriginRecenter;
|
||||
_worldReveal = result.WorldReveal;
|
||||
_spawnClaimHydration = result.SpawnClaimHydration;
|
||||
_liveSessionController = result.LiveSession;
|
||||
_liveEntityHydration = result.Hydration;
|
||||
_liveEntityNetworkUpdates = result.NetworkUpdates;
|
||||
_liveEntityLiveness = result.Liveness;
|
||||
_liveEntitySessionEvents = result.SessionEvents;
|
||||
_gameplayInputFrame = result.GameplayInput;
|
||||
_localPlayerAnimation = result.LocalPlayerAnimation;
|
||||
_localPlayerShadowSynchronizer = result.LocalPlayerShadow;
|
||||
_playerModeController = result.PlayerMode;
|
||||
_playerModeAutoEntry = result.PlayerModeAutoEntry;
|
||||
_localPlayerTeleport = result.LocalTeleport;
|
||||
_sessionPlayerBindings = result.RuntimeBindings;
|
||||
}
|
||||
|
||||
private static void PublishCompositionOwner<T>(
|
||||
ref T? destination,
|
||||
T value,
|
||||
|
|
@ -1139,9 +1178,6 @@ public sealed class GameWindow :
|
|||
Console.WriteLine),
|
||||
this).Compose(platform, contentEffectsAudio, settingsDevTools);
|
||||
string shadersDir = worldRender.Foundation.ShadersDirectory;
|
||||
TerrainAtlas terrainAtlas = worldRender.Foundation.TerrainAtlas;
|
||||
int centerX = worldRender.TerrainBuild.InitialCenterX;
|
||||
int centerY = worldRender.TerrainBuild.InitialCenterY;
|
||||
Console.WriteLine(
|
||||
$"loading world view centered on " +
|
||||
$"0x{worldRender.TerrainBuild.InitialCenterLandblockId:X8}");
|
||||
|
|
@ -1243,441 +1279,64 @@ public sealed class GameWindow :
|
|||
worldRender,
|
||||
interactionUi);
|
||||
|
||||
// Apply radii from the same immutable quality snapshot used for the
|
||||
// window's MSAA, terrain anisotropy, and dispatcher A2C setup.
|
||||
// Legacy ACDREAM_STREAM_RADIUS is still honoured for backward-compat.
|
||||
_nearRadius = _runtimeSettings.ResolvedQuality.NearRadius;
|
||||
_farRadius = _runtimeSettings.ResolvedQuality.FarRadius;
|
||||
|
||||
// Legacy override: ACDREAM_STREAM_RADIUS acts as nearRadius and
|
||||
// ensures farRadius >= streamRadius. Parsed once into
|
||||
// RuntimeOptions.LegacyStreamRadius (null when unset or invalid).
|
||||
if (_options.LegacyStreamRadius is { } sr)
|
||||
{
|
||||
_nearRadius = sr;
|
||||
_farRadius = System.Math.Max(sr, _farRadius);
|
||||
}
|
||||
Console.WriteLine(
|
||||
$"streaming: nearRadius={_nearRadius} (window={2 * _nearRadius + 1}x{2 * _nearRadius + 1})" +
|
||||
$" farRadius={_farRadius} (window={2 * _farRadius + 1}x{2 * _farRadius + 1})");
|
||||
|
||||
// Phase A.5 T11+: the streamer now runs on a dedicated worker thread.
|
||||
// loadLandblock acquires _datLock (T10) before touching DatCollection.
|
||||
// buildMeshOrNull (T12) receives the already-loaded LoadedLandblock so
|
||||
// it can call LandblockMesh.Build without a dat read — _heightTable and
|
||||
// _blendCtx are read-only after init, _surfaceCache is ConcurrentDictionary (T9).
|
||||
var landblockBuildFactory = new AcDream.App.Streaming.LandblockBuildFactory(
|
||||
_dats!,
|
||||
_datLock,
|
||||
_heightTable!,
|
||||
_physicsDataCache,
|
||||
_options.DumpSceneryZ);
|
||||
_streamer = AcDream.App.Streaming.LandblockStreamer.CreateForRequests(
|
||||
loadLandblock: landblockBuildFactory.Build,
|
||||
buildMeshOrNull: (id, lb) =>
|
||||
{
|
||||
if (lb is null || _heightTable is null || _blendCtx is null || _surfaceCache is null)
|
||||
return null;
|
||||
uint lbX = (id >> 24) & 0xFFu;
|
||||
uint lbY = (id >> 16) & 0xFFu;
|
||||
// _surfaceCache is ConcurrentDictionary (T9) — safe from worker thread.
|
||||
// _heightTable and _blendCtx are read-only after initialization.
|
||||
// lb.Heightmap is the pre-loaded LandBlock; no dat read needed here.
|
||||
return AcDream.Core.Terrain.LandblockMesh.Build(
|
||||
lb.Heightmap, lbX, lbY, _heightTable, _blendCtx, _surfaceCache);
|
||||
});
|
||||
_streamer.Start();
|
||||
|
||||
_streamingController = new AcDream.App.Streaming.StreamingController(
|
||||
enqueueLoad: (id, kind, generation) => _streamer.EnqueueLoad(
|
||||
new AcDream.App.Streaming.LandblockBuildRequest(
|
||||
id,
|
||||
kind,
|
||||
generation,
|
||||
new AcDream.App.Streaming.LandblockBuildOrigin(
|
||||
_liveCenterX,
|
||||
_liveCenterY))),
|
||||
enqueueUnload: (id, generation) => _streamer.EnqueueUnload(id, generation),
|
||||
drainCompletions: _streamer.DrainCompletions,
|
||||
state: _worldState,
|
||||
nearRadius: _nearRadius,
|
||||
farRadius: _farRadius,
|
||||
clearPendingLoads: _streamer.ClearPendingLoads,
|
||||
// Retained-object recovery runs after each
|
||||
// (dungeon-exit expand or Far→Near promote). ACE won't re-send the
|
||||
// objects it still considers known.
|
||||
presentationPipeline: _landblockPresentationPipeline!);
|
||||
_streamingOriginRecenter =
|
||||
new AcDream.App.Streaming.StreamingOriginRecenterCoordinator(
|
||||
_streamingController,
|
||||
_liveWorldOrigin);
|
||||
// A.5 T22.5: apply max-completions from resolved quality.
|
||||
_streamingController.MaxCompletionsPerFrame =
|
||||
_runtimeSettings.ResolvedQuality.MaxCompletionsPerFrame;
|
||||
_runtimeSettings.BindRuntimeTargets(
|
||||
new RuntimeSettingsTargets(
|
||||
new SilkRuntimeDisplayWindowTarget(_window!),
|
||||
_wbDrawDispatcher!,
|
||||
terrainAtlas,
|
||||
_streamingController,
|
||||
_renderRange,
|
||||
_uiHost?.Root,
|
||||
Console.WriteLine));
|
||||
_worldReveal = new AcDream.App.Streaming.WorldRevealCoordinator(
|
||||
isRenderNeighborhoodReady: _streamingController.IsRenderNeighborhoodResident,
|
||||
isSpawnCellReady: _physicsEngine.IsSpawnCellReady,
|
||||
isTerrainNeighborhoodReady: _physicsEngine.IsNeighborhoodTerrainResident,
|
||||
areCompositeTexturesReady: () => _wbDrawDispatcher?.CompositeTexturesReady == true,
|
||||
prepareCompositeTextures: (destinationCell, radius) =>
|
||||
_wbDrawDispatcher?.PrepareCompositeTextures(
|
||||
_worldState.Entities,
|
||||
_worldState.FlatViewGeneration,
|
||||
destinationCell,
|
||||
radius),
|
||||
invalidateCompositeTextures: () =>
|
||||
_wbDrawDispatcher?.InvalidateCompositeWarmupReadiness(),
|
||||
isSpawnClaimUnhydratable: IsSpawnClaimUnhydratable,
|
||||
log: message => Console.WriteLine(message));
|
||||
|
||||
var networkUpdateBridge =
|
||||
new AcDream.App.World.DeferredLiveEntityNetworkUpdateSink();
|
||||
var sealedDungeonCells =
|
||||
new AcDream.App.Streaming.DatSealedDungeonCellClassifier(
|
||||
_dats!,
|
||||
_datLock);
|
||||
var projectionMaterializer = new DatLiveEntityProjectionMaterializer(
|
||||
_options,
|
||||
_dats!,
|
||||
_liveEntities!,
|
||||
_physicsDataCache,
|
||||
_animLoader!,
|
||||
_wbEntitySpawnAdapter!,
|
||||
_textureCache!,
|
||||
_classificationCache,
|
||||
_effectPoses,
|
||||
_equippedChildRenderer!,
|
||||
_worldGameState,
|
||||
_worldEvents,
|
||||
_physicsEngine.ShadowObjects,
|
||||
_liveEntityCollisionBuilder!,
|
||||
_projectileController!,
|
||||
_animationPresenter,
|
||||
_staticAnimationScheduler!,
|
||||
_liveWorldOrigin,
|
||||
_updateFrameClock);
|
||||
var originCoordinator =
|
||||
new AcDream.App.World.LiveEntityWorldOriginCoordinator(
|
||||
_liveWorldOrigin,
|
||||
_streamingController,
|
||||
_worldState,
|
||||
_worldReveal,
|
||||
_localPlayerIdentity,
|
||||
sealedDungeonCells,
|
||||
Console.WriteLine);
|
||||
_liveSessionController = new AcDream.App.Net.LiveSessionController();
|
||||
interactionUi.LateBindings.AdoptLateOwnerBinding(
|
||||
"live session",
|
||||
interactionUi.LateBindings.Session.Bind(_liveSessionController));
|
||||
var localPhysicsTimestamps =
|
||||
new AcDream.App.World.LiveSessionLocalPhysicsTimestampPublisher(
|
||||
_localPlayerIdentity,
|
||||
_liveSessionController);
|
||||
var liveEntityTeardown =
|
||||
new AcDream.App.World.LiveEntityRuntimeTeardownController(
|
||||
livePresentation.LiveEntities,
|
||||
_liveEntityPresentation!,
|
||||
_entityEffects!,
|
||||
_remoteTeleportController!,
|
||||
_selectionInteractions,
|
||||
_selection,
|
||||
_animatedEntities,
|
||||
_remoteMovementObservations,
|
||||
_translucencyFades,
|
||||
_liveEntityProjectionWithdrawal!,
|
||||
_equippedChildRenderer!,
|
||||
_physicsEngine.ShadowObjects,
|
||||
_liveEntityLights!,
|
||||
_classificationCache,
|
||||
_localPlayerIdentity);
|
||||
livePresentation.ComponentLifecycle.Bind(liveEntityTeardown);
|
||||
var liveEntityDeletion =
|
||||
new AcDream.App.World.LiveEntityDeletionController(
|
||||
livePresentation.LiveEntities,
|
||||
Objects,
|
||||
liveEntityTeardown,
|
||||
_localPlayerIdentity,
|
||||
_options.DumpLiveSpawns ? Console.WriteLine : null);
|
||||
_liveEntityHydration = new AcDream.App.World.LiveEntityHydrationController(
|
||||
livePresentation.LiveEntities,
|
||||
Objects,
|
||||
_datLock,
|
||||
projectionMaterializer,
|
||||
new AcDream.App.World.LiveEntityRelationshipProjection(
|
||||
livePresentation.EquippedChildren),
|
||||
new AcDream.App.World.LiveEntityReadyPublisher(
|
||||
livePresentation.LiveEntities,
|
||||
_entityEffects!,
|
||||
_liveEntityPresentation!),
|
||||
originCoordinator,
|
||||
networkUpdateBridge,
|
||||
localPhysicsTimestamps,
|
||||
_localPlayerIdentity,
|
||||
liveEntityDeletion,
|
||||
_options.DumpLiveSpawns ? Console.WriteLine : null);
|
||||
livePresentation.RuntimeBindings.Adopt(
|
||||
"landblock-loaded hydration",
|
||||
livePresentation.LandblockLoaded.Bind(_liveEntityHydration));
|
||||
_liveEntityNetworkUpdates =
|
||||
new AcDream.App.Physics.LiveEntityNetworkUpdateController(
|
||||
livePresentation.LiveEntities,
|
||||
Objects,
|
||||
_liveEntityHydration,
|
||||
_entityEffects!,
|
||||
_liveEntityPresentation!,
|
||||
_liveEntityLights!,
|
||||
_equippedChildRenderer!,
|
||||
_projectileController!,
|
||||
_remoteTeleportController!,
|
||||
_animatedEntities,
|
||||
new LiveEntityRemoteMotionRuntimeView<RemoteMotion>(
|
||||
_liveEntityRuntimeSlot),
|
||||
_remoteMovementObservations,
|
||||
_remotePhysicsUpdater,
|
||||
_remoteInboundMotion,
|
||||
livePresentation.MotionRuntime,
|
||||
_physicsEngine,
|
||||
_dats!,
|
||||
_animLoader!,
|
||||
_combatTargetController,
|
||||
_liveWorldOrigin,
|
||||
_localPlayerTeleportSink,
|
||||
_playerControllerSlot,
|
||||
_localPlayerOutbound,
|
||||
_playerHostSlot,
|
||||
_localPlayerIdentity,
|
||||
_updateFrameClock,
|
||||
_liveSessionController,
|
||||
localPhysicsTimestamps.Publish,
|
||||
_movementTruthDiagnostics);
|
||||
_liveEntityLiveness = new AcDream.App.World.LiveEntityLivenessController(
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity,
|
||||
liveEntityDeletion);
|
||||
_liveEntitySessionEvents = new AcDream.App.Net.LiveEntitySessionController(
|
||||
_inboundEntityEvents,
|
||||
_liveEntityHydration,
|
||||
_liveEntityNetworkUpdates,
|
||||
_localPlayerTeleportSink,
|
||||
_entityEffects!);
|
||||
livePresentation.ParentAcceptance.Bind(
|
||||
_liveEntityHydration.TryAcceptParentForProjection);
|
||||
networkUpdateBridge.Bind(_liveEntityNetworkUpdates);
|
||||
livePresentation.EquippedChildren.EntityReady += candidate =>
|
||||
_liveEntityHydration.OnEntityReady(candidate);
|
||||
_liveEntityHydration.AppearanceApplied += guid =>
|
||||
{
|
||||
if (guid == _playerServerGuid)
|
||||
_paperdollFramePresenter?.MarkDirty();
|
||||
};
|
||||
|
||||
// Phase 4.7: optional live-mode startup. Connect to the ACE server,
|
||||
// enter the world as the first character on the account, and stream
|
||||
// CreateObject messages into _worldGameState as they arrive. Entirely
|
||||
// gated behind ACDREAM_LIVE=1 so the default run path is unchanged.
|
||||
_liveWorldOrigin.SetPlaceholder(centerX, centerY);
|
||||
_combatAttackOperations.Bind(
|
||||
new AcDream.App.Combat.LiveCombatAttackOperations(
|
||||
Combat,
|
||||
new AcDream.App.Combat.CombatAttackTargetSource(
|
||||
_selection,
|
||||
_liveEntities!,
|
||||
Objects,
|
||||
_localPlayerIdentity),
|
||||
_runtimeSettings,
|
||||
_playerControllerSlot,
|
||||
_localPlayerOutbound,
|
||||
_liveSessionController,
|
||||
_liveSessionController,
|
||||
_combatFeedback));
|
||||
AcDream.App.Input.MouseLookController? mouseLookController =
|
||||
_mouseSource is not null && _mouseLookCursor is not null
|
||||
? new AcDream.App.Input.MouseLookController(
|
||||
_mouseSource,
|
||||
_pointerPosition,
|
||||
_localPlayerMode,
|
||||
_playerControllerSlot,
|
||||
_cameraController!,
|
||||
_chaseCameraInput,
|
||||
_movementInput,
|
||||
_localPlayerOutbound,
|
||||
_liveSessionController,
|
||||
_mouseLookCursor,
|
||||
new AcDream.App.Input.EnvironmentInputMonotonicClock())
|
||||
: null;
|
||||
_gameplayInputFrame = new AcDream.App.Input.GameplayInputFrameController(
|
||||
_inputDispatcher,
|
||||
_movementInput,
|
||||
mouseLookController,
|
||||
new AcDream.App.Input.CombatAttackInputFrameAdapter(
|
||||
_combatAttackController!));
|
||||
_cameraPointerInput?.BindGameplayFrame(_gameplayInputFrame);
|
||||
var streamingFrame = new AcDream.App.Streaming.StreamingFrameController(
|
||||
_options.LiveMode,
|
||||
_localPlayerMode,
|
||||
_playerControllerSlot,
|
||||
_liveSessionController,
|
||||
_liveWorldOrigin,
|
||||
_liveEntityNetworkUpdates!,
|
||||
new AcDream.App.Streaming.FlyCameraStreamingObserverSource(
|
||||
_cameraController!),
|
||||
new AcDream.App.Streaming.PhysicsStreamingDungeonCellSource(
|
||||
_physicsEngine),
|
||||
_streamingOriginRecenter!,
|
||||
_streamingController!,
|
||||
new AcDream.App.Streaming.LiveProjectionRescueRebucketter(
|
||||
_worldState,
|
||||
livePresentation.LiveEntities));
|
||||
var liveEffectFrame = new AcDream.App.Update.LiveEffectFrameController(
|
||||
_translucencyFades,
|
||||
_animationHookFrames!,
|
||||
_entityEffects!,
|
||||
_particleSink!,
|
||||
_liveEntityLights!,
|
||||
_particleVisibility,
|
||||
_particleSystem!,
|
||||
_scriptRunner!,
|
||||
_updateFrameClock,
|
||||
new AcDream.App.Update.SettingsParticleRangeSource(
|
||||
_runtimeSettings));
|
||||
var liveSpatialReconciler =
|
||||
new AcDream.App.Update.LiveSpatialPresentationReconciler(
|
||||
_entityEffects!,
|
||||
_equippedChildRenderer!,
|
||||
_particleSink!,
|
||||
_liveEntityLights!);
|
||||
_localPlayerAnimation =
|
||||
new AcDream.App.Input.LocalPlayerAnimationController(
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity,
|
||||
_animatedEntities,
|
||||
_animationPresenter,
|
||||
_animationHookFrames!);
|
||||
_localPlayerShadowSynchronizer =
|
||||
new AcDream.App.Physics.LocalPlayerShadowSynchronizer(
|
||||
_physicsEngine,
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity,
|
||||
_liveWorldOrigin,
|
||||
_localPlayerShadow);
|
||||
var localPlayerProjection =
|
||||
new AcDream.App.Input.LocalPlayerProjectionController(
|
||||
new AcDream.App.Input.LiveLocalPlayerProjectionRuntime(
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity,
|
||||
_liveWorldOrigin,
|
||||
_localPlayerShadowSynchronizer));
|
||||
var localPlayerFrameRuntime =
|
||||
new AcDream.App.Input.LiveLocalPlayerFrameRuntime(
|
||||
_cameraController!,
|
||||
_localPlayerMode,
|
||||
_playerControllerSlot,
|
||||
_chaseCameraInput,
|
||||
_movementInput,
|
||||
_inputCapture,
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity,
|
||||
_playerHostSlot,
|
||||
localPlayerProjection,
|
||||
_localPlayerOutbound,
|
||||
_liveSessionController);
|
||||
var localPlayerFrame =
|
||||
new AcDream.App.Input.RetailLocalPlayerFrameController(
|
||||
localPlayerFrameRuntime,
|
||||
_movementInput);
|
||||
var liveObjectFrame = new AcDream.App.Update.LiveObjectFrameController(
|
||||
_inboundEntityEvents,
|
||||
localPlayerFrame,
|
||||
_selectionInteractions,
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity,
|
||||
_liveWorldOrigin,
|
||||
_liveAnimationScheduler,
|
||||
livePresentation.StaticAnimationScheduler,
|
||||
_animationPresenter,
|
||||
_animatedEntities,
|
||||
_equippedChildRenderer!,
|
||||
liveEffectFrame);
|
||||
_playerModeController = new AcDream.App.Input.PlayerModeController(
|
||||
_localPlayerMode,
|
||||
_playerControllerSlot,
|
||||
_playerHostSlot,
|
||||
_chaseCameraInput,
|
||||
_cameraController!,
|
||||
_physicsEngine,
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity,
|
||||
_liveWorldOrigin,
|
||||
_liveEntityMotionBindings,
|
||||
_dats!,
|
||||
_datLock,
|
||||
_physicsDataCache,
|
||||
_animatedEntities,
|
||||
_localPlayerAnimation,
|
||||
_localPlayerShadowSynchronizer,
|
||||
_playerApproachCompletions,
|
||||
_gameplayInputFrame,
|
||||
_liveSessionController,
|
||||
_movementTruthDiagnostics,
|
||||
_localPlayerSkills,
|
||||
_viewportAspect);
|
||||
_devToolsComposition?.LateBindings.PlayerModeCommands.Bind(
|
||||
_playerModeController);
|
||||
_devToolsCommandBus?.Bind(_liveSessionController);
|
||||
_playerModeAutoEntry = new AcDream.App.Input.PlayerModeAutoEntry(
|
||||
new AcDream.App.Input.LivePlayerModeAutoEntryContext(
|
||||
_liveSessionController,
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity,
|
||||
_worldReveal
|
||||
?? throw new InvalidOperationException(
|
||||
"World reveal was not composed before player auto-entry."),
|
||||
_localPlayerMode,
|
||||
_playerModeController));
|
||||
_playerModeController.BindAutoEntry(_playerModeAutoEntry);
|
||||
_localPlayerTeleport = _portalTunnelFallback.Transfer(
|
||||
portalTunnel => new AcDream.App.Streaming.LocalPlayerTeleportController(
|
||||
new AcDream.App.Streaming.LiveLocalPlayerTeleportAuthority(
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity),
|
||||
_gameplayInputFrame,
|
||||
_playerModeController,
|
||||
new AcDream.App.Streaming.LocalPlayerTeleportStreamingOperations(
|
||||
_liveWorldOrigin,
|
||||
_streamingOriginRecenter!,
|
||||
_streamingController!,
|
||||
sealedDungeonCells),
|
||||
_worldReveal,
|
||||
new AcDream.App.Streaming.LocalPlayerTeleportPlacement(
|
||||
SessionPlayerResult sessionPlayer =
|
||||
new SessionPlayerCompositionPhase(
|
||||
new SessionPlayerDependencies(
|
||||
_options,
|
||||
_window!,
|
||||
_datLock,
|
||||
_runtimeSettings,
|
||||
settingsDevTools,
|
||||
_physicsEngine,
|
||||
livePresentation.LiveEntities,
|
||||
_physicsDataCache,
|
||||
_worldGameState,
|
||||
_worldEvents,
|
||||
_selection,
|
||||
Objects,
|
||||
_classificationCache,
|
||||
_liveEntityRuntimeSlot,
|
||||
_animatedEntities,
|
||||
_remoteMovementObservations,
|
||||
_remotePhysicsUpdater,
|
||||
_remoteInboundMotion,
|
||||
_inboundEntityEvents,
|
||||
_liveEntityMotionBindings,
|
||||
_localPlayerIdentity,
|
||||
_playerControllerSlot,
|
||||
_playerHostSlot,
|
||||
_localPlayerMode,
|
||||
_chaseCameraInput,
|
||||
_localPlayerOutbound,
|
||||
_localPlayerTeleportSink,
|
||||
_liveWorldOrigin,
|
||||
liveSpatialReconciler),
|
||||
new AcDream.App.Streaming.LocalPlayerTeleportSession(
|
||||
_liveSessionController),
|
||||
new AcDream.App.Streaming.LocalPlayerTeleportPresentation(
|
||||
portalTunnel)));
|
||||
_localPlayerTeleportSink.Bind(_localPlayerTeleport);
|
||||
interactionUi.LateBindings.AdoptLateOwnerBinding(
|
||||
"selection view plane",
|
||||
interactionUi.LateBindings.SelectionViewPlane.Bind(
|
||||
_localPlayerTeleport));
|
||||
_renderRange,
|
||||
_localPlayerShadow,
|
||||
_localPlayerSkills,
|
||||
_viewportAspect,
|
||||
_playerApproachCompletions,
|
||||
_pointerPosition,
|
||||
_movementInput,
|
||||
_inputCapture,
|
||||
_particleVisibility,
|
||||
_translucencyFades,
|
||||
_effectPoses,
|
||||
_updateFrameClock,
|
||||
_movementTruthDiagnostics,
|
||||
_combatAttackOperations,
|
||||
Combat,
|
||||
_combatFeedback,
|
||||
_portalTunnelFallback,
|
||||
Console.WriteLine),
|
||||
this).Compose(
|
||||
hostInputCamera,
|
||||
contentEffectsAudio,
|
||||
worldRender,
|
||||
interactionUi,
|
||||
livePresentation);
|
||||
|
||||
var teleportRenderState =
|
||||
new AcDream.App.Rendering.LocalPlayerTeleportRenderStateSource(
|
||||
_localPlayerTeleport);
|
||||
sessionPlayer.LocalTeleport);
|
||||
var renderLoginState = new AcDream.App.Rendering.RenderLoginStateSource(
|
||||
_options.LiveMode,
|
||||
_localPlayerMode);
|
||||
|
|
@ -1743,7 +1402,7 @@ public sealed class GameWindow :
|
|||
new AcDream.App.Rendering.WorldRenderFrameBuilder(
|
||||
new AcDream.App.Rendering.RuntimeWorldFrameCameraSource(
|
||||
_cameraController!,
|
||||
_localPlayerTeleport),
|
||||
sessionPlayer.LocalTeleport),
|
||||
new AcDream.App.Rendering.RuntimeWorldFrameVisibilityPreparation(
|
||||
_retailSelectionScene,
|
||||
_particleVisibility,
|
||||
|
|
@ -1876,7 +1535,7 @@ public sealed class GameWindow :
|
|||
var privatePresentation =
|
||||
new AcDream.App.Rendering.PrivatePresentationRenderer(
|
||||
new AcDream.App.Rendering.LocalPlayerPortalViewport(
|
||||
_localPlayerTeleport!,
|
||||
sessionPlayer.LocalTeleport,
|
||||
_cameraController!),
|
||||
renderFrameResources,
|
||||
_paperdollFramePresenter,
|
||||
|
|
@ -1899,19 +1558,19 @@ public sealed class GameWindow :
|
|||
_devToolsFramePresenter
|
||||
?? AcDream.App.Rendering.NullRenderFrameFailureRecovery.Instance);
|
||||
var liveFrameCoordinator = new AcDream.App.World.RetailLiveFrameCoordinator(
|
||||
liveObjectFrame,
|
||||
sessionPlayer.LiveObjectFrame,
|
||||
_worldState,
|
||||
_liveSessionController,
|
||||
localPlayerFrame,
|
||||
liveSpatialReconciler);
|
||||
sessionPlayer.LiveSession,
|
||||
sessionPlayer.LocalPlayerFrame,
|
||||
sessionPlayer.LiveSpatialReconciler);
|
||||
var cameraFrame = new AcDream.App.Rendering.CameraFrameController(
|
||||
_cameraController!,
|
||||
_inputCapture,
|
||||
_cameraInput,
|
||||
localPlayerFrameRuntime,
|
||||
sessionPlayer.LocalPlayerFrameRuntime,
|
||||
_chaseCameraInput,
|
||||
localPlayerFrame,
|
||||
liveSpatialReconciler,
|
||||
sessionPlayer.LocalPlayerFrame,
|
||||
sessionPlayer.LiveSpatialReconciler,
|
||||
new AcDream.App.Combat.CombatCameraTargetSource(
|
||||
_runtimeSettings,
|
||||
Combat,
|
||||
|
|
@ -1923,15 +1582,15 @@ public sealed class GameWindow :
|
|||
new AcDream.App.Update.ConsoleUpdateFrameFailureSink(),
|
||||
_updateFrameClock,
|
||||
new AcDream.App.Update.PhysicsScriptClockPublisher(_scriptRunner!),
|
||||
streamingFrame,
|
||||
_gameplayInputFrame,
|
||||
sessionPlayer.StreamingFrame,
|
||||
sessionPlayer.GameplayInput,
|
||||
liveFrameCoordinator,
|
||||
new AcDream.App.Update.LiveEntityLivenessFramePhase(
|
||||
_liveEntityLiveness,
|
||||
sessionPlayer.Liveness,
|
||||
new AcDream.App.Update.StopwatchClientMonotonicTimeSource()),
|
||||
_localPlayerTeleport,
|
||||
sessionPlayer.LocalTeleport,
|
||||
new AcDream.App.Update.PlayerModeAutoEntryFramePhase(
|
||||
_playerModeAutoEntry),
|
||||
sessionPlayer.PlayerModeAutoEntry),
|
||||
cameraFrame);
|
||||
_frameGraphs.Publish(updateFrameOrchestrator, renderFrameOrchestrator);
|
||||
_liveSessionHost = CreateLiveSessionHost();
|
||||
|
|
@ -1988,7 +1647,7 @@ public sealed class GameWindow :
|
|||
runtimeDiagnostics,
|
||||
new AcDream.App.Input.PlayerModeGameplayCommands(
|
||||
_localPlayerMode,
|
||||
_playerModeController),
|
||||
sessionPlayer.PlayerMode),
|
||||
new AcDream.App.Input.ItemTargetModeCommands(
|
||||
_itemInteractionController!),
|
||||
new AcDream.App.Input.GameplayCameraModeCommands(
|
||||
|
|
@ -1996,7 +1655,7 @@ public sealed class GameWindow :
|
|||
combatCommand,
|
||||
new AcDream.App.Input.GameplayWindowCommands(_window!.Close));
|
||||
var targets = new AcDream.App.Input.RuntimeGameplayInputPriorityTargets(
|
||||
_gameplayInputFrame!,
|
||||
sessionPlayer.GameplayInput,
|
||||
pointer,
|
||||
_retailUiRuntime,
|
||||
_selectionInteractions,
|
||||
|
|
@ -2095,7 +1754,7 @@ public sealed class GameWindow :
|
|||
private void ResetSessionPlayerPresentation()
|
||||
{
|
||||
_playerModeController?.ResetSession();
|
||||
_spawnClaimRangeMemo = null;
|
||||
_spawnClaimHydration?.Reset();
|
||||
}
|
||||
|
||||
private void ResetSessionIdentity()
|
||||
|
|
@ -2157,7 +1816,10 @@ public sealed class GameWindow :
|
|||
|
||||
return new AcDream.App.Net.LiveSessionEventRouter(
|
||||
session,
|
||||
_liveEntitySessionEvents.CreateSink(),
|
||||
(_liveEntitySessionEvents
|
||||
?? throw new InvalidOperationException(
|
||||
"Live entity session routing was not composed."))
|
||||
.CreateSink(),
|
||||
new AcDream.App.Net.LiveEnvironmentSessionSink(
|
||||
_worldEnvironment.ApplyAdminEnvirons,
|
||||
_worldEnvironment.SynchronizeFromServer),
|
||||
|
|
@ -2392,34 +2054,6 @@ public sealed class GameWindow :
|
|||
private void OnFramebufferResize(Silk.NET.Maths.Vector2D<int> newSize)
|
||||
=> _framebufferResize.Resize(newSize);
|
||||
|
||||
// #107 (2026-06-10): memoized "this indoor spawn claim can never hydrate"
|
||||
// check against the dat's LandBlockInfo.NumCells. Used by the auto-entry
|
||||
// hold so a garbage claim doesn't stall login forever; the Resolve-head
|
||||
// safety net demotes it loudly once entry proceeds.
|
||||
private (uint Claim, bool Unhydratable)? _spawnClaimRangeMemo;
|
||||
|
||||
private bool IsSpawnClaimUnhydratable(uint claim)
|
||||
{
|
||||
if ((claim & 0xFFFFu) < 0x0100u) return false;
|
||||
if (_spawnClaimRangeMemo is { } m && m.Claim == claim) return m.Unhydratable;
|
||||
|
||||
bool unhydratable = false;
|
||||
if (_dats is not null)
|
||||
{
|
||||
DatReaderWriter.DBObjs.LandBlockInfo? lbInfo;
|
||||
lock (_datLock)
|
||||
{
|
||||
lbInfo = _dats.Get<DatReaderWriter.DBObjs.LandBlockInfo>(
|
||||
(claim & 0xFFFF0000u) | 0xFFFEu);
|
||||
}
|
||||
uint low = claim & 0xFFFFu;
|
||||
unhydratable = lbInfo is null || lbInfo.NumCells == 0
|
||||
|| low >= 0x0100u + lbInfo.NumCells;
|
||||
}
|
||||
_spawnClaimRangeMemo = (claim, unhydratable);
|
||||
return unhydratable;
|
||||
}
|
||||
|
||||
private void OnClosing()
|
||||
{
|
||||
_hostQuiescence.StopAccepting();
|
||||
|
|
@ -2565,11 +2199,18 @@ public sealed class GameWindow :
|
|||
// the later render-frontend stage still disposes the GL owners.
|
||||
new ResourceShutdownStage("frame borrowers",
|
||||
[
|
||||
new("runtime settings targets", _runtimeSettings.UnbindRuntimeTargets),
|
||||
new("world frame composition", () =>
|
||||
{
|
||||
_frameGraphs.Withdraw();
|
||||
}),
|
||||
new("session/player bindings", () =>
|
||||
{
|
||||
SessionPlayerRuntimeBindings? bindings = _sessionPlayerBindings;
|
||||
if (bindings is null)
|
||||
return;
|
||||
bindings.Dispose();
|
||||
_sessionPlayerBindings = null;
|
||||
}),
|
||||
]),
|
||||
new ResourceShutdownStage("session dependents",
|
||||
[
|
||||
|
|
@ -2587,8 +2228,6 @@ public sealed class GameWindow :
|
|||
_cameraPointerInput;
|
||||
if (pointer is null) return;
|
||||
pointer.ReleaseMouseLookAfterSessionRetirement();
|
||||
if (_gameplayInputFrame is { } gameplayFrame)
|
||||
pointer.UnbindGameplayFrame(gameplayFrame);
|
||||
_cameraPointerInput = null;
|
||||
}),
|
||||
new("retail UI", () =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue