diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 81eea9c0b..f7ef0a864 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 286e66a15..8823ca077 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -12,6 +12,7 @@ RequirePackageSerializerBoundary(root); RequireEveRuntimeBootstrap(root); RequireNoRendererLocalConsole(root); RequireNoRendererLocalDebugPanels(root); +RequireMainMenuSettingsCommit(root); await using var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-state-verify"); @@ -453,6 +454,7 @@ Console.WriteLine("Package serializer boundary: MessagePack symbols remain in na Console.WriteLine("Eve runtime bootstrap: operations surface mounts through UI Toolkit presenter"); Console.WriteLine("Renderer-local console authority: deleted; UI commands flow through Eve command documents"); Console.WriteLine("Renderer-local debug panels: obsolete uGUI field tester authority is deleted"); +Console.WriteLine("Main-menu settings authority: gameplay and graphics settings return through typed player-settings commits"); static void RequireCount(AetheriaMigrationLedger ledger, string documentType, int actual) { @@ -684,6 +686,64 @@ static void RequireNoRendererLocalDebugPanels(string root) } } +static void RequireMainMenuSettingsCommit(string root) +{ + var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"); + if (!File.Exists(mainMenuPath)) + { + throw new InvalidOperationException("Cannot verify main-menu settings commit path; MainMenu.cs is missing."); + } + + var source = File.ReadAllText(mainMenuPath); + if (!source.Contains("private void CommitRuntimeSettingsAndReturn()", StringComparison.Ordinal) || + !source.Contains("ActionGameManager.QueueRuntimePlayerSettingsCommit();", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "MainMenu no longer exposes a shared typed player-settings commit return path."); + } + + var graphicsSettings = ExtractMethodBody(source, "ShowGraphicsSettings"); + if (!graphicsSettings.Contains("CommitRuntimeSettingsAndReturn", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "MainMenu graphics settings can return without queuing the typed player-settings commit."); + } + + var gameplaySettings = ExtractMethodBody(source, "ShowGameplaySettings"); + if (!gameplaySettings.Contains("CommitRuntimeSettingsAndReturn", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "MainMenu gameplay settings can return without queuing the typed player-settings commit."); + } +} + +static string ExtractMethodBody(string source, string methodName) +{ + var signature = "private void " + methodName + "()"; + var signatureIndex = source.IndexOf(signature, StringComparison.Ordinal); + if (signatureIndex < 0) + throw new InvalidOperationException($"Cannot find MainMenu.{methodName} for settings authority verification."); + + var braceIndex = source.IndexOf('{', signatureIndex); + if (braceIndex < 0) + throw new InvalidOperationException($"Cannot find MainMenu.{methodName} body for settings authority verification."); + + var depth = 0; + for (var index = braceIndex; index < source.Length; index++) + { + if (source[index] == '{') + depth++; + else if (source[index] == '}') + { + depth--; + if (depth == 0) + return source.Substring(braceIndex, index - braceIndex + 1); + } + } + + throw new InvalidOperationException($"MainMenu.{methodName} body is not balanced."); +} + static bool ContainsBehaviorValueKind(AetheriaBehaviorValue value, string kind) { return string.Equals(value.Kind, kind, StringComparison.OrdinalIgnoreCase) || diff --git a/Assets/Scripts/UI/MainMenu.cs b/Assets/Scripts/UI/MainMenu.cs index 9e5fd64f3..9c624146c 100644 --- a/Assets/Scripts/UI/MainMenu.cs +++ b/Assets/Scripts/UI/MainMenu.cs @@ -222,12 +222,7 @@ public class MainMenu : MonoBehaviour () => ActionGameManager.RuntimePlayerSettings.GameplaySettings.SignificantDigits, i => ActionGameManager.RuntimePlayerSettings.GameplaySettings.SignificantDigits = i); _nextMenu.panel.AddButton("Back", - () => - { - ActionGameManager.QueueRuntimePlayerSettingsCommit(); - ShowSettings(); - Fade(false); - }); + CommitRuntimeSettingsAndReturn); } private void ShowGraphicsSettings() @@ -246,11 +241,7 @@ public class MainMenu : MonoBehaviour () => ActionGameManager.RuntimePlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap, b => ActionGameManager.RuntimePlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap = b); _nextMenu.panel.AddButton("Back", - () => - { - ShowSettings(); - Fade(false); - }); + CommitRuntimeSettingsAndReturn); } private void ShowInputSettings() @@ -276,4 +267,11 @@ public class MainMenu : MonoBehaviour Fade(false); }); } + + private void CommitRuntimeSettingsAndReturn() + { + ActionGameManager.QueueRuntimePlayerSettingsCommit(); + ShowSettings(); + Fade(false); + } } diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 1b7e77b59..9da332346 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -87,9 +87,11 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. name, tutorial flag, story-file hash cursors, gameplay formatting, graphics preferences, input binding overrides, and action-bar inputs. Unity's menu and input screens mutate `RuntimePlayerSettings` in memory and queue typed Verse - commits through shared player-settings commit primitives; the in-memory - projection is not portable state authority. Aetheria has its own remapping UI - that calls Unity's InputSystem at the binding/action layer; Unity's generated + commits through shared player-settings commit primitives. Gameplay and + graphics settings now return through the same main-menu commit primitive; the + in-memory projection is not portable state authority. Aetheria has its own + remapping UI that calls Unity's InputSystem at the binding/action layer; + Unity's generated `AetheriaInput` class is the edge consumer of typed binding overrides, not the durable owner. Binding drag/drop and action-bar remapping both route through named runtime input commit methods instead of writing the input @@ -1549,6 +1551,9 @@ First Aetheria surfaces to publish: - Done: route input-screen binding/action-bar edits through named typed player-settings commit methods on the Unity runtime boundary, so the UI is no longer a direct writer of input binding or action-bar collections. + - Done: route main-menu graphics settings returns through the same typed + player-settings commit primitive as gameplay settings, so graphics edits + no longer survive only as session-local `RuntimePlayerSettings`. - Remaining: delete or quarantine old cache abstractions that no longer protect an invariant once catalog migration has a typed reader.