using System; using System.Collections.Generic; using System.Linq; #nullable enable namespace GameCult.Aetheria.State.Verse { public sealed class AetheriaRuntimeDaemonTickOptions { public string DaemonId { get; set; } = "aetheria-daemon"; public string SessionId { get; set; } = "local"; public string VerseId { get; set; } = "aetheria.local"; public string CultMeshAddress { get; set; } = "cultmesh://aetheria.local/eve/providers/aetheria.daemon"; public long FrameId { get; set; } public double SimulationTimeSeconds { get; set; } public double FixedDeltaSeconds { get; set; } public IReadOnlyList ObservedCommands { get; set; } = Array.Empty(); public IReadOnlyList AccountedCommandIds { get; set; } = Array.Empty(); public IReadOnlyList PreRejectedCommandIds { get; set; } = Array.Empty(); public IReadOnlyList CumulativeAppliedCommandIds { get; set; } = Array.Empty(); public IReadOnlyList CumulativeRejectedCommandIds { get; set; } = Array.Empty(); public AetheriaRuntimeDaemonOperationContext OperationContext { get; set; } = new AetheriaRuntimeDaemonOperationContext(); public AetheriaRuntimeStarbridgeScenarioDocument? StarbridgeScenario { get; set; } public AetheriaRuntimeStarbridgeSessionDocument? StarbridgeSession { get; set; } public AetheriaRuntimeCatalogSnapshot? Catalog { get; set; } public AetheriaRuntimeDaemonRenderSettings RenderSettings { get; set; } = AetheriaRuntimeDaemonRenderSettings.AetheriaDefault; public AetheriaRuntimeDaemonSimulationSettings SimulationSettings { get; set; } = AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault; public bool BuildPublications { get; set; } = true; } public sealed class AetheriaRuntimeDaemonTickResult { public AetheriaRuntimeDaemonTickResult( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonOperationResult operationResult, AetheriaRuntimeDaemonFrameDocument frame, AetheriaRuntimeDaemonSoaViewDocument? soaView = null, AetheriaRuntimeDaemonProviderAdvertisementDocument? providerAdvertisement = null, AetheriaRuntimeDaemonHealthDocument? health = null, AetheriaRuntimeDaemonCommandBoundaryDocument? commandBoundary = null, AetheriaRuntimeAssetManifestDocument? assetManifest = null, AetheriaRuntimeStarbridgeSessionSummaryDocument? starbridgeSessionSummary = null, AetheriaRuntimeSurfaceDocument? gameSurface = null, AetheriaRuntimeSurfaceDocument? gameTuiSurface = null, AetheriaRuntimeSurfaceDocument? editorSurface = null, AetheriaRuntimeSurfaceDocument? editorTuiSurface = null) { Run = run ?? new AetheriaRuntimeRunCheckpointCommit(); OperationResult = operationResult ?? new AetheriaRuntimeDaemonOperationResult( Run, Array.Empty(), Array.Empty()); Frame = frame ?? new AetheriaRuntimeDaemonFrameDocument(); SoaView = soaView; ProviderAdvertisement = providerAdvertisement; Health = health; CommandBoundary = commandBoundary; AssetManifest = assetManifest; StarbridgeSessionSummary = starbridgeSessionSummary; GameSurface = gameSurface; GameTuiSurface = gameTuiSurface; EditorSurface = editorSurface; EditorTuiSurface = editorTuiSurface; } public AetheriaRuntimeRunCheckpointCommit Run { get; } public AetheriaRuntimeDaemonOperationResult OperationResult { get; } public AetheriaRuntimeDaemonFrameDocument Frame { get; } public AetheriaRuntimeDaemonSoaViewDocument? SoaView { get; } public AetheriaRuntimeDaemonProviderAdvertisementDocument? ProviderAdvertisement { get; } public AetheriaRuntimeDaemonHealthDocument? Health { get; } public AetheriaRuntimeDaemonCommandBoundaryDocument? CommandBoundary { get; } public AetheriaRuntimeAssetManifestDocument? AssetManifest { get; } public AetheriaRuntimeStarbridgeSessionSummaryDocument? StarbridgeSessionSummary { get; } public AetheriaRuntimeSurfaceDocument? GameSurface { get; } public AetheriaRuntimeSurfaceDocument? GameTuiSurface { get; } public AetheriaRuntimeSurfaceDocument? EditorSurface { get; } public AetheriaRuntimeSurfaceDocument? EditorTuiSurface { get; } public AetheriaRuntimeDaemonIntentState Intents => OperationResult.Intents; } public static class AetheriaRuntimeDaemonTickRunner { public static AetheriaRuntimeDaemonTickResult Tick( string stateFilePath, AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonTickOptions options) { if (string.IsNullOrWhiteSpace(stateFilePath)) throw new ArgumentException("State file path must be non-empty.", nameof(stateFilePath)); run ??= new AetheriaRuntimeRunCheckpointCommit(); options ??= new AetheriaRuntimeDaemonTickOptions(); options.OperationContext ??= new AetheriaRuntimeDaemonOperationContext(); var observedCommands = (options.ObservedCommands ?? Array.Empty()) .Where(command => command != null) .ToArray(); var accountedBeforeTick = new HashSet( options.AccountedCommandIds ?? Array.Empty(), StringComparer.Ordinal); var commands = observedCommands .Where(command => !string.IsNullOrWhiteSpace(command.CommandId)) .Where(command => !accountedBeforeTick.Contains(command.CommandId)) .ToArray(); var operationResult = AetheriaRuntimeDaemonOperations.Execute( run, commands, options.OperationContext); var preRejectedCommandIds = (options.PreRejectedCommandIds ?? Array.Empty()) .Where(commandId => !string.IsNullOrWhiteSpace(commandId)) .Distinct(StringComparer.Ordinal) .ToArray(); if (preRejectedCommandIds.Length > 0) { operationResult = new AetheriaRuntimeDaemonOperationResult( operationResult.Run, operationResult.AppliedCommandIds, preRejectedCommandIds .Concat(operationResult.RejectedCommandIds) .Distinct(StringComparer.Ordinal) .ToArray(), operationResult.Intents); } AetheriaRuntimeDaemonSimulation.Step( operationResult.Run, operationResult.Intents, options.FixedDeltaSeconds, options.SimulationSettings); StampZoneSimulationTime(operationResult.Run, options.SimulationTimeSeconds); var frame = AetheriaRuntimeDaemonFrameDocument.Create( operationResult.Run, options.DaemonId, options.SessionId, options.FrameId, options.SimulationTimeSeconds, options.FixedDeltaSeconds, renderSettings: options.RenderSettings, simulationSettings: options.SimulationSettings); frame.AppliedCommandIds = operationResult.AppliedCommandIds; frame.RejectedCommandIds = operationResult.RejectedCommandIds; frame.AccountedCommandIds = accountedBeforeTick .Concat(operationResult.AppliedCommandIds) .Concat(operationResult.RejectedCommandIds) .Where(commandId => !string.IsNullOrWhiteSpace(commandId)) .Distinct(StringComparer.Ordinal) .ToArray(); frame.CumulativeAppliedCommandIds = (options.CumulativeAppliedCommandIds ?? Array.Empty()) .Concat(operationResult.AppliedCommandIds) .Where(commandId => !string.IsNullOrWhiteSpace(commandId)) .Distinct(StringComparer.Ordinal) .ToArray(); frame.CumulativeRejectedCommandIds = (options.CumulativeRejectedCommandIds ?? Array.Empty()) .Concat(operationResult.RejectedCommandIds) .Where(commandId => !string.IsNullOrWhiteSpace(commandId)) .Distinct(StringComparer.Ordinal) .ToArray(); frame.Capabilities = new[] { "aetheria.daemon.operation_execute.v1", "aetheria.daemon.intent_state.v1", "aetheria.daemon.authoritative_frame.v1", AetheriaRuntimeDaemonSchemas.AssetManifest, AetheriaRuntimeDaemonSchemas.ProviderAdvertisement, AetheriaRuntimeDaemonSchemas.Health, AetheriaRuntimeDaemonSchemas.CommandBoundary, AetheriaRuntimeDaemonSchemas.StarbridgeSessionSummary, AetheriaRuntimeDaemonSchemas.GameSurface, AetheriaRuntimeDaemonSchemas.EditorSurface }; if (!options.BuildPublications) { return new AetheriaRuntimeDaemonTickResult( operationResult.Run, operationResult, frame); } var soaView = AetheriaRuntimeDaemonSoaFramePublisher.BuildCurrentZoneEntities(stateFilePath, frame); var commandBoundary = AetheriaRuntimeDaemonCommandBoundaryDocument.Create(options.DaemonId); var providerAdvertisement = AetheriaRuntimeDaemonProviderAdvertisementDocument.Create( stateFilePath, options.DaemonId, options.VerseId, options.CultMeshAddress); var health = new AetheriaRuntimeDaemonHealthDocument { DaemonId = string.IsNullOrWhiteSpace(options.DaemonId) ? "aetheria-daemon" : options.DaemonId, VerseId = string.IsNullOrWhiteSpace(options.VerseId) ? "aetheria.local" : options.VerseId, PublishedAtUtc = DateTime.UtcNow.ToString("O"), StatePath = stateFilePath, FrameId = frame.FrameId, ObservedCommandCount = observedCommands.Length, AppliedCommandCount = operationResult.AppliedCommandIds.Count, RejectedCommandCount = operationResult.RejectedCommandIds.Count, Status = operationResult.RejectedCommandIds.Count == 0 ? "healthy" : "commands-rejected", PublicationSource = "daemon-published", Transport = "cultmesh-managed", CommandBoundaryPath = AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary.ToString() }; var catalog = options.Catalog ?? new AetheriaRuntimeCatalogSnapshot( Array.Empty(), Array.Empty(), Array.Empty()); var assetManifest = AetheriaRuntimeAssets.ProjectManifest( catalog, frame.Run?.RunId ?? "", "cultmesh://aetheria.local/assets"); var starbridgeSummary = AetheriaRuntimeStarbridgeDocuments.SessionSummary( frame, options.StarbridgeScenario, options.StarbridgeSession, catalog); var gameSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( frame, health, commandBoundary, starbridgeSummary); var designerSurfaces = new[] { AetheriaRuntimeStatRecipeSurfaceBuilder.BuildFromCatalog(catalog), AetheriaRuntimeTradeValuePolicySurfaceBuilder.BuildFromCatalog(catalog) }; var editorSurface = AetheriaRuntimeDaemonEditorSurfaceBuilder.Build( providerAdvertisement, health, commandBoundary, designerSurfaces); return new AetheriaRuntimeDaemonTickResult( operationResult.Run, operationResult, frame, soaView, providerAdvertisement, health, commandBoundary, assetManifest, starbridgeSummary, gameSurface, gameSurface, editorSurface, editorSurface); } private static void StampZoneSimulationTime( AetheriaRuntimeRunCheckpointCommit run, double simulationTimeSeconds) { foreach (var zone in run?.Zones ?? Array.Empty()) { if (zone != null) zone.SimulationTimeSeconds = simulationTimeSeconds; } } } }