Move observed sector projection into gameplay boot

This commit is contained in:
Meta Vens 2026-06-28 08:34:28 +01:00
commit 5b7a5b3a8c
5 changed files with 79 additions and 65 deletions

View file

@ -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",

View file

@ -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,

View file

@ -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; }
}

View file

@ -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<string> 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)

View file

@ -18,7 +18,6 @@ public class MainMenu : MonoBehaviour
private UIDocument _menuSurfaceDocument;
private string _clientStatePath;
private AetheriaRuntimeCatalogSnapshot _catalog;
private Func<bool> _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<string> 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)