Loadouts.Commit stops refusing package designs (ruling catalog-is-a-package-stack)
The catalog is a stack of packages, the shipped content included, so a Loadout may
name any design the stack holds. The mod-design refusal (from 54917bb3, backed by no
ruling) is deleted, and its test now pins that a package design commits under its key.
The IsModKey comment no longer claims the shipped catalog never references a mod.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
bd628de3bf
commit
7f1337167d
3 changed files with 14 additions and 16 deletions
|
|
@ -99,14 +99,13 @@ public static class Loadouts
|
|||
// The only writer of Loadout records. It opens its own short-lived cache over the catalog file, writable, and disposes it
|
||||
// after the one commit. No other cache is touched, so no catalog instance play holds (or has mutated) can reach the
|
||||
// file; a cache that already has the catalog open sees the loadout only once it reloads.
|
||||
// Throws and writes nothing when the loadout names a mod design (hull or slot), when the file is missing (capture never creates a catalog), when a loadout of this name
|
||||
// The catalog is a stack of packages, the shipped content included (ruling catalog-is-a-package-stack), so a loadout may
|
||||
// name any design the stack holds; one whose package is later removed fails closed where it is built, with its reason logged.
|
||||
// Throws and writes nothing when the file is missing (capture never creates a catalog), when a loadout of this name
|
||||
// exists under another key, or when one exists under KeyOf(Name) and replace is not set. The commit is conditional
|
||||
// on the record at KeyOf(Name), so a loadout changed on disk since this open is not clobbered: returns false then.
|
||||
public static bool Commit(string catalogPath, Loadout loadout, bool replace)
|
||||
{
|
||||
var modDesigns = new[] { loadout.Hull.Key }.Concat(loadout.Slots.Select(slot => slot.Design.Key)).Where(ShipModCatalog.IsModKey).Select(key => key.Value).Distinct().ToArray();
|
||||
if (modDesigns.Length > 0)
|
||||
throw new InvalidOperationException($"Loadout '{loadout.Name}' names mod designs ({string.Join(", ", modDesigns)}); the shipped catalog never references a mod, so a mod ship cannot be captured as a loadout.");
|
||||
if (!File.Exists(catalogPath))
|
||||
throw new InvalidOperationException($"Catalog {catalogPath} does not exist; a loadout commit never creates one.");
|
||||
using var cache = AetheriaStores.Open(catalogPath, catalogWritable: true);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ public static class ShipModCatalog
|
|||
|
||||
public static CultRecordKey ProductKey(string id, CultRecordKey maker) => new CultRecordKey("mod-product:" + id + ":" + maker.Value);
|
||||
|
||||
// A key the mods own. The shipped catalog and its authored records never reference one: a mod may be uninstalled.
|
||||
// A key a package owns. Any record may reference one (ruling catalog-is-a-package-stack); one whose package is removed
|
||||
// fails closed where it is read.
|
||||
public static bool IsModKey(CultRecordKey key) =>
|
||||
key.IsSet() && (key.Value.StartsWith("mod-hull:", StringComparison.Ordinal) || key.Value.StartsWith("mod-ship:", StringComparison.Ordinal) || key.Value.StartsWith("mod-product:", StringComparison.Ordinal));
|
||||
|
||||
|
|
|
|||
|
|
@ -122,27 +122,25 @@ public sealed class LoadoutTests : IDisposable
|
|||
Assert.True(!File.Exists(Player) || !SchemaNames(Player).Contains("aetheria.loadout"));
|
||||
}
|
||||
|
||||
// The shipped catalog never references a mod: a mod may be uninstalled, and a preset naming one would dangle. The
|
||||
// refusal is at the only writer, so no caller (the editor command included) can get one into the file.
|
||||
// The catalog is a stack of packages (ruling catalog-is-a-package-stack): a preset may name a package's design, hull or
|
||||
// slot, and the only writer commits it under the package's key. Whether the design resolves is decided where the preset is
|
||||
// built, not here.
|
||||
[Theory]
|
||||
[InlineData("hull", "mod-hull:mod.skiff")]
|
||||
[InlineData("slot", "mod-hull:mod.skiff")]
|
||||
[InlineData("slot", "mod-ship:mod.skiff")]
|
||||
public void APresetNamingAModDesignIsRefusedAndNothingIsWritten(string where, string key)
|
||||
public void APresetNamingAPackageDesignIsCommitted(string where, string key)
|
||||
{
|
||||
var mod = new CultRecordRef<EquippableItemData>(new CultRecordKey(key));
|
||||
using var cache = Open();
|
||||
var loadout = HandBuilt(cache);
|
||||
if (where == "hull") loadout.Hull = new CultRecordRef<HullData>(new CultRecordKey(key));
|
||||
else loadout.Slots[1].Design = mod;
|
||||
var before = Hash(Catalog);
|
||||
else loadout.Slots[1].Design = new CultRecordRef<EquippableItemData>(new CultRecordKey(key));
|
||||
|
||||
var error = Assert.Throws<InvalidOperationException>(() => Loadouts.Commit(Catalog, loadout, replace: false));
|
||||
Assert.True(Loadouts.Commit(Catalog, loadout, replace: false));
|
||||
|
||||
Assert.Contains("names mod designs", error.Message);
|
||||
Assert.Contains(key, error.Message);
|
||||
Assert.Equal(before, Hash(Catalog));
|
||||
Assert.True(Loadouts.Commit(Catalog, HandBuilt(cache), replace: false));
|
||||
using var reopened = Open();
|
||||
var written = Assert.Single(reopened.GetAll<Loadout>());
|
||||
Assert.Equal(key, (where == "hull" ? written.Hull.Key : written.Slots[1].Design.Key).Value);
|
||||
}
|
||||
|
||||
// Recapturing a name refuses without replace and leaves the file untouched; with replace it replaces, never duplicates.
|
||||
|
|
|
|||
Loading…
Reference in a new issue