Publish player settings as a provider-owned Eve surface
This commit is contained in:
parent
69c5811cf5
commit
be6d7b3b25
14 changed files with 534 additions and 70 deletions
Binary file not shown.
|
|
@ -51,6 +51,7 @@ var eveStatus = new AetheriaEveCommandDrainStatus
|
|||
CommandsRejected = eveReport.RejectedCommands,
|
||||
AppliedCatalogRefreshes = eveReport.AppliedCatalogRefreshes,
|
||||
AppliedOperationsRefreshes = eveReport.AppliedOperationsRefreshes,
|
||||
AppliedPlayerSettingsCommands = eveReport.AppliedPlayerSettingsCommands,
|
||||
LastRejectedCommand = eveReport.LastRejectedCommand,
|
||||
LastRejectedReason = eveReport.LastRejectedReason,
|
||||
Status = eveReport.RejectedCommands > 0 ? "rejected" : "ok"
|
||||
|
|
@ -67,6 +68,7 @@ var completedSession = new AetheriaRuntimeSession
|
|||
};
|
||||
await node.PutRuntimeSessionAsync(completedSession);
|
||||
await node.PutOperationsSurfaceAsync(AetheriaOperationsSurfaceProjector.Build(commitStatus, eveStatus, completedSession));
|
||||
await PublishPlayerSettingsSurfaceAsync(node, now);
|
||||
await node.PutProviderAdvertisementAsync(AetheriaProviderAdvertisementProjector.Build(statePath, now));
|
||||
await node.FlushAsync();
|
||||
|
||||
|
|
@ -79,6 +81,7 @@ Console.WriteLine($"Eve commands accepted: {eveReport.AcceptedPaths.Length}");
|
|||
Console.WriteLine($"Eve commands rejected: {eveReport.RejectedCommands}");
|
||||
Console.WriteLine($"Eve catalog refreshes: {eveReport.AppliedCatalogRefreshes}");
|
||||
Console.WriteLine($"Eve operations refreshes: {eveReport.AppliedOperationsRefreshes}");
|
||||
Console.WriteLine($"Eve player settings commands: {eveReport.AppliedPlayerSettingsCommands}");
|
||||
if (!deleteApplied)
|
||||
Console.WriteLine("Applied command files were kept because --keep was supplied.");
|
||||
|
||||
|
|
@ -97,3 +100,13 @@ static int CountPendingEveCommands(string statePath)
|
|||
? Directory.EnumerateFiles(pendingDirectory, "*.cc").Count()
|
||||
: 0;
|
||||
}
|
||||
|
||||
static async Task PublishPlayerSettingsSurfaceAsync(AetheriaStateNode node, string updatedAtUtc)
|
||||
{
|
||||
var settings = await node.GetPlayerSettingsAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings();
|
||||
var publishedAtUtc = string.IsNullOrWhiteSpace(settings.LastUpdatedAtUtc)
|
||||
? updatedAtUtc
|
||||
: settings.LastUpdatedAtUtc;
|
||||
await node.PutPlayerSettingsSurfaceAsync(
|
||||
AetheriaPlayerSettingsSurfaceProjector.Build(settings, publishedAtUtc)).ConfigureAwait(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,46 +119,6 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s
|
|||
drainStatus,
|
||||
runtimeSession: await node.GetRuntimeSessionAsync("smoke-runtime")));
|
||||
|
||||
AetheriaRuntimeEveCommandLog.QueueCommand(
|
||||
statePath,
|
||||
new EveSurfaceCommandRequest(
|
||||
AetheriaProviderAdvertisementProjector.ProviderId,
|
||||
AetheriaCatalogSurfaceProjector.SurfaceId,
|
||||
"aetheria.catalog.refresh",
|
||||
new Dictionary<string, string> { ["source"] = "state-smoke" },
|
||||
DateTimeOffset.UtcNow,
|
||||
"aetheria-state-smoke"));
|
||||
AetheriaRuntimeEveCommandLog.QueueCommand(
|
||||
statePath,
|
||||
new EveSurfaceCommandRequest(
|
||||
AetheriaProviderAdvertisementProjector.ProviderId,
|
||||
AetheriaCatalogSurfaceProjector.SurfaceId,
|
||||
"aetheria.catalog.unknown",
|
||||
new Dictionary<string, string> { ["source"] = "state-smoke" },
|
||||
DateTimeOffset.UtcNow,
|
||||
"aetheria-state-smoke"));
|
||||
var eveCommandReport = await AetheriaEveCommandBridge.ApplyPendingAsync(node);
|
||||
var eveCommandStatus = new AetheriaEveCommandDrainStatus
|
||||
{
|
||||
RuntimeId = "smoke-runtime",
|
||||
StatePath = statePath,
|
||||
LastPollAtUtc = now,
|
||||
LastAcceptedAtUtc = now,
|
||||
PendingBeforeApply = 2,
|
||||
CommandsAccepted = eveCommandReport.AcceptedPaths.Length,
|
||||
CommandsRejected = eveCommandReport.RejectedCommands,
|
||||
AppliedCatalogRefreshes = eveCommandReport.AppliedCatalogRefreshes,
|
||||
AppliedOperationsRefreshes = eveCommandReport.AppliedOperationsRefreshes,
|
||||
LastRejectedCommand = eveCommandReport.LastRejectedCommand,
|
||||
LastRejectedReason = eveCommandReport.LastRejectedReason,
|
||||
Status = eveCommandReport.RejectedCommands > 0 ? "rejected" : "ok"
|
||||
};
|
||||
await node.PutEveCommandDrainStatusAsync(eveCommandStatus);
|
||||
await node.PutOperationsSurfaceAsync(AetheriaOperationsSurfaceProjector.Build(
|
||||
drainStatus,
|
||||
eveCommandStatus,
|
||||
await node.GetRuntimeSessionAsync("smoke-runtime")));
|
||||
|
||||
await node.PutLoadoutTemplateAsync(loadoutKey, new AetheriaLoadoutTemplate
|
||||
{
|
||||
Name = "Smoke Aether Runner",
|
||||
|
|
@ -515,6 +475,69 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s
|
|||
ActionBarInputs = ["<Keyboard>/1", "<Mouse>/leftButton"]
|
||||
}
|
||||
});
|
||||
await node.PutPlayerSettingsSurfaceAsync(
|
||||
AetheriaPlayerSettingsSurfaceProjector.Build(
|
||||
await node.GetPlayerSettingsAsync(),
|
||||
now));
|
||||
|
||||
AetheriaRuntimeEveCommandLog.QueueCommand(
|
||||
statePath,
|
||||
new EveSurfaceCommandRequest(
|
||||
AetheriaProviderAdvertisementProjector.ProviderId,
|
||||
AetheriaCatalogSurfaceProjector.SurfaceId,
|
||||
"aetheria.catalog.refresh",
|
||||
new Dictionary<string, string> { ["source"] = "state-smoke" },
|
||||
DateTimeOffset.UtcNow,
|
||||
"aetheria-state-smoke"));
|
||||
AetheriaRuntimeEveCommandLog.QueueCommand(
|
||||
statePath,
|
||||
new EveSurfaceCommandRequest(
|
||||
AetheriaProviderAdvertisementProjector.ProviderId,
|
||||
AetheriaPlayerSettingsSurfaceProjector.SurfaceId,
|
||||
"aetheria.player_settings.gameplay.significant_digits.increment",
|
||||
new Dictionary<string, string> { ["source"] = "state-smoke" },
|
||||
DateTimeOffset.UtcNow,
|
||||
"aetheria-state-smoke"));
|
||||
AetheriaRuntimeEveCommandLog.QueueCommand(
|
||||
statePath,
|
||||
new EveSurfaceCommandRequest(
|
||||
AetheriaProviderAdvertisementProjector.ProviderId,
|
||||
AetheriaPlayerSettingsSurfaceProjector.SurfaceId,
|
||||
"aetheria.player_settings.graphics.show_asteroids.toggle",
|
||||
new Dictionary<string, string> { ["source"] = "state-smoke" },
|
||||
DateTimeOffset.UtcNow,
|
||||
"aetheria-state-smoke"));
|
||||
AetheriaRuntimeEveCommandLog.QueueCommand(
|
||||
statePath,
|
||||
new EveSurfaceCommandRequest(
|
||||
AetheriaProviderAdvertisementProjector.ProviderId,
|
||||
AetheriaCatalogSurfaceProjector.SurfaceId,
|
||||
"aetheria.catalog.unknown",
|
||||
new Dictionary<string, string> { ["source"] = "state-smoke" },
|
||||
DateTimeOffset.UtcNow,
|
||||
"aetheria-state-smoke"));
|
||||
var eveCommandReport = await AetheriaEveCommandBridge.ApplyPendingAsync(node);
|
||||
var eveCommandStatus = new AetheriaEveCommandDrainStatus
|
||||
{
|
||||
RuntimeId = "smoke-runtime",
|
||||
StatePath = statePath,
|
||||
LastPollAtUtc = now,
|
||||
LastAcceptedAtUtc = now,
|
||||
PendingBeforeApply = 4,
|
||||
CommandsAccepted = eveCommandReport.AcceptedPaths.Length,
|
||||
CommandsRejected = eveCommandReport.RejectedCommands,
|
||||
AppliedCatalogRefreshes = eveCommandReport.AppliedCatalogRefreshes,
|
||||
AppliedOperationsRefreshes = eveCommandReport.AppliedOperationsRefreshes,
|
||||
AppliedPlayerSettingsCommands = eveCommandReport.AppliedPlayerSettingsCommands,
|
||||
LastRejectedCommand = eveCommandReport.LastRejectedCommand,
|
||||
LastRejectedReason = eveCommandReport.LastRejectedReason,
|
||||
Status = eveCommandReport.RejectedCommands > 0 ? "rejected" : "ok"
|
||||
};
|
||||
await node.PutEveCommandDrainStatusAsync(eveCommandStatus);
|
||||
await node.PutOperationsSurfaceAsync(AetheriaOperationsSurfaceProjector.Build(
|
||||
drainStatus,
|
||||
eveCommandStatus,
|
||||
await node.GetRuntimeSessionAsync("smoke-runtime")));
|
||||
|
||||
await node.FlushAsync();
|
||||
}
|
||||
|
|
@ -530,6 +553,7 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether
|
|||
var drainStatus = await reopened.GetRuntimeCommitDrainStatusAsync();
|
||||
var eveCommandStatus = await reopened.GetEveCommandDrainStatusAsync();
|
||||
var operationsSurface = await reopened.GetOperationsSurfaceAsync();
|
||||
var playerSettingsSurface = await reopened.GetPlayerSettingsSurfaceAsync();
|
||||
var advertisement = await reopened.GetProviderAdvertisementAsync();
|
||||
var runtimeSession = await reopened.GetRuntimeSessionAsync("smoke-runtime");
|
||||
var playerSettings = await reopened.GetPlayerSettingsAsync();
|
||||
|
|
@ -571,9 +595,10 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether
|
|||
|
||||
if (drainStatus?.RuntimeId != "smoke-runtime" ||
|
||||
drainStatus.CommandsApplied != 1 ||
|
||||
eveCommandStatus?.CommandsAccepted != 1 ||
|
||||
eveCommandStatus?.CommandsAccepted != 3 ||
|
||||
eveCommandStatus.CommandsRejected != 1 ||
|
||||
eveCommandStatus.AppliedCatalogRefreshes != 1 ||
|
||||
eveCommandStatus.AppliedPlayerSettingsCommands != 2 ||
|
||||
!eveCommandStatus.LastRejectedReason.Contains("not advertised", StringComparison.Ordinal) ||
|
||||
operationsSurface?.Surface.Id != AetheriaOperationsSurfaceProjector.SurfaceId ||
|
||||
!operationsSurface.Surface.Root.Children.Any(child => child.Id == "aetheria.operations.eveCommandDrain") ||
|
||||
|
|
@ -583,10 +608,12 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether
|
|||
}
|
||||
|
||||
if (advertisement?.ProviderId != AetheriaProviderAdvertisementProjector.ProviderId ||
|
||||
advertisement.Surfaces.Length < 2 ||
|
||||
advertisement.Surfaces.Length < 3 ||
|
||||
!advertisement.Surfaces.Any(surface => surface.SurfaceId == AetheriaCatalogSurfaceProjector.SurfaceId) ||
|
||||
!advertisement.Surfaces.Any(surface => surface.SurfaceId == AetheriaOperationsSurfaceProjector.SurfaceId) ||
|
||||
!advertisement.Surfaces.Any(surface => surface.SurfaceId == AetheriaPlayerSettingsSurfaceProjector.SurfaceId) ||
|
||||
!advertisement.Schemas.Contains("aetheria.runtime_session.v1") ||
|
||||
!advertisement.Commands.Any(command => command.Command == "aetheria.player_settings.graphics.show_asteroids.toggle") ||
|
||||
!advertisement.Schemas.Contains(AetheriaEveCommandBridge.CommandSchema))
|
||||
{
|
||||
throw new InvalidOperationException("Aetheria Eve provider advertisement did not survive flush/reopen.");
|
||||
|
|
@ -603,14 +630,22 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether
|
|||
playerSettings.PlayerName != "Smoke Pilot" ||
|
||||
!playerSettings.TutorialPassed ||
|
||||
playerSettings.StoryFileHashes.Length != 1 ||
|
||||
playerSettings.Gameplay.SignificantDigits != 4 ||
|
||||
playerSettings.Gameplay.SignificantDigits != 5 ||
|
||||
playerSettings.Graphics.NebulaQuality != "High" ||
|
||||
playerSettings.Graphics.ShowAsteroidsInMinimap ||
|
||||
playerSettings.Input.BindingOverrides.Length != 1 ||
|
||||
playerSettings.Input.ActionBarInputs.Length != 2)
|
||||
{
|
||||
throw new InvalidOperationException("Player settings did not survive flush/reopen.");
|
||||
}
|
||||
|
||||
if (playerSettingsSurface?.Surface.Id != AetheriaPlayerSettingsSurfaceProjector.SurfaceId ||
|
||||
!playerSettingsSurface.Surface.Root.Children.Any(child => child.Id == "aetheria.playerSettings.gameplay") ||
|
||||
!playerSettingsSurface.Surface.Root.Children.Any(child => child.Id == "aetheria.playerSettings.graphics"))
|
||||
{
|
||||
throw new InvalidOperationException("Player settings Eve surface did not survive flush/reopen.");
|
||||
}
|
||||
|
||||
if (loadout?.RootEntity.Hull.ItemKey != AetheriaCatalogKeys.ItemDefinitionFromLegacyId(itemLegacyId).ToString() ||
|
||||
loadout.RootEntity.Equipment.Length != 1 ||
|
||||
loadout.RootEntity.Equipment[0].Item.Enabled ||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ RequireEveRuntimeBootstrap(root);
|
|||
RequireNoRendererLocalConsole(root);
|
||||
RequireNoRendererLocalDebugPanels(root);
|
||||
RequireMainMenuSettingsCommit(root);
|
||||
RequirePlayerSettingsEveSurface(root);
|
||||
RequireMainMenuContinueRunState(root);
|
||||
RequirePropertiesPanelReadOnlyInspector(root);
|
||||
RequireRuntimeSimulationTuningCommits(root);
|
||||
|
|
@ -1840,6 +1841,72 @@ static void RequireMainMenuSettingsCommit(string root)
|
|||
}
|
||||
}
|
||||
|
||||
static void RequirePlayerSettingsEveSurface(string root)
|
||||
{
|
||||
var projectorPath = Path.Combine(root, "Aetheria.State", "AetheriaPlayerSettingsSurfaceProjector.cs");
|
||||
var bridgePath = Path.Combine(root, "Aetheria.State", "AetheriaEveCommandBridge.cs");
|
||||
var providerPath = Path.Combine(root, "Aetheria.State", "AetheriaProviderAdvertisementProjector.cs");
|
||||
var serverPath = Path.Combine(root, "Economy.Server", "Program.cs");
|
||||
|
||||
if (!File.Exists(projectorPath))
|
||||
{
|
||||
throw new InvalidOperationException("Player settings Eve surface projector is missing.");
|
||||
}
|
||||
|
||||
var projector = File.ReadAllText(projectorPath);
|
||||
var requiredProjectorSymbols = new[]
|
||||
{
|
||||
"SurfaceId = \"aetheria.player_settings\"",
|
||||
"aetheria.player_settings.gameplay.temperature_unit.cycle",
|
||||
"aetheria.player_settings.gameplay.significant_digits.increment",
|
||||
"aetheria.player_settings.graphics.nebula_quality.cycle",
|
||||
"aetheria.player_settings.graphics.show_asteroids.toggle"
|
||||
};
|
||||
|
||||
var missingProjectorSymbols = requiredProjectorSymbols
|
||||
.Where(symbol => !projector.Contains(symbol, StringComparison.Ordinal))
|
||||
.ToArray();
|
||||
|
||||
if (missingProjectorSymbols.Length > 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Player settings Eve surface projector is missing required typed controls: " +
|
||||
string.Join(", ", missingProjectorSymbols));
|
||||
}
|
||||
|
||||
var bridge = File.Exists(bridgePath)
|
||||
? File.ReadAllText(bridgePath)
|
||||
: throw new InvalidOperationException("Player settings Eve command bridge is missing.");
|
||||
var provider = File.Exists(providerPath)
|
||||
? File.ReadAllText(providerPath)
|
||||
: throw new InvalidOperationException("Player settings provider advertisement projector is missing.");
|
||||
var server = File.Exists(serverPath)
|
||||
? File.ReadAllText(serverPath)
|
||||
: throw new InvalidOperationException("Economy.Server program is missing.");
|
||||
|
||||
if (!bridge.Contains("AppliedPlayerSettingsCommands", StringComparison.Ordinal) ||
|
||||
!bridge.Contains("ApplyPlayerSettingsCommandAsync", StringComparison.Ordinal) ||
|
||||
!bridge.Contains("PutPlayerSettingsSurfaceAsync", StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Eve command bridge no longer owns the typed player-settings surface mutation path.");
|
||||
}
|
||||
|
||||
if (!provider.Contains("AetheriaPlayerSettingsSurfaceProjector.SurfaceId", StringComparison.Ordinal) ||
|
||||
!provider.Contains("aetheria.player_settings.refresh", StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Provider advertisement no longer publishes the player-settings Eve surface and commands.");
|
||||
}
|
||||
|
||||
if (!server.Contains("PublishPlayerSettingsSurfaceAsync", StringComparison.Ordinal) ||
|
||||
!server.Contains("PutPlayerSettingsSurfaceAsync", StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Economy.Server no longer republishes the provider-owned player-settings Eve surface.");
|
||||
}
|
||||
}
|
||||
|
||||
static void RequireMainMenuContinueRunState(string root)
|
||||
{
|
||||
var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs");
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ public sealed class AetheriaEveCommandApplyReport
|
|||
{
|
||||
public int AppliedCatalogRefreshes { get; set; }
|
||||
public int AppliedOperationsRefreshes { get; set; }
|
||||
public int AppliedPlayerSettingsCommands { get; set; }
|
||||
public int RejectedCommands { get; set; }
|
||||
public string[] AcceptedPaths { get; set; } = [];
|
||||
public string[] RejectedPaths { get; set; } = [];
|
||||
|
|
@ -66,6 +67,15 @@ public static class AetheriaEveCommandBridge
|
|||
AetheriaOperationsSurfaceProjector.Build(commitStatus, eveStatus)).ConfigureAwait(false);
|
||||
report.AppliedOperationsRefreshes++;
|
||||
break;
|
||||
case "aetheria.player_settings.refresh":
|
||||
case "aetheria.player_settings.gameplay.temperature_unit.cycle":
|
||||
case "aetheria.player_settings.gameplay.significant_digits.decrement":
|
||||
case "aetheria.player_settings.gameplay.significant_digits.increment":
|
||||
case "aetheria.player_settings.graphics.nebula_quality.cycle":
|
||||
case "aetheria.player_settings.graphics.show_asteroids.toggle":
|
||||
await ApplyPlayerSettingsCommandAsync(node, command).ConfigureAwait(false);
|
||||
report.AppliedPlayerSettingsCommands++;
|
||||
break;
|
||||
}
|
||||
|
||||
accepted.Add(path);
|
||||
|
|
@ -101,7 +111,85 @@ public static class AetheriaEveCommandBridge
|
|||
return (string.Equals(surfaceId, AetheriaCatalogSurfaceProjector.SurfaceId, StringComparison.Ordinal) &&
|
||||
string.Equals(command, "aetheria.catalog.refresh", StringComparison.Ordinal)) ||
|
||||
(string.Equals(surfaceId, AetheriaOperationsSurfaceProjector.SurfaceId, StringComparison.Ordinal) &&
|
||||
string.Equals(command, "aetheria.operations.refresh", StringComparison.Ordinal));
|
||||
string.Equals(command, "aetheria.operations.refresh", StringComparison.Ordinal)) ||
|
||||
(string.Equals(surfaceId, AetheriaPlayerSettingsSurfaceProjector.SurfaceId, StringComparison.Ordinal) &&
|
||||
KnownPlayerSettingsCommand(command));
|
||||
}
|
||||
|
||||
private static bool KnownPlayerSettingsCommand(string command)
|
||||
{
|
||||
return string.Equals(command, "aetheria.player_settings.refresh", StringComparison.Ordinal) ||
|
||||
string.Equals(command, "aetheria.player_settings.gameplay.temperature_unit.cycle", StringComparison.Ordinal) ||
|
||||
string.Equals(command, "aetheria.player_settings.gameplay.significant_digits.decrement", StringComparison.Ordinal) ||
|
||||
string.Equals(command, "aetheria.player_settings.gameplay.significant_digits.increment", StringComparison.Ordinal) ||
|
||||
string.Equals(command, "aetheria.player_settings.graphics.nebula_quality.cycle", StringComparison.Ordinal) ||
|
||||
string.Equals(command, "aetheria.player_settings.graphics.show_asteroids.toggle", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static async Task ApplyPlayerSettingsCommandAsync(
|
||||
AetheriaStateNode node,
|
||||
AetheriaRuntimeEveCommandDocument command)
|
||||
{
|
||||
var settings = await node.GetPlayerSettingsAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings();
|
||||
settings.Gameplay ??= new AetheriaPlayerGameplaySettings();
|
||||
settings.Graphics ??= new AetheriaPlayerGraphicsSettings();
|
||||
var persistSettings = false;
|
||||
|
||||
switch (command.Command)
|
||||
{
|
||||
case "aetheria.player_settings.gameplay.temperature_unit.cycle":
|
||||
settings.Gameplay.TemperatureUnit = Cycle(
|
||||
settings.Gameplay.TemperatureUnit,
|
||||
"Kelvin",
|
||||
"Celsius",
|
||||
"Fahrenheit");
|
||||
persistSettings = true;
|
||||
break;
|
||||
case "aetheria.player_settings.gameplay.significant_digits.decrement":
|
||||
settings.Gameplay.SignificantDigits = Math.Max(0, settings.Gameplay.SignificantDigits - 1);
|
||||
persistSettings = true;
|
||||
break;
|
||||
case "aetheria.player_settings.gameplay.significant_digits.increment":
|
||||
if (settings.Gameplay.SignificantDigits < int.MaxValue)
|
||||
settings.Gameplay.SignificantDigits++;
|
||||
persistSettings = true;
|
||||
break;
|
||||
case "aetheria.player_settings.graphics.nebula_quality.cycle":
|
||||
settings.Graphics.NebulaQuality = Cycle(
|
||||
settings.Graphics.NebulaQuality,
|
||||
"Low",
|
||||
"Normal",
|
||||
"High",
|
||||
"Ultra");
|
||||
persistSettings = true;
|
||||
break;
|
||||
case "aetheria.player_settings.graphics.show_asteroids.toggle":
|
||||
settings.Graphics.ShowAsteroidsInMinimap = !settings.Graphics.ShowAsteroidsInMinimap;
|
||||
persistSettings = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (persistSettings)
|
||||
{
|
||||
settings.LastUpdatedAtUtc = command.IssuedAtUtc;
|
||||
await node.PutPlayerSettingsAsync(settings).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await node.PutPlayerSettingsSurfaceAsync(
|
||||
AetheriaPlayerSettingsSurfaceProjector.Build(settings, command.IssuedAtUtc))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static string Cycle(string current, params string[] values)
|
||||
{
|
||||
if (values.Length == 0)
|
||||
return current;
|
||||
|
||||
var index = Array.FindIndex(values, value => string.Equals(value, current, StringComparison.Ordinal));
|
||||
if (index < 0)
|
||||
return values[0];
|
||||
|
||||
return values[(index + 1) % values.Length];
|
||||
}
|
||||
|
||||
private static void RecordRejection(
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ public static class AetheriaOperationsSurfaceProjector
|
|||
Metric("eveCommandDrain.rejected", "Commands Rejected", (eveCommandStatus?.CommandsRejected ?? 0).ToString()),
|
||||
Metric("eveCommandDrain.catalogRefreshes", "Catalog Refreshes", (eveCommandStatus?.AppliedCatalogRefreshes ?? 0).ToString()),
|
||||
Metric("eveCommandDrain.operationsRefreshes", "Operations Refreshes", (eveCommandStatus?.AppliedOperationsRefreshes ?? 0).ToString()),
|
||||
Metric("eveCommandDrain.playerSettings", "Player Settings Commands", (eveCommandStatus?.AppliedPlayerSettingsCommands ?? 0).ToString()),
|
||||
Metric("eveCommandDrain.failures", "Consecutive Failures", (eveCommandStatus?.ConsecutiveFailures ?? 0).ToString()),
|
||||
Row(
|
||||
"eveCommandDrain.last",
|
||||
|
|
|
|||
177
Aetheria.State/AetheriaPlayerSettingsSurfaceProjector.cs
Normal file
177
Aetheria.State/AetheriaPlayerSettingsSurfaceProjector.cs
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
using System.Linq;
|
||||
using Aetheria.State.Documents;
|
||||
|
||||
namespace Aetheria.State;
|
||||
|
||||
public static class AetheriaPlayerSettingsSurfaceProjector
|
||||
{
|
||||
public const string SurfaceKey = "eve:surface:aetheria.player_settings";
|
||||
public const string SurfaceId = "aetheria.player_settings";
|
||||
|
||||
public static EveSurfaceState Build(
|
||||
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;
|
||||
|
||||
return new EveSurfaceState
|
||||
{
|
||||
ProviderId = "aetheria",
|
||||
ProviderKind = "game.runtime",
|
||||
Title = "Aetheria Player Settings",
|
||||
Version = version,
|
||||
UpdatedAtUtc = publishedAtUtc,
|
||||
Surface = new EveSurface
|
||||
{
|
||||
Id = SurfaceId,
|
||||
Root = Node(
|
||||
"aetheria.playerSettings.root",
|
||||
"surface",
|
||||
[],
|
||||
Node(
|
||||
"aetheria.playerSettings.summary",
|
||||
"card",
|
||||
[("title", "Player Settings")],
|
||||
Row(
|
||||
"playerSettings.summary.values",
|
||||
("playerName", settings.PlayerName),
|
||||
("tutorialPassed", settings.TutorialPassed ? "Yes" : "No"),
|
||||
("activeRun", settings.ActiveRunKey)),
|
||||
Text(
|
||||
"playerSettings.summary.note",
|
||||
"Input remapping still lives on the runtime-owned screen until Eve grows typed rebinding controls.")),
|
||||
Node(
|
||||
"aetheria.playerSettings.gameplay",
|
||||
"card",
|
||||
[("title", "Gameplay")],
|
||||
Metric("playerSettings.gameplay.temperatureUnit", "Temperature Unit", gameplay.TemperatureUnit),
|
||||
ButtonRow(
|
||||
"playerSettings.gameplay.temperatureUnit.buttons",
|
||||
Button(
|
||||
"playerSettings.gameplay.temperatureUnit.cycle",
|
||||
"Cycle Temperature Unit",
|
||||
"aetheria.player_settings.gameplay.temperature_unit.cycle")),
|
||||
Metric(
|
||||
"playerSettings.gameplay.significantDigits",
|
||||
"Significant Digits",
|
||||
gameplay.SignificantDigits.ToString()),
|
||||
ButtonRow(
|
||||
"playerSettings.gameplay.significantDigits.buttons",
|
||||
Button(
|
||||
"playerSettings.gameplay.significantDigits.decrement",
|
||||
"Digits -",
|
||||
"aetheria.player_settings.gameplay.significant_digits.decrement"),
|
||||
Button(
|
||||
"playerSettings.gameplay.significantDigits.increment",
|
||||
"Digits +",
|
||||
"aetheria.player_settings.gameplay.significant_digits.increment"))),
|
||||
Node(
|
||||
"aetheria.playerSettings.graphics",
|
||||
"card",
|
||||
[("title", "Graphics")],
|
||||
Metric("playerSettings.graphics.nebulaQuality", "Nebula Quality", graphics.NebulaQuality),
|
||||
ButtonRow(
|
||||
"playerSettings.graphics.nebulaQuality.buttons",
|
||||
Button(
|
||||
"playerSettings.graphics.nebulaQuality.cycle",
|
||||
"Cycle Nebula Quality",
|
||||
"aetheria.player_settings.graphics.nebula_quality.cycle")),
|
||||
Metric(
|
||||
"playerSettings.graphics.showAsteroids",
|
||||
"Show Asteroids In Minimap",
|
||||
graphics.ShowAsteroidsInMinimap ? "Enabled" : "Disabled"),
|
||||
ButtonRow(
|
||||
"playerSettings.graphics.showAsteroids.buttons",
|
||||
Button(
|
||||
"playerSettings.graphics.showAsteroids.toggle",
|
||||
graphics.ShowAsteroidsInMinimap ? "Disable Minimap Asteroids" : "Enable Minimap Asteroids",
|
||||
"aetheria.player_settings.graphics.show_asteroids.toggle"))))
|
||||
},
|
||||
Commands =
|
||||
[
|
||||
new EveCommandTemplate
|
||||
{
|
||||
Command = "aetheria.player_settings.refresh",
|
||||
Label = "Refresh",
|
||||
Transport = "cultmesh"
|
||||
},
|
||||
new EveCommandTemplate
|
||||
{
|
||||
Command = "aetheria.player_settings.gameplay.temperature_unit.cycle",
|
||||
Label = "Cycle Temperature Unit",
|
||||
Transport = "cultmesh"
|
||||
},
|
||||
new EveCommandTemplate
|
||||
{
|
||||
Command = "aetheria.player_settings.gameplay.significant_digits.decrement",
|
||||
Label = "Digits -",
|
||||
Transport = "cultmesh"
|
||||
},
|
||||
new EveCommandTemplate
|
||||
{
|
||||
Command = "aetheria.player_settings.gameplay.significant_digits.increment",
|
||||
Label = "Digits +",
|
||||
Transport = "cultmesh"
|
||||
},
|
||||
new EveCommandTemplate
|
||||
{
|
||||
Command = "aetheria.player_settings.graphics.nebula_quality.cycle",
|
||||
Label = "Cycle Nebula Quality",
|
||||
Transport = "cultmesh"
|
||||
},
|
||||
new EveCommandTemplate
|
||||
{
|
||||
Command = "aetheria.player_settings.graphics.show_asteroids.toggle",
|
||||
Label = "Toggle Minimap Asteroids",
|
||||
Transport = "cultmesh"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
private static EveSurfaceComponent Metric(string id, string label, string value)
|
||||
{
|
||||
return Node(id, "metric", [("label", label), ("value", value)]);
|
||||
}
|
||||
|
||||
private static EveSurfaceComponent Text(string id, string value)
|
||||
{
|
||||
return Node(id, "text", [("value", value)]);
|
||||
}
|
||||
|
||||
private static EveSurfaceComponent Button(string id, string label, string command)
|
||||
{
|
||||
return Node(id, "control.button", [("label", label), ("command", command)]);
|
||||
}
|
||||
|
||||
private static EveSurfaceComponent ButtonRow(string id, params EveSurfaceComponent[] children)
|
||||
{
|
||||
return Node(id, "row", [], children);
|
||||
}
|
||||
|
||||
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 = id,
|
||||
Kind = kind,
|
||||
Props = props.ToDictionary(prop => prop.Key, prop => prop.Value),
|
||||
Children = children
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -64,6 +64,11 @@ public static class AetheriaProviderAdvertisementProjector
|
|||
{
|
||||
SurfaceId = AetheriaOperationsSurfaceProjector.SurfaceId,
|
||||
Key = AetheriaOperationsSurfaceProjector.SurfaceKey
|
||||
},
|
||||
new EveProviderSurfaceRef
|
||||
{
|
||||
SurfaceId = AetheriaPlayerSettingsSurfaceProjector.SurfaceId,
|
||||
Key = AetheriaPlayerSettingsSurfaceProjector.SurfaceKey
|
||||
}
|
||||
],
|
||||
Commands =
|
||||
|
|
@ -77,6 +82,36 @@ public static class AetheriaProviderAdvertisementProjector
|
|||
{
|
||||
Command = "aetheria.operations.refresh",
|
||||
Summary = "Refresh operations projection"
|
||||
},
|
||||
new EveProviderCommandRef
|
||||
{
|
||||
Command = "aetheria.player_settings.refresh",
|
||||
Summary = "Refresh player settings projection"
|
||||
},
|
||||
new EveProviderCommandRef
|
||||
{
|
||||
Command = "aetheria.player_settings.gameplay.temperature_unit.cycle",
|
||||
Summary = "Cycle the typed player temperature unit"
|
||||
},
|
||||
new EveProviderCommandRef
|
||||
{
|
||||
Command = "aetheria.player_settings.gameplay.significant_digits.decrement",
|
||||
Summary = "Decrease typed player significant digits"
|
||||
},
|
||||
new EveProviderCommandRef
|
||||
{
|
||||
Command = "aetheria.player_settings.gameplay.significant_digits.increment",
|
||||
Summary = "Increase typed player significant digits"
|
||||
},
|
||||
new EveProviderCommandRef
|
||||
{
|
||||
Command = "aetheria.player_settings.graphics.nebula_quality.cycle",
|
||||
Summary = "Cycle typed player nebula quality"
|
||||
},
|
||||
new EveProviderCommandRef
|
||||
{
|
||||
Command = "aetheria.player_settings.graphics.show_asteroids.toggle",
|
||||
Summary = "Toggle typed player minimap asteroid visibility"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,8 +38,12 @@ public static class AetheriaRuntimeCommitLogApplier
|
|||
switch (command.Kind)
|
||||
{
|
||||
case "player_settings":
|
||||
await node.PutPlayerSettingsAsync(
|
||||
ToPlayerSettings(command.PlayerSettings ?? throw MissingPayload(command), command.CreatedAtUtc))
|
||||
var settings = ToPlayerSettings(
|
||||
command.PlayerSettings ?? throw MissingPayload(command),
|
||||
command.CreatedAtUtc);
|
||||
await node.PutPlayerSettingsAsync(settings).ConfigureAwait(false);
|
||||
await node.PutPlayerSettingsSurfaceAsync(
|
||||
AetheriaPlayerSettingsSurfaceProjector.Build(settings, command.CreatedAtUtc))
|
||||
.ConfigureAwait(false);
|
||||
report.AppliedPlayerSettings++;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -183,6 +183,16 @@ public sealed class AetheriaStateNode : IAsyncDisposable, IDisposable
|
|||
return Database.GetAsync<EveSurfaceState>(new CultRecordKey(AetheriaOperationsSurfaceProjector.SurfaceKey));
|
||||
}
|
||||
|
||||
public Task<CultRecordHandle<EveSurfaceState>> PutPlayerSettingsSurfaceAsync(EveSurfaceState surface)
|
||||
{
|
||||
return Database.PutAsync(new CultRecordKey(AetheriaPlayerSettingsSurfaceProjector.SurfaceKey), surface);
|
||||
}
|
||||
|
||||
public Task<EveSurfaceState?> GetPlayerSettingsSurfaceAsync()
|
||||
{
|
||||
return Database.GetAsync<EveSurfaceState>(new CultRecordKey(AetheriaPlayerSettingsSurfaceProjector.SurfaceKey));
|
||||
}
|
||||
|
||||
public Task<CultRecordHandle<EveProviderAdvertisementState>> PutProviderAdvertisementAsync(
|
||||
EveProviderAdvertisementState advertisement)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -54,4 +54,7 @@ public sealed class AetheriaEveCommandDrainStatus
|
|||
|
||||
[Key(14)]
|
||||
public string Status { get; set; } = "ok";
|
||||
|
||||
[Key(15)]
|
||||
public int AppliedPlayerSettingsCommands { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,8 +53,9 @@ Unity Eve surfaces also emit typed `gamecult.eve.command.v1` documents under
|
|||
`GameData/aetheria-world.cc.eve.pending`. `AetheriaEveCommandBridge` is the
|
||||
provider-owned acceptance organ for those commands: it validates the provider,
|
||||
surface, and command template before running the narrow refresh handlers for
|
||||
the catalog and operations surfaces. Renderer callbacks do not accept commands
|
||||
or mutate game state locally.
|
||||
the catalog and operations surfaces plus the typed player-settings mutation
|
||||
handlers for gameplay/graphics Eve controls. Renderer callbacks do not accept
|
||||
commands or mutate game state locally.
|
||||
`AetheriaEveRuntimeBootstrap` mounts the `aetheria.operations` surface through a
|
||||
runtime-created `UIDocument` after scene load, with environment and command-line
|
||||
switches for diagnostics. UI Toolkit is now a live runtime lowering path, not
|
||||
|
|
@ -319,9 +320,12 @@ allegiance edges so they cannot reclaim catalog lookup authority.
|
|||
|
||||
`Aetheria.State.Smoke` writes and reopens full typed player settings plus a
|
||||
typed loadout template in addition to world, catalog, run state, zone state, and
|
||||
an entity snapshot. It also writes and reopens `AetheriaRuntimeSession` and
|
||||
checks that the Eve provider advertises the runtime-session schema. The run
|
||||
smoke proves a typed run can reference a typed zone, the zone can preserve
|
||||
an entity snapshot. It also writes and reopens `AetheriaRuntimeSession`, the
|
||||
provider-owned `aetheria.player_settings` Eve surface, and player-settings Eve
|
||||
commands that mutate typed significant-digit and minimap-asteroid settings
|
||||
through the provider bridge. The smoke checks that the Eve provider advertises
|
||||
the runtime-session schema and player-settings command surface. The run smoke
|
||||
proves a typed run can reference a typed zone, the zone can preserve
|
||||
orbit/body rows and reference typed entity snapshots, and entity snapshots can
|
||||
preserve equipment slots, weapon groups, and simulation stat grids without
|
||||
reviving `PlayerSettings.msgpack`, `.loadout`, or `.zone` serialization.
|
||||
|
|
@ -374,17 +378,19 @@ dotnet run --project .\Economy.Server\Economy.Server.csproj -- --apply-pending-o
|
|||
```
|
||||
|
||||
The server publishes `AetheriaRuntimeCommitDrainStatus`,
|
||||
`AetheriaEveCommandDrainStatus`, and the `aetheria.operations` Eve surface after
|
||||
drain attempts, so pending depth, accepted/applied/rejected counts, failures,
|
||||
and timestamps are typed state rather than console-only status. It also
|
||||
`AetheriaEveCommandDrainStatus`, the `aetheria.operations` Eve surface, and the
|
||||
typed `aetheria.player_settings` Eve surface after drain attempts, so pending
|
||||
depth, accepted/applied/rejected counts, failures, timestamps, and player
|
||||
settings controls are typed state rather than console-only status. It also
|
||||
publishes `gamecult.eve.provider_advertisement.v1` for the `aetheria` provider,
|
||||
advertising the catalog and operations surfaces plus the typed schemas witnessed
|
||||
by the local `.cc` state file.
|
||||
advertising the catalog, operations, and player-settings surfaces plus the
|
||||
typed schemas witnessed by the local `.cc` state file.
|
||||
|
||||
`Aetheria.State.ApplyPending` is the smaller local operator applicator for
|
||||
queued Unity runtime commits and Eve commands when the server host is not being
|
||||
used. It also republishes the typed drain status, operations surface, and
|
||||
provider advertisement after applying pending files:
|
||||
used. It also republishes the typed drain status, operations surface,
|
||||
player-settings surface, and provider advertisement after applying pending
|
||||
files:
|
||||
|
||||
```powershell
|
||||
dotnet run --project .\Aetheria.State.ApplyPending\Aetheria.State.ApplyPending.csproj -- .
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ internal static class Program
|
|||
CommandsRejected = report.RejectedCommands,
|
||||
AppliedCatalogRefreshes = report.AppliedCatalogRefreshes,
|
||||
AppliedOperationsRefreshes = report.AppliedOperationsRefreshes,
|
||||
AppliedPlayerSettingsCommands = report.AppliedPlayerSettingsCommands,
|
||||
LastRejectedCommand = report.LastRejectedCommand,
|
||||
LastRejectedReason = report.LastRejectedReason,
|
||||
ConsecutiveFailures = 0,
|
||||
|
|
@ -174,7 +175,8 @@ internal static class Program
|
|||
$"accepted={report.AcceptedPaths.Length}, " +
|
||||
$"rejected={report.RejectedCommands}, " +
|
||||
$"catalogRefreshes={report.AppliedCatalogRefreshes}, " +
|
||||
$"operationsRefreshes={report.AppliedOperationsRefreshes}");
|
||||
$"operationsRefreshes={report.AppliedOperationsRefreshes}, " +
|
||||
$"playerSettings={report.AppliedPlayerSettingsCommands}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -219,11 +221,24 @@ internal static class Program
|
|||
var runtimeSession = await node.GetRuntimeSessionAsync(RuntimeId).ConfigureAwait(false);
|
||||
await node.PutOperationsSurfaceAsync(AetheriaOperationsSurfaceProjector.Build(commitStatus, eveStatus, runtimeSession))
|
||||
.ConfigureAwait(false);
|
||||
await PublishPlayerSettingsSurfaceAsync(node, updatedAtUtc).ConfigureAwait(false);
|
||||
await node.PutProviderAdvertisementAsync(
|
||||
AetheriaProviderAdvertisementProjector.Build(node.StatePath, updatedAtUtc)).ConfigureAwait(false);
|
||||
await node.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task PublishPlayerSettingsSurfaceAsync(
|
||||
AetheriaStateNode node,
|
||||
string updatedAtUtc)
|
||||
{
|
||||
var settings = await node.GetPlayerSettingsAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings();
|
||||
var publishedAtUtc = string.IsNullOrWhiteSpace(settings.LastUpdatedAtUtc)
|
||||
? updatedAtUtc
|
||||
: settings.LastUpdatedAtUtc;
|
||||
await node.PutPlayerSettingsSurfaceAsync(
|
||||
AetheriaPlayerSettingsSurfaceProjector.Build(settings, publishedAtUtc)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task PublishRuntimeSessionAsync(
|
||||
AetheriaStateNode node,
|
||||
string startedAtUtc,
|
||||
|
|
|
|||
|
|
@ -483,11 +483,15 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
|
|||
- `AetheriaRuntimeCommitDrainStatus` and the `aetheria.operations` Eve surface
|
||||
publish pending-drain health, pending depth, applied counts, failures, and
|
||||
timestamps as typed state. Console logs are notification-only.
|
||||
- `AetheriaPlayerSettingsSurfaceProjector` now emits a provider-owned
|
||||
`aetheria.player_settings` Eve surface from canonical `AetheriaPlayerSettings`
|
||||
state. Gameplay and graphics settings are exposed as typed read models plus
|
||||
narrow typed command buttons, not renderer-local fields.
|
||||
- `AetheriaProviderAdvertisementProjector` publishes
|
||||
`gamecult.eve.provider_advertisement.v1` for the `aetheria` provider,
|
||||
advertising the catalog and operations surfaces, command boundaries, schemas,
|
||||
and `.cc` witness path. This is the discovery map for Odin/Eve, not a health
|
||||
page.
|
||||
advertising the catalog, operations, and player-settings surfaces, command
|
||||
boundaries, schemas, and `.cc` witness path. This is the discovery map for
|
||||
Odin/Eve, not a health page.
|
||||
- `AetheriaCatalogSurfaceProjector` now emits the first provider-owned Eve
|
||||
surface from typed catalog state. The importer materializes a
|
||||
`gamecult.eve.surface.v1` catalog operator document at
|
||||
|
|
@ -636,9 +640,10 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
|
|||
path queues `gamecult.eve.command.v1` envelopes under
|
||||
`aetheria-world.cc.eve.pending` for the provider-owned command bridge instead
|
||||
of accepting renderer-local command effects. `AetheriaEveCommandBridge`
|
||||
currently accepts the advertised catalog/operations refresh commands,
|
||||
republishes provider-owned surfaces, rejects unknown/unadvertised commands,
|
||||
and records `AetheriaEveCommandDrainStatus` as typed state.
|
||||
currently accepts the advertised catalog/operations refresh commands plus the
|
||||
first provider-owned player-settings mutation commands, republishes
|
||||
provider-owned surfaces, rejects unknown/unadvertised commands, and records
|
||||
`AetheriaEveCommandDrainStatus` as typed state.
|
||||
|
||||
## Invariants
|
||||
|
||||
|
|
@ -1443,8 +1448,10 @@ First Aetheria surfaces to publish:
|
|||
commands. `AetheriaRuntimePendingCultCacheStore` owns the temporary
|
||||
envelope writer/reader until the Unity package can use generated CultCache
|
||||
serializers directly.
|
||||
- Extend the command bridge beyond refresh commands as gameplay/editor Eve
|
||||
surfaces acquire provider-owned handlers.
|
||||
- Done: extend the command bridge beyond refresh commands for the first
|
||||
mutating settings surface; player-settings Eve commands now mutate
|
||||
canonical `AetheriaPlayerSettings` and republish the provider-owned
|
||||
`aetheria.player_settings` surface.
|
||||
- Done: wire the presenter into runtime through `AetheriaEveRuntimeBootstrap`
|
||||
so the operations surface mounts as a UI Toolkit surface after scene load.
|
||||
- Done: delete the concrete uGUI debug console path: `ConsoleView`,
|
||||
|
|
@ -1546,6 +1553,8 @@ First Aetheria surfaces to publish:
|
|||
operations surface.
|
||||
- Done: publish Eve command-drain health as typed CultCache state and include
|
||||
it in the operations surface.
|
||||
- Done: publish provider-owned player-settings controls as a typed Eve
|
||||
surface and advertise their command boundary through the provider ad.
|
||||
- Done: publish an Eve provider advertisement so Odin/Eve can discover
|
||||
Aetheria surfaces and command boundaries through typed state.
|
||||
- Done: stop legacy catalog pull/read paths from writing entries back to
|
||||
|
|
@ -1840,9 +1849,10 @@ First Aetheria surfaces to publish:
|
|||
`InputLayout` rows/columns. The dead commented Ink `ToJson` write path and
|
||||
checked-in `ansi104.json` display file have been removed from live source.
|
||||
- `Aetheria.State.Smoke` proves the provider-owned Eve command bridge drains
|
||||
`gamecult.eve.command.v1` envelopes, accepts advertised refresh commands,
|
||||
rejects unknown commands, persists `AetheriaEveCommandDrainStatus`, and exposes
|
||||
the Eve command drain through the operations surface.
|
||||
`gamecult.eve.command.v1` envelopes, accepts advertised refresh commands plus
|
||||
player-settings mutation commands, rejects unknown commands, persists
|
||||
`AetheriaEveCommandDrainStatus`, and exposes the Eve command drain through the
|
||||
operations surface while preserving the `aetheria.player_settings` surface.
|
||||
- Unity play smoke proves runtime UI reads from Eve surfaces and sends commands
|
||||
through the shared state service.
|
||||
- UI Toolkit lowering parity compares Aetheria surfaces against the Eve browser
|
||||
|
|
|
|||
Loading…
Reference in a new issue