From 672b133ee16c8ecd7dcb924e3d06cc515f0b3b8f Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 16 Jun 2026 06:09:46 +0100 Subject: [PATCH] Route Continue through typed run state --- .voidbot/state/aetheria.cc | Bin 145748 -> 145947 bytes Aetheria.State.Verify/Program.cs | 61 ++++++++++++++++ Assets/Scripts/Gameplay/ActionGameManager.cs | 31 +++++++- Assets/Scripts/UI/MainMenu.cs | 73 ++++++++++++++++++- docs/aetheria-perfect-machine-map.md | 15 +++- 5 files changed, 172 insertions(+), 8 deletions(-) diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index c13e868338a70dc8f3a47cc2eb3201ea5de672df..1a9bf94e80af8b8d0bef8fc9cc5a665211ee59cf 100644 GIT binary patch delta 1372 zcmZuxO=uNY6z+_F(FiUQOc#}$t(Z)riS26&f)tUU2^vK~#d6=g^WI!DbMJWXoxHqm zqPUVE3L^}t&_xx|jem>RZnXO@TH$;D}~w2;oR?h=R4myum2so z{eI~2U4MB0+;nYjc3&_(v#(&2{#W_${*_OHH5=TS%MGi8;H=BsnGbCQ@2_XOHy-(C zZE=BdF`1yq;Cm?y7a4+(ZO|BFXf&8kiVaYi0Bx8-;6fW@4UkQ!BNr`TAhI}ydS;9i z!M!kMJo&@BU||<5s-kQS2hl{RI9tlZ35TNvOLZiNZHY+9tQA3i`Px|Vc--I2tKt!n z!8U4R8B7pX3k53TVP7G%Sj<*C&{Q&U|C+53*)oSq;*8jsAm&n)FM7yjM_lBWUyiKx z`|fj_045p`OUHZZ*x>*UhKA4%vji8IU@cUD!l3Grs35~kDT>37QeX+uc1cdcl!wb% z3QD#$w7H37WGU6KO(}t7eLKNGol1{(6B*>JR2pSmkx>Gvt z09%oqW|%4@CXI6FNFFgvkqnNdrB$)FRw>^7?2Z2L_SZ#RH5Ef>OSMe-GK(9)lQc$h zp0R2YZ74lK!~QREJC44%Won%}A~}0Ma#~?#yQqrv=LlpJG97is1o?KwKDCA=-E7#J zWvZm?nhqW^5k^uiVN?J4MfmL2hb_bN^9ziIMtl@0&|8MS+?KW?b%Wvj`>cZ& zOY<8KcNgbJy^-SB9dEq5=y~Th968#@syNg8%2>|Lm7DZ3Pc=B3l)_J1dq7AVM)u5~ z-`v$5-{h_D$)DahFgMpnCT`ZqpYO3{{r_v$eK75HznedPiuSe1NhpOE+$ktGy#ns7 zeA)lSjJq8hoC|kA$hO5#vG-=Qx>kE<&fGPuMG9Z)t|#8MEjz;U?mVI$H$3UC8}Gp{ zgQK-~rg#$gV}rog?zFwyT0s^Ur~EJJ(rqrz27WR~{N`JC1B<@{e{B%0&AMp!>XiRt L>-GwyRy*?%V`=d3 delta 1279 zcma)6ON$dh5O%UY*2hi7i$dv5bSIm2apNi~D9ZXm;wrc-NN1*!X?Lc3?CvoeMRO3m zcu^wc;K7R`cu^3H7ty;H4<3B}1rMTkt2>j4;zbC_q`La6ufF=G9{e2t@MHY>qtb@y z+3D(RWyY^oXBO^xn|lZS((3NJ(yiiP-*nX;bmiFcD7JUf#l}~Hx8=pol_$S;HC{J0kI|yQb8dLA(0`*gpf*&8fxzPNo?;1);+wqspHpB z8>#TOBGMJv6f;YBoB}zA#me5aU?nFGO+-SF||knfMRx-gv$;KD8y&vEXCrOoTTz8 zO~f)__OBC*jH`|Rl!+jcs)h~ybw&NfmtSkv*J`I(N?F#mg`&>&mS7>92IL;@HKD|7&4aR*(GqucZcN9Qv>O=Kj|HiB;a6UHvl^ z@8$kO$1jl28{Cc#uVkF~<(*B?4Cs-C?oGDMxEDEh>#*J4>BRfEsaP+xMIIT}B&lxp z_X%&^n*L(uJ$$wPkA&{~u`T(Esrr@4VjsO81@{hj(dyogaw#berl#CM?|He@83kvi T?Uw4Foh-dsTSWI%stdmXbF|B; diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 7cf2fe78e..3dc04a364 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -13,6 +13,7 @@ RequireEveRuntimeBootstrap(root); RequireNoRendererLocalConsole(root); RequireNoRendererLocalDebugPanels(root); RequireMainMenuSettingsCommit(root); +RequireMainMenuContinueRunState(root); RequirePropertiesPanelReadOnlyInspector(root); RequireRuntimeSimulationTuningCommits(root); RequireHullConductivityCommitAuthority(root); @@ -467,6 +468,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: 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: reflection inspection is read-only display"); Console.WriteLine("Runtime simulation tuning authority: UI writes flow through gameplay checkpoint commits"); Console.WriteLine("Hull conductivity authority: inventory UI toggles flow through gameplay checkpoint commits"); @@ -778,6 +780,65 @@ static void RequireMainMenuSettingsCommit(string root) } } +static void RequireMainMenuContinueRunState(string root) +{ + var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"); + var mainMenu = File.Exists(mainMenuPath) + ? File.ReadAllText(mainMenuPath) + : throw new InvalidOperationException("Cannot verify main-menu Continue path; MainMenu.cs is missing."); + + var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); + var actionGameManager = File.Exists(actionGameManagerPath) + ? File.ReadAllText(actionGameManagerPath) + : throw new InvalidOperationException("Cannot verify Continue run authority; ActionGameManager.cs is missing."); + + var requiredMenuSymbols = new[] + { + "LatestContinueRun", + "AetheriaRuntimeCatalogStore", + "ReadRunStates(ActionGameManager.RuntimeStateFilePath)", + "ContinueGame(continueRun)", + "ActionGameManager.ContinueRunState = run", + "SceneManager.LoadScene(\"ARPG\")" + }; + + var missingMenuSymbols = requiredMenuSymbols + .Where(symbol => !mainMenu.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + + if (missingMenuSymbols.Length > 0) + { + throw new InvalidOperationException( + "MainMenu Continue no longer selects typed run state: " + + string.Join(", ", missingMenuSymbols)); + } + + if (mainMenu.Contains("AddButton(\"Continue\", null)", StringComparison.Ordinal)) + { + throw new InvalidOperationException("MainMenu Continue regressed to a null button."); + } + + var requiredGameplaySymbols = new[] + { + "AetheriaRuntimeRunStateSnapshot ContinueRunState", + "ResolveStartZone(continuingRun)", + "if (continuingRun != null)", + "ContinueRunState = null", + "RestoreDroppedPickupsFromTypedZoneState" + }; + + var missingGameplaySymbols = requiredGameplaySymbols + .Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + + if (missingGameplaySymbols.Length > 0) + { + throw new InvalidOperationException( + "ActionGameManager no longer has the typed Continue boot path: " + + string.Join(", ", missingGameplaySymbols)); + } +} + static void RequirePropertiesPanelReadOnlyInspector(string root) { var propertiesPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Properties Panel", "PropertiesPanel.cs"); diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 67b8ee0de..641c3fb8b 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -46,7 +46,7 @@ public class ActionGameManager : MonoBehaviour } private static string _runtimeStateFilePath; - private static string RuntimeStateFilePath => + public static string RuntimeStateFilePath => _runtimeStateFilePath ??= AetheriaRuntimeStateBoundary.GetStateFilePath(GameDataDirectory); private static RuntimePlayerSettings _runtimePlayerSettings; @@ -315,6 +315,7 @@ public class ActionGameManager : MonoBehaviour public static Galaxy CurrentGalaxy; public static bool IsTutorial; + public static AetheriaRuntimeRunStateSnapshot ContinueRunState { get; set; } public static AetheriaRuntimeCatalogSnapshot RuntimeCatalog { get; private set; } public GameSettings Settings; @@ -2420,8 +2421,17 @@ public class ActionGameManager : MonoBehaviour { if (CurrentGalaxy != null) { - SectorMap.QueueZoneReveal(CurrentGalaxy.Entrance.AdjacentZones.Prepend(CurrentGalaxy.Entrance)); - PopulateLevel(CurrentGalaxy.Entrance); + var continuingRun = ContinueRunState; + var startZone = ResolveStartZone(continuingRun); + SectorMap.QueueZoneReveal(startZone.AdjacentZones.Prepend(startZone)); + PopulateLevel(startZone); + + if (continuingRun != null) + { + ContinueRunState = null; + return; + } + var loadoutGenerator = new LoadoutGenerator(ref ItemManager.Random, ItemManager, RuntimeCatalog, CurrentGalaxy, Zone.GalaxyZone, IsTutorial ? CurrentGalaxy.ResolveFaction(Settings.TutorialGenerationSettings.ProtagonistFaction) : null, 2); var ship = EntityConstructionBlueprintProjector.InstantiateFromBlueprint( ItemManager, @@ -2437,6 +2447,21 @@ public class ActionGameManager : MonoBehaviour } } + private static GalaxyZone ResolveStartZone(AetheriaRuntimeRunStateSnapshot run) + { + if (CurrentGalaxy?.Zones == null || CurrentGalaxy.Zones.Length == 0) + return CurrentGalaxy?.Entrance; + + if (run != null && + run.CurrentZoneIndex >= 0 && + run.CurrentZoneIndex < CurrentGalaxy.Zones.Length) + { + return CurrentGalaxy.Zones[run.CurrentZoneIndex]; + } + + return CurrentGalaxy.Entrance; + } + private IEnumerator IntroCutscene(Ship ship) { ZoneRenderer.PerspectiveEntity = ship; diff --git a/Assets/Scripts/UI/MainMenu.cs b/Assets/Scripts/UI/MainMenu.cs index 0e4bf36f2..5fd3c337e 100644 --- a/Assets/Scripts/UI/MainMenu.cs +++ b/Assets/Scripts/UI/MainMenu.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; +using GameCult.Aetheria.State.Unity; using TMPro; using UniRx; using UnityEngine; @@ -88,7 +89,8 @@ public class MainMenu : MonoBehaviour _nextMenu.panel.Title.text = TitleSubtitle("aetheria", "terminus"); if (!InGame) { - _nextMenu.panel.AddButton("Continue", null); + var continueRun = LatestContinueRun(); + _nextMenu.panel.AddButton("Continue", continueRun == null ? null : () => ContinueGame(continueRun)); } _nextMenu.panel.AddButton("New Game", () => @@ -171,6 +173,75 @@ public class MainMenu : MonoBehaviour return seed == 0 ? 1u : seed; } + private static AetheriaRuntimeRunStateSnapshot LatestContinueRun() + { + try + { + return AetheriaRuntimeCatalogStore + .ReadRunStates(ActionGameManager.RuntimeStateFilePath) + .Where(run => !string.IsNullOrWhiteSpace(run.RunId)) + .OrderByDescending(run => run.UpdatedAtUtc, StringComparer.Ordinal) + .ThenByDescending(run => run.RunId, StringComparer.Ordinal) + .FirstOrDefault(); + } + catch (Exception ex) + { + Debug.LogError($"Failed to read typed Aetheria run state for Continue: {ex}"); + return null; + } + } + + private void ContinueGame(AetheriaRuntimeRunStateSnapshot run) + { + if (run == null) + return; + + var generatorState = "Loading typed run"; + Action setState = s => generatorState = s; + + Fade(true); + _nextMenu.panel.gameObject.SetActive(false); + Dialog.Clear(); + Dialog.Title.text = "Continuing Run"; + Dialog.AddProperty(() => generatorState); + Dialog.Show(); + + ActionGameManager.IsTutorial = run.IsTutorial; + ActionGameManager.ContinueRunState = run; + var generationSeed = run.GenerationSeed == 0 ? NextGenerationSeed() : run.GenerationSeed; + var sectorSettings = run.IsTutorial ? Settings.TutorialGenerationSettings : Settings.SectorGenerationSettings; + var backgroundSettings = run.IsTutorial ? Settings.TutorialBackgroundSettings : Settings.SectorBackgroundSettings; + backgroundSettings.NoisePosition = generationSeed; + + Task.Run(() => + { + var sector = run.IsTutorial + ? new Galaxy( + Settings.TutorialGenerationSettings, + Settings.TutorialBackgroundSettings, + Settings.NameGeneratorSettings, + ActionGameManager.RuntimeCatalog, + ActionGameManager.RuntimePlayerSettings, + ActionGameManager.GameDataDirectory.CreateSubdirectory("Narrative"), + Debug.Log, + setState, + generationSeed) + : new Galaxy( + sectorSettings, + backgroundSettings, + Settings.NameGeneratorSettings, + ActionGameManager.RuntimeCatalog, + Debug.Log, + setState, + generationSeed); + Observable.NextFrame().Subscribe(_ => + { + ActionGameManager.CurrentGalaxy = sector; + SceneManager.LoadScene("ARPG"); + }); + }).WrapErrors(); + } + private void ShowSettings() { _nextMenu.panel.Clear(); diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index adf13584a..c055f0a5f 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -962,11 +962,18 @@ First Aetheria surfaces to publish: zone load. `AetheriaRuntimeZoneStateSnapshot.RecordKey` preserves the exact typed zone identity, and `ActionGameManager` restores only the matching `RunId + ZoneIndex` pickup rows through `ZoneRenderer.DropItem`. + - Done: replace the main-menu null `Continue` button with a typed run-state + entry point. `MainMenu` reads available `AetheriaRunState` records through + `AetheriaRuntimeCatalogStore`, selects the newest run, regenerates the + galaxy from the saved generation seed, and passes the selected run to + `ActionGameManager` so boot enters the saved current zone and lowers typed + pickup rows. Entity/player reconstruction from `AetheriaEntitySnapshot` + remains future Continue work, not a fake restored ship. - Remaining: add typed documents/mappers for runtime object graphs, - complete Continue/run restore from typed state, typed behavior factory - construction, remaining behavior-private state not covered by progress, - weapon, sensor, radiator, reactor, or capacitor rows, and any catalog - fields not covered by the stable scalar/fingerprint/payload pass. + complete Continue/run entity reconstruction from typed state, typed + behavior factory construction, remaining behavior-private state not covered + by progress, weapon, sensor, radiator, reactor, or capacitor rows, and any + catalog fields not covered by the stable scalar/fingerprint/payload pass. 4. Runtime cutover - Done: add a Unity-facing typed catalog read facade and smoke proving it can