using System; using System.Collections.Generic; using System.Linq; #nullable enable namespace GameCult.Aetheria.State.Verse { public static class AetheriaRuntimeGameDocuments { public static AetheriaRuntimeGameViewportDocument Viewport( AetheriaRuntimeDaemonFrameDocument frame, AetheriaRuntimeViewportBounds viewport) { var objects = ObjectsViewport(frame, viewport); var gravity = GravityViewport(frame, viewport); return new AetheriaRuntimeGameViewportDocument { FrameId = objects.FrameId, PublishedAtUtc = objects.PublishedAtUtc, SimulationTimeSeconds = objects.SimulationTimeSeconds, RunId = objects.RunId, ZoneIndex = objects.ZoneIndex, ZoneName = objects.ZoneName, CurrentEntityKey = objects.CurrentEntityKey, Viewport = objects.Viewport, ControlledEntityIndices = objects.ControlledEntityIndices, Objects = objects.Objects, GravityInfluences = gravity.GravityInfluences, Bodies = gravity.Bodies }; } public static AetheriaRuntimeObjectsViewportDocument ObjectsViewport( AetheriaRuntimeDaemonFrameDocument frame, AetheriaRuntimeViewportBounds viewport) { frame ??= new AetheriaRuntimeDaemonFrameDocument(); viewport ??= new AetheriaRuntimeViewportBounds(); var normalizedViewport = Normalize(viewport); var context = Context(frame); var run = context.Run; var zone = context.Zone; var entities = zone.Entities ?? Array.Empty(); var projectiles = zone.Projectiles ?? Array.Empty(); var controlledEntityIndices = entities .Where(IsPlayerControlled) .Select(entity => entity.EntityIndex) .ToArray(); var controlled = entities .Where(entity => controlledEntityIndices.Contains(entity.EntityIndex)) .ToArray(); return new AetheriaRuntimeObjectsViewportDocument { FrameId = frame.FrameId, PublishedAtUtc = frame.PublishedAtUtc ?? "", SimulationTimeSeconds = frame.SimulationTimeSeconds, RunId = context.RunId, ZoneIndex = zone.ZoneIndex, ZoneName = string.IsNullOrWhiteSpace(zone.Name) ? $"Zone {zone.ZoneIndex}" : zone.Name, CurrentEntityKey = run.CurrentEntityKey ?? "", Viewport = normalizedViewport, ControlledEntityIndices = controlledEntityIndices, Objects = entities .Where(entity => IntersectsViewport(entity, normalizedViewport)) .Where(entity => IsPlayerControlled(entity) || controlled.Length == 0 || controlled.Any(observer => CanSee(observer, entity))) .Select(entity => ToViewportObject(entity, context.RunId, zone.ZoneIndex)) .Concat(projectiles .Where(projectile => projectile != null && projectile.Active) .Where(projectile => IntersectsViewport(projectile, normalizedViewport)) .Select(ToViewportObject)) .ToArray() }; } public static AetheriaRuntimeGravityViewportDocument GravityViewport( AetheriaRuntimeDaemonFrameDocument frame, AetheriaRuntimeViewportBounds viewport) { frame ??= new AetheriaRuntimeDaemonFrameDocument(); viewport ??= new AetheriaRuntimeViewportBounds(); var normalizedViewport = Normalize(viewport); var context = Context(frame); var zone = context.Zone; var visibleBodies = (zone.Bodies ?? Array.Empty()) .Where(body => GravityInfluenceIntersectsViewport(body, normalizedViewport)) .ToArray(); return new AetheriaRuntimeGravityViewportDocument { FrameId = frame.FrameId, PublishedAtUtc = frame.PublishedAtUtc ?? "", SimulationTimeSeconds = frame.SimulationTimeSeconds, RunId = context.RunId, ZoneIndex = zone.ZoneIndex, ZoneName = string.IsNullOrWhiteSpace(zone.Name) ? $"Zone {zone.ZoneIndex}" : zone.Name, Viewport = normalizedViewport, GravityInfluences = visibleBodies.Select(ToGravityInfluence).ToArray(), Bodies = visibleBodies.Select(body => ToBodyView(body, frame.RenderSettings)).ToArray(), TerrainRadius = zone.GravityTerrainRadius, TerrainDepth = zone.GravityTerrainDepth, TerrainDepthExponent = zone.GravityTerrainDepthExponent, TerrainWaveFrequency = zone.GravityTerrainWaveFrequency }; } public static AetheriaRuntimeRenderSplatsViewportDocument RenderSplatsViewport( AetheriaRuntimeDaemonFrameDocument frame, AetheriaRuntimeViewportBounds viewport) { frame ??= new AetheriaRuntimeDaemonFrameDocument(); viewport ??= new AetheriaRuntimeViewportBounds(); var normalizedViewport = Normalize(viewport); var context = Context(frame); var zone = context.Zone; var layers = BuildDefaultRenderSplatLayers(); var layerIndices = layers .Select((layer, index) => (layer.LayerKey, index)) .ToDictionary(pair => pair.LayerKey, pair => pair.index, StringComparer.Ordinal); var splats = new RenderSplatBuilder(); var viewportCenterX = (normalizedViewport.MinX + normalizedViewport.MaxX) * 0.5; var viewportCenterY = (normalizedViewport.MinY + normalizedViewport.MaxY) * 0.5; var viewportHalfX = Math.Max(0.0001, (normalizedViewport.MaxX - normalizedViewport.MinX) * 0.5); var viewportHalfY = Math.Max(0.0001, (normalizedViewport.MaxY - normalizedViewport.MinY) * 0.5); if (zone.GravityTerrainDepth != 0) { splats.Add( layerIndices[AetheriaRuntimeRenderSplatLayerKeys.GravityHeight], viewportCenterX, viewportCenterY, viewportHalfX, viewportHalfY, AetheriaRuntimeRenderSplatChannels.Gravity, AetheriaRuntimeRenderSplatFalloffs.Solid, -zone.GravityTerrainDepth, 0, 0, 1, "environment.gravity_terrain", sourceKind: AetheriaRuntimeRenderSplatSourceKinds.AnimatedSimplexNoise, frequencyX: 3.0, frequencyY: 3.0, animationSpeed: zone.GravityTerrainWaveFrequency * 0.025, sourceFlags: 1); } splats.Add( layerIndices[AetheriaRuntimeRenderSplatLayerKeys.FogSurfaceHeight], viewportCenterX, viewportCenterY, viewportHalfX, viewportHalfY, AetheriaRuntimeRenderSplatChannels.Tint, AetheriaRuntimeRenderSplatFalloffs.Solid, 1, 0, 0, 1, "environment.fog_surface_height", sourceKind: AetheriaRuntimeRenderSplatSourceKinds.AnimatedSimplexNoise, frequencyX: 4.0, frequencyY: 4.0, animationSpeed: 0.015, sourceFlags: 1); splats.Add( layerIndices[AetheriaRuntimeRenderSplatLayerKeys.FogPatchHeight], viewportCenterX, viewportCenterY, viewportHalfX, viewportHalfY, AetheriaRuntimeRenderSplatChannels.Tint, AetheriaRuntimeRenderSplatFalloffs.Solid, 1, 0, 0, 1, "environment.fog_patch_height", sourceKind: AetheriaRuntimeRenderSplatSourceKinds.AnimatedSimplexNoise, frequencyX: 9.0, frequencyY: 9.0, animationSpeed: 0.02, sourceFlags: 1); splats.Add( layerIndices[AetheriaRuntimeRenderSplatLayerKeys.FogPatch], viewportCenterX, viewportCenterY, viewportHalfX, viewportHalfY, AetheriaRuntimeRenderSplatChannels.Tint, AetheriaRuntimeRenderSplatFalloffs.Solid, 1, 0, 0, 1, "environment.fog_patch", sourceKind: AetheriaRuntimeRenderSplatSourceKinds.AnimatedSimplexNoise, frequencyX: 6.0, frequencyY: 6.0, animationSpeed: 0.01, sourceFlags: 1); foreach (var body in zone.Bodies ?? Array.Empty()) { if (!GravityInfluenceIntersectsViewport(body, normalizedViewport)) continue; var radius = ResolveGravityRadius(body); splats.Add( layerIndices[AetheriaRuntimeRenderSplatLayerKeys.GravityHeight], body.GravityInfluenceCenterX, body.GravityInfluenceCenterZ, radius, radius, AetheriaRuntimeRenderSplatChannels.Gravity, AetheriaRuntimeRenderSplatFalloffs.InverseSmooth, body.GravityWellDepth, 0, 0, 1, body.BodyKey ?? ""); if (body.GravityWaveRadius > 0 && body.GravityWaveDepth != 0) { splats.Add( layerIndices[AetheriaRuntimeRenderSplatLayerKeys.GravityWave], body.GravityInfluenceCenterX, body.GravityInfluenceCenterZ, body.GravityWaveRadius, body.GravityWaveRadius, AetheriaRuntimeRenderSplatChannels.GravityWave, AetheriaRuntimeRenderSplatFalloffs.Smooth, body.GravityWaveDepth, body.GravityWaveSpeed, 0, 1, body.BodyKey ?? ""); } if (IsBodyKind(body, "sun")) { var tintRadius = Math.Max(radius, Math.Max(32, body.BodyRadiusMultiplier * 70) * Math.Max(0.01, body.SunVisual?.LightRadiusMultiplier ?? 1.0)); splats.Add( layerIndices[AetheriaRuntimeRenderSplatLayerKeys.FogTint], body.GravityInfluenceCenterX, body.GravityInfluenceCenterZ, tintRadius, tintRadius, AetheriaRuntimeRenderSplatChannels.Tint, AetheriaRuntimeRenderSplatFalloffs.Smooth, body.SunVisual?.FogTintColorX ?? 0, body.SunVisual?.FogTintColorY ?? 0, body.SunVisual?.FogTintColorZ ?? 0, 1, body.BodyKey ?? ""); } } foreach (var entity in zone.Entities ?? Array.Empty()) { if (!IsPlayerControlled(entity)) continue; var visibility = Math.Max(180, entity.Visibility); if (entity.PositionX + visibility < normalizedViewport.MinX || entity.PositionX - visibility > normalizedViewport.MaxX || entity.PositionZ + visibility < normalizedViewport.MinY || entity.PositionZ - visibility > normalizedViewport.MaxY) { continue; } splats.Add( layerIndices[AetheriaRuntimeRenderSplatLayerKeys.Visibility], entity.PositionX, entity.PositionZ, visibility, visibility, AetheriaRuntimeRenderSplatChannels.Visibility, AetheriaRuntimeRenderSplatFalloffs.Smooth, 1, 1, 1, 1, AetheriaRuntimeRunCheckpointCommit.EntityRecordKey(context.RunId, zone.ZoneIndex, entity.EntityIndex)); } return new AetheriaRuntimeRenderSplatsViewportDocument { FrameId = frame.FrameId, PublishedAtUtc = frame.PublishedAtUtc ?? "", SimulationTimeSeconds = frame.SimulationTimeSeconds, RunId = context.RunId, ZoneIndex = zone.ZoneIndex, ZoneName = string.IsNullOrWhiteSpace(zone.Name) ? $"Zone {zone.ZoneIndex}" : zone.Name, Viewport = normalizedViewport, Layers = layers, Splats = splats.Build() }; } private static IReadOnlyList BuildDefaultRenderSplatLayers() { return new[] { new AetheriaRuntimeRenderSplatLayerDefinition { LayerKey = AetheriaRuntimeRenderSplatLayerKeys.GravityHeight, DisplayName = "Gravity Height", Channel = AetheriaRuntimeRenderSplatChannels.Gravity, BlendMode = AetheriaRuntimeRenderSplatBlendModes.Add, GraphicsFormat = "R16_SFloat" }, new AetheriaRuntimeRenderSplatLayerDefinition { LayerKey = AetheriaRuntimeRenderSplatLayerKeys.GravityWave, DisplayName = "Gravity Wave", Channel = AetheriaRuntimeRenderSplatChannels.GravityWave, BlendMode = AetheriaRuntimeRenderSplatBlendModes.Add, GraphicsFormat = "R16_SFloat" }, new AetheriaRuntimeRenderSplatLayerDefinition { LayerKey = AetheriaRuntimeRenderSplatLayerKeys.Visibility, DisplayName = "Visibility Mask", Channel = AetheriaRuntimeRenderSplatChannels.Visibility, BlendMode = AetheriaRuntimeRenderSplatBlendModes.Max, GraphicsFormat = "R16_SFloat" }, new AetheriaRuntimeRenderSplatLayerDefinition { LayerKey = AetheriaRuntimeRenderSplatLayerKeys.FogSurfaceHeight, DisplayName = "Fog Surface Height", Channel = AetheriaRuntimeRenderSplatChannels.Tint, BlendMode = AetheriaRuntimeRenderSplatBlendModes.Add, GraphicsFormat = "R16_SFloat" }, new AetheriaRuntimeRenderSplatLayerDefinition { LayerKey = AetheriaRuntimeRenderSplatLayerKeys.FogPatchHeight, DisplayName = "Fog Patch Height", Channel = AetheriaRuntimeRenderSplatChannels.Tint, BlendMode = AetheriaRuntimeRenderSplatBlendModes.Add, GraphicsFormat = "R16_SFloat" }, new AetheriaRuntimeRenderSplatLayerDefinition { LayerKey = AetheriaRuntimeRenderSplatLayerKeys.FogPatch, DisplayName = "Fog Patch", Channel = AetheriaRuntimeRenderSplatChannels.Tint, BlendMode = AetheriaRuntimeRenderSplatBlendModes.Max, GraphicsFormat = "R16_SFloat" }, new AetheriaRuntimeRenderSplatLayerDefinition { LayerKey = AetheriaRuntimeRenderSplatLayerKeys.FogTint, DisplayName = "Fog Tint", Channel = AetheriaRuntimeRenderSplatChannels.Tint, BlendMode = AetheriaRuntimeRenderSplatBlendModes.Add, GraphicsFormat = "B10G11R11_UFloatPack32" }, new AetheriaRuntimeRenderSplatLayerDefinition { LayerKey = AetheriaRuntimeRenderSplatLayerKeys.Influence, DisplayName = "Influence", Channel = AetheriaRuntimeRenderSplatChannels.Influence, BlendMode = AetheriaRuntimeRenderSplatBlendModes.Add, GraphicsFormat = "R16_SFloat" } }; } public static AetheriaRuntimeCurrentZoneDocument CurrentZone( AetheriaRuntimeDaemonFrameDocument frame) { frame ??= new AetheriaRuntimeDaemonFrameDocument(); var context = Context(frame); var run = context.Run; var zone = context.Zone; return new AetheriaRuntimeCurrentZoneDocument { FrameId = frame.FrameId, PublishedAtUtc = frame.PublishedAtUtc ?? "", SimulationTimeSeconds = frame.SimulationTimeSeconds, RunId = context.RunId, ZoneIndex = zone.ZoneIndex, ZoneName = string.IsNullOrWhiteSpace(zone.Name) ? $"Zone {zone.ZoneIndex}" : zone.Name, PositionX = zone.PositionX, PositionY = zone.PositionY, CurrentEntityKey = run.CurrentEntityKey ?? "", AdjacentZoneIndices = zone.AdjacentZoneIndices ?? Array.Empty() }; } public static AetheriaRuntimeCurrentEntityDocument CurrentEntity( AetheriaRuntimeDaemonFrameDocument frame) { frame ??= new AetheriaRuntimeDaemonFrameDocument(); var context = Context(frame); var currentEntityIndex = TryParseEntityIndex(context.Run.CurrentEntityKey); var entity = (context.Zone.Entities ?? Array.Empty()) .FirstOrDefault(candidate => candidate.EntityIndex == currentEntityIndex); var entityKey = entity == null ? context.Run.CurrentEntityKey ?? "" : AetheriaRuntimeRunCheckpointCommit.EntityRecordKey(context.RunId, context.Zone.ZoneIndex, entity.EntityIndex); var inventory = entity == null ? Array.Empty() : Inventory(entity).ToArray(); return new AetheriaRuntimeCurrentEntityDocument { FrameId = frame.FrameId, PublishedAtUtc = frame.PublishedAtUtc ?? "", SimulationTimeSeconds = frame.SimulationTimeSeconds, RunId = context.RunId, ZoneIndex = context.Zone.ZoneIndex, EntityKey = entityKey, EntityIndex = entity?.EntityIndex ?? currentEntityIndex, Entity = entity == null ? null : ToViewportObject(entity, context.RunId, context.Zone.ZoneIndex), Status = entity == null ? new AetheriaRuntimeEntityStatus() : new AetheriaRuntimeEntityStatus { Hull = Stat(entity, "hull"), Shield = Stat(entity, "shield"), Heat = Stat(entity, "heat") }, Inventory = inventory, Equipment = inventory.Where(item => string.Equals(item.Source, "equipment", StringComparison.Ordinal)).ToArray(), Cargo = inventory.Where(item => string.Equals(item.Source, "cargo", StringComparison.Ordinal)).ToArray(), ShutdownPerformance = entity?.ShutdownPerformance ?? 0, Hud = CurrentEntityHudStatus(entity) }; } public static AetheriaRuntimeCurrentDockingDocument CurrentDocking( AetheriaRuntimeDaemonFrameDocument frame) { frame ??= new AetheriaRuntimeDaemonFrameDocument(); var context = Context(frame); var currentEntityIndex = TryParseEntityIndex(context.Run.CurrentEntityKey); var parent = FindDockParent(context.Zone, currentEntityIndex, out var dockingBayIndex); var currentEntityKey = currentEntityIndex < 0 ? context.Run.CurrentEntityKey ?? "" : AetheriaRuntimeRunCheckpointCommit.EntityRecordKey(context.RunId, context.Zone.ZoneIndex, currentEntityIndex); var parentKey = parent == null ? "" : AetheriaRuntimeRunCheckpointCommit.EntityRecordKey(context.RunId, context.Zone.ZoneIndex, parent.EntityIndex); return new AetheriaRuntimeCurrentDockingDocument { FrameId = frame.FrameId, PublishedAtUtc = frame.PublishedAtUtc ?? "", SimulationTimeSeconds = frame.SimulationTimeSeconds, RunId = context.RunId, ZoneIndex = context.Zone.ZoneIndex, CurrentEntityKey = currentEntityKey, CurrentEntityIndex = currentEntityIndex, IsDocked = parent != null && dockingBayIndex >= 0, DockParentEntityKey = parentKey, DockParentEntityIndex = parent?.EntityIndex ?? -1, DockingBayIndex = dockingBayIndex, DockParent = parent == null ? null : ToViewportObject(parent, context.RunId, context.Zone.ZoneIndex) }; } public static AetheriaRuntimeZoneContactsDocument ZoneContacts( AetheriaRuntimeDaemonFrameDocument frame) { frame ??= new AetheriaRuntimeDaemonFrameDocument(); var context = Context(frame); var entities = context.Zone.Entities ?? Array.Empty(); var entityMap = entities .Where(entity => entity != null && entity.EntityIndex >= 0) .ToDictionary(entity => entity.EntityIndex, entity => entity); return new AetheriaRuntimeZoneContactsDocument { FrameId = frame.FrameId, PublishedAtUtc = frame.PublishedAtUtc ?? "", SimulationTimeSeconds = frame.SimulationTimeSeconds, RunId = context.RunId, ZoneIndex = context.Zone.ZoneIndex, CurrentEntityKey = context.Run.CurrentEntityKey ?? "", Targets = entities .Where(entity => entity != null && entity.TargetEntityIndex >= 0 && entityMap.ContainsKey(entity.TargetEntityIndex)) .Select(entity => ToZoneTargetRow(entity, entityMap[entity.TargetEntityIndex])) .ToArray(), Contacts = entities .Where(entity => entity != null) .SelectMany(entity => (entity.Contacts ?? Array.Empty()) .Where(contact => contact != null && contact.TargetEntityIndex >= 0 && entityMap.ContainsKey(contact.TargetEntityIndex)) .Select(contact => ProjectZoneContactRow(entity, entityMap[contact.TargetEntityIndex], contact))) .ToArray() }; } public static AetheriaRuntimeStationRefitDocument StationRefit( AetheriaRuntimeDaemonFrameDocument frame, IReadOnlyList? loadoutTemplates = null, AetheriaRuntimeCatalogSnapshot? catalog = null) { frame ??= new AetheriaRuntimeDaemonFrameDocument(); var context = Context(frame); var currentEntityIndex = TryParseEntityIndex(context.Run.CurrentEntityKey); var parent = FindDockParent(context.Zone, currentEntityIndex, out var dockingBayIndex); var currentEntityKey = currentEntityIndex < 0 ? context.Run.CurrentEntityKey ?? "" : AetheriaRuntimeRunCheckpointCommit.EntityRecordKey(context.RunId, context.Zone.ZoneIndex, currentEntityIndex); var parentKey = parent == null ? "" : AetheriaRuntimeRunCheckpointCommit.EntityRecordKey(context.RunId, context.Zone.ZoneIndex, parent.EntityIndex); var availableEntities = parent == null ? Array.Empty() : StationRefitEntities(context, parent, currentEntityIndex); var stationStock = parent == null ? Array.Empty() : StationStock(parent); var dockingBays = parent == null ? Array.Empty() : ProjectStationDockingBays(context, parent, currentEntityIndex); var cargoTargets = parent == null ? Array.Empty() : ProjectStationCargoTargets(parentKey, dockingBayIndex, dockingBays, availableEntities); stationStock = StationStockTradeFacts( stationStock, availableEntities, cargoTargets, context.Run.Credits, catalog); return new AetheriaRuntimeStationRefitDocument { FrameId = frame.FrameId, PublishedAtUtc = frame.PublishedAtUtc ?? "", SimulationTimeSeconds = frame.SimulationTimeSeconds, RunId = context.RunId, ZoneIndex = context.Zone.ZoneIndex, CurrentEntityKey = currentEntityKey, CurrentEntityIndex = currentEntityIndex, IsDocked = parent != null && dockingBayIndex >= 0, DockParentEntityKey = parentKey, DockParentEntityIndex = parent?.EntityIndex ?? -1, DockingBayIndex = dockingBayIndex, DockParent = parent == null ? null : ToViewportObject(parent, context.RunId, context.Zone.ZoneIndex), AvailableEntities = availableEntities, Credits = context.Run.Credits, StationStock = stationStock, DockingBays = dockingBays, LoadoutRestoreOptions = parent == null ? Array.Empty() : ProjectLoadoutRestoreOptions(parentKey, context.Run.Credits, loadoutTemplates, catalog), CargoTargets = cargoTargets }; } private static AetheriaRuntimeZoneTargetRow ToZoneTargetRow( AetheriaRuntimeEntitySnapshotCommit observer, AetheriaRuntimeEntitySnapshotCommit target) { var deltaX = target.PositionX - observer.PositionX; var deltaY = target.PositionY - observer.PositionY; var deltaZ = target.PositionZ - observer.PositionZ; return new AetheriaRuntimeZoneTargetRow { EntityIndex = observer.EntityIndex, TargetEntityIndex = target.EntityIndex, TargetPositionX = target.PositionX, TargetPositionY = target.PositionY, TargetPositionZ = target.PositionZ, DeltaX = deltaX, DeltaY = deltaY, DeltaZ = deltaZ, Distance = Math.Sqrt(deltaX * deltaX + deltaZ * deltaZ) }; } private static AetheriaRuntimeZoneContactRow ProjectZoneContactRow( AetheriaRuntimeEntitySnapshotCommit observer, AetheriaRuntimeEntitySnapshotCommit target, AetheriaRuntimeEntityContactCommit contact) { var deltaX = target.PositionX - observer.PositionX; var deltaY = target.PositionY - observer.PositionY; var deltaZ = target.PositionZ - observer.PositionZ; return new AetheriaRuntimeZoneContactRow { ObserverEntityIndex = observer.EntityIndex, TargetEntityIndex = target.EntityIndex, InfoGathered = contact.InfoGathered, Hostile = contact.Hostile, Visible = contact.Visible, TargetPositionX = target.PositionX, TargetPositionY = target.PositionY, TargetPositionZ = target.PositionZ, DeltaX = deltaX, DeltaY = deltaY, DeltaZ = deltaZ, Distance = Math.Sqrt(deltaX * deltaX + deltaZ * deltaZ) }; } public static AetheriaRuntimeSectorMapDocument SectorMap( AetheriaRuntimeDaemonFrameDocument frame) { frame ??= new AetheriaRuntimeDaemonFrameDocument(); var context = Context(frame); var run = context.Run; var zones = run.Zones ?? Array.Empty(); var discovered = new HashSet(run.DiscoveredZoneIndices ?? Array.Empty()); if (run.CurrentZoneIndex >= 0) discovered.Add(run.CurrentZoneIndex); return new AetheriaRuntimeSectorMapDocument { FrameId = frame.FrameId, PublishedAtUtc = frame.PublishedAtUtc ?? "", SimulationTimeSeconds = frame.SimulationTimeSeconds, RunId = context.RunId, CurrentZoneIndex = run.CurrentZoneIndex, EntranceZoneIndex = run.EntranceZoneIndex, ExitZoneIndex = run.ExitZoneIndex, IsTutorial = run.IsTutorial, GenerationSeed = run.GenerationSeed, FactionRelationships = (run.FactionRelationships ?? Array.Empty()) .Where(relationship => relationship != null) .ToArray(), DiscoveredZoneIndices = discovered.OrderBy(index => index).ToArray(), Zones = zones .OrderBy(zone => zone.ZoneIndex) .Select(zone => ToSectorMapZone(zone, run, discovered)) .ToArray(), Links = SectorMapLinks(zones, discovered) }; } public static AetheriaRuntimeZoneDetailsDocument ZoneDetails( AetheriaRuntimeDaemonFrameDocument frame, int zoneIndex) { frame ??= new AetheriaRuntimeDaemonFrameDocument(); var context = Context(frame); var zone = (context.Run.Zones ?? Array.Empty()) .FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == zoneIndex); if (zone == null) { return new AetheriaRuntimeZoneDetailsDocument { FrameId = frame.FrameId, PublishedAtUtc = frame.PublishedAtUtc ?? "", SimulationTimeSeconds = frame.SimulationTimeSeconds, RunId = context.RunId, ZoneIndex = zoneIndex, ZoneName = zoneIndex < 0 ? "" : $"Zone {zoneIndex}", HasContents = false }; } return new AetheriaRuntimeZoneDetailsDocument { FrameId = frame.FrameId, PublishedAtUtc = frame.PublishedAtUtc ?? "", SimulationTimeSeconds = frame.SimulationTimeSeconds, RunId = context.RunId, ZoneIndex = zone.ZoneIndex, ZoneName = string.IsNullOrWhiteSpace(zone.Name) ? $"Zone {zone.ZoneIndex}" : zone.Name, Mass = (zone.Bodies ?? Array.Empty()) .Where(body => body != null) .Sum(body => body.Mass), Radius = Math.Max(0, zone.GravityTerrainRadius), BodyKinds = (zone.Bodies ?? Array.Empty()) .Where(body => body != null && !string.IsNullOrWhiteSpace(body.Kind)) .Select(body => body.Kind) .ToArray(), EntityHullItemKeys = (zone.Entities ?? Array.Empty()) .Where(entity => entity != null && !string.IsNullOrWhiteSpace(entity.HullItemKey)) .Select(entity => entity.HullItemKey) .ToArray(), HasContents = true }; } public static AetheriaRuntimeZoneRenderDocument ZoneRender( AetheriaRuntimeDaemonFrameDocument frame) { frame ??= new AetheriaRuntimeDaemonFrameDocument(); var context = Context(frame); var run = context.Run; var zone = context.Zone; var zoneRenderRadius = AetheriaRuntimeDaemonRenderQueries.ResolveZoneRenderRadius(zone, 2000); return new AetheriaRuntimeZoneRenderDocument { FrameId = frame.FrameId, PublishedAtUtc = frame.PublishedAtUtc ?? "", SimulationTimeSeconds = frame.SimulationTimeSeconds, RunId = context.RunId, ZoneIndex = zone.ZoneIndex, ZoneName = string.IsNullOrWhiteSpace(zone.Name) ? $"Zone {zone.ZoneIndex}" : zone.Name, CurrentEntityKey = run.CurrentEntityKey ?? "", ZoneRenderRadius = zoneRenderRadius, Credits = run.Credits, AdjacentZones = ZoneRenderAdjacentZones(run, zone), WormholeExits = ZoneRenderWormholeExits(run, zone, zoneRenderRadius, frame.RenderSettings), BodyPoses = ZoneRenderBodyPoses(zone), AsteroidBeltPoses = ZoneRenderAsteroidBeltPoses(zone, frame.SimulationTimeSeconds), DroppedPickups = (zone.DroppedPickups ?? Array.Empty()) .Where(pickup => pickup != null) .OrderBy(pickup => pickup.PickupIndex) .ToArray(), EntitySnapshots = (zone.Entities ?? Array.Empty()) .Where(entity => entity != null) .OrderBy(entity => entity.EntityIndex) .ToArray(), Orbits = (zone.Orbits ?? Array.Empty()) .Where(orbit => orbit != null) .ToArray(), Bodies = (zone.Bodies ?? Array.Empty()) .Where(body => body != null) .ToArray(), Projectiles = (zone.Projectiles ?? Array.Empty()) .Where(projectile => projectile != null && projectile.Active) .ToArray() }; } public static AetheriaRuntimeSelectedObjectDocument SelectedObject( AetheriaRuntimeDaemonFrameDocument frame, int entityIndex) { frame ??= new AetheriaRuntimeDaemonFrameDocument(); var context = Context(frame); var entity = (context.Zone.Entities ?? Array.Empty()) .FirstOrDefault(candidate => candidate.EntityIndex == entityIndex); return new AetheriaRuntimeSelectedObjectDocument { FrameId = frame.FrameId, RunId = context.RunId, ZoneIndex = context.Zone.ZoneIndex, EntityIndex = entityIndex, Selected = entity == null ? null : ToViewportObject(entity, context.RunId, context.Zone.ZoneIndex) }; } public static AetheriaRuntimeInventoryDocument Inventory( AetheriaRuntimeDaemonFrameDocument frame, int entityIndex) { frame ??= new AetheriaRuntimeDaemonFrameDocument(); var context = Context(frame); var entity = (context.Zone.Entities ?? Array.Empty()) .FirstOrDefault(candidate => candidate.EntityIndex == entityIndex); var items = entity == null ? Array.Empty() : Inventory(entity).ToArray(); return new AetheriaRuntimeInventoryDocument { FrameId = frame.FrameId, RunId = context.RunId, ZoneIndex = context.Zone.ZoneIndex, EntityIndex = entityIndex, EntityKey = entity == null ? "" : AetheriaRuntimeRunCheckpointCommit.EntityRecordKey(context.RunId, context.Zone.ZoneIndex, entityIndex), Items = items, Equipment = items.Where(item => string.Equals(item.Source, "equipment", StringComparison.Ordinal)).ToArray(), Cargo = items.Where(item => string.Equals(item.Source, "cargo", StringComparison.Ordinal)).ToArray() }; } public static AetheriaRuntimeViewportBounds Normalize(AetheriaRuntimeViewportBounds viewport) { viewport ??= new AetheriaRuntimeViewportBounds(); return new AetheriaRuntimeViewportBounds { MinX = Math.Min(viewport.MinX, viewport.MaxX), MinY = Math.Min(viewport.MinY, viewport.MaxY), MaxX = Math.Max(viewport.MinX, viewport.MaxX), MaxY = Math.Max(viewport.MinY, viewport.MaxY) }; } public static bool IntersectsViewport( AetheriaRuntimeEntitySnapshotCommit entity, AetheriaRuntimeViewportBounds viewport) { return entity.PositionX >= viewport.MinX && entity.PositionX <= viewport.MaxX && entity.PositionZ >= viewport.MinY && entity.PositionZ <= viewport.MaxY; } public static bool IntersectsViewport( AetheriaRuntimeProjectileCommit projectile, AetheriaRuntimeViewportBounds viewport) { return projectile.PositionX >= viewport.MinX && projectile.PositionX <= viewport.MaxX && projectile.PositionZ >= viewport.MinY && projectile.PositionZ <= viewport.MaxY; } public static bool GravityInfluenceIntersectsViewport( AetheriaRuntimeBodySnapshotCommit body, AetheriaRuntimeViewportBounds viewport) { var radius = ResolveGravityRadius(body); return body.GravityInfluenceCenterX + radius >= viewport.MinX && body.GravityInfluenceCenterX - radius <= viewport.MaxX && body.GravityInfluenceCenterZ + radius >= viewport.MinY && body.GravityInfluenceCenterZ - radius <= viewport.MaxY; } public static double ResolveGravityRadius(AetheriaRuntimeBodySnapshotCommit body) { if (double.IsFinite(body.GravityInfluenceRadius) && body.GravityInfluenceRadius > 0) return body.GravityInfluenceRadius; return Math.Max(32, body.BodyRadiusMultiplier * 70); } private static AetheriaRuntimeViewportObject ToViewportObject( AetheriaRuntimeEntitySnapshotCommit entity, string runId, int zoneIndex) { var obj = new AetheriaRuntimeViewportObject { EntityIndex = entity.EntityIndex, EntityKey = AetheriaRuntimeRunCheckpointCommit.EntityRecordKey(runId, zoneIndex, entity.EntityIndex), DisplayName = entity.Name ?? "", Kind = entity.Kind ?? "", FactionKey = entity.FactionKey ?? "", X = entity.PositionX, Y = entity.PositionZ, Z = entity.PositionY, DirectionX = entity.DirectionX, DirectionY = entity.DirectionY, VelocityX = entity.VelocityX, VelocityY = entity.VelocityY, Controlled = IsPlayerControlled(entity), TargetEntityIndex = entity.TargetEntityIndex, IsActive = entity.IsActive, Visibility = entity.Visibility, Status = new AetheriaRuntimeEntityStatus { Hull = Stat(entity, "hull"), Shield = Stat(entity, "shield"), Heat = Stat(entity, "heat") }, Inventory = Inventory(entity) }; obj.IconAsset = AetheriaRuntimeAssets.ResolveEntityIcon(obj); return obj; } private static AetheriaRuntimeViewportObject ToViewportObject( AetheriaRuntimeProjectileCommit projectile) { return new AetheriaRuntimeViewportObject { EntityIndex = -1, EntityKey = projectile.ProjectileId ?? "", DisplayName = projectile.WeaponKind ?? "projectile", Kind = "projectile", FactionKey = projectile.FactionKey ?? "", X = projectile.PositionX, Y = projectile.PositionZ, Z = projectile.PositionY, DirectionX = projectile.DirectionX, DirectionY = projectile.DirectionY, VelocityX = projectile.VelocityX, VelocityY = projectile.VelocityY, Controlled = false, TargetEntityIndex = projectile.TargetEntityIndex, IsActive = projectile.Active, Visibility = projectile.Radius, Status = new AetheriaRuntimeEntityStatus { Hull = projectile.Damage, Shield = 0, Heat = projectile.AgeSeconds }, Inventory = Array.Empty(), IconAsset = AetheriaRuntimeAssetRef.FromKey( "map.entity.projectile", AetheriaRuntimeAssetKinds.Sprite, "cultmesh://aetheria/assets/map/entity/projectile", AetheriaRuntimeAssetTransports.CultMesh, "image/*") }; } private static AetheriaRuntimeBodyView ToBodyView( AetheriaRuntimeBodySnapshotCommit body, AetheriaRuntimeDaemonRenderSettings renderSettings) { var view = new AetheriaRuntimeBodyView { BodyKey = body.BodyKey ?? "", OrbitKey = body.OrbitKey ?? "", Name = body.Name ?? "", Kind = body.Kind ?? "", X = body.GravityInfluenceCenterX, Y = body.GravityInfluenceCenterZ, Radius = Math.Max(32, body.BodyRadiusMultiplier * 70), IsAsteroidBelt = (body.Kind ?? "").IndexOf("asteroid", StringComparison.OrdinalIgnoreCase) >= 0, Body = body, IconSize = body.IconSize > 0 ? body.IconSize : renderSettings.ResolveBodyIconSize(body.Mass) }; view.IconAsset = AetheriaRuntimeAssets.ResolveBodyIcon(view); return view; } private static AetheriaRuntimeGravityInfluence ToGravityInfluence(AetheriaRuntimeBodySnapshotCommit body) { return new AetheriaRuntimeGravityInfluence { BodyKey = body.BodyKey ?? "", OrbitKey = body.OrbitKey ?? "", Kind = body.Kind ?? "", X = body.GravityInfluenceCenterX, Y = body.GravityInfluenceCenterZ, Radius = ResolveGravityRadius(body), GravityDepth = body.GravityWellDepth, GravityDepthExponent = body.GravityDepthExponent, WaveRadius = body.GravityWaveRadius, WaveDepth = body.GravityWaveDepth, WaveSpeed = body.GravityWaveSpeed }; } private static AetheriaRuntimeSectorMapZone ToSectorMapZone( AetheriaRuntimeZoneSnapshotCommit zone, AetheriaRuntimeRunCheckpointCommit run, HashSet discovered) { return new AetheriaRuntimeSectorMapZone { ZoneIndex = zone.ZoneIndex, Name = zone.Name ?? "", X = zone.PositionX, Y = zone.PositionY, OwnerFactionIndex = zone.OwnerFactionIndex, FactionIndices = zone.FactionIndices ?? Array.Empty(), AdjacentZoneIndices = zone.AdjacentZoneIndices ?? Array.Empty(), Discovered = discovered.Contains(zone.ZoneIndex), Current = zone.ZoneIndex == run.CurrentZoneIndex, Entrance = zone.ZoneIndex == run.EntranceZoneIndex, Exit = zone.ZoneIndex == run.ExitZoneIndex }; } private static IReadOnlyList SectorMapLinks( IReadOnlyList zones, HashSet discovered) { var links = new List(); foreach (var zone in zones ?? Array.Empty()) { foreach (var adjacentIndex in zone.AdjacentZoneIndices ?? Array.Empty()) { if (zone.ZoneIndex < 0 || adjacentIndex < 0 || zone.ZoneIndex >= adjacentIndex) continue; links.Add(new AetheriaRuntimeSectorMapLink { FromZoneIndex = zone.ZoneIndex, ToZoneIndex = adjacentIndex, Discovered = discovered.Contains(zone.ZoneIndex) && discovered.Contains(adjacentIndex) }); } } return links.ToArray(); } private static AetheriaRuntimeZoneRenderAdjacentZone[] ZoneRenderAdjacentZones( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeZoneSnapshotCommit zone) { var adjacent = new HashSet(zone.AdjacentZoneIndices ?? Array.Empty()); if (adjacent.Count == 0) return Array.Empty(); return (run.Zones ?? Array.Empty()) .Where(candidate => candidate != null && adjacent.Contains(candidate.ZoneIndex)) .OrderBy(candidate => candidate.ZoneIndex) .Select(candidate => new AetheriaRuntimeZoneRenderAdjacentZone { ZoneIndex = candidate.ZoneIndex, X = candidate.PositionX, Y = candidate.PositionY }) .ToArray(); } private static AetheriaRuntimeZoneRenderWormholeExit[] ZoneRenderWormholeExits( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeZoneSnapshotCommit zone, double zoneRenderRadius, AetheriaRuntimeDaemonRenderSettings renderSettings) { return AetheriaRuntimeDaemonRenderQueries .QueryWormholeExits(run, zone, zoneRenderRadius, renderSettings.WormholeDistanceRatio) .Select(exit => new AetheriaRuntimeZoneRenderWormholeExit { TargetZoneIndex = exit.TargetZoneIndex, DirectionX = exit.DirectionX, DirectionZ = exit.DirectionZ, PositionX = exit.PositionX, PositionZ = exit.PositionZ }) .ToArray(); } private static AetheriaRuntimeZoneRenderBodyPose[] ZoneRenderBodyPoses( AetheriaRuntimeZoneSnapshotCommit zone) { return AetheriaRuntimeDaemonRenderQueries.QueryBodyPoses(zone) .Select(pose => new AetheriaRuntimeZoneRenderBodyPose { BodyKey = pose.BodyKey, OrbitKey = pose.OrbitKey, ParentOrbitKey = pose.ParentOrbitKey, Kind = pose.Kind, CenterX = pose.CenterX, CenterZ = pose.CenterZ, ParentCenterX = pose.ParentCenterX, ParentCenterZ = pose.ParentCenterZ, GravityWaveSpeed = pose.GravityWaveSpeed }) .ToArray(); } private static AetheriaRuntimeZoneRenderAsteroidBeltPose[] ZoneRenderAsteroidBeltPoses( AetheriaRuntimeZoneSnapshotCommit zone, double simulationTimeSeconds) { return AetheriaRuntimeDaemonRenderQueries.QueryAsteroidBeltPoses(zone) .Select(pose => new AetheriaRuntimeZoneRenderAsteroidBeltPose { BodyKey = pose.BodyKey, OrbitKey = pose.OrbitKey, CenterX = pose.CenterX, CenterZ = pose.CenterZ, Radius = pose.Radius, AsteroidCount = pose.AsteroidCount, InstancePoses = ZoneRenderAsteroidInstancePoses( zone, pose.BodyKey, simulationTimeSeconds) }) .ToArray(); } private static AetheriaRuntimeZoneRenderAsteroidInstancePose[] ZoneRenderAsteroidInstancePoses( AetheriaRuntimeZoneSnapshotCommit zone, string bodyKey, double simulationTimeSeconds) { return AetheriaRuntimeDaemonRenderQueries .QueryAsteroidInstancePoses(zone, bodyKey, simulationTimeSeconds) .Select(pose => new AetheriaRuntimeZoneRenderAsteroidInstancePose { BodyKey = pose.BodyKey, AsteroidIndex = pose.AsteroidIndex, PositionX = pose.PositionX, PositionZ = pose.PositionZ, Rotation = pose.Rotation, Size = pose.Size }) .ToArray(); } private static IReadOnlyList Inventory(AetheriaRuntimeEntitySnapshotCommit entity) { var items = new List(); var equipment = entity.Equipment ?? Array.Empty(); for (var equipmentIndex = 0; equipmentIndex < equipment.Count; equipmentIndex++) AddSlot(items, "equipment", equipmentIndex, equipment[equipmentIndex]); var cargo = entity.CargoContents ?? Array.Empty(); for (var cargoBayIndex = 0; cargoBayIndex < cargo.Count; cargoBayIndex++) { var bay = cargo[cargoBayIndex]; foreach (var slot in bay.Items ?? Array.Empty()) AddSlot(items, "cargo", cargoBayIndex, slot); } return items.Where(item => !string.IsNullOrWhiteSpace(item.ItemKey)).ToArray(); } private static IReadOnlyList StationRefitEntities( DocumentContext context, AetheriaRuntimeEntitySnapshotCommit dockParent, int currentEntityIndex) { var entities = context.Zone.Entities ?? Array.Empty(); var assignments = dockParent.DockingBayAssignments ?? Array.Empty(); var options = new List(); for (var dockingBayIndex = 0; dockingBayIndex < assignments.Count; dockingBayIndex++) { var entityIndex = assignments[dockingBayIndex]; if (entityIndex < 0) continue; var entity = entities.FirstOrDefault(candidate => candidate.EntityIndex == entityIndex); if (entity == null) continue; options.Add(new AetheriaRuntimeStationRefitEntityOption { EntityKey = AetheriaRuntimeRunCheckpointCommit.EntityRecordKey(context.RunId, context.Zone.ZoneIndex, entity.EntityIndex), EntityIndex = entity.EntityIndex, DisplayName = entity.Name ?? "", Kind = entity.Kind ?? "", IsCurrentEntity = entity.EntityIndex == currentEntityIndex, IsPlayerShip = IsPlayerControlled(entity) && string.Equals(entity.Kind, "Ship", StringComparison.OrdinalIgnoreCase), CargoBayCount = Math.Max( entity.CargoBays?.Count ?? 0, entity.CargoContents?.Count ?? 0), DockingBayIndex = dockingBayIndex, HullItemKey = entity.HullItemKey ?? "", CargoItems = StationStock(entity) }); } return options.ToArray(); } private static IReadOnlyList StationStock( AetheriaRuntimeEntitySnapshotCommit dockParent) { var stock = new List(); var cargo = dockParent.CargoContents ?? Array.Empty(); for (var cargoBayIndex = 0; cargoBayIndex < cargo.Count; cargoBayIndex++) { var bay = cargo[cargoBayIndex]; if (bay == null) continue; foreach (var slot in bay.Items ?? Array.Empty()) { if (slot == null) continue; var item = slot.Item; if (item == null || string.IsNullOrWhiteSpace(item.ItemKey)) continue; stock.Add(new AetheriaRuntimeStationStockItem { ItemKey = item.ItemKey ?? "", Quantity = item.Quantity, Quality = item.Quality, Durability = item.Durability, CargoBayIndex = cargoBayIndex, X = slot.X, Y = slot.Y }); } } return stock.ToArray(); } private static IReadOnlyList ProjectStationDockingBays( DocumentContext context, AetheriaRuntimeEntitySnapshotCommit dockParent, int currentEntityIndex) { var entities = context.Zone.Entities ?? Array.Empty(); var bays = dockParent.DockingBays ?? Array.Empty(); var assignments = dockParent.DockingBayAssignments ?? Array.Empty(); var contents = dockParent.DockingBayContents ?? Array.Empty(); var rows = new List(); for (var dockingBayIndex = 0; dockingBayIndex < bays.Count; dockingBayIndex++) { var bay = bays[dockingBayIndex]; var assignedEntityIndex = dockingBayIndex < assignments.Count ? assignments[dockingBayIndex] : -1; var assignedEntity = assignedEntityIndex < 0 ? null : entities.FirstOrDefault(candidate => candidate.EntityIndex == assignedEntityIndex); rows.Add(new AetheriaRuntimeStationDockingBayRow { DockingBayIndex = dockingBayIndex, ItemKey = bay?.Item?.ItemKey ?? "", X = bay?.X ?? -1, Y = bay?.Y ?? -1, OccupiedEntityIndex = assignedEntity?.EntityIndex ?? assignedEntityIndex, OccupiedEntityKey = assignedEntity == null ? "" : AetheriaRuntimeRunCheckpointCommit.EntityRecordKey(context.RunId, context.Zone.ZoneIndex, assignedEntity.EntityIndex), OccupiedEntityName = assignedEntity?.Name ?? "", OccupiedHullItemKey = assignedEntity?.HullItemKey ?? "", OccupiedByCurrentEntity = assignedEntityIndex >= 0 && assignedEntityIndex == currentEntityIndex, CargoItems = dockingBayIndex < contents.Count ? StationStock(contents[dockingBayIndex], dockingBayIndex) : Array.Empty() }); } return rows.ToArray(); } private static IReadOnlyList StationStock( AetheriaRuntimeCargoBayLoadoutCommit cargoBay, int cargoBayIndex) { if (cargoBay == null) return Array.Empty(); return (cargoBay.Items ?? Array.Empty()) .Where(slot => slot?.Item != null && !string.IsNullOrWhiteSpace(slot.Item.ItemKey)) .Select(slot => new AetheriaRuntimeStationStockItem { ItemKey = slot.Item.ItemKey ?? "", Quantity = slot.Item.Quantity, Quality = slot.Item.Quality, Durability = slot.Item.Durability, CargoBayIndex = cargoBayIndex, X = slot.X, Y = slot.Y }) .ToArray(); } private static IReadOnlyList ProjectLoadoutRestoreOptions( string targetEntityKey, int credits, IReadOnlyList? loadoutTemplates, AetheriaRuntimeCatalogSnapshot? catalog) { var templates = loadoutTemplates ?? Array.Empty(); if (string.IsNullOrWhiteSpace(targetEntityKey) || templates.Count == 0) return Array.Empty(); var options = new List(); for (var templateIndex = 0; templateIndex < templates.Count; templateIndex++) { var template = templates[templateIndex]; var canPrice = AetheriaRuntimeDaemonTradeItemQueries.TryPriceLoadoutTemplate( template, catalog, catalog?.TradeValueSettings, out var price); var canRestore = canPrice && price >= 0 && credits >= price && !string.IsNullOrWhiteSpace(template?.Name); options.Add(new AetheriaRuntimeStationLoadoutRestoreOption { TemplateIndex = templateIndex, TemplateName = template?.Name ?? "", TargetEntityKey = targetEntityKey, Price = canPrice ? price : 0, CanRestore = canRestore }); } return options.ToArray(); } private static IReadOnlyList StationStockTradeFacts( IReadOnlyList stationStock, IReadOnlyList availableEntities, IReadOnlyList cargoTargets, int credits, AetheriaRuntimeCatalogSnapshot? catalog) { return (stationStock ?? Array.Empty()) .Select(stock => { var itemKey = stock?.ItemKey ?? ""; var price = StationStockPrice(stock, catalog); return new AetheriaRuntimeStationStockItem { ItemKey = itemKey, Quantity = stock?.Quantity ?? 0, Quality = stock?.Quality ?? 1, Durability = stock?.Durability ?? 1, CargoBayIndex = stock?.CargoBayIndex ?? -1, X = stock?.X ?? -1, Y = stock?.Y ?? -1, Price = price, CanAfford = price >= 0 && credits >= price, OwnedQuantity = CountStationOwnedQuantity(itemKey, availableEntities, cargoTargets, catalog) }; }) .ToArray(); } private static int StationStockPrice( AetheriaRuntimeStationStockItem? stock, AetheriaRuntimeCatalogSnapshot? catalog) { var itemKey = stock?.ItemKey ?? ""; var typedItem = catalog?.FindItem(itemKey); if (typedItem == null) return 0; return AetheriaRuntimeDaemonTradeItemQueries.TradeItemValue( typedItem, AetheriaRuntimeDaemonTradeItemQueries.CraftedItemCommit( itemKey, stock?.Quality ?? 1, stock?.Durability ?? 1), catalog?.TradeValueSettings) .Price; } private static int CountStationOwnedQuantity( string itemKey, IReadOnlyList availableEntities, IReadOnlyList cargoTargets, AetheriaRuntimeCatalogSnapshot? catalog) { if (string.IsNullOrWhiteSpace(itemKey)) return 0; var typedItem = catalog?.FindItem(itemKey); if (!string.IsNullOrWhiteSpace(typedItem?.HullType)) { return (availableEntities ?? Array.Empty()) .Count(entity => entity?.IsPlayerShip == true && string.Equals(entity.HullItemKey, itemKey, StringComparison.Ordinal)); } var stackable = typedItem?.Stackable == true; var matchingCargo = (cargoTargets ?? Array.Empty()) .SelectMany(target => target?.CargoItems ?? Array.Empty()) .Where(item => string.Equals(item.ItemKey, itemKey, StringComparison.Ordinal)); return stackable ? matchingCargo.Sum(item => Math.Max(item.Quantity, 0)) : matchingCargo.Count(); } private static IReadOnlyList ProjectStationCargoTargets( string dockParentEntityKey, int currentDockingBayIndex, IReadOnlyList dockingBays, IReadOnlyList availableEntities) { var targets = new List(); var targetIndex = 0; var currentDockingBay = (dockingBays ?? Array.Empty()) .FirstOrDefault(row => row != null && row.DockingBayIndex == currentDockingBayIndex); if (!string.IsNullOrWhiteSpace(dockParentEntityKey) && currentDockingBayIndex >= 0) { targets.Add(new AetheriaRuntimeStationCargoTargetRow { TargetIndex = targetIndex++, Kind = AetheriaRuntimeTradeCargoTargetKind.DockingBay, Label = "Docking Bay", EntityKey = dockParentEntityKey, BayIndex = currentDockingBayIndex, IsCurrent = true, CargoItems = currentDockingBay?.CargoItems ?? Array.Empty() }); } foreach (var entity in availableEntities ?? Array.Empty()) { if (entity?.IsPlayerShip != true || string.IsNullOrWhiteSpace(entity.EntityKey) || entity.CargoBayCount <= 0) { continue; } var displayName = string.IsNullOrWhiteSpace(entity.DisplayName) ? $"Ship {entity.EntityIndex}" : entity.DisplayName; for (var bayIndex = 0; bayIndex < entity.CargoBayCount; bayIndex++) { targets.Add(new AetheriaRuntimeStationCargoTargetRow { TargetIndex = targetIndex++, Kind = AetheriaRuntimeTradeCargoTargetKind.ShipBay, Label = $"{displayName} Bay {bayIndex + 1}", EntityKey = entity.EntityKey, BayIndex = bayIndex, IsPlayerShip = true, HullItemKey = entity.HullItemKey ?? "", CargoItems = (entity.CargoItems ?? Array.Empty()) .Where(item => item.CargoBayIndex == bayIndex) .ToArray() }); } } return targets.ToArray(); } private static void AddSlot( List items, string source, int sourceIndex, AetheriaRuntimeLoadoutItemSlotCommit slot) { var item = slot.Item ?? new AetheriaRuntimeLoadoutItemCommit(); items.Add(new AetheriaRuntimeInventoryItem { Source = source, ItemKey = item.ItemKey ?? "", Quantity = item.Quantity, Quality = item.Quality, Durability = item.Durability, Enabled = item.Enabled, SourceIndex = sourceIndex, X = slot.X, Y = slot.Y, IconAsset = AetheriaRuntimeAssetRef.FromKey( $"item.{item.ItemKey ?? ""}.icon", AetheriaRuntimeAssetKinds.Texture, $"item.{item.ItemKey ?? ""}.icon") }); } private static bool CanSee( AetheriaRuntimeEntitySnapshotCommit observer, AetheriaRuntimeEntitySnapshotCommit target) { if (observer.EntityIndex == target.EntityIndex) return true; var dx = observer.PositionX - target.PositionX; var dy = observer.PositionZ - target.PositionZ; var range = Math.Max(180, observer.Visibility); return dx * dx + dy * dy <= range * range; } private static bool IsPlayerControlled(AetheriaRuntimeEntitySnapshotCommit entity) { return string.Equals(entity.FactionKey, "player", StringComparison.OrdinalIgnoreCase); } private static bool IsBodyKind(AetheriaRuntimeBodySnapshotCommit body, string kind) { return body != null && string.Equals(body.Kind ?? "", kind ?? "", StringComparison.OrdinalIgnoreCase); } private sealed class RenderSplatBuilder { private readonly List _centerX = new List(); private readonly List _centerY = new List(); private readonly List _halfExtentX = new List(); private readonly List _halfExtentY = new List(); private readonly List _rotationCos = new List(); private readonly List _rotationSin = new List(); private readonly List _channel = new List(); private readonly List _falloff = new List(); private readonly List _valueR = new List(); private readonly List _valueG = new List(); private readonly List _valueB = new List(); private readonly List _valueA = new List(); private readonly List _sourceKey = new List(); private readonly List _layerIndex = new List(); private readonly List _sourceKind = new List(); private readonly List _frequencyX = new List(); private readonly List _frequencyY = new List(); private readonly List _phaseX = new List(); private readonly List _phaseY = new List(); private readonly List _animationSpeed = new List(); private readonly List _sourceFlags = new List(); public void Add( int layerIndex, double centerX, double centerY, double halfExtentX, double halfExtentY, int channel, int falloff, double valueR, double valueG, double valueB, double valueA, string sourceKey, double rotationRadians = 0, int sourceKind = AetheriaRuntimeRenderSplatSourceKinds.Constant, double frequencyX = 1, double frequencyY = 1, double phaseX = 0, double phaseY = 0, double animationSpeed = 0, double sourceFlags = 0) { _layerIndex.Add(Math.Max(0, layerIndex)); _centerX.Add(centerX); _centerY.Add(centerY); _halfExtentX.Add(Math.Max(0, halfExtentX)); _halfExtentY.Add(Math.Max(0, halfExtentY)); _rotationCos.Add(Math.Cos(rotationRadians)); _rotationSin.Add(Math.Sin(rotationRadians)); _channel.Add(channel); _falloff.Add(falloff); _valueR.Add(valueR); _valueG.Add(valueG); _valueB.Add(valueB); _valueA.Add(valueA); _sourceKey.Add(sourceKey ?? ""); _sourceKind.Add(sourceKind); _frequencyX.Add(frequencyX); _frequencyY.Add(frequencyY); _phaseX.Add(phaseX); _phaseY.Add(phaseY); _animationSpeed.Add(animationSpeed); _sourceFlags.Add(sourceFlags); } public AetheriaRuntimeRenderSplatSoa Build() { return new AetheriaRuntimeRenderSplatSoa { Count = _centerX.Count, CenterX = _centerX.ToArray(), CenterY = _centerY.ToArray(), HalfExtentX = _halfExtentX.ToArray(), HalfExtentY = _halfExtentY.ToArray(), RotationCos = _rotationCos.ToArray(), RotationSin = _rotationSin.ToArray(), Channel = _channel.ToArray(), Falloff = _falloff.ToArray(), ValueR = _valueR.ToArray(), ValueG = _valueG.ToArray(), ValueB = _valueB.ToArray(), ValueA = _valueA.ToArray(), SourceKey = _sourceKey.ToArray(), LayerIndex = _layerIndex.ToArray(), SourceKind = _sourceKind.ToArray(), FrequencyX = _frequencyX.ToArray(), FrequencyY = _frequencyY.ToArray(), PhaseX = _phaseX.ToArray(), PhaseY = _phaseY.ToArray(), AnimationSpeed = _animationSpeed.ToArray(), SourceFlags = _sourceFlags.ToArray() }; } } private static DocumentContext Context(AetheriaRuntimeDaemonFrameDocument frame) { var run = frame.Run ?? new AetheriaRuntimeRunCheckpointCommit(); var zones = run.Zones ?? Array.Empty(); var zone = zones.FirstOrDefault(candidate => candidate.ZoneIndex == run.CurrentZoneIndex) ?? zones.FirstOrDefault() ?? new AetheriaRuntimeZoneSnapshotCommit(); var runId = string.IsNullOrWhiteSpace(run.RunId) ? "local-starbridge" : run.RunId; return new DocumentContext(run, zone, runId); } private static double Stat(AetheriaRuntimeEntitySnapshotCommit entity, string name) { var grid = (entity.StatGrids ?? Array.Empty()) .FirstOrDefault(candidate => string.Equals(candidate.Name, name, StringComparison.OrdinalIgnoreCase)); return grid?.Values?.FirstOrDefault() ?? 0; } private static AetheriaRuntimeCurrentEntityHudStatus CurrentEntityHudStatus( AetheriaRuntimeEntitySnapshotCommit? entity) { if (entity == null) return new AetheriaRuntimeCurrentEntityHudStatus(); var states = entity.BehaviorStates ?? Array.Empty(); var radiatorTemperatures = states .Where(state => string.Equals(state?.BehaviorKind, "Radiator", StringComparison.OrdinalIgnoreCase)) .Select(state => state.RadiatorTemperature) .Where(temperature => temperature > 0) .ToArray(); var capacitorStates = states .Where(state => state != null && state.CapacitorCapacity > 0) .ToArray(); var driveState = states.FirstOrDefault(state => state != null && state.AetherDriveMaximumRpm > 0); return new AetheriaRuntimeCurrentEntityHudStatus { OverrideShutdown = entity.OverrideShutdown, ShieldActive = Stat(entity, "shield") > 0, HeatsinksEnabled = entity.HeatsinksEnabled, Heatstroke = entity.Heatstroke, Hypothermia = entity.Hypothermia, Visibility = entity.Visibility, HullDurabilityRatio = Math.Clamp(Stat(entity, "hull"), 0, 1), RadiatorTemperatureMinimum = radiatorTemperatures.Length == 0 ? 0 : radiatorTemperatures.Min(), RadiatorTemperatureMaximum = radiatorTemperatures.Length == 0 ? 0 : radiatorTemperatures.Max(), RadiatorCount = radiatorTemperatures.Length, SensorCooldown = states .Where(state => string.Equals(state?.BehaviorKind, "Sensor", StringComparison.OrdinalIgnoreCase)) .Select(state => state.PingCooldown) .DefaultIfEmpty(0) .Max(), ReactorDraw = states.Sum(state => state?.ReactorDraw ?? 0), CapacitorCharge = capacitorStates.Sum(state => state.CapacitorCharge), CapacitorCapacity = capacitorStates.Sum(state => state.CapacitorCapacity), AetherDriveRpmX = driveState?.AetherDriveRpmX ?? 0, AetherDriveRpmY = driveState?.AetherDriveRpmY ?? 0, AetherDriveRpmZ = driveState?.AetherDriveRpmZ ?? 0, AetherDriveMaximumRpm = driveState?.AetherDriveMaximumRpm ?? 0 }; } private static AetheriaRuntimeEntitySnapshotCommit? FindDockParent( AetheriaRuntimeZoneSnapshotCommit zone, int currentEntityIndex, out int dockingBayIndex) { dockingBayIndex = -1; if (currentEntityIndex < 0) return null; foreach (var candidate in zone.Entities ?? Array.Empty()) { var assignments = candidate?.DockingBayAssignments ?? Array.Empty(); for (var index = 0; index < assignments.Count; index++) { if (assignments[index] != currentEntityIndex) continue; dockingBayIndex = index; return candidate; } } return null; } private static int TryParseEntityIndex(string? entityKey) { if (string.IsNullOrWhiteSpace(entityKey)) return -1; const string marker = ".entity."; var markerIndex = entityKey.LastIndexOf(marker, StringComparison.Ordinal); if (markerIndex < 0) return -1; var start = markerIndex + marker.Length; var end = entityKey.IndexOf('.', start); var text = end < 0 ? entityKey.Substring(start) : entityKey.Substring(start, end - start); return int.TryParse(text, out var value) ? value : -1; } private readonly struct DocumentContext { public DocumentContext( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeZoneSnapshotCommit zone, string runId) { Run = run; Zone = zone; RunId = runId ?? ""; } public AetheriaRuntimeRunCheckpointCommit Run { get; } public AetheriaRuntimeZoneSnapshotCommit Zone { get; } public string RunId { get; } } } }