From 0ced284cabc0cdca479ac47dec63000067b2c493 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 17 Jun 2026 04:57:56 +0100 Subject: [PATCH] Lower main menu root shell through Eve --- Aetheria.State.Verify/Program.cs | 97 +++++++ Assets/Prefabs/UI/Main Menu Canvas.prefab | 5 - Assets/Scripts/UI/MainMenu.cs | 307 +++++++++++----------- docs/aetheria-perfect-machine-map.md | 13 +- 4 files changed, 259 insertions(+), 163 deletions(-) diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 023cf051e..00a403724 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -32,6 +32,7 @@ RequireEveRuntimeBootstrap(root); RequireNoRendererLocalConsole(root); RequireNoRendererLocalDebugPanels(root); RequireMainMenuSettingsCommit(root); +RequireMainMenuRootUsesEveSurface(root); RequireMainMenuSettingsShellUsesEveSurface(root); RequireSectorMapZoneDetailsUseEveSurface(root); RequireRuntimeMenuTabsUseEveSurface(root); @@ -507,6 +508,7 @@ Console.WriteLine("Eve runtime bootstrap: operations surface mounts through UI T 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: player name, gameplay, and graphics settings return through typed player-settings commits"); +Console.WriteLine("Main-menu root shell: root navigation lowers through an Eve UI Toolkit surface instead of the legacy PropertiesPanel/fade shell"); Console.WriteLine("Main-menu settings shell: settings/input/audio subpages lower through Eve UI Toolkit surfaces instead of PropertiesPanel buttons"); Console.WriteLine("Sector-map zone details shell: zone inspection lowers through an Eve UI Toolkit surface instead of PropertiesPanel rows"); Console.WriteLine("Runtime menu tab shell: MenuPanel owns tab metadata and lowers navigation through an Eve UI Toolkit surface"); @@ -2491,6 +2493,101 @@ static void RequireMainMenuSettingsShellUsesEveSurface(string root) } } +static void RequireMainMenuRootUsesEveSurface(string root) +{ + var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"); + if (!File.Exists(mainMenuPath)) + { + throw new InvalidOperationException("Cannot verify main-menu root shell; MainMenu.cs is missing."); + } + + var source = File.ReadAllText(mainMenuPath); + var requiredSymbols = new[] + { + "MainSurfaceId", + "ContinueRunCommand", + "NewGameCommand", + "ShowSettingsCommand", + "QuitCommand", + "BuildMainSurfaceDefinition(", + "HandleMainSurfaceCommand(", + "RenderMenuSurface(BuildMainSurfaceDefinition(LatestContinueRun(), InGame), HandleMainSurfaceCommand);", + "HideMenuSurface();" + }; + + var missingSymbols = requiredSymbols + .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + + if (missingSymbols.Length > 0) + { + throw new InvalidOperationException( + "MainMenu no longer lowers the root shell through Eve surfaces: " + + string.Join(", ", missingSymbols)); + } + + var forbiddenSymbols = new[] + { + "PanelPrototype", + "FadeTime", + "FadeDistance", + "FadeAlphaExponent", + "FadePositionExponent", + "_currentMenu", + "_nextMenu", + "_fadeFromRight", + "_fadeLerp", + "_fading", + "_panelPosition", + "TitleSubtitle(", + "IsMenuSurfaceVisible(", + "_nextMenu.panel.AddButton(\"Continue\"", + "_nextMenu.panel.AddButton(\"New Game\"", + "_nextMenu.panel.AddButton(\"Settings\"", + "_nextMenu.panel.AddButton(\"Quit\"", + "Fade(true)", + "Fade(false)" + }; + + var hits = forbiddenSymbols + .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + + if (hits.Length > 0) + { + throw new InvalidOperationException( + "MainMenu still owns root navigation through the legacy PropertiesPanel/fade shell: " + + string.Join(", ", hits)); + } + + var prefabPath = Path.Combine(root, "Assets", "Prefabs", "UI", "Main Menu Canvas.prefab"); + if (!File.Exists(prefabPath)) + { + throw new InvalidOperationException("Cannot verify main-menu prefab shell; Main Menu Canvas.prefab is missing."); + } + + var prefab = File.ReadAllText(prefabPath); + var forbiddenPrefabSymbols = new[] + { + "PanelPrototype:", + "FadeTime:", + "FadeDistance:", + "FadeAlphaExponent:", + "FadePositionExponent:" + }; + + var prefabHits = forbiddenPrefabSymbols + .Where(symbol => prefab.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + + if (prefabHits.Length > 0) + { + throw new InvalidOperationException( + "Main-menu prefab still serializes the dead root shell: " + + string.Join(", ", prefabHits)); + } +} + static void RequireSectorMapZoneDetailsUseEveSurface(string root) { var sectorRendererPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorRenderer.cs"); diff --git a/Assets/Prefabs/UI/Main Menu Canvas.prefab b/Assets/Prefabs/UI/Main Menu Canvas.prefab index a03628ba0..1939b09ae 100644 --- a/Assets/Prefabs/UI/Main Menu Canvas.prefab +++ b/Assets/Prefabs/UI/Main Menu Canvas.prefab @@ -916,11 +916,6 @@ MonoBehaviour: Settings: {fileID: 11400000, guid: cd2344e2001a37544aab0960c164d4ff, type: 2} Dialog: {fileID: 4533226043449861657} InGame: 0 - PanelPrototype: {fileID: 7329274282942065594} - FadeTime: 0.5 - FadeDistance: 1024 - FadeAlphaExponent: 2 - FadePositionExponent: 2 --- !u!1 &4533226043526995715 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/UI/MainMenu.cs b/Assets/Scripts/UI/MainMenu.cs index 9871af66b..f55f5ed73 100644 --- a/Assets/Scripts/UI/MainMenu.cs +++ b/Assets/Scripts/UI/MainMenu.cs @@ -24,10 +24,15 @@ public class MainMenu : MonoBehaviour private const string MenuSurfaceSchema = "gamecult.eve.surface.v1"; private const string MenuSurfaceProviderId = "aetheria"; private const string MenuSurfaceProviderKind = "game.menu"; + private const string MainSurfaceId = "aetheria.main_menu.root"; private const string SettingsSurfaceId = "aetheria.main_menu.settings"; private const string InputSettingsSurfaceId = "aetheria.main_menu.input_settings"; private const string AudioSettingsSurfaceId = "aetheria.main_menu.audio_settings"; private const string PlayerSettingsShellSurfaceId = "aetheria.main_menu.player_settings"; + private const string ContinueRunCommand = "aetheria.main_menu.root.continue"; + private const string NewGameCommand = "aetheria.main_menu.root.new_game"; + private const string ShowSettingsCommand = "aetheria.main_menu.root.show_settings"; + private const string QuitCommand = "aetheria.main_menu.root.quit"; private const string ShowPlayerSettingsCommand = "aetheria.main_menu.settings.show_player_settings"; private const string ShowInputSettingsCommand = "aetheria.main_menu.settings.show_input_settings"; private const string ShowAudioSettingsCommand = "aetheria.main_menu.settings.show_audio_settings"; @@ -38,160 +43,112 @@ public class MainMenu : MonoBehaviour public GameSettings Settings; public ConfirmationDialog Dialog; public bool InGame; - public Prototype PanelPrototype; - public float FadeTime = .5f; - public float FadeDistance = 512; - public float FadeAlphaExponent = 2; - public float FadePositionExponent = 2; - private (PropertiesPanel panel, CanvasGroup group) _currentMenu, _nextMenu; - private bool _fadeFromRight; - private float _fadeLerp; - private bool _fading; - private Vector3 _panelPosition; private UIDocument _menuSurfaceDocument; void Start() { - _panelPosition = PanelPrototype.transform.position; - - var panel1 = PanelPrototype.Instantiate(); - _currentMenu = (panel1, panel1.GetComponent()); - - var panel2 = PanelPrototype.Instantiate(); - _nextMenu = (panel2, panel2.GetComponent()); - - _currentMenu.panel.gameObject.SetActive(false); - ShowMain(); - Fade(true); - } - - private void Update() - { - if (_fading) - { - _fadeLerp += Time.deltaTime / FadeTime; - - _currentMenu.panel.transform.position = - _panelPosition + (_fadeFromRight ? Vector3.left : Vector3.right) * (FadeDistance * pow(_fadeLerp, FadePositionExponent)); - _nextMenu.panel.transform.position = - _panelPosition + (_fadeFromRight ? Vector3.right : Vector3.left) * (FadeDistance * pow(1-_fadeLerp, FadePositionExponent)); - _currentMenu.group.alpha = pow(1 - _fadeLerp, FadeAlphaExponent); - _nextMenu.group.alpha = pow(_fadeLerp, FadeAlphaExponent); - - if (_fadeLerp > 1) - { - _fading = false; - _currentMenu.panel.gameObject.SetActive(false); - var temp = _currentMenu; - _currentMenu = _nextMenu; - _nextMenu = temp; - if (IsMenuSurfaceVisible()) - _currentMenu.panel.gameObject.SetActive(false); - } - } - } - - private string TitleSubtitle(string title, string subtitle) => $"{title}\n{subtitle}"; - - private void Fade(bool fromRight) - { - _nextMenu.panel.gameObject.SetActive(true); - _nextMenu.group.alpha = 0; - _fading = true; - _fadeLerp = 0; - _fadeFromRight = fromRight; - } - - private bool IsMenuSurfaceVisible() - { - return _menuSurfaceDocument != null && _menuSurfaceDocument.gameObject.activeSelf; } private void ShowMain() { - HideMenuSurface(); - _nextMenu.panel.Clear(); - _nextMenu.panel.Title.text = TitleSubtitle("aetheria", "terminus"); - if (!InGame) + RenderMenuSurface(BuildMainSurfaceDefinition(LatestContinueRun(), InGame), HandleMainSurfaceCommand); + } + + private void HandleMainSurfaceCommand(EveSurfaceCommandRequest request) + { + switch (request.Command) { - var continueRun = LatestContinueRun(); - _nextMenu.panel.AddButton("Continue", continueRun == null ? null : () => ContinueGame(continueRun)); - } - _nextMenu.panel.AddButton("New Game", - () => - { - var generatorState = "Loading typed catalog"; - Action setState = s => generatorState = s; - - Fade(true); - _nextMenu.panel.gameObject.SetActive(false); - Dialog.Clear(); - Dialog.Title.text = "Generating Galaxy"; - Dialog.AddProperty(() => generatorState); - Dialog.Show(); - - if (ActionGameManager.RuntimePlayerSettings.TutorialPassed) + case ContinueRunCommand: + var continueRun = LatestContinueRun(); + if (continueRun == null) { - Settings.SectorBackgroundSettings.NoisePosition = Random.value * 1000; - ActionGameManager.IsTutorial = false; - var generationSeed = NextGenerationSeed(); - Task.Run(() => - { - var sector = new Galaxy( - Settings.SectorGenerationSettings, - Settings.SectorBackgroundSettings, - Settings.NameGeneratorSettings, - ActionGameManager.RuntimeCatalog, - Debug.Log, - setState, - generationSeed); - Observable.NextFrame().Subscribe(_ => - { - ActionGameManager.CurrentGalaxy = sector; - SceneManager.LoadScene("ARPG"); - }); - }).WrapErrors(); + Debug.LogWarning("Main-menu Continue requested without a typed run state."); + ShowMain(); + return; } - else - { - int iteration = 1; - do - { - Settings.TutorialBackgroundSettings.NoisePosition = Random.value * 1000; - setState($"Finding Galaxy Position: iteration {iteration++}"); - } while (Settings.TutorialBackgroundSettings.CloudDensity(float2(0.5f)) < .5f); - - ActionGameManager.IsTutorial = true; - var generationSeed = NextGenerationSeed(); - Task.Run(() => - { - var sector = new Galaxy( - Settings.TutorialGenerationSettings, - Settings.TutorialBackgroundSettings, - Settings.NameGeneratorSettings, - ActionGameManager.RuntimeCatalog, - ActionGameManager.RuntimePlayerSettings, - ActionGameManager.GameDataDirectory.CreateSubdirectory("Narrative"), - Debug.Log, - setState, - generationSeed); - Observable.NextFrame().Subscribe(_ => - { - ActionGameManager.CurrentGalaxy = sector; - SceneManager.LoadScene("ARPG"); - }); - }).WrapErrors(); - } - }); - _nextMenu.panel.AddButton("Settings", - () => - { + + ContinueGame(continueRun); + return; + case NewGameCommand: + StartNewGame(); + return; + case ShowSettingsCommand: ShowSettings(); - Fade(true); - }); - _nextMenu.panel.AddButton("Quit", Application.Quit); + return; + case QuitCommand: + Application.Quit(); + return; + default: + Debug.LogWarning($"Unknown main menu command: {request.Command}"); + return; + } + } + + private void StartNewGame() + { + var generatorState = "Loading typed catalog"; + Action setState = s => generatorState = s; + + HideMenuSurface(); + Dialog.Clear(); + Dialog.Title.text = "Generating Galaxy"; + Dialog.AddProperty(() => generatorState); + Dialog.Show(); + + if (ActionGameManager.RuntimePlayerSettings.TutorialPassed) + { + Settings.SectorBackgroundSettings.NoisePosition = Random.value * 1000; + ActionGameManager.IsTutorial = false; + var generationSeed = NextGenerationSeed(); + Task.Run(() => + { + var sector = new Galaxy( + Settings.SectorGenerationSettings, + Settings.SectorBackgroundSettings, + Settings.NameGeneratorSettings, + ActionGameManager.RuntimeCatalog, + Debug.Log, + setState, + generationSeed); + Observable.NextFrame().Subscribe(_ => + { + ActionGameManager.CurrentGalaxy = sector; + SceneManager.LoadScene("ARPG"); + }); + }).WrapErrors(); + } + else + { + int iteration = 1; + do + { + Settings.TutorialBackgroundSettings.NoisePosition = Random.value * 1000; + setState($"Finding Galaxy Position: iteration {iteration++}"); + } while (Settings.TutorialBackgroundSettings.CloudDensity(float2(0.5f)) < .5f); + + ActionGameManager.IsTutorial = true; + var generationSeed = NextGenerationSeed(); + Task.Run(() => + { + var sector = new Galaxy( + Settings.TutorialGenerationSettings, + Settings.TutorialBackgroundSettings, + Settings.NameGeneratorSettings, + ActionGameManager.RuntimeCatalog, + ActionGameManager.RuntimePlayerSettings, + ActionGameManager.GameDataDirectory.CreateSubdirectory("Narrative"), + Debug.Log, + setState, + generationSeed); + Observable.NextFrame().Subscribe(_ => + { + ActionGameManager.CurrentGalaxy = sector; + SceneManager.LoadScene("ARPG"); + }); + }).WrapErrors(); + } } private static uint NextGenerationSeed() @@ -226,8 +183,7 @@ public class MainMenu : MonoBehaviour var generatorState = "Loading typed run"; Action setState = s => generatorState = s; - Fade(true); - _nextMenu.panel.gameObject.SetActive(false); + HideMenuSurface(); Dialog.Clear(); Dialog.Title.text = "Continuing Run"; Dialog.AddProperty(() => generatorState); @@ -270,29 +226,21 @@ public class MainMenu : MonoBehaviour private void ShowSettings() { - _nextMenu.panel.Clear(); - _nextMenu.panel.gameObject.SetActive(false); RenderMenuSurface(BuildSettingsSurfaceDefinition(), HandleSettingsSurfaceCommand); } private void ShowInputSettings() { - _nextMenu.panel.Clear(); - _nextMenu.panel.gameObject.SetActive(false); RenderMenuSurface(BuildInputSettingsSurfaceDefinition(), HandleInputSettingsSurfaceCommand); } private void ShowAudioSettings() { - _nextMenu.panel.Clear(); - _nextMenu.panel.gameObject.SetActive(false); RenderMenuSurface(BuildAudioSettingsSurfaceDefinition(), HandleAudioSettingsSurfaceCommand); } private void ShowPlayerSettingsSurface() { - _nextMenu.panel.Clear(); - _nextMenu.panel.gameObject.SetActive(false); RenderMenuSurface( WithBackAction( ToEveSurfaceDocument(BuildPlayerSettingsSurfaceDefinition()), @@ -349,9 +297,7 @@ public class MainMenu : MonoBehaviour ShowAudioSettings(); return; case BackToMainCommand: - HideMenuSurface(); ShowMain(); - Fade(false); return; default: Debug.LogWarning($"Unknown settings menu command: {request.Command}"); @@ -436,6 +382,56 @@ public class MainMenu : MonoBehaviour DateTime.UtcNow.ToString("O"))); } + private static EveSurfaceDocument BuildMainSurfaceDefinition( + AetheriaRuntimeRunStateSnapshot continueRun, + bool inGame) + { + var commands = new List(); + var actionButtons = new List(); + var cardChildren = new List(); + + if (!inGame) + { + if (continueRun == null) + { + cardChildren.Add(Text( + $"{MainSurfaceId}.continue.note", + "No typed run state is available yet. Start a new galaxy or connect to a Verse that already has one.")); + } + else + { + commands.Add(new EveCommandTemplate(ContinueRunCommand, "Continue", "unity-uitoolkit")); + actionButtons.Add(Button($"{MainSurfaceId}.continue", "Continue", ContinueRunCommand)); + cardChildren.Add(Metric( + $"{MainSurfaceId}.continue.run", + "Latest Run", + continueRun.RunId)); + } + } + + commands.Add(new EveCommandTemplate(NewGameCommand, "New Game", "unity-uitoolkit")); + commands.Add(new EveCommandTemplate(ShowSettingsCommand, "Settings", "unity-uitoolkit")); + commands.Add(new EveCommandTemplate(QuitCommand, "Quit", "unity-uitoolkit")); + + actionButtons.Add(Button($"{MainSurfaceId}.newGame", "New Game", NewGameCommand)); + actionButtons.Add(Button($"{MainSurfaceId}.settings", "Settings", ShowSettingsCommand)); + actionButtons.Add(Button($"{MainSurfaceId}.quit", "Quit", QuitCommand)); + + cardChildren.Add(Text( + $"{MainSurfaceId}.note", + "The client lowers this shell through Eve. Verse state and game truth belong to the daemon.")); + cardChildren.Add(ButtonColumn($"{MainSurfaceId}.actions", actionButtons.ToArray())); + + return BuildMenuSurfaceDocument( + MainSurfaceId, + "Aetheria Terminus", + commands, + Card( + $"{MainSurfaceId}.card", + "Aetheria Terminus", + cardChildren.ToArray())); + } + private static EveSurfaceDocument BuildSettingsSurfaceDefinition() { return BuildMenuSurfaceDocument( @@ -594,6 +590,13 @@ public class MainMenu : MonoBehaviour return Node(id, "row", Array.Empty<(string Key, string Value)>(), children); } + private static EveSurfaceComponent ButtonColumn( + string id, + params EveSurfaceComponent[] children) + { + return Node(id, "column", Array.Empty<(string Key, string Value)>(), children); + } + private static EveSurfaceComponent Node( string id, string kind, diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index cba26aef9..af43f59ef 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -1562,12 +1562,13 @@ First Aetheria surfaces to publish: `aetheria.player_settings` Eve surface contract. `MainMenu` no longer owns a special-case text field; UI Toolkit lowers the shared Eve text control and gameplay still owns the typed player-settings commit. - - Done: replace the main-menu settings/input/audio subpages with - Eve-owned UI Toolkit surfaces. `MainMenu` still owns root navigation and - entry/exit flow, but the old `PropertiesPanel` settings shell no longer - owns those pages. Input rebinding itself remains on the runtime-owned - remap screen until Eve grows typed rebinding controls, and audio still - lacks a typed state surface. + - Done: replace the whole main-menu shell with Eve-owned UI Toolkit + surfaces. Root navigation, settings, input, and audio now lower through a + single Eve host instead of the old `PropertiesPanel`/fade shell. The menu + remains a client-side projection: the Aetheria daemon owns Verse state and + Unity renders and submits commands. Input rebinding itself remains on the + runtime-owned remap screen until Eve grows typed rebinding controls, and + audio still lacks a typed state surface. - Done: replace the sector-map zone details panel with an Eve-owned UI Toolkit surface. `SectorRenderer` still owns reveal/camera behavior, but zone inspection no longer rebuilds `PropertiesPanel` rows when the player