feat: simplified local-space and celestial, removed bubbles

This commit is contained in:
2026-03-18 08:49:35 -04:00
parent 00a008bda5
commit 933c6afd08
19 changed files with 246 additions and 448 deletions

View File

@@ -30,7 +30,7 @@ public sealed record ResourceNodeSnapshot(
string Id, string Id,
string SystemId, string SystemId,
Vector3Dto LocalPosition, Vector3Dto LocalPosition,
string? AnchorNodeId, string? CelestialId,
string SourceKind, string SourceKind,
float OreRemaining, float OreRemaining,
float MaxOre, float MaxOre,
@@ -40,48 +40,28 @@ public sealed record ResourceNodeDelta(
string Id, string Id,
string SystemId, string SystemId,
Vector3Dto LocalPosition, Vector3Dto LocalPosition,
string? AnchorNodeId, string? CelestialId,
string SourceKind, string SourceKind,
float OreRemaining, float OreRemaining,
float MaxOre, float MaxOre,
string ItemId); string ItemId);
public sealed record SpatialNodeSnapshot( public sealed record CelestialSnapshot(
string Id, string Id,
string SystemId, string SystemId,
string Kind, string Kind,
Vector3Dto LocalPosition, Vector3Dto OrbitalAnchor,
string BubbleId, float LocalSpaceRadius,
string? ParentNodeId, string? ParentNodeId,
string? OccupyingStructureId, string? OccupyingStructureId,
string? OrbitReferenceId); string? OrbitReferenceId);
public sealed record SpatialNodeDelta( public sealed record CelestialDelta(
string Id, string Id,
string SystemId, string SystemId,
string Kind, string Kind,
Vector3Dto LocalPosition, Vector3Dto OrbitalAnchor,
string BubbleId, float LocalSpaceRadius,
string? ParentNodeId, string? ParentNodeId,
string? OccupyingStructureId, string? OccupyingStructureId,
string? OrbitReferenceId); string? OrbitReferenceId);
public sealed record LocalBubbleSnapshot(
string Id,
string NodeId,
string SystemId,
float Radius,
IReadOnlyList<string> OccupantShipIds,
IReadOnlyList<string> OccupantStationIds,
IReadOnlyList<string> OccupantClaimIds,
IReadOnlyList<string> OccupantConstructionSiteIds);
public sealed record LocalBubbleDelta(
string Id,
string NodeId,
string SystemId,
float Radius,
IReadOnlyList<string> OccupantShipIds,
IReadOnlyList<string> OccupantStationIds,
IReadOnlyList<string> OccupantClaimIds,
IReadOnlyList<string> OccupantConstructionSiteIds);

View File

@@ -10,9 +10,7 @@ public sealed record StationSnapshot(
string Category, string Category,
string SystemId, string SystemId,
Vector3Dto LocalPosition, Vector3Dto LocalPosition,
string? NodeId, string? CelestialId,
string? BubbleId,
string? AnchorNodeId,
string Color, string Color,
int DockedShips, int DockedShips,
IReadOnlyList<string> DockedShipIds, IReadOnlyList<string> DockedShipIds,
@@ -36,9 +34,7 @@ public sealed record StationDelta(
string Category, string Category,
string SystemId, string SystemId,
Vector3Dto LocalPosition, Vector3Dto LocalPosition,
string? NodeId, string? CelestialId,
string? BubbleId,
string? AnchorNodeId,
string Color, string Color,
int DockedShips, int DockedShips,
IReadOnlyList<string> DockedShipIds, IReadOnlyList<string> DockedShipIds,
@@ -70,8 +66,7 @@ public sealed record ClaimSnapshot(
string Id, string Id,
string FactionId, string FactionId,
string SystemId, string SystemId,
string NodeId, string CelestialId,
string BubbleId,
string State, string State,
float Health, float Health,
DateTimeOffset PlacedAtUtc, DateTimeOffset PlacedAtUtc,
@@ -81,8 +76,7 @@ public sealed record ClaimDelta(
string Id, string Id,
string FactionId, string FactionId,
string SystemId, string SystemId,
string NodeId, string CelestialId,
string BubbleId,
string State, string State,
float Health, float Health,
DateTimeOffset PlacedAtUtc, DateTimeOffset PlacedAtUtc,
@@ -92,8 +86,7 @@ public sealed record ConstructionSiteSnapshot(
string Id, string Id,
string FactionId, string FactionId,
string SystemId, string SystemId,
string NodeId, string CelestialId,
string BubbleId,
string TargetKind, string TargetKind,
string TargetDefinitionId, string TargetDefinitionId,
string? BlueprintId, string? BlueprintId,
@@ -111,8 +104,7 @@ public sealed record ConstructionSiteDelta(
string Id, string Id,
string FactionId, string FactionId,
string SystemId, string SystemId,
string NodeId, string CelestialId,
string BubbleId,
string TargetKind, string TargetKind,
string TargetDefinitionId, string TargetDefinitionId,
string? BlueprintId, string? BlueprintId,

View File

@@ -15,8 +15,7 @@ public sealed record ShipSnapshot(
string? BehaviorPhase, string? BehaviorPhase,
string ControllerTaskKind, string ControllerTaskKind,
string? CommanderObjective, string? CommanderObjective,
string? NodeId, string? CelestialId,
string? BubbleId,
string? DockedStationId, string? DockedStationId,
string? CommanderId, string? CommanderId,
string? PolicySetId, string? PolicySetId,
@@ -46,8 +45,7 @@ public sealed record ShipDelta(
string? BehaviorPhase, string? BehaviorPhase,
string ControllerTaskKind, string ControllerTaskKind,
string? CommanderObjective, string? CommanderObjective,
string? NodeId, string? CelestialId,
string? BubbleId,
string? DockedStationId, string? DockedStationId,
string? CommanderId, string? CommanderId,
string? PolicySetId, string? PolicySetId,
@@ -69,8 +67,7 @@ public sealed record ShipActionProgressSnapshot(
public sealed record ShipSpatialStateSnapshot( public sealed record ShipSpatialStateSnapshot(
string SpaceLayer, string SpaceLayer,
string CurrentSystemId, string CurrentSystemId,
string? CurrentNodeId, string? CurrentCelestialId,
string? CurrentBubbleId,
Vector3Dto? LocalPosition, Vector3Dto? LocalPosition,
Vector3Dto? SystemPosition, Vector3Dto? SystemPosition,
string MovementRegime, string MovementRegime,

View File

@@ -9,8 +9,7 @@ public sealed record WorldSnapshot(
OrbitalSimulationSnapshot OrbitalSimulation, OrbitalSimulationSnapshot OrbitalSimulation,
DateTimeOffset GeneratedAtUtc, DateTimeOffset GeneratedAtUtc,
IReadOnlyList<SystemSnapshot> Systems, IReadOnlyList<SystemSnapshot> Systems,
IReadOnlyList<SpatialNodeSnapshot> SpatialNodes, IReadOnlyList<CelestialSnapshot> Celestials,
IReadOnlyList<LocalBubbleSnapshot> LocalBubbles,
IReadOnlyList<ResourceNodeSnapshot> Nodes, IReadOnlyList<ResourceNodeSnapshot> Nodes,
IReadOnlyList<StationSnapshot> Stations, IReadOnlyList<StationSnapshot> Stations,
IReadOnlyList<ClaimSnapshot> Claims, IReadOnlyList<ClaimSnapshot> Claims,
@@ -28,8 +27,7 @@ public sealed record WorldDelta(
DateTimeOffset GeneratedAtUtc, DateTimeOffset GeneratedAtUtc,
bool RequiresSnapshotRefresh, bool RequiresSnapshotRefresh,
IReadOnlyList<SimulationEventRecord> Events, IReadOnlyList<SimulationEventRecord> Events,
IReadOnlyList<SpatialNodeDelta> SpatialNodes, IReadOnlyList<CelestialDelta> Celestials,
IReadOnlyList<LocalBubbleDelta> LocalBubbles,
IReadOnlyList<ResourceNodeDelta> Nodes, IReadOnlyList<ResourceNodeDelta> Nodes,
IReadOnlyList<StationDelta> Stations, IReadOnlyList<StationDelta> Stations,
IReadOnlyList<ClaimDelta> Claims, IReadOnlyList<ClaimDelta> Claims,
@@ -54,7 +52,7 @@ public sealed record SimulationEventRecord(
public sealed record ObserverScope( public sealed record ObserverScope(
string ScopeKind, string ScopeKind,
string? SystemId = null, string? SystemId = null,
string? BubbleId = null); string? CelestialId = null);
public sealed record OrbitalSimulationSnapshot( public sealed record OrbitalSimulationSnapshot(
double SimulatedSecondsPerRealSecond); double SimulatedSecondsPerRealSecond);

View File

@@ -5,8 +5,7 @@ public sealed class ClaimRuntime
public required string Id { get; init; } public required string Id { get; init; }
public required string FactionId { get; init; } public required string FactionId { get; init; }
public required string SystemId { get; init; } public required string SystemId { get; init; }
public required string NodeId { get; init; } public required string CelestialId { get; init; }
public required string BubbleId { get; init; }
public string? CommanderId { get; set; } public string? CommanderId { get; set; }
public DateTimeOffset PlacedAtUtc { get; init; } public DateTimeOffset PlacedAtUtc { get; init; }
public DateTimeOffset ActivatesAtUtc { get; set; } public DateTimeOffset ActivatesAtUtc { get; set; }
@@ -20,8 +19,7 @@ public sealed class ConstructionSiteRuntime
public required string Id { get; init; } public required string Id { get; init; }
public required string FactionId { get; init; } public required string FactionId { get; init; }
public required string SystemId { get; init; } public required string SystemId { get; init; }
public required string NodeId { get; init; } public required string CelestialId { get; init; }
public required string BubbleId { get; init; }
public required string TargetKind { get; init; } public required string TargetKind { get; init; }
public required string TargetDefinitionId { get; init; } public required string TargetDefinitionId { get; init; }
public string? BlueprintId { get; set; } public string? BlueprintId { get; set; }

View File

@@ -6,8 +6,6 @@ public enum SpatialNodeKind
Planet, Planet,
Moon, Moon,
LagrangePoint, LagrangePoint,
Station,
ResourceSite,
} }
public enum WorkStatus public enum WorkStatus
@@ -112,7 +110,7 @@ public static class ShipTaskKinds
public const string BuildConstructionSite = "build-construction-site"; public const string BuildConstructionSite = "build-construction-site";
public const string EscortTarget = "escort-target"; public const string EscortTarget = "escort-target";
public const string AttackTarget = "attack-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 Retreat = "retreat";
public const string HoldPosition = "hold-position"; public const string HoldPosition = "hold-position";
} }
@@ -167,8 +165,6 @@ public static class SimulationEnumMappings
SpatialNodeKind.Planet => "planet", SpatialNodeKind.Planet => "planet",
SpatialNodeKind.Moon => "moon", SpatialNodeKind.Moon => "moon",
SpatialNodeKind.LagrangePoint => "lagrange-point", SpatialNodeKind.LagrangePoint => "lagrange-point",
SpatialNodeKind.Station => "station",
SpatialNodeKind.ResourceSite => "resource-site",
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null), _ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null),
}; };

View File

@@ -9,8 +9,7 @@ public sealed class SimulationWorld
public required BalanceDefinition Balance { get; init; } public required BalanceDefinition Balance { get; init; }
public required List<SystemRuntime> Systems { get; init; } public required List<SystemRuntime> Systems { get; init; }
public required List<ResourceNodeRuntime> Nodes { get; init; } public required List<ResourceNodeRuntime> Nodes { get; init; }
public required List<NodeRuntime> SpatialNodes { get; init; } public required List<CelestialRuntime> Celestials { get; init; }
public required List<LocalBubbleRuntime> LocalBubbles { get; init; }
public required List<StationRuntime> Stations { get; init; } public required List<StationRuntime> Stations { get; init; }
public required List<ShipRuntime> Ships { get; init; } public required List<ShipRuntime> Ships { get; init; }
public required List<FactionRuntime> Factions { get; init; } public required List<FactionRuntime> Factions { get; init; }

View File

@@ -15,7 +15,7 @@ public sealed class ResourceNodeRuntime
public required Vector3 Position { get; set; } public required Vector3 Position { get; set; }
public required string SourceKind { get; init; } public required string SourceKind { get; init; }
public required string ItemId { 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 OrbitRadius { get; init; }
public float OrbitPhase { get; init; } public float OrbitPhase { get; init; }
public float OrbitInclination { get; init; } public float OrbitInclination { get; init; }
@@ -24,38 +24,24 @@ public sealed class ResourceNodeRuntime
public string LastDeltaSignature { get; set; } = string.Empty; public string LastDeltaSignature { get; set; } = string.Empty;
} }
public sealed class NodeRuntime public sealed class CelestialRuntime
{ {
public required string Id { get; init; } public required string Id { get; init; }
public required string SystemId { get; init; } public required string SystemId { get; init; }
public required SpatialNodeKind Kind { get; init; } public required SpatialNodeKind Kind { get; init; }
public required Vector3 Position { get; set; } public required Vector3 Position { get; set; }
public required string BubbleId { get; init; } public float LocalSpaceRadius { get; init; }
public string? ParentNodeId { get; set; } public string? ParentNodeId { get; set; }
public string? OccupyingStructureId { get; set; } public string? OccupyingStructureId { get; set; }
public string? OrbitReferenceId { get; set; } public string? OrbitReferenceId { get; set; }
public string LastDeltaSignature { get; set; } = string.Empty; 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<string> OccupantShipIds { get; } = new(StringComparer.Ordinal);
public HashSet<string> OccupantStationIds { get; } = new(StringComparer.Ordinal);
public HashSet<string> OccupantClaimIds { get; } = new(StringComparer.Ordinal);
public HashSet<string> OccupantConstructionSiteIds { get; } = new(StringComparer.Ordinal);
public string LastDeltaSignature { get; set; } = string.Empty;
}
public sealed class ShipSpatialStateRuntime public sealed class ShipSpatialStateRuntime
{ {
public string SpaceLayer { get; set; } = SpaceLayerKinds.LocalSpace; public string SpaceLayer { get; set; } = SpaceLayerKinds.LocalSpace;
public required string CurrentSystemId { get; set; } public required string CurrentSystemId { get; set; }
public string? CurrentNodeId { get; set; } public string? CurrentCelestialId { get; set; }
public string? CurrentBubbleId { get; set; }
public Vector3? LocalPosition { get; set; } public Vector3? LocalPosition { get; set; }
public Vector3? SystemPosition { get; set; } public Vector3? SystemPosition { get; set; }
public string MovementRegime { get; set; } = MovementRegimeKinds.LocalFlight; public string MovementRegime { get; set; } = MovementRegimeKinds.LocalFlight;

View File

@@ -10,9 +10,7 @@ public sealed class StationRuntime
public required Vector3 Position { get; set; } public required Vector3 Position { get; set; }
public float Radius { get; set; } = 24f; public float Radius { get; set; } = 24f;
public required string FactionId { get; init; } public required string FactionId { get; init; }
public string? NodeId { get; set; } public string? CelestialId { get; set; }
public string? BubbleId { get; set; }
public string? AnchorNodeId { get; set; }
public string? CommanderId { get; set; } public string? CommanderId { get; set; }
public string? PolicySetId { get; set; } public string? PolicySetId { get; set; }
public List<StationModuleRuntime> Modules { get; } = []; public List<StationModuleRuntime> Modules { get; } = [];

View File

@@ -88,27 +88,26 @@ public sealed partial class ScenarioLoader
private static List<ClaimRuntime> CreateClaims( private static List<ClaimRuntime> CreateClaims(
IReadOnlyCollection<StationRuntime> stations, IReadOnlyCollection<StationRuntime> stations,
IReadOnlyCollection<NodeRuntime> nodes, IReadOnlyCollection<CelestialRuntime> celestials,
DateTimeOffset nowUtc) DateTimeOffset nowUtc)
{ {
var stationsByAnchorNodeId = stations var stationsByCelestialId = stations
.Where((station) => station.AnchorNodeId is not null) .Where((station) => station.CelestialId is not null)
.ToDictionary((station) => station.AnchorNodeId!, StringComparer.Ordinal); .ToDictionary((station) => station.CelestialId!, StringComparer.Ordinal);
var claims = new List<ClaimRuntime>(); var claims = new List<ClaimRuntime>();
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; continue;
} }
claims.Add(new ClaimRuntime claims.Add(new ClaimRuntime
{ {
Id = $"claim-{node.Id}", Id = $"claim-{celestial.Id}",
FactionId = station.FactionId, FactionId = station.FactionId,
SystemId = node.SystemId, SystemId = celestial.SystemId,
NodeId = node.Id, CelestialId = celestial.Id,
BubbleId = node.BubbleId,
PlacedAtUtc = nowUtc, PlacedAtUtc = nowUtc,
ActivatesAtUtc = nowUtc.AddSeconds(8), ActivatesAtUtc = nowUtc.AddSeconds(8),
State = ClaimStateKinds.Activating, State = ClaimStateKinds.Activating,
@@ -122,7 +121,6 @@ public sealed partial class ScenarioLoader
private static (List<ConstructionSiteRuntime> ConstructionSites, List<MarketOrderRuntime> MarketOrders) CreateConstructionSites( private static (List<ConstructionSiteRuntime> ConstructionSites, List<MarketOrderRuntime> MarketOrders) CreateConstructionSites(
IReadOnlyCollection<StationRuntime> stations, IReadOnlyCollection<StationRuntime> stations,
IReadOnlyCollection<ClaimRuntime> claims, IReadOnlyCollection<ClaimRuntime> claims,
IReadOnlyCollection<NodeRuntime> nodes,
IReadOnlyDictionary<string, ModuleRecipeDefinition> moduleRecipes) IReadOnlyDictionary<string, ModuleRecipeDefinition> moduleRecipes)
{ {
var sites = new List<ConstructionSiteRuntime>(); var sites = new List<ConstructionSiteRuntime>();
@@ -131,18 +129,12 @@ public sealed partial class ScenarioLoader
foreach (var station in stations) foreach (var station in stations)
{ {
var moduleId = GetNextConstructionSiteModule(station, moduleRecipes); var moduleId = GetNextConstructionSiteModule(station, moduleRecipes);
if (moduleId is null || station.AnchorNodeId is null) if (moduleId is null || station.CelestialId is null)
{ {
continue; continue;
} }
var anchorNode = nodes.FirstOrDefault((node) => node.Id == station.AnchorNodeId); var claim = claims.FirstOrDefault((candidate) => candidate.CelestialId == station.CelestialId);
if (anchorNode is null)
{
continue;
}
var claim = claims.FirstOrDefault((candidate) => candidate.NodeId == anchorNode.Id);
if (claim is null || !moduleRecipes.TryGetValue(moduleId, out var recipe)) if (claim is null || !moduleRecipes.TryGetValue(moduleId, out var recipe))
{ {
continue; continue;
@@ -153,8 +145,7 @@ public sealed partial class ScenarioLoader
Id = $"site-{station.Id}", Id = $"site-{station.Id}",
FactionId = station.FactionId, FactionId = station.FactionId,
SystemId = station.SystemId, SystemId = station.SystemId,
NodeId = anchorNode.Id, CelestialId = station.CelestialId,
BubbleId = anchorNode.BubbleId,
TargetKind = "station-module", TargetKind = "station-module",
TargetDefinitionId = "station", TargetDefinitionId = "station",
BlueprintId = moduleId, BlueprintId = moduleId,

View File

@@ -6,48 +6,44 @@ public sealed partial class ScenarioLoader
{ {
private static SystemSpatialGraph BuildSystemSpatialGraph(SystemRuntime system) private static SystemSpatialGraph BuildSystemSpatialGraph(SystemRuntime system)
{ {
var nodes = new List<NodeRuntime>(); var celestials = new List<CelestialRuntime>();
var bubbles = new List<LocalBubbleRuntime>(); var lagrangeNodesByPlanetIndex = new Dictionary<int, Dictionary<string, CelestialRuntime>>();
var lagrangeNodesByPlanetIndex = new Dictionary<int, Dictionary<string, NodeRuntime>>();
var starNode = AddSpatialNode( AddCelestial(
nodes, celestials,
bubbles,
id: $"node-{system.Definition.Id}-star", id: $"node-{system.Definition.Id}-star",
systemId: system.Definition.Id, systemId: system.Definition.Id,
kind: SpatialNodeKind.Star, kind: SpatialNodeKind.Star,
position: Vector3.Zero, 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) for (var planetIndex = 0; planetIndex < system.Definition.Planets.Count; planetIndex += 1)
{ {
var planet = system.Definition.Planets[planetIndex]; var planet = system.Definition.Planets[planetIndex];
var planetNodeId = $"node-{system.Definition.Id}-planet-{planetIndex + 1}"; var planetNodeId = $"node-{system.Definition.Id}-planet-{planetIndex + 1}";
var planetPosition = ComputePlanetPosition(planet); var planetPosition = ComputePlanetPosition(planet);
var planetNode = AddSpatialNode( var planetCelestial = AddCelestial(
nodes, celestials,
bubbles,
id: planetNodeId, id: planetNodeId,
systemId: system.Definition.Id, systemId: system.Definition.Id,
kind: SpatialNodeKind.Planet, kind: SpatialNodeKind.Planet,
position: planetPosition, position: planetPosition,
radius: MathF.Max(planet.Size + PlanetBubbleRadiusPadding, 120f), localSpaceRadius: LocalSpaceRadius,
parentNodeId: starNode.Id); parentNodeId: $"node-{system.Definition.Id}-star");
var lagrangeNodes = new Dictionary<string, NodeRuntime>(StringComparer.Ordinal); var lagrangeNodes = new Dictionary<string, CelestialRuntime>(StringComparer.Ordinal);
foreach (var point in EnumeratePlanetLagrangePoints(planetPosition, planet)) foreach (var point in EnumeratePlanetLagrangePoints(planetPosition, planet))
{ {
var lagrangeNode = AddSpatialNode( var lagrangeCelestial = AddCelestial(
nodes, celestials,
bubbles,
id: $"node-{system.Definition.Id}-planet-{planetIndex + 1}-{point.Designation.ToLowerInvariant()}", id: $"node-{system.Definition.Id}-planet-{planetIndex + 1}-{point.Designation.ToLowerInvariant()}",
systemId: system.Definition.Id, systemId: system.Definition.Id,
kind: SpatialNodeKind.LagrangePoint, kind: SpatialNodeKind.LagrangePoint,
position: point.Position, position: point.Position,
radius: LagrangeBubbleRadius, localSpaceRadius: LocalSpaceRadius,
parentNodeId: planetNode.Id, parentNodeId: planetCelestial.Id,
orbitReferenceId: point.Designation); orbitReferenceId: point.Designation);
lagrangeNodes[point.Designation] = lagrangeNode; lagrangeNodes[point.Designation] = lagrangeCelestial;
} }
lagrangeNodesByPlanetIndex[planetIndex] = lagrangeNodes; lagrangeNodesByPlanetIndex[planetIndex] = lagrangeNodes;
@@ -61,54 +57,44 @@ public sealed partial class ScenarioLoader
for (var moonIndex = 0; moonIndex < planet.MoonCount; moonIndex += 1) for (var moonIndex = 0; moonIndex < planet.MoonCount; moonIndex += 1)
{ {
var moonPosition = ComputeMoonPosition(planetPosition, moonOrbitRadius, moonIndex, planetIndex); var moonPosition = ComputeMoonPosition(planetPosition, moonOrbitRadius, moonIndex, planetIndex);
AddSpatialNode( AddCelestial(
nodes, celestials,
bubbles,
id: $"node-{system.Definition.Id}-planet-{planetIndex + 1}-moon-{moonIndex + 1}", id: $"node-{system.Definition.Id}-planet-{planetIndex + 1}-moon-{moonIndex + 1}",
systemId: system.Definition.Id, systemId: system.Definition.Id,
kind: SpatialNodeKind.Moon, kind: SpatialNodeKind.Moon,
position: moonPosition, position: moonPosition,
radius: MoonBubbleRadiusPadding + 24f, localSpaceRadius: LocalSpaceRadius,
parentNodeId: planetNode.Id); parentNodeId: planetCelestial.Id);
moonOrbitRadius += 30f; moonOrbitRadius += 30f;
} }
} }
return new SystemSpatialGraph(system.Definition.Id, nodes, bubbles, lagrangeNodesByPlanetIndex); return new SystemSpatialGraph(system.Definition.Id, celestials, lagrangeNodesByPlanetIndex);
} }
private static NodeRuntime AddSpatialNode( private static CelestialRuntime AddCelestial(
ICollection<NodeRuntime> nodes, ICollection<CelestialRuntime> celestials,
ICollection<LocalBubbleRuntime> bubbles,
string id, string id,
string systemId, string systemId,
SpatialNodeKind kind, SpatialNodeKind kind,
Vector3 position, Vector3 position,
float radius, float localSpaceRadius,
string? parentNodeId = null, string? parentNodeId = null,
string? orbitReferenceId = null) string? orbitReferenceId = null)
{ {
var bubbleId = $"bubble-{id}"; var celestial = new CelestialRuntime
var node = new NodeRuntime
{ {
Id = id, Id = id,
SystemId = systemId, SystemId = systemId,
Kind = kind, Kind = kind,
Position = position, Position = position,
BubbleId = bubbleId, LocalSpaceRadius = localSpaceRadius,
ParentNodeId = parentNodeId, ParentNodeId = parentNodeId,
OrbitReferenceId = orbitReferenceId, OrbitReferenceId = orbitReferenceId,
}; };
nodes.Add(node); celestials.Add(celestial);
bubbles.Add(new LocalBubbleRuntime return celestial;
{
Id = bubbleId,
NodeId = id,
SystemId = systemId,
Radius = radius,
});
return node;
} }
private static IEnumerable<LagrangePointPlacement> EnumeratePlanetLagrangePoints( private static IEnumerable<LagrangePointPlacement> EnumeratePlanetLagrangePoints(
@@ -165,36 +151,36 @@ public sealed partial class ScenarioLoader
InitialStationDefinition plan, InitialStationDefinition plan,
SystemRuntime system, SystemRuntime system,
SystemSpatialGraph graph, SystemSpatialGraph graph,
IReadOnlyCollection<NodeRuntime> existingNodes) IReadOnlyCollection<CelestialRuntime> existingCelestials)
{ {
if (plan.PlanetIndex is int planetIndex && if (plan.PlanetIndex is int planetIndex &&
graph.LagrangeNodesByPlanetIndex.TryGetValue(planetIndex, out var lagrangeNodes)) graph.LagrangeNodesByPlanetIndex.TryGetValue(planetIndex, out var lagrangeNodes))
{ {
var designation = ResolveLagrangeDesignation(plan.LagrangeSide); 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 }) if (plan.Position is { Length: 3 })
{ {
var targetPosition = NormalizeScenarioPoint(system, plan.Position); var targetPosition = NormalizeScenarioPoint(system, plan.Position);
var preferredNode = existingNodes var preferredCelestial = existingCelestials
.Where((node) => node.SystemId == system.Definition.Id && node.Kind == SpatialNodeKind.LagrangePoint) .Where((c) => c.SystemId == system.Definition.Id && c.Kind == SpatialNodeKind.LagrangePoint)
.OrderBy((node) => node.Position.DistanceTo(targetPosition)) .OrderBy((c) => c.Position.DistanceTo(targetPosition))
.FirstOrDefault() .FirstOrDefault()
?? existingNodes ?? existingCelestials
.Where((node) => node.SystemId == system.Definition.Id) .Where((c) => c.SystemId == system.Definition.Id)
.OrderBy((node) => node.Position.DistanceTo(targetPosition)) .OrderBy((c) => c.Position.DistanceTo(targetPosition))
.First(); .First();
return new StationPlacement(preferredNode, preferredNode.Position); return new StationPlacement(preferredCelestial, preferredCelestial.Position);
} }
var fallbackNode = graph.Nodes var fallbackCelestial = graph.Celestials
.FirstOrDefault((node) => node.Kind == SpatialNodeKind.LagrangePoint && string.IsNullOrEmpty(node.OccupyingStructureId)) .FirstOrDefault((c) => c.Kind == SpatialNodeKind.LagrangePoint && string.IsNullOrEmpty(c.OccupyingStructureId))
?? graph.Nodes.First((node) => node.Kind == SpatialNodeKind.Planet); ?? graph.Celestials.First((c) => c.Kind == SpatialNodeKind.Planet);
return new StationPlacement(fallbackNode, fallbackNode.Position); return new StationPlacement(fallbackCelestial, fallbackCelestial.Position);
} }
private static string ResolveLagrangeDesignation(int? lagrangeSide) => lagrangeSide switch private static string ResolveLagrangeDesignation(int? lagrangeSide) => lagrangeSide switch
@@ -204,7 +190,7 @@ public sealed partial class ScenarioLoader
_ => "L1", _ => "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) 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) if (definition.AnchorMoonIndex is int moonIndex && moonIndex >= 0)
{ {
var moonNodeId = $"node-{graph.SystemId}-planet-{planetIndex + 1}-moon-{moonIndex + 1}"; 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}"; 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 verticalOffset = MathF.Sin(DegreesToRadians(definition.InclinationDegrees)) * MathF.Min(definition.RadiusOffset * 0.04f, 25000f);
var offset = new Vector3( var offset = new Vector3(
@@ -229,12 +215,12 @@ public sealed partial class ScenarioLoader
verticalOffset, verticalOffset,
MathF.Sin(definition.Angle) * definition.RadiusOffset); 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 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) 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)); return Add(planetPosition, new Vector3(MathF.Cos(angle) * orbitRadius, 0f, MathF.Sin(angle) * orbitRadius));
} }
private static ShipSpatialStateRuntime CreateInitialShipSpatialState(string systemId, Vector3 position, IReadOnlyCollection<NodeRuntime> nodes) private static ShipSpatialStateRuntime CreateInitialShipSpatialState(string systemId, Vector3 position, IReadOnlyCollection<CelestialRuntime> celestials)
{ {
var nearestNode = nodes var nearestCelestial = celestials
.Where((node) => node.SystemId == systemId) .Where((c) => c.SystemId == systemId)
.OrderBy((node) => node.Position.DistanceTo(position)) .OrderBy((c) => c.Position.DistanceTo(position))
.FirstOrDefault(); .FirstOrDefault();
return new ShipSpatialStateRuntime return new ShipSpatialStateRuntime
{ {
CurrentSystemId = systemId, CurrentSystemId = systemId,
SpaceLayer = SpaceLayerKinds.LocalSpace, SpaceLayer = SpaceLayerKinds.LocalSpace,
CurrentNodeId = nearestNode?.Id, CurrentCelestialId = nearestCelestial?.Id,
CurrentBubbleId = nearestNode?.BubbleId,
LocalPosition = position, LocalPosition = position,
SystemPosition = position, SystemPosition = position,
MovementRegime = MovementRegimeKinds.LocalFlight, MovementRegime = MovementRegimeKinds.LocalFlight,
@@ -273,11 +258,10 @@ public sealed partial class ScenarioLoader
private sealed record SystemSpatialGraph( private sealed record SystemSpatialGraph(
string SystemId, string SystemId,
List<NodeRuntime> Nodes, List<CelestialRuntime> Celestials,
List<LocalBubbleRuntime> Bubbles, Dictionary<int, Dictionary<string, CelestialRuntime>> LagrangeNodesByPlanetIndex);
Dictionary<int, Dictionary<string, NodeRuntime>> LagrangeNodesByPlanetIndex);
private sealed record LagrangePointPlacement(string Designation, Vector3 Position); private sealed record LagrangePointPlacement(string Designation, Vector3 Position);
private sealed record StationPlacement(NodeRuntime AnchorNode, Vector3 Position); private sealed record StationPlacement(CelestialRuntime AnchorCelestial, Vector3 Position);
} }

View File

@@ -13,10 +13,7 @@ public sealed partial class ScenarioLoader
private const float MinimumShipyardStock = 0f; private const float MinimumShipyardStock = 0f;
private const float MinimumSystemSeparation = 3.2f; private const float MinimumSystemSeparation = 3.2f;
private const float StarBubbleRadiusPadding = 40f; private const float StarBubbleRadiusPadding = 40f;
private const float PlanetBubbleRadiusPadding = 80f; private const float LocalSpaceRadius = 10_000f;
private const float MoonBubbleRadiusPadding = 40f;
private const float LagrangeBubbleRadius = 150f;
private const float ResourceBubbleRadius = 120f;
private static readonly string[] GeneratedSystemNames = private static readonly string[] GeneratedSystemNames =
[ [
"Aquila Verge", "Aquila Verge",
@@ -121,14 +118,12 @@ public sealed partial class ScenarioLoader
(system) => BuildSystemSpatialGraph(system), (system) => BuildSystemSpatialGraph(system),
StringComparer.Ordinal); StringComparer.Ordinal);
var celestials = new List<CelestialRuntime>();
var nodes = new List<ResourceNodeRuntime>(); var nodes = new List<ResourceNodeRuntime>();
var spatialNodes = new List<NodeRuntime>();
var localBubbles = new List<LocalBubbleRuntime>();
var nodeIdCounter = 0; var nodeIdCounter = 0;
foreach (var graph in systemGraphs.Values) foreach (var graph in systemGraphs.Values)
{ {
spatialNodes.AddRange(graph.Nodes); celestials.AddRange(graph.Celestials);
localBubbles.AddRange(graph.Bubbles);
} }
foreach (var system in systemRuntimes) foreach (var system in systemRuntimes)
@@ -136,15 +131,15 @@ public sealed partial class ScenarioLoader
var systemGraph = systemGraphs[system.Definition.Id]; var systemGraph = systemGraphs[system.Definition.Id];
foreach (var node in system.Definition.ResourceNodes) foreach (var node in system.Definition.ResourceNodes)
{ {
var anchorNode = ResolveResourceNodeAnchor(systemGraph, node); var anchorCelestial = ResolveResourceNodeAnchor(systemGraph, node);
var resourceNode = new ResourceNodeRuntime var resourceNode = new ResourceNodeRuntime
{ {
Id = $"node-{++nodeIdCounter}", Id = $"node-{++nodeIdCounter}",
SystemId = system.Definition.Id, SystemId = system.Definition.Id,
Position = ComputeResourceNodePosition(anchorNode, node, balance.YPlane), Position = ComputeResourceNodePosition(anchorCelestial, node, balance.YPlane),
SourceKind = node.SourceKind, SourceKind = node.SourceKind,
ItemId = node.ItemId, ItemId = node.ItemId,
AnchorNodeId = anchorNode?.Id, CelestialId = anchorCelestial?.Id,
OrbitRadius = node.RadiusOffset, OrbitRadius = node.RadiusOffset,
OrbitPhase = node.Angle, OrbitPhase = node.Angle,
OrbitInclination = DegreesToRadians(node.InclinationDegrees), OrbitInclination = DegreesToRadians(node.InclinationDegrees),
@@ -153,23 +148,6 @@ public sealed partial class ScenarioLoader
}; };
nodes.Add(resourceNode); 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; 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 var station = new StationRuntime
{ {
Id = $"station-{++stationIdCounter}", Id = $"station-{++stationIdCounter}",
@@ -193,31 +171,9 @@ public sealed partial class ScenarioLoader
FactionId = plan.FactionId ?? DefaultFactionId, FactionId = plan.FactionId ?? DefaultFactionId,
}; };
var stationNodeId = $"node-{station.Id}"; station.CelestialId = placement.AnchorCelestial.Id;
var stationBubbleId = $"bubble-{station.Id}";
station.NodeId = stationNodeId;
station.BubbleId = stationBubbleId;
station.AnchorNodeId = placement.AnchorNode.Id;
stations.Add(station); stations.Add(station);
spatialNodes.Add(new NodeRuntime placement.AnchorCelestial.OccupyingStructureId = station.Id;
{
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;
var startingModules = plan.StartingModules.Count > 0 var startingModules = plan.StartingModules.Count > 0
? plan.StartingModules ? plan.StartingModules
@@ -274,7 +230,7 @@ public sealed partial class ScenarioLoader
FactionId = formation.FactionId ?? DefaultFactionId, FactionId = formation.FactionId ?? DefaultFactionId,
Position = position, Position = position,
TargetPosition = position, TargetPosition = position,
SpatialState = CreateInitialShipSpatialState(formation.SystemId, position, spatialNodes), SpatialState = CreateInitialShipSpatialState(formation.SystemId, position, celestials),
DefaultBehavior = CreateBehavior(definition, formation.SystemId, scenario, patrolRoutes, refinery), DefaultBehavior = CreateBehavior(definition, formation.SystemId, scenario, patrolRoutes, refinery),
ControllerTask = new ControllerTaskRuntime { Kind = ControllerTaskKind.Idle, Threshold = balance.ArrivalThreshold, Status = WorkStatus.Pending }, ControllerTask = new ControllerTaskRuntime { Kind = ControllerTaskKind.Idle, Threshold = balance.ArrivalThreshold, Status = WorkStatus.Pending },
Health = definition.MaxHealth, Health = definition.MaxHealth,
@@ -287,8 +243,8 @@ public sealed partial class ScenarioLoader
var policies = CreatePolicies(factions); var policies = CreatePolicies(factions);
var commanders = CreateCommanders(factions, stations, shipsRuntime); var commanders = CreateCommanders(factions, stations, shipsRuntime);
var now = DateTimeOffset.UtcNow; var now = DateTimeOffset.UtcNow;
var claims = CreateClaims(stations, spatialNodes, now); var claims = CreateClaims(stations, celestials, now);
var (constructionSites, marketOrders) = CreateConstructionSites(stations, claims, spatialNodes, moduleRecipeDefinitions); var (constructionSites, marketOrders) = CreateConstructionSites(stations, claims, moduleRecipeDefinitions);
return new SimulationWorld return new SimulationWorld
{ {
@@ -296,9 +252,8 @@ public sealed partial class ScenarioLoader
Seed = WorldSeed, Seed = WorldSeed,
Balance = balance, Balance = balance,
Systems = systemRuntimes, Systems = systemRuntimes,
Celestials = celestials,
Nodes = nodes, Nodes = nodes,
SpatialNodes = spatialNodes,
LocalBubbles = localBubbles,
Stations = stations, Stations = stations,
Ships = shipsRuntime, Ships = shipsRuntime,
Factions = factions, Factions = factions,

View File

@@ -51,8 +51,9 @@ public sealed partial class SimulationEngine
return "none"; return "none";
} }
var targetPosition = task.TargetPosition.Value; // Resolve live position each frame — entities like stations orbit celestials and move every tick
var targetNode = ResolveTravelTargetNode(world, task, targetPosition); var targetPosition = ResolveCurrentTargetPosition(world, task);
var targetCelestial = ResolveTravelTargetCelestial(world, task, targetPosition);
ship.TargetPosition = targetPosition; ship.TargetPosition = targetPosition;
if (ship.SystemId != task.TargetSystemId) if (ship.SystemId != task.TargetSystemId)
@@ -63,63 +64,83 @@ public sealed partial class SimulationEngine
return "none"; return "none";
} }
var destinationEntryNode = ResolveSystemEntryNode(world, task.TargetSystemId); var destinationEntryCelestial = ResolveSystemEntryCelestial(world, task.TargetSystemId);
var destinationEntryPosition = destinationEntryNode?.Position ?? Vector3.Zero; var destinationEntryPosition = destinationEntryCelestial?.Position ?? Vector3.Zero;
return UpdateFtlTransit(ship, world, deltaSeconds, task.TargetSystemId, destinationEntryPosition, destinationEntryNode); return UpdateFtlTransit(ship, world, deltaSeconds, task.TargetSystemId, destinationEntryPosition, destinationEntryCelestial);
} }
var currentNode = ResolveCurrentNode(world, ship); var currentCelestial = ResolveCurrentCelestial(world, ship);
if (targetNode is not null && currentNode is not null && !string.Equals(currentNode.Id, targetNode.Id, StringComparison.Ordinal)) if (targetCelestial is not null && currentCelestial is not null && !string.Equals(currentCelestial.Id, targetCelestial.Id, StringComparison.Ordinal))
{ {
if (!HasShipCapabilities(ship.Definition, "warp")) 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)) if (!string.IsNullOrWhiteSpace(task.TargetEntityId))
{ {
var station = world.Stations.FirstOrDefault(candidate => candidate.Id == 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); var celestial = world.Celestials.FirstOrDefault(candidate => candidate.Id == task.TargetEntityId);
if (node is not null) 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) .Where(candidate => task.TargetSystemId is null || candidate.SystemId == task.TargetSystemId)
.OrderBy(candidate => candidate.Position.DistanceTo(targetPosition)) .OrderBy(candidate => candidate.Position.DistanceTo(targetPosition))
.FirstOrDefault(); .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) .Where(candidate => candidate.SystemId == ship.SystemId)
.OrderBy(candidate => candidate.Position.DistanceTo(ship.Position)) .OrderBy(candidate => candidate.Position.DistanceTo(ship.Position))
.FirstOrDefault(); .FirstOrDefault();
} }
private static NodeRuntime? ResolveSystemEntryNode(SimulationWorld world, string systemId) => private static CelestialRuntime? ResolveSystemEntryCelestial(SimulationWorld world, string systemId) =>
world.SpatialNodes.FirstOrDefault(candidate => world.Celestials.FirstOrDefault(candidate =>
candidate.SystemId == systemId && candidate.SystemId == systemId &&
candidate.Kind == SpatialNodeKind.Star); candidate.Kind == SpatialNodeKind.Star);
@@ -129,14 +150,14 @@ public sealed partial class SimulationEngine
float deltaSeconds, float deltaSeconds,
string targetSystemId, string targetSystemId,
Vector3 targetPosition, Vector3 targetPosition,
NodeRuntime? targetNode, CelestialRuntime? targetCelestial,
float threshold) float threshold)
{ {
var distance = ship.Position.DistanceTo(targetPosition); var distance = ship.Position.DistanceTo(targetPosition);
ship.SpatialState.SpaceLayer = SpaceLayerKinds.LocalSpace; ship.SpatialState.SpaceLayer = SpaceLayerKinds.LocalSpace;
ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight; ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight;
ship.SpatialState.Transit = null; ship.SpatialState.Transit = null;
ship.SpatialState.DestinationNodeId = targetNode?.Id; ship.SpatialState.DestinationNodeId = targetCelestial?.Id;
if (distance <= threshold) if (distance <= threshold)
{ {
@@ -144,8 +165,7 @@ public sealed partial class SimulationEngine
ship.Position = targetPosition; ship.Position = targetPosition;
ship.TargetPosition = ship.Position; ship.TargetPosition = ship.Position;
ship.SystemId = targetSystemId; ship.SystemId = targetSystemId;
ship.SpatialState.CurrentNodeId = targetNode?.Id; ship.SpatialState.CurrentCelestialId = targetCelestial?.Id;
ship.SpatialState.CurrentBubbleId = targetNode?.BubbleId;
ship.State = ShipState.Arriving; ship.State = ShipState.Arriving;
return "arrived"; return "arrived";
} }
@@ -156,16 +176,16 @@ public sealed partial class SimulationEngine
return "none"; 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; 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 transit = new ShipTransitRuntime
{ {
Regime = MovementRegimeKinds.Warp, Regime = MovementRegimeKinds.Warp,
OriginNodeId = ship.SpatialState.CurrentNodeId, OriginNodeId = ship.SpatialState.CurrentCelestialId,
DestinationNodeId = targetNode.Id, DestinationNodeId = targetCelestial.Id,
StartedAtUtc = world.GeneratedAtUtc, StartedAtUtc = world.GeneratedAtUtc,
}; };
ship.SpatialState.Transit = transit; ship.SpatialState.Transit = transit;
@@ -173,9 +193,8 @@ public sealed partial class SimulationEngine
ship.SpatialState.SpaceLayer = SpaceLayerKinds.SystemSpace; ship.SpatialState.SpaceLayer = SpaceLayerKinds.SystemSpace;
ship.SpatialState.MovementRegime = MovementRegimeKinds.Warp; ship.SpatialState.MovementRegime = MovementRegimeKinds.Warp;
ship.SpatialState.CurrentNodeId = null; ship.SpatialState.CurrentCelestialId = null;
ship.SpatialState.CurrentBubbleId = null; ship.SpatialState.DestinationNodeId = targetCelestial.Id;
ship.SpatialState.DestinationNodeId = targetNode.Id;
var spoolDuration = MathF.Max(0.4f, ship.Definition.SpoolTime * 0.5f); var spoolDuration = MathF.Max(0.4f, ship.Definition.SpoolTime * 0.5f);
if (ship.State != ShipState.Warping) if (ship.State != ShipState.Warping)
@@ -196,24 +215,24 @@ public sealed partial class SimulationEngine
var totalDistance = MathF.Max(0.001f, transit.OriginNodeId is null var totalDistance = MathF.Max(0.001f, transit.OriginNodeId is null
? ship.Position.DistanceTo(targetPosition) ? 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); ship.Position = ship.Position.MoveToward(targetPosition, GetWarpTravelSpeed(ship) * deltaSeconds);
transit.Progress = MathF.Min(1f, 1f - (ship.Position.DistanceTo(targetPosition) / totalDistance)); transit.Progress = MathF.Min(1f, 1f - (ship.Position.DistanceTo(targetPosition) / totalDistance));
return ship.Position.DistanceTo(targetPosition) <= 18f return ship.Position.DistanceTo(targetPosition) <= 18f
? CompleteTransitArrival(ship, targetNode.SystemId, targetPosition, targetNode) ? CompleteTransitArrival(ship, targetCelestial.SystemId, targetPosition, targetCelestial)
: "none"; : "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; var transit = ship.SpatialState.Transit;
if (transit is null || transit.Regime != MovementRegimeKinds.FtlTransit || transit.DestinationNodeId != destinationNodeId) if (transit is null || transit.Regime != MovementRegimeKinds.FtlTransit || transit.DestinationNodeId != destinationNodeId)
{ {
transit = new ShipTransitRuntime transit = new ShipTransitRuntime
{ {
Regime = MovementRegimeKinds.FtlTransit, Regime = MovementRegimeKinds.FtlTransit,
OriginNodeId = ship.SpatialState.CurrentNodeId, OriginNodeId = ship.SpatialState.CurrentCelestialId,
DestinationNodeId = destinationNodeId, DestinationNodeId = destinationNodeId,
StartedAtUtc = world.GeneratedAtUtc, StartedAtUtc = world.GeneratedAtUtc,
}; };
@@ -222,8 +241,7 @@ public sealed partial class SimulationEngine
ship.SpatialState.SpaceLayer = SpaceLayerKinds.GalaxySpace; ship.SpatialState.SpaceLayer = SpaceLayerKinds.GalaxySpace;
ship.SpatialState.MovementRegime = MovementRegimeKinds.FtlTransit; ship.SpatialState.MovementRegime = MovementRegimeKinds.FtlTransit;
ship.SpatialState.CurrentNodeId = null; ship.SpatialState.CurrentCelestialId = null;
ship.SpatialState.CurrentBubbleId = null;
ship.SpatialState.DestinationNodeId = destinationNodeId; ship.SpatialState.DestinationNodeId = destinationNodeId;
if (ship.State != ShipState.Ftl) if (ship.State != ShipState.Ftl)
@@ -247,11 +265,11 @@ public sealed partial class SimulationEngine
var totalDistance = MathF.Max(0.001f, originSystemPosition.DistanceTo(destinationSystemPosition)); var totalDistance = MathF.Max(0.001f, originSystemPosition.DistanceTo(destinationSystemPosition));
transit.Progress = MathF.Min(1f, transit.Progress + ((ship.Definition.FtlSpeed * deltaSeconds) / totalDistance)); transit.Progress = MathF.Min(1f, transit.Progress + ((ship.Definition.FtlSpeed * deltaSeconds) / totalDistance));
return transit.Progress >= 0.999f return transit.Progress >= 0.999f
? CompleteSystemEntryArrival(ship, targetSystemId, targetPosition, targetNode) ? CompleteSystemEntryArrival(ship, targetSystemId, targetPosition, targetCelestial)
: "none"; : "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.ActionTimer = 0f;
ship.Position = targetPosition; ship.Position = targetPosition;
@@ -260,14 +278,13 @@ public sealed partial class SimulationEngine
ship.SpatialState.Transit = null; ship.SpatialState.Transit = null;
ship.SpatialState.SpaceLayer = SpaceLayerKinds.LocalSpace; ship.SpatialState.SpaceLayer = SpaceLayerKinds.LocalSpace;
ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight; ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight;
ship.SpatialState.CurrentNodeId = targetNode?.Id; ship.SpatialState.CurrentCelestialId = targetCelestial?.Id;
ship.SpatialState.CurrentBubbleId = targetNode?.BubbleId; ship.SpatialState.DestinationNodeId = targetCelestial?.Id;
ship.SpatialState.DestinationNodeId = targetNode?.Id;
ship.State = ShipState.Arriving; ship.State = ShipState.Arriving;
return "arrived"; 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.ActionTimer = 0f;
ship.Position = targetPosition; ship.Position = targetPosition;
@@ -276,9 +293,8 @@ public sealed partial class SimulationEngine
ship.SpatialState.Transit = null; ship.SpatialState.Transit = null;
ship.SpatialState.SpaceLayer = SpaceLayerKinds.LocalSpace; ship.SpatialState.SpaceLayer = SpaceLayerKinds.LocalSpace;
ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight; ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight;
ship.SpatialState.CurrentNodeId = targetNode?.Id; ship.SpatialState.CurrentCelestialId = targetCelestial?.Id;
ship.SpatialState.CurrentBubbleId = targetNode?.BubbleId; ship.SpatialState.DestinationNodeId = targetCelestial?.Id;
ship.SpatialState.DestinationNodeId = targetNode?.Id;
ship.State = ShipState.Arriving; ship.State = ShipState.Arriving;
return "none"; return "none";
} }

View File

@@ -175,12 +175,12 @@ public sealed partial class SimulationEngine
private void UpdateOrbitalState(SimulationWorld world) private void UpdateOrbitalState(SimulationWorld world)
{ {
var worldTimeSeconds = (float)world.OrbitalTimeSeconds; 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) foreach (var system in world.Systems)
{ {
var starNodeId = $"node-{system.Definition.Id}-star"; 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; starNode.Position = Vector3.Zero;
} }
@@ -189,7 +189,7 @@ public sealed partial class SimulationEngine
{ {
var planet = system.Definition.Planets[planetIndex]; var planet = system.Definition.Planets[planetIndex];
var planetNodeId = $"node-{system.Definition.Id}-planet-{planetIndex + 1}"; var planetNodeId = $"node-{system.Definition.Id}-planet-{planetIndex + 1}";
if (!spatialNodesById.TryGetValue(planetNodeId, out var planetNode)) if (!celestialsById.TryGetValue(planetNodeId, out var planetNode))
{ {
continue; continue;
} }
@@ -200,7 +200,7 @@ public sealed partial class SimulationEngine
foreach (var lagrange in EnumeratePlanetLagrangePoints(planetPosition, planet)) foreach (var lagrange in EnumeratePlanetLagrangePoints(planetPosition, planet))
{ {
var lagrangeId = $"node-{system.Definition.Id}-planet-{planetIndex + 1}-{lagrange.Designation.ToLowerInvariant()}"; 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; lagrangeNode.Position = lagrange.Position;
} }
@@ -209,7 +209,7 @@ public sealed partial class SimulationEngine
for (var moonIndex = 0; moonIndex < planet.MoonCount; moonIndex += 1) for (var moonIndex = 0; moonIndex < planet.MoonCount; moonIndex += 1)
{ {
var moonId = $"node-{system.Definition.Id}-planet-{planetIndex + 1}-moon-{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; continue;
} }
@@ -221,30 +221,22 @@ public sealed partial class SimulationEngine
foreach (var station in world.Stations) 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; continue;
} }
station.Position = anchorNode.Position; station.Position = anchorCelestial.Position;
if (station.NodeId is not null && spatialNodesById.TryGetValue(station.NodeId, out var stationNode))
{
stationNode.Position = station.Position;
}
} }
foreach (var node in world.Nodes) 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; continue;
} }
node.Position = Add(anchorNode.Position, ComputeResourceNodeOffset(node, worldTimeSeconds)); node.Position = Add(anchorCelestial.Position, ComputeResourceNodeOffset(node, worldTimeSeconds));
if (spatialNodesById.TryGetValue(node.Id, out var resourceNode))
{
resourceNode.Position = node.Position;
}
} }
foreach (var ship in world.Ships.Where(ship => ship.DockedStationId is not null)) 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) private static void SyncSpatialState(SimulationWorld world)
{ {
foreach (var bubble in world.LocalBubbles)
{
bubble.OccupantShipIds.Clear();
}
foreach (var ship in world.Ships) foreach (var ship in world.Ships)
{ {
ship.SpatialState.CurrentSystemId = ship.SystemId; ship.SpatialState.CurrentSystemId = ship.SystemId;
@@ -275,25 +262,17 @@ public sealed partial class SimulationEngine
ship.SpatialState.SystemPosition = ship.Position; ship.SpatialState.SystemPosition = ship.Position;
if (ship.SpatialState.Transit is not null) if (ship.SpatialState.Transit is not null)
{ {
ship.SpatialState.CurrentNodeId = null; ship.SpatialState.CurrentCelestialId = null;
ship.SpatialState.CurrentBubbleId = null;
continue; continue;
} }
ship.SpatialState.SpaceLayer = SpaceLayerKinds.LocalSpace; ship.SpatialState.SpaceLayer = SpaceLayerKinds.LocalSpace;
ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight; ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight;
var nearestNode = world.SpatialNodes var nearestCelestial = world.Celestials
.Where(candidate => candidate.SystemId == ship.SystemId) .Where(candidate => candidate.SystemId == ship.SystemId)
.OrderBy(candidate => candidate.Position.DistanceTo(ship.Position)) .OrderBy(candidate => candidate.Position.DistanceTo(ship.Position))
.FirstOrDefault(); .FirstOrDefault();
ship.SpatialState.CurrentNodeId = nearestNode?.Id; ship.SpatialState.CurrentCelestialId = nearestCelestial?.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);
}
if (ship.DockedStationId is null) if (ship.DockedStationId is null)
{ {
@@ -301,15 +280,10 @@ public sealed partial class SimulationEngine
} }
var station = world.Stations.FirstOrDefault(candidate => candidate.Id == ship.DockedStationId); 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);
} }
} }

View File

@@ -39,29 +39,20 @@ public sealed partial class SimulationEngine
planet.Size, planet.Size,
planet.Color, planet.Color,
planet.HasRing)).ToList())).ToList(), planet.HasRing)).ToList())).ToList(),
world.SpatialNodes.Select(ToSpatialNodeDelta).Select(node => new SpatialNodeSnapshot( world.Celestials.Select(ToCelestialDelta).Select(c => new CelestialSnapshot(
node.Id, c.Id,
node.SystemId, c.SystemId,
node.Kind, c.Kind,
node.LocalPosition, c.OrbitalAnchor,
node.BubbleId, c.LocalSpaceRadius,
node.ParentNodeId, c.ParentNodeId,
node.OccupyingStructureId, c.OccupyingStructureId,
node.OrbitReferenceId)).ToList(), c.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.Nodes.Select(ToNodeDelta).Select(node => new ResourceNodeSnapshot( world.Nodes.Select(ToNodeDelta).Select(node => new ResourceNodeSnapshot(
node.Id, node.Id,
node.SystemId, node.SystemId,
node.LocalPosition, node.LocalPosition,
node.AnchorNodeId, node.CelestialId,
node.SourceKind, node.SourceKind,
node.OreRemaining, node.OreRemaining,
node.MaxOre, node.MaxOre,
@@ -72,9 +63,7 @@ public sealed partial class SimulationEngine
station.Category, station.Category,
station.SystemId, station.SystemId,
station.LocalPosition, station.LocalPosition,
station.NodeId, station.CelestialId,
station.BubbleId,
station.AnchorNodeId,
station.Color, station.Color,
station.DockedShips, station.DockedShips,
station.DockedShipIds, station.DockedShipIds,
@@ -95,8 +84,7 @@ public sealed partial class SimulationEngine
claim.Id, claim.Id,
claim.FactionId, claim.FactionId,
claim.SystemId, claim.SystemId,
claim.NodeId, claim.CelestialId,
claim.BubbleId,
claim.State, claim.State,
claim.Health, claim.Health,
claim.PlacedAtUtc, claim.PlacedAtUtc,
@@ -105,8 +93,7 @@ public sealed partial class SimulationEngine
site.Id, site.Id,
site.FactionId, site.FactionId,
site.SystemId, site.SystemId,
site.NodeId, site.CelestialId,
site.BubbleId,
site.TargetKind, site.TargetKind,
site.TargetDefinitionId, site.TargetDefinitionId,
site.BlueprintId, site.BlueprintId,
@@ -155,8 +142,7 @@ public sealed partial class SimulationEngine
ship.BehaviorPhase, ship.BehaviorPhase,
ship.ControllerTaskKind, ship.ControllerTaskKind,
ship.CommanderObjective, ship.CommanderObjective,
ship.NodeId, ship.CelestialId,
ship.BubbleId,
ship.DockedStationId, ship.DockedStationId,
ship.CommanderId, ship.CommanderId,
ship.PolicySetId, ship.PolicySetId,
@@ -191,14 +177,9 @@ public sealed partial class SimulationEngine
node.LastDeltaSignature = BuildNodeSignature(node); node.LastDeltaSignature = BuildNodeSignature(node);
} }
foreach (var node in world.SpatialNodes) foreach (var celestial in world.Celestials)
{ {
node.LastDeltaSignature = BuildSpatialNodeSignature(node); celestial.LastDeltaSignature = BuildCelestialSignature(celestial);
}
foreach (var bubble in world.LocalBubbles)
{
bubble.LastDeltaSignature = BuildLocalBubbleSignature(bubble);
} }
foreach (var station in world.Stations) foreach (var station in world.Stations)
@@ -255,37 +236,19 @@ public sealed partial class SimulationEngine
return deltas; return deltas;
} }
private static IReadOnlyList<SpatialNodeDelta> BuildSpatialNodeDeltas(SimulationWorld world) private static IReadOnlyList<CelestialDelta> BuildCelestialDeltas(SimulationWorld world)
{ {
var deltas = new List<SpatialNodeDelta>(); var deltas = new List<CelestialDelta>();
foreach (var node in world.SpatialNodes) foreach (var celestial in world.Celestials)
{ {
var signature = BuildSpatialNodeSignature(node); var signature = BuildCelestialSignature(celestial);
if (signature == node.LastDeltaSignature) if (signature == celestial.LastDeltaSignature)
{ {
continue; continue;
} }
node.LastDeltaSignature = signature; celestial.LastDeltaSignature = signature;
deltas.Add(ToSpatialNodeDelta(node)); deltas.Add(ToCelestialDelta(celestial));
}
return deltas;
}
private static IReadOnlyList<LocalBubbleDelta> BuildLocalBubbleDeltas(SimulationWorld world)
{
var deltas = new List<LocalBubbleDelta>();
foreach (var bubble in world.LocalBubbles)
{
var signature = BuildLocalBubbleSignature(bubble);
if (signature == bubble.LastDeltaSignature)
{
continue;
}
bubble.LastDeltaSignature = signature;
deltas.Add(ToLocalBubbleDelta(bubble));
} }
return deltas; return deltas;
@@ -424,22 +387,17 @@ public sealed partial class SimulationEngine
string.Equals(c.Kind, CommanderKind.Faction, StringComparison.Ordinal)); string.Equals(c.Kind, CommanderKind.Faction, StringComparison.Ordinal));
private static string BuildNodeSignature(ResourceNodeRuntime node) => 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) => private static string BuildCelestialSignature(CelestialRuntime celestial) =>
$"{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}"; $"{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 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 BuildStationSignature(SimulationWorld world, StationRuntime station) private static string BuildStationSignature(SimulationWorld world, StationRuntime station)
{ {
var processes = ToStationActionProgressSnapshots(world, station); var processes = ToStationActionProgressSnapshots(world, station);
return string.Join("|", return string.Join("|",
station.SystemId, station.SystemId,
station.NodeId ?? "none", station.CelestialId ?? "none",
station.BubbleId ?? "none",
station.AnchorNodeId ?? "none",
station.CommanderId ?? "none", station.CommanderId ?? "none",
station.PolicySetId ?? "none", station.PolicySetId ?? "none",
BuildInventorySignature(station.Inventory), BuildInventorySignature(station.Inventory),
@@ -458,10 +416,10 @@ public sealed partial class SimulationEngine
} }
private static string BuildClaimSignature(ClaimRuntime claim) => 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) => 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) => 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}"; $"{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.Kind,
ship.DefaultBehavior.Phase ?? "none", ship.DefaultBehavior.Phase ?? "none",
ship.ControllerTask.Kind.ToContractValue(), ship.ControllerTask.Kind.ToContractValue(),
ship.SpatialState.CurrentNodeId ?? "none", ship.SpatialState.CurrentCelestialId ?? "none",
ship.SpatialState.CurrentBubbleId ?? "none",
ship.DockedStationId ?? "none", ship.DockedStationId ?? "none",
ship.CommanderId ?? "none", ship.CommanderId ?? "none",
ship.PolicySetId ?? "none", ship.PolicySetId ?? "none",
ship.SpatialState.SpaceLayer, ship.SpatialState.SpaceLayer,
ship.SpatialState.CurrentNodeId ?? "none", ship.SpatialState.CurrentCelestialId ?? "none",
ship.SpatialState.CurrentBubbleId ?? "none",
ship.SpatialState.MovementRegime, ship.SpatialState.MovementRegime,
ship.SpatialState.DestinationNodeId ?? "none", ship.SpatialState.DestinationNodeId ?? "none",
ship.SpatialState.Transit?.Regime ?? "none", ship.SpatialState.Transit?.Regime ?? "none",
@@ -528,31 +484,21 @@ public sealed partial class SimulationEngine
node.Id, node.Id,
node.SystemId, node.SystemId,
ToDto(node.Position), ToDto(node.Position),
node.AnchorNodeId, node.CelestialId,
node.SourceKind, node.SourceKind,
node.OreRemaining, node.OreRemaining,
node.MaxOre, node.MaxOre,
node.ItemId); node.ItemId);
private static SpatialNodeDelta ToSpatialNodeDelta(NodeRuntime node) => new( private static CelestialDelta ToCelestialDelta(CelestialRuntime celestial) => new(
node.Id, celestial.Id,
node.SystemId, celestial.SystemId,
node.Kind.ToContractValue(), celestial.Kind.ToContractValue(),
ToDto(node.Position), ToDto(celestial.Position),
node.BubbleId, celestial.LocalSpaceRadius,
node.ParentNodeId, celestial.ParentNodeId,
node.OccupyingStructureId, celestial.OccupyingStructureId,
node.OrbitReferenceId); celestial.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 StationDelta ToStationDelta(SimulationWorld world, StationRuntime station) => new( private static StationDelta ToStationDelta(SimulationWorld world, StationRuntime station) => new(
station.Id, station.Id,
@@ -560,9 +506,7 @@ public sealed partial class SimulationEngine
station.Category, station.Category,
station.SystemId, station.SystemId,
ToDto(station.Position), ToDto(station.Position),
station.NodeId, station.CelestialId,
station.BubbleId,
station.AnchorNodeId,
station.Color, station.Color,
station.DockedShipIds.Count, station.DockedShipIds.Count,
station.DockedShipIds.OrderBy(id => id, StringComparer.Ordinal).ToList(), station.DockedShipIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
@@ -615,8 +559,7 @@ public sealed partial class SimulationEngine
claim.Id, claim.Id,
claim.FactionId, claim.FactionId,
claim.SystemId, claim.SystemId,
claim.NodeId, claim.CelestialId,
claim.BubbleId,
claim.State, claim.State,
claim.Health, claim.Health,
claim.PlacedAtUtc, claim.PlacedAtUtc,
@@ -626,8 +569,7 @@ public sealed partial class SimulationEngine
site.Id, site.Id,
site.FactionId, site.FactionId,
site.SystemId, site.SystemId,
site.NodeId, site.CelestialId,
site.BubbleId,
site.TargetKind, site.TargetKind,
site.TargetDefinitionId, site.TargetDefinitionId,
site.BlueprintId, site.BlueprintId,
@@ -696,8 +638,7 @@ public sealed partial class SimulationEngine
ship.DefaultBehavior.Phase, ship.DefaultBehavior.Phase,
ship.ControllerTask.Kind.ToContractValue(), ship.ControllerTask.Kind.ToContractValue(),
commander?.ActiveActionName, commander?.ActiveActionName,
ship.SpatialState.CurrentNodeId, ship.SpatialState.CurrentCelestialId,
ship.SpatialState.CurrentBubbleId,
ship.DockedStationId, ship.DockedStationId,
ship.CommanderId, ship.CommanderId,
ship.PolicySetId, ship.PolicySetId,
@@ -810,8 +751,7 @@ public sealed partial class SimulationEngine
private static ShipSpatialStateSnapshot ToShipSpatialStateSnapshot(ShipSpatialStateRuntime state) => new( private static ShipSpatialStateSnapshot ToShipSpatialStateSnapshot(ShipSpatialStateRuntime state) => new(
state.SpaceLayer, state.SpaceLayer,
state.CurrentSystemId, state.CurrentSystemId,
state.CurrentNodeId, state.CurrentCelestialId,
state.CurrentBubbleId,
state.LocalPosition is null ? null : ToDto(state.LocalPosition.Value), state.LocalPosition is null ? null : ToDto(state.LocalPosition.Value),
state.SystemPosition is null ? null : ToDto(state.SystemPosition.Value), state.SystemPosition is null ? null : ToDto(state.SystemPosition.Value),
state.MovementRegime, state.MovementRegime,

View File

@@ -305,7 +305,7 @@ public sealed partial class SimulationEngine
private static bool FactionControlsSystem(SimulationWorld world, string factionId, string systemId) 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.SystemId == systemId &&
node.Kind == SpatialNodeKind.LagrangePoint); node.Kind == SpatialNodeKind.LagrangePoint);
if (totalLagrangePoints == 0) if (totalLagrangePoints == 0)

View File

@@ -90,8 +90,7 @@ public sealed partial class SimulationEngine
{ {
CurrentSystemId = station.SystemId, CurrentSystemId = station.SystemId,
SpaceLayer = SpaceLayerKinds.LocalSpace, SpaceLayer = SpaceLayerKinds.LocalSpace,
CurrentNodeId = station.NodeId, CurrentCelestialId = station.CelestialId,
CurrentBubbleId = station.BubbleId,
LocalPosition = position, LocalPosition = position,
SystemPosition = position, SystemPosition = position,
MovementRegime = MovementRegimeKinds.LocalFlight, MovementRegime = MovementRegimeKinds.LocalFlight,

View File

@@ -70,8 +70,7 @@ public sealed partial class SimulationEngine
world.GeneratedAtUtc, world.GeneratedAtUtc,
false, false,
events, events,
BuildSpatialNodeDeltas(world), BuildCelestialDeltas(world),
BuildLocalBubbleDeltas(world),
BuildNodeDeltas(world), BuildNodeDeltas(world),
BuildStationDeltas(world), BuildStationDeltas(world),
BuildClaimDeltas(world), BuildClaimDeltas(world),

View File

@@ -116,7 +116,6 @@ public sealed class WorldService(
[], [],
[], [],
[], [],
[],
[]); []);
_history.Enqueue(resetDelta); _history.Enqueue(resetDelta);
@@ -132,8 +131,7 @@ public sealed class WorldService(
private static bool HasMeaningfulDelta(WorldDelta delta) => private static bool HasMeaningfulDelta(WorldDelta delta) =>
delta.RequiresSnapshotRefresh delta.RequiresSnapshotRefresh
|| delta.Events.Count > 0 || delta.Events.Count > 0
|| delta.SpatialNodes.Count > 0 || delta.Celestials.Count > 0
|| delta.LocalBubbles.Count > 0
|| delta.Nodes.Count > 0 || delta.Nodes.Count > 0
|| delta.Stations.Count > 0 || delta.Stations.Count > 0
|| delta.Claims.Count > 0 || delta.Claims.Count > 0
@@ -168,9 +166,9 @@ public sealed class WorldService(
} }
var systemFilter = scope.SystemId; 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 return delta with
@@ -179,8 +177,7 @@ public sealed class WorldService(
.Select((evt) => EnrichEventScope(evt)) .Select((evt) => EnrichEventScope(evt))
.Where((evt) => IsEventVisibleToScope(evt, scope, systemFilter)) .Where((evt) => IsEventVisibleToScope(evt, scope, systemFilter))
.ToList(), .ToList(),
SpatialNodes = delta.SpatialNodes.Where((node) => systemFilter is null || node.SystemId == systemFilter).ToList(), Celestials = delta.Celestials.Where((celestial) => systemFilter is null || celestial.SystemId == systemFilter).ToList(),
LocalBubbles = delta.LocalBubbles.Where((bubble) => systemFilter is null || bubble.SystemId == systemFilter).ToList(),
Nodes = delta.Nodes.Where((node) => systemFilter is null || node.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(), Stations = delta.Stations.Where((station) => systemFilter is null || station.SystemId == systemFilter).ToList(),
Claims = delta.Claims.Where((claim) => systemFilter is null || claim.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), "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), "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), "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), "celestial" => WithEntityScope(evt, "system", _world.Celestials.FirstOrDefault((c) => c.Id == evt.EntityId)?.SystemId),
"local-bubble" => WithEntityScope(evt, "local-bubble", _world.LocalBubbles.FirstOrDefault((bubble) => bubble.Id == evt.EntityId)?.Id),
"claim" => WithEntityScope(evt, "system", _world.Claims.FirstOrDefault((claim) => claim.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), "construction-site" => WithEntityScope(evt, "system", _world.ConstructionSites.FirstOrDefault((site) => site.Id == evt.EntityId)?.SystemId),
"market-order" => WithEntityScope(evt, "system", ResolveMarketOrderSystemId(evt.EntityId)), "market-order" => WithEntityScope(evt, "system", ResolveMarketOrderSystemId(evt.EntityId)),
@@ -226,8 +222,8 @@ public sealed class WorldService(
ScopeEntityId = scopeEntityId, ScopeEntityId = scopeEntityId,
}; };
private string? ResolveBubbleSystemId(string bubbleId) => private string? ResolveCelestialSystemId(string celestialId) =>
_world.LocalBubbles.FirstOrDefault((bubble) => bubble.Id == bubbleId)?.SystemId; _world.Celestials.FirstOrDefault((c) => c.Id == celestialId)?.SystemId;
private string? ResolveMarketOrderSystemId(string orderId) private string? ResolveMarketOrderSystemId(string orderId)
{ {
@@ -271,7 +267,7 @@ public sealed class WorldService(
{ {
"universe" => true, "universe" => true,
"system" => evt.ScopeKind == "universe" || evt.ScopeEntityId == systemFilter, "system" => evt.ScopeKind == "universe" || evt.ScopeEntityId == systemFilter,
"local-bubble" => evt.ScopeKind == "universe" || evt.ScopeEntityId == systemFilter, "local-celestial" => evt.ScopeKind == "universe" || evt.ScopeEntityId == systemFilter,
_ => true, _ => true,
}; };
} }