using AcDream.Core.Plugins; using AcDream.App.Composition; using AcDream.App.Physics; using AcDream.App.Rendering.Gpu; using AcDream.App.Rendering.Scene; using AcDream.App.Rendering.Wb; using AcDream.App.Settings; using AcDream.App.Platform; using AcDream.App.World; using AcDream.Content; using AcDream.Platform; using AcDream.Runtime; using AcDream.Runtime.Entities; using AcDream.Runtime.Gameplay; using AcDream.Runtime.Session; using DatReaderWriter; using Silk.NET.Input; using Silk.NET.Maths; using Silk.NET.Windowing; namespace AcDream.App.Rendering; public sealed class GameWindow : IDisposable, IGameWindowPlatformPublication, IGameWindowHostInputCameraPublication, IGameWindowContentEffectsAudioPublication, IGameWindowWorldRenderPublication, IGameWindowInteractionRetainedUiPublication, IGameWindowLivePresentationPublication, IGameWindowSessionPlayerPublication, IGameWindowFrameRootPublication { private static double ClientTimerNow() => System.Diagnostics.Stopwatch.GetTimestamp() / (double)System.Diagnostics.Stopwatch.Frequency; private readonly AcDream.App.RuntimeOptions _options; // Campaign LA slice LA1: no-op instance when --session-config didn't // configure a statusFile (or the env-var launch path was used at all). private readonly SessionStatusWriter _statusWriter; private readonly AnimationPresentationDiagnostics _animationDiagnostics; private readonly string _datDir; private readonly WorldGameState _worldGameState; private readonly WorldEvents _worldEvents; private readonly HostQuiescenceGate _hostQuiescence = new(); private IWindow? _window; // #343: mirrors Silk's own ViewImplementationBase._inRenderLoop guard, // which we cannot read directly (it is a private field on Silk's // internal type). Set true before OnUpdate/OnRender run and cleared // only on their normal (non-throwing) return — exactly like Silk's own // bracket around DoUpdate/DoRender — so a frame callback that throws // leaves this stuck true, same as Silk's real guard. GameWindowLifetime // reads it before disposing the native window, so a wounded loop defers // the release instead of calling Reset() while Silk still thinks it is // mid-frame (which throws "You cannot call `Reset` inside of the render // loop!" and would otherwise bury whatever exception actually wounded // the loop). See docs/ISSUES.md #343. private bool _renderLoopArmed; private SilkWindowCallbackBinding? _windowCallbacks; private GameWindowGraphics? _graphics; // Campaign V slice V6h: borrowed, not owned — _graphics owns the context and // disposes it. Retained because the render callback has to bring the // swapchain up to date before a frame opens, which is the one piece of // presentation the RHI contract deliberately does not cover. private AcDream.App.Rendering.Gpu.Vk.VulkanGraphicsContext? _vulkanGraphics; private FramePacingPolicy _startupPacing; private AcDream.UI.Abstractions.Settings.QualitySettings _startupQuality = AcDream.UI.Abstractions.Settings.QualitySettings.From( AcDream.UI.Abstractions.Settings.QualityPreset.High); private IInputContext? _input; private TerrainModernRenderer? _terrain; private CameraController? _cameraController; private IDatReaderWriter? _dats; private IPreparedAssetSource? _preparedAssets; private readonly AcDream.App.Input.PointerPositionState _pointerPosition = new(); private AcDream.App.Input.CameraPointerInputController? _cameraPointerInput; private TextureCache? _textureCache; /// Phase N.4+: WB-backed rendering pipeline adapter. Always non-null /// after OnLoad completes (modern path is mandatory as of N.5). private AcDream.App.Rendering.Wb.WbMeshAdapter? _wbMeshAdapter; private AcDream.App.Rendering.Wb.EntitySpawnAdapter? _wbEntitySpawnAdapter; private AcDream.App.Rendering.Vfx.EntityScriptActivator? _entityScriptActivator; private RetailStaticAnimatingObjectScheduler? _staticAnimationScheduler; private RenderSceneShadowRuntime? _renderSceneShadow; private AcDream.App.Rendering.Wb.WbDrawDispatcher? _wbDrawDispatcher; private AcDream.App.Rendering.Selection.RetailSelectionScene? _retailSelectionScene; private AcDream.App.Interaction.WorldSelectionQuery? _worldSelectionQuery; private AcDream.App.Interaction.SelectionInteractionController? _selectionInteractions; private DebugLineRenderer? _debugLines; // K-fix4 (2026-04-26): default OFF. The orange BSP / green cylinder // wireframes are noisy outdoors and confuse first-time users into // thinking they're a rendering bug. Ctrl+F2 toggles, the DebugPanel // → Diagnostics → "Toggle collision wires" button toggles too. private readonly AcDream.App.Rendering.WorldSceneDebugState _worldSceneDebugState = new(); // Phase I.2: the old StbTrueTypeSharp DebugOverlay was deleted in favor of // the ImGui-backed DebugPanel, which Campaign V slice V11 removed in turn. // The TextRenderer + BitmapFont fields stay alive because they're shared // with UiHost and reserved for the future world-space HUD (D.6 — // damage floaters, name plates) where ImGui couldn't reach into the 3D // scene. They are no longer used for any debug overlay. private TextRenderer? _textRenderer; private BitmapFont? _debugFont; // [FRAME-DIAG]: retain only the render-thread entity upload distribution. // Landblock publication timing/counts live with the focused publishers and // are read from their typed snapshots when diagnostics flush. private readonly bool _frameDiag = string.Equals( System.Environment.GetEnvironmentVariable("ACDREAM_WB_DIAG"), "1", System.StringComparison.Ordinal); // MP0 (2026-07-05): permanent frame profiler — one FrameBoundary call // per OnRender + three stage scopes. All logic lives in // AcDream.App.Diagnostics.FrameProfiler (structure rule 1). private readonly AcDream.App.Diagnostics.FrameProfiler _frameProfiler = new(); private readonly AcDream.App.Rendering.IRenderFrameDiagnosticLog _renderDiagnosticLog = new AcDream.App.Rendering.ConsoleRenderFrameDiagnosticLog(); private readonly AcDream.App.Rendering.DebugVmRenderFactsPublisher _debugVmRenderFacts = new(); private AcDream.App.Rendering.RenderFrameDiagnosticsController? _renderFrameDiagnostics; private LivePresentationRuntimeBindings? _livePresentationBindings; private SessionPlayerRuntimeBindings? _sessionPlayerBindings; private AcDream.App.Diagnostics.FrameScreenshotController? _frameScreenshots; private FrameRootRuntimeBindings? _frameRootBindings; private IDisposable? _frameGraphPublication; private AcDream.App.Rendering.GpuFrameFlightController? _gpuFrameFlights; private IGpuDevice? _gpuDevice; private GpuDeviceFrameLifetime? _gpuFrameLifetime; private readonly AcDream.App.Rendering.GameFrameGraphSlot _frameGraphs = new(); private readonly AcDream.App.Rendering.GameRenderResourceLifetime _renderResourceLifetime = new(); private readonly AcDream.App.Rendering.ResourceConstructionCleanupLedger _constructionCleanup = new(); private readonly AcDream.App.World.WorldEnvironmentController _worldEnvironment; private readonly GameWindowLifetime _lifetime = new(); private readonly DisplayFramePacingController _displayFramePacing; private readonly RuntimeSettingsController _runtimeSettings; // Phase A.1: streaming fields replacing the one-shot _entities list. private AcDream.App.Streaming.LandblockStreamer? _streamer; private AcDream.App.Streaming.GpuWorldState _worldState = new(); private AcDream.App.Streaming.LandblockPresentationPipeline? _landblockPresentationPipeline; private AcDream.App.Rendering.EquippedChildRenderController? _equippedChildRenderer; private AcDream.App.Streaming.StreamingController? _streamingController; 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); // Phase B.3: physics engine — populated from the streaming pipeline. private AcDream.Core.Physics.PhysicsEngine _physicsEngine => _runtimeEntityObjects.Physics.Engine; // Task 4: physics data cache — BSP trees + collision shapes extracted from // GfxObj/Setup dats during streaming. Populated on the worker thread; // ConcurrentDictionary inside makes cross-thread access safe. private AcDream.Core.Physics.PhysicsDataCache _physicsDataCache => _runtimeEntityObjects.Physics.DataCache; /// /// TS-23 (Campaign P Slice P3, 2026-07-30): resolves a mover's own /// PK/PKLite/Impenetrable /// bits from its ClientObjectTable row — the same table CreateObject /// already populates PublicWeenieBitfield on (per-GUID, including /// every remote's own row, not just collision targets). /// private AcDream.Core.Physics.ObjectInfoState GetMoverPvpState(uint serverGuid) => AcDream.Core.Physics.EntityCollisionFlagsExt.ResolveMoverPvpState( _runtimeEntityObjects.Objects, serverGuid); // #184 Slice 2a: the per-remote dead-reckoning tick, extracted out of the // >10k-line TickAnimations (Code Structure Rule 1). Reusable setup/motion // policy is supplied through the once-bound live motion runtime, while the // stateless server-velocity cycle remains a focused Physics helper. private readonly AcDream.App.Physics.RemotePhysicsUpdater _remotePhysicsUpdater; private readonly AcDream.App.Physics.RemoteInboundMotionDispatcher _remoteInboundMotion; private readonly AcDream.App.World.RetailInboundEventDispatcher _inboundEntityEvents = new(); private LiveEntityAnimationScheduler _liveAnimationScheduler = null!; private LiveEntityAnimationPresenter _animationPresenter = null!; private readonly AcDream.App.Input.MovementTruthDiagnosticController _movementTruthDiagnostics; private readonly LocalPlayerOutboundController _localPlayerOutbound; private readonly AcDream.App.Update.UpdateFrameClock _updateFrameClock; // Step 7 projectile presentation. The controller owns no identity map; // each runtime component is stored on the canonical LiveEntityRecord. private AcDream.App.Physics.ProjectileController? _projectileController; private AcDream.App.World.LiveEntityProjectionWithdrawalController? _liveEntityProjectionWithdrawal; private AcDream.App.World.LiveEntityHydrationController? _liveEntityHydration; private AcDream.App.Physics.LiveEntityNetworkUpdateController? _liveEntityNetworkUpdates; private AcDream.App.Net.LiveEntitySessionController? _liveEntitySessionEvents; private readonly AcDream.App.Physics.DeferredLiveEntityMotionRuntimeBindings _liveEntityMotionBindings = new(); // Step 4: portal-based interior cell visibility. private readonly CellVisibility _cellVisibility = new(); // Phase A.1 hotfix / Phase A.5 T10: DatCollection is NOT thread-safe. // DatReaderWriter's DatBinReader uses a shared buffer position internally — // concurrent _dats.Get calls from the streaming worker thread (T11+) and // the render thread (LandblockBuildFactory on the worker; live-spawn // handlers + animation ticks on the render // thread) corrupt that state and produce half-populated LandBlock.Height[] // arrays, rendering as "a giant ball with spikes". All _dats.Get call // sites that can race with the streaming worker MUST hold this lock. // // Worker-thread DAT reads enter through LandblockBuildFactory.Build, which // holds _datLock for the complete CPU transaction. The terrain-mesh closure // consumes that completed heightmap and performs no DAT read. Render-thread // live hydration holds this lock via its outer wrapper; all remaining // render-thread // _dats.Get calls run only when no worker dat read can be in flight (during // initialization or within the same lock scope). private readonly object _datLock = new(); // Terrain build context shared by the off-thread mesh-build closure across // all streamed landblocks. private float[]? _heightTable; private AcDream.Core.Terrain.TerrainBlendingContext? _blendCtx; // Was: Dictionary. ConcurrentDictionary so the off-thread // mesh builder (A.5 T11+) can call LandblockMesh.Build without a lock. private System.Collections.Concurrent.ConcurrentDictionary? _surfaceCache; // Phase A8 (2026-05-28): WB EnvCellRenderManager port. Cells render // through this dedicated pipeline now, NOT through WbDrawDispatcher. // The dispatcher's IndoorPass still walks cell-static stabs (WorldEntity // records with real GfxObj MeshRefs); only the cell GEOMETRY (walls / // floors / ceilings) flows through here. private AcDream.App.Rendering.Wb.EnvCellRenderer? _envCellRenderer; private AcDream.App.Rendering.Wb.WbFrustum? _envCellFrustum; // R1 (render redesign): portal-view draw owners are retained transitively // by the frame orchestrator rather than duplicated as GameWindow roots. private AcDream.App.Rendering.PortalDepthMaskRenderer? _portalDepthMask; private readonly AcDream.App.Rendering.TransferableResourceSlot< AcDream.App.Rendering.PortalTunnelPresentation> _portalTunnelFallback = new(); // Phase U.3: the shared per-frame clip data (binding=2 mesh SSBO + terrain // UBO). In U.3 a single ClipFrame.NoClip() instance is created lazily (??=) and // REUSED across frames — its GL buffers persist; only the cheap CPU-side no-clip // state is re-uploaded each frame before terrain/entities draw, so the whole // scene renders ungated (identical to pre-U.3). The buffer ids are handed to the // three renderers so each re-binds binding=2 immediately before its own draw. // U.4 replaces the NoClip() frame with one built from the portal-visibility result. private ClipFrame? _clipFrame; /// /// Phase 6.4: per-entity animation playback state for entities whose /// MotionTable resolved to a real cycle. The render loop ticks each /// of these every frame, advances the current frame number, then /// rebuilds the entity's MeshRefs by re-flattening the Setup against /// the new . /// Static decorations and entities with no motion table never /// appear in this map. /// private readonly LiveEntityAnimationRuntimeView _animatedEntities; private readonly AcDream.App.World.LiveEntityRuntimeSlot _liveEntityRuntimeSlot = new(); /// /// Tier 1 cache (#53): per-entity classification results for static /// entities (those NOT in ). Conceptually /// paired with — that dictionary is the /// gating predicate, this cache is the lookup that depends on it. /// Passed to at /// construction time. Tasks 9-10 of the cache plan wire the per-entity /// miss-populate / hit-fast-path through the dispatcher's loop. /// private readonly AcDream.App.Rendering.Wb.EntityClassificationCache _classificationCache = new(); private AcDream.Core.Physics.IAnimationLoader? _animLoader; private AcDream.Runtime.Physics.LiveEntityCollisionBuilder? _liveEntityCollisionBuilder; // Phase E.1: central fan-out for animation hooks. Audio (E.2), // particles (E.3), combat (E.4), and renderer state mutators all // register sinks at startup. The router is always non-null so the // per-entity tick loop can just call it unconditionally. private readonly AcDream.Core.Physics.AnimationHookRouter _hookRouter = new(); private AcDream.App.Composition.AnimationHookRegistrationSet? _hookRegistrations; private readonly AcDream.App.Rendering.Vfx.DeferredEntityEffectAdvanceSource _entityEffectAdvance = new(); // Phase E.2 audio. Null when ACDREAM_NO_AUDIO=1 or the OpenAL driver // failed to init; all three are set together. private AcDream.App.Audio.OpenAlAudioEngine? _audioEngine; private AcDream.Core.Audio.DatSoundCache? _soundCache; private AcDream.App.Audio.DictionaryEntitySoundTable? _entitySoundTables; private AcDream.App.Audio.AudioHookSink? _audioSink; // Phase E.3 particles. private AcDream.Core.Vfx.EmitterDescRegistry? _emitterRegistry; private AcDream.Core.Vfx.ParticleSystem? _particleSystem; private AcDream.Core.Vfx.ParticleHookSink? _particleSink; private readonly AcDream.App.Rendering.Vfx.ParticleVisibilityController _particleVisibility = new(); private readonly AcDream.App.Rendering.Vfx.EntityEffectPoseRegistry _effectPoses = new(); private AcDream.App.Rendering.Vfx.AnimationHookFrameQueue? _animationHookFrames; // Phase 6 — retail PhysicsScript runtime. Receives PlayScript (0xF754) // and typed PlayScriptType (0xF755) events through one effect owner, then // fans every dat-defined hook to particles, audio, lights, translucency, // and nested/default-script routing at its StartTime offset. private AcDream.Core.Vfx.PhysicsScriptRunner? _scriptRunner; private AcDream.Content.Vfx.RetailPhysicsScriptLoader? _physicsScriptLoader; private AcDream.App.Rendering.Vfx.EntityEffectController? _entityEffects; private AcDream.App.Rendering.ParticleRenderer? _particleRenderer; private readonly AcDream.App.Rendering.RetailAlphaQueue _retailAlphaQueue; // Remote-entity motion inference: tracks when each remote entity last // moved meaningfully. Used in TickAnimations to swap to Ready when // position has stalled for >StopIdleMs — retail observer pattern per // ACE Player_Tick.cs line 368: the client never sends "released forward" // MoveToState, so the server never broadcasts an explicit stop. Observer // must infer it from position deltas. private readonly AcDream.App.Physics.RemoteMovementObservationTracker _remoteMovementObservations = new(); /// /// Per-remote-entity movement state for smoothing between server /// UpdatePosition broadcasts. Retail queues each received position and /// advances the body toward the queue head through /// InterpolationManager::adjust_offset every object tick. /// /// /// Each entry owns the retail interpolation queue, MovementManager, /// PhysicsBody, latest authoritative position, and the velocity estimate /// used only to select animation cycles for NPCs that receive no motion /// messages. Body translation consumes queue catch-up plus the literal /// root-motion Frame emitted by CSequence; it never guesses movement from /// a Walk/Run command or from packet cadence. /// /// /// /// L.2g S1 (DEV-6): per-entity inbound movement-event staleness gates, /// keyed by server guid. Retail keeps these stamps on CPhysicsObj /// (update_times); seeded from CreateObject's PhysicsDesc timestamp /// block during , consulted at the /// top of , and dropped by the live /// entity teardown owner. /// private AcDream.App.World.LiveEntityRuntime? _liveEntities; private AcDream.App.World.LiveEntityLivenessController? _liveEntityLiveness; // J4/J5.1: Runtime owns communication, selection, combat, and target-mode // state. App, UI, plugins, and live-session routing borrow exact children. private readonly GameRuntime _runtime; private readonly IDisposable _runtimeHostLease; private RuntimeCommunicationState _runtimeCommunication => _runtime.CommunicationOwner; private RuntimeActionState _runtimeActions => _runtime.ActionOwner; public AcDream.Core.Selection.SelectionState Selection => _runtimeActions.Selection; internal SessionStatusWriter StatusWriter => _statusWriter; public AcDream.Core.Chat.ChatLog Chat => _runtimeCommunication.Chat; public AcDream.Core.Chat.TurbineChatState TurbineChat => _runtimeCommunication.TurbineChat; public AcDream.Core.Social.FriendsState Friends => _runtimeCommunication.Friends; public AcDream.Core.Social.SquelchState Squelch => _runtimeCommunication.Squelch; public AcDream.Core.Combat.CombatState Combat => _runtimeActions.Combat; private RuntimeEntityObjectLifetime _runtimeEntityObjects => _runtime.EntityObjects; private RuntimeInventoryState _runtimeInventory => _runtime.InventoryOwner; private RuntimeCharacterState _runtimeCharacter => _runtime.CharacterOwner; public AcDream.Core.Items.ItemManaState ItemMana => _runtimeInventory.ItemMana; // Retail resolves learned IDs through portal.dat's CSpellTable. MagicCatalog // installs that immutable table once DatCollection opens in OnLoad. public AcDream.Core.Spells.SpellTable SpellTable => SpellBook.Metadata; public AcDream.Core.Spells.Spellbook SpellBook => _runtimeCharacter.Spellbook; /// Persisted hotbar shortcuts from the last PlayerDescription (D.5.1 toolbar source). public IReadOnlyList Shortcuts => _runtimeInventory.Shortcuts.Items; // Issue #5 — caches CreatureProfile.{Stamina, Mana, *Max} from // PlayerDescription so the Vitals HUD can render those bars. // Issue #6 — wired to SpellBook so GetMaxApprox folds enchantment // buffs into the max formula via Spellbook.GetVitalMod. public AcDream.Core.Player.LocalPlayerState LocalPlayer => _runtimeCharacter.LocalPlayer; // Phase D.2a — ImGui devtools UI overlay. Removed at Campaign V slice V11; // see docs/plans/2026-04-24-ui-framework.md for the staged UI strategy and // docs/plans/2026-07-27-vulkan-campaign.md for the removal. private AcDream.UI.Abstractions.Panels.Chat.ChatVM? _retailChatVm; // Phase D.2b — retained host + composition runtime. Null unless ACDREAM_RETAIL_UI=1. private AcDream.App.UI.UiHost? _uiHost; private AcDream.App.UI.RetailUiRuntime? _retailUiRuntime; private readonly AcDream.App.UI.RetailUiRuntimeLease _retailUiLease = new(); private InteractionUiLateBindings? _interactionUiLateBindings; private readonly DeferredRenderFrameDiagnosticsSource _uiFrameDiagnostics = new(); private readonly AcDream.App.Combat.CombatAttackOperationsSlot _combatAttackOperations = new(); private readonly AcDream.App.Combat.RuntimeCombatTargetOperationsSlot _combatTargetOperations = new(); private readonly AcDream.App.Combat.RuntimeCombatModeOperationsSlot _combatModeOperations = new(); private readonly AcDream.App.Spells.RuntimeSpellCastOperationsSlot _spellCastOperations = new(); private readonly AcDream.App.Combat.CombatFeedbackSlot _combatFeedback = new(); private RuntimeCombatAttackState? _combatAttackController; private AcDream.App.UI.ItemInteractionController? _itemInteractionController; private AcDream.App.World.ExternalContainerLifecycleController? _externalContainerLifecycle; private AcDream.App.Spells.MagicRuntime? _magicRuntime; private MagicCatalog? _magicCatalog; private readonly AcDream.Core.Items.StackSplitQuantityState _stackSplitQuantity = new(); // Phase D.2b Sub-phase C Slice 2 — the 3-D doll viewport: an off-screen RTT renderer, the UiViewport // widget that blits it, the inventory frame (for the open-gate), and a dirty flag (re-dress on 0xF625). private AcDream.App.Rendering.PaperdollViewportRenderer? _paperdollViewportRenderer; private AcDream.App.Rendering.PaperdollFramePresenter? _paperdollFramePresenter; private AcDream.App.Rendering.CreatureAppraisalViewportRenderer? _creatureAppraisalViewportRenderer; private AcDream.App.Rendering.CreatureAppraisalFramePresenter? _creatureAppraisalFramePresenter; // Campaign CC slice CC6b-MOUNT — the chargen Appearance-page preview, // same guard/shutdown shape as the paperdoll/creature-appraisal // viewports above. private AcDream.App.Rendering.ChargenPreviewRenderer? _chargenPreviewRenderer; private AcDream.App.Rendering.ChargenPreviewController? _chargenPreviewController; // Campaign CC slice CC5: the Summary page's own gmCG3DView instance — // a SEPARATE renderer/controller pair from the Appearance preview above. private AcDream.App.Rendering.ChargenPreviewRenderer? _summaryPreviewRenderer; private AcDream.App.Rendering.ChargenPreviewController? _summaryPreviewController; // Phase D.2b Task 9 — plugin UI registrations buffered before OnLoad; drained in OnLoad. private readonly AcDream.App.Plugins.BufferedUiRegistry? _uiRegistry; private AcDream.App.Plugins.GraphicalPluginSession? _pluginSession; // Campaign V slice V11 deleted the ImGui developer-tools frontend along // with the OpenGL backend it required, so no host ever composes a // developer UI regardless of ACDREAM_DEVTOOLS. The flag still reaches // VulkanGraphicsContext, where it selects the optional debug-utils // instance/device extensions — see the ACDREAM_DEVTOOLS log line in // Run() below, which is the one remaining observable effect of the flag. private const bool DevToolsEnabled = false; // Phase G.1-G.2 world lighting/time state. The environment owner keeps // the clock, selected day group, and weather transitions coherent. public readonly AcDream.Core.World.WorldTimeService WorldTime; public readonly AcDream.Core.Lighting.LightManager Lighting = new(); public readonly AcDream.Core.World.WeatherSystem Weather; // Wired into the hook router in OnLoad so SetLightHook fires // from the animation pipeline flip the matching LightSource.IsLit. private AcDream.Core.Lighting.LightingHookSink? _lightingSink; private AcDream.App.Rendering.Vfx.LiveEntityLightController? _liveEntityLights; private AcDream.App.World.LiveEntityPresentationController? _liveEntityPresentation; // #188 — TransparentPartHook fires from the animation pipeline drive // a per-(entity,part) translucency ramp; WbDrawDispatcher reads it // per frame. Wired into the hook router in OnLoad, advanced once per // frame in the main loop regardless of _animatedEntities membership // (a fade must keep running even if its entity's one-shot cycle ends). private readonly AcDream.Core.Rendering.TranslucencyFadeManager _translucencyFades = new(); private AcDream.Core.Rendering.TranslucencyHookSink? _translucencySink; // Phase G.1 sky renderer + shared UBO. Created once the GL context // exists in OnLoad; shared across every other renderer via // binding = 1 so terrain/mesh/instanced/sky all read the same // sun / ambient / fog / flash data per frame. private AcDream.App.Rendering.SceneLightingUboBinding? _sceneLightingUbo; private AcDream.App.Rendering.Sky.SkyRenderer? _skyRenderer; // Phase B.2: player movement mode. private RuntimeLocalPlayerMovementState _playerControllerSlot => _runtime.MovementOwner; private PlayerMovementController? _playerController => _playerControllerSlot.Controller; private readonly AcDream.App.Input.ChaseCameraInputState _chaseCameraInput = new(); private AcDream.App.Rendering.ChaseCamera? _chaseCamera => _chaseCameraInput.Legacy; private AcDream.App.Rendering.RetailChaseCamera? _retailChaseCamera => _chaseCameraInput.Retail; private readonly AcDream.App.Input.LocalPlayerModeState _localPlayerMode = new(); private bool _playerMode => _localPlayerMode.IsPlayerMode; private readonly AcDream.App.Input.LocalPlayerIdentityState _localPlayerIdentity; private uint _playerServerGuid { get => _localPlayerIdentity.ServerGuid; set => _localPlayerIdentity.ServerGuid = value; } private readonly AcDream.App.Physics.LocalPlayerShadowState _localPlayerShadow = new(); private readonly AcDream.App.Input.ViewportAspectState _viewportAspect = new(); private readonly FramebufferResizeController _framebufferResize; private AcDream.App.Input.PlayerModeController? _playerModeController; private readonly AcDream.App.Interaction.PlayerApproachCompletionState _playerApproachCompletions = new(); private AcDream.App.Input.LocalPlayerAnimationController? _localPlayerAnimation; private AcDream.App.Physics.LocalPlayerShadowSynchronizer? _localPlayerShadowSynchronizer; // Phase D.2b-C — live character-sheet assembly + raise flow (extracted // feature class; GameWindow only wires it). Null unless ACDREAM_RETAIL_UI=1. private AcDream.App.UI.Layout.CharacterSheetProvider? _characterSheetProvider; // Phase K.2 — auto-enter player mode after a successful login. Armed // by LiveSessionHost's entered-world transition; ticked from // OnUpdate; disarmed if the user manually enters fly mode (or any // other path that pre-empts the chase camera). Skipped entirely // offline (orbit camera stays the foreground). The class internally // tracks IsArmed; we read it via the guard rather than mirroring // the bool here. private AcDream.App.Input.PlayerModeAutoEntry? _playerModeAutoEntry; // Phase K.1b / Slice 8 F — one semantic input path. Transitional actions // flow through GameplayInputActionRouter; held movement is polled through // InputDispatcher. Raw axis motion belongs to CameraPointerInputController. private AcDream.App.Input.SilkKeyboardSource? _kbSource; private AcDream.App.Input.SilkMouseSource? _mouseSource; private AcDream.UI.Abstractions.Input.InputDispatcher? _inputDispatcher; private readonly AcDream.App.Input.RetainedUiInputCaptureSlot _retainedInputCapture; private readonly AcDream.App.Input.CompositeInputCaptureSource _inputCapture; private readonly AcDream.App.Input.DispatcherMovementInputSource _movementInput; private readonly AcDream.App.Input.DispatcherCameraInputSource _cameraInput = new(); private AcDream.App.Input.IMouseLookCursor? _mouseLookCursor; private AcDream.App.Input.GameplayInputFrameController? _gameplayInputFrame; private AcDream.App.Input.GameplayInputActionRouter? _gameplayInputActions; private AcDream.App.Input.RetainedUiGameplayBinding? _retainedUiGameplayBinding; private readonly AcDream.App.Diagnostics.RuntimeDiagnosticCommandSlot _runtimeDiagnosticCommands = new(); private readonly AcDream.App.Combat.LiveCombatModeCommandSlot _liveCombatModeCommands = new(); // K.1c/L0: load user-customized bindings from the canonical per-platform // configuration path, // falling back to the retail-faithful defaults if the file is missing // or corrupt. This is THE single source of truth for the keymap at // startup — no other call to RetailDefaults() / AcdreamCurrentDefaults() // should land in the GameWindow construction path. private readonly AcDream.UI.Abstractions.Input.KeyBindings _keyBindings; private readonly GraphicalHostPlatformServices _platformServices; private readonly ApplicationPathSet _applicationPaths; private static AcDream.UI.Abstractions.Input.KeyBindings LoadStartupKeyBindings( string path) { var bindings = AcDream.UI.Abstractions.Input.KeyBindings.LoadOrDefault(path); Console.WriteLine($"keybinds: loaded {bindings.All.Count} bindings from {path}"); return bindings; } // Phase 4.7: optional live connection to an ACE server. Enabled only when // ACDREAM_LIVE=1 is in the environment — fully backward compatible with // the offline rendering pipeline. // Runtime owns the canonical session generation and transport lifetime. // The window retains only the composition handles needed for startup and // shutdown; graphical feature callbacks borrow state through App adapters. private LiveSessionHost? _liveSessionHost; private AcDream.Core.Net.WorldSession? LiveSession => _liveSessionHost?.CurrentSession; private readonly AcDream.App.World.LiveWorldOriginState _liveWorldOrigin = new(); private int _liveCenterX => _liveWorldOrigin.CenterX; private int _liveCenterY => _liveWorldOrigin.CenterY; // K-fix1 (2026-04-26): cached at startup so per-frame branches are // single-flag reads instead of env-var lookups. True iff // ACDREAM_LIVE=1 was set when the window came up. // Backed by RuntimeOptions.LiveMode via the _options field. /// /// Phase 6.6/6.7: server-guid → local WorldEntity lookup so /// UpdateMotion and UpdatePosition handlers can find the entity the /// server is talking about. This is the canonical materialized top-level /// projection, including live objects parked in pending landblocks; /// visibility must never suppress authoritative mutation. /// private static readonly IReadOnlyDictionary EmptyLiveSpawnMap = new Dictionary(); /// /// Latest for each /// guid. Captured before the renderability gate so no-position inventory / /// parented children retain Setup and parent metadata; hydration rereads /// this canonical snapshot after every relationship callback. /// reuses the cached /// position/setup/motion fields when a 0xF625 ObjDescEvent carries only /// updated visuals. /// private IReadOnlyDictionary LastSpawns => _liveEntities?.Snapshots ?? EmptyLiveSpawnMap; // R5-V2: the local player's CPhysicsObj stand-in — owns the player's // TargetManager voyeur system, stored on the exact live record so remote // entities chasing the player resolve it. Replaces the AP-79 // _playerMoveToTarget* poll fields. private readonly AcDream.App.Input.LocalPlayerPhysicsHostSlot _playerHostSlot = new(); private EntityPhysicsHost? _playerHost => _playerHostSlot.Host; // R5-V2: guid → per-entity IPhysicsObjHost registry (retail's // CObjectMaint::GetObjectA lookup). Backs every host's GetObjectA seam, // giving the TargetManager voyeur round-trip its cross-entity delivery // path. Populated for remotes plus the PlayerModeController local entry // (player); pruned only by logical LiveEntityRuntime teardown. // ServerControlledVelocityStaleSeconds moved to RemotePhysicsUpdater (#184 // Slice 2a — the DR tick's stale-velocity anim-stop was its only user). public GameWindow( AcDream.App.RuntimeOptions options, WorldGameState worldGameState, WorldEvents worldEvents, AcDream.App.Plugins.BufferedUiRegistry? uiRegistry = null) : this( options, worldGameState, worldEvents, uiRegistry, GraphicalHostPlatformServices.Resolve()) { } internal GameWindow( AcDream.App.RuntimeOptions options, WorldGameState worldGameState, WorldEvents worldEvents, AcDream.App.Plugins.BufferedUiRegistry? uiRegistry, GraphicalHostPlatformServices platformServices) { _options = options ?? throw new System.ArgumentNullException(nameof(options)); _statusWriter = new SessionStatusWriter(options.StatusFilePath); _platformServices = platformServices ?? throw new ArgumentNullException(nameof(platformServices)); _applicationPaths = _platformServices.Paths; _keyBindings = LoadStartupKeyBindings( _applicationPaths.KeyBindingsFile); _runtime = new GameRuntime(new GameRuntimeDependencies( _combatAttackOperations, _combatTargetOperations, _combatModeOperations, _spellCastOperations, Log: Console.WriteLine, TimeSyncDiagnostic: options.DumpSky ? Console.WriteLine : null)); _runtimeHostLease = _runtime.AcquireHostLease( "graphical GameWindow"); _localPlayerIdentity = new AcDream.App.Input.LocalPlayerIdentityState( _runtime.PlayerIdentity); _updateFrameClock = new AcDream.App.Update.UpdateFrameClock( _runtime.Clock); _worldEnvironment = new AcDream.App.World.WorldEnvironmentController( _runtime.EnvironmentOwner, options.ForcedDayGroupIndex, Console.WriteLine, options.PinnedWorldDayFraction); var alphaScratchBudgets = AcDream.App.Rendering.Residency.AlphaScratchBudgetProfile.Create( _options.ResidencyBudgets.AlphaScratchBytes); _retailAlphaQueue = new AcDream.App.Rendering.RetailAlphaQueue( alphaScratchBudgets.QueueBytes); WorldTime = _worldEnvironment.WorldTime; Weather = _worldEnvironment.Weather; // Campaign OP slice OP4 (2026-08-11): PlayerOption // DisableDistanceFog, polled — see WeatherSystem. // DisableDistanceFogSource's doc comment. Bound once here (not // per-session): _runtime.CharacterOwner is the ONE canonical // RuntimeCharacterOptionsState instance for this GameRuntime's // whole lifetime, reused (not replaced) across reconnects. Weather.DisableDistanceFogSource = () => _runtime.CharacterOwner.Options.GetOptionBit( AcDream.Core.Net.Messages.CharacterOptionId.DisableDistanceFog); // Campaign OP slice OP4 (2026-08-11): PlayerOption // DisplayTimeStamps, polled — see RuntimeCommunicationState. // DisplayTimestampsSource's doc comment. Same one-time, // whole-lifetime bind as the two above. _runtime.CommunicationOwner.DisplayTimestampsSource = () => _runtime.CharacterOwner.Options.GetOptionBit( AcDream.Core.Net.Messages.CharacterOptionId.DisplayTimeStamps); _retainedInputCapture = new AcDream.App.Input.RetainedUiInputCaptureSlot(); _inputCapture = new AcDream.App.Input.CompositeInputCaptureSource( new AcDream.App.Input.DevToolsInputCaptureSource(options.DevTools), _retainedInputCapture); _movementInput = new AcDream.App.Input.DispatcherMovementInputSource( _playerControllerSlot, _inputCapture); // Campaign OP slice OP4 (2026-08-11): retail PlayerOption id 0xA // (acclient.h's ToggleRun_PlayerOption — ACE's name for the same // bit is RunAsDefaultMovement; N7, OP4 review-fix round // 2026-08-11), polled — see RuntimeLocalPlayerMovementState. // RunAsDefaultMovementSource's doc comment. Same one-time, // whole-lifetime bind as DisableDistanceFogSource above. _playerControllerSlot.RunAsDefaultMovementSource = () => _runtime.CharacterOwner.Options.GetOptionBit( AcDream.Core.Net.Messages.CharacterOptionId.ToggleRun); _framebufferResize = new FramebufferResizeController(_viewportAspect); _datDir = options.DatDir; _worldGameState = worldGameState; _worldEvents = worldEvents; _displayFramePacing = new DisplayFramePacingController( options.UncappedRendering, _frameProfiler, _platformServices.FramePacingWaiters); _runtimeSettings = new RuntimeSettingsController( new JsonRuntimeSettingsStorage( _applicationPaths.SettingsFile), log: Console.WriteLine); _animationDiagnostics = AnimationPresentationDiagnostics.FromEnvironment(); _uiRegistry = uiRegistry; _animatedEntities = new LiveEntityAnimationRuntimeView( _liveEntityRuntimeSlot); // #184 Slice 2a: the extracted per-remote DR tick. Its stateful // setup/motion dependency crosses the fail-fast construction bridge; // the server-velocity animation cycle is stateless shared policy. _remotePhysicsUpdater = new AcDream.App.Physics.RemotePhysicsUpdater( _runtimeEntityObjects.Physics, _liveEntityMotionBindings.GetSetupCylinder, _liveEntityMotionBindings.GetSetupMoverShape, AcDream.App.Physics.RemoteServerControlledVelocityCycle.Apply, GetMoverPvpState); _remoteInboundMotion = new AcDream.App.Physics.RemoteInboundMotionDispatcher( (movement, cellId, update) => _liveEntityMotionBindings.RouteServerMoveTo( movement, cellId, update), (host, targetGuid) => _liveEntityMotionBindings.StickToObjectFromWire(host, targetGuid)); _movementTruthDiagnostics = new AcDream.App.Input.MovementTruthDiagnosticController( options.DumpMoveTruth, _playerControllerSlot, _localPlayerIdentity); _localPlayerOutbound = new LocalPlayerOutboundController( _movementTruthDiagnostics); } /// /// Transfers the graphical plugin lifetime into the window shutdown graph /// and starts it before retained UI construction drains registrations. /// internal void StartPluginHosting( AcDream.App.Plugins.GraphicalPluginSession pluginSession) { ArgumentNullException.ThrowIfNull(pluginSession); if (_pluginSession is not null) { throw new InvalidOperationException( "The graphical plugin session is already attached."); } _pluginSession = pluginSession; pluginSession.Start(); } public void Run() { _platformServices.ConfigureWindowBackend(); // The runtime-settings owner was constructed before Window.Create and // exposes the one immutable startup snapshot. MSAA is a context // attribute, so it must come from this same snapshot rather than a // second settings load during OnLoad. RuntimeSettingsSnapshot startup = _runtimeSettings.Startup; if (_options.VulkanCapabilityProbe) { // Campaign V slice V6h reduced the V5 bring-up host to what its name // says: a capability probe. It opens its own window, runs the gate, // presents the synthetic V6c/V6d verification scenes, and exits — // useful for answering "does this machine pass the Vulkan gate, and // does the backend draw?" without the client. The production Vulkan // path is the composition host below. using var probe = new AcDream.App.Rendering.Gpu.Vk.VulkanBringUpHost( _options, _platformServices, startup.Display.VSync); probe.Run(); return; } FramePacingPolicy startupPacing = _displayFramePacing.InitializeStartup(startup.Display.VSync); // Vulkan needs a client-API-less window (the surface comes from // VK_KHR_surface); neither MSAA nor the stencil bit count is a window // attribute there — both are attachment properties the RHI device // configures instead. The raw-GL window options this used to fork to // were deleted at Campaign V slice V11. var options = WindowOptions.DefaultVulkan with { Size = new Vector2D(1280, 720), Title = "acdream — Vulkan", VSync = startupPacing.UseVSync, }; _startupPacing = startupPacing; _startupQuality = startup.Quality; _window = Window.Create(options); IWindow window = _window; _lifetime.PublishNativeWindow( window, isRenderLoopArmed: () => _renderLoopArmed, requestClose: window.Close); _displayFramePacing.BindSurface( new SilkDisplayFramePacingSurface(_window)); // The fixed binding preserves main Render before post-render pacing, // owns rollback/reverse-detach, and gates every native entry point. _windowCallbacks = SilkWindowCallbackBinding.Create( _window, new WindowCallbackTargets( OnLoad, OnUpdate, OnRender, OnClosing, OnFocusChanged, OnFramebufferResize), _displayFramePacing, _hostQuiescence); _windowCallbacks.Attach(); try { _window.Run(); } catch (Exception failure) { _constructionCleanup.RetainFrom(failure); throw; } } void IGameWindowPlatformPublication.PublishGraphics( GameWindowGraphics graphics) => PublishCompositionOwner(ref _graphics, graphics, "graphics API"); void IGameWindowPlatformPublication.PublishInput( IInputContext input) => PublishCompositionOwner(ref _input, input, "input context"); void IGameWindowHostInputCameraPublication.PublishGpuFrameFlights( GpuFrameFlightController? value) { // Null on a backend whose RHI device owns its own frame flights. if (value is not null) PublishCompositionOwner(ref _gpuFrameFlights, value, "GPU frame flights"); } void IGameWindowHostInputCameraPublication.PublishGpuDevice( IGpuDevice value) => PublishCompositionOwner(ref _gpuDevice, value, "GPU device (RHI)"); void IGameWindowHostInputCameraPublication.PublishGpuFrameLifetime( GpuDeviceFrameLifetime value) => PublishCompositionOwner(ref _gpuFrameLifetime, value, "GPU frame lifetime"); void IGameWindowHostInputCameraPublication.PublishKeyboardSource( AcDream.App.Input.SilkKeyboardSource value) => PublishCompositionOwner(ref _kbSource, value, "keyboard source"); void IGameWindowHostInputCameraPublication.PublishMouseSource( AcDream.App.Input.SilkMouseSource value) => PublishCompositionOwner(ref _mouseSource, value, "mouse source"); void IGameWindowHostInputCameraPublication.PublishMouseLookCursor( AcDream.App.Input.IMouseLookCursor value) => PublishCompositionOwner(ref _mouseLookCursor, value, "mouse-look cursor"); void IGameWindowHostInputCameraPublication.PublishInputDispatcher( AcDream.UI.Abstractions.Input.InputDispatcher value) => PublishCompositionOwner(ref _inputDispatcher, value, "input dispatcher"); void IGameWindowHostInputCameraPublication.PublishCameraController( CameraController value) => PublishCompositionOwner(ref _cameraController, value, "camera controller"); void IGameWindowHostInputCameraPublication.PublishCameraPointerInput( AcDream.App.Input.CameraPointerInputController value) => PublishCompositionOwner(ref _cameraPointerInput, value, "camera pointer input"); void IGameWindowContentEffectsAudioPublication.PublishDatCollection( IDatReaderWriter value) => PublishCompositionOwner(ref _dats, value, "DAT collection"); void IGameWindowContentEffectsAudioPublication.PublishPreparedAssetSource( IPreparedAssetSource value) => PublishCompositionOwner( ref _preparedAssets, value, "prepared asset source"); void IGameWindowContentEffectsAudioPublication.PublishMagicCatalog( MagicCatalog value) => PublishCompositionOwner(ref _magicCatalog, value, "magic catalog"); void IGameWindowContentEffectsAudioPublication.PublishAnimationLoader( AcDream.Core.Physics.IAnimationLoader value) => PublishCompositionOwner(ref _animLoader, value, "animation loader"); void IGameWindowContentEffectsAudioPublication.PublishLiveEntityCollisionBuilder( AcDream.Runtime.Physics.LiveEntityCollisionBuilder value) => PublishCompositionOwner( ref _liveEntityCollisionBuilder, value, "live-entity collision builder"); void IGameWindowContentEffectsAudioPublication.PublishEmitterRegistry( AcDream.Core.Vfx.EmitterDescRegistry value) => PublishCompositionOwner(ref _emitterRegistry, value, "emitter registry"); void IGameWindowContentEffectsAudioPublication.PublishParticleSystem( AcDream.Core.Vfx.ParticleSystem value) => PublishCompositionOwner(ref _particleSystem, value, "particle system"); void IGameWindowContentEffectsAudioPublication.PublishParticleSink( AcDream.Core.Vfx.ParticleHookSink value) => PublishCompositionOwner(ref _particleSink, value, "particle hook sink"); void IGameWindowContentEffectsAudioPublication.PublishAnimationHookFrames( AcDream.App.Rendering.Vfx.AnimationHookFrameQueue value) => PublishCompositionOwner( ref _animationHookFrames, value, "animation-hook frame queue"); void IGameWindowContentEffectsAudioPublication.PublishPhysicsScriptLoader( AcDream.Content.Vfx.RetailPhysicsScriptLoader value) => PublishCompositionOwner( ref _physicsScriptLoader, value, "physics-script loader"); void IGameWindowContentEffectsAudioPublication.PublishPhysicsScriptRunner( AcDream.Core.Vfx.PhysicsScriptRunner value) => PublishCompositionOwner(ref _scriptRunner, value, "physics-script runner"); void IGameWindowContentEffectsAudioPublication.PublishLightingSink( AcDream.Core.Lighting.LightingHookSink value) => PublishCompositionOwner(ref _lightingSink, value, "lighting hook sink"); void IGameWindowContentEffectsAudioPublication.PublishTranslucencySink( AcDream.Core.Rendering.TranslucencyHookSink value) => PublishCompositionOwner( ref _translucencySink, value, "translucency hook sink"); void IGameWindowContentEffectsAudioPublication.PublishHookRegistrations( AcDream.App.Composition.AnimationHookRegistrationSet value) => PublishCompositionOwner( ref _hookRegistrations, value, "animation-hook registrations"); void IGameWindowContentEffectsAudioPublication.PublishAudio( ContentAudioGraph value) { ArgumentNullException.ThrowIfNull(value); if (_soundCache is not null || _audioEngine is not null || _entitySoundTables is not null || _audioSink is not null) { throw new InvalidOperationException( "The GameWindow composition shell already owns audio state."); } _soundCache = value.SoundCache; _audioEngine = value.Engine; _entitySoundTables = value.EntitySoundTables; _audioSink = value.HookSink; } void IGameWindowWorldRenderPublication.PublishSceneLighting( SceneLightingUboBinding value) => PublishCompositionOwner( ref _sceneLightingUbo, value, "scene lighting"); void IGameWindowWorldRenderPublication.PublishDebugLines( DebugLineRenderer value) => PublishCompositionOwner(ref _debugLines, value, "debug lines"); void IGameWindowWorldRenderPublication.PublishHudResources( BitmapFont font, TextRenderer text) { ArgumentNullException.ThrowIfNull(font); ArgumentNullException.ThrowIfNull(text); if (_debugFont is not null || _textRenderer is not null) { throw new InvalidOperationException( "The GameWindow composition shell already owns world HUD resources."); } _debugFont = font; _textRenderer = text; } void IGameWindowWorldRenderPublication.PublishTerrain( TerrainModernRenderer value) => PublishCompositionOwner(ref _terrain, value, "terrain renderer"); void IGameWindowWorldRenderPublication.PublishTerrainBuildState( float[] heightTable, AcDream.Core.Terrain.TerrainBlendingContext blending, System.Collections.Concurrent.ConcurrentDictionary< uint, AcDream.Core.Terrain.SurfaceInfo> surfaceCache) { ArgumentNullException.ThrowIfNull(heightTable); ArgumentNullException.ThrowIfNull(blending); ArgumentNullException.ThrowIfNull(surfaceCache); if (_heightTable is not null || _blendCtx is not null || _surfaceCache is not null) { throw new InvalidOperationException( "The GameWindow composition shell already owns terrain build state."); } _heightTable = heightTable; _blendCtx = blending; _surfaceCache = surfaceCache; } void IGameWindowWorldRenderPublication.PublishWbMeshAdapter( WbMeshAdapter value) => PublishCompositionOwner(ref _wbMeshAdapter, value, "WB mesh adapter"); void IGameWindowWorldRenderPublication.PublishTextureCache( TextureCache value) => PublishCompositionOwner(ref _textureCache, value, "texture cache"); void IGameWindowInteractionRetainedUiPublication.PublishInteractionRetainedUi( InteractionRetainedUiResult result) { ArgumentNullException.ThrowIfNull(result); if (_combatAttackController is not null || _externalContainerLifecycle is not null || _itemInteractionController is not null || _interactionUiLateBindings is not null || _uiHost is not null || _retailUiRuntime is not null || _retailChatVm is not null || _characterSheetProvider is not null || _magicRuntime is not null || _frameScreenshots is not null) { throw new InvalidOperationException( "The GameWindow composition shell already owns interaction/UI state."); } _combatAttackController = result.CombatAttack; _externalContainerLifecycle = result.ExternalContainerLifecycle; _itemInteractionController = result.ItemInteraction; _interactionUiLateBindings = result.LateBindings; _magicRuntime = result.Magic; if (result.RetainedUi is { } retained) { _uiHost = retained.Host; _retailUiRuntime = retained.Runtime; _retailChatVm = retained.Chat; _characterSheetProvider = retained.CharacterSheet; _frameScreenshots = retained.Screenshots; // #348: cursor switches ride a process-lifetime native cache // instead of Silk's recreate-per-assignment path. retained.Runtime.AttachNativeCursorWindow(_window?.Native?.Glfw ?? 0); } } void IGameWindowLivePresentationPublication.PublishLivePresentation( LivePresentationResult result) { ArgumentNullException.ThrowIfNull(result); if (_liveEntities is not null || _wbEntitySpawnAdapter is not null || _entityScriptActivator is not null || _staticAnimationScheduler is not null || _renderSceneShadow is not null || _wbDrawDispatcher is not null || _retailSelectionScene is not null || _worldSelectionQuery is not null || _selectionInteractions is not null || _retainedUiGameplayBinding is not null || _equippedChildRenderer is not null || _entityEffects is not null || _liveEntityPresentation is not null || _projectileController is not null || _liveEntityProjectionWithdrawal is not null || _liveEntityLights is not null || _liveAnimationScheduler is not null || _animationPresenter is not null || _paperdollViewportRenderer is not null || _paperdollFramePresenter is not null || _creatureAppraisalViewportRenderer is not null || _creatureAppraisalFramePresenter is not null || _chargenPreviewRenderer is not null || _chargenPreviewController is not null || _envCellRenderer is not null || _envCellFrustum is not null || _landblockPresentationPipeline is not null || _portalDepthMask is not null || _clipFrame is not null || _skyRenderer is not null || _particleRenderer is not null || _renderFrameDiagnostics is not null || _livePresentationBindings is not null) { throw new InvalidOperationException( "The GameWindow composition shell already owns live presentation state."); } _wbEntitySpawnAdapter = result.EntitySpawnAdapter; _entityScriptActivator = result.EntityScriptActivator; _staticAnimationScheduler = result.StaticAnimationScheduler; _worldState = result.WorldState; _renderSceneShadow = result.RenderSceneShadow; _liveEntities = result.LiveEntities; _projectileController = result.ProjectileController; _liveEntityProjectionWithdrawal = result.ProjectionWithdrawal; _liveEntityLights = result.Lights; _liveAnimationScheduler = result.AnimationScheduler; _animationPresenter = result.AnimationPresenter; _equippedChildRenderer = result.EquippedChildren; _entityEffects = result.EntityEffects; _liveEntityPresentation = result.Presentation; _wbDrawDispatcher = result.DrawDispatcher; _retailSelectionScene = result.SelectionScene; _worldSelectionQuery = result.SelectionQuery; _selectionInteractions = result.SelectionInteractions; _retainedUiGameplayBinding = result.RetainedGameplay; _paperdollViewportRenderer = result.PaperdollRenderer; _paperdollFramePresenter = result.PaperdollPresenter; _creatureAppraisalViewportRenderer = result.CreatureAppraisalRenderer; _creatureAppraisalFramePresenter = result.CreatureAppraisalPresenter; _chargenPreviewRenderer = result.ChargenPreviewRenderer; _chargenPreviewController = result.ChargenPreviewController; _summaryPreviewRenderer = result.SummaryPreviewRenderer; _summaryPreviewController = result.SummaryPreviewController; _envCellFrustum = result.EnvCellFrustum; _envCellRenderer = result.EnvCellRenderer; _landblockPresentationPipeline = result.LandblockPipeline; _clipFrame = result.ClipFrame; _portalDepthMask = result.PortalDepthMask; _skyRenderer = result.SkyRenderer; _particleRenderer = result.ParticleRenderer; _renderFrameDiagnostics = result.FrameDiagnostics; _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 || _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 || _liveSessionHost is not null || _gameplayInputActions 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; _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; _liveSessionHost = result.SessionHost; _gameplayInputActions = result.GameplayActions; _sessionPlayerBindings = result.RuntimeBindings; } void IGameWindowFrameRootPublication.PublishFrameRoots( FrameRootResult result) { ArgumentNullException.ThrowIfNull(result); if (_frameRootBindings is not null || _frameGraphPublication is not null) { throw new InvalidOperationException( "The GameWindow composition shell already owns frame roots."); } _frameRootBindings = result.RuntimeBindings; _frameGraphPublication = result.FrameGraphPublication; } private static void PublishCompositionOwner( ref T? destination, T value, string name) where T : class { ArgumentNullException.ThrowIfNull(value); if (destination is not null) throw new InvalidOperationException( $"The GameWindow composition shell already owns {name}."); destination = value; } [System.Diagnostics.CodeAnalysis.MemberNotNull(nameof(_graphics), nameof(_input))] private GameWindowPlatformResult AcquirePlatform() { GameWindowPlatformResult platform = GameWindowPlatformAcquisition.Acquire( CreateGraphics, static graphics => graphics.Dispose(), () => _window!.CreateInput(), static input => input.Dispose(), this); if (_graphics is null || _input is null) throw new InvalidOperationException( "Platform acquisition returned without publishing both owners."); return platform; } /// /// How the UI probe reads a completed frame. /// is documented top-left-origin, /// so flips it to match the PNG's /// row order. The raw-GL read this used to fork from — whose rows already /// come out bottom-up, needing no flip — was deleted at Campaign V slice /// V11. /// private static Func CreateBackbufferReader( GameWindowGraphics graphics, IGpuDevice device) => (width, height) => AcDream.App.Diagnostics.FrameScreenshotController.FlipRows( device.CaptureBackbuffer(width, height), width, height); /// /// Vulkan's context acquisition runs its own capability gate inside /// , /// throwing into the same exit-code-4 /// contract Program.cs publishes. The raw-GL fork this used to make /// was deleted at Campaign V slice V11. /// private GameWindowGraphics CreateGraphics() { AcDream.App.Rendering.Gpu.Vk.VulkanGraphicsContext vulkan = AcDream.App.Rendering.Gpu.Vk.VulkanGraphicsContext.Acquire( _window!, _options, _platformServices, _startupPacing, _startupQuality.MsaaSamples, Console.WriteLine); _vulkanGraphics = vulkan; return new VulkanGameWindowGraphics(vulkan); } private void OnLoad() { // Task 7: wire the physics data cache into the engine so Transition can // run narrow-phase BSP tests during FindObjCollisions. GameWindowPlatformResult platform = AcquirePlatform(); // The raw-GL capability gate that used to run here (reading GL // extension strings) was deleted at Campaign V slice V11. Vulkan's // equivalent gate already ran inside VulkanGraphicsContext.Acquire and // wrote its own report. // #391: capture the monitor's curated resolution catalog once, on the // windowing thread, before any Options-panel mount reads it. DisplayModeCatalog.InstallFromWindow(_window!); GameWindowCompositionPipeline.Run< GameWindowPlatformResult, HostInputCameraResult, ContentEffectsAudioResult, SettingsDevToolsResult, WorldRenderResult, InteractionRetainedUiResult, LivePresentationResult, SessionPlayerResult, FrameRootResult>( platform, platformResult => new HostInputCameraCompositionPhase( new HostInputCameraDependencies( _framebufferResize, _window!.FramebufferSize, _hostQuiescence, _inputCapture, _keyBindings, _movementInput, _cameraInput, _localPlayerMode, _chaseCameraInput, _pointerPosition, _renderDiagnosticLog), this).Compose(platformResult), (platformResult, hostInputCamera) => new ContentEffectsAudioCompositionPhase( new ContentEffectsAudioDependencies( _datDir, _options.PreparedAssetPath, _options.ResidencyBudgets, _physicsDataCache, _animationDiagnostics.DumpMotionEnabled, _runtime, _hookRouter, _effectPoses, _entityEffectAdvance, Lighting, _translucencyFades, _options.NoAudio, Console.WriteLine, Console.Error.WriteLine), this).Compose(platformResult, hostInputCamera), (platformResult, hostInputCamera, contentEffectsAudio) => new SettingsDevToolsCompositionPhase( new SettingsDevToolsDependencies( _runtimeSettings, new RuntimeSettingsStartupTargets( new SilkRuntimeDisplayWindowTarget(_window!), _displayFramePacing, hostInputCamera.CameraController, contentEffectsAudio.Audio?.Engine))) .Compose(platformResult, hostInputCamera, contentEffectsAudio), (platformResult, contentEffectsAudio, settingsDevTools) => { const uint initialCenterLandblockId = 0xA9B4FFFFu; WorldRenderResult worldRender = new WorldRenderCompositionPhase( new WorldRenderDependencies( _worldEnvironment, _renderResourceLifetime, // Campaign V slice V6h: the device's own retirement queue // rather than the GL frame-flight controller directly. On // GL these are the same object (GlGpuDevice.Retirement // returns the controller it was constructed with); on // Vulkan the device owns its timeline-backed queue. _gpuDevice!.Retirement, _options.ResidencyBudgets, initialCenterLandblockId, _applicationPaths.DiagnosticsDirectory, Console.WriteLine, _gpuDevice!, _gpuFrameLifetime!), this).Compose(platformResult, contentEffectsAudio, settingsDevTools); Console.WriteLine( $"loading world view centered on " + $"0x{worldRender.TerrainBuild.InitialCenterLandblockId:X8}"); return worldRender; }, (platformResult, hostInputCamera, contentEffectsAudio, settingsDevTools, worldRender) => { // The ImGui developer-tools debug toast sink was removed at // Campaign V slice V11 along with the rest of the ImGui // frontend; there is no replacement toast surface yet. Action? compositionToast = null; return new InteractionRetainedUiCompositionPhase( new InteractionRetainedUiDependencies( _options, platformResult.Graphics, CreateBackbufferReader( platformResult.Graphics, hostInputCamera.GpuDevice), _window!, platformResult.Input, worldRender.Foundation.ShadersDirectory, contentEffectsAudio.Dats, _datLock, worldRender.Foundation.TextureCache, worldRender.Foundation.DebugFont, _hostQuiescence, _retainedInputCapture, hostInputCamera.InputDispatcher, _applicationPaths.KeyBindingsFile, _runtimeSettings, _runtime, _combatAttackOperations, _combatTargetOperations, _spellCastOperations, contentEffectsAudio.MagicCatalog, _stackSplitQuantity, _uiRegistry, _liveCombatModeCommands, _localPlayerIdentity, _localPlayerMode, viewPlane => new SelectionCameraSource( hostInputCamera.CameraController, _window!, viewPlane), _uiFrameDiagnostics, ExistingVitals: null, compositionToast, ClientTimerNow, Console.WriteLine, hostInputCamera.GpuDevice, hostInputCamera.GpuFrameLifetime), _retailUiLease, this).Compose( platformResult, hostInputCamera, contentEffectsAudio, settingsDevTools, worldRender); }, (platformResult, hostInputCamera, contentEffectsAudio, settingsDevTools, worldRender, interactionUi) => { // The ImGui developer-tools debug toast sink was removed at // Campaign V slice V11 along with the rest of the ImGui // frontend; there is no replacement toast surface yet. Action? compositionToast = null; return new LivePresentationCompositionPhase( new LivePresentationDependencies( _options, platformResult.Graphics, _window!, _datLock, _runtimeSettings, _hostQuiescence, _physicsEngine, _physicsDataCache, _worldGameState, _worldEvents, _runtime, _liveEntityRuntimeSlot, _liveEntityMotionBindings, _entityEffectAdvance, _effectPoses, _remotePhysicsUpdater, _localPlayerShadow, _animatedEntities, _animationDiagnostics, _classificationCache, _translucencyFades, _retailAlphaQueue, _cellVisibility, _liveWorldOrigin, _localPlayerIdentity, _pointerPosition, _playerApproachCompletions, _renderResourceLifetime, _portalTunnelFallback, _hookRouter, _renderDiagnosticLog, WorldTime, DevWorldEntities: null, DevFrameDiagnostics: null, _uiFrameDiagnostics, Console.WriteLine, compositionToast), this).Compose( platformResult, hostInputCamera, contentEffectsAudio, settingsDevTools, worldRender, interactionUi); }, (hostInputCamera, contentEffectsAudio, settingsDevTools, worldRender, interactionUi, livePresentation) => new SessionPlayerCompositionPhase( new SessionPlayerDependencies( _options, _runtime, _window!, _datLock, _runtimeSettings, settingsDevTools, _worldEnvironment, _worldSceneDebugState, _runtimeDiagnosticCommands, _liveCombatModeCommands, _combatModeOperations, _hostQuiescence, _physicsEngine, _physicsDataCache, _worldGameState, _worldEvents, _classificationCache, _liveEntityRuntimeSlot, _animatedEntities, _remoteMovementObservations, _remotePhysicsUpdater, _remoteInboundMotion, _inboundEntityEvents, _liveEntityMotionBindings, _localPlayerIdentity, _playerHostSlot, _localPlayerMode, _chaseCameraInput, _localPlayerOutbound, _localPlayerTeleportSink, _liveWorldOrigin, _renderRange, _localPlayerShadow, _viewportAspect, _playerApproachCompletions, _pointerPosition, _movementInput, _inputCapture, _particleVisibility, _translucencyFades, _effectPoses, _updateFrameClock, _movementTruthDiagnostics, _combatAttackOperations, _combatFeedback, _portalTunnelFallback, Console.WriteLine, _statusWriter), this).Compose( hostInputCamera, contentEffectsAudio, settingsDevTools, worldRender, interactionUi, livePresentation), (platformResult, hostInputCamera, contentEffectsAudio, settingsDevTools, worldRender, interactionUi, livePresentation, sessionPlayer) => new FrameRootCompositionPhase( new FrameRootDependencies( _options, _runtime, platformResult.Graphics, _window!, platformResult.Input, WorldTime, Weather, Lighting, _worldEnvironment, _physicsEngine, _cellVisibility, _localPlayerMode, _localPlayerIdentity, _chaseCameraInput, _liveWorldOrigin, _particleVisibility, _effectPoses, _renderRange, _runtimeSettings, _displayFramePacing, _worldSceneDebugState, _retailAlphaQueue, _frameProfiler, _frameDiag, _renderDiagnosticLog, _debugVmRenderFacts, _inputCapture, _cameraInput, _animatedEntities, _updateFrameClock, _frameGraphs, Console.WriteLine), this).Compose( platformResult, hostInputCamera, contentEffectsAudio, settingsDevTools, worldRender, interactionUi, livePresentation, sessionPlayer), frameRoots => new SessionStartCompositionPhase( new SessionStartDependencies( Console.WriteLine)) .Start(frameRoots)); } private void OnUpdate(double dt) { // #343: armed before the callback body runs, cleared only on normal // return. Deliberately NOT a try/finally — if Tick throws, the flag // must stay true (mirroring Silk's own stuck _inRenderLoop guard), // not get cleared on the way out. _renderLoopArmed = true; using var _updStage = _frameProfiler.BeginStage( AcDream.App.Diagnostics.FrameStage.Update); _frameGraphs.Tick(new AcDream.App.Update.UpdateFrameInput(dt)); _renderLoopArmed = false; } // Performance overlay state — updated every ~0.5s and written to the // window title so there's zero rendering cost (no font/overlay needed). private void OnRender(double deltaSeconds) { // #343: see OnUpdate above — armed on entry, cleared on every normal // exit path below, left stuck true if anything here throws. _renderLoopArmed = true; Vector2D size = _window!.Size; // Campaign V slice V6h: swapchain currency is the one piece of // presentation the RHI contract deliberately leaves to the host (plan // §4.9). A minimised window has no swapchain to render into; an // out-of-date one is recreated at a frame boundary, the only safe point. // The handoff below stays exactly one call on both backends. if (_vulkanGraphics is { } vulkan && !vulkan.PrepareFrame()) { _renderLoopArmed = false; return; } try { _frameGraphs.Render( new AcDream.App.Rendering.RenderFrameInput( deltaSeconds, size.X, size.Y), out _); } catch (AcDream.App.Rendering.Gpu.Vk.VulkanSwapchainOutOfDateException) when (_vulkanGraphics is not null) { _vulkanGraphics.RequestRecreate(); _renderLoopArmed = false; return; } _vulkanGraphics?.NoteFrameClosed(); _renderLoopArmed = false; } // IsEntityCurrentlyMoving REMOVED (2026-07-09): it powered a cache-bypass // narrowing that dropped settled-open doors / faded-out walls back onto the // stale Tier-1 rest-pose cache entry (the door/fade "flip-back"). See the // animatedIds build site — every Sequencer entity is now added // unconditionally, which is the known-good pre-optimization behavior. // R3-W6: UpdatePlayerAnimation DELETED — the player's sequencer is // driven through the SAME MotionTableDispatchSink/DefaultSink funnel // remotes use (edge-driven DoMotion/StopMotion/set_hold_run in // PlayerMovementController; airborne-Falling falls out of // contact_allows_move + apply_current_movement). The #45 sidestep // 1.248x factor + ACDREAM_ANIM_SPEED_SCALE died with it — // EXPECTED-DIFF: local sidestep pacing now matches how remotes have // always played (w6-cutover-map.md R3). /// /// L.0 Display tab: framebuffer-resize handler — update GL viewport /// + camera aspect when the window is resized (by the user dragging /// the corner OR by the runtime display target applying a saved /// Resolution). Without this, the viewport stays pinned at the /// startup size, producing a small render inside a big window. /// Also force-resets ImGui panel layout so panels that were /// previously off the new viewport snap back to default positions. /// private void OnFramebufferResize(Silk.NET.Maths.Vector2D newSize) => _framebufferResize.Resize(newSize); private void OnClosing() => CompleteShutdown(releaseNativeWindow: false); private void CompleteShutdown(bool releaseNativeWindow) { if (!_lifetime.HasShutdownRoots) { // Campaign LA slice LA1: capture BEFORE the shutdown roots run — // by the time teardown completes, IsInWorld is always false // regardless of whether a real session was ever connected. // OnClosing() and Dispose() both funnel through this method; // HasShutdownRoots's own guard means this fires exactly once, // from whichever of the two reaches it first. if (_runtime.Session.IsInWorld) _statusWriter.Disconnected(_options.SessionId ?? "app", "stopped"); _lifetime.PublishShutdownRoots(CaptureShutdownRoots()); } GameWindowLifetimeReport report = releaseNativeWindow ? _lifetime.CompleteAndReleaseNativeWindow() : _lifetime.TryComplete(); if (report.Status == GameWindowLifetimeStatus.Complete) { // "exited" = terminal — only the true Dispose() call (not the // OnClosing() native-window-close-request pass) represents the // process actually being done. if (releaseNativeWindow) _statusWriter.Exited(_options.SessionId ?? "app", 0, "graceful"); return; } Console.Error.WriteLine( $"[shutdown] status={report.Status}, blocked={report.BlockedStage ?? "none"}"); foreach (ResourceShutdownCleanupFailure cleanup in report.CleanupFailures) { Console.Error.WriteLine( $"[shutdown] cleanup '{cleanup.Operation}' in '{cleanup.Stage}': " + cleanup.Error); } if (report.Error is not null) Console.Error.WriteLine($"[shutdown] {report.Error}"); if (releaseNativeWindow) { _statusWriter.Exited( _options.SessionId ?? "app", 1, "shutdown-incomplete"); } } private GameWindowShutdownRoots CaptureShutdownRoots() => new( new IngressShutdownRoots( _hostQuiescence, _liveCombatModeCommands, _runtimeDiagnosticCommands, _retainedUiGameplayBinding, _gameplayInputActions, _cameraPointerInput, _inputDispatcher, _mouseSource, _kbSource, _retailUiLease, _uiHost, _pluginSession, _runtime, _movementInput, _cameraInput, _windowCallbacks), new FrameShutdownRoots( _frameGraphPublication, _frameRootBindings, _sessionPlayerBindings, _interactionUiLateBindings), new LiveShutdownRoots( _cameraPointerInput, _retailUiLease, _magicRuntime, _itemInteractionController, _externalContainerLifecycle, _streamer, _equippedChildRenderer, _liveEntities, _runtime, _runtimeHostLease, _renderSceneShadow, _livePresentationBindings, _entityEffectAdvance, _entityEffects, _hookRegistrations, _liveEntityLights, _liveEntityPresentation, _animationHookFrames, _effectPoses, _audioEngine), new RenderShutdownRoots( _gpuFrameFlights, _gpuDevice, _localPlayerTeleport, _portalTunnelFallback, _paperdollViewportRenderer, _creatureAppraisalViewportRenderer, _chargenPreviewRenderer, _chargenPreviewController, _summaryPreviewRenderer, _summaryPreviewController, _wbDrawDispatcher, _envCellRenderer, _portalDepthMask, _clipFrame, _skyRenderer, _particleRenderer, _textureCache, _wbMeshAdapter, _terrain, _sceneLightingUbo, _debugLines, _textRenderer, _debugFont, _displayFramePacing, _frameProfiler, _renderResourceLifetime, _constructionCleanup), new PlatformShutdownRoots( _dats, _preparedAssets, _input, _graphics)); private void OnFocusChanged(bool focused) => _cameraPointerInput?.HandleFocusChanged(focused); public void Dispose() { // Closing is the normal path and runs while the render context is // current. This direct call also covers a constructed-but-never-run // window and exceptions during Window.Create/Run, so owned kernel // handles (including the frame timer) never depend on a native window // event for disposal. CompleteShutdown(releaseNativeWindow: true); _window = null; } }