Complete universe model migration

This commit is contained in:
2026-04-07 14:16:59 -04:00
parent d0c6e30304
commit 6c92ab50c8
76 changed files with 2061 additions and 1072 deletions

View File

@@ -1,5 +1,7 @@
using static SpaceGame.Api.Stations.Simulation.InfrastructureSimulationService;
using SpaceGame.Api.Universe.Scenario;
namespace SpaceGame.Api.Universe.Simulation;
internal sealed class OrbitalStateUpdater
@@ -223,22 +225,47 @@ internal sealed class OrbitalStateUpdater
foreach (var station in world.Stations)
{
if (station.CelestialId is null || !celestialsById.TryGetValue(station.CelestialId, out var anchorCelestial))
if (station.AnchorId is not null && world.Anchors.Any(candidate => candidate.Id == station.AnchorId))
{
continue;
station.Position = Vector3.Zero;
}
station.Position = anchorCelestial.Position;
}
foreach (var node in world.Nodes)
{
if (node.CelestialId is null || !celestialsById.TryGetValue(node.CelestialId, out var anchorCelestial))
node.Position = ComputeResourceNodeOffset(node, worldTimeSeconds);
}
var nodeAnchorsById = world.Nodes.ToDictionary(node => node.AnchorId, StringComparer.Ordinal);
foreach (var anchor in world.Anchors)
{
if (string.Equals(anchor.SourceEntityKind, "resource-node", StringComparison.Ordinal))
{
if (nodeAnchorsById.TryGetValue(anchor.Id, out var node))
{
if (anchor.ParentAnchorId is not null && celestialsById.TryGetValue(anchor.ParentAnchorId, out var anchorCelestial))
{
anchor.Position = Add(anchorCelestial.Position, node.Position);
}
else
{
anchor.Position = node.Position;
}
anchor.LocalSpaceRadius = node.LocalSpaceRadius;
}
continue;
}
node.Position = Add(anchorCelestial.Position, ComputeResourceNodeOffset(node, worldTimeSeconds));
if (celestialsById.TryGetValue(anchor.Id, out var celestial))
{
anchor.Position = celestial.Position;
anchor.LocalSpaceRadius = celestial.LocalSpaceRadius;
anchor.ParentAnchorId = celestial.ParentAnchorId;
anchor.OccupyingStructureId = celestial.OccupyingStructureId;
anchor.OrbitReferenceId = celestial.OrbitReferenceId;
}
}
foreach (var ship in world.Ships.Where(ship => ship.DockedStationId is not null))
@@ -261,20 +288,29 @@ internal sealed class OrbitalStateUpdater
{
ship.SpatialState.CurrentSystemId = ship.SystemId;
ship.SpatialState.LocalPosition = ship.Position;
ship.SpatialState.SystemPosition = ship.Position;
if (ship.SpatialState.Transit is not null)
{
ship.SpatialState.CurrentCelestialId = null;
ship.SpatialState.CurrentAnchorId = null;
continue;
}
ship.SpatialState.SpaceLayer = SpaceLayerKind.LocalSpace;
ship.SpatialState.MovementRegime = MovementRegimeKind.LocalFlight;
var nearestCelestial = world.Celestials
.Where(candidate => candidate.SystemId == ship.SystemId)
.OrderBy(candidate => candidate.Position.DistanceTo(ship.Position))
.FirstOrDefault();
ship.SpatialState.CurrentCelestialId = nearestCelestial?.Id;
var currentAnchor = ship.SpatialState.CurrentAnchorId is not null
? world.Anchors.FirstOrDefault(candidate => candidate.Id == ship.SpatialState.CurrentAnchorId)
: null;
if (currentAnchor is null || !string.Equals(currentAnchor.SystemId, ship.SystemId, StringComparison.Ordinal))
{
currentAnchor = world.Anchors
.Where(candidate => candidate.SystemId == ship.SystemId)
.OrderBy(candidate => candidate.Position.DistanceTo(ship.Position))
.FirstOrDefault();
}
ship.SpatialState.CurrentAnchorId = currentAnchor?.Id;
ship.SpatialState.SystemPosition = currentAnchor is null
? ship.Position
: Add(currentAnchor.Position, ship.Position);
if (ship.DockedStationId is null)
{
@@ -282,9 +318,9 @@ internal sealed class OrbitalStateUpdater
}
var station = world.Stations.FirstOrDefault(candidate => candidate.Id == ship.DockedStationId);
if (station?.CelestialId is not null)
if (station is not null)
{
ship.SpatialState.CurrentCelestialId = station.CelestialId;
ship.SpatialState.CurrentAnchorId = station.AnchorId;
}
}
}

View File

@@ -315,6 +315,8 @@ public sealed class WorldService
string.Equals(candidate.FactionId, faction.Id, StringComparison.Ordinal)
&& string.Equals(candidate.SystemId, system.Definition.Id, StringComparison.Ordinal));
var defaultBehavior = CreateSpawnBehavior(request, definition, system.Definition.Id, homeStation);
var spatialState = SpatialBuilder.CreateInitialShipSpatialState(system.Definition.Id, spawnPosition, _world.Anchors);
var localPosition = spatialState.LocalPosition ?? Vector3.Zero;
var ship = new ShipRuntime
{
@@ -322,9 +324,9 @@ public sealed class WorldService
SystemId = system.Definition.Id,
Definition = definition,
FactionId = faction.Id,
Position = spawnPosition,
TargetPosition = spawnPosition,
SpatialState = SpatialBuilder.CreateInitialShipSpatialState(system.Definition.Id, spawnPosition, _world.Celestials),
Position = localPosition,
TargetPosition = localPosition,
SpatialState = spatialState,
DefaultBehavior = defaultBehavior,
Skills = ShipBootstrapPolicy.CreateSkills(definition),
Health = definition.Hull,
@@ -352,15 +354,18 @@ public sealed class WorldService
? $"{faction.Label} {ToTitleCaseToken(objective)} {CountFactionStationsInSystem(faction.Id, system.Definition.Id) + 1}"
: request.Label.Trim();
var stationId = $"station-{faction.Id}-{objective}-{Guid.NewGuid():N}".ToLowerInvariant();
var position = ResolveStationSpawnPosition(system.Definition.Id);
var requestedPosition = ResolveStationSpawnPosition(system.Definition.Id);
var anchor = ResolveNearestConstructibleAnchor(system.Definition.Id, requestedPosition)
?? throw new InvalidOperationException($"System '{system.Definition.Id}' does not have a valid constructible anchor for station spawning.");
var station = new StationRuntime
{
Id = stationId,
SystemId = system.Definition.Id,
AnchorId = anchor.Id,
Label = label,
Color = faction.Color,
Objective = objective,
Position = position,
Position = Vector3.Zero,
FactionId = faction.Id,
PolicySetId = faction.DefaultPolicySetId,
Health = 600f,
@@ -375,6 +380,7 @@ public sealed class WorldService
station.PopulationCapacity = GetStationSupportedPopulation(_world.ModuleDefinitions, station);
station.WorkforceRequired = GetStationRequiredWorkforce(_world.ModuleDefinitions, station);
_world.Stations.Add(station);
anchor.OccupyingStructureId = station.Id;
new GeopoliticalSimulationService().Update(_world, 0f, []);
PublishSnapshotRefreshUnsafe("spawn-station", $"Spawned station {station.Id}", "station", station.Id);
@@ -490,6 +496,7 @@ public sealed class WorldService
[],
[],
[],
[],
null);
_history.Enqueue(worldDelta);
@@ -526,6 +533,7 @@ public sealed class WorldService
[],
[],
[],
[],
null);
_history.Enqueue(worldDelta);
@@ -608,6 +616,8 @@ public sealed class WorldService
var shipId = $"ship-{playerFaction.Id}-{definition.Id}-{Guid.NewGuid():N}".ToLowerInvariant();
var spawnPosition = ResolveSpawnPosition(system.Definition.Id);
var defaultBehavior = CreateSpawnBehavior(request, definition, system.Definition.Id, null);
var spatialState = SpatialBuilder.CreateInitialShipSpatialState(system.Definition.Id, spawnPosition, _world.Anchors);
var localPosition = spatialState.LocalPosition ?? Vector3.Zero;
var ship = new ShipRuntime
{
@@ -615,9 +625,9 @@ public sealed class WorldService
SystemId = system.Definition.Id,
Definition = definition,
FactionId = playerFaction.Id,
Position = spawnPosition,
TargetPosition = spawnPosition,
SpatialState = SpatialBuilder.CreateInitialShipSpatialState(system.Definition.Id, spawnPosition, _world.Celestials),
Position = localPosition,
TargetPosition = localPosition,
SpatialState = spatialState,
DefaultBehavior = defaultBehavior,
Skills = ShipBootstrapPolicy.CreateSkills(definition),
Health = definition.Hull,
@@ -712,7 +722,7 @@ public sealed class WorldService
SourceStationId = request.SourceStationId,
DestinationStationId = request.DestinationStationId,
ItemId = request.ItemId,
NodeId = request.NodeId,
AnchorId = request.AnchorId,
ConstructionSiteId = request.ConstructionSiteId,
ModuleId = request.ModuleId,
WaitSeconds = MathF.Max(0f, request.WaitSeconds ?? 0f),
@@ -780,7 +790,7 @@ public sealed class WorldService
ship.DefaultBehavior.AreaSystemId = request.AreaSystemId;
ship.DefaultBehavior.TargetEntityId = request.TargetEntityId;
ship.DefaultBehavior.ItemId = request.ItemId;
ship.DefaultBehavior.PreferredNodeId = request.PreferredNodeId;
ship.DefaultBehavior.PreferredAnchorId = request.PreferredAnchorId;
ship.DefaultBehavior.PreferredConstructionSiteId = request.PreferredConstructionSiteId;
ship.DefaultBehavior.PreferredModuleId = request.PreferredModuleId;
ship.DefaultBehavior.TargetPosition = request.TargetPosition is null
@@ -807,7 +817,7 @@ public sealed class WorldService
SourceStationId = template.SourceStationId,
DestinationStationId = template.DestinationStationId,
ItemId = template.ItemId,
NodeId = template.NodeId,
AnchorId = template.AnchorId,
ConstructionSiteId = template.ConstructionSiteId,
ModuleId = template.ModuleId,
WaitSeconds = template.WaitSeconds ?? 0f,
@@ -905,6 +915,16 @@ public sealed class WorldService
return new Vector3(MathF.Cos(angle) * radius, 0f, MathF.Sin(angle) * radius);
}
private AnchorRuntime? ResolveNearestConstructibleAnchor(string systemId, Vector3 position) =>
_world.Anchors
.Where(candidate => string.Equals(candidate.SystemId, systemId, StringComparison.Ordinal))
.Where(candidate => SpatialBuilder.IsConstructibleAnchorKind(candidate.Kind))
.OrderBy(candidate => candidate.Position.DistanceTo(position))
.FirstOrDefault();
private string? ResolveNearestAnchorId(string systemId, Vector3 position) =>
ResolveNearestConstructibleAnchor(systemId, position)?.Id;
private IReadOnlyList<string> BuildStarterStationModules(string factionId, string objective)
{
var modules = new List<string>();
@@ -1079,9 +1099,9 @@ public sealed class WorldService
}
var systemFilter = scope.SystemId;
if (string.Equals(scope.ScopeKind, "local-celestial", StringComparison.OrdinalIgnoreCase) && systemFilter is null && scope.CelestialId is not null)
if (string.Equals(scope.ScopeKind, "local-anchor", StringComparison.OrdinalIgnoreCase) && systemFilter is null && scope.AnchorId is not null)
{
systemFilter = ResolveCelestialSystemId(scope.CelestialId);
systemFilter = ResolveAnchorSystemId(scope.AnchorId);
}
return delta with
@@ -1091,6 +1111,7 @@ public sealed class WorldService
.Where((evt) => IsEventVisibleToScope(evt, scope, systemFilter))
.ToList(),
Celestials = delta.Celestials.Where((celestial) => systemFilter is null || celestial.SystemId == systemFilter).ToList(),
Anchors = delta.Anchors.Where((anchor) => systemFilter is null || anchor.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(),
@@ -1136,8 +1157,8 @@ public sealed class WorldService
ScopeEntityId = scopeEntityId,
};
private string? ResolveCelestialSystemId(string celestialId) =>
_world.Celestials.FirstOrDefault((c) => c.Id == celestialId)?.SystemId;
private string? ResolveAnchorSystemId(string anchorId) =>
_world.Anchors.FirstOrDefault((anchor) => anchor.Id == anchorId)?.SystemId;
private string? ResolveMarketOrderSystemId(string orderId)
{
@@ -1181,7 +1202,7 @@ public sealed class WorldService
{
"universe" => true,
"system" => evt.ScopeKind == "universe" || evt.ScopeEntityId == systemFilter,
"local-celestial" => evt.ScopeKind == "universe" || evt.ScopeEntityId == systemFilter,
"local-anchor" => evt.ScopeKind == "universe" || evt.ScopeEntityId == systemFilter,
_ => true,
};
}