improvement on gm windows, ai

This commit is contained in:
2026-03-20 12:40:26 -04:00
parent ff078fe939
commit 3b56785f9a
39 changed files with 2594 additions and 358 deletions

View File

@@ -224,6 +224,23 @@ internal sealed class SpatialBuilder
private static CelestialRuntime? ResolveResourceNodeAnchor(SystemSpatialGraph graph, ResourceNodeDefinition definition)
{
if (!string.IsNullOrWhiteSpace(definition.AnchorReference))
{
var anchorId = definition.AnchorReference.ToLowerInvariant() switch
{
var reference when reference.StartsWith("star-", StringComparison.Ordinal)
=> $"node-{graph.SystemId}-{reference}",
var reference when reference.StartsWith("planet-", StringComparison.Ordinal)
=> $"node-{graph.SystemId}-{reference}",
_ => null,
};
if (anchorId is not null)
{
return graph.Celestials.FirstOrDefault(c => string.Equals(c.Id, anchorId, StringComparison.Ordinal));
}
}
if (definition.AnchorPlanetIndex is not int planetIndex || planetIndex < 0)
{
return null;

View File

@@ -9,7 +9,7 @@ internal sealed class SystemGenerationService
internal List<SolarSystemDefinition> InjectSpecialSystems(IReadOnlyList<SolarSystemDefinition> authoredSystems) =>
authoredSystems
.Select(CloneSystemDefinition)
.Select((system, index) => EnsureStrategicResourceCoverage(CloneSystemDefinition(system), index))
.ToList();
internal List<SolarSystemDefinition> ExpandSystems(
@@ -126,6 +126,7 @@ internal sealed class SystemGenerationService
.Select(node => new ResourceNodeDefinition
{
SourceKind = node.SourceKind,
AnchorReference = node.AnchorReference,
Angle = node.Angle,
RadiusOffset = node.RadiusOffset,
InclinationDegrees = node.InclinationDegrees,
@@ -137,7 +138,7 @@ internal sealed class SystemGenerationService
})
.ToList();
return new SolarSystemDefinition
return EnsureStrategicResourceCoverage(new SolarSystemDefinition
{
Id = id,
Label = label,
@@ -161,7 +162,7 @@ internal sealed class SystemGenerationService
},
ResourceNodes = resourceNodes,
Planets = planets,
};
}, generatedIndex + 1024);
}
private static SolarSystemDefinition CloneSystemDefinition(SolarSystemDefinition definition)
@@ -182,6 +183,7 @@ internal sealed class SystemGenerationService
ResourceNodes = definition.ResourceNodes.Select(node => new ResourceNodeDefinition
{
SourceKind = node.SourceKind,
AnchorReference = node.AnchorReference,
Angle = node.Angle,
RadiusOffset = node.RadiusOffset,
InclinationDegrees = node.InclinationDegrees,
@@ -223,6 +225,7 @@ internal sealed class SystemGenerationService
nodes.AddRange(template.ResourceNodes.Select(node => new ResourceNodeDefinition
{
SourceKind = node.SourceKind,
AnchorReference = node.AnchorReference,
Angle = node.Angle,
RadiusOffset = node.RadiusOffset,
InclinationDegrees = node.InclinationDegrees,
@@ -234,10 +237,30 @@ internal sealed class SystemGenerationService
}));
}
nodes.AddRange(BuildAsteroidBeltNodes(generatedIndex, planets));
return nodes;
}
private static SolarSystemDefinition EnsureStrategicResourceCoverage(SolarSystemDefinition system, int seed)
{
for (var index = 0; index < system.ResourceNodes.Count; index += 1)
{
system.ResourceNodes[index] = SanitizeResourceNode(system.ResourceNodes[index], system.Planets, seed, index);
}
var requiredItems = new[] { "ore", "silicon", "ice", "hydrogen", "helium", "methane" };
foreach (var itemId in requiredItems)
{
if (system.ResourceNodes.Any(node => string.Equals(node.ItemId, itemId, StringComparison.Ordinal)))
{
continue;
}
system.ResourceNodes.Add(BuildStrategicResourceNode(itemId, system.Planets, seed, system.ResourceNodes.Count));
}
return system;
}
private static List<Vector3> BuildGalaxyPositions(IReadOnlyCollection<Vector3> occupiedPositions, int count)
{
var allPositions = occupiedPositions.ToList();
@@ -303,25 +326,124 @@ internal sealed class SystemGenerationService
return $"gen-{ordinal}-{slug}";
}
private static IEnumerable<ResourceNodeDefinition> BuildAsteroidBeltNodes(int generatedIndex, IReadOnlyList<PlanetDefinition> planets)
private static ResourceNodeDefinition BuildStrategicResourceNode(
string itemId,
IReadOnlyList<PlanetDefinition> planets,
int seed,
int ordinal)
{
var nodeCount = 4 + (generatedIndex % 4);
var oreAmount = 1000f;
for (var index = 0; index < nodeCount; index += 1)
var anchorPlanetIndex = ResolveStrategicResourceAnchorPlanetIndex(itemId, planets);
return new ResourceNodeDefinition
{
yield return new ResourceNodeDefinition
SourceKind = "local-space",
AnchorReference = ResolveStrategicAnchorReference(itemId, planets, ordinal),
Angle = (MathF.PI * 2f * ((ordinal % 7) / 7f)) + Jitter(seed, 400 + ordinal, 0.35f),
RadiusOffset = 150000f + Jitter(seed, 460 + ordinal, 42000f),
InclinationDegrees = Jitter(seed, 520 + ordinal, 10f),
AnchorPlanetIndex = anchorPlanetIndex,
OreAmount = itemId switch
{
SourceKind = "asteroid-belt",
Angle = ((MathF.PI * 2f) / nodeCount) * index + Jitter(generatedIndex, 180 + index, 0.22f),
RadiusOffset = 120000f + Jitter(generatedIndex, 200 + index, 36000f),
InclinationDegrees = Jitter(generatedIndex, 280 + index, 12f),
AnchorPlanetIndex = ResolveAsteroidAnchorPlanetIndex(planets),
OreAmount = oreAmount,
ItemId = "ore",
ShardCount = 6 + (index % 4),
};
"ore" => 12000f,
"silicon" => 10000f,
"ice" => 9000f,
_ => 8000f,
},
ItemId = itemId,
ShardCount = itemId switch
{
"ore" or "silicon" or "ice" => 8,
_ => 6,
},
};
}
private static ResourceNodeDefinition SanitizeResourceNode(
ResourceNodeDefinition node,
IReadOnlyList<PlanetDefinition> planets,
int seed,
int ordinal)
{
node.SourceKind = "local-space";
node.AnchorReference ??= ResolveLegacyAnchorReference(node, planets, seed, ordinal);
return node;
}
private static string ResolveLegacyAnchorReference(
ResourceNodeDefinition node,
IReadOnlyList<PlanetDefinition> planets,
int seed,
int ordinal)
{
if (node.AnchorMoonIndex is int moonIndex && node.AnchorPlanetIndex is int planetIndex && planetIndex >= 0)
{
return $"planet-{planetIndex + 1}-moon-{moonIndex + 1}";
}
if (node.AnchorPlanetIndex is int anchoredPlanetIndex && anchoredPlanetIndex >= 0)
{
return $"planet-{anchoredPlanetIndex + 1}";
}
return ResolveStrategicAnchorReference(node.ItemId, planets, ordinal + seed);
}
private static string ResolveStrategicAnchorReference(string itemId, IReadOnlyList<PlanetDefinition> planets, int ordinal)
{
if (itemId is "hydrogen" or "helium" or "methane")
{
var gasGiantIndex = planets
.Select((planet, index) => (planet, index))
.FirstOrDefault(entry => entry.planet.PlanetType is "gas-giant" or "ice-giant")
.index;
return gasGiantIndex > 0 || (planets.Count > 0 && planets[0].PlanetType is "gas-giant" or "ice-giant")
? $"planet-{gasGiantIndex + 1}"
: "star-1";
}
if (itemId == "ice")
{
var moonAnchor = planets
.Select((planet, index) => (planet, index))
.FirstOrDefault(entry => entry.planet.Moons.Count > 0 && entry.planet.PlanetType is "ice" or "ice-giant" or "oceanic");
if (moonAnchor.planet is not null && moonAnchor.planet.Moons.Count > 0)
{
return $"planet-{moonAnchor.index + 1}-moon-1";
}
}
var anchorPlanetIndex = ResolveStrategicResourceAnchorPlanetIndex(itemId, planets);
var lagrange = (ordinal % 3) switch
{
0 => "l1",
1 => "l4",
_ => "l5",
};
return $"planet-{anchorPlanetIndex + 1}-{lagrange}";
}
private static int ResolveStrategicResourceAnchorPlanetIndex(string itemId, IReadOnlyList<PlanetDefinition> planets)
{
if (planets.Count == 0)
{
return 0;
}
bool MatchesPlanetType(PlanetDefinition planet) => itemId switch
{
"hydrogen" or "helium" or "methane" => planet.PlanetType is "gas-giant" or "ice-giant",
"ice" => planet.PlanetType is "ice" or "ice-giant" or "oceanic",
_ => planet.PlanetType is not "gas-giant" and not "ice-giant",
};
for (var index = 0; index < planets.Count; index += 1)
{
if (MatchesPlanetType(planets[index]))
{
return index;
}
}
return ResolveAsteroidAnchorPlanetIndex(planets);
}
private static int ResolveAsteroidAnchorPlanetIndex(IReadOnlyList<PlanetDefinition> planets)

View File

@@ -34,7 +34,8 @@ internal sealed class WorldBuilder(
systemsById,
spatialLayout.SystemGraphs,
spatialLayout.Celestials,
catalog.ModuleDefinitions);
catalog.ModuleDefinitions,
catalog.ItemDefinitions);
seedingService.InitializeStationStockpiles(stations);
var refinery = seedingService.SelectRefineryStation(stations, scenario);
@@ -106,7 +107,8 @@ internal sealed class WorldBuilder(
IReadOnlyDictionary<string, SystemRuntime> systemsById,
IReadOnlyDictionary<string, SystemSpatialGraph> systemGraphs,
IReadOnlyCollection<CelestialRuntime> celestials,
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions)
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
IReadOnlyDictionary<string, ItemDefinition> itemDefinitions)
{
var stations = new List<StationRuntime>();
var stationIdCounter = 0;
@@ -136,9 +138,7 @@ internal sealed class WorldBuilder(
stations.Add(station);
placement.AnchorCelestial.OccupyingStructureId = station.Id;
var startingModules = plan.StartingModules.Count > 0
? plan.StartingModules
: ["module_arg_dock_m_01_lowtech", "module_gen_prod_energycells_01", "module_arg_stor_solid_m_01", "module_arg_stor_liquid_m_01"];
var startingModules = BuildStartingModules(plan, moduleDefinitions, itemDefinitions);
foreach (var moduleId in startingModules)
{
@@ -149,6 +149,91 @@ internal sealed class WorldBuilder(
return stations;
}
private static IReadOnlyList<string> BuildStartingModules(
InitialStationDefinition plan,
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
IReadOnlyDictionary<string, ItemDefinition> itemDefinitions)
{
var startingModules = new List<string>(plan.StartingModules.Count > 0
? plan.StartingModules
: ["module_arg_dock_m_01_lowtech", "module_gen_prod_energycells_01", "module_arg_stor_container_m_01"]);
EnsureStartingModule(startingModules, "module_arg_dock_m_01_lowtech");
var objectiveModuleId = GetObjectiveStartingModuleId(plan.Objective);
if (!string.IsNullOrWhiteSpace(objectiveModuleId))
{
EnsureStartingModule(startingModules, objectiveModuleId);
if (!string.Equals(objectiveModuleId, "module_gen_prod_energycells_01", StringComparison.Ordinal))
{
EnsureStartingModule(startingModules, "module_gen_prod_energycells_01");
}
foreach (var storageModuleId in GetRequiredStartingStorageModules(objectiveModuleId, moduleDefinitions, itemDefinitions))
{
EnsureStartingModule(startingModules, storageModuleId);
}
}
return startingModules;
}
private static string? GetObjectiveStartingModuleId(string? objective) =>
StationSimulationService.NormalizeStationObjective(objective) switch
{
"power" => "module_gen_prod_energycells_01",
"refinery" => "module_gen_ref_ore_01",
"graphene" => "module_gen_prod_graphene_01",
"siliconwafers" => "module_gen_prod_siliconwafers_01",
"hullparts" => "module_gen_prod_hullparts_01",
"claytronics" => "module_gen_prod_claytronics_01",
"quantumtubes" => "module_gen_prod_quantumtubes_01",
"antimattercells" => "module_gen_prod_antimattercells_01",
"superfluidcoolant" => "module_gen_prod_superfluidcoolant_01",
"water" => "module_gen_prod_water_01",
_ => null,
};
private static IEnumerable<string> GetRequiredStartingStorageModules(
string moduleId,
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
IReadOnlyDictionary<string, ItemDefinition> itemDefinitions)
{
if (!moduleDefinitions.TryGetValue(moduleId, out var moduleDefinition))
{
yield break;
}
foreach (var wareId in moduleDefinition.Production
.SelectMany(production => production.Wares.Select(ware => ware.ItemId))
.Concat(moduleDefinition.Products)
.Distinct(StringComparer.Ordinal))
{
if (!itemDefinitions.TryGetValue(wareId, out var itemDefinition))
{
continue;
}
var storageModuleId = itemDefinition.CargoKind switch
{
"solid" => "module_arg_stor_solid_m_01",
"liquid" => "module_arg_stor_liquid_m_01",
_ => "module_arg_stor_container_m_01",
};
yield return storageModuleId;
}
}
private static void EnsureStartingModule(List<string> modules, string moduleId)
{
if (!modules.Contains(moduleId, StringComparer.Ordinal))
{
modules.Add(moduleId);
}
}
private static Dictionary<string, List<Vector3>> BuildPatrolRoutes(
ScenarioDefinition scenario,
IReadOnlyDictionary<string, SystemRuntime> systemsById)

View File

@@ -65,33 +65,6 @@ internal sealed class WorldSeedingService
foreach (var station in stations)
{
InitializeStationPopulation(station);
if (station.InstalledModules.Contains("module_gen_prod_energycells_01", StringComparer.Ordinal))
{
station.Inventory["energycells"] = MathF.Max(GetInventoryAmount(station.Inventory, "energycells"), 240f);
}
if (station.InstalledModules.Contains("module_gen_prod_refinedmetals_01", StringComparer.Ordinal))
{
station.Inventory["ore"] = MathF.Max(GetInventoryAmount(station.Inventory, "ore"), 220f);
}
if (station.InstalledModules.Contains("module_gen_prod_hullparts_01", StringComparer.Ordinal))
{
station.Inventory["refinedmetals"] = MathF.Max(GetInventoryAmount(station.Inventory, "refinedmetals"), 240f);
station.Inventory["graphene"] = MathF.Max(GetInventoryAmount(station.Inventory, "graphene"), 80f);
}
if (station.InstalledModules.Contains("module_gen_prod_claytronics_01", StringComparer.Ordinal))
{
station.Inventory["antimattercells"] = MathF.Max(GetInventoryAmount(station.Inventory, "antimattercells"), 90f);
station.Inventory["microchips"] = MathF.Max(GetInventoryAmount(station.Inventory, "microchips"), 120f);
station.Inventory["quantumtubes"] = MathF.Max(GetInventoryAmount(station.Inventory, "quantumtubes"), 90f);
}
if (station.Population > 0f)
{
station.Inventory["water"] = MathF.Max(60f, station.Population * 1.5f);
}
}
}
@@ -145,6 +118,11 @@ internal sealed class WorldSeedingService
foreach (var station in world.Stations)
{
if (HasSatisfiedStarterObjectiveLayout(world, station))
{
continue;
}
var moduleId = InfrastructureSimulationService.GetNextStationModuleToBuild(station, world);
if (moduleId is null || station.CelestialId is null)
{
@@ -200,6 +178,78 @@ internal sealed class WorldSeedingService
return (sites, orders);
}
private static bool HasSatisfiedStarterObjectiveLayout(SimulationWorld world, StationRuntime station)
{
var role = StationSimulationService.DetermineStationRole(station);
var objectiveModuleId = role switch
{
"power" => "module_gen_prod_energycells_01",
"refinery" => "module_gen_prod_refinedmetals_01",
"graphene" => "module_gen_prod_graphene_01",
"siliconwafers" => "module_gen_prod_siliconwafers_01",
"hullparts" => "module_gen_prod_hullparts_01",
"claytronics" => "module_gen_prod_claytronics_01",
"quantumtubes" => "module_gen_prod_quantumtubes_01",
"antimattercells" => "module_gen_prod_antimattercells_01",
"superfluidcoolant" => "module_gen_prod_superfluidcoolant_01",
"water" => "module_gen_prod_water_01",
_ => null,
};
if (objectiveModuleId is null)
{
return false;
}
if (!station.InstalledModules.Contains("module_arg_dock_m_01_lowtech", StringComparer.Ordinal)
|| !station.InstalledModules.Contains(objectiveModuleId, StringComparer.Ordinal))
{
return false;
}
if (!string.Equals(objectiveModuleId, "module_gen_prod_energycells_01", StringComparison.Ordinal)
&& !station.InstalledModules.Contains("module_gen_prod_energycells_01", StringComparer.Ordinal))
{
return false;
}
foreach (var storageModuleId in GetRequiredStorageModulesForInstalledObjective(world, objectiveModuleId))
{
if (!station.InstalledModules.Contains(storageModuleId, StringComparer.Ordinal))
{
return false;
}
}
return true;
}
private static IEnumerable<string> GetRequiredStorageModulesForInstalledObjective(SimulationWorld world, string moduleId)
{
if (!world.ModuleDefinitions.TryGetValue(moduleId, out var moduleDefinition))
{
yield break;
}
foreach (var wareId in moduleDefinition.Production
.SelectMany(production => production.Wares.Select(ware => ware.ItemId))
.Concat(moduleDefinition.Products)
.Distinct(StringComparer.Ordinal))
{
if (!world.ItemDefinitions.TryGetValue(wareId, out var itemDefinition))
{
continue;
}
yield return itemDefinition.CargoKind switch
{
"solid" => "module_arg_stor_solid_m_01",
"liquid" => "module_arg_stor_liquid_m_01",
_ => "module_arg_stor_container_m_01",
};
}
}
internal List<PolicySetRuntime> CreatePolicies(IReadOnlyCollection<FactionRuntime> factions)
{
var policies = new List<PolicySetRuntime>(factions.Count);
@@ -385,6 +435,13 @@ internal sealed class WorldSeedingService
Color = "#ff8f70",
Credits = MinimumFactionCredits,
},
"nadir-syndicate" => new FactionRuntime
{
Id = factionId,
Label = "Nadir Syndicate",
Color = "#91e6a8",
Credits = MinimumFactionCredits,
},
_ => new FactionRuntime
{
Id = factionId,