minor-power-factions command, MakerProfiles rows, Pirates allegiance top-up (ported from 57083412)
MinorPowerFactionsCommand authors Aya Collective, Megiddo and Orbital Forge as ordinary factions plus the gains on other factions' Allegiance. pirates-faction on an existing record now tops up allegiance under its key. Unbuilt here; verified at the head on Yggdrasil. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f66d921fec
commit
ac0b39f84e
2 changed files with 150 additions and 3 deletions
122
tools/AetherDb/MinorPowerFactionsCommand.cs
Normal file
122
tools/AetherDb/MinorPowerFactionsCommand.cs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using CultMath;
|
||||
using GameCult.Caching;
|
||||
|
||||
// minor-power-factions [apply] (aetheria-release, cut minor-power-factions; rulings no-territory-ban, miss-terris-belongs-to-emily-r3):
|
||||
// authors the Aya Collective, Megiddo and Orbital Forge as ordinary factions, and the allegiance every existing faction but the
|
||||
// Pirates and Miss Terri's holds to them. The Pirates' allegiance is pirates-faction's alone (it tops itself up on a re-run).
|
||||
// A faction already in the catalog under its name-derived key is skipped, as is a gain already held, so the same command is the
|
||||
// first apply, the second apply and the replay onto a merged catalog. Dry run unless passed "apply".
|
||||
public static class MinorPowerFactionsCommand
|
||||
{
|
||||
// Own: the weights this faction names for the other minor powers; every other faction but the Pirates gets Default.
|
||||
// Gain: the weight every existing faction (bar the Pirates and Miss Terri's) gains toward this faction.
|
||||
// Geonames is the name file the faction draws place names from (question minor-power-geonames: the nearest existing file).
|
||||
internal sealed record Def(string Name, string ShortName, string Description, float3 Primary, float3 Secondary,
|
||||
string Geonames, int Influence, float Gain, float Default, (string ShortName, float Weight)[] Own);
|
||||
|
||||
internal static readonly Def[] Factions =
|
||||
{
|
||||
new("Aya Collective", "Aya",
|
||||
"A federation of communes, clinics and repair crews. Its equipment comes with the drawings, so the people it protects can keep it running.",
|
||||
new float3(.72f, .36f, .22f), new float3(.12f, .36f, .25f), "Congo", 3, .3f, .3f,
|
||||
new[] { ("Forge", 1f), ("Megiddo", .5f) }),
|
||||
new("Megiddo", "Megiddo",
|
||||
"A defended fleet that holds its boundary until the question is answered. Others buy its protection without sharing its faith.",
|
||||
new float3(.07f, .09f, .15f), new float3(.90f, .85f, .74f), "pleiades", 4, .3f, .2f,
|
||||
new[] { ("Aya", .5f), ("Forge", .5f) }),
|
||||
new("Orbital Forge", "Forge",
|
||||
"Belt engineers who hollow asteroids into homes and yards. Their standards leave room to adapt them, and their drawings travel further than their ships.",
|
||||
new float3(.42f, .30f, .22f), new float3(.30f, .38f, .52f), "pleiades", 3, .5f, .5f,
|
||||
Array.Empty<(string, float)>()),
|
||||
};
|
||||
|
||||
private const string Pirates = "Pirates";
|
||||
private const string MissTerris = "Miss Terri's";
|
||||
|
||||
// A faction's record key is derived from its name, so a replay reproduces the same key and the same bytes.
|
||||
internal static CultRecordKey KeyOf(Def def) =>
|
||||
new(new Guid(SHA256.HashData(Encoding.UTF8.GetBytes("faction:" + def.Name)).AsSpan(0, 16)).ToString("N"));
|
||||
|
||||
public static int Run(string[] args, string root = null)
|
||||
{
|
||||
var apply = args.Contains("apply");
|
||||
var db = AetherDb.Open(catalogWritable: apply, root: root);
|
||||
var existing = db.Cache.GetAll<Faction>().ToArray();
|
||||
var nameFiles = db.Cache.GetAll<NameFile>().ToArray();
|
||||
|
||||
// Refuse before any write: a name or short name held by a faction under another key, and a missing name file.
|
||||
foreach (var def in Factions)
|
||||
{
|
||||
var key = KeyOf(def);
|
||||
foreach (var other in existing.Where(f => f.Name == def.Name || f.ShortName == def.ShortName))
|
||||
if (!db.Cache.RefOf(other).Key.Equals(key))
|
||||
throw new InvalidOperationException("Faction Name or ShortName: held by a faction under another key (conflict).");
|
||||
if (nameFiles.Count(n => n.Name == def.Geonames) != 1)
|
||||
throw new InvalidOperationException("Faction GeonameFile: expected exactly one name file (missing).");
|
||||
}
|
||||
|
||||
var minorKeys = Factions.ToDictionary(d => d.ShortName, KeyOf);
|
||||
var created = Factions.Where(d => !existing.Any(f => db.Cache.RefOf(f).Key.Equals(KeyOf(d)))).ToArray();
|
||||
|
||||
// Every existing faction that is not a minor power, the Pirates or Miss Terri's holds each minor power it lacks.
|
||||
var holders = existing.Where(f => f.ShortName != Pirates && f.ShortName != MissTerris && !minorKeys.ContainsKey(f.ShortName)).ToArray();
|
||||
var gains = holders
|
||||
.Select(h => (Holder: h, Missing: Factions.Where(d => !h.Allegiance.ContainsKey(new CultRecordRef<Faction>(minorKeys[d.ShortName]))).ToArray()))
|
||||
.Where(g => g.Missing.Length != 0).ToArray();
|
||||
|
||||
if (created.Length == 0 && gains.Length == 0)
|
||||
{
|
||||
Console.WriteLine("The minor powers are already authored; nothing to do.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
foreach (var def in created)
|
||||
Console.WriteLine($"New faction \"{def.Name}\" ({def.ShortName}): influence {def.Influence}, names \"{def.Geonames}\", colours ({def.Primary.x:0.00}, {def.Primary.y:0.00}, {def.Primary.z:0.00}) / ({def.Secondary.x:0.00}, {def.Secondary.y:0.00}, {def.Secondary.z:0.00})\n {def.Description}\n allegiance: {string.Join(", ", def.Own.Select(o => $"{o.ShortName} {o.Weight:0.##}"))} others {def.Default:0.##}");
|
||||
foreach (var (holder, missing) in gains)
|
||||
Console.WriteLine($" {holder.ShortName} gains {string.Join(", ", missing.Select(d => $"{d.ShortName} {d.Gain:0.##}"))}");
|
||||
|
||||
if (!apply)
|
||||
{
|
||||
Console.WriteLine($"\nDry run. Pass \"apply\" to author {created.Length} factions and {gains.Sum(g => g.Missing.Length)} allegiance gains.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// A new faction names every faction but the Pirates, the other minor powers included.
|
||||
var everyone = existing.Where(f => f.ShortName != Pirates && !minorKeys.ContainsKey(f.ShortName)).Select(f => db.Cache.RefOf(f)).ToArray();
|
||||
if (!db.Cache.Commit(batch =>
|
||||
{
|
||||
foreach (var def in created) batch.Expect(KeyOf(def), null);
|
||||
foreach (var def in created)
|
||||
{
|
||||
var faction = new Faction
|
||||
{
|
||||
Name = def.Name, ShortName = def.ShortName, Description = def.Description,
|
||||
PrimaryColor = def.Primary, SecondaryColor = def.Secondary,
|
||||
GeonameFile = db.Cache.RefOf(nameFiles.Single(n => n.Name == def.Geonames)), InfluenceDistance = def.Influence,
|
||||
};
|
||||
foreach (var reference in everyone) faction.Allegiance[reference] = def.Default;
|
||||
foreach (var other in Factions.Where(o => o != def))
|
||||
faction.Allegiance[new CultRecordRef<Faction>(minorKeys[other.ShortName])] =
|
||||
def.Own.Where(o => o.ShortName == other.ShortName).Select(o => o.Weight).DefaultIfEmpty(def.Default).First();
|
||||
batch.Upsert(typeof(Faction), faction, KeyOf(def));
|
||||
}
|
||||
foreach (var (holder, missing) in gains)
|
||||
{
|
||||
foreach (var def in missing) holder.Allegiance[new CultRecordRef<Faction>(minorKeys[def.ShortName])] = def.Gain;
|
||||
batch.Upsert(typeof(Faction), holder, db.Cache.RefOf(holder).Key);
|
||||
}
|
||||
}))
|
||||
throw new InvalidOperationException("The catalog changed while the minor powers were being authored; nothing was written. Run it again.");
|
||||
Console.WriteLine($"\nAuthored {created.Length} factions and {gains.Sum(g => g.Missing.Length)} allegiance gains in Aetheria.cc");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -56,9 +56,10 @@ public static class Program
|
|||
case "generic-designs": return GenericDesigns(args.Contains("apply"));
|
||||
case "inspection-scanner": return InspectionScannerCatalog(args.Contains("apply"));
|
||||
case "product-lines": return ProductLinesCommand.Run(args.Skip(1).ToArray());
|
||||
case "minor-power-factions": return MinorPowerFactionsCommand.Run(args.Skip(1).ToArray());
|
||||
case "doctrine-catalog": return DoctrineCatalog.Run(AetherDb.Open(catalogWritable: args.Contains("apply")).Cache, args.Contains("apply"), Console.Out);
|
||||
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], range-falloff [apply] [--effective <fraction>] [--end <multiplier>], targeting-catalog [apply], targeting-catalog-6c [apply], targeting-catalog-6d [apply], ship-authoring create|inspect|validate|compose, field-kinds [apply], mine-launcher [apply], smoke-hull [apply], smoke-rounds [apply], roles-backfill [apply], pd-gear [apply], consumables-first-set [apply], launch-suppressor [apply], pirates-faction [apply], verse-grammar [apply], generic-designs [apply], sensor-stat-set [apply], inspection-scanner [apply], product-lines weapons [apply], doctrine-catalog [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], range-falloff [apply] [--effective <fraction>] [--end <multiplier>], targeting-catalog [apply], targeting-catalog-6c [apply], targeting-catalog-6d [apply], ship-authoring create|inspect|validate|compose, field-kinds [apply], mine-launcher [apply], smoke-hull [apply], smoke-rounds [apply], roles-backfill [apply], pd-gear [apply], consumables-first-set [apply], launch-suppressor [apply], pirates-faction [apply], verse-grammar [apply], generic-designs [apply], sensor-stat-set [apply], inspection-scanner [apply], minor-power-factions [apply], product-lines weapons|minor-powers [apply], doctrine-catalog [apply]");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -258,6 +259,7 @@ public static class Program
|
|||
["Zhestokost"] = (.50f, .08f), ["AU"] = (.50f, .12f), ["Lightsail"] = (.55f, .10f), ["R&D"] = (.60f, .15f),
|
||||
["Lucent"] = (.60f, .15f), ["NiteLife"] = (.55f, .12f), ["Finch"] = (.65f, .20f), ["Alakrita"] = (.60f, .14f),
|
||||
["DME"] = (.55f, .20f), ["Adrasteia"] = (.60f, .10f),
|
||||
["Aya"] = (.50f, .08f), ["Megiddo"] = (.75f, .05f), ["Forge"] = (.45f, .15f),
|
||||
};
|
||||
|
||||
private static readonly (float Mean, float Dev) PirateProfile = (.45f, .22f);
|
||||
|
|
@ -2259,6 +2261,11 @@ public static class Program
|
|||
// only, written here as \U escapes so no editor or shell mangles them; the dry run prints them raw and as code points.
|
||||
//
|
||||
// Dry run unless passed "apply", same contract as the other *-migrate commands.
|
||||
// The factions the Pirates do not yet hold allegiance to: every faction but themselves and those already named. The one
|
||||
// rule of both the first apply and the top-up, so they cannot diverge.
|
||||
private static Faction[] AllegianceGaps(AetherDb db, Faction pirates, IEnumerable<Faction> factions) =>
|
||||
factions.Where(f => !ReferenceEquals(f, pirates) && !pirates.Allegiance.ContainsKey(db.Cache.RefOf(f))).ToArray();
|
||||
|
||||
public static int PiratesFaction(bool apply, string root = null)
|
||||
{
|
||||
var db = AetherDb.Open(catalogWritable: apply, root: root);
|
||||
|
|
@ -2266,7 +2273,25 @@ public static class Program
|
|||
var existing = db.Cache.GetAll<Faction>().FirstOrDefault(f => f.Name == "Pirates");
|
||||
if (existing != null)
|
||||
{
|
||||
Console.WriteLine("A faction named \"Pirates\" already exists; nothing to do.");
|
||||
// A Pirates record already in the catalog is topped up, never rewritten: it gains allegiance 1.0 to each faction added
|
||||
// since, under its existing key, and nothing else changes.
|
||||
var gaps = AllegianceGaps(db, existing, db.Cache.GetAll<Faction>());
|
||||
if (gaps.Length == 0)
|
||||
{
|
||||
Console.WriteLine("The Pirates' allegiance already covers every faction; nothing to do.");
|
||||
return 0;
|
||||
}
|
||||
Console.WriteLine($"The Pirates lack allegiance to {gaps.Length} faction(s): {string.Join(", ", gaps.Select(f => f.Name))}.");
|
||||
if (!apply)
|
||||
{
|
||||
Console.WriteLine("
|
||||
Dry run. Pass \"apply\" to give them allegiance 1.0 to each.");
|
||||
return 0;
|
||||
}
|
||||
foreach (var gap in gaps) existing.Allegiance[db.Cache.RefOf(gap)] = 1f;
|
||||
db.Cache.Commit(batch => batch.Upsert(typeof(Faction), existing, db.Cache.RefOf(existing).Key));
|
||||
Console.WriteLine($"
|
||||
Gave the Pirates allegiance 1.0 to {gaps.Length} faction(s) in Aetheria.cc");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -2293,7 +2318,7 @@ public static class Program
|
|||
GeonameFile = db.Cache.RefOf(nameFile),
|
||||
InfluenceDistance = 3,
|
||||
};
|
||||
foreach (var other in others) pirates.Allegiance[db.Cache.RefOf(other)] = 1f;
|
||||
foreach (var other in AllegianceGaps(db, pirates, others)) pirates.Allegiance[db.Cache.RefOf(other)] = 1f;
|
||||
|
||||
var rebrands = new (string Design, string Name, string Description)[]
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue