Content: a station reactor for every reactor maker

Operator ruling, 2026-09-30: "Keep the variety, not every galaxy will have
Zhestokost in it." A transient `AetherDb station-reactors [apply]` gives each
manufacturer that makes reactors a design for Zenith's 16-cell Reactor
hardpoint, scaled by cells from its largest reactor with every other value,
stat term, role, temperature and sound bank its own, and a product by it:

  Vulcan Station Reactor     (AU, from Vulcan 12 cells x1.333)  charge 66.7..266.7, mass 666.7, price 1,550,000
  Notorious Station Reactor  (Lucent, from Notorious 9 x1.778)  charge 711.1..2133.3, mass 1333.3, price 1,800,000
  MoveOnPro Station Reactor  (NiteLife, from MoveOnPro 4 x4)    charge 100..500, mass 1200, price 3,600,000
  Core Power Station Reactor (R&D, from Core Power 4 x4)        charge 400, mass 200, price 1,000,000
  products: Vulcan Station, Notorious Station, MoveOnPro Station, Steadfast Station

The Manhattan-derived one is renamed "Manhattan Station Reactor" (product
"Manhattan Station").

Reach is read through the one rule, LoadoutGenerator.IsAvailable. Test: a
faction allied only to AU, Lightsail and NiteLife is offered none of
Zhestokost's products and AU's, its stations carry nothing it cannot reach and
nothing by Zhestokost, and they are powered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-30 23:13:31 +02:00
commit f458206c76
3 changed files with 133 additions and 12 deletions

View file

@ -36,8 +36,9 @@ public static class Program
case "targeting-catalog": return TargetingCatalog(args.Contains("apply"));
case "targeting-catalog-6c": return TargetingCatalog6c(args.Contains("apply"));
case "targeting-catalog-6d": return TargetingCatalog6d(args.Contains("apply"));
case "station-reactors": return StationReactors(args.Contains("apply"));
default:
Console.WriteLine("commands: census, factions, station-fit, hardpoint-fit, loadout [seed], save, settings, settings-dump, dangling [clear <Type.Member>]... [apply], shield-migrate [apply], brownout-migrate [apply], roles-migrate [apply], firing-arc-migrate [apply], targeting-catalog [apply], targeting-catalog-6c [apply], targeting-catalog-6d [apply]");
Console.WriteLine("commands: census, factions, station-fit, hardpoint-fit, loadout [seed], save, settings, settings-dump, dangling [clear <Type.Member>]... [apply], shield-migrate [apply], brownout-migrate [apply], roles-migrate [apply], firing-arc-migrate [apply], targeting-catalog [apply], targeting-catalog-6c [apply], targeting-catalog-6d [apply], station-reactors [apply]");
return 1;
}
}
@ -1471,4 +1472,88 @@ public static class Program
Console.WriteLine($"\nLanded {precisionChanges.Length} Precision changes in Aetheria.cc");
return 0;
}
// Scenarios map, operator ruling 2026-09-30 on station reactors (B): "a station with no access to Zhestokost gear
// should not have Zhestokost gear". Every manufacturer that makes reactors gets its own design for Zenith's 16-cell
// Reactor hardpoint, scaled by cells from its largest reactor as the Manhattan one was: a full 4x4, charge, mass
// and price times 16 over the source's cells, every other value and stat term the source's own, sold by that
// manufacturer. The Manhattan-derived "Station Reactor" is renamed to the same pattern.
private static int StationReactors(bool apply)
{
var db = AetherDb.Open(catalogWritable: apply);
var cache = db.Cache;
var reactors = cache.GetAll<GearData>().Where(d => d.Hardpoint == HardpointType.Reactor).ToArray();
var products = cache.GetAll<FactionProductData>().Where(p => p.Manufacturer.IsSet()).ToArray();
FactionProductData ProductOf(GearData design) => products.Single(p => p.Design.Key.Equals(cache.RefOf(design).Key));
var existing = cache.GetAll<GearData>().Single(d => d.Name == "Station Reactor");
var existingProduct = ProductOf(existing);
var changes = new List<(Type Type, object Document, CultRecordKey Key)>();
existing.Name = "Manhattan Station Reactor";
existingProduct.Name = "Manhattan Station";
changes.Add((typeof(GearData), existing, cache.RefOf(existing).Key));
changes.Add((typeof(FactionProductData), existingProduct, cache.RefOf(existingProduct).Key));
Console.WriteLine("Renamed: Station Reactor -> Manhattan Station Reactor, product Station Reactor -> Manhattan Station");
var makers = reactors.Where(r => r.Shape.Coordinates.Length < 16).GroupBy(r => ProductOf(r).Manufacturer.Key)
.Where(g => !reactors.Any(r => r.Shape.Coordinates.Length == 16 && ProductOf(r).Manufacturer.Key.Equals(g.Key)));
foreach (var group in makers.OrderBy(g => cache.Get<Faction>(g.Key).Name, StringComparer.Ordinal))
{
var source = group.OrderByDescending(r => r.Shape.Coordinates.Length).ThenByDescending(r => r.Price).First();
var sourceProduct = ProductOf(source);
var scale = 16f / source.Shape.Coordinates.Length;
var shape = new Shape(4, 4);
foreach (var cell in shape.AllCoordinates) shape[cell] = true;
var design = new GearData
{
Name = source.Name + " Station Reactor",
Description = "A reactor sized for a station's core.",
Hardpoint = HardpointType.Reactor,
Shape = shape,
Mass = source.Mass * scale,
Price = (int) Math.Round(source.Price * scale / 50000) * 50000,
SpecificHeat = source.SpecificHeat,
Conductivity = source.Conductivity,
Durability = source.Durability,
MinimumTemperature = source.MinimumTemperature,
MaximumTemperature = source.MaximumTemperature,
OptimalTemperature = source.OptimalTemperature,
PlateauWidth = source.PlateauWidth,
ThermalResilience = source.ThermalResilience,
SoundBank = source.SoundBank,
Roles = source.Roles.Select(role => new ItemRole { Name = role.Name }).ToList(),
Behaviors = MessagePack.MessagePackSerializer.Deserialize<List<BehaviorData>>(MessagePack.MessagePackSerializer.Serialize(source.Behaviors)),
};
var reactor = design.Behaviors.OfType<ReactorData>().Single();
reactor.Charge.Min *= scale;
reactor.Charge.Max *= scale;
CultRecordRefs.Validate(design);
var key = new CultRecordKey(Guid.NewGuid().ToString("N"));
var product = new FactionProductData
{
Name = sourceProduct.Name + " Station",
Description = "",
Design = new CultRecordRef<CraftedItemData>(key),
Manufacturer = sourceProduct.Manufacturer,
Roles = design.Roles.Select(role => new ProductRole { Role = role.Name }).ToList(),
};
changes.Add((typeof(GearData), design, key));
changes.Add((typeof(FactionProductData), product, new CultRecordKey(Guid.NewGuid().ToString("N"))));
Console.WriteLine($"{design.Name}: from {source.Name} ({source.Shape.Coordinates.Length} cells) x {scale:F3}, mass {source.Mass} -> {design.Mass}, " +
$"price {source.Price} -> {design.Price}, charge {reactor.Charge.Min / scale}..{reactor.Charge.Max / scale} -> {reactor.Charge.Min}..{reactor.Charge.Max}; " +
$"product {product.Name} by {cache.Get(product.Manufacturer).Name}");
}
if (!apply)
{
Console.WriteLine("\nDry run. Pass \"apply\" to land it.");
return 0;
}
cache.Commit(batch =>
{
foreach (var (type, document, key) in changes) batch.Upsert(type, document, key);
});
Console.WriteLine($"\nLanded {changes.Count} records in Aetheria.cc");
return 0;
}
}