From 65a5a56635f7dfbd3ead4af224e64b8f80b37b60 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 16 Jun 2026 19:45:39 +0100 Subject: [PATCH] Lower player name through Eve settings surface --- Aetheria.State.Verify/Program.cs | 37 ++++++++++++++---- Aetheria.State/AetheriaEveCommandBridge.cs | 7 ++++ Assets/Scripts/Gameplay/ActionGameManager.cs | 14 ++++++- Assets/Scripts/UI/MainMenu.cs | 27 +------------ .../AetheriaRuntimePlayerSettingsCommands.cs | 2 + ...eriaRuntimePlayerSettingsSurfaceBuilder.cs | 15 ++++++- .../Runtime/EveUiToolkitSurfaceLowerer.cs | 39 ++++++++++++++++++- docs/aetheria-perfect-machine-map.md | 8 +++- 8 files changed, 111 insertions(+), 38 deletions(-) diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index bf658528a..bcf3de70a 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -486,7 +486,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"); +Console.WriteLine("Main-menu settings authority: player name, gameplay, and graphics settings return through typed player-settings commits"); Console.WriteLine("Main-menu Continue authority: Continue selects typed run state instead of a null button"); Console.WriteLine("PropertiesPanel inspector authority: dead generic reflection inspector path is deleted"); Console.WriteLine("Runtime simulation tuning authority: UI writes flow through gameplay checkpoint commits"); @@ -1980,7 +1980,6 @@ static void RequireMainMenuSettingsCommit(string root) var requiredMainMenuAuthoritySymbols = new[] { - "ActionGameManager.CommitRuntimePlayerName", "ActionGameManager.CommitRuntimePlayerSettingsCommand" }; @@ -1997,6 +1996,7 @@ static void RequireMainMenuSettingsCommit(string root) var requiredSharedCommands = new[] { + "SetPlayerName", "CycleTemperatureUnit", "DecrementSignificantDigits", "IncrementSignificantDigits", @@ -2018,6 +2018,8 @@ static void RequireMainMenuSettingsCommit(string root) var requiredSurfaceBuilderSymbols = new[] { "AetheriaRuntimePlayerSettingsSurfaceBuilder", + "AetheriaRuntimePlayerSettingsCommands.SetPlayerName", + "\"control.text\"", "AetheriaRuntimePlayerSettingsCommands.CycleTemperatureUnit", "AetheriaRuntimePlayerSettingsCommands.DecrementSignificantDigits", "AetheriaRuntimePlayerSettingsCommands.IncrementSignificantDigits", @@ -2040,8 +2042,7 @@ static void RequireMainMenuSettingsCommit(string root) { "AetheriaRuntimePlayerSettingsSurfaceBuilder.Build", "new EveUiToolkitSurfaceLowerer", - "ActionGameManager.CommitRuntimePlayerSettingsCommand", - "new TextField(\"Name\")" + "ActionGameManager.CommitRuntimePlayerSettingsCommand(request.Command, request.Payload)" }; var missingMainMenuSymbols = requiredMainMenuSymbols @@ -2055,6 +2056,23 @@ static void RequireMainMenuSettingsCommit(string root) string.Join(", ", missingMainMenuSymbols)); } + var forbiddenMainMenuSymbols = new[] + { + "new TextField(\"Name\")", + "ActionGameManager.CommitRuntimePlayerName(evt.newValue)" + }; + + var mainMenuHits = forbiddenMainMenuSymbols + .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + + if (mainMenuHits.Length > 0) + { + throw new InvalidOperationException( + "MainMenu should not keep renderer-local player-name input authority outside the shared Eve surface: " + + string.Join(", ", mainMenuHits)); + } + var forbiddenUiFieldSymbols = new[] { "AddField(\"Temperature Unit\"", @@ -2141,7 +2159,9 @@ static void RequirePlayerSettingsEveSurface(string root) if (!bridge.Contains("AppliedPlayerSettingsCommands", StringComparison.Ordinal) || !bridge.Contains("ApplyPlayerSettingsCommandAsync", StringComparison.Ordinal) || - !bridge.Contains("PutPlayerSettingsSurfaceAsync", StringComparison.Ordinal)) + !bridge.Contains("PutPlayerSettingsSurfaceAsync", StringComparison.Ordinal) || + !bridge.Contains("SetPlayerName", StringComparison.Ordinal) || + !bridge.Contains("command.Payload.TryGetValue(\"value\"", StringComparison.Ordinal)) { throw new InvalidOperationException( "Eve command bridge no longer owns the typed player-settings surface mutation path."); @@ -2162,13 +2182,16 @@ static void RequirePlayerSettingsEveSurface(string root) } if (!sharedCommands.Contains("SurfaceId = \"aetheria.player_settings\"", StringComparison.Ordinal) || - !sharedCommands.Contains("public static bool IsKnown", StringComparison.Ordinal)) + !sharedCommands.Contains("public static bool IsKnown", StringComparison.Ordinal) || + !sharedCommands.Contains("SetPlayerName", StringComparison.Ordinal)) { throw new InvalidOperationException( "Shared player-settings Eve command contract is missing the surface id or command registry helper."); } - if (!surfaceBuilder.Contains("public static class AetheriaRuntimePlayerSettingsSurfaceBuilder", StringComparison.Ordinal)) + if (!surfaceBuilder.Contains("public static class AetheriaRuntimePlayerSettingsSurfaceBuilder", StringComparison.Ordinal) || + !surfaceBuilder.Contains("\"control.text\"", StringComparison.Ordinal) || + !surfaceBuilder.Contains("SetPlayerName", StringComparison.Ordinal)) { throw new InvalidOperationException( "Shared player-settings Eve surface builder no longer owns the portable settings surface contract."); diff --git a/Aetheria.State/AetheriaEveCommandBridge.cs b/Aetheria.State/AetheriaEveCommandBridge.cs index 946fc64dc..51db607ca 100644 --- a/Aetheria.State/AetheriaEveCommandBridge.cs +++ b/Aetheria.State/AetheriaEveCommandBridge.cs @@ -68,6 +68,7 @@ public static class AetheriaEveCommandBridge report.AppliedOperationsRefreshes++; break; case GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.Refresh: + case GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.SetPlayerName: case GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.CycleTemperatureUnit: case GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.DecrementSignificantDigits: case GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.IncrementSignificantDigits: @@ -127,6 +128,12 @@ public static class AetheriaEveCommandBridge switch (command.Command) { + case GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.SetPlayerName: + settings.PlayerName = command.Payload.TryGetValue("value", out var playerName) + ? playerName ?? "" + : ""; + persistSettings = true; + break; case GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.CycleTemperatureUnit: settings.Gameplay.TemperatureUnit = Cycle( settings.Gameplay.TemperatureUnit, diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index dea77240a..1e804282d 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -116,10 +116,22 @@ public class ActionGameManager : MonoBehaviour QueueRuntimePlayerSettingsCommit(); } - public static bool CommitRuntimePlayerSettingsCommand(string command) + public static bool CommitRuntimePlayerSettingsCommand( + string command, + IReadOnlyDictionary payload = null) { switch (command) { + case AetheriaRuntimePlayerSettingsCommands.SetPlayerName: + if (payload != null && payload.TryGetValue("value", out var playerName)) + { + CommitRuntimePlayerName(playerName); + } + else + { + CommitRuntimePlayerName(""); + } + return true; case AetheriaRuntimePlayerSettingsCommands.CycleTemperatureUnit: CommitRuntimeTemperatureUnit( RuntimePlayerSettings.GameplaySettings.TemperatureUnit switch diff --git a/Assets/Scripts/UI/MainMenu.cs b/Assets/Scripts/UI/MainMenu.cs index 38cbe67e5..ceedc968d 100644 --- a/Assets/Scripts/UI/MainMenu.cs +++ b/Assets/Scripts/UI/MainMenu.cs @@ -338,37 +338,12 @@ public class MainMenu : MonoBehaviour shell.style.backgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.96f); root.Add(shell); - var title = new Label("Player Settings"); - title.style.unityFontStyleAndWeight = FontStyle.Bold; - title.style.fontSize = 24; - title.style.marginBottom = 6; - shell.Add(title); - - var subtitle = new Label("Shared Eve surface lowered locally while gameplay keeps commit authority."); - subtitle.style.marginBottom = 12; - shell.Add(subtitle); - - var nameField = new TextField("Name") - { - value = ActionGameManager.RuntimePlayerSettings.Name - }; - nameField.style.marginBottom = 14; - nameField.RegisterValueChangedCallback(evt => - { - if (string.Equals(evt.newValue, ActionGameManager.RuntimePlayerSettings.Name, StringComparison.Ordinal)) - return; - - ActionGameManager.CommitRuntimePlayerName(evt.newValue); - RenderPlayerSettingsSurface(); - }); - shell.Add(nameField); - var lowerer = new EveUiToolkitSurfaceLowerer(); var surface = lowerer.Lower( ToEveSurfaceDocument(BuildPlayerSettingsSurfaceDefinition()), request => { - if (!ActionGameManager.CommitRuntimePlayerSettingsCommand(request.Command)) + if (!ActionGameManager.CommitRuntimePlayerSettingsCommand(request.Command, request.Payload)) { Debug.LogWarning($"Unknown player-settings command: {request.Command}"); return; diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsCommands.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsCommands.cs index 23fd864ec..e76ff8c60 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsCommands.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsCommands.cs @@ -4,6 +4,7 @@ namespace GameCult.Aetheria.State.Unity { public const string SurfaceId = "aetheria.player_settings"; public const string Refresh = "aetheria.player_settings.refresh"; + public const string SetPlayerName = "aetheria.player_settings.player_name.set"; public const string CycleTemperatureUnit = "aetheria.player_settings.gameplay.temperature_unit.cycle"; public const string DecrementSignificantDigits = "aetheria.player_settings.gameplay.significant_digits.decrement"; public const string IncrementSignificantDigits = "aetheria.player_settings.gameplay.significant_digits.increment"; @@ -13,6 +14,7 @@ namespace GameCult.Aetheria.State.Unity public static bool IsKnown(string command) { return command == Refresh || + command == SetPlayerName || command == CycleTemperatureUnit || command == DecrementSignificantDigits || command == IncrementSignificantDigits || diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs index a95ec365d..47af58aad 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs @@ -181,9 +181,13 @@ namespace GameCult.Aetheria.State.Unity "aetheria.playerSettings.summary", "card", new[] { ("title", "Player Settings") }, + TextInput( + "playerSettings.summary.playerName", + "Name", + settings.PlayerName, + AetheriaRuntimePlayerSettingsCommands.SetPlayerName), Row( "playerSettings.summary.values", - ("playerName", settings.PlayerName), ("tutorialPassed", settings.TutorialPassed ? "Yes" : "No"), ("activeRun", settings.ActiveRunKey)), Text( @@ -242,6 +246,10 @@ namespace GameCult.Aetheria.State.Unity AetheriaRuntimePlayerSettingsCommands.Refresh, "Refresh", "cultmesh"), + new AetheriaRuntimeSurfaceCommandTemplate( + AetheriaRuntimePlayerSettingsCommands.SetPlayerName, + "Set Player Name", + "cultmesh"), new AetheriaRuntimeSurfaceCommandTemplate( AetheriaRuntimePlayerSettingsCommands.CycleTemperatureUnit, "Cycle Temperature Unit", @@ -280,6 +288,11 @@ namespace GameCult.Aetheria.State.Unity return Node(id, "control.button", new[] { ("label", label), ("command", command) }); } + private static AetheriaRuntimeSurfaceComponent TextInput(string id, string label, string value, string command) + { + return Node(id, "control.text", new[] { ("label", label), ("value", value), ("command", command) }); + } + private static AetheriaRuntimeSurfaceComponent ButtonRow( string id, params AetheriaRuntimeSurfaceComponent[] children) diff --git a/Packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs b/Packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs index 5be4aa809..f73fc28e0 100644 --- a/Packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs +++ b/Packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs @@ -113,6 +113,26 @@ namespace GameCult.Eve.UnityUIToolkit var command = component.GetProp("command", component.GetProp("action", "invoke")); return new Button(() => EmitCommand(document, component, command, commandSink)) { text = label }; } + case "control.text": + { + var label = component.GetProp("label", component.GetProp("title", "Value")); + var command = component.GetProp("command", component.GetProp("action", "set")); + var value = component.GetProp("value"); + var field = new TextField(label); + field.SetValueWithoutNotify(value); + field.RegisterValueChangedCallback(evt => + { + if (string.Equals(evt.newValue, value, StringComparison.Ordinal)) + return; + + var payload = new Dictionary(component.Props, StringComparer.Ordinal) + { + ["value"] = evt.newValue ?? "" + }; + EmitCommand(document, component, command, payload, commandSink); + }); + return field; + } default: { var element = new VisualElement(); @@ -130,6 +150,21 @@ namespace GameCult.Eve.UnityUIToolkit EveSurfaceComponent component, string command, Action? commandSink) + { + EmitCommand( + document, + component, + command, + new Dictionary(component.Props, StringComparer.Ordinal), + commandSink); + } + + private static void EmitCommand( + EveSurfaceDocument document, + EveSurfaceComponent component, + string command, + IReadOnlyDictionary payload, + Action? commandSink) { if (commandSink == null || string.IsNullOrWhiteSpace(command)) return; @@ -138,7 +173,7 @@ namespace GameCult.Eve.UnityUIToolkit document.ProviderId, document.Surface.Id, command, - new Dictionary(component.Props, StringComparer.Ordinal), + payload, DateTimeOffset.UtcNow, "unity-uitoolkit")); } @@ -205,6 +240,8 @@ namespace GameCult.Eve.UnityUIToolkit return "text"; if (kind.StartsWith("control.button.", StringComparison.Ordinal)) return "control.button"; + if (kind.StartsWith("control.text.", StringComparison.Ordinal)) + return "control.text"; return kind; } diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index f9e318ddd..512014d7e 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -1515,8 +1515,12 @@ First Aetheria surfaces to publish: - Done: replace the main-menu gameplay/graphics settings screen with a locally lowered UI Toolkit projection of the shared `aetheria.player_settings` Eve surface contract. The menu shell still owns - entry/exit flow and player-name input, but gameplay now owns surface - command semantics through `ActionGameManager.CommitRuntimePlayerSettingsCommand`. + entry/exit flow, but gameplay now owns surface command semantics through + `ActionGameManager.CommitRuntimePlayerSettingsCommand`. + - Done: move the main-menu player-name field into the shared + `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. - 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.