diff --git a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs index bd8ed0fa9..a6347619f 100644 --- a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs +++ b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs @@ -510,6 +510,23 @@ public class DaemonRuntimeDocumentTests CollectionAssert.Contains(providerAdvertisement.PublishedSchemas, AetheriaRuntimeDaemonSchemas.CommandBoundary); CollectionAssert.Contains(providerAdvertisement.PublishedSchemas, AetheriaRuntimeDaemonSchemas.GameSurface); CollectionAssert.Contains(providerAdvertisement.PublishedSchemas, AetheriaRuntimeDaemonSchemas.EditorSurface); + var advertisedGameSurface = providerAdvertisement.EveSurfaces.Single(surface => + surface.SurfaceId == AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId); + Assert.AreEqual("interactive-world", advertisedGameSurface.SurfaceKind); + Assert.AreEqual(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface.ToString(), advertisedGameSurface.RecordRef); + Assert.IsNotNull(advertisedGameSurface.WorldInteraction); + Assert.AreEqual( + "provider-authored-world-surface", + advertisedGameSurface.WorldInteraction.ProjectionKind); + Assert.AreEqual("aetheria.daemon.commands", advertisedGameSurface.WorldInteraction.CommandBoundary); + Assert.AreEqual( + AetheriaRuntimeDaemonSchemas.EveCommandAcceptanceStatus, + advertisedGameSurface.WorldInteraction.ReceiptSchema); + Assert.AreEqual( + "provider-owns-world-state-assets-command-acceptance-and-receipts", + advertisedGameSurface.WorldInteraction.Ownership); + CollectionAssert.Contains(advertisedGameSurface.WorldInteraction.LoweringTargets, "unity-scene"); + CollectionAssert.Contains(advertisedGameSurface.WorldInteraction.StateSchemas, AetheriaRuntimeDaemonSchemas.Frame); var health = result.Health; Assert.IsNotNull(health); Assert.AreEqual(AetheriaRuntimeDaemonSchemas.Health, health.Schema); @@ -2899,10 +2916,25 @@ public class DaemonRuntimeDocumentTests var presentation = runtime.Connect(); Assert.AreEqual("aetheria-cultmesh-cultcache-transport", liveBridge.TransportKind); + Assert.AreEqual(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface.ToString(), liveBridge.SurfacePointer); Assert.AreEqual(AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest.ToString(), liveBridge.AssetManifestPointer); + Assert.AreEqual("interactive-world", liveBridge.CurrentSurfaceDocument.AdvertisedSurface.SurfaceKind); + Assert.AreEqual( + "provider-authored-world-surface", + liveBridge.CurrentSurfaceDocument.AdvertisedSurface.WorldInteraction.ProjectionKind); + Assert.AreEqual( + "aetheria.daemon.commands", + liveBridge.CurrentSurfaceDocument.AdvertisedSurface.WorldInteraction.CommandBoundary); + Assert.AreEqual( + AetheriaRuntimeDaemonSchemas.EveCommandAcceptanceStatus, + liveBridge.CurrentSurfaceDocument.AdvertisedSurface.WorldInteraction.ReceiptSchema); Assert.AreEqual(1, presentation.ActiveEntities); Assert.AreEqual(1, sceneSink.Upserts.Count); Assert.IsNotNull(runtime.ActiveWorld); + Assert.IsNotNull(runtime.ActiveProjection); + Assert.AreEqual("provider-authored-world-surface", runtime.ActiveProjection.ProjectionKind); + Assert.AreEqual("aetheria.daemon.commands", runtime.ActiveProjection.CommandBoundary); + Assert.AreEqual(AetheriaRuntimeDaemonSchemas.EveCommandAcceptanceStatus, runtime.ActiveProjection.ReceiptSchema); Assert.AreEqual( AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest.ToString(), runtime.ActiveWorld.StatePointerId); @@ -2938,7 +2970,7 @@ public class DaemonRuntimeDocumentTests Assert.AreEqual("aetheria.daemon", moveIntent.ProviderId); Assert.AreEqual(AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId, moveIntent.SurfaceId); Assert.AreEqual("aetheria.daemon.commands", moveIntent.CommandBoundary); - Assert.AreEqual("aetheria.eve_command_acceptance_status.v1", moveIntent.ReceiptSchema); + Assert.AreEqual(AetheriaRuntimeDaemonSchemas.EveCommandAcceptanceStatus, moveIntent.ReceiptSchema); Assert.AreEqual( AetheriaRuntimeDaemonSurfaceCommandCatalog.CommandName(AetheriaRuntimeDaemonCommandKinds.SetMoveVector), moveIntent.Payload.GetString("commandId")); diff --git a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySceneProviderBridge.cs b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySceneProviderBridge.cs index 368299052..df9d10771 100644 --- a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySceneProviderBridge.cs +++ b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySceneProviderBridge.cs @@ -115,7 +115,10 @@ namespace GameCult.Aetheria.EveRuntime var surface = ReadSurface(runtimeState, stateBoot); if (surface != null) { - CurrentDocument = CreateSurfaceDocument(surface, surface.Version); + CurrentDocument = CreateSurfaceDocument( + surface, + surface.Version, + ReadAdvertisedSurface(runtimeState)); DocumentAvailable?.Invoke(CurrentDocument); } @@ -198,26 +201,40 @@ namespace GameCult.Aetheria.EveRuntime return ToUnityPlayableWorldAssetManifest(manifest); } + private AetheriaRuntimeEveSurfaceAdvertisement? ReadAdvertisedSurface(AetheriaClientState runtimeState) + { + var provider = runtimeState.ProviderAdvertisement.Latest(); + return (provider.EveSurfaces ?? Array.Empty()) + .FirstOrDefault(surface => surface != null && string.Equals(surface.SurfaceId, _surfaceId, StringComparison.Ordinal)) + ?? AetheriaRuntimeEveSurfaceCatalog.Find(_surfaceId); + } + private EveUnitySceneProviderSurfaceDocument CreateSurfaceDocument( EveSurfaceDocument surface, - long version) + long version, + AetheriaRuntimeEveSurfaceAdvertisement? advertisedSurface = null) { + advertisedSurface ??= AetheriaRuntimeEveSurfaceCatalog.Find(_surfaceId); + var worldInteraction = advertisedSurface?.WorldInteraction; return new EveUnitySceneProviderSurfaceDocument( surface, new EveUnitySceneProviderSurfaceAdvertisement( _surfaceId, - "interactive-world", + advertisedSurface?.SurfaceKind ?? "", new EveUnitySceneWorldInteraction( - "provider-authored-world-surface", - "aetheria.daemon.commands", - "aetheria.eve_command_acceptance_status.v1", - "provider-owns-world-state-assets-command-acceptance-and-receipts")), - SourcePointer(), + worldInteraction?.ProjectionKind ?? "", + worldInteraction?.CommandBoundary ?? "", + worldInteraction?.ReceiptSchema ?? "", + worldInteraction?.Ownership ?? "")), + SourcePointer(advertisedSurface), version); } - private string SourcePointer() + private string SourcePointer(AetheriaRuntimeEveSurfaceAdvertisement? advertisedSurface) { + if (!string.IsNullOrWhiteSpace(advertisedSurface?.RecordRef)) + return advertisedSurface.RecordRef; + var path = _stateBoot?.StateFilePath ?? _stateFilePathOverride; return string.IsNullOrWhiteSpace(path) ? AetheriaRuntimeVerseRecordKeys.DaemonGameSurface.ToString() diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs index e1020b663..232d0583e 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs @@ -88,6 +88,7 @@ namespace GameCult.Aetheria.State.Verse public const string ProviderAdvertisement = "gamecult.aetheria.daemon_provider_advertisement.v1"; public const string Health = "gamecult.aetheria.daemon_health.v1"; public const string CommandBoundary = "gamecult.aetheria.daemon_command_boundary.v1"; + public const string EveCommandAcceptanceStatus = "aetheria.eve_command_acceptance_status.v1"; public const string GameSurface = "gamecult.aetheria.daemon_game_surface.v1"; public const string EditorSurface = "gamecult.aetheria.daemon_editor_surface.v1"; } diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonEditorSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonEditorSurfaceBuilder.cs index 66c978352..63ba1bc1d 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonEditorSurfaceBuilder.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonEditorSurfaceBuilder.cs @@ -156,6 +156,9 @@ namespace GameCult.Aetheria.State.Verse ("title", surface.Title), ("surfaceId", surface.SurfaceId), ("record", surface.RecordRef), + ("kind", surface.SurfaceKind), + ("projection", surface.WorldInteraction?.ProjectionKind ?? ""), + ("commandBoundary", surface.WorldInteraction?.CommandBoundary ?? ""), ("status", surface.Status), ("audience", surface.Audience), ("provider", surface.ProviderId) diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceCatalog.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceCatalog.cs index ca0bc772c..399641dd3 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceCatalog.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceCatalog.cs @@ -42,6 +42,34 @@ namespace GameCult.Aetheria.State.Verse [Key(10)] public IReadOnlyList Commands { get; set; } = Array.Empty(); + + [Key(11)] + public string SurfaceKind { get; set; } = ""; + + [Key(12)] + public AetheriaRuntimeEveWorldInteractionAdvertisement? WorldInteraction { get; set; } + } + + [MessagePackObject] + public sealed class AetheriaRuntimeEveWorldInteractionAdvertisement + { + [Key(0)] + public string ProjectionKind { get; set; } = ""; + + [Key(1)] + public IReadOnlyList StateSchemas { get; set; } = Array.Empty(); + + [Key(2)] + public IReadOnlyList LoweringTargets { get; set; } = Array.Empty(); + + [Key(3)] + public string CommandBoundary { get; set; } = ""; + + [Key(4)] + public string ReceiptSchema { get; set; } = ""; + + [Key(5)] + public string Ownership { get; set; } = ""; } public static class AetheriaRuntimeEveSurfaceCatalog @@ -59,10 +87,10 @@ namespace GameCult.Aetheria.State.Verse AetheriaRuntimePlayerSettingsCommands.IncrementSignificantDigits, AetheriaRuntimePlayerSettingsCommands.CycleNebulaQuality, AetheriaRuntimePlayerSettingsCommands.ToggleShowAsteroidsInMinimap), - Surface(AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId, "Aetheria Daemon", AetheriaRuntimeVerseRecordKeys.DaemonGameSurface.ToString(), "player", "Authoritative daemon game surface.", providerId: "aetheria.daemon", providerKind: "game.daemon", transport: "cultmesh-record"), - Surface(AetheriaRuntimeDaemonGameSurfaceBuilder.TuiSurfaceId, "Aetheria Daemon TUI", AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface.ToString(), "agent", "Compact daemon game surface for dense inspection.", providerId: "aetheria.daemon", providerKind: "game.daemon", transport: "cultmesh-record"), - Surface(AetheriaRuntimeDaemonEditorSurfaceBuilder.SurfaceId, "Aetheria Daemon Editor", AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface.ToString(), "operator", "Daemon editor and surface inventory.", providerId: "aetheria.daemon", providerKind: "editor.daemon", transport: "cultmesh-record"), - Surface(AetheriaRuntimeDaemonEditorSurfaceBuilder.TuiSurfaceId, "Aetheria Daemon Editor TUI", AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface.ToString(), "agent", "Compact daemon editor and surface inventory.", providerId: "aetheria.daemon", providerKind: "editor.daemon", transport: "cultmesh-record"), + Surface(AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId, "Aetheria Daemon", AetheriaRuntimeVerseRecordKeys.DaemonGameSurface.ToString(), "player", "Authoritative daemon game surface.", providerId: "aetheria.daemon", providerKind: "game.daemon", transport: "cultmesh-record", surfaceKind: "interactive-world", worldInteraction: WorldInteraction("provider-authored-world-surface")), + Surface(AetheriaRuntimeDaemonGameSurfaceBuilder.TuiSurfaceId, "Aetheria Daemon TUI", AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface.ToString(), "agent", "Compact daemon game surface for dense inspection.", providerId: "aetheria.daemon", providerKind: "game.daemon", transport: "cultmesh-record", surfaceKind: "interactive-world", worldInteraction: WorldInteraction("provider-authored-world-surface")), + Surface(AetheriaRuntimeDaemonEditorSurfaceBuilder.SurfaceId, "Aetheria Daemon Editor", AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface.ToString(), "operator", "Daemon editor and surface inventory.", providerId: "aetheria.daemon", providerKind: "editor.daemon", transport: "cultmesh-record", surfaceKind: "interactive-world-editor", worldInteraction: WorldInteraction("provider-authored-world-editor-surface")), + Surface(AetheriaRuntimeDaemonEditorSurfaceBuilder.TuiSurfaceId, "Aetheria Daemon Editor TUI", AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface.ToString(), "agent", "Compact daemon editor and surface inventory.", providerId: "aetheria.daemon", providerKind: "editor.daemon", transport: "cultmesh-record", surfaceKind: "interactive-world-editor", worldInteraction: WorldInteraction("provider-authored-world-editor-surface")), Surface(AetheriaRuntimeMainMenuCommands.RootSurfaceId, "Main Menu", AetheriaRuntimeVerseRecordKeys.MainMenuSurface.ToString(), "player", "Runtime main menu root.", transport: "cultmesh-record"), Surface(AetheriaRuntimeMainMenuCommands.SettingsSurfaceId, "Main Menu Settings", AetheriaRuntimeVerseRecordKeys.MainMenuSettingsSurface.ToString(), "player", "Runtime main menu settings panel.", transport: "cultmesh-record"), Surface(AetheriaRuntimeMainMenuCommands.InputSettingsSurfaceId, "Main Menu Input Settings", AetheriaRuntimeVerseRecordKeys.MainMenuInputSettingsSurface.ToString(), "player", "Main menu input settings panel.", transport: "cultmesh-record"), @@ -92,6 +120,16 @@ namespace GameCult.Aetheria.State.Verse public static IReadOnlyList SurfaceSchemas { get; } = new[] { SurfaceSchema }; + public static AetheriaRuntimeEveSurfaceAdvertisement? Find(string surfaceId) + { + if (string.IsNullOrWhiteSpace(surfaceId)) + return null; + + return Surfaces.FirstOrDefault(surface => + surface != null && + string.Equals(surface.SurfaceId, surfaceId, StringComparison.Ordinal)); + } + private static AetheriaRuntimeEveSurfaceAdvertisement Surface( string surfaceId, string title, @@ -100,7 +138,17 @@ namespace GameCult.Aetheria.State.Verse string summary, params string[] commands) { - return Surface(surfaceId, title, recordRef, audience, summary, "aetheria", "game.runtime", "cultmesh-managed", "available", commands); + return Surface( + surfaceId, + title, + recordRef, + audience, + summary, + "aetheria", + "game.runtime", + "cultmesh-managed", + "available", + commands: commands); } private static AetheriaRuntimeEveSurfaceAdvertisement Surface( @@ -113,6 +161,8 @@ namespace GameCult.Aetheria.State.Verse string providerKind = "game.runtime", string transport = "cultmesh-managed", string status = "available", + string surfaceKind = "", + AetheriaRuntimeEveWorldInteractionAdvertisement? worldInteraction = null, params string[] commands) { return new AetheriaRuntimeEveSurfaceAdvertisement @@ -127,7 +177,33 @@ namespace GameCult.Aetheria.State.Verse Audience = audience, Mode = "interactive", Summary = summary, - Commands = commands?.Where(command => !string.IsNullOrWhiteSpace(command)).ToArray() ?? Array.Empty() + Commands = commands?.Where(command => !string.IsNullOrWhiteSpace(command)).ToArray() ?? Array.Empty(), + SurfaceKind = surfaceKind ?? "", + WorldInteraction = worldInteraction + }; + } + + private static AetheriaRuntimeEveWorldInteractionAdvertisement WorldInteraction(string projectionKind) + { + return new AetheriaRuntimeEveWorldInteractionAdvertisement + { + ProjectionKind = projectionKind ?? "", + StateSchemas = new[] + { + AetheriaRuntimeDaemonSchemas.Frame, + AetheriaRuntimeDaemonSchemas.AssetManifest + }, + LoweringTargets = new[] + { + "web-reference", + "unity-uitoolkit", + "unity-scene", + "electron-shell", + "tui" + }, + CommandBoundary = "aetheria.daemon.commands", + ReceiptSchema = AetheriaRuntimeDaemonSchemas.EveCommandAcceptanceStatus, + Ownership = "provider-owns-world-state-assets-command-acceptance-and-receipts" }; } }