Aetheria/tools/AetherDb/AetherDb.cs
Meta Vens 285b57713f Clear dangling boss hull links, and stop the tool writing on read
Eight factions pointed at one boss hull GUID that no longer resolves,
shared wreckage from the database loss. PlaceFactionsMain gives a boss zone
to every faction carrying a BossHull, so each dangling link claimed a
chokepoint that could never spawn a boss.

AetherDb.Open no longer registers the name file store. CultCache subscribes
Add to a store EntryAdded, MultiFileBackingStore.PullAll re-emits what it
loads, and Add pushes to that type store, whose Push writes a file: opening
the cache rewrote all twelve name files, 4.4 MB, on every command. They were
byte identical, and it only surfaced as an error because Unity had one of
them memory mapped. Name files now load only on request, and the scope doc
records the underlying smell.

Adds commands: factions, clear-boss-hulls, save, loadout (runs the real
LoadoutGenerator against every hull), hardpoint-fit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 02:05:33 +02:00

46 lines
2.3 KiB
C#

using System;
using System.IO;
// Opens GameData/AetherDB.msgpack through the game's own CultCache and data types, for inspection and editing.
// Close the editor's Database Tools before saving, or its next Save will overwrite what was written here.
//
// Reads do not write. That needs saying because CultCache makes it easy to get wrong: AddBackingStore subscribes
// Add to a store's EntryAdded, MultiFileBackingStore.PullAll re-emits every entry it loads, and Add pushes to the
// store registered for that type, whose Push writes a file. So registering a second store makes merely opening the
// cache rewrite its files. Name files are therefore loaded only when a command asks for them.
public sealed class AetherDb
{
public string Root { get; }
public CultCache Cache { get; }
private readonly SingleFileMessagePackBackingStore _store;
private AetherDb(string root, bool withNameFiles)
{
Root = root;
RegisterResolver.Register();
var gameData = Path.Combine(root, "GameData");
_store = new SingleFileMessagePackBackingStore(Path.Combine(gameData, "AetherDB.msgpack"));
Cache = new CultCache();
Cache.AddBackingStore(_store);
if (withNameFiles) Cache.AddBackingStore(new MultiFileMessagePackBackingStore(gameData), typeof(NameFile));
Cache.PullAllBackingStores();
}
// With no root given, walks up from the working directory to the repository
public static AetherDb Open(string root = null) => new AetherDb(root ?? FindRoot(), false);
// Galaxy generation needs the name files; loading them rewrites them, so only ask when they are needed,
// and not while the editor holds them open.
public static AetherDb OpenWithNameFiles(string root = null) => new AetherDb(root ?? FindRoot(), true);
// Writes every entry back to AetherDB.msgpack, as the editor's Save button does
public void Save() => _store.PushAll();
private static string FindRoot()
{
for (var dir = new DirectoryInfo(Environment.CurrentDirectory); dir != null; dir = dir.Parent)
if (File.Exists(Path.Combine(dir.FullName, "GameData", "AetherDB.msgpack")))
return dir.FullName;
throw new DirectoryNotFoundException("Run from inside the Aetheria repository.");
}
}