diff --git a/apps/backend/Contracts/WorldContracts.Celestial.cs b/apps/backend/Contracts/WorldContracts.Celestial.cs index 1eebf78..6ef8ba3 100644 --- a/apps/backend/Contracts/WorldContracts.Celestial.cs +++ b/apps/backend/Contracts/WorldContracts.Celestial.cs @@ -30,7 +30,7 @@ public sealed record ResourceNodeSnapshot( string Id, string SystemId, Vector3Dto LocalPosition, - string? AnchorNodeId, + string? CelestialId, string SourceKind, float OreRemaining, float MaxOre, @@ -40,48 +40,28 @@ public sealed record ResourceNodeDelta( string Id, string SystemId, Vector3Dto LocalPosition, - string? AnchorNodeId, + string? CelestialId, string SourceKind, float OreRemaining, float MaxOre, string ItemId); -public sealed record SpatialNodeSnapshot( +public sealed record CelestialSnapshot( string Id, string SystemId, string Kind, - Vector3Dto LocalPosition, - string BubbleId, + Vector3Dto OrbitalAnchor, + float LocalSpaceRadius, string? ParentNodeId, string? OccupyingStructureId, string? OrbitReferenceId); -public sealed record SpatialNodeDelta( +public sealed record CelestialDelta( string Id, string SystemId, string Kind, - Vector3Dto LocalPosition, - string BubbleId, + Vector3Dto OrbitalAnchor, + float LocalSpaceRadius, string? ParentNodeId, string? OccupyingStructureId, string? OrbitReferenceId); - -public sealed record LocalBubbleSnapshot( - string Id, - string NodeId, - string SystemId, - float Radius, - IReadOnlyList OccupantShipIds, - IReadOnlyList OccupantStationIds, - IReadOnlyList OccupantClaimIds, - IReadOnlyList OccupantConstructionSiteIds); - -public sealed record LocalBubbleDelta( - string Id, - string NodeId, - string SystemId, - float Radius, - IReadOnlyList OccupantShipIds, - IReadOnlyList OccupantStationIds, - IReadOnlyList OccupantClaimIds, - IReadOnlyList OccupantConstructionSiteIds); diff --git a/apps/backend/Contracts/WorldContracts.Infrastructure.cs b/apps/backend/Contracts/WorldContracts.Infrastructure.cs index 08f87bc..0caf12d 100644 --- a/apps/backend/Contracts/WorldContracts.Infrastructure.cs +++ b/apps/backend/Contracts/WorldContracts.Infrastructure.cs @@ -10,9 +10,7 @@ public sealed record StationSnapshot( string Category, string SystemId, Vector3Dto LocalPosition, - string? NodeId, - string? BubbleId, - string? AnchorNodeId, + string? CelestialId, string Color, int DockedShips, IReadOnlyList DockedShipIds, @@ -36,9 +34,7 @@ public sealed record StationDelta( string Category, string SystemId, Vector3Dto LocalPosition, - string? NodeId, - string? BubbleId, - string? AnchorNodeId, + string? CelestialId, string Color, int DockedShips, IReadOnlyList DockedShipIds, @@ -70,8 +66,7 @@ public sealed record ClaimSnapshot( string Id, string FactionId, string SystemId, - string NodeId, - string BubbleId, + string CelestialId, string State, float Health, DateTimeOffset PlacedAtUtc, @@ -81,8 +76,7 @@ public sealed record ClaimDelta( string Id, string FactionId, string SystemId, - string NodeId, - string BubbleId, + string CelestialId, string State, float Health, DateTimeOffset PlacedAtUtc, @@ -92,8 +86,7 @@ public sealed record ConstructionSiteSnapshot( string Id, string FactionId, string SystemId, - string NodeId, - string BubbleId, + string CelestialId, string TargetKind, string TargetDefinitionId, string? BlueprintId, @@ -111,8 +104,7 @@ public sealed record ConstructionSiteDelta( string Id, string FactionId, string SystemId, - string NodeId, - string BubbleId, + string CelestialId, string TargetKind, string TargetDefinitionId, string? BlueprintId, diff --git a/apps/backend/Contracts/WorldContracts.Ships.cs b/apps/backend/Contracts/WorldContracts.Ships.cs index e0ef934..9429435 100644 --- a/apps/backend/Contracts/WorldContracts.Ships.cs +++ b/apps/backend/Contracts/WorldContracts.Ships.cs @@ -15,8 +15,7 @@ public sealed record ShipSnapshot( string? BehaviorPhase, string ControllerTaskKind, string? CommanderObjective, - string? NodeId, - string? BubbleId, + string? CelestialId, string? DockedStationId, string? CommanderId, string? PolicySetId, @@ -46,8 +45,7 @@ public sealed record ShipDelta( string? BehaviorPhase, string ControllerTaskKind, string? CommanderObjective, - string? NodeId, - string? BubbleId, + string? CelestialId, string? DockedStationId, string? CommanderId, string? PolicySetId, @@ -69,8 +67,7 @@ public sealed record ShipActionProgressSnapshot( public sealed record ShipSpatialStateSnapshot( string SpaceLayer, string CurrentSystemId, - string? CurrentNodeId, - string? CurrentBubbleId, + string? CurrentCelestialId, Vector3Dto? LocalPosition, Vector3Dto? SystemPosition, string MovementRegime, diff --git a/apps/backend/Contracts/WorldContracts.World.cs b/apps/backend/Contracts/WorldContracts.World.cs index 7150d3f..414681b 100644 --- a/apps/backend/Contracts/WorldContracts.World.cs +++ b/apps/backend/Contracts/WorldContracts.World.cs @@ -9,8 +9,7 @@ public sealed record WorldSnapshot( OrbitalSimulationSnapshot OrbitalSimulation, DateTimeOffset GeneratedAtUtc, IReadOnlyList Systems, - IReadOnlyList SpatialNodes, - IReadOnlyList LocalBubbles, + IReadOnlyList Celestials, IReadOnlyList Nodes, IReadOnlyList Stations, IReadOnlyList Claims, @@ -28,8 +27,7 @@ public sealed record WorldDelta( DateTimeOffset GeneratedAtUtc, bool RequiresSnapshotRefresh, IReadOnlyList Events, - IReadOnlyList SpatialNodes, - IReadOnlyList LocalBubbles, + IReadOnlyList Celestials, IReadOnlyList Nodes, IReadOnlyList Stations, IReadOnlyList Claims, @@ -54,7 +52,7 @@ public sealed record SimulationEventRecord( public sealed record ObserverScope( string ScopeKind, string? SystemId = null, - string? BubbleId = null); + string? CelestialId = null); public sealed record OrbitalSimulationSnapshot( double SimulatedSecondsPerRealSecond); diff --git a/apps/backend/Simulation/Model/ConstructionRuntimeModels.cs b/apps/backend/Simulation/Model/ConstructionRuntimeModels.cs index 4251286..d177989 100644 --- a/apps/backend/Simulation/Model/ConstructionRuntimeModels.cs +++ b/apps/backend/Simulation/Model/ConstructionRuntimeModels.cs @@ -5,8 +5,7 @@ public sealed class ClaimRuntime public required string Id { get; init; } public required string FactionId { get; init; } public required string SystemId { get; init; } - public required string NodeId { get; init; } - public required string BubbleId { get; init; } + public required string CelestialId { get; init; } public string? CommanderId { get; set; } public DateTimeOffset PlacedAtUtc { get; init; } public DateTimeOffset ActivatesAtUtc { get; set; } @@ -20,8 +19,7 @@ public sealed class ConstructionSiteRuntime public required string Id { get; init; } public required string FactionId { get; init; } public required string SystemId { get; init; } - public required string NodeId { get; init; } - public required string BubbleId { get; init; } + public required string CelestialId { get; init; } public required string TargetKind { get; init; } public required string TargetDefinitionId { get; init; } public string? BlueprintId { get; set; } diff --git a/apps/backend/Simulation/Model/SimulationKinds.cs b/apps/backend/Simulation/Model/SimulationKinds.cs index 9553307..5caf7c2 100644 --- a/apps/backend/Simulation/Model/SimulationKinds.cs +++ b/apps/backend/Simulation/Model/SimulationKinds.cs @@ -6,8 +6,6 @@ public enum SpatialNodeKind Planet, Moon, LagrangePoint, - Station, - ResourceSite, } public enum WorkStatus @@ -112,7 +110,7 @@ public static class ShipTaskKinds public const string BuildConstructionSite = "build-construction-site"; public const string EscortTarget = "escort-target"; public const string AttackTarget = "attack-target"; - public const string DefendBubble = "defend-bubble"; + public const string DefendCelestial = "defend-celestial"; public const string Retreat = "retreat"; public const string HoldPosition = "hold-position"; } @@ -167,8 +165,6 @@ public static class SimulationEnumMappings SpatialNodeKind.Planet => "planet", SpatialNodeKind.Moon => "moon", SpatialNodeKind.LagrangePoint => "lagrange-point", - SpatialNodeKind.Station => "station", - SpatialNodeKind.ResourceSite => "resource-site", _ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null), }; diff --git a/apps/backend/Simulation/Model/SimulationWorld.cs b/apps/backend/Simulation/Model/SimulationWorld.cs index 0b7ebf0..1ef7766 100644 --- a/apps/backend/Simulation/Model/SimulationWorld.cs +++ b/apps/backend/Simulation/Model/SimulationWorld.cs @@ -9,8 +9,7 @@ public sealed class SimulationWorld public required BalanceDefinition Balance { get; init; } public required List Systems { get; init; } public required List Nodes { get; init; } - public required List SpatialNodes { get; init; } - public required List LocalBubbles { get; init; } + public required List Celestials { get; init; } public required List Stations { get; init; } public required List Ships { get; init; } public required List Factions { get; init; } diff --git a/apps/backend/Simulation/Model/SpatialRuntimeModels.cs b/apps/backend/Simulation/Model/SpatialRuntimeModels.cs index 04fcabb..4f11bda 100644 --- a/apps/backend/Simulation/Model/SpatialRuntimeModels.cs +++ b/apps/backend/Simulation/Model/SpatialRuntimeModels.cs @@ -15,7 +15,7 @@ public sealed class ResourceNodeRuntime public required Vector3 Position { get; set; } public required string SourceKind { get; init; } public required string ItemId { get; init; } - public string? AnchorNodeId { get; set; } + public string? CelestialId { get; set; } public float OrbitRadius { get; init; } public float OrbitPhase { get; init; } public float OrbitInclination { get; init; } @@ -24,38 +24,24 @@ public sealed class ResourceNodeRuntime public string LastDeltaSignature { get; set; } = string.Empty; } -public sealed class NodeRuntime +public sealed class CelestialRuntime { public required string Id { get; init; } public required string SystemId { get; init; } public required SpatialNodeKind Kind { get; init; } public required Vector3 Position { get; set; } - public required string BubbleId { get; init; } + public float LocalSpaceRadius { get; init; } public string? ParentNodeId { get; set; } public string? OccupyingStructureId { get; set; } public string? OrbitReferenceId { get; set; } public string LastDeltaSignature { get; set; } = string.Empty; } -public sealed class LocalBubbleRuntime -{ - public required string Id { get; init; } - public required string NodeId { get; init; } - public required string SystemId { get; init; } - public float Radius { get; init; } - public HashSet OccupantShipIds { get; } = new(StringComparer.Ordinal); - public HashSet OccupantStationIds { get; } = new(StringComparer.Ordinal); - public HashSet OccupantClaimIds { get; } = new(StringComparer.Ordinal); - public HashSet OccupantConstructionSiteIds { get; } = new(StringComparer.Ordinal); - public string LastDeltaSignature { get; set; } = string.Empty; -} - public sealed class ShipSpatialStateRuntime { public string SpaceLayer { get; set; } = SpaceLayerKinds.LocalSpace; public required string CurrentSystemId { get; set; } - public string? CurrentNodeId { get; set; } - public string? CurrentBubbleId { get; set; } + public string? CurrentCelestialId { get; set; } public Vector3? LocalPosition { get; set; } public Vector3? SystemPosition { get; set; } public string MovementRegime { get; set; } = MovementRegimeKinds.LocalFlight; diff --git a/apps/backend/Simulation/Model/StationRuntimeModels.cs b/apps/backend/Simulation/Model/StationRuntimeModels.cs index 402f1c9..503ecfc 100644 --- a/apps/backend/Simulation/Model/StationRuntimeModels.cs +++ b/apps/backend/Simulation/Model/StationRuntimeModels.cs @@ -10,9 +10,7 @@ public sealed class StationRuntime public required Vector3 Position { get; set; } public float Radius { get; set; } = 24f; public required string FactionId { get; init; } - public string? NodeId { get; set; } - public string? BubbleId { get; set; } - public string? AnchorNodeId { get; set; } + public string? CelestialId { get; set; } public string? CommanderId { get; set; } public string? PolicySetId { get; set; } public List Modules { get; } = []; diff --git a/apps/backend/Simulation/ScenarioLoader.Seeding.cs b/apps/backend/Simulation/ScenarioLoader.Seeding.cs index 1a6c8cf..6b8a499 100644 --- a/apps/backend/Simulation/ScenarioLoader.Seeding.cs +++ b/apps/backend/Simulation/ScenarioLoader.Seeding.cs @@ -88,27 +88,26 @@ public sealed partial class ScenarioLoader private static List CreateClaims( IReadOnlyCollection stations, - IReadOnlyCollection nodes, + IReadOnlyCollection celestials, DateTimeOffset nowUtc) { - var stationsByAnchorNodeId = stations - .Where((station) => station.AnchorNodeId is not null) - .ToDictionary((station) => station.AnchorNodeId!, StringComparer.Ordinal); + var stationsByCelestialId = stations + .Where((station) => station.CelestialId is not null) + .ToDictionary((station) => station.CelestialId!, StringComparer.Ordinal); var claims = new List(); - foreach (var node in nodes.Where((candidate) => candidate.Kind == SpatialNodeKind.LagrangePoint)) + foreach (var celestial in celestials.Where((c) => c.Kind == SpatialNodeKind.LagrangePoint)) { - if (!stationsByAnchorNodeId.TryGetValue(node.Id, out var station)) + if (!stationsByCelestialId.TryGetValue(celestial.Id, out var station)) { continue; } claims.Add(new ClaimRuntime { - Id = $"claim-{node.Id}", + Id = $"claim-{celestial.Id}", FactionId = station.FactionId, - SystemId = node.SystemId, - NodeId = node.Id, - BubbleId = node.BubbleId, + SystemId = celestial.SystemId, + CelestialId = celestial.Id, PlacedAtUtc = nowUtc, ActivatesAtUtc = nowUtc.AddSeconds(8), State = ClaimStateKinds.Activating, @@ -122,7 +121,6 @@ public sealed partial class ScenarioLoader private static (List ConstructionSites, List MarketOrders) CreateConstructionSites( IReadOnlyCollection stations, IReadOnlyCollection claims, - IReadOnlyCollection nodes, IReadOnlyDictionary moduleRecipes) { var sites = new List(); @@ -131,18 +129,12 @@ public sealed partial class ScenarioLoader foreach (var station in stations) { var moduleId = GetNextConstructionSiteModule(station, moduleRecipes); - if (moduleId is null || station.AnchorNodeId is null) + if (moduleId is null || station.CelestialId is null) { continue; } - var anchorNode = nodes.FirstOrDefault((node) => node.Id == station.AnchorNodeId); - if (anchorNode is null) - { - continue; - } - - var claim = claims.FirstOrDefault((candidate) => candidate.NodeId == anchorNode.Id); + var claim = claims.FirstOrDefault((candidate) => candidate.CelestialId == station.CelestialId); if (claim is null || !moduleRecipes.TryGetValue(moduleId, out var recipe)) { continue; @@ -153,8 +145,7 @@ public sealed partial class ScenarioLoader Id = $"site-{station.Id}", FactionId = station.FactionId, SystemId = station.SystemId, - NodeId = anchorNode.Id, - BubbleId = anchorNode.BubbleId, + CelestialId = station.CelestialId, TargetKind = "station-module", TargetDefinitionId = "station", BlueprintId = moduleId, diff --git a/apps/backend/Simulation/ScenarioLoader.Spatial.cs b/apps/backend/Simulation/ScenarioLoader.Spatial.cs index 995f9b0..6641948 100644 --- a/apps/backend/Simulation/ScenarioLoader.Spatial.cs +++ b/apps/backend/Simulation/ScenarioLoader.Spatial.cs @@ -6,48 +6,44 @@ public sealed partial class ScenarioLoader { private static SystemSpatialGraph BuildSystemSpatialGraph(SystemRuntime system) { - var nodes = new List(); - var bubbles = new List(); - var lagrangeNodesByPlanetIndex = new Dictionary>(); + var celestials = new List(); + var lagrangeNodesByPlanetIndex = new Dictionary>(); - var starNode = AddSpatialNode( - nodes, - bubbles, + AddCelestial( + celestials, id: $"node-{system.Definition.Id}-star", systemId: system.Definition.Id, kind: SpatialNodeKind.Star, position: Vector3.Zero, - radius: MathF.Max(system.Definition.GravityWellRadius + StarBubbleRadiusPadding, 180f)); + localSpaceRadius: MathF.Max(system.Definition.GravityWellRadius + StarBubbleRadiusPadding, LocalSpaceRadius)); for (var planetIndex = 0; planetIndex < system.Definition.Planets.Count; planetIndex += 1) { var planet = system.Definition.Planets[planetIndex]; var planetNodeId = $"node-{system.Definition.Id}-planet-{planetIndex + 1}"; var planetPosition = ComputePlanetPosition(planet); - var planetNode = AddSpatialNode( - nodes, - bubbles, + var planetCelestial = AddCelestial( + celestials, id: planetNodeId, systemId: system.Definition.Id, kind: SpatialNodeKind.Planet, position: planetPosition, - radius: MathF.Max(planet.Size + PlanetBubbleRadiusPadding, 120f), - parentNodeId: starNode.Id); + localSpaceRadius: LocalSpaceRadius, + parentNodeId: $"node-{system.Definition.Id}-star"); - var lagrangeNodes = new Dictionary(StringComparer.Ordinal); + var lagrangeNodes = new Dictionary(StringComparer.Ordinal); foreach (var point in EnumeratePlanetLagrangePoints(planetPosition, planet)) { - var lagrangeNode = AddSpatialNode( - nodes, - bubbles, + var lagrangeCelestial = AddCelestial( + celestials, id: $"node-{system.Definition.Id}-planet-{planetIndex + 1}-{point.Designation.ToLowerInvariant()}", systemId: system.Definition.Id, kind: SpatialNodeKind.LagrangePoint, position: point.Position, - radius: LagrangeBubbleRadius, - parentNodeId: planetNode.Id, + localSpaceRadius: LocalSpaceRadius, + parentNodeId: planetCelestial.Id, orbitReferenceId: point.Designation); - lagrangeNodes[point.Designation] = lagrangeNode; + lagrangeNodes[point.Designation] = lagrangeCelestial; } lagrangeNodesByPlanetIndex[planetIndex] = lagrangeNodes; @@ -61,54 +57,44 @@ public sealed partial class ScenarioLoader for (var moonIndex = 0; moonIndex < planet.MoonCount; moonIndex += 1) { var moonPosition = ComputeMoonPosition(planetPosition, moonOrbitRadius, moonIndex, planetIndex); - AddSpatialNode( - nodes, - bubbles, + AddCelestial( + celestials, id: $"node-{system.Definition.Id}-planet-{planetIndex + 1}-moon-{moonIndex + 1}", systemId: system.Definition.Id, kind: SpatialNodeKind.Moon, position: moonPosition, - radius: MoonBubbleRadiusPadding + 24f, - parentNodeId: planetNode.Id); + localSpaceRadius: LocalSpaceRadius, + parentNodeId: planetCelestial.Id); moonOrbitRadius += 30f; } } - return new SystemSpatialGraph(system.Definition.Id, nodes, bubbles, lagrangeNodesByPlanetIndex); + return new SystemSpatialGraph(system.Definition.Id, celestials, lagrangeNodesByPlanetIndex); } - private static NodeRuntime AddSpatialNode( - ICollection nodes, - ICollection bubbles, + private static CelestialRuntime AddCelestial( + ICollection celestials, string id, string systemId, SpatialNodeKind kind, Vector3 position, - float radius, + float localSpaceRadius, string? parentNodeId = null, string? orbitReferenceId = null) { - var bubbleId = $"bubble-{id}"; - var node = new NodeRuntime + var celestial = new CelestialRuntime { Id = id, SystemId = systemId, Kind = kind, Position = position, - BubbleId = bubbleId, + LocalSpaceRadius = localSpaceRadius, ParentNodeId = parentNodeId, OrbitReferenceId = orbitReferenceId, }; - nodes.Add(node); - bubbles.Add(new LocalBubbleRuntime - { - Id = bubbleId, - NodeId = id, - SystemId = systemId, - Radius = radius, - }); - return node; + celestials.Add(celestial); + return celestial; } private static IEnumerable EnumeratePlanetLagrangePoints( @@ -165,36 +151,36 @@ public sealed partial class ScenarioLoader InitialStationDefinition plan, SystemRuntime system, SystemSpatialGraph graph, - IReadOnlyCollection existingNodes) + IReadOnlyCollection existingCelestials) { if (plan.PlanetIndex is int planetIndex && graph.LagrangeNodesByPlanetIndex.TryGetValue(planetIndex, out var lagrangeNodes)) { var designation = ResolveLagrangeDesignation(plan.LagrangeSide); - if (lagrangeNodes.TryGetValue(designation, out var lagrangeNode)) + if (lagrangeNodes.TryGetValue(designation, out var lagrangeCelestial)) { - return new StationPlacement(lagrangeNode, lagrangeNode.Position); + return new StationPlacement(lagrangeCelestial, lagrangeCelestial.Position); } } if (plan.Position is { Length: 3 }) { var targetPosition = NormalizeScenarioPoint(system, plan.Position); - var preferredNode = existingNodes - .Where((node) => node.SystemId == system.Definition.Id && node.Kind == SpatialNodeKind.LagrangePoint) - .OrderBy((node) => node.Position.DistanceTo(targetPosition)) + var preferredCelestial = existingCelestials + .Where((c) => c.SystemId == system.Definition.Id && c.Kind == SpatialNodeKind.LagrangePoint) + .OrderBy((c) => c.Position.DistanceTo(targetPosition)) .FirstOrDefault() - ?? existingNodes - .Where((node) => node.SystemId == system.Definition.Id) - .OrderBy((node) => node.Position.DistanceTo(targetPosition)) + ?? existingCelestials + .Where((c) => c.SystemId == system.Definition.Id) + .OrderBy((c) => c.Position.DistanceTo(targetPosition)) .First(); - return new StationPlacement(preferredNode, preferredNode.Position); + return new StationPlacement(preferredCelestial, preferredCelestial.Position); } - var fallbackNode = graph.Nodes - .FirstOrDefault((node) => node.Kind == SpatialNodeKind.LagrangePoint && string.IsNullOrEmpty(node.OccupyingStructureId)) - ?? graph.Nodes.First((node) => node.Kind == SpatialNodeKind.Planet); - return new StationPlacement(fallbackNode, fallbackNode.Position); + var fallbackCelestial = graph.Celestials + .FirstOrDefault((c) => c.Kind == SpatialNodeKind.LagrangePoint && string.IsNullOrEmpty(c.OccupyingStructureId)) + ?? graph.Celestials.First((c) => c.Kind == SpatialNodeKind.Planet); + return new StationPlacement(fallbackCelestial, fallbackCelestial.Position); } private static string ResolveLagrangeDesignation(int? lagrangeSide) => lagrangeSide switch @@ -204,7 +190,7 @@ public sealed partial class ScenarioLoader _ => "L1", }; - private static NodeRuntime? ResolveResourceNodeAnchor(SystemSpatialGraph graph, ResourceNodeDefinition definition) + private static CelestialRuntime? ResolveResourceNodeAnchor(SystemSpatialGraph graph, ResourceNodeDefinition definition) { if (definition.AnchorPlanetIndex is not int planetIndex || planetIndex < 0) { @@ -214,14 +200,14 @@ public sealed partial class ScenarioLoader if (definition.AnchorMoonIndex is int moonIndex && moonIndex >= 0) { var moonNodeId = $"node-{graph.SystemId}-planet-{planetIndex + 1}-moon-{moonIndex + 1}"; - return graph.Nodes.FirstOrDefault((node) => node.Id == moonNodeId); + return graph.Celestials.FirstOrDefault((c) => c.Id == moonNodeId); } var planetNodeId = $"node-{graph.SystemId}-planet-{planetIndex + 1}"; - return graph.Nodes.FirstOrDefault((node) => node.Id == planetNodeId); + return graph.Celestials.FirstOrDefault((c) => c.Id == planetNodeId); } - private static Vector3 ComputeResourceNodePosition(NodeRuntime? anchorNode, ResourceNodeDefinition definition, float yPlane) + private static Vector3 ComputeResourceNodePosition(CelestialRuntime? anchorCelestial, ResourceNodeDefinition definition, float yPlane) { var verticalOffset = MathF.Sin(DegreesToRadians(definition.InclinationDegrees)) * MathF.Min(definition.RadiusOffset * 0.04f, 25000f); var offset = new Vector3( @@ -229,12 +215,12 @@ public sealed partial class ScenarioLoader verticalOffset, MathF.Sin(definition.Angle) * definition.RadiusOffset); - if (anchorNode is null) + if (anchorCelestial is null) { return new Vector3(offset.X, yPlane + offset.Y, offset.Z); } - return Add(anchorNode.Position, offset); + return Add(anchorCelestial.Position, offset); } private static Vector3 ComputePlanetPosition(PlanetDefinition planet) @@ -252,19 +238,18 @@ public sealed partial class ScenarioLoader return Add(planetPosition, new Vector3(MathF.Cos(angle) * orbitRadius, 0f, MathF.Sin(angle) * orbitRadius)); } - private static ShipSpatialStateRuntime CreateInitialShipSpatialState(string systemId, Vector3 position, IReadOnlyCollection nodes) + private static ShipSpatialStateRuntime CreateInitialShipSpatialState(string systemId, Vector3 position, IReadOnlyCollection celestials) { - var nearestNode = nodes - .Where((node) => node.SystemId == systemId) - .OrderBy((node) => node.Position.DistanceTo(position)) + var nearestCelestial = celestials + .Where((c) => c.SystemId == systemId) + .OrderBy((c) => c.Position.DistanceTo(position)) .FirstOrDefault(); return new ShipSpatialStateRuntime { CurrentSystemId = systemId, SpaceLayer = SpaceLayerKinds.LocalSpace, - CurrentNodeId = nearestNode?.Id, - CurrentBubbleId = nearestNode?.BubbleId, + CurrentCelestialId = nearestCelestial?.Id, LocalPosition = position, SystemPosition = position, MovementRegime = MovementRegimeKinds.LocalFlight, @@ -273,11 +258,10 @@ public sealed partial class ScenarioLoader private sealed record SystemSpatialGraph( string SystemId, - List Nodes, - List Bubbles, - Dictionary> LagrangeNodesByPlanetIndex); + List Celestials, + Dictionary> LagrangeNodesByPlanetIndex); private sealed record LagrangePointPlacement(string Designation, Vector3 Position); - private sealed record StationPlacement(NodeRuntime AnchorNode, Vector3 Position); + private sealed record StationPlacement(CelestialRuntime AnchorCelestial, Vector3 Position); } diff --git a/apps/backend/Simulation/ScenarioLoader.cs b/apps/backend/Simulation/ScenarioLoader.cs index 9f1cdae..ab3b86f 100644 --- a/apps/backend/Simulation/ScenarioLoader.cs +++ b/apps/backend/Simulation/ScenarioLoader.cs @@ -13,10 +13,7 @@ public sealed partial class ScenarioLoader private const float MinimumShipyardStock = 0f; private const float MinimumSystemSeparation = 3.2f; private const float StarBubbleRadiusPadding = 40f; - private const float PlanetBubbleRadiusPadding = 80f; - private const float MoonBubbleRadiusPadding = 40f; - private const float LagrangeBubbleRadius = 150f; - private const float ResourceBubbleRadius = 120f; + private const float LocalSpaceRadius = 10_000f; private static readonly string[] GeneratedSystemNames = [ "Aquila Verge", @@ -121,14 +118,12 @@ public sealed partial class ScenarioLoader (system) => BuildSystemSpatialGraph(system), StringComparer.Ordinal); + var celestials = new List(); var nodes = new List(); - var spatialNodes = new List(); - var localBubbles = new List(); var nodeIdCounter = 0; foreach (var graph in systemGraphs.Values) { - spatialNodes.AddRange(graph.Nodes); - localBubbles.AddRange(graph.Bubbles); + celestials.AddRange(graph.Celestials); } foreach (var system in systemRuntimes) @@ -136,15 +131,15 @@ public sealed partial class ScenarioLoader var systemGraph = systemGraphs[system.Definition.Id]; foreach (var node in system.Definition.ResourceNodes) { - var anchorNode = ResolveResourceNodeAnchor(systemGraph, node); + var anchorCelestial = ResolveResourceNodeAnchor(systemGraph, node); var resourceNode = new ResourceNodeRuntime { Id = $"node-{++nodeIdCounter}", SystemId = system.Definition.Id, - Position = ComputeResourceNodePosition(anchorNode, node, balance.YPlane), + Position = ComputeResourceNodePosition(anchorCelestial, node, balance.YPlane), SourceKind = node.SourceKind, ItemId = node.ItemId, - AnchorNodeId = anchorNode?.Id, + CelestialId = anchorCelestial?.Id, OrbitRadius = node.RadiusOffset, OrbitPhase = node.Angle, OrbitInclination = DegreesToRadians(node.InclinationDegrees), @@ -153,23 +148,6 @@ public sealed partial class ScenarioLoader }; nodes.Add(resourceNode); - var bubbleId = $"bubble-{resourceNode.Id}"; - spatialNodes.Add(new NodeRuntime - { - Id = resourceNode.Id, - SystemId = resourceNode.SystemId, - Kind = SpatialNodeKind.ResourceSite, - Position = resourceNode.Position, - BubbleId = bubbleId, - ParentNodeId = anchorNode?.Id, - }); - localBubbles.Add(new LocalBubbleRuntime - { - Id = bubbleId, - NodeId = resourceNode.Id, - SystemId = resourceNode.SystemId, - Radius = ResourceBubbleRadius, - }); } } @@ -182,7 +160,7 @@ public sealed partial class ScenarioLoader continue; } - var placement = ResolveStationPlacement(plan, system, systemGraphs[system.Definition.Id], spatialNodes); + var placement = ResolveStationPlacement(plan, system, systemGraphs[system.Definition.Id], celestials); var station = new StationRuntime { Id = $"station-{++stationIdCounter}", @@ -193,31 +171,9 @@ public sealed partial class ScenarioLoader FactionId = plan.FactionId ?? DefaultFactionId, }; - var stationNodeId = $"node-{station.Id}"; - var stationBubbleId = $"bubble-{station.Id}"; - station.NodeId = stationNodeId; - station.BubbleId = stationBubbleId; - station.AnchorNodeId = placement.AnchorNode.Id; + station.CelestialId = placement.AnchorCelestial.Id; stations.Add(station); - spatialNodes.Add(new NodeRuntime - { - Id = stationNodeId, - SystemId = station.SystemId, - Kind = SpatialNodeKind.Station, - Position = station.Position, - BubbleId = stationBubbleId, - ParentNodeId = placement.AnchorNode.Id, - OccupyingStructureId = station.Id, - }); - localBubbles.Add(new LocalBubbleRuntime - { - Id = stationBubbleId, - NodeId = stationNodeId, - SystemId = station.SystemId, - Radius = MathF.Max(160f, GetStationRadius(moduleDefinitions, station) + 60f), - }); - localBubbles[^1].OccupantStationIds.Add(station.Id); - placement.AnchorNode.OccupyingStructureId = station.Id; + placement.AnchorCelestial.OccupyingStructureId = station.Id; var startingModules = plan.StartingModules.Count > 0 ? plan.StartingModules @@ -274,7 +230,7 @@ public sealed partial class ScenarioLoader FactionId = formation.FactionId ?? DefaultFactionId, Position = position, TargetPosition = position, - SpatialState = CreateInitialShipSpatialState(formation.SystemId, position, spatialNodes), + SpatialState = CreateInitialShipSpatialState(formation.SystemId, position, celestials), DefaultBehavior = CreateBehavior(definition, formation.SystemId, scenario, patrolRoutes, refinery), ControllerTask = new ControllerTaskRuntime { Kind = ControllerTaskKind.Idle, Threshold = balance.ArrivalThreshold, Status = WorkStatus.Pending }, Health = definition.MaxHealth, @@ -287,8 +243,8 @@ public sealed partial class ScenarioLoader var policies = CreatePolicies(factions); var commanders = CreateCommanders(factions, stations, shipsRuntime); var now = DateTimeOffset.UtcNow; - var claims = CreateClaims(stations, spatialNodes, now); - var (constructionSites, marketOrders) = CreateConstructionSites(stations, claims, spatialNodes, moduleRecipeDefinitions); + var claims = CreateClaims(stations, celestials, now); + var (constructionSites, marketOrders) = CreateConstructionSites(stations, claims, moduleRecipeDefinitions); return new SimulationWorld { @@ -296,9 +252,8 @@ public sealed partial class ScenarioLoader Seed = WorldSeed, Balance = balance, Systems = systemRuntimes, + Celestials = celestials, Nodes = nodes, - SpatialNodes = spatialNodes, - LocalBubbles = localBubbles, Stations = stations, Ships = shipsRuntime, Factions = factions, diff --git a/apps/backend/Simulation/SimulationEngine.MovementSystem.cs b/apps/backend/Simulation/SimulationEngine.MovementSystem.cs index bb0125a..f92afab 100644 --- a/apps/backend/Simulation/SimulationEngine.MovementSystem.cs +++ b/apps/backend/Simulation/SimulationEngine.MovementSystem.cs @@ -51,8 +51,9 @@ public sealed partial class SimulationEngine return "none"; } - var targetPosition = task.TargetPosition.Value; - var targetNode = ResolveTravelTargetNode(world, task, targetPosition); + // Resolve live position each frame — entities like stations orbit celestials and move every tick + var targetPosition = ResolveCurrentTargetPosition(world, task); + var targetCelestial = ResolveTravelTargetCelestial(world, task, targetPosition); ship.TargetPosition = targetPosition; if (ship.SystemId != task.TargetSystemId) @@ -63,63 +64,83 @@ public sealed partial class SimulationEngine return "none"; } - var destinationEntryNode = ResolveSystemEntryNode(world, task.TargetSystemId); - var destinationEntryPosition = destinationEntryNode?.Position ?? Vector3.Zero; - return UpdateFtlTransit(ship, world, deltaSeconds, task.TargetSystemId, destinationEntryPosition, destinationEntryNode); + var destinationEntryCelestial = ResolveSystemEntryCelestial(world, task.TargetSystemId); + var destinationEntryPosition = destinationEntryCelestial?.Position ?? Vector3.Zero; + return UpdateFtlTransit(ship, world, deltaSeconds, task.TargetSystemId, destinationEntryPosition, destinationEntryCelestial); } - var currentNode = ResolveCurrentNode(world, ship); - if (targetNode is not null && currentNode is not null && !string.Equals(currentNode.Id, targetNode.Id, StringComparison.Ordinal)) + var currentCelestial = ResolveCurrentCelestial(world, ship); + if (targetCelestial is not null && currentCelestial is not null && !string.Equals(currentCelestial.Id, targetCelestial.Id, StringComparison.Ordinal)) { if (!HasShipCapabilities(ship.Definition, "warp")) { - return UpdateLocalTravel(ship, world, deltaSeconds, task.TargetSystemId, targetPosition, targetNode, task.Threshold); + return UpdateLocalTravel(ship, world, deltaSeconds, task.TargetSystemId, targetPosition, targetCelestial, task.Threshold); } - return UpdateWarpTransit(ship, world, deltaSeconds, targetPosition, targetNode); + return UpdateWarpTransit(ship, world, deltaSeconds, targetPosition, targetCelestial); } - return UpdateLocalTravel(ship, world, deltaSeconds, task.TargetSystemId, targetPosition, targetNode, task.Threshold); + return UpdateLocalTravel(ship, world, deltaSeconds, task.TargetSystemId, targetPosition, targetCelestial, task.Threshold); } - private static NodeRuntime? ResolveTravelTargetNode(SimulationWorld world, ControllerTaskRuntime task, Vector3 targetPosition) + private static Vector3 ResolveCurrentTargetPosition(SimulationWorld world, ControllerTaskRuntime task) { if (!string.IsNullOrWhiteSpace(task.TargetEntityId)) { var station = world.Stations.FirstOrDefault(candidate => candidate.Id == task.TargetEntityId); - if (station?.NodeId is not null) + if (station is not null) { - return world.SpatialNodes.FirstOrDefault(candidate => candidate.Id == station.NodeId); + return station.Position; } - var node = world.SpatialNodes.FirstOrDefault(candidate => candidate.Id == task.TargetEntityId); - if (node is not null) + var celestial = world.Celestials.FirstOrDefault(candidate => candidate.Id == task.TargetEntityId); + if (celestial is not null) { - return node; + return celestial.Position; } } - return world.SpatialNodes + return task.TargetPosition!.Value; + } + + private static CelestialRuntime? ResolveTravelTargetCelestial(SimulationWorld world, ControllerTaskRuntime task, Vector3 targetPosition) + { + if (!string.IsNullOrWhiteSpace(task.TargetEntityId)) + { + var station = world.Stations.FirstOrDefault(candidate => candidate.Id == task.TargetEntityId); + if (station?.CelestialId is not null) + { + return world.Celestials.FirstOrDefault(candidate => candidate.Id == station.CelestialId); + } + + var celestial = world.Celestials.FirstOrDefault(candidate => candidate.Id == task.TargetEntityId); + if (celestial is not null) + { + return celestial; + } + } + + return world.Celestials .Where(candidate => task.TargetSystemId is null || candidate.SystemId == task.TargetSystemId) .OrderBy(candidate => candidate.Position.DistanceTo(targetPosition)) .FirstOrDefault(); } - private static NodeRuntime? ResolveCurrentNode(SimulationWorld world, ShipRuntime ship) + private static CelestialRuntime? ResolveCurrentCelestial(SimulationWorld world, ShipRuntime ship) { - if (ship.SpatialState.CurrentNodeId is not null) + if (ship.SpatialState.CurrentCelestialId is not null) { - return world.SpatialNodes.FirstOrDefault(candidate => candidate.Id == ship.SpatialState.CurrentNodeId); + return world.Celestials.FirstOrDefault(candidate => candidate.Id == ship.SpatialState.CurrentCelestialId); } - return world.SpatialNodes + return world.Celestials .Where(candidate => candidate.SystemId == ship.SystemId) .OrderBy(candidate => candidate.Position.DistanceTo(ship.Position)) .FirstOrDefault(); } - private static NodeRuntime? ResolveSystemEntryNode(SimulationWorld world, string systemId) => - world.SpatialNodes.FirstOrDefault(candidate => + private static CelestialRuntime? ResolveSystemEntryCelestial(SimulationWorld world, string systemId) => + world.Celestials.FirstOrDefault(candidate => candidate.SystemId == systemId && candidate.Kind == SpatialNodeKind.Star); @@ -129,14 +150,14 @@ public sealed partial class SimulationEngine float deltaSeconds, string targetSystemId, Vector3 targetPosition, - NodeRuntime? targetNode, + CelestialRuntime? targetCelestial, float threshold) { var distance = ship.Position.DistanceTo(targetPosition); ship.SpatialState.SpaceLayer = SpaceLayerKinds.LocalSpace; ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight; ship.SpatialState.Transit = null; - ship.SpatialState.DestinationNodeId = targetNode?.Id; + ship.SpatialState.DestinationNodeId = targetCelestial?.Id; if (distance <= threshold) { @@ -144,8 +165,7 @@ public sealed partial class SimulationEngine ship.Position = targetPosition; ship.TargetPosition = ship.Position; ship.SystemId = targetSystemId; - ship.SpatialState.CurrentNodeId = targetNode?.Id; - ship.SpatialState.CurrentBubbleId = targetNode?.BubbleId; + ship.SpatialState.CurrentCelestialId = targetCelestial?.Id; ship.State = ShipState.Arriving; return "arrived"; } @@ -156,16 +176,16 @@ public sealed partial class SimulationEngine return "none"; } - private string UpdateWarpTransit(ShipRuntime ship, SimulationWorld world, float deltaSeconds, Vector3 targetPosition, NodeRuntime targetNode) + private string UpdateWarpTransit(ShipRuntime ship, SimulationWorld world, float deltaSeconds, Vector3 targetPosition, CelestialRuntime targetCelestial) { var transit = ship.SpatialState.Transit; - if (transit is null || transit.Regime != MovementRegimeKinds.Warp || transit.DestinationNodeId != targetNode.Id) + if (transit is null || transit.Regime != MovementRegimeKinds.Warp || transit.DestinationNodeId != targetCelestial.Id) { transit = new ShipTransitRuntime { Regime = MovementRegimeKinds.Warp, - OriginNodeId = ship.SpatialState.CurrentNodeId, - DestinationNodeId = targetNode.Id, + OriginNodeId = ship.SpatialState.CurrentCelestialId, + DestinationNodeId = targetCelestial.Id, StartedAtUtc = world.GeneratedAtUtc, }; ship.SpatialState.Transit = transit; @@ -173,9 +193,8 @@ public sealed partial class SimulationEngine ship.SpatialState.SpaceLayer = SpaceLayerKinds.SystemSpace; ship.SpatialState.MovementRegime = MovementRegimeKinds.Warp; - ship.SpatialState.CurrentNodeId = null; - ship.SpatialState.CurrentBubbleId = null; - ship.SpatialState.DestinationNodeId = targetNode.Id; + ship.SpatialState.CurrentCelestialId = null; + ship.SpatialState.DestinationNodeId = targetCelestial.Id; var spoolDuration = MathF.Max(0.4f, ship.Definition.SpoolTime * 0.5f); if (ship.State != ShipState.Warping) @@ -196,24 +215,24 @@ public sealed partial class SimulationEngine var totalDistance = MathF.Max(0.001f, transit.OriginNodeId is null ? ship.Position.DistanceTo(targetPosition) - : (world.SpatialNodes.FirstOrDefault(candidate => candidate.Id == transit.OriginNodeId)?.Position.DistanceTo(targetPosition) ?? ship.Position.DistanceTo(targetPosition))); + : (world.Celestials.FirstOrDefault(candidate => candidate.Id == transit.OriginNodeId)?.Position.DistanceTo(targetPosition) ?? ship.Position.DistanceTo(targetPosition))); ship.Position = ship.Position.MoveToward(targetPosition, GetWarpTravelSpeed(ship) * deltaSeconds); transit.Progress = MathF.Min(1f, 1f - (ship.Position.DistanceTo(targetPosition) / totalDistance)); return ship.Position.DistanceTo(targetPosition) <= 18f - ? CompleteTransitArrival(ship, targetNode.SystemId, targetPosition, targetNode) + ? CompleteTransitArrival(ship, targetCelestial.SystemId, targetPosition, targetCelestial) : "none"; } - private string UpdateFtlTransit(ShipRuntime ship, SimulationWorld world, float deltaSeconds, string targetSystemId, Vector3 targetPosition, NodeRuntime? targetNode) + private string UpdateFtlTransit(ShipRuntime ship, SimulationWorld world, float deltaSeconds, string targetSystemId, Vector3 targetPosition, CelestialRuntime? targetCelestial) { - var destinationNodeId = targetNode?.Id; + var destinationNodeId = targetCelestial?.Id; var transit = ship.SpatialState.Transit; if (transit is null || transit.Regime != MovementRegimeKinds.FtlTransit || transit.DestinationNodeId != destinationNodeId) { transit = new ShipTransitRuntime { Regime = MovementRegimeKinds.FtlTransit, - OriginNodeId = ship.SpatialState.CurrentNodeId, + OriginNodeId = ship.SpatialState.CurrentCelestialId, DestinationNodeId = destinationNodeId, StartedAtUtc = world.GeneratedAtUtc, }; @@ -222,8 +241,7 @@ public sealed partial class SimulationEngine ship.SpatialState.SpaceLayer = SpaceLayerKinds.GalaxySpace; ship.SpatialState.MovementRegime = MovementRegimeKinds.FtlTransit; - ship.SpatialState.CurrentNodeId = null; - ship.SpatialState.CurrentBubbleId = null; + ship.SpatialState.CurrentCelestialId = null; ship.SpatialState.DestinationNodeId = destinationNodeId; if (ship.State != ShipState.Ftl) @@ -247,11 +265,11 @@ public sealed partial class SimulationEngine var totalDistance = MathF.Max(0.001f, originSystemPosition.DistanceTo(destinationSystemPosition)); transit.Progress = MathF.Min(1f, transit.Progress + ((ship.Definition.FtlSpeed * deltaSeconds) / totalDistance)); return transit.Progress >= 0.999f - ? CompleteSystemEntryArrival(ship, targetSystemId, targetPosition, targetNode) + ? CompleteSystemEntryArrival(ship, targetSystemId, targetPosition, targetCelestial) : "none"; } - private static string CompleteTransitArrival(ShipRuntime ship, string targetSystemId, Vector3 targetPosition, NodeRuntime? targetNode) + private static string CompleteTransitArrival(ShipRuntime ship, string targetSystemId, Vector3 targetPosition, CelestialRuntime? targetCelestial) { ship.ActionTimer = 0f; ship.Position = targetPosition; @@ -260,14 +278,13 @@ public sealed partial class SimulationEngine ship.SpatialState.Transit = null; ship.SpatialState.SpaceLayer = SpaceLayerKinds.LocalSpace; ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight; - ship.SpatialState.CurrentNodeId = targetNode?.Id; - ship.SpatialState.CurrentBubbleId = targetNode?.BubbleId; - ship.SpatialState.DestinationNodeId = targetNode?.Id; + ship.SpatialState.CurrentCelestialId = targetCelestial?.Id; + ship.SpatialState.DestinationNodeId = targetCelestial?.Id; ship.State = ShipState.Arriving; return "arrived"; } - private static string CompleteSystemEntryArrival(ShipRuntime ship, string targetSystemId, Vector3 targetPosition, NodeRuntime? targetNode) + private static string CompleteSystemEntryArrival(ShipRuntime ship, string targetSystemId, Vector3 targetPosition, CelestialRuntime? targetCelestial) { ship.ActionTimer = 0f; ship.Position = targetPosition; @@ -276,9 +293,8 @@ public sealed partial class SimulationEngine ship.SpatialState.Transit = null; ship.SpatialState.SpaceLayer = SpaceLayerKinds.LocalSpace; ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight; - ship.SpatialState.CurrentNodeId = targetNode?.Id; - ship.SpatialState.CurrentBubbleId = targetNode?.BubbleId; - ship.SpatialState.DestinationNodeId = targetNode?.Id; + ship.SpatialState.CurrentCelestialId = targetCelestial?.Id; + ship.SpatialState.DestinationNodeId = targetCelestial?.Id; ship.State = ShipState.Arriving; return "none"; } diff --git a/apps/backend/Simulation/SimulationEngine.OrbitalSystem.cs b/apps/backend/Simulation/SimulationEngine.OrbitalSystem.cs index 8e7f0d0..5e2f2eb 100644 --- a/apps/backend/Simulation/SimulationEngine.OrbitalSystem.cs +++ b/apps/backend/Simulation/SimulationEngine.OrbitalSystem.cs @@ -175,12 +175,12 @@ public sealed partial class SimulationEngine private void UpdateOrbitalState(SimulationWorld world) { var worldTimeSeconds = (float)world.OrbitalTimeSeconds; - var spatialNodesById = world.SpatialNodes.ToDictionary(node => node.Id, StringComparer.Ordinal); + var celestialsById = world.Celestials.ToDictionary(c => c.Id, StringComparer.Ordinal); foreach (var system in world.Systems) { var starNodeId = $"node-{system.Definition.Id}-star"; - if (spatialNodesById.TryGetValue(starNodeId, out var starNode)) + if (celestialsById.TryGetValue(starNodeId, out var starNode)) { starNode.Position = Vector3.Zero; } @@ -189,7 +189,7 @@ public sealed partial class SimulationEngine { var planet = system.Definition.Planets[planetIndex]; var planetNodeId = $"node-{system.Definition.Id}-planet-{planetIndex + 1}"; - if (!spatialNodesById.TryGetValue(planetNodeId, out var planetNode)) + if (!celestialsById.TryGetValue(planetNodeId, out var planetNode)) { continue; } @@ -200,7 +200,7 @@ public sealed partial class SimulationEngine foreach (var lagrange in EnumeratePlanetLagrangePoints(planetPosition, planet)) { var lagrangeId = $"node-{system.Definition.Id}-planet-{planetIndex + 1}-{lagrange.Designation.ToLowerInvariant()}"; - if (spatialNodesById.TryGetValue(lagrangeId, out var lagrangeNode)) + if (celestialsById.TryGetValue(lagrangeId, out var lagrangeNode)) { lagrangeNode.Position = lagrange.Position; } @@ -209,7 +209,7 @@ public sealed partial class SimulationEngine for (var moonIndex = 0; moonIndex < planet.MoonCount; moonIndex += 1) { var moonId = $"node-{system.Definition.Id}-planet-{planetIndex + 1}-moon-{moonIndex + 1}"; - if (!spatialNodesById.TryGetValue(moonId, out var moonNode)) + if (!celestialsById.TryGetValue(moonId, out var moonNode)) { continue; } @@ -221,30 +221,22 @@ public sealed partial class SimulationEngine foreach (var station in world.Stations) { - if (station.AnchorNodeId is null || !spatialNodesById.TryGetValue(station.AnchorNodeId, out var anchorNode)) + if (station.CelestialId is null || !celestialsById.TryGetValue(station.CelestialId, out var anchorCelestial)) { continue; } - station.Position = anchorNode.Position; - if (station.NodeId is not null && spatialNodesById.TryGetValue(station.NodeId, out var stationNode)) - { - stationNode.Position = station.Position; - } + station.Position = anchorCelestial.Position; } foreach (var node in world.Nodes) { - if (node.AnchorNodeId is null || !spatialNodesById.TryGetValue(node.AnchorNodeId, out var anchorNode)) + if (node.CelestialId is null || !celestialsById.TryGetValue(node.CelestialId, out var anchorCelestial)) { continue; } - node.Position = Add(anchorNode.Position, ComputeResourceNodeOffset(node, worldTimeSeconds)); - if (spatialNodesById.TryGetValue(node.Id, out var resourceNode)) - { - resourceNode.Position = node.Position; - } + node.Position = Add(anchorCelestial.Position, ComputeResourceNodeOffset(node, worldTimeSeconds)); } foreach (var ship in world.Ships.Where(ship => ship.DockedStationId is not null)) @@ -263,11 +255,6 @@ public sealed partial class SimulationEngine private static void SyncSpatialState(SimulationWorld world) { - foreach (var bubble in world.LocalBubbles) - { - bubble.OccupantShipIds.Clear(); - } - foreach (var ship in world.Ships) { ship.SpatialState.CurrentSystemId = ship.SystemId; @@ -275,25 +262,17 @@ public sealed partial class SimulationEngine ship.SpatialState.SystemPosition = ship.Position; if (ship.SpatialState.Transit is not null) { - ship.SpatialState.CurrentNodeId = null; - ship.SpatialState.CurrentBubbleId = null; + ship.SpatialState.CurrentCelestialId = null; continue; } ship.SpatialState.SpaceLayer = SpaceLayerKinds.LocalSpace; ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight; - var nearestNode = world.SpatialNodes + var nearestCelestial = world.Celestials .Where(candidate => candidate.SystemId == ship.SystemId) .OrderBy(candidate => candidate.Position.DistanceTo(ship.Position)) .FirstOrDefault(); - ship.SpatialState.CurrentNodeId = nearestNode?.Id; - ship.SpatialState.CurrentBubbleId = nearestNode?.BubbleId; - - if (nearestNode is not null) - { - var nearestBubble = world.LocalBubbles.FirstOrDefault(candidate => candidate.Id == nearestNode.BubbleId); - nearestBubble?.OccupantShipIds.Add(ship.Id); - } + ship.SpatialState.CurrentCelestialId = nearestCelestial?.Id; if (ship.DockedStationId is null) { @@ -301,15 +280,10 @@ public sealed partial class SimulationEngine } var station = world.Stations.FirstOrDefault(candidate => candidate.Id == ship.DockedStationId); - if (station?.BubbleId is null) + if (station?.CelestialId is not null) { - continue; + ship.SpatialState.CurrentCelestialId = station.CelestialId; } - - ship.SpatialState.CurrentNodeId = station.NodeId; - ship.SpatialState.CurrentBubbleId = station.BubbleId; - var bubble = world.LocalBubbles.FirstOrDefault(candidate => candidate.Id == station.BubbleId); - bubble?.OccupantShipIds.Add(ship.Id); } } diff --git a/apps/backend/Simulation/SimulationEngine.Replication.cs b/apps/backend/Simulation/SimulationEngine.Replication.cs index ccd8dfa..769b4d9 100644 --- a/apps/backend/Simulation/SimulationEngine.Replication.cs +++ b/apps/backend/Simulation/SimulationEngine.Replication.cs @@ -39,29 +39,20 @@ public sealed partial class SimulationEngine planet.Size, planet.Color, planet.HasRing)).ToList())).ToList(), - world.SpatialNodes.Select(ToSpatialNodeDelta).Select(node => new SpatialNodeSnapshot( - node.Id, - node.SystemId, - node.Kind, - node.LocalPosition, - node.BubbleId, - node.ParentNodeId, - node.OccupyingStructureId, - node.OrbitReferenceId)).ToList(), - world.LocalBubbles.Select(ToLocalBubbleDelta).Select(bubble => new LocalBubbleSnapshot( - bubble.Id, - bubble.NodeId, - bubble.SystemId, - bubble.Radius, - bubble.OccupantShipIds, - bubble.OccupantStationIds, - bubble.OccupantClaimIds, - bubble.OccupantConstructionSiteIds)).ToList(), + world.Celestials.Select(ToCelestialDelta).Select(c => new CelestialSnapshot( + c.Id, + c.SystemId, + c.Kind, + c.OrbitalAnchor, + c.LocalSpaceRadius, + c.ParentNodeId, + c.OccupyingStructureId, + c.OrbitReferenceId)).ToList(), world.Nodes.Select(ToNodeDelta).Select(node => new ResourceNodeSnapshot( node.Id, node.SystemId, node.LocalPosition, - node.AnchorNodeId, + node.CelestialId, node.SourceKind, node.OreRemaining, node.MaxOre, @@ -72,9 +63,7 @@ public sealed partial class SimulationEngine station.Category, station.SystemId, station.LocalPosition, - station.NodeId, - station.BubbleId, - station.AnchorNodeId, + station.CelestialId, station.Color, station.DockedShips, station.DockedShipIds, @@ -95,8 +84,7 @@ public sealed partial class SimulationEngine claim.Id, claim.FactionId, claim.SystemId, - claim.NodeId, - claim.BubbleId, + claim.CelestialId, claim.State, claim.Health, claim.PlacedAtUtc, @@ -105,8 +93,7 @@ public sealed partial class SimulationEngine site.Id, site.FactionId, site.SystemId, - site.NodeId, - site.BubbleId, + site.CelestialId, site.TargetKind, site.TargetDefinitionId, site.BlueprintId, @@ -155,8 +142,7 @@ public sealed partial class SimulationEngine ship.BehaviorPhase, ship.ControllerTaskKind, ship.CommanderObjective, - ship.NodeId, - ship.BubbleId, + ship.CelestialId, ship.DockedStationId, ship.CommanderId, ship.PolicySetId, @@ -191,14 +177,9 @@ public sealed partial class SimulationEngine node.LastDeltaSignature = BuildNodeSignature(node); } - foreach (var node in world.SpatialNodes) + foreach (var celestial in world.Celestials) { - node.LastDeltaSignature = BuildSpatialNodeSignature(node); - } - - foreach (var bubble in world.LocalBubbles) - { - bubble.LastDeltaSignature = BuildLocalBubbleSignature(bubble); + celestial.LastDeltaSignature = BuildCelestialSignature(celestial); } foreach (var station in world.Stations) @@ -255,37 +236,19 @@ public sealed partial class SimulationEngine return deltas; } - private static IReadOnlyList BuildSpatialNodeDeltas(SimulationWorld world) + private static IReadOnlyList BuildCelestialDeltas(SimulationWorld world) { - var deltas = new List(); - foreach (var node in world.SpatialNodes) + var deltas = new List(); + foreach (var celestial in world.Celestials) { - var signature = BuildSpatialNodeSignature(node); - if (signature == node.LastDeltaSignature) + var signature = BuildCelestialSignature(celestial); + if (signature == celestial.LastDeltaSignature) { continue; } - node.LastDeltaSignature = signature; - deltas.Add(ToSpatialNodeDelta(node)); - } - - return deltas; - } - - private static IReadOnlyList BuildLocalBubbleDeltas(SimulationWorld world) - { - var deltas = new List(); - foreach (var bubble in world.LocalBubbles) - { - var signature = BuildLocalBubbleSignature(bubble); - if (signature == bubble.LastDeltaSignature) - { - continue; - } - - bubble.LastDeltaSignature = signature; - deltas.Add(ToLocalBubbleDelta(bubble)); + celestial.LastDeltaSignature = signature; + deltas.Add(ToCelestialDelta(celestial)); } return deltas; @@ -424,22 +387,17 @@ public sealed partial class SimulationEngine string.Equals(c.Kind, CommanderKind.Faction, StringComparison.Ordinal)); private static string BuildNodeSignature(ResourceNodeRuntime node) => - $"{node.SystemId}|{node.Position.X:0.###}|{node.Position.Y:0.###}|{node.Position.Z:0.###}|{node.AnchorNodeId}|{node.OreRemaining:0.###}"; + $"{node.SystemId}|{node.Position.X:0.###}|{node.Position.Y:0.###}|{node.Position.Z:0.###}|{node.CelestialId}|{node.OreRemaining:0.###}"; - private static string BuildSpatialNodeSignature(NodeRuntime node) => - $"{node.SystemId}|{node.Kind.ToContractValue()}|{node.Position.X:0.###}|{node.Position.Y:0.###}|{node.Position.Z:0.###}|{node.BubbleId}|{node.ParentNodeId}|{node.OccupyingStructureId}|{node.OrbitReferenceId}"; - - private static string BuildLocalBubbleSignature(LocalBubbleRuntime bubble) => - $"{bubble.SystemId}|{bubble.NodeId}|{bubble.Radius:0.###}|{string.Join(",", bubble.OccupantShipIds.OrderBy(id => id, StringComparer.Ordinal))}|{string.Join(",", bubble.OccupantStationIds.OrderBy(id => id, StringComparer.Ordinal))}|{string.Join(",", bubble.OccupantClaimIds.OrderBy(id => id, StringComparer.Ordinal))}|{string.Join(",", bubble.OccupantConstructionSiteIds.OrderBy(id => id, StringComparer.Ordinal))}"; + private static string BuildCelestialSignature(CelestialRuntime celestial) => + $"{celestial.SystemId}|{celestial.Kind.ToContractValue()}|{celestial.Position.X:0.###}|{celestial.Position.Y:0.###}|{celestial.Position.Z:0.###}|{celestial.LocalSpaceRadius:0.###}|{celestial.ParentNodeId}|{celestial.OccupyingStructureId}|{celestial.OrbitReferenceId}"; private static string BuildStationSignature(SimulationWorld world, StationRuntime station) { var processes = ToStationActionProgressSnapshots(world, station); return string.Join("|", station.SystemId, - station.NodeId ?? "none", - station.BubbleId ?? "none", - station.AnchorNodeId ?? "none", + station.CelestialId ?? "none", station.CommanderId ?? "none", station.PolicySetId ?? "none", BuildInventorySignature(station.Inventory), @@ -458,10 +416,10 @@ public sealed partial class SimulationEngine } private static string BuildClaimSignature(ClaimRuntime claim) => - $"{claim.FactionId}|{claim.SystemId}|{claim.NodeId}|{claim.BubbleId}|{claim.State}|{claim.Health:0.###}|{claim.ActivatesAtUtc:O}"; + $"{claim.FactionId}|{claim.SystemId}|{claim.CelestialId}|{claim.State}|{claim.Health:0.###}|{claim.ActivatesAtUtc:O}"; private static string BuildConstructionSiteSignature(ConstructionSiteRuntime site) => - $"{site.FactionId}|{site.SystemId}|{site.NodeId}|{site.BubbleId}|{site.TargetKind}|{site.TargetDefinitionId}|{site.BlueprintId}|{site.ClaimId}|{site.StationId}|{site.State}|{site.Progress:0.###}|{BuildInventorySignature(site.Inventory)}|{BuildInventorySignature(site.RequiredItems)}|{BuildInventorySignature(site.DeliveredItems)}|{string.Join(",", site.AssignedConstructorShipIds.OrderBy(id => id, StringComparer.Ordinal))}|{string.Join(",", site.MarketOrderIds.OrderBy(id => id, StringComparer.Ordinal))}"; + $"{site.FactionId}|{site.SystemId}|{site.CelestialId}|{site.TargetKind}|{site.TargetDefinitionId}|{site.BlueprintId}|{site.ClaimId}|{site.StationId}|{site.State}|{site.Progress:0.###}|{BuildInventorySignature(site.Inventory)}|{BuildInventorySignature(site.RequiredItems)}|{BuildInventorySignature(site.DeliveredItems)}|{string.Join(",", site.AssignedConstructorShipIds.OrderBy(id => id, StringComparer.Ordinal))}|{string.Join(",", site.MarketOrderIds.OrderBy(id => id, StringComparer.Ordinal))}"; private static string BuildMarketOrderSignature(MarketOrderRuntime order) => $"{order.FactionId}|{order.StationId}|{order.ConstructionSiteId}|{order.Kind}|{order.ItemId}|{order.Amount:0.###}|{order.RemainingAmount:0.###}|{order.Valuation:0.###}|{order.ReserveThreshold?.ToString("0.###") ?? "none"}|{order.PolicySetId}|{order.State}"; @@ -486,14 +444,12 @@ public sealed partial class SimulationEngine ship.DefaultBehavior.Kind, ship.DefaultBehavior.Phase ?? "none", ship.ControllerTask.Kind.ToContractValue(), - ship.SpatialState.CurrentNodeId ?? "none", - ship.SpatialState.CurrentBubbleId ?? "none", + ship.SpatialState.CurrentCelestialId ?? "none", ship.DockedStationId ?? "none", ship.CommanderId ?? "none", ship.PolicySetId ?? "none", ship.SpatialState.SpaceLayer, - ship.SpatialState.CurrentNodeId ?? "none", - ship.SpatialState.CurrentBubbleId ?? "none", + ship.SpatialState.CurrentCelestialId ?? "none", ship.SpatialState.MovementRegime, ship.SpatialState.DestinationNodeId ?? "none", ship.SpatialState.Transit?.Regime ?? "none", @@ -528,31 +484,21 @@ public sealed partial class SimulationEngine node.Id, node.SystemId, ToDto(node.Position), - node.AnchorNodeId, + node.CelestialId, node.SourceKind, node.OreRemaining, node.MaxOre, node.ItemId); - private static SpatialNodeDelta ToSpatialNodeDelta(NodeRuntime node) => new( - node.Id, - node.SystemId, - node.Kind.ToContractValue(), - ToDto(node.Position), - node.BubbleId, - node.ParentNodeId, - node.OccupyingStructureId, - node.OrbitReferenceId); - - private static LocalBubbleDelta ToLocalBubbleDelta(LocalBubbleRuntime bubble) => new( - bubble.Id, - bubble.NodeId, - bubble.SystemId, - bubble.Radius, - bubble.OccupantShipIds.OrderBy(id => id, StringComparer.Ordinal).ToList(), - bubble.OccupantStationIds.OrderBy(id => id, StringComparer.Ordinal).ToList(), - bubble.OccupantClaimIds.OrderBy(id => id, StringComparer.Ordinal).ToList(), - bubble.OccupantConstructionSiteIds.OrderBy(id => id, StringComparer.Ordinal).ToList()); + private static CelestialDelta ToCelestialDelta(CelestialRuntime celestial) => new( + celestial.Id, + celestial.SystemId, + celestial.Kind.ToContractValue(), + ToDto(celestial.Position), + celestial.LocalSpaceRadius, + celestial.ParentNodeId, + celestial.OccupyingStructureId, + celestial.OrbitReferenceId); private static StationDelta ToStationDelta(SimulationWorld world, StationRuntime station) => new( station.Id, @@ -560,9 +506,7 @@ public sealed partial class SimulationEngine station.Category, station.SystemId, ToDto(station.Position), - station.NodeId, - station.BubbleId, - station.AnchorNodeId, + station.CelestialId, station.Color, station.DockedShipIds.Count, station.DockedShipIds.OrderBy(id => id, StringComparer.Ordinal).ToList(), @@ -615,8 +559,7 @@ public sealed partial class SimulationEngine claim.Id, claim.FactionId, claim.SystemId, - claim.NodeId, - claim.BubbleId, + claim.CelestialId, claim.State, claim.Health, claim.PlacedAtUtc, @@ -626,8 +569,7 @@ public sealed partial class SimulationEngine site.Id, site.FactionId, site.SystemId, - site.NodeId, - site.BubbleId, + site.CelestialId, site.TargetKind, site.TargetDefinitionId, site.BlueprintId, @@ -696,8 +638,7 @@ public sealed partial class SimulationEngine ship.DefaultBehavior.Phase, ship.ControllerTask.Kind.ToContractValue(), commander?.ActiveActionName, - ship.SpatialState.CurrentNodeId, - ship.SpatialState.CurrentBubbleId, + ship.SpatialState.CurrentCelestialId, ship.DockedStationId, ship.CommanderId, ship.PolicySetId, @@ -810,8 +751,7 @@ public sealed partial class SimulationEngine private static ShipSpatialStateSnapshot ToShipSpatialStateSnapshot(ShipSpatialStateRuntime state) => new( state.SpaceLayer, state.CurrentSystemId, - state.CurrentNodeId, - state.CurrentBubbleId, + state.CurrentCelestialId, state.LocalPosition is null ? null : ToDto(state.LocalPosition.Value), state.SystemPosition is null ? null : ToDto(state.SystemPosition.Value), state.MovementRegime, diff --git a/apps/backend/Simulation/SimulationEngine.StationController.cs b/apps/backend/Simulation/SimulationEngine.StationController.cs index 4844982..c360490 100644 --- a/apps/backend/Simulation/SimulationEngine.StationController.cs +++ b/apps/backend/Simulation/SimulationEngine.StationController.cs @@ -305,7 +305,7 @@ public sealed partial class SimulationEngine private static bool FactionControlsSystem(SimulationWorld world, string factionId, string systemId) { - var totalLagrangePoints = world.SpatialNodes.Count(node => + var totalLagrangePoints = world.Celestials.Count(node => node.SystemId == systemId && node.Kind == SpatialNodeKind.LagrangePoint); if (totalLagrangePoints == 0) diff --git a/apps/backend/Simulation/SimulationEngine.StationSystems.cs b/apps/backend/Simulation/SimulationEngine.StationSystems.cs index 6e2ab6e..9d16688 100644 --- a/apps/backend/Simulation/SimulationEngine.StationSystems.cs +++ b/apps/backend/Simulation/SimulationEngine.StationSystems.cs @@ -90,8 +90,7 @@ public sealed partial class SimulationEngine { CurrentSystemId = station.SystemId, SpaceLayer = SpaceLayerKinds.LocalSpace, - CurrentNodeId = station.NodeId, - CurrentBubbleId = station.BubbleId, + CurrentCelestialId = station.CelestialId, LocalPosition = position, SystemPosition = position, MovementRegime = MovementRegimeKinds.LocalFlight, diff --git a/apps/backend/Simulation/SimulationEngine.cs b/apps/backend/Simulation/SimulationEngine.cs index 41346df..67dfb9f 100644 --- a/apps/backend/Simulation/SimulationEngine.cs +++ b/apps/backend/Simulation/SimulationEngine.cs @@ -70,8 +70,7 @@ public sealed partial class SimulationEngine world.GeneratedAtUtc, false, events, - BuildSpatialNodeDeltas(world), - BuildLocalBubbleDeltas(world), + BuildCelestialDeltas(world), BuildNodeDeltas(world), BuildStationDeltas(world), BuildClaimDeltas(world), diff --git a/apps/backend/Simulation/WorldService.cs b/apps/backend/Simulation/WorldService.cs index 78e2cf4..d3aa7b5 100644 --- a/apps/backend/Simulation/WorldService.cs +++ b/apps/backend/Simulation/WorldService.cs @@ -116,7 +116,6 @@ public sealed class WorldService( [], [], [], - [], []); _history.Enqueue(resetDelta); @@ -132,8 +131,7 @@ public sealed class WorldService( private static bool HasMeaningfulDelta(WorldDelta delta) => delta.RequiresSnapshotRefresh || delta.Events.Count > 0 - || delta.SpatialNodes.Count > 0 - || delta.LocalBubbles.Count > 0 + || delta.Celestials.Count > 0 || delta.Nodes.Count > 0 || delta.Stations.Count > 0 || delta.Claims.Count > 0 @@ -168,9 +166,9 @@ public sealed class WorldService( } var systemFilter = scope.SystemId; - if (string.Equals(scope.ScopeKind, "local-bubble", StringComparison.OrdinalIgnoreCase) && systemFilter is null && scope.BubbleId is not null) + if (string.Equals(scope.ScopeKind, "local-celestial", StringComparison.OrdinalIgnoreCase) && systemFilter is null && scope.CelestialId is not null) { - systemFilter = ResolveBubbleSystemId(scope.BubbleId); + systemFilter = ResolveCelestialSystemId(scope.CelestialId); } return delta with @@ -179,8 +177,7 @@ public sealed class WorldService( .Select((evt) => EnrichEventScope(evt)) .Where((evt) => IsEventVisibleToScope(evt, scope, systemFilter)) .ToList(), - SpatialNodes = delta.SpatialNodes.Where((node) => systemFilter is null || node.SystemId == systemFilter).ToList(), - LocalBubbles = delta.LocalBubbles.Where((bubble) => systemFilter is null || bubble.SystemId == systemFilter).ToList(), + Celestials = delta.Celestials.Where((celestial) => systemFilter is null || celestial.SystemId == systemFilter).ToList(), Nodes = delta.Nodes.Where((node) => systemFilter is null || node.SystemId == systemFilter).ToList(), Stations = delta.Stations.Where((station) => systemFilter is null || station.SystemId == systemFilter).ToList(), Claims = delta.Claims.Where((claim) => systemFilter is null || claim.SystemId == systemFilter).ToList(), @@ -205,8 +202,7 @@ public sealed class WorldService( "ship" => WithEntityScope(evt, "system", _world.Ships.FirstOrDefault((ship) => ship.Id == evt.EntityId)?.SystemId), "station" => WithEntityScope(evt, "system", _world.Stations.FirstOrDefault((station) => station.Id == evt.EntityId)?.SystemId), "node" => WithEntityScope(evt, "system", _world.Nodes.FirstOrDefault((node) => node.Id == evt.EntityId)?.SystemId), - "spatial-node" => WithEntityScope(evt, "system", _world.SpatialNodes.FirstOrDefault((node) => node.Id == evt.EntityId)?.SystemId), - "local-bubble" => WithEntityScope(evt, "local-bubble", _world.LocalBubbles.FirstOrDefault((bubble) => bubble.Id == evt.EntityId)?.Id), + "celestial" => WithEntityScope(evt, "system", _world.Celestials.FirstOrDefault((c) => c.Id == evt.EntityId)?.SystemId), "claim" => WithEntityScope(evt, "system", _world.Claims.FirstOrDefault((claim) => claim.Id == evt.EntityId)?.SystemId), "construction-site" => WithEntityScope(evt, "system", _world.ConstructionSites.FirstOrDefault((site) => site.Id == evt.EntityId)?.SystemId), "market-order" => WithEntityScope(evt, "system", ResolveMarketOrderSystemId(evt.EntityId)), @@ -226,8 +222,8 @@ public sealed class WorldService( ScopeEntityId = scopeEntityId, }; - private string? ResolveBubbleSystemId(string bubbleId) => - _world.LocalBubbles.FirstOrDefault((bubble) => bubble.Id == bubbleId)?.SystemId; + private string? ResolveCelestialSystemId(string celestialId) => + _world.Celestials.FirstOrDefault((c) => c.Id == celestialId)?.SystemId; private string? ResolveMarketOrderSystemId(string orderId) { @@ -271,7 +267,7 @@ public sealed class WorldService( { "universe" => true, "system" => evt.ScopeKind == "universe" || evt.ScopeEntityId == systemFilter, - "local-bubble" => evt.ScopeKind == "universe" || evt.ScopeEntityId == systemFilter, + "local-celestial" => evt.ScopeKind == "universe" || evt.ScopeEntityId == systemFilter, _ => true, }; }