From 740e1ee7814a1f29eaee1e5fb49d8dbf098be9a7 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 17 Oct 2026 19:10:17 +0200 Subject: [PATCH 01/10] Place names: Faction.Places, Galaxy.NamePlaces and the place-names command Each faction names the zones it owns from its own curated list (ruling faction-curated-place-names): the home zone takes Places[0], the rest follow in distance order, a spent or absent list falls back to unique catalogue designations. Galaxy.NamePlaces replaces the Markov GenerateNames body. The place-names command authors the 15-row table and, until commit 3, purges the NameFile records and GeonameFile links while their type still exists. The shipped catalog carries no Places yet (commit 2), so the tests that read shipped lists fail at this commit. Co-Authored-By: Claude Opus 5.5 --- Assets/Scripts/ServerShared/Corporations.cs | 13 + Assets/Scripts/ServerShared/Galaxy.cs | 51 ++-- .../Aetheria.Shared.Tests/PlaceNamesTests.cs | 257 ++++++++++++++++++ tools/AetherDb/PlaceNamesCommand.cs | 133 +++++++++ tools/AetherDb/Program.cs | 5 +- 5 files changed, 435 insertions(+), 24 deletions(-) create mode 100644 tests/Aetheria.Shared.Tests/PlaceNamesTests.cs create mode 100644 tools/AetherDb/PlaceNamesCommand.cs diff --git a/Assets/Scripts/ServerShared/Corporations.cs b/Assets/Scripts/ServerShared/Corporations.cs index c1e69fcca..fb826cf8e 100644 --- a/Assets/Scripts/ServerShared/Corporations.cs +++ b/Assets/Scripts/ServerShared/Corporations.cs @@ -63,6 +63,19 @@ public class Faction // The faction's rules of engagement; null fights as FactionDoctrine.Default. [Inspectable, JsonProperty("doctrine"), Key(17)] public FactionDoctrine Doctrine; + + // Curated place names in authored order; the home zone takes the first (ruling faction-curated-place-names). + // Authored by AetherDb place-names, which restores the table on every apply. + [Inspectable, JsonProperty("places"), Key(21)] + public FactionPlace[] Places = Array.Empty(); +} + +// Historical, narrative and strategic intent join as keys 2+ when economics-driven placement lands (ruling faction-curated-place-names). +[MessagePackObject, JsonObject(MemberSerialization.OptIn)] +public class FactionPlace +{ + [Inspectable, JsonProperty("name"), Key(1)] + public string Name; } [CultDocument("aetheria.namefile", "1"), Inspectable, MessagePackObject] diff --git a/Assets/Scripts/ServerShared/Galaxy.cs b/Assets/Scripts/ServerShared/Galaxy.cs index 7026d8a52..41497ad68 100644 --- a/Assets/Scripts/ServerShared/Galaxy.cs +++ b/Assets/Scripts/ServerShared/Galaxy.cs @@ -34,7 +34,6 @@ public class Galaxy public uint Seed { get; } private GalaxyZone[] _exitPath; - private Dictionary _nameGenerators = new Dictionary(); private readonly CultCache _cache; public GalaxyZone[] ExitPath @@ -131,7 +130,7 @@ public class Galaxy CalculateFactionInfluence(progressCallback); - GenerateNames(cache, nameGeneratorSettings, ref random, progressCallback); + NamePlaces(ref random, progressCallback); progressCallback?.Invoke("Done!"); if(progressCallback!=null) Thread.Sleep(500); // Inserting Delay to make it seem like it's doing more work lmao @@ -259,7 +258,7 @@ public class Galaxy CalculateFactionInfluence(progressCallback); - GenerateNames(cache, nameGeneratorSettings, ref random, progressCallback); + NamePlaces(ref random, progressCallback); // progressCallback?.Invoke("Weaving Narrative"); // var processor = new StoryProcessor(playerSettings, narrativeDirectory, this, ref random, Log); @@ -350,31 +349,37 @@ public class Galaxy } } - private void GenerateNames(CultCache cache, - NameGeneratorSettings nameGeneratorSettings, - ref Random random, - Action progressCallback = null) + // Each faction names the zones it owns from its own curated list (ruling faction-curated-place-names): nearest its + // home first, so the home zone takes the capital, which is Places[0]. A zone left over when a list is spent, and every + // zone nobody owns, takes a catalogue designation. A name is never used twice, ignoring case. + internal void NamePlaces(ref Random random, Action progressCallback = null) { - var used = new HashSet(); - for (var i = 0; i < Factions.Length; i++) + progressCallback?.Invoke("Naming places"); + var used = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var zone in Zones) zone.Name = null; + + foreach (var faction in Factions) { - progressCallback?.Invoke($"Feeding Markov Chains: {i + 1} / {Factions.Length}"); - //if(progressCallback!=null) Thread.Sleep(250); // Inserting Delay to make it seem like it's doing more work lmao - var faction = Factions[i]; - _nameGenerators[faction] = new MarkovNameGenerator(ref random, cache.Get(faction.GeonameFile).Names, nameGeneratorSettings, used); + if (!HomeZones.TryGetValue(faction, out var home)) continue; + var places = (faction.Places ?? Array.Empty()).Select(p => p?.Name).Where(n => !string.IsNullOrEmpty(n)).ToArray(); + var next = 0; + var owned = Zones.Select((zone, index) => (zone, index)).Where(z => z.zone.Owner == faction) + .OrderBy(z => z.zone.Distance[home]).ThenBy(z => z.index); + foreach (var (zone, _) in owned) + { + while (next < places.Length && used.Contains(places[next])) next++; + if (next == places.Length) break; + zone.Name = places[next]; + used.Add(places[next++]); + } } - // Generate zone name using the owner's name generator, otherwise assign catalogue ID - foreach (var zone in Zones) + foreach (var zone in Zones.Where(z => z.Name == null)) { - if (zone.Owner != null) - { - zone.Name = _nameGenerators[zone.Owner].NextName.Trim(); - } - else - { - zone.Name = $"EAC-{random.NextInt(9999).ToString()}"; - } + string designation; + do designation = $"EAC-{random.NextInt(9999)}"; + while (!used.Add(designation)); + zone.Name = designation; } } diff --git a/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs b/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs new file mode 100644 index 000000000..c2d910a25 --- /dev/null +++ b/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs @@ -0,0 +1,257 @@ +/* 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.IO; +using System.Linq; +using System.Text.RegularExpressions; +using GameCult.Caching; +using Xunit; +using Random = CultMath.Random; + +// Each faction names the zones it owns from its own curated list (aetheria-release, cut faction-curated-place-names): the +// home zone takes the capital, the rest follow in distance order, a spent or absent list falls back to catalogue designations, +// and no name is used twice. Galaxies are built the way the game builds them, from the authored settings and the shipped catalog. +public sealed class PlaceNamesTests : IDisposable +{ + private static readonly Regex Designation = new(@"^EAC-\d{1,4}$"); + private static readonly AuthoredSettings Authored = AuthoredSettings.Load(RunStartTests.FindRepoRoot()); + private static readonly CultCache Shared = RestoredHullsTests.OpenCatalog(); + private static readonly Dictionary<(bool, uint), Galaxy> Built = new(); + + private readonly List _own = new(); + + public void Dispose() + { + foreach (var cache in _own) cache.Dispose(); + foreach (var root in _scratch) Directory.Delete(root, true); + } + + private static Galaxy Build(CultCache cache, bool prelude, uint seed) => prelude + ? new Galaxy(Authored.Read("TutorialGenerationSettings"), Authored.Read("TutorialBackgroundSettings"), + Authored.Read("NameGeneratorSettings"), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed) + : new Galaxy(Authored.Read("SectorGenerationSettings"), Authored.Read("SectorBackgroundSettings"), + Authored.Read("NameGeneratorSettings"), cache, _ => { }, null, seed); + + // A prelude galaxy sometimes refuses to generate for a seed (RestoredHullsTests calls it Galaxy's own seed-dependent + // flakiness); those seeds are skipped, and a test that depends on how many built says so. + private static Galaxy TryBuild(bool prelude, uint seed) + { + lock (Built) + { + if (Built.TryGetValue((prelude, seed), out var known)) return known; + Galaxy galaxy; + try { galaxy = Build(Shared, prelude, seed); } + catch (Exception) { galaxy = null; } + return Built[(prelude, seed)] = galaxy; + } + } + + private static IEnumerable Galaxies(bool prelude, uint last) => + Enumerable.Range(1, (int)last).Select(seed => TryBuild(prelude, (uint)seed)).Where(g => g != null); + + private static Faction[] Placed(Galaxy galaxy) => + galaxy.Factions.Where(f => galaxy.HomeZones.ContainsKey(f) && f.Places.Length != 0).ToArray(); + + private static GalaxyZone[] Owned(Galaxy galaxy, Faction faction) => + galaxy.Zones.Select((zone, index) => (zone, index)).Where(z => z.zone.Owner == faction) + .OrderBy(z => z.zone.Distance[galaxy.HomeZones[faction]]).ThenBy(z => z.index).Select(z => z.zone).ToArray(); + + [Fact] + public void TheHomeZoneTakesTheFirstName() + { + var checkedHomes = 0; + foreach (var galaxy in Galaxies(true, 10).Concat(Galaxies(false, 10))) + foreach (var faction in Placed(galaxy)) + { + Assert.Equal(faction.Places[0].Name, galaxy.HomeZones[faction].Name); + checkedHomes++; + } + Assert.True(checkedHomes >= 20, $"only {checkedHomes} homes were checked"); + } + + [Fact] + public void OwnedZonesTakeTheListInDistanceOrder() + { + var checkedFactions = 0; + foreach (var galaxy in Galaxies(true, 10).Concat(Galaxies(false, 10))) + foreach (var faction in Placed(galaxy)) + { + var owned = Owned(galaxy, faction); + var listed = Math.Min(owned.Length, faction.Places.Length); + Assert.Equal(faction.Places.Take(listed).Select(p => p.Name), owned.Take(listed).Select(z => z.Name)); + Assert.All(owned.Skip(listed), zone => Assert.Matches(Designation, zone.Name)); + checkedFactions++; + } + Assert.True(checkedFactions >= 20, $"only {checkedFactions} factions were checked"); + } + + [Fact] + public void NoZoneCarriesAnotherFactionsName() + { + var curated = Shared.GetAll().SelectMany(f => f.Places.Select(p => (p.Name, Faction: f))).ToDictionary(t => t.Name, t => t.Faction, StringComparer.OrdinalIgnoreCase); + var zones = 0; + foreach (var galaxy in Galaxies(true, 10).Concat(Galaxies(false, 10))) + foreach (var zone in galaxy.Zones.Where(z => curated.ContainsKey(z.Name))) + { + Assert.Same(curated[zone.Name], zone.Owner); + zones++; + } + Assert.True(zones >= 50, $"only {zones} curated zones were checked"); + } + + [Fact] + public void ASpentListFallsBackToCatalogueDesignations() + { + // Its own cache: the faction's list is cut, which must not reach the galaxies the other tests share. + var cache = RestoredHullsTests.OpenCatalog(); + _own.Add(cache); + var galaxy = Enumerable.Range(1, 20).Select(seed => { try { return Build(cache, true, (uint)seed); } catch (Exception) { return null; } }) + .First(g => g != null && Placed(g).Any(f => Owned(g, f).Length >= 4)); + var faction = Placed(galaxy).First(f => Owned(galaxy, f).Length >= 4); + faction.Places = faction.Places.Take(2).ToArray(); + + var random = new Random(7); + galaxy.NamePlaces(ref random); + + var owned = Owned(galaxy, faction); + Assert.Equal(faction.Places.Select(p => p.Name), owned.Take(2).Select(z => z.Name)); + Assert.Same(owned[0], galaxy.HomeZones[faction]); + Assert.All(owned.Skip(2), zone => Assert.Matches(Designation, zone.Name)); + } + + [Fact] + public void NoTwoZonesShareAName() + { + var checkedGalaxies = 0; + foreach (var galaxy in Galaxies(true, 40).Concat(Galaxies(false, 40))) + { + var names = galaxy.Zones.Select(z => z.Name).ToArray(); + Assert.All(names, name => Assert.False(string.IsNullOrEmpty(name))); + Assert.Equal(names.Length, names.Distinct(StringComparer.OrdinalIgnoreCase).Count()); + checkedGalaxies++; + } + Assert.True(checkedGalaxies >= 40, $"only {checkedGalaxies} galaxies were checked"); + } + + [Fact] + public void AFactionWithoutAListGetsCatalogueDesignations() + { + var terris = Shared.GetAll().Single(f => f.ShortName == "Miss Terri's"); + Assert.Empty(terris.Places); + var held = 0; + foreach (var galaxy in Galaxies(true, 10)) + foreach (var zone in galaxy.Zones.Where(z => z.Owner == terris)) + { + Assert.Matches(Designation, zone.Name); + held++; + } + Assert.True(held > 0, "Miss Terri's held no zone in the first ten prelude galaxies"); + } + + [Fact] + public void TheShippedListsAreTheTable() + { + var factions = Shared.GetAll().ToArray(); + foreach (var row in PlaceNamesCommand.Table) + { + var faction = Assert.Single(factions, f => Shared.RefOf(f).Key.Value == row.RecordKey); + Assert.Equal(row.ShortName, faction.ShortName); + Assert.Equal(row.Names, faction.Places.Select(p => p.Name)); + } + Assert.Equal(PlaceNamesCommand.Table.Length, factions.Count(f => f.Places.Length != 0)); + + var names = factions.SelectMany(f => f.Places.Select(p => p.Name)).ToArray(); + Assert.All(names, name => + { + Assert.False(string.IsNullOrEmpty(name)); + Assert.Equal(name.Trim(), name); + Assert.False(name.StartsWith("EAC-", StringComparison.OrdinalIgnoreCase)); + }); + Assert.Equal(names.Length, names.Distinct(StringComparer.OrdinalIgnoreCase).Count()); + } + + // The command's scratch catalog: the shipped one with every list cleared, so an apply has all of it to author. + private string ScratchWithoutLists() + { + var root = Path.Combine(Path.GetTempPath(), "place-names-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(Path.Combine(root, "GameData")); + var path = Path.Combine(root, "GameData", "Aetheria.cc"); + File.Copy(Path.Combine(RunStartTests.FindRepoRoot(), "GameData", "Aetheria.cc"), path); + using (var cache = new CultCache(TestCatalog.Registry())) + { + cache.AddBackingStore(new GameCult.Caching.MessagePack.SingleFileMessagePackBackingStore(path), AetheriaStores.CatalogTypes); + foreach (var faction in cache.GetAll().ToArray()) + { + faction.Places = Array.Empty(); + cache.Commit(batch => batch.Upsert(typeof(Faction), faction, cache.RefOf(faction).Key)); + } + cache.FlushAsync().Wait(); + } + _scratch.Add(root); + return root; + } + + private readonly List _scratch = new(); + private static string CatalogOf(string root) => Path.Combine(root, "GameData", "Aetheria.cc"); + + [Fact] + public void TheCommandIsIdempotentAndRefusesAMismatch() + { + var root = ScratchWithoutLists(); + var before = File.ReadAllBytes(CatalogOf(root)); + Assert.Equal(0, PlaceNamesCommand.Run(new string[0], root)); + Assert.Equal(before, File.ReadAllBytes(CatalogOf(root))); + + Assert.Equal(0, PlaceNamesCommand.Run(new[] { "apply" }, root)); + var landed = File.ReadAllBytes(CatalogOf(root)); + Assert.NotEqual(before, landed); + var db = AetherDb.Open(root: root); + try + { + foreach (var row in PlaceNamesCommand.Table) + Assert.Equal(row.Names, db.Cache.GetAll().Single(f => db.Cache.RefOf(f).Key.Value == row.RecordKey).Places.Select(p => p.Name)); + } + finally { db.Cache.Dispose(); } + Assert.Equal(0, PlaceNamesCommand.Run(new[] { "apply" }, root)); + Assert.Equal(landed, File.ReadAllBytes(CatalogOf(root))); + + // Aya's ShortName changed under its key: identity is the key, so the row no longer describes that faction. + var squatter = AetherDb.Open(catalogWritable: true, root: root); + var aya = squatter.Cache.GetAll().Single(f => f.ShortName == "Aya"); + aya.ShortName = "Ayx"; + squatter.Cache.Commit(batch => batch.Upsert(typeof(Faction), aya, squatter.Cache.RefOf(aya).Key)); + squatter.Cache.Dispose(); + var changed = File.ReadAllBytes(CatalogOf(root)); + var refusal = Assert.Throws(() => PlaceNamesCommand.Run(new[] { "apply" }, root)); + Assert.DoesNotContain("Ayx", refusal.Message); + Assert.Equal(changed, File.ReadAllBytes(CatalogOf(root))); + } + + [Theory] + [InlineData("EAC-12")] + [InlineData(" Padded")] + [InlineData("")] + [InlineData("shunya")] + public void ATableWithABadNameIsRefusedWithNothingWritten(string bad) + { + var root = ScratchWithoutLists(); + var before = File.ReadAllBytes(CatalogOf(root)); + var table = PlaceNamesCommand.Table.Select((row, i) => i == 1 ? row with { List = row.List + " | " + bad } : row).ToArray(); + var refusal = Assert.Throws(() => PlaceNamesCommand.Run(new[] { "apply" }, root, table)); + if (bad.Length != 0) Assert.DoesNotContain(bad, refusal.Message); + Assert.Equal(before, File.ReadAllBytes(CatalogOf(root))); + } + + [Fact] + public void ARowForAKeyTheCatalogDoesNotHoldIsRefusedWithNothingWritten() + { + var root = ScratchWithoutLists(); + var before = File.ReadAllBytes(CatalogOf(root)); + var table = PlaceNamesCommand.Table.Append(new PlaceNamesCommand.Row("00000000-0000-0000-0000-000000000000", "Nobody", "Elsewhere")).ToArray(); + Assert.Throws(() => PlaceNamesCommand.Run(new[] { "apply" }, root, table)); + Assert.Equal(before, File.ReadAllBytes(CatalogOf(root))); + } +} diff --git a/tools/AetherDb/PlaceNamesCommand.cs b/tools/AetherDb/PlaceNamesCommand.cs new file mode 100644 index 000000000..7c08e4150 --- /dev/null +++ b/tools/AetherDb/PlaceNamesCommand.cs @@ -0,0 +1,133 @@ +/* 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 (question miss-terris-place-names). +// 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"), + 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().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(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 (string.IsNullOrEmpty(name) || name != name.Trim()) + throw new InvalidOperationException("Place names row: a name is empty or untrimmed (invalid)."); + if (name.StartsWith("EAC-", StringComparison.OrdinalIgnoreCase)) + throw new InvalidOperationException("Place names row: a name is 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(); + var nameFiles = db.Cache.GetAll().ToArray(); + var geoned = factions.Where(f => f.GeonameFile.IsSet()).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 (nameFiles.Length != 0 || geoned.Length != 0) + Console.WriteLine($"Retired Markov data: {nameFiles.Length} name files, {geoned.Length} geoname links."); + + if (changed.Length == 0 && nameFiles.Length == 0 && geoned.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 => + { + // Commit 1 only: the Markov data goes while its type still exists; commit 3 deletes the type and this leg. + foreach (var faction in factions.Where(f => geoned.Contains(f) || changed.Any(r => r.RecordKey == db.Cache.RefOf(f).Key.Value))) + { + if (rows.TryGetValue(db.Cache.RefOf(faction).Key.Value, out var row)) + faction.Places = row.Names.Select(n => new FactionPlace { Name = n }).ToArray(); + faction.GeonameFile = default; + batch.Upsert(typeof(Faction), faction, db.Cache.RefOf(faction).Key); + } + foreach (var file in nameFiles) batch.Remove(db.Cache.RefOf(file).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; + } +} diff --git a/tools/AetherDb/Program.cs b/tools/AetherDb/Program.cs index 9d4fe5251..8c00d3e53 100644 --- a/tools/AetherDb/Program.cs +++ b/tools/AetherDb/Program.cs @@ -57,9 +57,12 @@ public static class Program 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 "place-names": + Console.OutputEncoding = new System.Text.UTF8Encoding(false); + return PlaceNamesCommand.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 ]... [apply], shield-migrate [apply], brownout-migrate [apply], roles-migrate [apply], firing-arc-migrate [apply], range-falloff [apply] [--effective ] [--end ], 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|gear [apply], doctrine-catalog [apply]"); + Console.WriteLine("commands: census, factions, station-fit, hardpoint-fit, loadout [seed], save, settings, settings-dump, dangling [clear ]... [apply], shield-migrate [apply], brownout-migrate [apply], roles-migrate [apply], firing-arc-migrate [apply], range-falloff [apply] [--effective ] [--end ], 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], place-names [apply], product-lines weapons|minor-powers|gear [apply], doctrine-catalog [apply]"); return 1; } } From 0a6bbdde1334f16a0f0a15b8c11654d7435ec0fa Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 17 Oct 2026 19:25:47 +0200 Subject: [PATCH 02/10] Catalog: place-names apply on master's catalog (15 place lists, Markov data purged) Built from 740e1ee7 with AetherDb place-names apply on 0f5def17's catalog; a second apply printed 'nothing to do'. The merge pass replays the command, not this blob. Co-Authored-By: Claude Opus 5.5 --- GameData/Aetheria.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GameData/Aetheria.cc b/GameData/Aetheria.cc index a53215eec..8008cac55 100644 --- a/GameData/Aetheria.cc +++ b/GameData/Aetheria.cc @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4ff0effc5b80bbd4edea4c9f3855555d622e6939edfb720d08c4603b61ebee9 -size 12760370 +oid sha256:d5513c6bd32b64c73ad803a3ca9609c7ee0ef7ffdc825214cb25d10070845efd +size 185359 From 1d689adfa6be76327b3c1b496ab4131dda23c45a Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 17 Oct 2026 19:26:15 +0200 Subject: [PATCH 03/10] Delete the Markov name generator, its data and settings, NameFile and GeonameFile The NameFile records went with commit 2's apply, so the type, Faction key 9 (left as a retired-key comment), NameGeneratorSettings and its parameter on Galaxy and GalaxyStage, the Settings.asset block, the Name Tools editor window, the Geonames resources, the purge leg of place-names and the minor-power and Pirates commands' name-file lookups all go. The Pirates missing-name-file test pins a retired rule and goes with it. Factions shows a places count where the geonames column was. Unity compile owed at merge. Co-Authored-By: Claude Opus 5.5 --- Assets/Resources/Geonames.meta | 8 - Assets/Resources/Geonames/Australia.csv | 3 - Assets/Resources/Geonames/Australia.csv.meta | 7 - Assets/Resources/Geonames/Congo.csv | 3 - Assets/Resources/Geonames/Congo.csv.meta | 7 - Assets/Resources/Geonames/Germany.csv | 3 - Assets/Resources/Geonames/Germany.csv.meta | 7 - Assets/Resources/Geonames/India.csv | 3 - Assets/Resources/Geonames/India.csv.meta | 7 - Assets/Resources/Geonames/Iran.csv | 3 - Assets/Resources/Geonames/Iran.csv.meta | 7 - Assets/Resources/Geonames/Japan.csv | 3 - Assets/Resources/Geonames/Japan.csv.meta | 7 - Assets/Resources/Geonames/Mexico.csv | 3 - Assets/Resources/Geonames/Mexico.csv.meta | 7 - Assets/Resources/Geonames/Russia.csv | 3 - Assets/Resources/Geonames/Russia.csv.meta | 7 - Assets/Resources/Geonames/Swahili.csv | 3 - Assets/Resources/Geonames/Swahili.csv.meta | 7 - Assets/Resources/Geonames/Thailand.csv | 3 - Assets/Resources/Geonames/Thailand.csv.meta | 7 - Assets/Resources/Geonames/USA.csv | 3 - Assets/Resources/Geonames/USA.csv.meta | 7 - Assets/Resources/Geonames/bad-words.txt | 1384 ----------------- Assets/Resources/Geonames/bad-words.txt.meta | 7 - Assets/Resources/Geonames/pleiades.csv | 3 - Assets/Resources/Geonames/pleiades.csv.meta | 7 - Assets/Resources/Settings.asset | 4 +- Assets/Scripts/Editor/NameTools.cs | 93 -- Assets/Scripts/Editor/NameTools.cs.meta | 11 - Assets/Scripts/Gameplay/GameSettings.cs | 1 - Assets/Scripts/ServerShared/AetheriaStores.cs | 2 +- Assets/Scripts/ServerShared/Corporations.cs | 9 +- Assets/Scripts/ServerShared/Galaxy.cs | 3 - Assets/Scripts/ServerShared/GalaxyStage.cs | 7 +- .../ServerShared/MarkovNameGenerator.cs | 129 -- .../ServerShared/MarkovNameGenerator.cs.meta | 11 - Assets/Scripts/ServerShared/Settings.cs | 8 - Assets/Scripts/UI/MainMenu.cs | 1 - .../ChildrenWithinReachTests.cs | 2 +- .../ConsumableSupplyTests.cs | 3 +- .../Aetheria.Shared.Tests/MiningCut3Tests.cs | 2 +- .../Aetheria.Shared.Tests/MinorPowersTests.cs | 2 +- .../OrbitsFromFieldTests.cs | 2 +- .../PiratesFactionCommandTests.cs | 14 - .../PiratesRecordTests.cs | 2 +- .../Aetheria.Shared.Tests/PlaceNamesTests.cs | 4 +- .../RestoredHullsTests.cs | 33 +- tests/Aetheria.Shared.Tests/RunStartTests.cs | 4 +- tests/Aetheria.Shared.Tests/ScenarioTests.cs | 1 - .../StationPlacementTests.cs | 2 +- .../SunsNeverGreenTests.cs | 2 +- tools/AetherDb/MinorPowerFactionsCommand.cs | 18 +- tools/AetherDb/PlaceNamesCommand.cs | 16 +- tools/AetherDb/Program.cs | 19 +- 55 files changed, 45 insertions(+), 1879 deletions(-) delete mode 100644 Assets/Resources/Geonames.meta delete mode 100644 Assets/Resources/Geonames/Australia.csv delete mode 100644 Assets/Resources/Geonames/Australia.csv.meta delete mode 100644 Assets/Resources/Geonames/Congo.csv delete mode 100644 Assets/Resources/Geonames/Congo.csv.meta delete mode 100644 Assets/Resources/Geonames/Germany.csv delete mode 100644 Assets/Resources/Geonames/Germany.csv.meta delete mode 100644 Assets/Resources/Geonames/India.csv delete mode 100644 Assets/Resources/Geonames/India.csv.meta delete mode 100644 Assets/Resources/Geonames/Iran.csv delete mode 100644 Assets/Resources/Geonames/Iran.csv.meta delete mode 100644 Assets/Resources/Geonames/Japan.csv delete mode 100644 Assets/Resources/Geonames/Japan.csv.meta delete mode 100644 Assets/Resources/Geonames/Mexico.csv delete mode 100644 Assets/Resources/Geonames/Mexico.csv.meta delete mode 100644 Assets/Resources/Geonames/Russia.csv delete mode 100644 Assets/Resources/Geonames/Russia.csv.meta delete mode 100644 Assets/Resources/Geonames/Swahili.csv delete mode 100644 Assets/Resources/Geonames/Swahili.csv.meta delete mode 100644 Assets/Resources/Geonames/Thailand.csv delete mode 100644 Assets/Resources/Geonames/Thailand.csv.meta delete mode 100644 Assets/Resources/Geonames/USA.csv delete mode 100644 Assets/Resources/Geonames/USA.csv.meta delete mode 100644 Assets/Resources/Geonames/bad-words.txt delete mode 100644 Assets/Resources/Geonames/bad-words.txt.meta delete mode 100644 Assets/Resources/Geonames/pleiades.csv delete mode 100644 Assets/Resources/Geonames/pleiades.csv.meta delete mode 100644 Assets/Scripts/Editor/NameTools.cs delete mode 100644 Assets/Scripts/Editor/NameTools.cs.meta delete mode 100644 Assets/Scripts/ServerShared/MarkovNameGenerator.cs delete mode 100644 Assets/Scripts/ServerShared/MarkovNameGenerator.cs.meta diff --git a/Assets/Resources/Geonames.meta b/Assets/Resources/Geonames.meta deleted file mode 100644 index 64c8c9a19..000000000 --- a/Assets/Resources/Geonames.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 0f3e518a4c9bcc2469abe6cebdcb3691 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/Australia.csv b/Assets/Resources/Geonames/Australia.csv deleted file mode 100644 index 05587efaa..000000000 --- a/Assets/Resources/Geonames/Australia.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:01bb478f4433dc42d5533e5030ef103ad163deca6bc40fb412025acd12e35c73 -size 442795 diff --git a/Assets/Resources/Geonames/Australia.csv.meta b/Assets/Resources/Geonames/Australia.csv.meta deleted file mode 100644 index 33d8c7dbe..000000000 --- a/Assets/Resources/Geonames/Australia.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 8ba25d5d0ecee31408c7ce7460b4e015 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/Congo.csv b/Assets/Resources/Geonames/Congo.csv deleted file mode 100644 index 5d0f0b66e..000000000 --- a/Assets/Resources/Geonames/Congo.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c84ff44083e4f0d9d9314e7b7095ed4ce2955905f26e7926faa09ed222655729 -size 506722 diff --git a/Assets/Resources/Geonames/Congo.csv.meta b/Assets/Resources/Geonames/Congo.csv.meta deleted file mode 100644 index 6147d0a96..000000000 --- a/Assets/Resources/Geonames/Congo.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 1e43b383bf860524986a6947496776d0 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/Germany.csv b/Assets/Resources/Geonames/Germany.csv deleted file mode 100644 index beb35d1d0..000000000 --- a/Assets/Resources/Geonames/Germany.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:73e4e7231d448879d0a6d8524b3200be75fc8f5011a70cfd670b0897d07eca10 -size 1284208 diff --git a/Assets/Resources/Geonames/Germany.csv.meta b/Assets/Resources/Geonames/Germany.csv.meta deleted file mode 100644 index 244e41eda..000000000 --- a/Assets/Resources/Geonames/Germany.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: ed470b8eb6cc68c4485ddc05d5a17ca1 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/India.csv b/Assets/Resources/Geonames/India.csv deleted file mode 100644 index 6b4eab5c6..000000000 --- a/Assets/Resources/Geonames/India.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:96c66414822c43a5451ce1910c1e545dedb224b0f3e79bc54f945dd484afc6a2 -size 3229324 diff --git a/Assets/Resources/Geonames/India.csv.meta b/Assets/Resources/Geonames/India.csv.meta deleted file mode 100644 index 3f8c176b4..000000000 --- a/Assets/Resources/Geonames/India.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: ca62f6d649360184aaa975c2716dfb27 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/Iran.csv b/Assets/Resources/Geonames/Iran.csv deleted file mode 100644 index 8d2754642..000000000 --- a/Assets/Resources/Geonames/Iran.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b70fbbe6b4832eb1a3f35844bd87c0eb479fc77b6e59d6600b64cbbe7cc1bf16 -size 1073119 diff --git a/Assets/Resources/Geonames/Iran.csv.meta b/Assets/Resources/Geonames/Iran.csv.meta deleted file mode 100644 index 507664280..000000000 --- a/Assets/Resources/Geonames/Iran.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: e2d60c7158f6a01418d78e46c5845729 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/Japan.csv b/Assets/Resources/Geonames/Japan.csv deleted file mode 100644 index d106ec364..000000000 --- a/Assets/Resources/Geonames/Japan.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d58dc0227cc6e3b7622726c155031467b6d7314623c7e3a33aad8ce1462b2c5b -size 1031132 diff --git a/Assets/Resources/Geonames/Japan.csv.meta b/Assets/Resources/Geonames/Japan.csv.meta deleted file mode 100644 index f46c1022d..000000000 --- a/Assets/Resources/Geonames/Japan.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 5dd450a6f5ec92d49871674b33509135 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/Mexico.csv b/Assets/Resources/Geonames/Mexico.csv deleted file mode 100644 index 517228e8f..000000000 --- a/Assets/Resources/Geonames/Mexico.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9a654b8451a08a0c769712f4fb1d96c3c661457a88bb57ec4288eab36eab7ec1 -size 633432 diff --git a/Assets/Resources/Geonames/Mexico.csv.meta b/Assets/Resources/Geonames/Mexico.csv.meta deleted file mode 100644 index 2b0283e78..000000000 --- a/Assets/Resources/Geonames/Mexico.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: b75e17ea31f6a62488628bf9605bd48c -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/Russia.csv b/Assets/Resources/Geonames/Russia.csv deleted file mode 100644 index 6344ff571..000000000 --- a/Assets/Resources/Geonames/Russia.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f4ed581eef681e5357a14efe4e6a1fc52370def7ada6565f4b953e410fa7f39e -size 2375319 diff --git a/Assets/Resources/Geonames/Russia.csv.meta b/Assets/Resources/Geonames/Russia.csv.meta deleted file mode 100644 index 7794294e9..000000000 --- a/Assets/Resources/Geonames/Russia.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 5c1aee30ccf3a1e44bddeb4d550edbf0 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/Swahili.csv b/Assets/Resources/Geonames/Swahili.csv deleted file mode 100644 index 678065eed..000000000 --- a/Assets/Resources/Geonames/Swahili.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fb0f44e7948c637a2b0f9e0ca098b2f4689d065fe6484723acf0bfa34a26d0a5 -size 501002 diff --git a/Assets/Resources/Geonames/Swahili.csv.meta b/Assets/Resources/Geonames/Swahili.csv.meta deleted file mode 100644 index 94bfbb4f8..000000000 --- a/Assets/Resources/Geonames/Swahili.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 63e21276398c8b0449e01d97a088add1 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/Thailand.csv b/Assets/Resources/Geonames/Thailand.csv deleted file mode 100644 index 400b8f1a9..000000000 --- a/Assets/Resources/Geonames/Thailand.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:888c929f0e29728369448e6981bb8a043c49610f4c4390a2cb549bf6b494d3dc -size 143084 diff --git a/Assets/Resources/Geonames/Thailand.csv.meta b/Assets/Resources/Geonames/Thailand.csv.meta deleted file mode 100644 index fd5bd346a..000000000 --- a/Assets/Resources/Geonames/Thailand.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 2923f8bd3a2f0fb4084f64138e6d793d -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/USA.csv b/Assets/Resources/Geonames/USA.csv deleted file mode 100644 index 05bd91140..000000000 --- a/Assets/Resources/Geonames/USA.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:314a30df2033795f04738589c660991e4310f1bfe0578c71bda2a9a72d8a617f -size 954928 diff --git a/Assets/Resources/Geonames/USA.csv.meta b/Assets/Resources/Geonames/USA.csv.meta deleted file mode 100644 index b59cdf567..000000000 --- a/Assets/Resources/Geonames/USA.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: a06c0e26f73d51f489303deadad77030 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/bad-words.txt b/Assets/Resources/Geonames/bad-words.txt deleted file mode 100644 index 5013fea9d..000000000 --- a/Assets/Resources/Geonames/bad-words.txt +++ /dev/null @@ -1,1384 +0,0 @@ - -abbo -abo -abortion -abuse -addict -addicts -adult -africa -african -alla -allah -alligatorbait -amateur -american -anal -analannie -analsex -angie -angry -anus -arab -arabs -areola -argie -aroused -arse -arsehole -asian -ass -assassin -assassinate -assassination -assault -assbagger -assblaster -assclown -asscowboy -asses -assfuck -assfucker -asshat -asshole -assholes -asshore -assjockey -asskiss -asskisser -assklown -asslick -asslicker -asslover -assman -assmonkey -assmunch -assmuncher -asspacker -asspirate -asspuppies -assranger -asswhore -asswipe -athletesfoot -attack -australian -babe -babies -backdoor -backdoorman -backseat -badfuck -balllicker -balls -ballsack -banging -baptist -barelylegal -barf -barface -barfface -bast -bastard -bazongas -bazooms -beaner -beast -beastality -beastial -beastiality -beatoff -beat-off -beatyourmeat -beaver -bestial -bestiality -bi -biatch -bible -bicurious -bigass -bigbastard -bigbutt -bigger -bisexual -bi-sexual -bitch -bitcher -bitches -bitchez -bitchin -bitching -bitchslap -bitchy -biteme -black -blackman -blackout -blacks -blind -blow -blowjob -boang -bogan -bohunk -bollick -bollock -bomb -bombers -bombing -bombs -bomd -bondage -boner -bong -boob -boobies -boobs -booby -boody -boom -boong -boonga -boonie -booty -bootycall -bountybar -bra -brea5t -breast -breastjob -breastlover -breastman -brothel -bugger -buggered -buggery -bullcrap -bulldike -bulldyke -bullshit -bumblefuck -bumfuck -bunga -bunghole -buried -burn -butchbabes -butchdike -butchdyke -butt -buttbang -butt-bang -buttface -buttfuck -butt-fuck -buttfucker -butt-fucker -buttfuckers -butt-fuckers -butthead -buttman -buttmunch -buttmuncher -buttpirate -buttplug -buttstain -byatch -cacker -cameljockey -cameltoe -canadian -cancer -carpetmuncher -carruth -catholic -catholics -cemetery -chav -cherrypopper -chickslick -children's -chin -chinaman -chinamen -chinese -chink -chinky -choad -chode -christ -christian -church -cigarette -cigs -clamdigger -clamdiver -clit -clitoris -clogwog -cocaine -cock -cockblock -cockblocker -cockcowboy -cockfight -cockhead -cockknob -cocklicker -cocklover -cocknob -cockqueen -cockrider -cocksman -cocksmith -cocksmoker -cocksucer -cocksuck -cocksucked -cocksucker -cocksucking -cocktail -cocktease -cocky -cohee -coitus -color -colored -coloured -commie -communist -condom -conservative -conspiracy -coolie -cooly -coon -coondog -copulate -cornhole -corruption -cra5h -crabs -crack -crackpipe -crackwhore -crack-whore -crap -crapola -crapper -crappy -crash -creamy -crime -crimes -criminal -criminals -crotch -crotchjockey -crotchmonkey -crotchrot -cum -cumbubble -cumfest -cumjockey -cumm -cummer -cumming -cumquat -cumqueen -cumshot -cunilingus -cunillingus -cunn -cunnilingus -cunntt -cunt -cunteyed -cuntfuck -cuntfucker -cuntlick -cuntlicker -cuntlicking -cuntsucker -cybersex -cyberslimer -dago -dahmer -dammit -damn -damnation -damnit -darkie -darky -datnigga -dead -deapthroat -death -deepthroat -defecate -dego -demon -deposit -desire -destroy -deth -devil -devilworshipper -dick -dickbrain -dickforbrains -dickhead -dickless -dicklick -dicklicker -dickman -dickwad -dickweed -diddle -die -died -dies -dike -dildo -dingleberry -dink -dipshit -dipstick -dirty -disease -diseases -disturbed -dive -dix -dixiedike -dixiedyke -doggiestyle -doggystyle -dong -doodoo -doo-doo -doom -dope -dragqueen -dragqween -dripdick -drug -drunk -drunken -dumb -dumbass -dumbbitch -dumbfuck -dyefly -dyke -easyslut -eatballs -eatme -eatpussy -ecstacy -ejaculate -ejaculated -ejaculating -ejaculation -enema -enemy -erect -erection -ero -escort -ethiopian -ethnic -european -evl -excrement -execute -executed -execution -executioner -explosion -facefucker -faeces -fag -fagging -faggot -fagot -failed -failure -fairies -fairy -faith -fannyfucker -fart -farted -farting -farty -fastfuck -fat -fatah -fatass -fatfuck -fatfucker -fatso -fckcum -fear -feces -felatio -felch -felcher -felching -fellatio -feltch -feltcher -feltching -fetish -fight -filipina -filipino -fingerfood -fingerfuck -fingerfucked -fingerfucker -fingerfuckers -fingerfucking -fire -firing -fister -fistfuck -fistfucked -fistfucker -fistfucking -fisting -flange -flasher -flatulence -floo -flydie -flydye -fok -fondle -footaction -footfuck -footfucker -footlicker -footstar -fore -foreskin -forni -fornicate -foursome -fourtwenty -fraud -freakfuck -freakyfucker -freefuck -fu -fubar -fuc -fucck -fuck -fucka -fuckable -fuckbag -fuckbuddy -fucked -fuckedup -fucker -fuckers -fuckface -fuckfest -fuckfreak -fuckfriend -fuckhead -fuckher -fuckin -fuckina -fucking -fuckingbitch -fuckinnuts -fuckinright -fuckit -fuckknob -fuckme -fuckmehard -fuckmonkey -fuckoff -fuckpig -fucks -fucktard -fuckwhore -fuckyou -fudgepacker -fugly -fuk -fuks -funeral -funfuck -fungus -fuuck -gangbang -gangbanged -gangbanger -gangsta -gatorbait -gay -gaymuthafuckinwhore -gaysex -geez -geezer -geni -genital -german -getiton -gin -ginzo -gipp -girls -givehead -glazeddonut -gob -god -godammit -goddamit -goddammit -goddamn -goddamned -goddamnes -goddamnit -goddamnmuthafucker -goldenshower -gonorrehea -gonzagas -gook -gotohell -goy -goyim -greaseball -gringo -groe -gross -grostulation -gubba -gummer -gun -gyp -gypo -gypp -gyppie -gyppo -gyppy -hamas -handjob -hapa -harder -hardon -harem -headfuck -headlights -hebe -heeb -hell -henhouse -heroin -herpes -heterosexual -hijack -hijacker -hijacking -hillbillies -hindoo -hiscock -hitler -hitlerism -hitlerist -hiv -ho -hobo -hodgie -hoes -hole -holestuffer -homicide -homo -homobangers -homosexual -honger -honk -honkers -honkey -honky -hook -hooker -hookers -hooters -hore -hork -horn -horney -horniest -horny -horseshit -hosejob -hoser -hostage -hotdamn -hotpussy -hottotrot -hummer -husky -hussy -hustler -hymen -hymie -iblowu -idiot -ikey -illegal -incest -insest -intercourse -interracial -intheass -inthebuff -israel -israeli -israel's -italiano -itch -jackass -jackoff -jackshit -jacktheripper -jade -jap -japanese -japcrap -jebus -jeez -jerkoff -jesus -jesuschrist -jew -jewish -jiga -jigaboo -jigg -jigga -jiggabo -jigger -jiggy -jihad -jijjiboo -jimfish -jism -jiz -jizim -jizjuice -jizm -jizz -jizzim -jizzum -joint -juggalo -jugs -junglebunny -kaffer -kaffir -kaffre -kafir -kanake -kid -kigger -kike -kill -killed -killer -killing -kills -kink -kinky -kissass -kkk -knife -knockers -kock -kondum -koon -kotex -krap -krappy -kraut -kum -kumbubble -kumbullbe -kummer -kumming -kumquat -kums -kunilingus -kunnilingus -kunt -ky -kyke -lactate -laid -lapdance -latin -lesbain -lesbayn -lesbian -lesbin -lesbo -lez -lezbe -lezbefriends -lezbo -lezz -lezzo -liberal -libido -licker -lickme -lies -limey -limpdick -limy -lingerie -liquor -livesex -loadedgun -lolita -looser -loser -lotion -lovebone -lovegoo -lovegun -lovejuice -lovemuscle -lovepistol -loverocket -lowlife -lsd -lubejob -lucifer -luckycammeltoe -lugan -lynch -macaca -mad -mafia -magicwand -mams -manhater -manpaste -marijuana -mastabate -mastabater -masterbate -masterblaster -mastrabator -masturbate -masturbating -mattressprincess -meatbeatter -meatrack -meth -mexican -mgger -mggor -mickeyfinn -mideast -milf -minority -mockey -mockie -mocky -mofo -moky -moles -molest -molestation -molester -molestor -moneyshot -mooncricket -mormon -moron -moslem -mosshead -mothafuck -mothafucka -mothafuckaz -mothafucked -mothafucker -mothafuckin -mothafucking -mothafuckings -motherfuck -motherfucked -motherfucker -motherfuckin -motherfucking -motherfuckings -motherlovebone -muff -muffdive -muffdiver -muffindiver -mufflikcer -mulatto -muncher -munt -murder -murderer -muslim -naked -narcotic -nasty -nastybitch -nastyho -nastyslut -nastywhore -nazi -necro -negro -negroes -negroid -negro's -nig -niger -nigerian -nigerians -nigg -nigga -niggah -niggaracci -niggard -niggarded -niggarding -niggardliness -niggardliness's -niggardly -niggards -niggard's -niggaz -nigger -niggerhead -niggerhole -niggers -nigger's -niggle -niggled -niggles -niggling -nigglings -niggor -niggur -niglet -nignog -nigr -nigra -nigre -nip -nipple -nipplering -nittit -nlgger -nlggor -nofuckingway -nook -nookey -nookie -noonan -nooner -nude -nudger -nuke -nutfucker -nymph -ontherag -oral -orga -orgasim -orgasm -orgies -orgy -osama -paki -palesimian -palestinian -pansies -pansy -panti -panties -payo -pearlnecklace -peck -pecker -peckerwood -pee -peehole -pee-pee -peepshow -peepshpw -pendy -penetration -peni5 -penile -penis -penises -penthouse -period -perv -phonesex -phuk -phuked -phuking -phukked -phukking -phungky -phuq -pi55 -picaninny -piccaninny -pickaninny -piker -pikey -piky -pimp -pimped -pimper -pimpjuic -pimpjuice -pimpsimp -pindick -piss -pissed -pisser -pisses -pisshead -pissin -pissing -pissoff -pistol -pixie -pixy -playboy -playgirl -pocha -pocho -pocketpool -pohm -polack -pom -pommie -pommy -poo -poon -poontang -poop -pooper -pooperscooper -pooping -poorwhitetrash -popimp -porchmonkey -porn -pornflick -pornking -porno -pornography -pornprincess -pot -poverty -premature -pric -prick -prickhead -primetime -propaganda -pros -prostitute -protestant -pu55i -pu55y -pube -pubic -pubiclice -pud -pudboy -pudd -puddboy -puke -puntang -purinapricness -puss -pussie -pussies -pussy -pussycat -pussyeater -pussyfucker -pussylicker -pussylips -pussylover -pussypounder -pusy -quashie -queef -queer -quickie -quim -ra8s -rabbi -racial -racist -radical -radicals -raghead -randy -rape -raped -raper -rapist -rearend -rearentry -rectum -redlight -redneck -reefer -reestie -refugee -reject -remains -rentafuck -republican -rere -retard -retarded -ribbed -rigger -rimjob -rimming -roach -robber -roundeye -rump -russki -russkie -sadis -sadom -samckdaddy -sandm -sandnigger -satan -scag -scallywag -scat -schlong -screw -screwyou -scrotum -scum -semen -seppo -servant -sex -sexed -sexfarm -sexhound -sexhouse -sexing -sexkitten -sexpot -sexslave -sextogo -sextoy -sextoys -sexual -sexually -sexwhore -sexy -sexymoma -sexy-slim -shag -shaggin -shagging -shat -shav -shawtypimp -sheeney -shhit -shinola -shit -shitcan -shitdick -shite -shiteater -shited -shitface -shitfaced -shitfit -shitforbrains -shitfuck -shitfucker -shitfull -shithapens -shithappens -shithead -shithouse -shiting -shitlist -shitola -shitoutofluck -shits -shitstain -shitted -shitter -shitting -shitty -shoot -shooting -shortfuck -showtime -sick -sissy -sixsixsix -sixtynine -sixtyniner -skank -skankbitch -skankfuck -skankwhore -skanky -skankybitch -skankywhore -skinflute -skum -skumbag -slant -slanteye -slapper -slaughter -slav -slave -slavedriver -sleezebag -sleezeball -slideitin -slime -slimeball -slimebucket -slopehead -slopey -slopy -slut -sluts -slutt -slutting -slutty -slutwear -slutwhore -smack -smackthemonkey -smut -snatch -snatchpatch -snigger -sniggered -sniggering -sniggers -snigger's -sniper -snot -snowback -snownigger -sob -sodom -sodomise -sodomite -sodomize -sodomy -sonofabitch -sonofbitch -sooty -sos -soviet -spaghettibender -spaghettinigger -spank -spankthemonkey -sperm -spermacide -spermbag -spermhearder -spermherder -spic -spick -spig -spigotty -spik -spit -spitter -splittail -spooge -spreadeagle -spunk -spunky -squaw -stagg -stiffy -strapon -stringer -stripclub -stroke -stroking -stupid -stupidfuck -stupidfucker -suck -suckdick -sucker -suckme -suckmyass -suckmydick -suckmytit -suckoff -suicide -swallow -swallower -swalow -swastika -sweetness -syphilis -taboo -taff -tampon -tang -tantra -tarbaby -tard -teat -terror -terrorist -teste -testicle -testicles -thicklips -thirdeye -thirdleg -threesome -threeway -timbernigger -tinkle -tit -titbitnipply -titfuck -titfucker -titfuckin -titjob -titlicker -titlover -tits -tittie -titties -titty -tnt -toilet -tongethruster -tongue -tonguethrust -tonguetramp -tortur -torture -tosser -towelhead -trailertrash -tramp -trannie -tranny -transexual -transsexual -transvestite -triplex -trisexual -trojan -trots -tuckahoe -tunneloflove -turd -turnon -twat -twink -twinkie -twobitwhore -uck -uk -unfuckable -upskirt -uptheass -upthebutt -urinary -urinate -urine -usama -uterus -vagina -vaginal -vatican -vibr -vibrater -vibrator -vietcong -violence -virgin -virginbreaker -vomit -vulva -wab -wank -wanker -wanking -waysted -weapon -weenie -weewee -welcher -welfare -wetb -wetback -wetspot -whacker -whash -whigger -whiskey -whiskeydick -whiskydick -whit -whitenigger -whites -whitetrash -whitey -whiz -whop -whore -whorefucker -whorehouse -wigger -willie -williewanker -willy -wn -wog -women's -wop -wtf -wuss -wuzzie -xtc -xxx -yankee -yellowman -zigabo -zipperhead diff --git a/Assets/Resources/Geonames/bad-words.txt.meta b/Assets/Resources/Geonames/bad-words.txt.meta deleted file mode 100644 index ff605e0c5..000000000 --- a/Assets/Resources/Geonames/bad-words.txt.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 790ef350f8658b04480e0645919a21fa -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/pleiades.csv b/Assets/Resources/Geonames/pleiades.csv deleted file mode 100644 index 581d44ff7..000000000 --- a/Assets/Resources/Geonames/pleiades.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:45b0dbc07d492958b26de3695427076d270bdd0525ab45c415be72560ebccb5f -size 400944 diff --git a/Assets/Resources/Geonames/pleiades.csv.meta b/Assets/Resources/Geonames/pleiades.csv.meta deleted file mode 100644 index dc0edb8f1..000000000 --- a/Assets/Resources/Geonames/pleiades.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: b532694a5ec3cc6458d42aa2835537b5 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Settings.asset b/Assets/Resources/Settings.asset index f92142d6b..948c9b1e8 100644 --- a/Assets/Resources/Settings.asset +++ b/Assets/Resources/Settings.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab2cdc0732716dcbfa4875e90e598b0dff3f0c8d4f871cc180649a3db2e4cf0c -size 12562 +oid sha256:2f11b85511c2379ad472446e2e1cc6c26b8d71c6308082db7384b57d458cdd03 +size 12450 diff --git a/Assets/Scripts/Editor/NameTools.cs b/Assets/Scripts/Editor/NameTools.cs deleted file mode 100644 index a048dce89..000000000 --- a/Assets/Scripts/Editor/NameTools.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Buffers.Text; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using MessagePack; -using CultMath; -using UnityEngine; -using UnityEditor; -using Random = CultMath.Random; - -public class NameTools : EditorWindow -{ - public int NameGeneratorMinLength = 5; - public int NameGeneratorMaxLength = 10; - public int NameGeneratorOrder = 4; - private int minWordLength = 4; - private TextAsset nameFile; - private MarkovNameGenerator _nameGenerator; - private bool _stripNumberTokens; - - - // Add menu named "My Window" to the Window menu - [MenuItem("Window/Aetheria/Name Tools")] - static void Init() - { - // Get existing open window or if none, make a new one: - NameTools window = (NameTools)EditorWindow.GetWindow(typeof(NameTools)); - window.Show(); - } - - bool HasNonASCIIChars(string str) - { - return (System.Text.Encoding.UTF8.GetByteCount(str) != str.Length); - } - - void OnGUI() - { - - nameFile = (TextAsset) EditorGUILayout.ObjectField("Name File", nameFile, typeof(TextAsset), false); - minWordLength = EditorGUILayout.IntField("Minimum File Word Length", minWordLength); - NameGeneratorMinLength = EditorGUILayout.IntField("Generated Minimum Word Length", NameGeneratorMinLength); - NameGeneratorMaxLength = EditorGUILayout.IntField("Generated Maximum Word Length", NameGeneratorMaxLength); - NameGeneratorOrder = EditorGUILayout.IntField("Generator Order", NameGeneratorOrder); - _stripNumberTokens = GUILayout.Toggle(_stripNumberTokens, "Strip Number Tokens"); - - if (GUILayout.Button("Clean Name File")) - { - var lines = nameFile.text.Split('\n'); - using StreamWriter outputFile = new StreamWriter(Path.Combine(Application.dataPath, nameFile.name + ".csv")); - var names = new HashSet(); - foreach (var line in lines) - { - var tokens = line.Split(',', ' '); - foreach (var t in tokens) - { - if (!HasNonASCIIChars(t)) - { - var s = new string(t.Where(c => char.IsLetter(c) || c == '-' || c == '`' || c == '\'').ToArray()).Trim().Trim('`','-'); - if(s.Length >= minWordLength && !names.Contains(s)) - { - names.Add(s); - outputFile.WriteLine(s); - } - } - } - } - } - - if (GUILayout.Button("Process Name File") && nameFile != null) - { - var names = new HashSet(); - var lines = nameFile.text.Split('\n'); - foreach (var line in lines) - { - foreach(var word in line.ToUpperInvariant().Split(' ', ',', '.', '"')) - if (word.Length >= minWordLength && !names.Contains(word)) - names.Add(word); - } - Debug.Log($"Found {lines.Length} lines, with {names.Count} unique names!"); - var random = new Random(1337); - _nameGenerator = new MarkovNameGenerator(ref random, names, NameGeneratorOrder, NameGeneratorMinLength, NameGeneratorMaxLength); - } - - if (_nameGenerator != null) - { - if (GUILayout.Button("Generate Name")) - { - Debug.Log(_nameGenerator.NextName); - } - } - } -} \ No newline at end of file diff --git a/Assets/Scripts/Editor/NameTools.cs.meta b/Assets/Scripts/Editor/NameTools.cs.meta deleted file mode 100644 index e0022b308..000000000 --- a/Assets/Scripts/Editor/NameTools.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 88b5f578fe63a904b997331630e61416 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/GameSettings.cs b/Assets/Scripts/Gameplay/GameSettings.cs index 65e134755..9d89c5822 100644 --- a/Assets/Scripts/Gameplay/GameSettings.cs +++ b/Assets/Scripts/Gameplay/GameSettings.cs @@ -22,7 +22,6 @@ public class GameSettings : ScriptableObject public float MinimapAsteroidSize = 3; public float MinimapIconSize = 1f / 32; public float PlanetRotationSpeed = 1; - public NameGeneratorSettings NameGeneratorSettings; public TutorialGenerationSettings TutorialGenerationSettings; public SectorBackgroundSettings TutorialBackgroundSettings; public SectorBackgroundSettings SectorBackgroundSettings; diff --git a/Assets/Scripts/ServerShared/AetheriaStores.cs b/Assets/Scripts/ServerShared/AetheriaStores.cs index 4697fe272..4afb00b1d 100644 --- a/Assets/Scripts/ServerShared/AetheriaStores.cs +++ b/Assets/Scripts/ServerShared/AetheriaStores.cs @@ -7,7 +7,7 @@ using GameCult.Caching.MessagePack; // Aetheria's stores: one home store per type, composed here and nowhere else. public static class AetheriaStores { - public static readonly Type[] CatalogTypes = { typeof(ItemData), typeof(ShipAuthoring), typeof(Faction), typeof(FactionProductData), typeof(PersonalityAttribute), typeof(NameFile), typeof(InputLayout), typeof(Loadout), typeof(FieldKindData), typeof(VerseGrammar), typeof(VerseVerb) }; + public static readonly Type[] CatalogTypes = { typeof(ItemData), typeof(ShipAuthoring), typeof(Faction), typeof(FactionProductData), typeof(PersonalityAttribute), typeof(InputLayout), typeof(Loadout), typeof(FieldKindData), typeof(VerseGrammar), typeof(VerseVerb) }; public static readonly Type[] RunTypes = { typeof(OrbitData), typeof(BodyData), typeof(SavedZone), typeof(SavedGame), typeof(ProvenanceLedger) }; public static readonly Type[] PlayerTypes = { typeof(PlayerSettings) }; diff --git a/Assets/Scripts/ServerShared/Corporations.cs b/Assets/Scripts/ServerShared/Corporations.cs index fb826cf8e..0b9be208d 100644 --- a/Assets/Scripts/ServerShared/Corporations.cs +++ b/Assets/Scripts/ServerShared/Corporations.cs @@ -37,8 +37,7 @@ public class Faction [InspectableColor, JsonProperty("secondaryColor"), Key(8)] public float3 SecondaryColor; - [Inspectable, JsonProperty("nameFile"), Key(9)] - public CultRecordRef GeonameFile; + // Key 9 was the faction's name-file link, retired by ruling faction-curated-place-names; never reuse it. [Inspectable, JsonProperty("bossHull"), Key(10)] public CultRecordRef BossHull; @@ -78,9 +77,3 @@ public class FactionPlace public string Name; } -[CultDocument("aetheria.namefile", "1"), Inspectable, MessagePackObject] -public class NameFile -{ - [CultName, Key(1)] public string Name; - [Key(2)] public string[] Names; -} diff --git a/Assets/Scripts/ServerShared/Galaxy.cs b/Assets/Scripts/ServerShared/Galaxy.cs index 41497ad68..504f419b7 100644 --- a/Assets/Scripts/ServerShared/Galaxy.cs +++ b/Assets/Scripts/ServerShared/Galaxy.cs @@ -20,7 +20,6 @@ public class Galaxy public HashSet DiscoveredZones = new HashSet(); public SectorBackgroundSettings Background { get; } - public NameGeneratorSettings NameGeneratorSettings { get; } public Faction[] Factions { get; } public GalaxyZone[] Zones { get; } public GalaxyZone Entrance { get; } @@ -95,7 +94,6 @@ public class Galaxy public Galaxy( SectorGenerationSettings settings, SectorBackgroundSettings background, - NameGeneratorSettings nameGeneratorSettings, CultCache cache, Action log, Action progressCallback = null, @@ -161,7 +159,6 @@ public class Galaxy public Galaxy( TutorialGenerationSettings settings, SectorBackgroundSettings background, - NameGeneratorSettings nameGeneratorSettings, CultCache cache, PlayerSettings playerSettings, DirectoryInfo narrativeDirectory, diff --git a/Assets/Scripts/ServerShared/GalaxyStage.cs b/Assets/Scripts/ServerShared/GalaxyStage.cs index 8b6e5a1a6..cd8feefed 100644 --- a/Assets/Scripts/ServerShared/GalaxyStage.cs +++ b/Assets/Scripts/ServerShared/GalaxyStage.cs @@ -15,7 +15,6 @@ public sealed class GalaxyStage private readonly SectorBackgroundSettings _sectorBackground; private readonly TutorialGenerationSettings _prelude; private readonly SectorBackgroundSettings _preludeBackground; - private readonly NameGeneratorSettings _names; private readonly CultCache _cache; private readonly PlayerSettings _playerSettings; private readonly DirectoryInfo _narrativeDirectory; @@ -28,7 +27,6 @@ public sealed class GalaxyStage SectorBackgroundSettings sectorBackground, TutorialGenerationSettings prelude, SectorBackgroundSettings preludeBackground, - NameGeneratorSettings names, CultCache cache, PlayerSettings playerSettings, DirectoryInfo narrativeDirectory, @@ -40,7 +38,6 @@ public sealed class GalaxyStage _sectorBackground = sectorBackground; _prelude = prelude; _preludeBackground = preludeBackground; - _names = names; _cache = cache; _playerSettings = playerSettings; _narrativeDirectory = narrativeDirectory; @@ -64,7 +61,7 @@ public sealed class GalaxyStage var background = Copy(_sectorBackground); var random = new Random(Seed); background.NoisePosition = random.NextFloat() * 1000; - return new Galaxy(_sector, background, _names, _cache, _log, _progress, Seed); + return new Galaxy(_sector, background, _cache, _log, _progress, Seed); } // A prelude galaxy: the tutorial settings and their fixed factions, centred in dense cloud. @@ -81,7 +78,7 @@ public sealed class GalaxyStage background.NoisePosition = random.NextFloat() * 1000; _progress?.Invoke($"Finding Galaxy Position: iteration {iteration++}"); } while (background.CloudDensity(float2(.5f)) < .5f); - return new Galaxy(cast, background, _names, _cache, _playerSettings, _narrativeDirectory, _log, _progress, Seed); + return new Galaxy(cast, background, _cache, _playerSettings, _narrativeDirectory, _log, _progress, Seed); } private static SectorBackgroundSettings Copy(SectorBackgroundSettings settings) => diff --git a/Assets/Scripts/ServerShared/MarkovNameGenerator.cs b/Assets/Scripts/ServerShared/MarkovNameGenerator.cs deleted file mode 100644 index 528684f40..000000000 --- a/Assets/Scripts/ServerShared/MarkovNameGenerator.cs +++ /dev/null @@ -1,129 +0,0 @@ -/* 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 static CultMath.math; -using Random = CultMath.Random; - -//Generates random names based on the statistical weight of letter sequences -//in a collection of sample names -public class MarkovNameGenerator -{ - private Dictionary> _chains = new Dictionary>(); - private List _samples = new List(); - // The names already given, shared by every generator that must not repeat another's: a galaxy passes one set to - // all its factions' generators. Never process-wide, so one galaxy's names cannot change the next galaxy's. - private readonly HashSet _used; - private int _order; - private int _minLength; - private int _maxLength; - private Random _random; - - public MarkovNameGenerator(ref Random random, IEnumerable sampleNames, NameGeneratorSettings settings, HashSet used) - { - _used = used; - var names = new HashSet(); - foreach (var line in sampleNames) - { - foreach(var word in line.ToUpperInvariant().Split(' ', ',', '.', '"')) - if (word.Length >= settings.NameGeneratorMinLength && !names.Contains(word)) - names.Add(word); - } - - _random = random; - - _order = max(1, settings.NameGeneratorOrder); - _minLength = max(1, settings.NameGeneratorMinLength); - _maxLength = settings.NameGeneratorMaxLength; - - Initialize(names); - } - - public MarkovNameGenerator(ref Random random, IEnumerable sampleNames, int order, int minLength, int maxLength) - { - _used = new HashSet(); - _random = random; - - _order = max(1, order); - _minLength = max(1, minLength); - _maxLength = maxLength; - - Initialize(sampleNames); - } - - private void Initialize(IEnumerable sampleNames) - { - //split comma delimited lines - foreach (string line in sampleNames) - { - string[] tokens = line.Split(' ','\'','(',')'); - foreach (string word in tokens) - { - string upper = word.Trim().ToLowerInvariant(); - if (upper.Length < _order + 1) - continue; - _samples.Add(upper + '|'); // Add end character - } - } - - //Build chains - foreach (string word in _samples) - { - for (int letter = 0; letter < word.Length - _order; letter++) - { - string token = word.Substring(letter, _order); - List entry = null; - if (_chains.ContainsKey(token)) - entry = _chains[token]; - else - { - entry = new List(); - _chains[token] = entry; - } - entry.Add(word[letter + _order]); - } - } - } - - //Get the next random name - public string NextName - { - get - { - - string s = ""; - do - { - int n = _random.NextInt(0,_samples.Count); - //int nameLength = _samples[n].Length; - s = _samples[n].Substring(0, _order);//get a random token at the start of a sample word - while (true)//(s.Length < nameLength) - { - string token = s.Substring(s.Length - _order, _order); - char c = GetLetter(token); - if (c != '?' && c != '|') - s += c;//GetLetter(token); - else - break; - } - s = s.Substring(0, 1).ToUpper() + s.Substring(1); - } - while (_used.Contains(s) || s.Length < _minLength || s.Length > _maxLength); - _used.Add(s); - return s; - } - } - - //Get a random letter from the chain - private char GetLetter(string token) - { - if (!_chains.ContainsKey(token)) - return '?'; - List letters = _chains[token]; - int n = _random.NextInt(0,letters.Count); - return letters[n]; - } -} \ No newline at end of file diff --git a/Assets/Scripts/ServerShared/MarkovNameGenerator.cs.meta b/Assets/Scripts/ServerShared/MarkovNameGenerator.cs.meta deleted file mode 100644 index a0219e5ec..000000000 --- a/Assets/Scripts/ServerShared/MarkovNameGenerator.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 33c3b119ccc9de447b8c8318b6f0d1af -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/ServerShared/Settings.cs b/Assets/Scripts/ServerShared/Settings.cs index 85d1f3d96..135fda882 100644 --- a/Assets/Scripts/ServerShared/Settings.cs +++ b/Assets/Scripts/ServerShared/Settings.cs @@ -45,14 +45,6 @@ public class GalaxyShapeSettings public float TwistExponent = 2; } -[Serializable, MessagePackObject(keyAsPropertyName:true), JsonObject] -public class NameGeneratorSettings -{ - public int NameGeneratorMinLength = 5; - public int NameGeneratorMaxLength = 10; - public int NameGeneratorOrder = 4; -} - [Serializable, MessagePackObject(keyAsPropertyName:true), JsonObject] public class SectorBackgroundSettings { diff --git a/Assets/Scripts/UI/MainMenu.cs b/Assets/Scripts/UI/MainMenu.cs index c442c2221..e72ce5263 100644 --- a/Assets/Scripts/UI/MainMenu.cs +++ b/Assets/Scripts/UI/MainMenu.cs @@ -177,7 +177,6 @@ public class MainMenu : MonoBehaviour Settings.SectorBackgroundSettings, Settings.TutorialGenerationSettings, Settings.TutorialBackgroundSettings, - Settings.NameGeneratorSettings, ActionGameManager.CultCache, ActionGameManager.PlayerSettings, ActionGameManager.GameDataDirectory.CreateSubdirectory("Narrative"), diff --git a/tests/Aetheria.Shared.Tests/ChildrenWithinReachTests.cs b/tests/Aetheria.Shared.Tests/ChildrenWithinReachTests.cs index 96ffb8b82..83aeded04 100644 --- a/tests/Aetheria.Shared.Tests/ChildrenWithinReachTests.cs +++ b/tests/Aetheria.Shared.Tests/ChildrenWithinReachTests.cs @@ -38,7 +38,7 @@ public sealed class ChildrenWithinReachTests : IClassFixture("TutorialGenerationSettings"), authored.Read("TutorialBackgroundSettings"), - authored.Read("NameGeneratorSettings"), cache, new PlayerSettings(), + cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(dir, "Narrative")), _ => { }, null, seed); var items = new ItemManager(cache, new ProvenanceLedger(), authored.Read("GameplaySettings"), _ => { }); var planetSettings = authored.Read("PlanetSettings"); diff --git a/tests/Aetheria.Shared.Tests/ConsumableSupplyTests.cs b/tests/Aetheria.Shared.Tests/ConsumableSupplyTests.cs index cd968e885..22ebe7c89 100644 --- a/tests/Aetheria.Shared.Tests/ConsumableSupplyTests.cs +++ b/tests/Aetheria.Shared.Tests/ConsumableSupplyTests.cs @@ -280,8 +280,7 @@ public sealed class ConsumableSupplyTests : IDisposable var authored = AuthoredSettings.Load(repo); var galaxy = new Galaxy(authored.Read("TutorialGenerationSettings"), - authored.Read("TutorialBackgroundSettings"), authored.Read("NameGeneratorSettings"), - cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(_root, "Narrative")), _ => { }, null, 1); + authored.Read("TutorialBackgroundSettings"), cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(_root, "Narrative")), _ => { }, null, 1); var items = new ItemManager(cache, new ProvenanceLedger(), authored.Read("GameplaySettings"), _ => { }); int stations = 0, ships = 0; diff --git a/tests/Aetheria.Shared.Tests/MiningCut3Tests.cs b/tests/Aetheria.Shared.Tests/MiningCut3Tests.cs index e77d6c89b..f3383d665 100644 --- a/tests/Aetheria.Shared.Tests/MiningCut3Tests.cs +++ b/tests/Aetheria.Shared.Tests/MiningCut3Tests.cs @@ -814,7 +814,7 @@ public sealed partial class MiningCut3Tests : IDisposable try { galaxy = new Galaxy(RestoredHullsTests.TutorialGalaxySettings(), RestoredHullsTests.TutorialBackgroundSettings(), - RestoredHullsTests.TutorialNameSettings(), cache, new PlayerSettings(), new DirectoryInfo(dir), _ => { }, null, seed); + cache, new PlayerSettings(), new DirectoryInfo(dir), _ => { }, null, seed); } catch (Exception) { continue; } // Galaxy's own seed-dependent flakiness (RestoredHullsTests) var items = new ItemManager(cache, new ProvenanceLedger(), GameSettings(), _ => { }); diff --git a/tests/Aetheria.Shared.Tests/MinorPowersTests.cs b/tests/Aetheria.Shared.Tests/MinorPowersTests.cs index 7416f08df..1988a033e 100644 --- a/tests/Aetheria.Shared.Tests/MinorPowersTests.cs +++ b/tests/Aetheria.Shared.Tests/MinorPowersTests.cs @@ -57,7 +57,7 @@ public sealed class MinorPowersTests : IDisposable foreach (var shortName in Minor) { var faction = Assert.Single(factions, f => f.ShortName == shortName); - Assert.NotNull(cache.Get(faction.GeonameFile)); + Assert.NotEmpty(faction.Places); Assert.True(faction.InfluenceDistance > 0, shortName); var named = faction.Allegiance.Keys.Select(k => k.Key).ToArray(); var expected = factions.Where(f => f != faction && f.ShortName != "Pirates").Select(f => cache.RefOf(f).Key).ToArray(); diff --git a/tests/Aetheria.Shared.Tests/OrbitsFromFieldTests.cs b/tests/Aetheria.Shared.Tests/OrbitsFromFieldTests.cs index 6a3b1c618..66a8e31d8 100644 --- a/tests/Aetheria.Shared.Tests/OrbitsFromFieldTests.cs +++ b/tests/Aetheria.Shared.Tests/OrbitsFromFieldTests.cs @@ -253,7 +253,7 @@ public sealed class OrbitsFromFieldTests : IDisposable var authored = AuthoredSettings.Load(RunStartTests.FindRepoRoot()); var tutorial = authored.Read("TutorialGenerationSettings"); var galaxy = new Galaxy(tutorial, authored.Read("TutorialBackgroundSettings"), - authored.Read("NameGeneratorSettings"), cache, new PlayerSettings(), + cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(dir, "Narrative")), _ => { }, null, seed); var items = new ItemManager(cache, new ProvenanceLedger(), authored.Read("GameplaySettings"), _ => { }); var pack = ZoneGenerator.GenerateZone(items, authored.Read("ZoneSettings"), authored.Read("PlanetSettings"), galaxy, galaxy.Entrance, diff --git a/tests/Aetheria.Shared.Tests/PiratesFactionCommandTests.cs b/tests/Aetheria.Shared.Tests/PiratesFactionCommandTests.cs index 0a3cb161d..99ec92249 100644 --- a/tests/Aetheria.Shared.Tests/PiratesFactionCommandTests.cs +++ b/tests/Aetheria.Shared.Tests/PiratesFactionCommandTests.cs @@ -82,7 +82,6 @@ public sealed class PiratesFactionCommandTests : IDisposable var db = AetherDb.Open(root: _root); try { - Assert.Equal("pleiades", db.Cache.Get(faction.GeonameFile).Name); var expected = new (string Design, string Name, string Description)[] { ("Autocannon", "🫳🔫", "Serial number filed off. Still shoots."), @@ -172,17 +171,4 @@ public sealed class PiratesFactionCommandTests : IDisposable Assert.Throws(() => Program.PiratesFaction(apply: true, root: _root)); Assert.Empty(Read().Pirates); } - - [Fact] - public void A_missing_name_file_refuses_and_lands_nothing() - { - var db = AetherDb.Open(catalogWritable: true, root: _root); - var pleiades = db.Cache.GetAll().Single(file => file.Name == "pleiades"); - Assert.True(db.Cache.Remove(db.Cache.RefOf(pleiades).Key)); - db.Cache.FlushAsync().Wait(); - db.Cache.Dispose(); - - Assert.Throws(() => Program.PiratesFaction(apply: true, root: _root)); - Assert.Empty(Read().Pirates); - } } diff --git a/tests/Aetheria.Shared.Tests/PiratesRecordTests.cs b/tests/Aetheria.Shared.Tests/PiratesRecordTests.cs index 637b2e65e..8b8367701 100644 --- a/tests/Aetheria.Shared.Tests/PiratesRecordTests.cs +++ b/tests/Aetheria.Shared.Tests/PiratesRecordTests.cs @@ -25,7 +25,7 @@ public sealed partial class RunStartTests var others = _cache.GetAll().Where(faction => faction != pirates).Select(faction => _cache.RefOf(faction)).ToArray(); Assert.NotEmpty(others); Assert.All(others, other => Assert.True(pirates.Allegiance.ContainsKey(other))); - Assert.NotNull(_cache.Get(pirates.GeonameFile)); + Assert.NotEmpty(pirates.Places); var products = PiratesProducts(); Assert.Equal(8, products.Length); diff --git a/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs b/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs index c2d910a25..a26925a36 100644 --- a/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs +++ b/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs @@ -31,9 +31,9 @@ public sealed class PlaceNamesTests : IDisposable private static Galaxy Build(CultCache cache, bool prelude, uint seed) => prelude ? new Galaxy(Authored.Read("TutorialGenerationSettings"), Authored.Read("TutorialBackgroundSettings"), - Authored.Read("NameGeneratorSettings"), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed) + cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed) : new Galaxy(Authored.Read("SectorGenerationSettings"), Authored.Read("SectorBackgroundSettings"), - Authored.Read("NameGeneratorSettings"), cache, _ => { }, null, seed); + cache, _ => { }, null, seed); // A prelude galaxy sometimes refuses to generate for a seed (RestoredHullsTests calls it Galaxy's own seed-dependent // flakiness); those seeds are skipped, and a test that depends on how many built says so. diff --git a/tests/Aetheria.Shared.Tests/RestoredHullsTests.cs b/tests/Aetheria.Shared.Tests/RestoredHullsTests.cs index 6711f894a..ed63994cd 100644 --- a/tests/Aetheria.Shared.Tests/RestoredHullsTests.cs +++ b/tests/Aetheria.Shared.Tests/RestoredHullsTests.cs @@ -91,9 +91,6 @@ public sealed class RestoredHullsTests NoisePosition = 536.5106f, CloudExponent = 10, CloudAmplitude = .01f, }; - internal static NameGeneratorSettings TutorialNameSettings() => - new NameGeneratorSettings { NameGeneratorMinLength = 5, NameGeneratorMaxLength = 10, NameGeneratorOrder = 3 }; - // A station's orbit is a copy of its source planet's own (Parent, Distance) (ZoneGenerator.CreateLagrangeOrbit), // so it always matches that one planet. It matches MORE than one planet only when the source planet shares its // distance with a sibling under the same parent -- exactly what ZoneGenerator's potentialLagrangePoints filter @@ -153,8 +150,7 @@ public sealed class RestoredHullsTests var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { }); for (var seed = 1u; seed <= 64u; seed++) { - var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(), - cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); + var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); foreach (var zone in galaxy.Zones.Where(z => !ReferenceEquals(z, galaxy.Entrance))) { var orbits = ParentedPlanetOrbits(cache, ZoneGenerator.GenerateZone(items, TutorialZoneSettings(), AuthoredPlanetSettings(), galaxy, zone, isTutorial: false, ambient: false)); @@ -366,7 +362,6 @@ public sealed class RestoredHullsTests var zoneSettings = TutorialZoneSettings(); var tutorialSettings = TutorialGalaxySettings(); var background = TutorialBackgroundSettings(); - var names = TutorialNameSettings(); var gameData = TestCatalog.Repo; var sawGenuineLagrangeOrbit = false; for (uint seed = 1; seed <= 25; seed++) @@ -375,7 +370,7 @@ public sealed class RestoredHullsTests try { using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun); - var galaxy = new Galaxy(tutorialSettings, background, names, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); + var galaxy = new Galaxy(tutorialSettings, background, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { }); var pack = ZoneGenerator.GenerateZone(items, zoneSettings, AuthoredPlanetSettings(), galaxy, galaxy.Entrance, isTutorial: true); @@ -407,7 +402,6 @@ public sealed class RestoredHullsTests var zoneSettings = TutorialZoneSettings(); var tutorialSettings = TutorialGalaxySettings(); var background = TutorialBackgroundSettings(); - var names = TutorialNameSettings(); var gameData = TestCatalog.Repo; var sawGenuineCandidate = false; for (uint seed = 1; seed <= 25; seed++) @@ -416,7 +410,7 @@ public sealed class RestoredHullsTests try { using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun); - var galaxy = new Galaxy(tutorialSettings, background, names, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); + var galaxy = new Galaxy(tutorialSettings, background, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { }); var pack = ZoneGenerator.GenerateZone(items, zoneSettings, AuthoredPlanetSettings(), galaxy, galaxy.Entrance, isTutorial: true); @@ -454,8 +448,7 @@ public sealed class RestoredHullsTests try { using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun); - var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(), - cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u); + var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u); var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { }); var zoneSettings = TutorialZoneSettings(); @@ -490,8 +483,7 @@ public sealed class RestoredHullsTests try { using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun); - var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(), - cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u); + var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u); var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { }); var zoneSettings = TutorialZoneSettings(); @@ -532,7 +524,6 @@ public sealed class RestoredHullsTests var zoneSettings = TutorialZoneSettings(); var tutorialSettings = TutorialGalaxySettings(); var background = TutorialBackgroundSettings(); - var names = TutorialNameSettings(); var gameData = TestCatalog.Repo; var checkedAny = false; @@ -542,7 +533,7 @@ public sealed class RestoredHullsTests try { using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun); - var galaxy = new Galaxy(tutorialSettings, background, names, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); + var galaxy = new Galaxy(tutorialSettings, background, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { }); var pack = ZoneGenerator.GenerateZone(items, zoneSettings, AuthoredPlanetSettings(), galaxy, galaxy.Entrance, isTutorial: true); @@ -677,8 +668,7 @@ public sealed class RestoredHullsTests // real zone from a real galaxy (e.g. one already known to have no Lagrange candidate) and make it the Entrance. private static Galaxy BuildGalaxyWithForcedEntrance(CultCache cache, uint seed, string entranceZoneName) { - var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(), - cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); + var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); var target = galaxy.Zones.Single(z => z.Name == entranceZoneName); var factions = galaxy.Factions; @@ -789,8 +779,7 @@ public sealed class RestoredHullsTests try { using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun); - var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(), - cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u); + var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u); var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { }); int StationCount(GalaxyZone zone, bool isTutorial) => @@ -830,8 +819,7 @@ public sealed class RestoredHullsTests try { using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun); - var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(), - cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u); + var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u); var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { }); var pack = ZoneGenerator.GenerateZone(items, TutorialZoneSettings(), AuthoredPlanetSettings(), galaxy, galaxy.Entrance, isTutorial: true); @@ -866,8 +854,7 @@ public sealed class RestoredHullsTests // Must fail if that guard is dropped for real. private static Galaxy BuildGalaxyWithBoostedPresence(CultCache cache, uint seed, string zoneName) { - var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(), - cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); + var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); var target = galaxy.Zones.Single(z => z.Name == zoneName); var factions = galaxy.Factions; diff --git a/tests/Aetheria.Shared.Tests/RunStartTests.cs b/tests/Aetheria.Shared.Tests/RunStartTests.cs index 2ce12782e..efedc20dd 100644 --- a/tests/Aetheria.Shared.Tests/RunStartTests.cs +++ b/tests/Aetheria.Shared.Tests/RunStartTests.cs @@ -51,7 +51,7 @@ public sealed partial class RunStartTests : IDisposable _planetSettings = authored.Read("PlanetSettings"); var tutorial = _tutorialSettings = authored.Read("TutorialGenerationSettings"); _galaxy = new Galaxy(tutorial, authored.Read("TutorialBackgroundSettings"), - authored.Read("NameGeneratorSettings"), _cache, new PlayerSettings(), + _cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(_root, "Narrative")), _ => { }, null, GalaxySeed); _items = new ItemManager(_cache, new ProvenanceLedger(), authored.Read("GameplaySettings"), _ => { }); _protagonist = _galaxy.ResolveFaction(tutorial.ProtagonistFaction); @@ -122,7 +122,7 @@ public sealed partial class RunStartTests : IDisposable var authored = AuthoredSettings.Load(FindRepoRoot()); return new Galaxy(authored.Read("SectorGenerationSettings"), authored.Read("SectorBackgroundSettings"), - authored.Read("NameGeneratorSettings"), _cache, _ => { }, null, GalaxySeed); + _cache, _ => { }, null, GalaxySeed); } // Every item a station carries, equipped or stocked, with the product it was made as. diff --git a/tests/Aetheria.Shared.Tests/ScenarioTests.cs b/tests/Aetheria.Shared.Tests/ScenarioTests.cs index 8e435d2f0..438a01085 100644 --- a/tests/Aetheria.Shared.Tests/ScenarioTests.cs +++ b/tests/Aetheria.Shared.Tests/ScenarioTests.cs @@ -55,7 +55,6 @@ public sealed partial class RunStartTests authored.Read("SectorBackgroundSettings"), authored.Read("TutorialGenerationSettings"), authored.Read("TutorialBackgroundSettings"), - authored.Read("NameGeneratorSettings"), _cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(_root, "Narrative")), _ => { }, null, clock); } diff --git a/tests/Aetheria.Shared.Tests/StationPlacementTests.cs b/tests/Aetheria.Shared.Tests/StationPlacementTests.cs index 759e3e83d..dfb2074ad 100644 --- a/tests/Aetheria.Shared.Tests/StationPlacementTests.cs +++ b/tests/Aetheria.Shared.Tests/StationPlacementTests.cs @@ -54,7 +54,7 @@ public sealed class StationPlacementTests : IDisposable var settings = authored.Read("GameplaySettings"); var galaxy = new Galaxy(authored.Read("TutorialGenerationSettings"), authored.Read("TutorialBackgroundSettings"), - authored.Read("NameGeneratorSettings"), cache, new PlayerSettings(), + cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(dir, "Narrative")), _ => { }, null, seed); var items = new ItemManager(cache, new ProvenanceLedger(), settings, _ => { }); stock?.Invoke(items); diff --git a/tests/Aetheria.Shared.Tests/SunsNeverGreenTests.cs b/tests/Aetheria.Shared.Tests/SunsNeverGreenTests.cs index 85cbe7ec8..517810c42 100644 --- a/tests/Aetheria.Shared.Tests/SunsNeverGreenTests.cs +++ b/tests/Aetheria.Shared.Tests/SunsNeverGreenTests.cs @@ -183,7 +183,7 @@ public sealed class SunsNeverGreenTests : IDisposable var authored = AuthoredSettings.Load(RunStartTests.FindRepoRoot()); var tutorial = authored.Read("TutorialGenerationSettings"); var galaxy = new Galaxy(tutorial, authored.Read("TutorialBackgroundSettings"), - authored.Read("NameGeneratorSettings"), cache, new PlayerSettings(), + cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(dir, "Narrative")), _ => { }, null, seed); var items = new ItemManager(cache, new ProvenanceLedger(), authored.Read("GameplaySettings"), _ => { }); var zoneSettings = authored.Read("ZoneSettings"); diff --git a/tools/AetherDb/MinorPowerFactionsCommand.cs b/tools/AetherDb/MinorPowerFactionsCommand.cs index fbe0a39b6..9853d07ba 100644 --- a/tools/AetherDb/MinorPowerFactionsCommand.cs +++ b/tools/AetherDb/MinorPowerFactionsCommand.cs @@ -19,23 +19,22 @@ 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); + 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 float3(.72f, .36f, .22f), new float3(.12f, .36f, .25f), 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 float3(.07f, .09f, .15f), new float3(.90f, .85f, .74f), 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, + new float3(.42f, .30f, .22f), new float3(.30f, .38f, .52f), 3, .5f, .5f, Array.Empty<(string, float)>()), }; @@ -51,17 +50,14 @@ public static class MinorPowerFactionsCommand var apply = args.Contains("apply"); var db = AetherDb.Open(catalogWritable: apply, root: root); var existing = db.Cache.GetAll().ToArray(); - var nameFiles = db.Cache.GetAll().ToArray(); - // Refuse before any write: a name or short name held by a faction under another key, and a missing name file. + // Refuse before any write: a name or short name held by a faction under another key. 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); @@ -80,7 +76,7 @@ public static class MinorPowerFactionsCommand } 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.##}"); + Console.WriteLine($"New faction \"{def.Name}\" ({def.ShortName}): influence {def.Influence}, 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.##}"))}"); @@ -101,7 +97,7 @@ public static class MinorPowerFactionsCommand { 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, + InfluenceDistance = def.Influence, }; foreach (var reference in everyone) faction.Allegiance[reference] = def.Default; foreach (var other in Factions.Where(o => o != def)) diff --git a/tools/AetherDb/PlaceNamesCommand.cs b/tools/AetherDb/PlaceNamesCommand.cs index 7c08e4150..dd3dcb137 100644 --- a/tools/AetherDb/PlaceNamesCommand.cs +++ b/tools/AetherDb/PlaceNamesCommand.cs @@ -88,8 +88,6 @@ public static class PlaceNamesCommand 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(); - var nameFiles = db.Cache.GetAll().ToArray(); - var geoned = factions.Where(f => f.GeonameFile.IsSet()).ToArray(); foreach (var faction in factions.OrderBy(f => f.Name, StringComparer.Ordinal)) { @@ -100,10 +98,7 @@ public static class PlaceNamesCommand } Console.WriteLine($"{faction.Name}: {row.Names.Length} places, capital {row.Names[0]}, {(changed.Contains(row) ? "changed" : "unchanged")}"); } - if (nameFiles.Length != 0 || geoned.Length != 0) - Console.WriteLine($"Retired Markov data: {nameFiles.Length} name files, {geoned.Length} geoname links."); - - if (changed.Length == 0 && nameFiles.Length == 0 && geoned.Length == 0) + if (changed.Length == 0) { Console.WriteLine("nothing to do"); return 0; @@ -116,15 +111,12 @@ public static class PlaceNamesCommand if (!db.Cache.Commit(batch => { - // Commit 1 only: the Markov data goes while its type still exists; commit 3 deletes the type and this leg. - foreach (var faction in factions.Where(f => geoned.Contains(f) || changed.Any(r => r.RecordKey == db.Cache.RefOf(f).Key.Value))) + foreach (var row in changed) { - if (rows.TryGetValue(db.Cache.RefOf(faction).Key.Value, out var row)) - faction.Places = row.Names.Select(n => new FactionPlace { Name = n }).ToArray(); - faction.GeonameFile = default; + 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); } - foreach (var file in nameFiles) batch.Remove(db.Cache.RefOf(file).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"); diff --git a/tools/AetherDb/Program.cs b/tools/AetherDb/Program.cs index 8c00d3e53..cc9d5ef83 100644 --- a/tools/AetherDb/Program.cs +++ b/tools/AetherDb/Program.cs @@ -544,7 +544,6 @@ public static class Program var tutorial = authored.Read("TutorialGenerationSettings"); var background = authored.Read("TutorialBackgroundSettings"); - var names = authored.Read("NameGeneratorSettings"); var zones = authored.Read("ZoneSettings"); var gameplay = authored.Read("GameplaySettings"); @@ -554,7 +553,6 @@ public static class Program $"{tutorial.ZoneCount} zones, link density {tutorial.LinkDensity}"); Console.WriteLine($"background: frequency {background.NoiseFrequency}, amplitude {background.NoiseAmplitude}, " + $"cloud exponent {background.CloudExponent}, density at centre {background.CloudDensity(new float2(.5f)):0.000}"); - Console.WriteLine($"names: order {names.NameGeneratorOrder}, length {names.NameGeneratorMinLength}-{names.NameGeneratorMaxLength}"); Console.WriteLine($"zones: sun mass {zones.SunMass}, planet mass {zones.PlanetMass}, satellite passes {zones.SatellitePasses}, " + $"belt probability {zones.BeltProbability}"); Console.WriteLine($" zone radius curve {zones.ZoneRadius?.Minimum}-{zones.ZoneRadius?.Maximum} exponent {zones.ZoneRadius?.Exponent}"); @@ -696,23 +694,22 @@ public static class Program return false; } - // Each faction's generation-critical links. Galaxy.GenerateNames dereferences the geoname file without a - // guard, so an unset link there stops galaxy generation outright. + // Each faction's generation-critical links and the size of its curated place list. An empty list is not broken: + // Galaxy.NamePlaces falls back to catalogue designations for a faction without one. private static int Factions() { var db = AetherDb.Open(); var products = db.Cache.GetAll().ToArray(); - Console.WriteLine($"{"faction",-26} {"short",-13} {"geonames",-22} {"boss hull",-14} {"influence",-9} {"doctrine",-10} products"); + Console.WriteLine($"{"faction",-26} {"short",-13} {"places",-22} {"boss hull",-14} {"influence",-9} {"doctrine",-10} products"); var broken = 0; foreach (var faction in db.Cache.GetAll().OrderBy(f => f.Name)) { var key = db.Cache.RefOf(faction).Key; - var geonames = faction.GeonameFile.IsSet() ? "set" : "UNSET"; var boss = !faction.BossHull.IsSet() ? "none" : db.Cache.Get(faction.BossHull)?.Name ?? "DANGLING"; - if (geonames == "UNSET" || boss == "DANGLING") broken++; - Console.WriteLine($"{faction.Name,-26} {faction.ShortName,-13} {geonames,-22} {boss,-14} {faction.InfluenceDistance,-9} {(faction.Doctrine == null ? "none" : faction.Doctrine.EngageOn.ToString()),-10} " + + if (boss == "DANGLING") broken++; + Console.WriteLine($"{faction.Name,-26} {faction.ShortName,-13} {faction.Places.Length,-22} {boss,-14} {faction.InfluenceDistance,-9} {(faction.Doctrine == null ? "none" : faction.Doctrine.EngageOn.ToString()),-10} " + products.Count(p => p.Manufacturer.Key.Equals(key))); } Console.WriteLine($"\n{broken} factions carry a link that would break generation"); @@ -2305,9 +2302,6 @@ public static class Program string CodePoints(string text) => string.Join(" ", text.EnumerateRunes().Select(r => $"U+{r.Value:X4}")); - var nameFile = db.Cache.GetAll().FirstOrDefault(n => n.Name == "pleiades"); - if (nameFile == null) throw new InvalidOperationException("No name file named \"pleiades\"."); - var others = db.Cache.GetAll().ToArray(); var pirates = new Faction { @@ -2316,7 +2310,6 @@ public static class Program Description = "A loose coalition of crews and hidden docks moving people and goods outside official control. No command, no common law: possession has a maintenance schedule.", PrimaryColor = new float3(.42f, .40f, .37f), SecondaryColor = new float3(1f, .45f, .05f), - GeonameFile = db.Cache.RefOf(nameFile), InfluenceDistance = 3, }; foreach (var other in AllegianceGaps(db, pirates, others)) pirates.Allegiance[db.Cache.RefOf(other)] = 1f; @@ -2340,7 +2333,7 @@ public static class Program Roles: (design.Roles ?? new List()).Select(role => new ProductRole { Role = role.Name, Mean = PirateProfile.Mean, StandardDeviation = PirateProfile.Dev }).ToList()); }).ToArray(); - Console.WriteLine($"New faction \"{pirates.Name}\": influence {pirates.InfluenceDistance}, names \"{nameFile.Name}\", allegiance 1.0 to {pirates.Allegiance.Count} factions."); + Console.WriteLine($"New faction \"{pirates.Name}\": influence {pirates.InfluenceDistance}, allegiance 1.0 to {pirates.Allegiance.Count} factions."); Console.WriteLine($" colours: primary ({pirates.PrimaryColor.x:0.00}, {pirates.PrimaryColor.y:0.00}, {pirates.PrimaryColor.z:0.00}) primer grey, secondary ({pirates.SecondaryColor.x:0.00}, {pirates.SecondaryColor.y:0.00}, {pirates.SecondaryColor.z:0.00}) hazard orange"); Console.WriteLine($" {pirates.Description}"); Console.WriteLine($"\nNew products ({products.Length}):"); From 9481e541ca553932e1474a0a138e2e56aecda2a9 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 17 Oct 2026 19:35:04 +0200 Subject: [PATCH 04/10] PlaceNamesTests: a home zone shared by two factions takes its owner's capital The prelude puts the quest faction's home on the buffer faction's, and a zone has one owner (the nearest home in faction order), so the first full run showed Adrasteia's home zone named Ishtar Studios, Lucent's capital. The rule stands; the test said the wrong thing about the shared home. Co-Authored-By: Claude Opus 5.5 --- .../Aetheria.Shared.Tests/PlaceNamesTests.cs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs b/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs index a26925a36..b47d41c74 100644 --- a/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs +++ b/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs @@ -59,17 +59,25 @@ public sealed class PlaceNamesTests : IDisposable galaxy.Zones.Select((zone, index) => (zone, index)).Where(z => z.zone.Owner == faction) .OrderBy(z => z.zone.Distance[galaxy.HomeZones[faction]]).ThenBy(z => z.index).Select(z => z.zone).ToArray(); + // Two factions can share a home zone (the prelude puts the quest faction on the buffer's); the zone's owner is the + // nearest home in faction order, and that owner's capital is what the zone is called. [Fact] - public void TheHomeZoneTakesTheFirstName() + public void TheHomeZoneTakesTheOwnersFirstName() { - var checkedHomes = 0; + var own = 0; + var shared = 0; foreach (var galaxy in Galaxies(true, 10).Concat(Galaxies(false, 10))) - foreach (var faction in Placed(galaxy)) + foreach (var faction in galaxy.Factions.Where(galaxy.HomeZones.ContainsKey)) { - Assert.Equal(faction.Places[0].Name, galaxy.HomeZones[faction].Name); - checkedHomes++; + var home = galaxy.HomeZones[faction]; + var owner = home.Owner; + Assert.NotNull(owner); + if (owner.Places.Length == 0) Assert.Matches(Designation, home.Name); + else Assert.Equal(owner.Places[0].Name, home.Name); + if (owner == faction) own++; else shared++; } - Assert.True(checkedHomes >= 20, $"only {checkedHomes} homes were checked"); + Assert.True(own >= 20, $"only {own} factions owned their home"); + Assert.True(shared > 0, "no galaxy shared a home zone, so the shared case was not exercised"); } [Fact] From 7bd11f882b36c43abd7d6e4a36160edf37f7a6a1 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 17 Oct 2026 19:35:05 +0200 Subject: [PATCH 05/10] FlightTests: Faction's wire array is 22 slots long, repinned from 18 Places rides at key 21, so the serialized Faction array grows from 18 slots to 22 (keys 18-20 empty). Slots 16 and 17 are unchanged. Base value 18, new value 22, reason: the new member; no zone content is involved. Co-Authored-By: Claude Opus 5.5 --- tests/Aetheria.Shared.Tests/FlightTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/Aetheria.Shared.Tests/FlightTests.cs b/tests/Aetheria.Shared.Tests/FlightTests.cs index 263f9a086..2614c13e8 100644 --- a/tests/Aetheria.Shared.Tests/FlightTests.cs +++ b/tests/Aetheria.Shared.Tests/FlightTests.cs @@ -910,7 +910,8 @@ public sealed class FlightTests : IDisposable Assert.Equal(turns, turn != 0f); } - // Faction.Doctrine rides at wire slot 17; slot 16 is reserved for Relations. Kills: the key moved. + // Faction.Doctrine rides at wire slot 17; slot 16 is reserved for Relations; Places rides at 21, which sets the array's length. + // Kills: the key moved. [Fact] public void TheFactionDoctrineRidesAtWireSlot17() { @@ -919,7 +920,7 @@ public sealed class FlightTests : IDisposable var slots = MessagePack.MessagePackSerializer.Deserialize(MessagePack.MessagePackSerializer.Serialize(faction, CultDocumentMessagePackSerialization.OptionsFor(typeof(Faction).Assembly))); - Assert.Equal(18, slots.Length); + Assert.Equal(22, slots.Length); Assert.Null(slots[16]); var doctrine = Assert.IsType(slots[17]); Assert.Equal(6f, Convert.ToSingle(doctrine[1])); From 1eb828d53a055f628ad842786115dfff0215cf40 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 17 Oct 2026 20:03:27 +0200 Subject: [PATCH 06/10] PlaceNamesTests: a name another faction used is skipped, whatever its case Mutation triage: skipping used names, and comparing them case-sensitively, both survived because the table refuses repeats before a galaxy sees them. A list that did not come through the table (a package's factions) can still repeat one, so the draw rule's skip now has a test. Co-Authored-By: Claude Opus 5.5 --- .../Aetheria.Shared.Tests/PlaceNamesTests.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs b/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs index b47d41c74..ffbd40e58 100644 --- a/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs +++ b/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs @@ -130,6 +130,33 @@ public sealed class PlaceNamesTests : IDisposable Assert.All(owned.Skip(2), zone => Assert.Matches(Designation, zone.Name)); } + // The table refuses a repeated name, but a list that did not come through it (a package's factions) can still hold + // one: the later faction skips a name the earlier one already used, whatever its case. + [Fact] + public void AnotherFactionsNameIsNotReusedWhateverItsCase() + { + var cache = RestoredHullsTests.OpenCatalog(); + _own.Add(cache); + var galaxy = Enumerable.Range(1, 20).Select(seed => { try { return Build(cache, true, (uint)seed); } catch (Exception) { return null; } }) + .First(g => g != null && Placed(g).Count(f => Owned(g, f).Length >= 2) >= 2); + var pair = Placed(galaxy).Where(f => Owned(galaxy, f).Length >= 2).Take(2).ToArray(); + var (first, later) = (pair[0], pair[1]); + Assert.True(Array.IndexOf(galaxy.Factions, first) < Array.IndexOf(galaxy.Factions, later)); + var taken = first.Places[0].Name; + var shouting = taken.ToUpperInvariant(); + Assert.NotEqual(taken, shouting); + var own = later.Places[0].Name; + later.Places = new[] { new FactionPlace { Name = shouting }, new FactionPlace { Name = own } }; + + var random = new Random(7); + galaxy.NamePlaces(ref random); + + Assert.Equal(taken, Owned(galaxy, first)[0].Name); + Assert.Equal(own, Owned(galaxy, later)[0].Name); + var names = galaxy.Zones.Select(z => z.Name).ToArray(); + Assert.Equal(names.Length, names.Distinct(StringComparer.OrdinalIgnoreCase).Count()); + } + [Fact] public void NoTwoZonesShareAName() { From a5a8199321307b0c36660da3ef899da9feb9648c Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 17 Oct 2026 20:35:37 +0200 Subject: [PATCH 07/10] Place names: Miss Terri's gets a draft list (ruling miss-terris-improvised-places) Real Australian place names, Indigenous-derived and English, in the old Australia file's spirit; a draft Emily may replace. The test that pinned her as designation-only now clears her list in memory to prove the fallback. Co-Authored-By: Claude Opus 5.5 --- .../Aetheria.Shared.Tests/PlaceNamesTests.cs | 25 +++++++++++-------- tools/AetherDb/PlaceNamesCommand.cs | 5 +++- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs b/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs index ffbd40e58..568f1f28d 100644 --- a/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs +++ b/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs @@ -174,16 +174,21 @@ public sealed class PlaceNamesTests : IDisposable [Fact] public void AFactionWithoutAListGetsCatalogueDesignations() { - var terris = Shared.GetAll().Single(f => f.ShortName == "Miss Terri's"); - Assert.Empty(terris.Places); - var held = 0; - foreach (var galaxy in Galaxies(true, 10)) - foreach (var zone in galaxy.Zones.Where(z => z.Owner == terris)) - { - Assert.Matches(Designation, zone.Name); - held++; - } - Assert.True(held > 0, "Miss Terri's held no zone in the first ten prelude galaxies"); + // Its own cache: the list is cleared, which must not reach the galaxies the other tests share. + var cache = RestoredHullsTests.OpenCatalog(); + _own.Add(cache); + var terris = cache.GetAll().Single(f => f.ShortName == "Miss Terri's"); + Assert.NotEmpty(terris.Places); + var galaxy = Enumerable.Range(1, 20).Select(seed => { try { return Build(cache, true, (uint)seed); } catch (Exception) { return null; } }) + .First(g => g != null && g.Zones.Any(z => z.Owner == terris)); + terris.Places = Array.Empty(); + + var random = new Random(7); + galaxy.NamePlaces(ref random); + + var held = galaxy.Zones.Where(z => z.Owner == terris).ToArray(); + Assert.NotEmpty(held); + Assert.All(held, zone => Assert.Matches(Designation, zone.Name)); } [Fact] diff --git a/tools/AetherDb/PlaceNamesCommand.cs b/tools/AetherDb/PlaceNamesCommand.cs index dd3dcb137..55348de64 100644 --- a/tools/AetherDb/PlaceNamesCommand.cs +++ b/tools/AetherDb/PlaceNamesCommand.cs @@ -11,7 +11,7 @@ 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 (question miss-terris-place-names). +// 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 { @@ -44,6 +44,9 @@ public static class PlaceNamesCommand "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", From 26c44020a1305f425a3fda9b6faee2686ee15939 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 17 Oct 2026 20:36:48 +0200 Subject: [PATCH 08/10] Catalog: place-names apply adds Miss Terri's list Built from a5a81993 on the previous commit's catalog; a second apply printed 'nothing to do'. The merge pass replays the command twice: built at commit 1 (with the purge leg) on master's catalog, then built at the tip. Co-Authored-By: Claude Opus 5.5 --- GameData/Aetheria.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GameData/Aetheria.cc b/GameData/Aetheria.cc index 8008cac55..f289cea9f 100644 --- a/GameData/Aetheria.cc +++ b/GameData/Aetheria.cc @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5513c6bd32b64c73ad803a3ca9609c7ee0ef7ffdc825214cb25d10070845efd +oid sha256:4f280d831c37adf99eb04c5b38a0afda490868569861a215972d1c0b2f32b123 size 185359 From ac259c6259262ddef8bdb29fa5151c6efbe8d61b Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 17 Oct 2026 22:14:22 +0200 Subject: [PATCH 09/10] Place names: one validity rule (FactionPlace.IsValidName) for NamePlaces and the place-names command NamePlaces treats an invalid entry as absent; the command refuses a table holding one. Co-Authored-By: Claude Opus 5.5 --- Assets/Scripts/ServerShared/Corporations.cs | 5 +++++ Assets/Scripts/ServerShared/Galaxy.cs | 2 +- tools/AetherDb/PlaceNamesCommand.cs | 6 ++---- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Assets/Scripts/ServerShared/Corporations.cs b/Assets/Scripts/ServerShared/Corporations.cs index 0b9be208d..4c38b60fc 100644 --- a/Assets/Scripts/ServerShared/Corporations.cs +++ b/Assets/Scripts/ServerShared/Corporations.cs @@ -75,5 +75,10 @@ public class FactionPlace { [Inspectable, JsonProperty("name"), Key(1)] public string Name; + + // The one rule for a valid place name: trimmed, non-empty and not a catalogue designation (EAC-, any case). Galaxy.NamePlaces + // treats an invalid entry as absent; AetherDb place-names refuses a table that holds one. + public static bool IsValidName(string name) => + !string.IsNullOrEmpty(name) && name == name.Trim() && !name.StartsWith("EAC-", StringComparison.OrdinalIgnoreCase); } diff --git a/Assets/Scripts/ServerShared/Galaxy.cs b/Assets/Scripts/ServerShared/Galaxy.cs index 504f419b7..0b2f71387 100644 --- a/Assets/Scripts/ServerShared/Galaxy.cs +++ b/Assets/Scripts/ServerShared/Galaxy.cs @@ -358,7 +358,7 @@ public class Galaxy foreach (var faction in Factions) { if (!HomeZones.TryGetValue(faction, out var home)) continue; - var places = (faction.Places ?? Array.Empty()).Select(p => p?.Name).Where(n => !string.IsNullOrEmpty(n)).ToArray(); + var places = (faction.Places ?? Array.Empty()).Select(p => p?.Name).Where(FactionPlace.IsValidName).ToArray(); var next = 0; var owned = Zones.Select((zone, index) => (zone, index)).Where(z => z.zone.Owner == faction) .OrderBy(z => z.zone.Distance[home]).ThenBy(z => z.index); diff --git a/tools/AetherDb/PlaceNamesCommand.cs b/tools/AetherDb/PlaceNamesCommand.cs index 55348de64..0bb0700b9 100644 --- a/tools/AetherDb/PlaceNamesCommand.cs +++ b/tools/AetherDb/PlaceNamesCommand.cs @@ -80,10 +80,8 @@ public static class PlaceNamesCommand throw new InvalidOperationException("Place names row: its ShortName differs from the faction under its key (conflict)."); foreach (var name in row.Names) { - if (string.IsNullOrEmpty(name) || name != name.Trim()) - throw new InvalidOperationException("Place names row: a name is empty or untrimmed (invalid)."); - if (name.StartsWith("EAC-", StringComparison.OrdinalIgnoreCase)) - throw new InvalidOperationException("Place names row: a name is a catalogue designation (invalid)."); + 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)."); } From 7da0d414cbfc4151d092c35a27ebab771881dff5 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 17 Oct 2026 22:14:26 +0200 Subject: [PATCH 10/10] PlaceNamesTests: assert on names, not zone graphs; pin invalid entries, homeless factions, runs of taken names, EAC- case and same-length restore A failed Assert.Same or Assert.All over cyclic Galaxy objects overflowed xunit's formatter and crashed the host. Co-Authored-By: Claude Opus 5.5 --- .../Aetheria.Shared.Tests/PlaceNamesTests.cs | 83 +++++++++++++++++-- 1 file changed, 76 insertions(+), 7 deletions(-) diff --git a/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs b/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs index 568f1f28d..9490d049b 100644 --- a/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs +++ b/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs @@ -90,7 +90,7 @@ public sealed class PlaceNamesTests : IDisposable var owned = Owned(galaxy, faction); var listed = Math.Min(owned.Length, faction.Places.Length); Assert.Equal(faction.Places.Take(listed).Select(p => p.Name), owned.Take(listed).Select(z => z.Name)); - Assert.All(owned.Skip(listed), zone => Assert.Matches(Designation, zone.Name)); + Assert.All(owned.Skip(listed).Select(z => z.Name), name => Assert.Matches(Designation, name)); checkedFactions++; } Assert.True(checkedFactions >= 20, $"only {checkedFactions} factions were checked"); @@ -104,7 +104,8 @@ public sealed class PlaceNamesTests : IDisposable foreach (var galaxy in Galaxies(true, 10).Concat(Galaxies(false, 10))) foreach (var zone in galaxy.Zones.Where(z => curated.ContainsKey(z.Name))) { - Assert.Same(curated[zone.Name], zone.Owner); + Assert.True(ReferenceEquals(curated[zone.Name], zone.Owner), + $"zone '{zone.Name}' is owned by '{zone.Owner?.ShortName}' but curated by '{curated[zone.Name].ShortName}'"); zones++; } Assert.True(zones >= 50, $"only {zones} curated zones were checked"); @@ -126,8 +127,8 @@ public sealed class PlaceNamesTests : IDisposable var owned = Owned(galaxy, faction); Assert.Equal(faction.Places.Select(p => p.Name), owned.Take(2).Select(z => z.Name)); - Assert.Same(owned[0], galaxy.HomeZones[faction]); - Assert.All(owned.Skip(2), zone => Assert.Matches(Designation, zone.Name)); + Assert.True(ReferenceEquals(owned[0], galaxy.HomeZones[faction]), $"the nearest zone '{owned[0].Name}' is not {faction.ShortName}'s home '{galaxy.HomeZones[faction].Name}'"); + Assert.All(owned.Skip(2).Select(z => z.Name), name => Assert.Matches(Designation, name)); } // The table refuses a repeated name, but a list that did not come through it (a package's factions) can still hold @@ -145,8 +146,10 @@ public sealed class PlaceNamesTests : IDisposable var taken = first.Places[0].Name; var shouting = taken.ToUpperInvariant(); Assert.NotEqual(taken, shouting); + // Two consecutive names are taken, so the later faction must skip a run, not a single name. + var takenToo = first.Places[1].Name; var own = later.Places[0].Name; - later.Places = new[] { new FactionPlace { Name = shouting }, new FactionPlace { Name = own } }; + later.Places = new[] { new FactionPlace { Name = shouting }, new FactionPlace { Name = takenToo.ToUpperInvariant() }, new FactionPlace { Name = own } }; var random = new Random(7); galaxy.NamePlaces(ref random); @@ -157,6 +160,50 @@ public sealed class PlaceNamesTests : IDisposable Assert.Equal(names.Length, names.Distinct(StringComparer.OrdinalIgnoreCase).Count()); } + // The rule is one function (FactionPlace.IsValidName); an entry that fails it counts as absent, so the next valid name + // is the capital and nothing invalid ever titles a zone. + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(" Padded")] + [InlineData("eac-6")] + [InlineData("EAC-6")] + [InlineData(null)] + public void AnInvalidEntryCountsAsAbsent(string bad) + { + var cache = RestoredHullsTests.OpenCatalog(); + _own.Add(cache); + var galaxy = Enumerable.Range(1, 20).Select(seed => { try { return Build(cache, true, (uint)seed); } catch (Exception) { return null; } }) + .First(g => g != null && Placed(g).Any(f => Owned(g, f).Length >= 3)); + var faction = Placed(galaxy).First(f => Owned(galaxy, f).Length >= 3); + faction.Places = new[] { new FactionPlace { Name = bad }, new FactionPlace { Name = "Real" }, null, new FactionPlace { Name = "Second" } }; + + var random = new Random(7); + galaxy.NamePlaces(ref random); + + Assert.Equal(new[] { "Real", "Second" }, Owned(galaxy, faction).Take(2).Select(z => z.Name)); + Assert.Matches(Designation, Owned(galaxy, faction)[2].Name); + } + + // A faction with no home does not stop the ones after it from naming their zones. + [Fact] + public void AFactionWithoutAHomeDoesNotEndTheNaming() + { + var cache = RestoredHullsTests.OpenCatalog(); + _own.Add(cache); + var galaxy = Enumerable.Range(1, 20).Select(seed => { try { return Build(cache, true, (uint)seed); } catch (Exception) { return null; } }) + .First(g => g != null && Placed(g).Count(f => g.HomeZones[f].Owner == f) >= 2); + var homeless = galaxy.Factions.First(galaxy.HomeZones.ContainsKey); + var later = galaxy.Factions.Where(f => f != homeless && galaxy.HomeZones.ContainsKey(f) && f.Places.Length != 0 && galaxy.HomeZones[f].Owner == f) + .First(f => Array.IndexOf(galaxy.Factions, f) > Array.IndexOf(galaxy.Factions, homeless)); + galaxy.HomeZones.Remove(homeless); + + var random = new Random(7); + galaxy.NamePlaces(ref random); + + Assert.Equal(later.Places[0].Name, galaxy.HomeZones[later].Name); + } + [Fact] public void NoTwoZonesShareAName() { @@ -188,7 +235,7 @@ public sealed class PlaceNamesTests : IDisposable var held = galaxy.Zones.Where(z => z.Owner == terris).ToArray(); Assert.NotEmpty(held); - Assert.All(held, zone => Assert.Matches(Designation, zone.Name)); + Assert.All(held.Select(z => z.Name), name => Assert.Matches(Designation, name)); } [Fact] @@ -197,7 +244,9 @@ public sealed class PlaceNamesTests : IDisposable var factions = Shared.GetAll().ToArray(); foreach (var row in PlaceNamesCommand.Table) { - var faction = Assert.Single(factions, f => Shared.RefOf(f).Key.Value == row.RecordKey); + var matching = factions.Where(f => Shared.RefOf(f).Key.Value == row.RecordKey).ToArray(); + Assert.True(matching.Length == 1, $"{matching.Length} catalog factions carry the key of the {row.ShortName} row"); + var faction = matching[0]; Assert.Equal(row.ShortName, faction.ShortName); Assert.Equal(row.Names, faction.Places.Select(p => p.Name)); } @@ -270,8 +319,28 @@ public sealed class PlaceNamesTests : IDisposable Assert.Equal(changed, File.ReadAllBytes(CatalogOf(root))); } + [Fact] + public void ApplyRestoresAnEditedListOfTheSameLength() + { + var root = ScratchWithoutLists(); + Assert.Equal(0, PlaceNamesCommand.Run(new[] { "apply" }, root)); + var row = PlaceNamesCommand.Table.First(); + var edited = AetherDb.Open(catalogWritable: true, root: root); + var faction = edited.Cache.GetAll().Single(f => edited.Cache.RefOf(f).Key.Value == row.RecordKey); + faction.Places = row.Names.Reverse().Select(n => new FactionPlace { Name = n }).ToArray(); + edited.Cache.Commit(batch => batch.Upsert(typeof(Faction), faction, edited.Cache.RefOf(faction).Key)); + edited.Cache.Dispose(); + + Assert.Equal(0, PlaceNamesCommand.Run(new[] { "apply" }, root)); + + var db = AetherDb.Open(root: root); + try { Assert.Equal(row.Names, db.Cache.GetAll().Single(f => db.Cache.RefOf(f).Key.Value == row.RecordKey).Places.Select(p => p.Name)); } + finally { db.Cache.Dispose(); } + } + [Theory] [InlineData("EAC-12")] + [InlineData("eac-12")] [InlineData(" Padded")] [InlineData("")] [InlineData("shunya")]