Aetheria/Aetheria.State/AetheriaEveSurfaceDocuments.cs
2026-07-17 07:28:53 +01:00

379 lines
19 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Aetheria.State.Documents;
using GameCult.Aetheria.State.Verse;
using GameCult.Mesh;
using EveCommandTemplate = GameCult.Eve.Surface.EveCommandTemplate;
using EveCommandReceiptDocument = GameCult.Eve.Surface.EveCommandReceiptDocument;
using EveAdvertisedCommand = GameCult.Eve.Surface.EveAdvertisedCommand;
using EveAdvertisedSurface = GameCult.Eve.Surface.EveAdvertisedSurface;
using EveProviderAdvertisementDocument = GameCult.Eve.Surface.EveProviderAdvertisementDocument;
using EveProviderFreshness = GameCult.Eve.Surface.EveProviderFreshness;
using EveProviderWitness = GameCult.Eve.Surface.EveProviderWitness;
using EveStyleToken = GameCult.Eve.Surface.EveStyleToken;
using EveSurfaceComponent = GameCult.Eve.Surface.EveSurfaceComponent;
using EveSurfaceDocument = GameCult.Eve.Surface.EveSurfaceDocument;
using EveSurfaceTree = GameCult.Eve.Surface.EveSurfaceTree;
using EveWorldInteractionAdvertisement = GameCult.Eve.Surface.EveWorldInteractionAdvertisement;
namespace Aetheria.State;
public static class AetheriaEveSurfaceDocuments
{
public const string ProviderId = "aetheria";
public const string ProviderAdvertisementKey = "eve:provider:aetheria";
public const string CatalogSurfaceKey = "eve:surface:aetheria.catalog.operator";
public const string OperationsSurfaceKey = "eve:surface:aetheria.operations";
public const string PlayerSettingsSurfaceKey = "eve:surface:aetheria.player_settings";
public const string DaemonGameSurfaceKey = "eve:surface:aetheria.daemon.game";
public const string DaemonGameTuiSurfaceKey = "eve:surface:aetheria.daemon.game.tui";
public const string DaemonEditorSurfaceKey = "eve:surface:aetheria.daemon.editor";
public const string DaemonEditorTuiSurfaceKey = "eve:surface:aetheria.daemon.editor.tui";
public const string CatalogSurfaceId = AetheriaRuntimeCatalogCommands.SurfaceId;
public const string OperationsSurfaceId = AetheriaRuntimeOperationsCommands.SurfaceId;
public const string PlayerSettingsSurfaceId = AetheriaRuntimePlayerSettingsCommands.SurfaceId;
private const string DaemonCommandBoundaryId = "aetheria.daemon.commands";
private const string DaemonRecordTransport = "cultmesh-record";
public static EveSurfaceDocument BuildCatalogSurface(
AetheriaRuntimeCatalogSnapshot catalog,
string updatedAtUtc,
long version = 1)
{
var tradeItems = catalog.TradeItems.Take(12).Select(item =>
Row(
$"item.{SafeId(item.ItemKey)}",
("name", item.Name),
("manufacturer", catalog.GetManufacturer(item)?.Name ?? "GameCult"),
("price", item.Price.ToString("N0")),
("size", item.ShapeWidth > 0 && item.ShapeHeight > 0 ? $"{item.ShapeWidth}x{item.ShapeHeight}" : "")))
.ToArray();
var corporations = catalog.Corporations.Take(12).Select(corporation =>
Row(
$"corporation.{SafeId(corporation.CorporationKey)}",
("name", corporation.Name),
("short", corporation.ShortName),
("names", catalog.GetNameFile(corporation)?.Name ?? ""),
("influence", corporation.InfluenceDistance.ToString())))
.ToArray();
var refreshCommand = CultMesh.OperationBindingRecord(
CultMesh.OperationBinding(
AetheriaRuntimeCatalogCommands.Refresh,
label: "Refresh",
routeHint: new CultMeshRouteHint(
CultMeshLocalityKind.Automatic,
"cultmesh")));
return new EveSurfaceDocument(
ProviderId,
"game.runtime",
"Aetheria Catalog",
version,
updatedAtUtc,
new EveSurfaceTree(
CatalogSurfaceId,
Node(
"aetheria.catalog.root",
"surface",
[],
Node(
"aetheria.catalog.summary",
"grid",
[("columns", "6")],
Metric("summary.items", "Items", catalog.Items.Count.ToString()),
Metric("summary.trade", "Trade Items", catalog.TradeItems.Count().ToString()),
Metric("summary.equipment", "Equipment", catalog.EquipmentItems.Count().ToString()),
Metric("summary.behaviors", "Behavior Kinds", catalog.Items.SelectMany(item => item.BehaviorKinds).Distinct().Count().ToString()),
Metric("summary.corporations", "Corporations", catalog.Corporations.Count.ToString()),
Metric("summary.nameFiles", "Name Files", catalog.NameFiles.Count.ToString())),
Node(
"aetheria.catalog.trade",
"card",
[("title", "Trade Catalog")],
Node("aetheria.catalog.trade.rows", "inspector.kv", [], tradeItems)),
Node(
"aetheria.catalog.corporations",
"card",
[("title", "Corporations")],
Node("aetheria.catalog.corporation.rows", "inspector.kv", [], corporations))),
Array.Empty<EveStyleToken>()),
new[]
{
new EveCommandTemplate(refreshCommand.ToBinding())
});
}
public static EveSurfaceDocument BuildOperationsSurface(
AetheriaEveCommandAcceptanceStatus? eveCommandStatus = null,
AetheriaVerseHostSettings? verseHostSettings = null,
AetheriaRuntimeSession? runtimeSession = null,
long version = 1)
{
var normalizedVerseHost = AetheriaVerseHostSettingsNormalizer.Normalize(verseHostSettings);
var updatedAtUtc = LatestTimestamp(
eveCommandStatus?.LastPollAtUtc,
normalizedVerseHost.LastUpdatedAtUtc,
runtimeSession?.LastSeenAtUtc);
var refreshCommand = CultMesh.OperationBindingRecord(
CultMesh.OperationBinding(
AetheriaRuntimeOperationsCommands.Refresh,
label: "Refresh",
routeHint: new CultMeshRouteHint(
CultMeshLocalityKind.Automatic,
"cultmesh")));
return new EveSurfaceDocument(
ProviderId,
"game.runtime",
"Aetheria Operations",
version,
updatedAtUtc,
new EveSurfaceTree(
OperationsSurfaceId,
Node(
"aetheria.operations.root",
"surface",
[],
Node(
"aetheria.operations.eveCommandAcceptance",
"card",
[("title", "Eve Request Acceptance")],
Metric("eveCommandAcceptance.status", "Status", eveCommandStatus?.Status ?? "missing"),
Metric("eveCommandAcceptance.observed", "Observed Before Accept", (eveCommandStatus?.ObservedBeforeAccept ?? 0).ToString()),
Metric("eveCommandAcceptance.accepted", "Commands Accepted", (eveCommandStatus?.CommandsAccepted ?? 0).ToString()),
Metric("eveCommandAcceptance.rejected", "Commands Rejected", (eveCommandStatus?.CommandsRejected ?? 0).ToString()),
Metric("eveCommandAcceptance.catalogRefreshes", "Catalog Refreshes", (eveCommandStatus?.AppliedCatalogRefreshes ?? 0).ToString()),
Metric("eveCommandAcceptance.operationsRefreshes", "Operations Refreshes", (eveCommandStatus?.AppliedOperationsRefreshes ?? 0).ToString()),
Metric("eveCommandAcceptance.playerSettings", "Player Settings Commands", (eveCommandStatus?.AppliedPlayerSettingsCommands ?? 0).ToString()),
Metric("eveCommandAcceptance.inputSettings", "Input Settings Commands", (eveCommandStatus?.AppliedInputSettingsCommands ?? 0).ToString()),
Metric("eveCommandAcceptance.loadoutTemplates", "Loadout Template Commands", (eveCommandStatus?.AppliedLoadoutTemplateCommands ?? 0).ToString()),
Metric("eveCommandAcceptance.verseHost", "Verse Host Commands", (eveCommandStatus?.AppliedVerseHostCommands ?? 0).ToString()),
Metric("eveCommandAcceptance.failures", "Consecutive Failures", (eveCommandStatus?.ConsecutiveFailures ?? 0).ToString()),
Row(
"eveCommandAcceptance.last",
("runtime", eveCommandStatus?.RuntimeId ?? ""),
("lastPoll", eveCommandStatus?.LastPollAtUtc ?? ""),
("lastAccepted", eveCommandStatus?.LastAcceptedAtUtc ?? ""),
("lastRejected", eveCommandStatus?.LastRejectedCommand ?? ""),
("rejectedReason", eveCommandStatus?.LastRejectedReason ?? ""),
("error", eveCommandStatus?.LastError ?? ""))),
Node(
"aetheria.operations.verseHost",
"card",
[("title", "Verse Host")],
Metric("verseHost.visibility", "Visibility", normalizedVerseHost.Visibility),
Metric("verseHost.service", "Service", normalizedVerseHost.ServiceId),
Metric("verseHost.verse", "Verse", normalizedVerseHost.VerseId),
Row(
"verseHost.identity",
("rootVerse", normalizedVerseHost.RootVerse),
("canonicalService", normalizedVerseHost.CanonicalService),
("locatedService", normalizedVerseHost.LocatedService),
("cultMeshAddress", normalizedVerseHost.CultMeshAddress))),
Node(
"aetheria.operations.runtimeSession",
"card",
[("title", "Runtime Session")],
Metric("runtimeSession.status", "Status", runtimeSession?.Status ?? "missing"),
Metric("runtimeSession.role", "Role", runtimeSession?.Role ?? ""),
Row(
"runtimeSession.last",
("runtime", runtimeSession?.RuntimeId ?? ""),
("started", runtimeSession?.StartedAtUtc ?? ""),
("lastSeen", runtimeSession?.LastSeenAtUtc ?? "")))),
Array.Empty<EveStyleToken>()),
new[]
{
new EveCommandTemplate(refreshCommand.ToBinding())
});
}
public static EveSurfaceDocument BuildPlayerSettingsSurface(
AetheriaPlayerSettings? settings,
string updatedAtUtc,
long version = 1)
{
settings ??= new AetheriaPlayerSettings();
var gameplay = settings.Gameplay ?? new AetheriaPlayerGameplaySettings();
var graphics = settings.Graphics ?? new AetheriaPlayerGraphicsSettings();
var publishedAtUtc = !string.IsNullOrWhiteSpace(settings.LastUpdatedAtUtc)
? settings.LastUpdatedAtUtc
: updatedAtUtc;
var surface = AetheriaRuntimePlayerSettingsSurfaceBuilder.Build(
settings.PlayerName,
settings.TutorialPassed,
settings.ActiveRunKey,
gameplay.TemperatureUnit,
gameplay.SignificantDigits,
graphics.NebulaQuality,
graphics.ShowAsteroidsInMinimap,
publishedAtUtc,
version);
return AetheriaRuntimeSurfaceDocuments.ToPortableSurface(surface);
}
public static EveProviderAdvertisementDocument BuildProviderAdvertisement(
AetheriaVerseHostSettings settings,
string statePath,
string updatedAtUtc)
{
var normalized = AetheriaVerseHostSettingsNormalizer.Normalize(settings);
var schemas = new[]
{
"aetheria.world_state.v1",
"aetheria.item_definition.v1",
"aetheria.corporation.v2",
"aetheria.name_file.v2",
"aetheria.trade_value_policy.v1",
"aetheria.player_settings.v1",
"aetheria.game_session.v1",
"aetheria.loadout_template.v1",
"aetheria.run_state.v1",
"aetheria.zone_state.v1",
"aetheria.entity_snapshot.v1",
"aetheria.verse_host_settings.v1",
"aetheria.runtime_session.v1",
"aetheria.eve_command_acceptance_status.v1",
EveSurfaceDocument.SchemaId,
AetheriaRuntimeEveCommandDocument.SchemaId,
AetheriaRuntimeDaemonSchemas.ProviderAdvertisement,
AetheriaRuntimeDaemonSchemas.Frame,
AetheriaRuntimeDaemonSchemas.SoaView,
AetheriaRuntimeDaemonSchemas.Health,
AetheriaRuntimeDaemonSchemas.CommandBoundary,
AetheriaRuntimeDaemonSchemas.GameSurface,
AetheriaRuntimeDaemonSchemas.EditorSurface,
AetheriaRuntimeDaemonSchemas.Command
};
var witnesses = new[]
{
Witness("cultcache", statePath, "Aetheria typed CultCache state file"),
Witness(DaemonRecordTransport, AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement.ToString(),
"Aetheria daemon-owned provider advertisement record"),
Witness(DaemonRecordTransport, AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest.ToString(),
"Aetheria daemon latest simulation frame record"),
Witness(DaemonRecordTransport, AetheriaRuntimeVerseRecordKeys.DaemonSoaViewLatest.ToString(),
"Aetheria daemon latest SoA view record for thin clients"),
Witness(DaemonRecordTransport, AetheriaRuntimeVerseRecordKeys.DaemonHealth.ToString(),
"Aetheria daemon health record"),
Witness(DaemonRecordTransport, AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary.ToString(),
"Aetheria daemon typed command boundary record"),
Witness(DaemonRecordTransport, AetheriaRuntimeVerseRecordKeys.DaemonGameSurface.ToString(),
"Aetheria daemon game Eve GUI surface record"),
Witness(DaemonRecordTransport, AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface.ToString(),
"Aetheria daemon game Eve TUI surface record"),
Witness(DaemonRecordTransport, AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface.ToString(),
"Aetheria daemon editor Eve GUI surface record"),
Witness(DaemonRecordTransport, AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface.ToString(),
"Aetheria daemon editor Eve TUI surface record")
};
var commands = new[]
{
Command(DaemonCommandBoundaryId, "", "Aetheria daemon typed command boundary"),
Command(AetheriaRuntimeCatalogCommands.Refresh, AetheriaRuntimeCatalogCommands.SurfaceId, "Refresh catalog state"),
Command(AetheriaRuntimeOperationsCommands.Refresh, AetheriaRuntimeOperationsCommands.SurfaceId, "Refresh operations state"),
Command(AetheriaRuntimePlayerSettingsCommands.Refresh, AetheriaRuntimePlayerSettingsCommands.SurfaceId, "Refresh player settings state"),
Command(AetheriaRuntimePlayerSettingsCommands.CycleTemperatureUnit, AetheriaRuntimePlayerSettingsCommands.SurfaceId, "Cycle the typed player temperature unit"),
Command(AetheriaRuntimePlayerSettingsCommands.DecrementSignificantDigits, AetheriaRuntimePlayerSettingsCommands.SurfaceId, "Decrease typed player significant digits"),
Command(AetheriaRuntimePlayerSettingsCommands.IncrementSignificantDigits, AetheriaRuntimePlayerSettingsCommands.SurfaceId, "Increase typed player significant digits"),
Command(AetheriaRuntimePlayerSettingsCommands.CycleNebulaQuality, AetheriaRuntimePlayerSettingsCommands.SurfaceId, "Cycle typed player nebula quality"),
Command(AetheriaRuntimePlayerSettingsCommands.ToggleShowAsteroidsInMinimap, AetheriaRuntimePlayerSettingsCommands.SurfaceId, "Toggle typed player minimap asteroid visibility")
};
return new EveProviderAdvertisementDocument(
ProviderId,
normalized.ServiceId,
normalized.VerseId,
normalized.Title,
"game.runtime",
normalized.CultMeshAddress,
updatedAtUtc,
new EveProviderFreshness("fresh", updatedAtUtc, 15000),
schemas,
witnesses,
AetheriaRuntimeEveSurfaceCatalog.All.Select(ToProviderSurface).ToArray(),
commands,
new[] { AetheriaRuntimeDaemonSoaFramePublisher.ProducerId });
}
private static EveProviderWitness Witness(string kind, string reference, string summary) =>
new(kind, reference, summary);
private static EveAdvertisedCommand Command(string command, string surfaceId, string summary) =>
new(command, surfaceId, "cultmesh", summary);
private static EveAdvertisedSurface ToProviderSurface(AetheriaRuntimeEveSurfaceAdvertisement surface)
{
var interaction = surface.WorldInteraction == null
? null
: new EveWorldInteractionAdvertisement(
surface.WorldInteraction.ProjectionKind,
surface.WorldInteraction.StateSchemas,
surface.WorldInteraction.CommandBoundary,
AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary.ToString(),
EveCommandReceiptDocument.SchemaId,
AetheriaRuntimeVerseRecordKeys.EveReceiptRecordPrefix,
AetheriaRuntimeVerseRecordKeys.EveAssetCatalog.ToString(),
surface.WorldInteraction.LoweringTargets,
surface.WorldInteraction.Ownership);
return new EveAdvertisedSurface(
surface.SurfaceId,
EveSurfaceDocument.SchemaId,
surface.RecordRef,
surface.Transport,
surface.Status,
surface.SurfaceKind,
interaction);
}
private static string LatestTimestamp(params string?[] timestamps)
{
var latest = "";
foreach (var timestamp in timestamps)
{
if (!string.IsNullOrWhiteSpace(timestamp) &&
(string.IsNullOrWhiteSpace(latest) || string.CompareOrdinal(timestamp, latest) > 0))
{
latest = timestamp;
}
}
return latest;
}
private static EveSurfaceComponent Metric(string id, string label, string value)
{
return Node(id, "metric", [("label", label), ("value", value)]);
}
private static EveSurfaceComponent Row(string id, params (string Key, string Value)[] props)
{
return Node(id, "row", props);
}
private static EveSurfaceComponent Node(
string id,
string kind,
(string Key, string Value)[] props,
params EveSurfaceComponent[] children)
{
return new EveSurfaceComponent(
id,
kind,
props.ToDictionary(prop => prop.Key, prop => prop.Value),
children);
}
private static string SafeId(string value)
{
return string.IsNullOrWhiteSpace(value)
? "empty"
: new string(value.Select(character => char.IsLetterOrDigit(character) ? character : '.').ToArray());
}
}