Catalog read refuses a bin with a non-finite Floor, a missing Line or a Line that is a bin; a bin mints from exactly its named line (tested)
The runtime no longer trusts the authoring tool for a bin: a NaN Floor weighed NaN and nulled every draw of its design. AetheriaStores.Open names the field and echoes no value. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
129e5ff4dc
commit
f73507ff2f
2 changed files with 97 additions and 0 deletions
|
|
@ -41,6 +41,7 @@ public static class AetheriaStores
|
|||
foreach (var data in cache.GetAll<EquippableItemData>()) CultRecordRefs.Validate(data);
|
||||
foreach (var data in cache.GetAll<ConsumableItemData>()) CultRecordRefs.Validate(data);
|
||||
VerseGrammarValidation.ValidateAll(cache.GetAll<VerseVerb>());
|
||||
ValidateBins(cache);
|
||||
return cache;
|
||||
}
|
||||
catch
|
||||
|
|
@ -49,6 +50,24 @@ public static class AetheriaStores
|
|||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// The mint and the weighting trust a bin: they never check its Floor or its Line. So the catalog read refuses one whose Floor is
|
||||
// not a finite number (it would weigh NaN and poison every draw of its design), whose Line names no product the catalog holds, or
|
||||
// whose Line is itself a bin (a bin is of a line, never of a bin). The refusals name the field and echo no value.
|
||||
private static void ValidateBins(CultCache cache)
|
||||
{
|
||||
foreach (var product in cache.GetAll<FactionProductData>())
|
||||
{
|
||||
if (product.Bin == null) continue;
|
||||
if (!float.IsFinite(product.Bin.Floor))
|
||||
throw new InvalidOperationException("A product bin's Floor is not a finite number.");
|
||||
var line = cache.Get(product.Bin.Line);
|
||||
if (line == null)
|
||||
throw new InvalidOperationException("A product bin's Line names no product this catalog holds.");
|
||||
if (line.Bin != null)
|
||||
throw new InvalidOperationException("A product bin's Line names another bin; a bin is of a line.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class CultRecordRefs
|
||||
|
|
|
|||
|
|
@ -183,6 +183,84 @@ public sealed class ProductBinTests : IDisposable
|
|||
Assert.Equal("Premiere", product.Name);
|
||||
}
|
||||
|
||||
// The line a bin draws from is the one Bin.Line names, not another line of the design: three lines of one design, by three
|
||||
// makers, with the bin on the middle one by name, so a bin that reads the first or the last line of the design is far off.
|
||||
[Fact]
|
||||
public void ABinMintsFromExactlyTheLineItNames()
|
||||
{
|
||||
Line("A line", lensMean: .3f, makerMean: .2f);
|
||||
var middle = Line("M line", lensMean: .6f, makerMean: .5f);
|
||||
Line("Z line", lensMean: .9f, makerMean: .9f);
|
||||
var lots = Mint(Bin(middle, "Middle Premiere", 0), 400);
|
||||
Assert.All(lots, lot =>
|
||||
{
|
||||
Assert.True(lot.Roles.Single().Quality >= .6f - 1e-5f, "a fill came from a line below the named one");
|
||||
Assert.True(lot.Quality >= .5f - 1e-5f, "an assembly came from a maker below the named line's");
|
||||
});
|
||||
Assert.InRange(Fills(lots).Average(), .64f, .72f);
|
||||
Assert.InRange(lots.Average(lot => lot.Quality), .55f, .62f);
|
||||
}
|
||||
|
||||
private const string Canary = "canary-7f3a91";
|
||||
|
||||
private enum Damage { None, LineRemoved, LineIsBin }
|
||||
|
||||
// A catalog on disk holding one line and a bin of it, damaged as asked, for AetheriaStores.Open to read.
|
||||
private string DamagedCatalog(float floor, Damage damage)
|
||||
{
|
||||
var path = Path.Combine(_root, Guid.NewGuid().ToString("N"), "Aetheria.cc");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
using var seed = AetheriaStores.Open(path, catalogWritable: true);
|
||||
seed.Upsert(new VerseGrammar { Revision = 1 });
|
||||
var maker = seed.Upsert(new Faction { Name = Canary + " Maker" });
|
||||
var gear = seed.Upsert(new GearData { Name = "Lensed", Hardpoint = HardpointType.Sensors, Shape = new Shape(), Price = 10, Roles = { new ItemRole { Name = "lens" } } });
|
||||
var design = new CultRecordRef<CraftedItemData>(gear.Key);
|
||||
var line = seed.Upsert(new FactionProductData { Name = Canary + " Line", Design = design, Manufacturer = maker, Roles = { new ProductRole { Role = "lens" } } });
|
||||
var bin = seed.Upsert(new FactionProductData { Name = Canary, Design = design, Manufacturer = maker, Bin = new ProductBin { Line = line, Floor = floor } });
|
||||
if (damage == Damage.LineIsBin)
|
||||
seed.Upsert(new FactionProductData { Name = Canary + " Nested", Design = design, Manufacturer = maker, Bin = new ProductBin { Line = bin, Floor = 0 } });
|
||||
if (damage == Damage.LineRemoved) seed.Remove(line.Key);
|
||||
seed.FlushAsync().Wait();
|
||||
return path;
|
||||
}
|
||||
|
||||
// The runtime does not trust authoring for a bin: a NaN floor would weigh NaN and null every draw of its design.
|
||||
[Theory]
|
||||
[InlineData(float.NaN)]
|
||||
[InlineData(float.PositiveInfinity)]
|
||||
[InlineData(float.NegativeInfinity)]
|
||||
public void ACatalogWhoseBinFloorIsNotFiniteIsRefusedWhereItIsRead(float floor)
|
||||
{
|
||||
var refusal = Assert.Throws<InvalidOperationException>(() => AetheriaStores.Open(DamagedCatalog(floor, Damage.None)));
|
||||
Assert.Contains("Floor", refusal.Message);
|
||||
Assert.DoesNotContain(Canary, refusal.Message);
|
||||
Assert.DoesNotContain(floor.ToString(), refusal.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ACatalogWhoseBinNamesNoLineIsRefusedWhereItIsRead()
|
||||
{
|
||||
var refusal = Assert.Throws<InvalidOperationException>(() => AetheriaStores.Open(DamagedCatalog(.67f, Damage.LineRemoved)));
|
||||
Assert.Contains("Line", refusal.Message);
|
||||
Assert.DoesNotContain(Canary, refusal.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ACatalogWhoseBinNamesAnotherBinIsRefusedWhereItIsRead()
|
||||
{
|
||||
var refusal = Assert.Throws<InvalidOperationException>(() => AetheriaStores.Open(DamagedCatalog(.67f, Damage.LineIsBin)));
|
||||
Assert.Contains("Line", refusal.Message);
|
||||
Assert.DoesNotContain(Canary, refusal.Message);
|
||||
}
|
||||
|
||||
// The refusals are of damage, not of bins: a sound bin on a sound line reads.
|
||||
[Fact]
|
||||
public void ACatalogWithASoundBinIsRead()
|
||||
{
|
||||
using var cache = AetheriaStores.Open(DamagedCatalog(.67f, Damage.None));
|
||||
Assert.Equal(.67f, cache.GetAll<FactionProductData>().Single(product => product.Name == Canary).Bin.Floor);
|
||||
}
|
||||
|
||||
// A bin whose line the catalog does not hold mints as a product with no roles of its own, and says so.
|
||||
[Fact]
|
||||
public void ABinOfALineTheCatalogLacksMintsAtDefaultsAndLogs()
|
||||
|
|
|
|||
Loading…
Reference in a new issue