Split reactive gameplay facts from topology
This commit is contained in:
parent
83ce86c75e
commit
82ce77209e
4 changed files with 134 additions and 48 deletions
|
|
@ -942,7 +942,18 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
tick.Frame,
|
||||
new AetheriaRuntimeDaemonHealthDocument(),
|
||||
AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"));
|
||||
Require(Flatten(surface.Surface.Root).Any(node => node.Kind == "feedback.event" &&
|
||||
var reactiveMount = Flatten(surface.Surface.Root).Single(node =>
|
||||
node.Id == "aetheria.daemon.game.reactive");
|
||||
var reactiveSlot = reactiveMount.EmbeddedDocuments.Single();
|
||||
Require(reactiveSlot.DocumentId == AetheriaRuntimeVerseRecordKeys.DaemonGameReactiveSurface.ToString() &&
|
||||
reactiveSlot.SchemaId == EveSurfaceDocument.SchemaId &&
|
||||
reactiveSlot.RouteHint.Kind == CultMeshLocalityKind.SharedMemory,
|
||||
"the static pilot surface must advertise one exact, mapped-friendly reactive gameplay document");
|
||||
Require(!Flatten(surface.Surface.Root).Any(node =>
|
||||
node.Kind is "feedback.event" or "shot.receipt"),
|
||||
"the static pilot surface must not retain mutable gameplay facts");
|
||||
var reactive = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay(tick.Frame);
|
||||
Require(Flatten(reactive.Surface.Root).Any(node => node.Kind == "feedback.event" &&
|
||||
node.Props["eventKind"] == "simulation.interrupted" &&
|
||||
node.Props["reason"] == "entity.damaged" &&
|
||||
node.Props["scalarValue"] == "4" && node.Props["auxiliaryValue"] == "1"),
|
||||
|
|
@ -2635,7 +2646,9 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
new AetheriaRuntimeDaemonHealthDocument(),
|
||||
AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"),
|
||||
catalog: catalog);
|
||||
Require(Flatten(surface.Surface.Root).Any(node => node.Kind == "feedback.event" &&
|
||||
var reactive = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = successRun });
|
||||
Require(Flatten(reactive.Surface.Root).Any(node => node.Kind == "feedback.event" &&
|
||||
node.Props["eventKind"] == "ship.undocked" &&
|
||||
node.Props["sourceEntityIndex"] == "0" && node.Props["targetEntityIndex"] == "1" &&
|
||||
node.Props["scalarValue"] == "0"),
|
||||
|
|
@ -3687,7 +3700,9 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
node.Props["behaviorKind"] == AetheriaRuntimeBehaviorKinds.ConstantWeapon &&
|
||||
node.Props["itemKey"] == "test-beam" && node.Props["firing"] == "false"),
|
||||
$"Eve world entity must project recoverable generic ConstantWeapon state; nodes={string.Join(";", weaponNodes.Select(node => string.Join(",", node.Props.Select(pair => $"{pair.Key}={pair.Value}"))))}");
|
||||
Require(Flatten(surface.Surface.Root).Any(node => node.Kind == "feedback.event" &&
|
||||
var reactive = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 11, Run = run });
|
||||
Require(Flatten(reactive.Surface.Root).Any(node => node.Kind == "feedback.event" &&
|
||||
node.Props["eventKind"] == "weapon.firing.started" && node.Props["itemKey"] == "test-beam"),
|
||||
"Eve feedback stream must project continuous weapon transition chronology");
|
||||
var cockpit = Flatten(surface.Surface.Root).Single(node => node.Id == "aetheria.daemon.game.cockpit");
|
||||
|
|
@ -3796,7 +3811,9 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
Require(Flatten(surface.Surface.Root).Any(node => node.Kind == "weapon.state" &&
|
||||
node.Props.ContainsKey("chargeMalfunctionRisk")),
|
||||
"Eve must expose charged hold duration and malfunction risk generically");
|
||||
Require(Flatten(surface.Surface.Root).Any(node => node.Kind == "feedback.event" &&
|
||||
var reactive = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 11, Run = run });
|
||||
Require(Flatten(reactive.Surface.Root).Any(node => node.Kind == "feedback.event" &&
|
||||
node.Props["eventKind"] == "weapon.charge.committed"),
|
||||
"Eve feedback must expose charged solution commit chronology");
|
||||
}
|
||||
|
|
@ -4034,10 +4051,8 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
value.TargetEntityIndex == 2 && value.Reason == "target-invalid") == 1,
|
||||
$"an invalid target must clear an idle weapon's authoritative lock and publish one loss transition; " +
|
||||
$"target={state.LockTargetEntityIndex} progress={state.LockProgress:0.###} shots={run.ShotReceipts.Count} events={string.Join('|', run.GameEvents.Where(value => value.Kind.StartsWith("weapon.lock.", StringComparison.Ordinal)).Select(value => $"{value.Kind}:{value.TargetEntityIndex}:{value.Reason}:{value.AuxiliaryValue:0.###}->{value.ScalarValue:0.###}"))}");
|
||||
var resetSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 22, Run = run },
|
||||
new AetheriaRuntimeDaemonHealthDocument(),
|
||||
AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"));
|
||||
var resetSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 22, Run = run });
|
||||
Require(Flatten(resetSurface.Surface.Root).Any(node => node.Kind == "feedback.event" &&
|
||||
node.Props["eventKind"] == "weapon.lock.lost" &&
|
||||
node.Props["targetEntityIndex"] == "2" &&
|
||||
|
|
@ -4286,10 +4301,8 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
MessagePack.MessagePackSerializer.Serialize(run));
|
||||
RequireEqual("target-entity", restored.ShotReceipts.Single().GuidanceMode,
|
||||
"guided presentation levers must survive daemon checkpoint restart");
|
||||
var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 4, Run = run },
|
||||
new AetheriaRuntimeDaemonHealthDocument(),
|
||||
AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"));
|
||||
var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 4, Run = run });
|
||||
var shot = Flatten(surface.Surface.Root).Single(node =>
|
||||
node.Kind == "shot.receipt" && node.Props.TryGetValue("itemKey", out var itemKey) &&
|
||||
itemKey == "test-launcher");
|
||||
|
|
@ -4593,10 +4606,8 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
Math.Abs(value.ScalarValue - 20) < 0.000001 &&
|
||||
Math.Abs(value.AuxiliaryValue - 10) < 0.000001),
|
||||
"shield outcome must publish absorbed damage and authored heat contribution");
|
||||
var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = run },
|
||||
new AetheriaRuntimeDaemonHealthDocument(),
|
||||
AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"));
|
||||
var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = run });
|
||||
Require(Flatten(surface.Surface.Root).Any(node => node.Kind == "feedback.event" &&
|
||||
node.Props["eventKind"] == "shield.absorbed" &&
|
||||
node.Props["scalarValue"] == "20" && node.Props["auxiliaryValue"] == "10"),
|
||||
|
|
@ -4735,10 +4746,8 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
$"cells={string.Join(';', receipt.DamageCells.Select(cell => $"{cell.X},{cell.Y}"))}");
|
||||
RequireNear(90, Stat(target, "hull"), 0.000001,
|
||||
"a disconnected hull island must not divide or receive penetration damage beyond a gap");
|
||||
var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = receipt.FrameId, Run = run },
|
||||
new AetheriaRuntimeDaemonHealthDocument(),
|
||||
AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"));
|
||||
var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = receipt.FrameId, Run = run });
|
||||
var shot = Flatten(surface.Surface.Root).Single(node =>
|
||||
node.Kind == "shot.receipt" && node.Props["itemKey"] == "gap-penetrator");
|
||||
RequireEqual("1", shot.Props["damageCellCount"],
|
||||
|
|
@ -4906,10 +4915,8 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
RequireEqual(1, run.GameEvents.Count(value => value.Kind == "equipment.destroyed" &&
|
||||
value.TargetEntityIndex == target.EntityIndex && value.ItemKey == "damageable-weapon"),
|
||||
"the durability crossing must publish exactly one generic equipment destruction fact");
|
||||
var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = run },
|
||||
new AetheriaRuntimeDaemonHealthDocument(),
|
||||
AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"));
|
||||
var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = run });
|
||||
Require(Flatten(surface.Surface.Root).Any(node => node.Kind == "feedback.event" &&
|
||||
node.Props["eventKind"] == "equipment.destroyed" &&
|
||||
node.Props["itemKey"] == "damageable-weapon"),
|
||||
|
|
@ -6333,10 +6340,8 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
$"projectiles={run.Zones[0].PhysicalPayloads.Count} task={run.AgentTasks.Single().Status}");
|
||||
RequireEqual(AetheriaRuntimeAgentTaskStatuses.Completed, run.AgentTasks.Single().Status,
|
||||
"attack task must complete when its target dies");
|
||||
var shotSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 60, Run = run },
|
||||
new AetheriaRuntimeDaemonHealthDocument(),
|
||||
AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"));
|
||||
var shotSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 60, Run = run });
|
||||
Require(Flatten(shotSurface.Surface.Root).Any(node => node.Kind == "shot.receipt" &&
|
||||
node.Props["itemKey"] == "test-lock-cannon" && node.Props["outcome"] == "hit" &&
|
||||
node.Props["presentationKind"] == "bolt" &&
|
||||
|
|
@ -8063,10 +8068,8 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
"Ymir contact must emit one projectile-identity impact event");
|
||||
RequireEqual(0, run.GameEvents.Count(value => value.Kind == "entity.damaged" && value.TargetEntityIndex == target.EntityIndex),
|
||||
"presentation projectile contact must not manufacture a damage event");
|
||||
var feedback = AetheriaRuntimeDaemonGameSurfaceBuilder.Build(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 0, Run = run },
|
||||
new AetheriaRuntimeDaemonHealthDocument(),
|
||||
AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"));
|
||||
var feedback = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 0, Run = run });
|
||||
Require(Flatten(feedback.Surface.Root).Any(node => node.Kind == "feedback.event" && node.Props["eventKind"] == "physical-payload.contact" && node.Props["subjectKey"] == "smoke-projectile"),
|
||||
"Eve feedback must project authoritative projectile impact identity");
|
||||
}
|
||||
|
|
@ -8253,10 +8256,8 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
"collection feedback must publish cargo quantity before the transaction");
|
||||
RequireEqual(0, run.PickupContactReceipts.Count,
|
||||
"proximity collection must not mint obsolete Ymir-contact receipts");
|
||||
var collectionFeedback = AetheriaRuntimeDaemonGameSurfaceBuilder.Build(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 3, Run = run },
|
||||
new AetheriaRuntimeDaemonHealthDocument(),
|
||||
AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"));
|
||||
var collectionFeedback = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 3, Run = run });
|
||||
Require(Flatten(collectionFeedback.Surface.Root).Any(node => node.Kind == "feedback.event" &&
|
||||
node.Props["eventKind"] == "pickup.collected" && node.Props["pickupIndex"] == "7" &&
|
||||
node.Props["itemKey"] == salvage.ItemKey && node.Props["scalarValue"] == "1" &&
|
||||
|
|
@ -8701,10 +8702,8 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
new AetheriaRuntimeDaemonTickOptions { WorldPhysics = fullPhysics, FrameId = 2, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = 0.2, Catalog = catalog, BuildPublications = false });
|
||||
RequireEqual(1, full.GameEvents.Count(value => value.Kind == "pickup.rejected" && value.PickupIndex == 10),
|
||||
"the outward kick must prevent an immediate second rejection event");
|
||||
var feedback = AetheriaRuntimeDaemonGameSurfaceBuilder.Build(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = full },
|
||||
new AetheriaRuntimeDaemonHealthDocument(),
|
||||
AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"));
|
||||
var feedback = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = full });
|
||||
Require(Flatten(feedback.Surface.Root).Any(node => node.Kind == "feedback.event" &&
|
||||
node.Props["eventKind"] == "pickup.rejected" && node.Props["pickupIndex"] == "10" &&
|
||||
node.Props["reason"] == "cargo-capacity" &&
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ var clientPump = RunClientCultMeshPumpAsync(cultMeshRudpHost, clientPumpCancella
|
|||
var nextApiPublicationUtc = DateTimeOffset.UtcNow;
|
||||
var ingressState = new AetheriaDaemonIngressState();
|
||||
var hotState = new AetheriaHotEntityPublicationState();
|
||||
var reactiveSurfaceState = new AetheriaReactiveSurfacePublicationState();
|
||||
var firstTick = await TickAsync(node, options, worldPhysics, latestFrame, ingressState, buildPublications: false).ConfigureAwait(false);
|
||||
ThrowIfClientPumpFaulted(clientPump);
|
||||
latestFrame = firstTick.Frame;
|
||||
|
|
@ -79,6 +80,7 @@ await PublishPreparedDocumentsAsync(
|
|||
options,
|
||||
physicsPersistence,
|
||||
firstPublication,
|
||||
reactiveSurfaceState,
|
||||
publishTopology: true).ConfigureAwait(false);
|
||||
await PublishHotEntityStateAsync(node, soaPublisher, hotState, firstTick.Frame, ingressState.Catalog).ConfigureAwait(false);
|
||||
nextApiPublicationUtc = DateTimeOffset.UtcNow.Add(options.ApiPublicationInterval);
|
||||
|
|
@ -138,6 +140,7 @@ while (!stopped.Task.IsCompleted)
|
|||
options,
|
||||
physicsPersistence,
|
||||
publication,
|
||||
reactiveSurfaceState,
|
||||
publishTopology: false);
|
||||
nextApiPublicationUtc = DateTimeOffset.UtcNow.Add(options.ApiPublicationInterval);
|
||||
}
|
||||
|
|
@ -396,6 +399,7 @@ static async Task PublishPreparedDocumentsAsync(
|
|||
AetheriaDaemonHostOptions options,
|
||||
AetheriaYmirPersistenceCoordinator physicsPersistence,
|
||||
AetheriaPreparedPublication prepared,
|
||||
AetheriaReactiveSurfacePublicationState reactiveSurfaceState,
|
||||
bool publishTopology)
|
||||
{
|
||||
await physicsPersistence.PersistPrivateAsync(prepared.Physics).ConfigureAwait(false);
|
||||
|
|
@ -403,7 +407,8 @@ static async Task PublishPreparedDocumentsAsync(
|
|||
node,
|
||||
options,
|
||||
prepared.Publication,
|
||||
publishTopology).ConfigureAwait(false);
|
||||
publishTopology,
|
||||
reactiveSurfaceState).ConfigureAwait(false);
|
||||
if (publishTopology)
|
||||
{
|
||||
await PublishStateSurfacesAsync(node, options, prepared.Publication.Frame.PublishedAtUtc).ConfigureAwait(false);
|
||||
|
|
@ -1550,7 +1555,8 @@ static async Task PublishDaemonApiDocumentsAsync(
|
|||
AetheriaStateNode node,
|
||||
AetheriaDaemonHostOptions options,
|
||||
AetheriaRuntimeDaemonTickResult result,
|
||||
bool publishTopology)
|
||||
bool publishTopology,
|
||||
AetheriaReactiveSurfacePublicationState? reactiveSurfaceState = null)
|
||||
{
|
||||
await node.MutableDocument<AetheriaRuntimeDaemonFrameDocument>(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest)
|
||||
.ReplaceAsync(result.Frame)
|
||||
|
|
@ -1602,6 +1608,15 @@ static async Task PublishDaemonApiDocumentsAsync(
|
|||
var activeMainMenuSurfaceId = string.IsNullOrWhiteSpace(mainMenuState?.ActiveSurfaceId)
|
||||
? AetheriaRuntimeMainMenuCommands.RootSurfaceId
|
||||
: mainMenuState.ActiveSurfaceId;
|
||||
var reactiveGameSurface = AetheriaRuntimeSurfaceDocuments.ToPortableSurface(
|
||||
AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay(result.Frame));
|
||||
if (reactiveSurfaceState?.Matches(reactiveGameSurface.Version) != true)
|
||||
{
|
||||
await node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.DaemonGameReactiveSurface)
|
||||
.ReplaceAsync(reactiveGameSurface)
|
||||
.ConfigureAwait(false);
|
||||
reactiveSurfaceState?.Set(reactiveGameSurface.Version);
|
||||
}
|
||||
if (publishTopology)
|
||||
{
|
||||
var gameSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build(
|
||||
|
|
@ -3150,6 +3165,15 @@ internal sealed record AetheriaPreparedPublication(
|
|||
AetheriaRuntimeDaemonTickResult Publication,
|
||||
IReadOnlyList<AetheriaYmirZonePersistenceCapture> Physics);
|
||||
|
||||
internal sealed class AetheriaReactiveSurfacePublicationState
|
||||
{
|
||||
private long? _version;
|
||||
|
||||
public bool Matches(long version) => _version == version;
|
||||
|
||||
public void Set(long version) => _version = version;
|
||||
}
|
||||
|
||||
internal sealed class AetheriaHotEntityPublicationState
|
||||
{
|
||||
private EveEntitySoaViewDocument? _layout;
|
||||
|
|
|
|||
|
|
@ -42,8 +42,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
frame.SimulationSettings,
|
||||
catalog),
|
||||
CockpitOverlay(entity, target, frame.SimulationSettings),
|
||||
FeedbackStream(run, frame.FrameId),
|
||||
ShotReceiptStream(run),
|
||||
ReactiveGameplaySlot(),
|
||||
GravityFieldSurface("aetheria.daemon.game.field"),
|
||||
MainMenuOverlay("aetheria.daemon.game.main_menu", activeMainMenuSurfaceId),
|
||||
Hidden(Node(
|
||||
|
|
@ -187,6 +186,64 @@ namespace GameCult.Aetheria.State.Verse
|
|||
.ToArray());
|
||||
}
|
||||
|
||||
public static AetheriaRuntimeSurfaceDocument BuildReactiveGameplay(
|
||||
AetheriaRuntimeDaemonFrameDocument frame)
|
||||
{
|
||||
frame ??= new AetheriaRuntimeDaemonFrameDocument();
|
||||
var run = frame.Run ?? new AetheriaRuntimeRunCheckpointCommit();
|
||||
var zone = FindCurrentZone(run);
|
||||
var entity = FindCurrentEntity(run, zone);
|
||||
var entityIndex = entity?.EntityIndex ?? -1;
|
||||
var events = (run.GameEvents ?? Array.Empty<AetheriaRuntimeGameEventCommit>())
|
||||
.Where(value => value != null &&
|
||||
(entityIndex < 0 || value.SourceEntityIndex == entityIndex || value.TargetEntityIndex == entityIndex))
|
||||
.ToArray();
|
||||
var receipts = (run.ShotReceipts ?? Array.Empty<AetheriaRuntimeShotReceiptCommit>())
|
||||
.Where(value => value != null &&
|
||||
(entityIndex < 0 || value.SourceEntityIndex == entityIndex || value.TargetEntityIndex == entityIndex))
|
||||
.ToArray();
|
||||
var lastFrame = events.Select(value => value.FrameId)
|
||||
.Concat(receipts.Select(value => value.FrameId))
|
||||
.DefaultIfEmpty(0)
|
||||
.Max();
|
||||
var version = checked(lastFrame * 100000L + events.Length * 100L + receipts.Length);
|
||||
return new AetheriaRuntimeSurfaceDocument(
|
||||
AetheriaRuntimeProviderIdentity.ProviderId,
|
||||
"game.daemon",
|
||||
"Aetheria Pilot Reactive State",
|
||||
version,
|
||||
frame.PublishedAtUtc,
|
||||
new AetheriaRuntimeSurfaceTree(
|
||||
"aetheria.daemon.game.reactive",
|
||||
Node(
|
||||
"aetheria.daemon.game.reactive.root",
|
||||
"layer.reactive",
|
||||
Array.Empty<(string, string)>(),
|
||||
FeedbackStream(events, frame.FrameId),
|
||||
ShotReceiptStream(receipts)),
|
||||
Array.Empty<AetheriaRuntimeSurfaceStyleToken>()),
|
||||
Array.Empty<AetheriaRuntimeSurfaceCommandTemplate>());
|
||||
}
|
||||
|
||||
private static AetheriaRuntimeSurfaceComponent ReactiveGameplaySlot() =>
|
||||
new AetheriaRuntimeSurfaceComponent(
|
||||
"aetheria.daemon.game.reactive",
|
||||
"layer.reactive",
|
||||
new Dictionary<string, string>(StringComparer.Ordinal),
|
||||
Array.Empty<AetheriaRuntimeSurfaceComponent>(),
|
||||
Array.Empty<GameCult.Mesh.CultMeshStateBindingDescriptor>(),
|
||||
new[]
|
||||
{
|
||||
new AetheriaRuntimeEmbeddedDocumentSlot(
|
||||
"pilot-reactive-state",
|
||||
AetheriaRuntimeVerseRecordKeys.DaemonGameReactiveSurface.ToString(),
|
||||
EveSurfaceDocument.SchemaId,
|
||||
"reactive-state",
|
||||
new GameCult.Mesh.CultMeshRouteHint(
|
||||
GameCult.Mesh.CultMeshLocalityKind.SharedMemory,
|
||||
"exact retained pilot presentation state"))
|
||||
});
|
||||
|
||||
private static AetheriaRuntimeSurfaceCommandTemplate SurfaceCommand(
|
||||
AetheriaRuntimeDaemonCommandKinds kind) =>
|
||||
new AetheriaRuntimeSurfaceCommandTemplate(
|
||||
|
|
@ -477,9 +534,11 @@ namespace GameCult.Aetheria.State.Verse
|
|||
return (null, null);
|
||||
}
|
||||
|
||||
private static AetheriaRuntimeSurfaceComponent FeedbackStream(AetheriaRuntimeRunCheckpointCommit run, long frameId)
|
||||
private static AetheriaRuntimeSurfaceComponent FeedbackStream(
|
||||
IReadOnlyList<AetheriaRuntimeGameEventCommit> sourceEvents,
|
||||
long frameId)
|
||||
{
|
||||
var events = (run.GameEvents ?? Array.Empty<AetheriaRuntimeGameEventCommit>())
|
||||
var events = (sourceEvents ?? Array.Empty<AetheriaRuntimeGameEventCommit>())
|
||||
.Where(value => value != null)
|
||||
.OrderBy(value => value.FrameId)
|
||||
.ThenBy(value => value.EventId, StringComparer.Ordinal)
|
||||
|
|
@ -518,9 +577,10 @@ namespace GameCult.Aetheria.State.Verse
|
|||
return FormatNumber(after ? current : before);
|
||||
}
|
||||
|
||||
private static AetheriaRuntimeSurfaceComponent ShotReceiptStream(AetheriaRuntimeRunCheckpointCommit run)
|
||||
private static AetheriaRuntimeSurfaceComponent ShotReceiptStream(
|
||||
IReadOnlyList<AetheriaRuntimeShotReceiptCommit> sourceReceipts)
|
||||
{
|
||||
var receipts = (run.ShotReceipts ?? Array.Empty<AetheriaRuntimeShotReceiptCommit>())
|
||||
var receipts = (sourceReceipts ?? Array.Empty<AetheriaRuntimeShotReceiptCommit>())
|
||||
.Where(value => value != null)
|
||||
.OrderBy(value => value.FrameId)
|
||||
.ThenBy(value => value.ShotId, StringComparer.Ordinal)
|
||||
|
|
|
|||
|
|
@ -73,6 +73,9 @@ namespace GameCult.Aetheria.State.Verse
|
|||
public static CultRecordKey DaemonGameSurface { get; } =
|
||||
new CultRecordKey("eve:surface:aetheria.daemon.game");
|
||||
|
||||
public static CultRecordKey DaemonGameReactiveSurface { get; } =
|
||||
new CultRecordKey("eve:surface:aetheria.daemon.game.reactive");
|
||||
|
||||
public static CultRecordKey StarbridgeCommanderSurface { get; } =
|
||||
new CultRecordKey("eve:surface:aetheria.starbridge.commander");
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue