diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 4e2c76ba7..c53499f75 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -91,6 +91,7 @@ RequireDroppedPickupCheckpointState(root); RequireTradePurchaseRequestAuthority(root); RequireInventoryProjectionSlotIdentity(root); RequireInventoryValidationUsesManagedTypedDocuments(root); +RequireMenuDockingUsesManagedTypedSnapshot(root); RequireInventoryLoadoutSaveRequestAuthority(root); RequireInventoryLoadoutRestoreRequestAuthority(root); RequireDockedCurrentShipRequestAuthority(root); @@ -4670,7 +4671,7 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root) "new AetheriaRuntimeMenuTabProjectionOption(", "ResolveVisibleTabs(", ".DockingState", - ".Latest()", + ".TryLatest(", "ResolveCurrentDocking()?.IsDocked == true", "AetheriaClient", "AetheriaUnityRuntimeClientProvider.ResolveClient(", @@ -4786,7 +4787,7 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root) "AetheriaRuntimeLocalStoryCommandKind.Choose", "new AetheriaRuntimeLocalStoryChoiceState(", ".DockingState", - ".Latest()", + ".TryLatest(", "unity-runtime-local-story" }; var dockedStoryObserverCorpus = localMenu; @@ -4905,7 +4906,7 @@ static void RequireInventoryShipSettingsUseEveSurface(string root) "latestCurrentEntity.EntityKey", "(float)latestCurrentEntity.ShutdownPerformance", ".DockingState", - ".Latest()", + ".TryLatest(", "?.CurrentEntity" }; @@ -5616,7 +5617,7 @@ static void RequireTradeCargoSelectorUseEveSurface(string root) "AetheriaRuntimeTradeCargoSelectorCommandKind.Close", "AetheriaRuntimeTradeCargoSelectorCommandKind.Select", ".DockingState", - ".Latest()", + ".TryLatest(", "SetTargetCargo(", "selection.EntityKey", "OwnedQuantity", @@ -6249,7 +6250,7 @@ static void RequireInventoryDropdownUseEveSurface(string root) if (!inventoryMenu.Contains("TryResolveCurrentEntity(out var currentEntity)", StringComparison.Ordinal) || !inventoryMenu.Contains("TryResolveCurrentDockingBay(out var dockingBay)", StringComparison.Ordinal) || !inventoryMenu.Contains(".DockingState", StringComparison.Ordinal) || - !inventoryMenu.Contains(".Latest()", StringComparison.Ordinal) || + !inventoryMenu.Contains(".TryLatest(", StringComparison.Ordinal) || !inventoryMenu.Contains("TryResolveObservedDockingIndex(out var dockingIndex)", StringComparison.Ordinal) || !inventoryMenu.Contains("dockingIndex.TryResolveCurrentDockingBay(out var resolvedDockingBay)", StringComparison.Ordinal) || inventoryMenu.Contains("TryGetTypedCurrentDockingBayFacade", StringComparison.Ordinal) || @@ -6889,6 +6890,70 @@ static void RequireInventoryValidationUsesManagedTypedDocuments(string root) Console.WriteLine("Inventory validation: cargo/equipment checks read managed typed client documents instead of manual handle walks"); } +static void RequireMenuDockingUsesManagedTypedSnapshot(string root) +{ + var clientStatePath = Path.Combine( + root, + "Packages", + "org.gamecult.aetheria.state", + "Runtime", + "AetheriaClientState.cs"); + if (!File.Exists(clientStatePath)) + throw new InvalidOperationException("Cannot verify managed docking snapshot access; AetheriaClientState.cs is missing."); + + var clientState = File.ReadAllText(clientStatePath); + if (!clientState.Contains("public bool TryLatest(out AetheriaClientDockingSnapshot", StringComparison.Ordinal) || + !clientState.Contains("snapshot = Latest();", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "AetheriaClientDockingState must expose a managed typed safe latest snapshot read for Unity menu surfaces."); + } + + var menuPaths = new[] + { + Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"), + Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs"), + Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "LocalMenu.cs"), + Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MenuPanel.cs"), + Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs") + }; + + var missingPaths = menuPaths.Where(path => !File.Exists(path)).ToArray(); + if (missingPaths.Length > 0) + { + throw new InvalidOperationException( + "Cannot verify managed docking snapshot menu access; missing sources: " + + string.Join(", ", missingPaths.Select(path => Path.GetRelativePath(root, path)))); + } + + var offenders = menuPaths + .Select(path => new + { + Path = path, + Source = File.ReadAllText(path) + }) + .Select(entry => new + { + entry.Path, + entry.Source, + Compact = CompactSource(entry.Source) + }) + .Where(entry => + !entry.Source.Contains(".DockingState.TryLatest(", StringComparison.Ordinal) || + entry.Compact.Contains(CompactSource(".DockingState.Latest()"), StringComparison.Ordinal)) + .Select(entry => Path.GetRelativePath(root, entry.Path)) + .ToArray(); + + if (offenders.Length > 0) + { + throw new InvalidOperationException( + "Unity menu docking state must read through AetheriaClientDockingState.TryLatest instead of local latest wrappers: " + + string.Join(", ", offenders)); + } + + Console.WriteLine("Menu docking state: Unity menus read managed typed docking snapshots through AetheriaClientDockingState.TryLatest"); +} + static void RequireVerseHostSettingsAuthority(string root) { var settingsPath = Path.Combine(root, "Aetheria.State", "Documents", "AetheriaVerseHostSettings.cs"); diff --git a/Assets/Scripts/UI/Menu/InventoryMenu.cs b/Assets/Scripts/UI/Menu/InventoryMenu.cs index 612b38b14..ec37e5335 100644 --- a/Assets/Scripts/UI/Menu/InventoryMenu.cs +++ b/Assets/Scripts/UI/Menu/InventoryMenu.cs @@ -737,22 +737,14 @@ public class InventoryMenu : MonoBehaviour private bool TryResolveCurrentEntityDocument(out AetheriaRuntimeCurrentEntityDocument currentEntity) { - currentEntity = null; - try + currentEntity = ResolveDockingState()?.CurrentEntity; + if (currentEntity == null) { - currentEntity = ResolveClient() - .Aetheria() - .DockingState - .Latest() - ?.CurrentEntity; - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to read Aetheria current entity for inventory ship settings: {ex.Message}"); + Debug.LogWarning("Failed to read Aetheria current entity for inventory ship settings."); return false; } - return currentEntity != null; + return true; } private AetheriaRuntimeStationRefitDocument ResolveStationRefit() @@ -782,18 +774,11 @@ public class InventoryMenu : MonoBehaviour private AetheriaClientDockingSnapshot ResolveDockingState() { - try - { - return ResolveClient() - .Aetheria() - .DockingState - .Latest(); - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to read Aetheria docking state for inventory menu: {ex.Message}"); - return null; - } + if (ResolveClient().Aetheria().DockingState.TryLatest(out var docking)) + return docking; + + Debug.LogWarning("Failed to read Aetheria docking state for inventory menu."); + return null; } private bool TryResolveObservedDockingIndex(out AetheriaUnityObservedDockingIndex dockingIndex) diff --git a/Assets/Scripts/UI/Menu/InventoryPanel.cs b/Assets/Scripts/UI/Menu/InventoryPanel.cs index 335f593db..f40038e69 100644 --- a/Assets/Scripts/UI/Menu/InventoryPanel.cs +++ b/Assets/Scripts/UI/Menu/InventoryPanel.cs @@ -1162,18 +1162,11 @@ private void Update() private AetheriaClientDockingSnapshot ResolveDockingState() { - try - { - return ResolveClient() - .Aetheria() - .DockingState - .Latest(); - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to read Aetheria docking state for inventory panel: {ex.Message}"); - return null; - } + if (ResolveClient().Aetheria().DockingState.TryLatest(out var docking)) + return docking; + + Debug.LogWarning("Failed to read Aetheria docking state for inventory panel."); + return null; } private bool TryResolveObservedDockingIndex(out AetheriaUnityObservedDockingIndex dockingIndex) diff --git a/Assets/Scripts/UI/Menu/LocalMenu.cs b/Assets/Scripts/UI/Menu/LocalMenu.cs index 915d71c0f..d4a783580 100644 --- a/Assets/Scripts/UI/Menu/LocalMenu.cs +++ b/Assets/Scripts/UI/Menu/LocalMenu.cs @@ -203,22 +203,10 @@ public class LocalMenu : MonoBehaviour private bool TryResolveDockingState(out AetheriaClientDockingSnapshot dockingState) { - dockingState = null; - - try - { - dockingState = ResolveClient() - .Aetheria() - .DockingState - .Latest(); - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to read Aetheria docking state for local story surface: {ex.Message}"); - return false; - } - - return dockingState != null; + var resolved = ResolveClient().Aetheria().DockingState.TryLatest(out dockingState); + if (!resolved) + Debug.LogWarning("Failed to read Aetheria docking state for local story surface."); + return resolved; } private bool TryResolveDockedLocalStory(out LocationStory story) diff --git a/Assets/Scripts/UI/Menu/MenuPanel.cs b/Assets/Scripts/UI/Menu/MenuPanel.cs index f10080271..22cfd1236 100644 --- a/Assets/Scripts/UI/Menu/MenuPanel.cs +++ b/Assets/Scripts/UI/Menu/MenuPanel.cs @@ -167,18 +167,11 @@ public class MenuPanel : MonoBehaviour private AetheriaClientDockingSnapshot ResolveCurrentDocking() { - try - { - return ResolveClient() - .Aetheria() - .DockingState - .Latest(); - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to read Aetheria current docking for runtime menu tabs: {ex.Message}"); - return null; - } + if (ResolveClient().Aetheria().DockingState.TryLatest(out var docking)) + return docking; + + Debug.LogWarning("Failed to read Aetheria current docking for runtime menu tabs."); + return null; } private AetheriaClient ResolveClient() diff --git a/Assets/Scripts/UI/Menu/TradeMenu.cs b/Assets/Scripts/UI/Menu/TradeMenu.cs index 763cf987d..e0f31de8e 100644 --- a/Assets/Scripts/UI/Menu/TradeMenu.cs +++ b/Assets/Scripts/UI/Menu/TradeMenu.cs @@ -539,20 +539,13 @@ public class TradeMenu : MonoBehaviour if (_dockingState != null) return _dockingState; - try + if (!ResolveClient().Aetheria().DockingState.TryLatest(out _dockingState)) { - _dockingState = ResolveClient() - .Aetheria() - .DockingState - .Latest(); - _stationRefit = _dockingState?.StationRefit; - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to read Aetheria docking state for trade menu: {ex.Message}"); + Debug.LogWarning("Failed to read Aetheria docking state for trade menu."); return null; } + _stationRefit = _dockingState?.StationRefit; return _dockingState; } diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs index 198c3cfc2..2a1a3b846 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs @@ -347,6 +347,20 @@ namespace GameCult.Aetheria.State.Verse return LatestAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } + public bool TryLatest(out AetheriaClientDockingSnapshot? snapshot) + { + snapshot = null; + try + { + snapshot = Latest(); + return snapshot != null; + } + catch + { + return false; + } + } + private static AetheriaClientDockingSnapshot CreateSnapshot( AetheriaRuntimeCurrentEntityDocument? entity, AetheriaRuntimeCurrentDockingDocument? docking,