diff --git a/Assets/Scripts/ServerShared/ItemData.cs b/Assets/Scripts/ServerShared/ItemData.cs index e2bc01fb2..b416cfef0 100644 --- a/Assets/Scripts/ServerShared/ItemData.cs +++ b/Assets/Scripts/ServerShared/ItemData.cs @@ -276,9 +276,9 @@ public abstract class ItemData [Inspectable, CultInspectorTextArea, JsonProperty("description"), Key(2)] public string Description; - - [Inspectable, JsonProperty("creator"), Key(3)] - public CultRecordRef Manufacturer; + + // MessagePack key 3 belonged to a removed creator/faction reference field; do not reuse it. That provenance + // now lives on FactionProductData, and branding is derived, never stored on the design. [Inspectable, JsonProperty("mass"), Key(4)] public float Mass; diff --git a/tests/Aetheria.Shared.Tests/AetheriaStoresTests.cs b/tests/Aetheria.Shared.Tests/AetheriaStoresTests.cs index 12a961e69..1542950f0 100644 --- a/tests/Aetheria.Shared.Tests/AetheriaStoresTests.cs +++ b/tests/Aetheria.Shared.Tests/AetheriaStoresTests.cs @@ -29,7 +29,8 @@ public sealed class AetheriaStoresTests : IDisposable var faction = new Faction { Name = "Adrasteia", ShortName = "ADR", PrimaryColor = new float3(1, .5f, .25f) }; faction.Allegiance[cache.Upsert(faction)] = 1; cache.Upsert(faction); - cache.Upsert(new WeaponItemData { Name = "Lance", Manufacturer = cache.RefOf(faction) }); + var lance = cache.Upsert(new WeaponItemData { Name = "Lance" }); + cache.Upsert(new FactionProductData { Name = "Lance", Design = new CultRecordRef(lance.Key), Manufacturer = cache.RefOf(faction) }); cache.Upsert(new InputLayout { Rows = new InputLayoutRow[] { new InputLayoutRowSpacer { Height = 1 } } }); cache.FlushAsync().Wait(); } @@ -106,7 +107,7 @@ public sealed class AetheriaStoresTests : IDisposable { using var cache = AetheriaStores.Open(Catalog); var faction = cache.GetAll().Single(); - Assert.Same(faction, cache.Get(cache.GetAll().Single().Manufacturer)); + Assert.Same(faction, cache.Get(cache.GetAll().Single().Manufacturer)); Assert.Same(faction, cache.Get(faction.Allegiance.Keys.Single())); Assert.Same(faction, cache.GetByName("Adrasteia")); } diff --git a/tests/mutation_tests.py b/tests/mutation_tests.py index 70dcc370c..be918d028 100644 --- a/tests/mutation_tests.py +++ b/tests/mutation_tests.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Mutation tests for the Cut B provenance rules (docs/item-provenance-cut.md). +"""Mutation tests for the Cut B and Cut C provenance rules (docs/item-provenance-cut.md). For each rule this applies its named mutation to an exact, unique anchor of source text, runs the one test that should catch it, restores the file @@ -259,6 +259,22 @@ MUTATIONS: list[Mutation] = [ test="RunSaveTests.CommitTakesRootsFromEveryZone", expect="red", ), + # --- Cut C: the manufacturer moves from the design to the product; the catalog fixture and its + # round-trip assertion (FactionIsASingletonInstance) must actually depend on that wiring. --- + Mutation( + rule="the catalog fixture's product carries the maker, not an unset reference", + file="tests/Aetheria.Shared.Tests/AetheriaStoresTests.cs", + anchor=( + 'cache.Upsert(new FactionProductData { Name = "Lance", ' + "Design = new CultRecordRef(lance.Key), Manufacturer = cache.RefOf(faction) });" + ), + mutated=( + 'cache.Upsert(new FactionProductData { Name = "Lance", ' + "Design = new CultRecordRef(lance.Key), Manufacturer = default });" + ), + test="AetheriaStoresTests.FactionIsASingletonInstance", + expect="red", + ), ] diff --git a/tools/AetherDb/Program.cs b/tools/AetherDb/Program.cs index 01334b388..662001dd7 100644 --- a/tools/AetherDb/Program.cs +++ b/tools/AetherDb/Program.cs @@ -40,7 +40,17 @@ public static class Program { var db = AetherDb.Open(); var items = db.Cache.GetAll().ToArray(); - var products = db.Cache.GetAll().ToArray(); + var products = db.Cache.GetAll() + .OrderBy(p => db.Cache.RefOf(p).Key.Value, StringComparer.Ordinal).ToArray(); + + // The manufacturer no longer lives on the design: it is derived the way Brand() derives it, from the + // first product in record-key order that sells the design. A design no product sells shows "(none)". + string MakerOf(EquippableItemData item) + { + var design = db.Cache.RefOf(item).Key; + var product = products.FirstOrDefault(p => p.Design.Key.Equals(design)); + return product == null ? "(none)" : db.Cache.Get(product.Manufacturer)?.ShortName ?? "(none)"; + } var kinds = new Dictionary>(); foreach (var item in items) @@ -51,7 +61,7 @@ public static class Program : item is WeaponItemData weapon ? $"Weapon/{weapon.HardpointType}" : item.HardpointType.ToString(); if (!kinds.TryGetValue(kind, out var makers)) kinds[kind] = makers = new List(); - makers.Add(db.Cache.Get(item.Manufacturer)?.ShortName ?? "(none)"); + makers.Add(MakerOf(item)); } Console.WriteLine($"{items.Length} designs, {products.Length} products\n");