Test the lot ledger, its GC, and stats reading through it
Every existing ItemManager/RunSave.Commit call site now threads a ProvenanceLedger. Five new tests and one rewrite cover the rulings this cut lands: a materialized ship's lots survive a real save and reload with unchanged quality and Attributed provenance; RunSave.Commit keeps only lots reachable from a save's packed entities, closing over Produced.Facility/Inputs, while the live ledger keeps everything; ProvenanceLedger's indexer and Reachable are loud on a missing lot; matching hardpoints mint from the same lot; Evaluate/GetTier read a lot's Quality and per-role fills; and Brand's first-in-key-order rule is asserted directly instead of through the old Product field. Two of these (MaterializedLotsSurviveSaveAndReload, CommitKeepsOnlyReachableLots) currently fail: committing any EntityPack into the run store and reopening it throws `TypeAccessException: No hash-resistant equality comparer available for type: CultMath.int2`, because EntityPack.PersistedBehaviors is a Dictionary<int2, ...> and CultMessagePackSecurity registers a collision-resistant comparer only for CultRecordRef<T>. This predates this cut (PersistedBehaviors is untouched here) and is a CultLib gap, not a defect in this schema; see the landing report. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
415f8e1152
commit
76c331e9bc
2 changed files with 227 additions and 27 deletions
|
|
@ -7,6 +7,7 @@ using CultMath;
|
|||
using GameCult.Caching;
|
||||
using GameCult.Caching.MessagePack;
|
||||
using Xunit;
|
||||
using Random = CultMath.Random;
|
||||
|
||||
// Loadouts are ship presets: item designs only, authored into the catalog, materialized all-or-nothing against it.
|
||||
// The fixture hull is 3x3: InteriorCells is Shape.Shrink(), so a 2x2 hull has no interior cell a cargo bay could use.
|
||||
|
|
@ -84,7 +85,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
Loadout hand;
|
||||
using (var cache = Open())
|
||||
{
|
||||
var items = new ItemManager(cache, RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
hand = Armed(cache);
|
||||
var failures = new List<string>();
|
||||
var ship = Build(items, hand, failures);
|
||||
|
|
@ -102,7 +103,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
Assert.Equal(hand.Slots.Select(Describe), loaded.Slots.Select(Describe));
|
||||
Assert.Equal(hand.WeaponGroups, loaded.WeaponGroups);
|
||||
|
||||
var items = new ItemManager(cache, RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var failures = new List<string>();
|
||||
var ship = Build(items, loaded, failures);
|
||||
Assert.Empty(failures);
|
||||
|
|
@ -249,7 +250,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
|
||||
using (var cache = Open())
|
||||
{
|
||||
var items = new ItemManager(cache, RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var missing = HandBuilt(cache);
|
||||
missing.Slots[1].Design = new CultRecordRef<EquippableItemData>(new CultRecordKey("absent-design"));
|
||||
var failures = new List<string>();
|
||||
|
|
@ -274,7 +275,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
public void FailedFitBuildsNothing()
|
||||
{
|
||||
using var cache = Open();
|
||||
var items = new ItemManager(cache, RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var clash = HandBuilt(cache);
|
||||
clash.Slots[1].Position = HardpointCell;
|
||||
var failures = new List<string>();
|
||||
|
|
@ -283,24 +284,28 @@ public sealed class LoadoutTests : IDisposable
|
|||
Assert.Contains("slot 0,0: Crate does not fit", failure);
|
||||
}
|
||||
|
||||
// Two more Lamp products, upserted into the live cache so its enumeration order (z before a) disagrees with
|
||||
// record-key order; a reopened snapshot could already hand them back sorted.
|
||||
// Two new makers, each with one Lamp product, upserted into the live cache so its enumeration order (z before a)
|
||||
// disagrees with record-key order; a reopened snapshot could already hand them back sorted. Brand() requires at
|
||||
// most one product per (maker, design), so each gets its own maker rather than sharing "Maker".
|
||||
[Fact]
|
||||
public void FirstAvailableProductInKeyOrderBuildsTheSlot()
|
||||
{
|
||||
using var cache = AetheriaStores.Open(Catalog, catalogWritable: true);
|
||||
var maker = cache.RefOf(cache.GetByName<Faction>("Maker"));
|
||||
var lamp = new CultRecordRef<CraftedItemData>(cache.RefOf(cache.GetByName<GearData>("Lamp")).Key);
|
||||
foreach (var key in new[] { "lamp-z", "lamp-a" })
|
||||
cache.Commit(batch => batch.Upsert(typeof(FactionProductData), new FactionProductData { Name = key, Design = lamp, Manufacturer = maker }, new CultRecordKey(key)));
|
||||
var zMaker = cache.Upsert(new Faction { Name = "Zeta", ShortName = "ZET" });
|
||||
var aMaker = cache.Upsert(new Faction { Name = "Alpha", ShortName = "ALP" });
|
||||
cache.Commit(batch => batch.Upsert(typeof(FactionProductData), new FactionProductData { Name = "lamp-z", Design = lamp, Manufacturer = zMaker }, new CultRecordKey("lamp-z")));
|
||||
cache.Commit(batch => batch.Upsert(typeof(FactionProductData), new FactionProductData { Name = "lamp-a", Design = lamp, Manufacturer = aMaker }, new CultRecordKey("lamp-a")));
|
||||
Assert.Equal(new[] { "lamp-z", "lamp-a" }, cache.GetAll<FactionProductData>().Select(p => p.Name).Where(name => name.StartsWith("lamp-")));
|
||||
|
||||
var items = new ItemManager(cache, RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var hand = HandBuilt(cache);
|
||||
string LampProduct(Predicate<FactionProductData> available, List<string> failures)
|
||||
{
|
||||
var ship = Loadouts.Materialize(items, null, hand, available, failures);
|
||||
return ship == null ? null : cache.Get(ship.Equipment.Single(item => item.EquippableItem != ship.Hull && item.Position.Equals(HardpointCell)).EquippableItem.Product).Name;
|
||||
if (ship == null) return null;
|
||||
var unit = ship.Equipment.Single(item => item.EquippableItem != ship.Hull && item.Position.Equals(HardpointCell)).EquippableItem;
|
||||
return items.Brand(unit).Product.Name;
|
||||
}
|
||||
|
||||
var failures = new List<string>();
|
||||
|
|
@ -311,11 +316,132 @@ public sealed class LoadoutTests : IDisposable
|
|||
Assert.Contains("slot 0,0: no available product of Lamp", Assert.Single(failures));
|
||||
}
|
||||
|
||||
// Materialize a hand-built ship, pack it into a zone, commit through Open() and reopen: every crafted instance
|
||||
// still resolves in RunSave.Lots, its Data still equals the lot's Design, the origin is Attributed with the
|
||||
// product's maker, and Quality is unchanged from the lot minted before the save.
|
||||
[Fact]
|
||||
public void MaterializedLotsSurviveSaveAndReload()
|
||||
{
|
||||
Dictionary<int, float> preSaveQuality;
|
||||
CultRecordKey maker;
|
||||
using (var cache = Open())
|
||||
{
|
||||
maker = cache.RefOf(cache.GetByName<Faction>("Maker")).Key;
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var hand = HandBuilt(cache);
|
||||
var failures = new List<string>();
|
||||
var ship = Build(items, hand, failures);
|
||||
Assert.Empty(failures);
|
||||
|
||||
preSaveQuality = items.Lots.Lots.ToDictionary(kv => kv.Key, kv => kv.Value.Quality);
|
||||
|
||||
var pack = EntitySerializer.Pack(ship);
|
||||
var game = new SavedGame
|
||||
{
|
||||
Factions = Array.Empty<CultRecordRef<Faction>>(),
|
||||
Relationships = Array.Empty<FactionRelationship>(),
|
||||
HomeZones = new Dictionary<int, int>(),
|
||||
BossZones = new Dictionary<int, int>(),
|
||||
DiscoveredZones = new[] { 0 },
|
||||
ActionBarBindings = new SavedActionBarBinding[0],
|
||||
Exit = -1
|
||||
};
|
||||
var zones = new[]
|
||||
{
|
||||
new SavedZone
|
||||
{
|
||||
Name = "Zone 0", AdjacentZones = Array.Empty<int>(), Factions = Array.Empty<int>(), Owner = -1,
|
||||
Contents = new ZonePack { Entities = new List<EntityPack> { pack } }
|
||||
}
|
||||
};
|
||||
RunSave.Commit(cache, game, zones, items.Lots);
|
||||
}
|
||||
|
||||
using (var cache = Open())
|
||||
{
|
||||
var lots = RunSave.Lots(cache);
|
||||
var run = cache.GetGlobal<SavedGame>();
|
||||
var zone = cache.Get(run.Zones[0]);
|
||||
var crafted = EntitySerializer.Items(zone.Contents.Entities[0]).OfType<CraftedItemInstance>().ToArray();
|
||||
Assert.NotEmpty(crafted);
|
||||
foreach (var instance in crafted)
|
||||
{
|
||||
var lot = lots[instance.Lot];
|
||||
Assert.Equal(instance.Data.Key, lot.Design.Key);
|
||||
var attributed = Assert.IsType<Attributed>(lot.Origin);
|
||||
Assert.Equal(maker, attributed.Faction.Key);
|
||||
Assert.Equal(preSaveQuality[instance.Lot], lot.Quality);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FillInterior requires a capacitor product; this fixture hull adds one alongside a 4x4 hull with two identical
|
||||
// Sensors hardpoints.
|
||||
[Fact]
|
||||
public void MatchingHardpointsShareALot()
|
||||
{
|
||||
using var cache = AetheriaStores.Open(Catalog, catalogWritable: true);
|
||||
var maker = cache.RefOf(cache.GetByName<Faction>("Maker"));
|
||||
var hullShape = new Shape(4, 4);
|
||||
foreach (var cell in hullShape.AllCoordinates) hullShape[cell] = true;
|
||||
var hull = cache.Upsert(new HullData
|
||||
{
|
||||
Name = "Twin", HullType = HullType.Ship, Shape = hullShape, Price = 100,
|
||||
Hardpoints =
|
||||
{
|
||||
new HardpointData { Type = HardpointType.Sensors, Position = new int2(0, 0), Shape = new Shape() },
|
||||
new HardpointData { Type = HardpointType.Sensors, Position = new int2(1, 0), Shape = new Shape() }
|
||||
}
|
||||
});
|
||||
var capacitor = cache.Upsert(new GearData { Name = "Cap", Hardpoint = HardpointType.Tool, Shape = new Shape(), Price = 1, Behaviors = { new CapacitorData() } });
|
||||
foreach (var (name, design) in new[] { ("Twin by Maker", hull.Key), ("Cap by Maker", capacitor.Key) })
|
||||
cache.Upsert(new FactionProductData { Name = name, Design = new CultRecordRef<CraftedItemData>(design), Manufacturer = maker });
|
||||
cache.FlushAsync().Wait();
|
||||
|
||||
var random = new Random(1);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var generator = new LoadoutGenerator(ref random, items, null, null, null, .5f);
|
||||
var pack = generator.GenerateShipLoadout(candidate => candidate.Name == "Twin");
|
||||
Assert.NotNull(pack);
|
||||
|
||||
var hardpointItems = pack.Equipment
|
||||
.Where(e => (cache.Get(e.item.Data) as GearData)?.Hardpoint == HardpointType.Sensors)
|
||||
.ToArray();
|
||||
Assert.Equal(2, hardpointItems.Length);
|
||||
Assert.Equal(hardpointItems[0].item.Lot, hardpointItems[1].item.Lot);
|
||||
}
|
||||
|
||||
// A Lamp lot with Quality .2 and one role fill (lens .9): a stat reading that role gets .9, one reading none
|
||||
// gets .2, and GetTier reads .2.
|
||||
[Fact]
|
||||
public void StatsReadTheLot()
|
||||
{
|
||||
using var cache = Open();
|
||||
var settings = RunSaveTests.TestSettings();
|
||||
settings.Tiers = new[] { new RarityTier { Name = "Common", Quality = .2f, Rarity = 0, Color = new float3(1, 1, 1) } };
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var lamp = cache.GetByName<GearData>("Lamp");
|
||||
var lotId = items.Lots.Add(new Lot
|
||||
{
|
||||
Design = cache.RefOf<ItemData>(lamp),
|
||||
Origin = new Attributed(),
|
||||
Quality = .2f,
|
||||
Roles = new List<RoleFill> { new RoleFill { Role = "lens", Quality = .9f } }
|
||||
});
|
||||
var instance = (EquippableItem) items.CreateInstance(lotId);
|
||||
|
||||
var withRole = new PerformanceStat { FromRole = "lens", Min = 0, Max = 1, QualityExponent = 1 };
|
||||
var noRole = new PerformanceStat { Min = 0, Max = 1, QualityExponent = 1 };
|
||||
Assert.Equal(.9f, items.Evaluate(withRole, instance), 3);
|
||||
Assert.Equal(.2f, items.Evaluate(noRole, instance), 3);
|
||||
Assert.Equal(.2f, items.GetTier(instance).tier.Quality, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OutOfRangeWeaponGroupIndexBuildsNothing()
|
||||
{
|
||||
using var cache = Open();
|
||||
var items = new ItemManager(cache, RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var stale = Armed(cache);
|
||||
stale.WeaponGroups = new[] { new[] { 0, 2 }, new[] { -1 } };
|
||||
var failures = new List<string>();
|
||||
|
|
@ -327,7 +453,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
public void UnequippingAGroupedWeaponRemovesItFromItsGroups()
|
||||
{
|
||||
using var cache = Open();
|
||||
var items = new ItemManager(cache, RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var ship = Build(items, Armed(cache), new List<string>());
|
||||
var item = Assert.Single(ship.WeaponGroups[0].items);
|
||||
Assert.NotNull(item.GetBehavior<Weapon>());
|
||||
|
|
@ -342,7 +468,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
public void NonWeaponGroupIndexBuildsNothing()
|
||||
{
|
||||
using var cache = Open();
|
||||
var items = new ItemManager(cache, RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var lampGrouped = WithDesign(Armed(cache), 0, cache, "Lamp");
|
||||
var failures = new List<string>();
|
||||
Assert.Null(Build(items, lampGrouped, failures));
|
||||
|
|
@ -353,7 +479,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
public void MoreWeaponGroupsThanTheGameHasBuildsNothing()
|
||||
{
|
||||
using var cache = Open();
|
||||
var items = new ItemManager(cache, RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var wide = Armed(cache);
|
||||
wide.WeaponGroups = wide.WeaponGroups.Append(new[] { 0 }).ToArray();
|
||||
var failures = new List<string>();
|
||||
|
|
@ -367,7 +493,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
public void FewerWeaponGroupsArePaddedToTheGameCount()
|
||||
{
|
||||
using var cache = Open();
|
||||
var items = new ItemManager(cache, RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var armed = Armed(cache);
|
||||
armed.WeaponGroups = new[] { new[] { 0 } };
|
||||
foreach (var groups in new[] { armed.WeaponGroups, null })
|
||||
|
|
|
|||
|
|
@ -40,13 +40,13 @@ public sealed class RunSaveTests : IDisposable
|
|||
using (var cache = Open())
|
||||
{
|
||||
contents ??= StageZoneContents(cache);
|
||||
RunSave.Commit(cache, Game(cache), Zones(3, contents, 0));
|
||||
RunSave.Commit(cache, Game(cache), Zones(3, contents, 0), new ProvenanceLedger());
|
||||
}
|
||||
|
||||
using (var cache = Open())
|
||||
{
|
||||
Assert.Equal(7, RunRecordCount(cache));
|
||||
Assert.Equal(7, RecordsIn(Run));
|
||||
Assert.Equal(8, RunRecordCount(cache));
|
||||
Assert.Equal(8, RecordsIn(Run));
|
||||
Assert.Equal(new[] { "savedzone-0", "savedzone-1", "savedzone-2" }, SavedZoneKeys(cache));
|
||||
}
|
||||
}
|
||||
|
|
@ -57,8 +57,8 @@ public sealed class RunSaveTests : IDisposable
|
|||
{
|
||||
using (var cache = Open())
|
||||
{
|
||||
RunSave.Commit(cache, Game(cache), Zones(3, null, -1));
|
||||
RunSave.Commit(cache, Game(cache), Zones(2, null, -1));
|
||||
RunSave.Commit(cache, Game(cache), Zones(3, null, -1), new ProvenanceLedger());
|
||||
RunSave.Commit(cache, Game(cache), Zones(2, null, -1), new ProvenanceLedger());
|
||||
}
|
||||
|
||||
using (var cache = Open())
|
||||
|
|
@ -73,25 +73,25 @@ public sealed class RunSaveTests : IDisposable
|
|||
var before = new[] { Catalog, Player }.Select(Hash).ToArray();
|
||||
|
||||
using (var cache = Open())
|
||||
RunSave.Commit(cache, Game(cache), Zones(3, StageZoneContents(cache), 0));
|
||||
RunSave.Commit(cache, Game(cache), Zones(3, StageZoneContents(cache), 0), new ProvenanceLedger());
|
||||
|
||||
Assert.Equal(before, new[] { Catalog, Player }.Select(Hash).ToArray());
|
||||
Assert.Equal(7, RecordsIn(Run));
|
||||
Assert.Equal(8, RecordsIn(Run));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResumeThenSaveKeepsUnloadedZoneContents()
|
||||
{
|
||||
using (var cache = Open())
|
||||
RunSave.Commit(cache, Game(cache), Zones(2, StageZoneContents(cache), 1));
|
||||
RunSave.Commit(cache, Game(cache), Zones(2, StageZoneContents(cache), 1), new ProvenanceLedger());
|
||||
|
||||
using (var cache = Open())
|
||||
{
|
||||
var galaxy = new Galaxy(cache, cache.GetGlobal<SavedGame>(), _ => { });
|
||||
var itemManager = new ItemManager(cache, TestSettings(), _ => { });
|
||||
var itemManager = new ItemManager(cache, new ProvenanceLedger(), TestSettings(), _ => { });
|
||||
galaxy.Zones[0].Contents = new Zone(itemManager, new PlanetSettings(), new ZonePack(), galaxy.Zones[0], galaxy);
|
||||
var (game, zones) = RunSave.Capture(cache, galaxy, galaxy.Zones[0].Contents, null, false, new SavedActionBarBinding[0]);
|
||||
RunSave.Commit(cache, game, zones);
|
||||
RunSave.Commit(cache, game, zones, RunSave.Lots(cache));
|
||||
}
|
||||
|
||||
using (var cache = Open())
|
||||
|
|
@ -109,7 +109,7 @@ public sealed class RunSaveTests : IDisposable
|
|||
using (var cache = Open())
|
||||
{
|
||||
cache.Commit(batch => batch.Upsert(new PlayerSettings()));
|
||||
RunSave.Commit(cache, Game(cache), Zones(3, StageZoneContents(cache), 0));
|
||||
RunSave.Commit(cache, Game(cache), Zones(3, StageZoneContents(cache), 0), new ProvenanceLedger());
|
||||
}
|
||||
var before = new[] { Catalog, Player }.Select(Hash).ToArray();
|
||||
|
||||
|
|
@ -125,6 +125,80 @@ public sealed class RunSaveTests : IDisposable
|
|||
Assert.Equal(before, new[] { Catalog, Player }.Select(Hash).ToArray());
|
||||
}
|
||||
|
||||
// Lot 1 sits on the root ship's hull; lot 3 (Produced from facility 4, input 5) sits in a docked child's
|
||||
// docking-bay contents; lots 2 and 6 are minted but referenced by nothing packed. Only the reachable closure
|
||||
// {1, 3, 4, 5} survives into the written copy; the live ledger keeps every lot.
|
||||
[Fact]
|
||||
public void CommitKeepsOnlyReachableLots()
|
||||
{
|
||||
using (var cache = Open())
|
||||
{
|
||||
var lots = new ProvenanceLedger { NextLot = 7 };
|
||||
lots.Lots[1] = new Lot { Origin = new Attributed() };
|
||||
lots.Lots[2] = new Lot { Origin = new Attributed() };
|
||||
lots.Lots[3] = new Lot { Origin = new Produced { Facility = 4, Inputs = new[] { 5 } } };
|
||||
lots.Lots[4] = new Lot { Origin = new Attributed() };
|
||||
lots.Lots[5] = new Lot { Origin = new Extracted() };
|
||||
lots.Lots[6] = new Lot { Origin = new Attributed() };
|
||||
|
||||
var child = BarePack(hull: null, dockingBayContents: new (int2, ItemInstance)[][]
|
||||
{
|
||||
new (int2, ItemInstance)[] { (default, new CompoundCommodity { Lot = 3 }) }
|
||||
});
|
||||
var root = BarePack(hull: new EquippableItem { Lot = 1 }, children: new EntityPack[] { child });
|
||||
|
||||
var zones = new[]
|
||||
{
|
||||
new SavedZone
|
||||
{
|
||||
Name = "Zone 0", AdjacentZones = Array.Empty<int>(), Factions = Array.Empty<int>(), Owner = -1,
|
||||
Contents = new ZonePack { Entities = new List<EntityPack> { root } }
|
||||
}
|
||||
};
|
||||
RunSave.Commit(cache, Game(cache), zones, lots);
|
||||
|
||||
// The live ledger is never pruned.
|
||||
Assert.Equal(new[] { 1, 2, 3, 4, 5, 6 }, lots.Lots.Keys.OrderBy(k => k));
|
||||
}
|
||||
|
||||
using (var cache = Open())
|
||||
{
|
||||
var stored = RunSave.Lots(cache);
|
||||
Assert.Equal(new[] { 1, 3, 4, 5 }, stored.Lots.Keys.OrderBy(k => k));
|
||||
Assert.Equal(7, stored.NextLot);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MissingLotIsLoud()
|
||||
{
|
||||
var ledger = new ProvenanceLedger();
|
||||
Assert.Throws<InvalidOperationException>(() => ledger[0]);
|
||||
Assert.Throws<InvalidOperationException>(() => ledger.Reachable(new[] { 9 }));
|
||||
}
|
||||
|
||||
// A minimal, valid EntityPack with every collection field non-null, for tests that build packs directly rather
|
||||
// than through EntitySerializer.Pack.
|
||||
private static ShipPack BarePack(EquippableItem hull, EntityPack[] children = null,
|
||||
(int2, ItemInstance)[][] dockingBayContents = null) => new ShipPack
|
||||
{
|
||||
Name = "ship",
|
||||
Hull = hull,
|
||||
Equipment = Array.Empty<(int2, EquippableItem)>(),
|
||||
CargoBays = Array.Empty<(int2, EquippableItem)>(),
|
||||
DockingBays = Array.Empty<(int2, EquippableItem)>(),
|
||||
CargoContents = Array.Empty<(int2, ItemInstance)[]>(),
|
||||
DockingBayContents = dockingBayContents ?? Array.Empty<(int2, ItemInstance)[]>(),
|
||||
Children = children ?? Array.Empty<EntityPack>(),
|
||||
PersistedBehaviors = new Dictionary<int2, PersistentBehaviorData[]>(),
|
||||
Temperature = new float[0, 0],
|
||||
Armor = new float[0, 0],
|
||||
Conductivity = new bool2[0, 0],
|
||||
DockingBayAssignments = Array.Empty<int>(),
|
||||
Settings = new EntitySettings(),
|
||||
WeaponGroups = Array.Empty<int[]>()
|
||||
};
|
||||
|
||||
// Zone seeds hash names with this; string.GetHashCode differs per process on .NET Core. The literal was computed
|
||||
// once, from an independent transcription of CultMath's pcg over the UTF-8 bytes.
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Reference in a new issue