NamePlaces treats an invalid entry as absent; the command refuses a table holding one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
126 lines
9.9 KiB
C#
126 lines
9.9 KiB
C#
/* 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.Text;
|
|
using GameCult.Caching;
|
|
|
|
// place-names [apply] (aetheria-release, cut faction-curated-place-names; ruling faction-curated-place-names):
|
|
// authors every curated faction's Places from the table below, which is the one source of those names. A faction is
|
|
// identified by its record key, and its ShortName must agree (ghostlight-verse ruling faction-identity-is-record-key).
|
|
// A faction without a row keeps the catalogue designations Galaxy.NamePlaces falls back to.
|
|
// Order is meaning: the first name is the faction's capital, and the home zone takes it. Dry run unless passed "apply".
|
|
public static class PlaceNamesCommand
|
|
{
|
|
// List holds the names separated by " | ", in authored order.
|
|
internal sealed record Row(string RecordKey, string ShortName, string List)
|
|
{
|
|
public string[] Names => List.Split(" | ");
|
|
}
|
|
|
|
internal static readonly Row[] Table =
|
|
{
|
|
new("bbd619ed-76c1-464c-829d-3f419cdc2525", "Adrasteia",
|
|
"Shunya | Unlisted | Tamas | Silent Address | Maya | The Warm Hour | Nishabda | The Vouch | Akasha | No Such Node | Chhaya | Heat Death Reach | Santulan | Cold Margin | Antara | Without Light | Nirvata | Redacted Deep | Quiet Reach | Last Known"),
|
|
new("8443f966-0d88-4ec7-a20e-8edf73fa62f6", "AU",
|
|
"Ceres Ramp | Pallas Provisional | Ramp Two | Claimshare | Vesta Works | Hygiea | Bloom Seven | Half Measure | Further Out | Semi-Permanent | Under Construction | Drawing Board | Survey Pending | Ceres Control | Margin of Error | Proof of Concept | Ramp Nine | Good Enough | Ambition | Not Yet Named"),
|
|
new("a1f76631-d6d1-443a-9be1-c9b1267d6ca2", "Alakrita",
|
|
"Aurea | Celeritas | Elegantia | Via Alakrita | Fatum | Quiet Bay | Laurea | Gloria | Prima Linea | Destiny Yard | Aequor | Corona | Vela | Lumen | Nobilis | Portus Aureus | Inscription | Tessera | Proportio | Decus"),
|
|
new("48bbb7537ff9ed0a73c3c353598e8f69", "Aya",
|
|
"Hellas | Sanctuary | Common Kitchen | Open Courtyard | Enceladus Commons | Mutual | Assembly Hall | Repair Circle | Refuge Route | Not Inevitable | Free Clinic | Shared Table | Drawings Included | All Welcome | Congress | Keep It Running | Terracotta | Open Door | Freehold Garden | Green Shade"),
|
|
// DME's voice is lowercase on purpose.
|
|
new("8419f9bc-0167-46ec-86c3-3265d2905fbc", "DME",
|
|
"the choir loft | encore | burn it down | turn it up | void communion | last call | margin poem | basement tape | loud room | second encore | feedback | no permit | the pit | spare amp | open mic | after hours | smoke machine | b-side | green room | yara's version"),
|
|
new("a9672650-c335-484a-9555-71a35c841aa1", "Ewan Hart",
|
|
"Homestead | Second Harvest | Convoy's End | Invented Spring | Grange Hall | Long Furrow | Silo Nine | Good Dog | Feed and Seed | False October | County Line | Still Growing | Tractor Pull | Prairie Bloom | Rain Check | Elevator | Hayloft | Late Frost | Sealed Summer | Old Blue"),
|
|
new("1dfaa629-6c63-42a7-91e1-f1ae90f92f09", "Finch",
|
|
"Tycho | Serenity Ward | Tranquility | Far Side | Nectaris Clinic | Grace | Crisium | Mireille | Copernicus | Imbrium | Full Coverage | Aitken | Fecunditas | Vaporum | Recovery Ring | Clavius | Earthrise | Second Opinion | Frigoris | Plato"),
|
|
new("2f74e6b4-d268-48a9-b2a8-24591c680e84", "Lightsail",
|
|
"Mile Zero | Ganymede Stop | Last Anchorage | Dispatch | The Drop Book | Truck Stop | Route Choir | On Time | Callisto Crossing | Haul Road | Weigh Station | Long Haul | Bill of Lading | Overnight | Deadhead | Jake Brake | Signed For | The Line | Breaker Nineteen | Final Mile"),
|
|
new("a012e0f4-4b22-47b7-8b24-ff3e1439c949", "Lucent",
|
|
"Ishtar Studios | Prime Time | Maxwell Heights | Cloud Nine Live | Channel Aphrodite | Clout | Buzz Market | Ratings Tower | Lagrange Feed | Spotlight | Exclusive | Trending | Broadcast Row | Red Carpet | Season Finale | Sponsored | Headliner | Viral | Golden Hour | Breaking News"),
|
|
new("087df3f89e59beab14f9d41c849b202f", "Megiddo",
|
|
"First Vigil | Earth Stars | Continuity | The Long Silence | Deferred Ground | Unverified | Boundary Watch | Custody | Not Our World | Pleiades Watch | Second Vault | Presumption | Under Orion | Night Watch | Witness | Flatland Edge | Old Sky | Record Keeper | Patience | Revelation Pending"),
|
|
// Real Australian place names, Indigenous-derived and English, a draft Emily may replace (ruling miss-terris-improvised-places).
|
|
new("d6466821-6776-4f40-9609-2342debf5c86", "Miss Terri's",
|
|
"Woolloomooloo | Wagga Wagga | Humpty Doo | Come By Chance | Yackandandah | Nowhere Else | Kurri Kurri | Lightning Ridge | Mooloolaba | Woy Woy | Banana | Coober Pedy | Gundagai | Burpengary | Toowoomba | Kalgoorlie | Bald Hills | Wollongong | Innaloo | Ballarat"),
|
|
new("73d368dd-58a8-4431-b9ba-2d44754d36a1", "NiteLife",
|
|
"Caloris Array | Arete Grid | Malam | Cahaya | Graveyard Shift | Lights On | Bintang | Night Count | Pelita | Uptime Pro | Bulan | Grid Plus | Kilat | Afterglow | Load Balance | Peak Hours | Terang | Version Two | Midnight Blue | Keep On"),
|
|
new("e34d0354f46873df7ec27273fa2abe3d", "Forge",
|
|
"Hollow Pallas | Cinderlace | Stubborn | Open Drawing | Pattern Yard | Bored Through | Slagside | Tolerance | Free Standard | Lathe | Anvil Rock | Copied Twice | Mandrel | Inner Shell | Quench | Forge Light | Billet | Rivet | Blueprint | Worlds Enough"),
|
|
// The two emoji are U+1F3F4 and U+1F99C (ruling pirates-record-content).
|
|
new("1d8a168def994a76a1ce7cb18f59c038", "Pirates",
|
|
"Trojan Nest | Genuine Rescue | Ring Shadow | Patched Cavity | \U0001F3F4 Nobody's | Repainted | Finders Keepers | Hidden Dock | Free Parking | Legit Waystation | New Management | No Questions | Second Hand | False Beacon | Salvage Rights | The Fence | Elegance. Ours. | \U0001F99C Roost | Maintenance Due | Not A Base"),
|
|
new("adfb9b08-aabc-45c6-9586-915698a9c4b0", "R&D",
|
|
"Prüfstand | Findings | Recertification | Configuration B | Asterisk | Freigabe | Friendly | Sicherheit | Compliance | Revision C | Normal Operation | Abnahme | Test Bench Four | Fine Print | Hold Music | Reassurance | Warranty | Grundlage | Standard Procedure | Kinderleicht"),
|
|
new("16bad4f9-7fb5-41c5-8da0-dc76709eb7d3", "Zhestokost",
|
|
"Cimmeria | Tharsis Bastion | Olimp | Molot | Valles Forward | Arsenal Commission | Eternal Council | Moneta | Kuznya | Overkill | Assignment Board | Stal | Krepost | Gromada | Campaign | Frontline | Beton | Plamya | Zhelezo | Pobeda"),
|
|
};
|
|
|
|
public static int Run(string[] args, string root = null) => Run(args, root, Table);
|
|
|
|
// The table is a parameter so a test can hand it a malformed one; production always passes Table.
|
|
internal static int Run(string[] args, string root, Row[] table)
|
|
{
|
|
var apply = args.Contains("apply");
|
|
var db = AetherDb.Open(catalogWritable: apply, root: root);
|
|
var factions = db.Cache.GetAll<Faction>().ToArray();
|
|
var keyOf = factions.ToDictionary(f => db.Cache.RefOf(f).Key.Value, StringComparer.Ordinal);
|
|
|
|
// Refuse before any write. A refusal never echoes the offending value.
|
|
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var row in table)
|
|
{
|
|
if (!keyOf.TryGetValue(row.RecordKey, out var faction))
|
|
throw new InvalidOperationException("Place names row: its key is not a faction in the catalog (missing).");
|
|
if (faction.ShortName != row.ShortName)
|
|
throw new InvalidOperationException("Place names row: its ShortName differs from the faction under its key (conflict).");
|
|
foreach (var name in row.Names)
|
|
{
|
|
if (!FactionPlace.IsValidName(name))
|
|
throw new InvalidOperationException("Place names row: a name is empty, untrimmed or a catalogue designation (invalid).");
|
|
if (!seen.Add(name))
|
|
throw new InvalidOperationException("Place names table: a name is repeated (conflict).");
|
|
}
|
|
}
|
|
|
|
var rows = table.ToDictionary(r => r.RecordKey, StringComparer.Ordinal);
|
|
var changed = table.Where(r => !keyOf[r.RecordKey].Places.Select(p => p.Name).SequenceEqual(r.Names)).ToArray();
|
|
|
|
foreach (var faction in factions.OrderBy(f => f.Name, StringComparer.Ordinal))
|
|
{
|
|
if (!rows.TryGetValue(db.Cache.RefOf(faction).Key.Value, out var row))
|
|
{
|
|
Console.WriteLine($"{faction.Name}: no list: catalogue designations");
|
|
continue;
|
|
}
|
|
Console.WriteLine($"{faction.Name}: {row.Names.Length} places, capital {row.Names[0]}, {(changed.Contains(row) ? "changed" : "unchanged")}");
|
|
}
|
|
if (changed.Length == 0)
|
|
{
|
|
Console.WriteLine("nothing to do");
|
|
return 0;
|
|
}
|
|
if (!apply)
|
|
{
|
|
Console.WriteLine($"\nDry run. Pass \"apply\" to author {changed.Length} place lists.");
|
|
return 0;
|
|
}
|
|
|
|
if (!db.Cache.Commit(batch =>
|
|
{
|
|
foreach (var row in changed)
|
|
{
|
|
var faction = keyOf[row.RecordKey];
|
|
faction.Places = row.Names.Select(n => new FactionPlace { Name = n }).ToArray();
|
|
batch.Upsert(typeof(Faction), faction, db.Cache.RefOf(faction).Key);
|
|
}
|
|
}))
|
|
throw new InvalidOperationException("The catalog changed while place names were being authored; nothing was written. Run it again.");
|
|
Console.WriteLine($"\nAuthored {changed.Length} place lists in Aetheria.cc");
|
|
return 0;
|
|
}
|
|
}
|