A lot records its product: Lot.Product owns an item's brand, Brand reads it

Brand no longer re-derives the product from maker and design (key-first pick);
CreateLot(product) is the one writer of Lot.Product. The census duplicate-brand
block and the key-order tie-break test go with the derivation they policed.
Unverified at this commit; build and tests follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-08 16:45:56 +02:00
commit 978632bc26
6 changed files with 89 additions and 56 deletions

View file

@ -140,6 +140,7 @@ public class ItemManager
{
Design = ItemData.RefOf<ItemData>(design),
Origin = new Attributed { Faction = product.Manufacturer },
Product = ItemData.RefOf(product),
Quality = RollQuality(),
Roles = new List<RoleFill>()
};
@ -200,9 +201,10 @@ public class ItemManager
return CreateInstance(CreateLot(product));
}
// Who made an item and what it is branded as, derived from its lot's provenance. Null maker (an unset faction,
// or an Extracted lot with none) means no brand at all; a set maker with no matching product still shows the
// maker, with no product name or flavour text.
// Who made an item and what it is branded as. The maker derives from its lot's provenance and the product is the
// one its lot records; nothing re-derives it. Null maker (an unset faction, or an Extracted lot with none) means
// no brand at all; a set maker whose lot records no product (or one the catalog no longer holds) still shows
// the maker, with no product name or flavour text.
public (Faction Maker, FactionProductData Product) Brand(CraftedItemInstance item)
{
var lot = GetLot(item);
@ -214,14 +216,11 @@ public class ItemManager
};
if (!maker.IsSet()) return (null, null);
var product = ItemData.GetAll<FactionProductData>()
.Where(p => p.Manufacturer.Key.Equals(maker.Key) && p.Design.Key.Equals(item.Data.Key))
.OrderBy(p => ItemData.RefOf(p).Key.Value, StringComparer.Ordinal)
.FirstOrDefault();
var product = lot.Product.IsSet() ? ItemData.Get(lot.Product) : null;
return (ItemData.Get(maker), product);
}
// What the player reads an item as: its product's name when its lot's maker has a product of this design,
// What the player reads an item as: its lot's product name when it records one,
// otherwise the design's own name (unbranded, console-given and extracted lots, and commodities).
public string Title(ItemInstance item) =>
(item is CraftedItemInstance crafted ? Brand(crafted).Product?.Name : null) ?? GetData(item).Name;

View file

@ -58,8 +58,8 @@ public class ProvenanceLedger
}
}
// One lot: the design it built, who made it and from what, its rolled workmanship, and how well each design role
// was filled. Immutable once minted; instances carry a LotId, never a copy.
// One lot: the design it built, who made it and from what, its rolled workmanship, how well each design role
// was filled, and the branded product it was made as. Immutable once minted; instances carry a LotId, never a copy.
[MessagePackObject]
public class Lot
{
@ -68,6 +68,10 @@ public class Lot
[Key(2)] public float Quality;
[Key(3)] public List<RoleFill> Roles;
// The product the lot was made as: the one owner of which product names its units. Unset for unbranded lots
// (console give, presets, extracted), which brand their maker only.
[Key(4)] public CultRecordRef<FactionProductData> Product;
// The quality a stat reads for one of the design's roles. An unnamed role, a design without roles, and a lot
// minted before roles existed all fall back to the lot's own workmanship.
public float QualityForRole(string role)