diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 9029284c7..a71310eb6 100644 Binary files a/.voidbot/state/aetheria.cc and b/.voidbot/state/aetheria.cc differ diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 2eef7a521..4276dd340 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -21,6 +21,7 @@ RequireWeaponGroupCommitAuthority(root); RequireInventoryDoubleClickTransferCommitAuthority(root); RequireTradePurchaseCommitAuthority(root); RequireInventoryLoadoutRestoreCommitAuthority(root); +RequireDockedCurrentShipCommitAuthority(root); await using var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-state-verify"); @@ -471,6 +472,7 @@ Console.WriteLine("Weapon group authority: UI assignment flows through gameplay Console.WriteLine("Inventory transfer authority: UI transfer and drag/drop requests flow through gameplay checkpoint commits"); Console.WriteLine("Trade purchase authority: UI buy requests flow through gameplay checkpoint commits"); Console.WriteLine("Inventory loadout restore authority: UI restore requests flow through gameplay checkpoint commits"); +Console.WriteLine("Docked current-ship authority: UI selection requests flow through gameplay checkpoint commits"); static void RequireCount(AetheriaMigrationLedger ledger, string documentType, int actual) { @@ -1124,6 +1126,58 @@ static void RequireInventoryLoadoutRestoreCommitAuthority(string root) } } +static void RequireDockedCurrentShipCommitAuthority(string root) +{ + var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); + var actionGameManager = File.Exists(actionGameManagerPath) + ? File.ReadAllText(actionGameManagerPath) + : throw new InvalidOperationException("Cannot verify docked current-ship authority; ActionGameManager.cs is missing."); + + var requiredSymbols = new[] + { + "CommitDockedCurrentShip", + "QueueRunCheckpoint(\"docked-current-ship\")", + "DockingBay.DockedShip = ship" + }; + + var missingSymbols = requiredSymbols + .Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + + if (missingSymbols.Length > 0) + { + throw new InvalidOperationException( + "Docked current-ship selection no longer has a gameplay-owned checkpoint commit primitive: " + + string.Join(", ", missingSymbols)); + } + + var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs"); + var forbiddenSymbols = new[] + { + "GameManager.CurrentEntity =", + "GameManager.DockingBay.DockedShip =" + }; + + var hits = File.ReadLines(inventoryPanelPath) + .Select((line, index) => new { LineNumber = index + 1, Line = line }) + .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) + .Select(line => $"{Path.GetRelativePath(root, inventoryPanelPath)}:{line.LineNumber}: {line.Line.Trim()}") + .ToArray(); + + if (hits.Length > 0) + { + throw new InvalidOperationException( + "InventoryPanel still owns direct docked current-ship mutation: " + + string.Join("; ", hits)); + } + + var inventoryPanel = File.ReadAllText(inventoryPanelPath); + if (!inventoryPanel.Contains("GameManager.CommitDockedCurrentShip", StringComparison.Ordinal)) + { + throw new InvalidOperationException("InventoryPanel no longer routes current-ship selection through ActionGameManager."); + } +} + static string ExtractMethodBody(string source, string methodName) { var signature = "private void " + methodName + "()"; diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index d5449e5b8..93c00996b 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -1336,6 +1336,23 @@ public class ActionGameManager : MonoBehaviour return true; } + public bool CommitDockedCurrentShip(Ship ship) + { + if (ship == null || + !ship.IsPlayerShip || + DockedEntity == null || + DockingBay == null || + !DockedEntity.Children.Contains(ship)) + { + return false; + } + + CurrentEntity = ship; + DockingBay.DockedShip = ship; + QueueRunCheckpoint("docked-current-ship"); + return true; + } + private static AetheriaRuntimeLoadoutTemplateSnapshot CreateRuntimeLoadoutTemplateSnapshot( AetheriaRuntimeLoadoutTemplateCommit template) { diff --git a/Assets/Scripts/UI/Menu/InventoryPanel.cs b/Assets/Scripts/UI/Menu/InventoryPanel.cs index 02b331af4..e929132e3 100644 --- a/Assets/Scripts/UI/Menu/InventoryPanel.cs +++ b/Assets/Scripts/UI/Menu/InventoryPanel.cs @@ -119,9 +119,8 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler { if(_displayedEntity is Ship ship) { - GameManager.CurrentEntity = ship; - GameManager.DockingBay.DockedShip = ship; - Current.targetGraphic.color = ToggleEnabledColor; + if (GameManager.CommitDockedCurrentShip(ship)) + Current.targetGraphic.color = ToggleEnabledColor; } else { diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index b4b311a33..8d67c313f 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -1406,6 +1406,10 @@ First Aetheria surfaces to publish: cargo-to-cargo, cargo-to-equipment, equipment-to-cargo, or equipment-to-equipment movement and then refresh display. uGUI still owns the presentation shell for now, not the inventory mutation. + - Done: route docked current-ship selection through + `ActionGameManager.CommitDockedCurrentShip`. `InventoryPanel` may request + a selected docked player ship and update the button color, but gameplay + owns `CurrentEntity`, `DockingBay.DockedShip`, and the typed checkpoint. - Replace the next concrete uGUI screen with an Eve-owned surface. - Move the staged packages into the Eve repo once its worktree is clean, then import them back into Aetheria from Eve instead of carrying a local copy. @@ -1733,7 +1737,9 @@ cargo/equipment movement, gameplay owns the move and queues the checkpoint. Trade purchases follow the same rule: UI requests the purchase, gameplay owns credit changes, cargo transfer, ship creation, and checkpoint. Runtime loadout restore follows the same rule: UI requests restoration, gameplay owns -instantiation, credits, dock assignment, current entity, and checkpoint. Any +instantiation, credits, dock assignment, current entity, and checkpoint. Docked +current-ship selection follows the same rule: UI requests selection, gameplay +owns `CurrentEntity`, `DockingBay.DockedShip`, and checkpoint. Any predicate that still needs legacy DTO objects must earn that dependency by using behavior objects or simulation-only methods that typed facets do not yet expose.