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 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-17 19:10:17 +02:00
commit 740e1ee781
5 changed files with 435 additions and 24 deletions

View file

@ -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<FactionPlace>();
}
// 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]

View file

@ -34,7 +34,6 @@ public class Galaxy
public uint Seed { get; }
private GalaxyZone[] _exitPath;
private Dictionary<Faction, MarkovNameGenerator> _nameGenerators = new Dictionary<Faction, MarkovNameGenerator>();
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<string> 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<string> progressCallback = null)
{
var used = new HashSet<string>();
for (var i = 0; i < Factions.Length; i++)
progressCallback?.Invoke("Naming places");
var used = new HashSet<string>(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<FactionPlace>()).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;
}
}