From 5b7a5b3a8cd63eedab4c73bfb851155ccb13580c Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 28 Jun 2026 08:34:28 +0100 Subject: [PATCH] Move observed sector projection into gameplay boot --- Aetheria.State.Verify/Program.cs | 57 +++++++++++++++---- Assets/Scripts/Gameplay/ActionGameManager.cs | 6 +- .../AetheriaUnityGameplayBootShell.cs | 24 +++++++- .../AetheriaUnityObservedRunProjection.cs | 14 ++--- Assets/Scripts/UI/MainMenu.cs | 43 +------------- 5 files changed, 79 insertions(+), 65 deletions(-) diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index e633202b2..d3c70add6 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -11145,7 +11145,6 @@ static void RequireMainMenuContinueRunState(string root) ".Aetheria()", ".LatestSectorMap()", "ContinueGame()", - "AetheriaUnityObservedRunProjection.Project(", "SceneManager.LoadScene(\"ARPG\")" }; @@ -11173,6 +11172,9 @@ static void RequireMainMenuContinueRunState(string root) "GameplayLoopShell.LateTick()", "ApplyLatestZoneRender = () => ObservedFrameApplier.ApplyLatestZoneRender()", "private AetheriaUnityObservedFrameApplier ObservedFrameApplier =>", + "private Galaxy ObservedGalaxy { get; set; }", + "ObservedGalaxy = boot.ObservedGalaxy", + "zoneIndex => AetheriaUnityObservedRunProjection.FindZone(ObservedGalaxy, zoneIndex)", "private AetheriaUnityObservedZoneContextProjector ObservedZoneContextProjector =>", "entity => CurrentEntityBinder.RestoreBinding(entity)", "private AetheriaUnityObservedTargetQuery ObservedTargetQuery =>", @@ -12141,6 +12143,9 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root) "ApplyLatestZoneRender = () => ObservedFrameApplier.ApplyLatestZoneRender()", "ResolveDaemonObserver()", "private AetheriaUnityObservedFrameApplier ObservedFrameApplier =>", + "private Galaxy ObservedGalaxy { get; set; }", + "ObservedGalaxy = boot.ObservedGalaxy", + "zoneIndex => AetheriaUnityObservedRunProjection.FindZone(ObservedGalaxy, zoneIndex)", "private AetheriaUnityObservedZoneContextProjector ObservedZoneContextProjector =>", "entity => CurrentEntityBinder.RestoreBinding(entity)", "private AetheriaUnityEntityConstructionBlueprintProjector EntityConstructionBlueprintProjector =>", @@ -13220,10 +13225,7 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root) "LatestSectorMap(stateBoot)", ".Aetheria()", ".LatestSectorMap()", - "AetheriaUnityObservedRunProjection.Project(", "sectorMap.FrameId", - "sectorMap.IsTutorial", - "sectorMap.GenerationSeed", "SceneManager.LoadScene(\"ARPG\")" }; @@ -13822,6 +13824,7 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) var runtimeEveSurfaceAdapterPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeEveSurfaceAdapter.cs"); var unityPackageProjectPath = Path.Combine(root, "GameCult.Aetheria.State.Unity.csproj"); var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); + var gameplayBootShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayBootShell.cs"); var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"); var eveSurfacePresenterPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveSurfacePresenter.cs"); var eveUnitySurfaceHostPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveUnitySurfaceHost.cs"); @@ -13832,6 +13835,7 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) runtimeEveSurfaceAdapterPath, unityPackageProjectPath, actionGameManagerPath, + gameplayBootShellPath, mainMenuPath, eveSurfacePresenterPath, eveUnitySurfaceHostPath @@ -13853,6 +13857,7 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) var runtimeEveSurfaceAdapter = File.ReadAllText(runtimeEveSurfaceAdapterPath); var unityPackageProject = File.ReadAllText(unityPackageProjectPath); var actionGameManager = File.ReadAllText(actionGameManagerPath); + var gameplayBootShell = File.ReadAllText(gameplayBootShellPath); var mainMenu = File.ReadAllText(mainMenuPath); var eveSurfacePresenter = File.ReadAllText(eveSurfacePresenterPath); var eveUnitySurfaceHost = File.ReadAllText(eveUnitySurfaceHostPath); @@ -14055,10 +14060,6 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) var inputDisplayLayout = File.Exists(inputDisplayLayoutPath) ? File.ReadAllText(inputDisplayLayoutPath) : throw new InvalidOperationException("Cannot verify daemon state acquisition; InputDisplayLayout.cs is missing."); - var gameplayBootShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayBootShell.cs"); - var gameplayBootShell = File.Exists(gameplayBootShellPath) - ? File.ReadAllText(gameplayBootShellPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaUnityGameplayBootShell.cs is missing."); var aetheriaClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaClient.cs"); var aetheriaClient = File.Exists(aetheriaClientPath) ? File.ReadAllText(aetheriaClientPath) @@ -14426,10 +14427,8 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) if (!mainMenu.Contains("AetheriaClient", StringComparison.Ordinal) || !mainMenu.Contains("LatestSectorMap(AetheriaRuntimeStateBootReport stateBoot)", StringComparison.Ordinal) || - !mainMenu.Contains("LatestRuntimeCatalog(AetheriaRuntimeStateBootReport stateBoot)", StringComparison.Ordinal) || !mainMenu.Contains(".Aetheria()", StringComparison.Ordinal) || !mainMenu.Contains(".LatestSectorMap()", StringComparison.Ordinal) || - !mainMenu.Contains(".LatestCatalog()", StringComparison.Ordinal) || !mainMenu.Contains(".LatestPlayer()", StringComparison.Ordinal)) { throw new InvalidOperationException( @@ -14448,6 +14447,44 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) "MainMenu still names runtime catalog access as a raw catalog open instead of a managed latest document read."); } + if (mainMenu.Contains("LatestRuntimeCatalog(", StringComparison.Ordinal) || + mainMenu.Contains("AetheriaUnityObservedRunProjection.Project(", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "MainMenu still owns gameplay catalog/sector projection instead of leaving observed-scene boot to AetheriaUnityGameplayBootShell."); + } + + var requiredGameplayBootSymbols = new[] + { + ".LatestCatalog()", + ".LatestSectorMap()", + "AetheriaUnityObservedRunProjection.Project(", + "sectorMap.IsTutorial", + "sectorMap.GenerationSeed", + "new AetheriaUnityGameplayBootResult(", + "ObservedGalaxy" + }; + var missingGameplayBootSymbols = requiredGameplayBootSymbols + .Where(symbol => !gameplayBootShell.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (missingGameplayBootSymbols.Length > 0) + { + throw new InvalidOperationException( + "AetheriaUnityGameplayBootShell must own managed catalog/sector-map projection for the observed gameplay scene: " + + string.Join(", ", missingGameplayBootSymbols)); + } + + var observedRunProjectionPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedRunProjection.cs"); + var observedRunProjection = File.Exists(observedRunProjectionPath) + ? File.ReadAllText(observedRunProjectionPath) + : throw new InvalidOperationException("Cannot verify observed run projection ownership; AetheriaUnityObservedRunProjection.cs is missing."); + if (observedRunProjection.Contains("public static Galaxy Galaxy", StringComparison.Ordinal) || + observedRunProjection.Contains("private set;", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "AetheriaUnityObservedRunProjection must be a pure projector; observed galaxy ownership belongs to gameplay boot state."); + } + var forbiddenMainMenuReaderSymbols = new[] { "AetheriaRuntimeStateReader.ReadPlayerSettings", diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 0ff6c6611..fb08ecd30 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -49,7 +49,7 @@ public class ActionGameManager : MonoBehaviour _observedZoneContextProjector ??= new AetheriaUnityObservedZoneContextProjector( ItemManager, Settings.PlanetSettings, - () => AetheriaUnityObservedRunProjection.Galaxy, + () => ObservedGalaxy, Debug.LogWarning, PlayMusic); private AetheriaUnityCurrentEntityBinder CurrentEntityBinder => @@ -96,7 +96,7 @@ public class ActionGameManager : MonoBehaviour private AetheriaUnityObservedFrameApplier ObservedFrameApplier => _observedFrameApplier ??= new AetheriaUnityObservedFrameApplier( ResolveDaemonObserver, - AetheriaUnityObservedRunProjection.FindZone, + zoneIndex => AetheriaUnityObservedRunProjection.FindZone(ObservedGalaxy, zoneIndex), () => Zone, zone => Zone = zone, _observedEntityIndex, @@ -292,6 +292,7 @@ public class ActionGameManager : MonoBehaviour private ItemManager ItemManager { get; set; } private AetheriaUnityLoadoutItemProjector _loadoutItemProjector; + private Galaxy ObservedGalaxy { get; set; } private Zone Zone { get; set; } private readonly AetheriaUnityDragSession _dragSession = new AetheriaUnityDragSession(); @@ -319,6 +320,7 @@ public class ActionGameManager : MonoBehaviour var boot = GameplayBootShell.Boot(); ItemManager = boot.ItemManager; _loadoutItemProjector = boot.LoadoutItemProjector; + ObservedGalaxy = boot.ObservedGalaxy; SceneWiring.ConfigureCurrentEntityPresentation(_currentEntityPresentation, boot.RuntimeCatalog); SceneWiring.ConfigureTargetPresentation( _targetPresentation, diff --git a/Assets/Scripts/Gameplay/AetheriaUnityGameplayBootShell.cs b/Assets/Scripts/Gameplay/AetheriaUnityGameplayBootShell.cs index c7097e10d..89c8ad435 100644 --- a/Assets/Scripts/Gameplay/AetheriaUnityGameplayBootShell.cs +++ b/Assets/Scripts/Gameplay/AetheriaUnityGameplayBootShell.cs @@ -34,13 +34,27 @@ public sealed class AetheriaUnityGameplayBootShell $"Aetheria runtime state file is missing at {stateBoot.StateFilePath}; gameplay requires an authoritative daemon mirror."); } - var runtimeCatalog = AetheriaUnityRuntimeClientProvider.ResolveClient(stateBoot.StateFilePath, stateBoot.RuntimeId) - .Aetheria() - .LatestCatalog(); + var aetheria = AetheriaUnityRuntimeClientProvider.ResolveClient(stateBoot.StateFilePath, stateBoot.RuntimeId) + .Aetheria(); + var runtimeCatalog = aetheria.LatestCatalog(); if (runtimeCatalog == null) throw new InvalidOperationException("Aetheria typed runtime catalog is required before gameplay boot."); + var sectorMap = aetheria.LatestSectorMap(); + if (sectorMap == null) + throw new InvalidOperationException("Aetheria typed sector map is required before gameplay boot."); Log?.Invoke($"Aetheria runtime catalog: {runtimeCatalog.Items.Count} items, {runtimeCatalog.Corporations.Count} corporations, {runtimeCatalog.NameFiles.Count} name files"); + Log?.Invoke($"Aetheria observed sector-map frame: {sectorMap.FrameId}"); + + var backgroundSettings = sectorMap.IsTutorial + ? Settings.TutorialBackgroundSettings + : Settings.SectorBackgroundSettings; + backgroundSettings.NoisePosition = sectorMap.GenerationSeed == 0 ? 1 : sectorMap.GenerationSeed; + var observedGalaxy = AetheriaUnityObservedRunProjection.Project( + sectorMap, + backgroundSettings, + runtimeCatalog, + Log); var itemManager = new ItemManager( runtimeCatalog, @@ -60,6 +74,7 @@ public sealed class AetheriaUnityGameplayBootShell return new AetheriaUnityGameplayBootResult( runtimeCatalog, + observedGalaxy, itemManager, loadoutItemProjector); } @@ -104,15 +119,18 @@ public readonly struct AetheriaUnityGameplayBootResult { public AetheriaUnityGameplayBootResult( AetheriaRuntimeCatalogSnapshot runtimeCatalog, + Galaxy observedGalaxy, ItemManager itemManager, AetheriaUnityLoadoutItemProjector loadoutItemProjector) { RuntimeCatalog = runtimeCatalog; + ObservedGalaxy = observedGalaxy; ItemManager = itemManager; LoadoutItemProjector = loadoutItemProjector; } public AetheriaRuntimeCatalogSnapshot RuntimeCatalog { get; } + public Galaxy ObservedGalaxy { get; } public ItemManager ItemManager { get; } public AetheriaUnityLoadoutItemProjector LoadoutItemProjector { get; } } diff --git a/Assets/Scripts/Gameplay/AetheriaUnityObservedRunProjection.cs b/Assets/Scripts/Gameplay/AetheriaUnityObservedRunProjection.cs index dece35fdc..be4b2c59c 100644 --- a/Assets/Scripts/Gameplay/AetheriaUnityObservedRunProjection.cs +++ b/Assets/Scripts/Gameplay/AetheriaUnityObservedRunProjection.cs @@ -4,18 +4,16 @@ using GameCult.Aetheria.State.Verse; public static class AetheriaUnityObservedRunProjection { - public static Galaxy Galaxy { get; private set; } - - public static void Project( + public static Galaxy Project( AetheriaRuntimeSectorMapDocument sectorMap, SectorBackgroundSettings backgroundSettings, AetheriaRuntimeCatalogSnapshot runtimeCatalog, Action log) { - Galaxy = global::Galaxy.ProjectObservedSectorMap(sectorMap, backgroundSettings, runtimeCatalog, log); + return global::Galaxy.ProjectObservedSectorMap(sectorMap, backgroundSettings, runtimeCatalog, log); } - public static void Project( + public static Galaxy Project( AetheriaRuntimeDaemonFrameDocument frame, SectorBackgroundSettings backgroundSettings, AetheriaRuntimeCatalogSnapshot runtimeCatalog, @@ -24,14 +22,14 @@ public static class AetheriaUnityObservedRunProjection if (frame == null || frame.Run == null) throw new ArgumentNullException(nameof(frame)); - Galaxy = global::Galaxy.ProjectObservedDaemonRun(frame.Run, backgroundSettings, runtimeCatalog, log); + return global::Galaxy.ProjectObservedDaemonRun(frame.Run, backgroundSettings, runtimeCatalog, log); } - public static GalaxyZone FindZone(int daemonZoneIndex) + public static GalaxyZone FindZone(Galaxy galaxy, int daemonZoneIndex) { return daemonZoneIndex < 0 ? null - : Galaxy?.Zones?.FirstOrDefault(zone => zone != null && zone.ZoneIndex == daemonZoneIndex); + : galaxy?.Zones?.FirstOrDefault(zone => zone != null && zone.ZoneIndex == daemonZoneIndex); } public static AetheriaRuntimeZoneSnapshotCommit FindZoneSnapshot(AetheriaRuntimeRunCheckpointCommit run) diff --git a/Assets/Scripts/UI/MainMenu.cs b/Assets/Scripts/UI/MainMenu.cs index 8314ee43c..451a638cf 100644 --- a/Assets/Scripts/UI/MainMenu.cs +++ b/Assets/Scripts/UI/MainMenu.cs @@ -18,7 +18,6 @@ public class MainMenu : MonoBehaviour private UIDocument _menuSurfaceDocument; private string _clientStatePath; - private AetheriaRuntimeCatalogSnapshot _catalog; private Func _canOpenRuntimeInputScreen; private Action _openRuntimeInputScreen; private readonly AetheriaEveUnitySurfaceChrome _menuSurfaceChrome = new AetheriaEveUnitySurfaceChrome @@ -119,7 +118,7 @@ public class MainMenu : MonoBehaviour return false; } - var generatorState = "Loading runtime catalog"; + var generatorState = "Loading daemon sector"; Action setState = s => generatorState = s; HideMenuSurface(); @@ -129,20 +128,6 @@ public class MainMenu : MonoBehaviour Dialog.Show(); setState($"Observing sector-map frame {sectorMap.FrameId}"); - var backgroundSettings = sectorMap.IsTutorial ? Settings.TutorialBackgroundSettings : Settings.SectorBackgroundSettings; - backgroundSettings.NoisePosition = sectorMap.GenerationSeed == 0 ? 1 : sectorMap.GenerationSeed; - var runtimeCatalog = LatestRuntimeCatalog(stateBoot); - if (runtimeCatalog == null) - { - Debug.LogWarning($"Cannot start Aetheria observer scene without a runtime catalog in {stateBoot.StateFilePath}."); - return false; - } - - AetheriaUnityObservedRunProjection.Project( - sectorMap, - backgroundSettings, - runtimeCatalog, - Debug.Log); SceneManager.LoadScene("ARPG"); return true; } @@ -210,26 +195,6 @@ public class MainMenu : MonoBehaviour } } - private AetheriaRuntimeCatalogSnapshot LatestRuntimeCatalog(AetheriaRuntimeStateBootReport stateBoot) - { - if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists) - return null; - - if (_catalog != null) - return _catalog; - - try - { - _catalog = ResolveClient(stateBoot).Aetheria().LatestCatalog(); - return _catalog; - } - catch (Exception ex) - { - Debug.LogError($"Failed to read typed Aetheria runtime catalog for the main menu: {ex}"); - return null; - } - } - private void ContinueGame() { if (!TryStartDaemonObservedGame("Continuing Daemon Run")) @@ -493,17 +458,11 @@ public class MainMenu : MonoBehaviour if (!string.Equals(_clientStatePath, statePath, StringComparison.Ordinal)) { _clientStatePath = statePath; - ClearClientCaches(); } return AetheriaUnityRuntimeClientProvider.ResolveClient(stateBoot, "unity-main-menu"); } - private void ClearClientCaches() - { - _catalog = null; - } - private void OnDestroy() { if (_menuSurfaceDocument != null)