Aetheria/Assets/Scripts/ServerShared/Provenance.cs
Meta Vens d23038f53c Assembly is the maker's: re-land of h1 on 0f5def17 (net diff of ae300c6a..5187a59d)
Applies the net diff of eureka/aetheria-release-assembly-from-maker (5187a59d) without its catalog blob and RunStartTests residue, git apply -3, overlaps resolved on master's side. 066fb581's message ('git log --oneline -1') was a heredoc slip; the old branch stays as it is. Drops the Rarity = fixture member from the tests master added since ae300c6a. Faction keys 19-20 here are h1's; the next commit reshapes them to one Assembly at 19.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:55:16 +02:00

126 lines
4.7 KiB
C#

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System;
using System.Collections.Generic;
using GameCult.Caching;
using MessagePack;
// The run's lot ledger: one run-scoped global document, LotId -> Lot. The live instance is ItemManager.Lots.
[CultDocument("aetheria.provenanceledger", "1"), CultGlobal, MessagePackObject]
public class ProvenanceLedger
{
[Key(0)] public int NextLot = 1;
[Key(1)] public Dictionary<int, Lot> Lots = new Dictionary<int, Lot>();
// The only way a lot enters the ledger.
public int Add(Lot lot)
{
var id = NextLot++;
Lots[id] = lot;
return id;
}
// Absent lots throw: a dangling LotId is corruption, and it must be loud. No try-variant.
public Lot this[int id]
{
get
{
if (!Lots.TryGetValue(id, out var lot))
throw new InvalidOperationException($"Lot {id} is not in the provenance ledger.");
return lot;
}
}
// A new ledger holding only lots reachable from roots, closed over Produced.Facility and Produced.Inputs. Never
// mutates this: the live ledger keeps unreachable lots, because a floor pickup can still be collected after a save.
public ProvenanceLedger Reachable(IEnumerable<int> roots)
{
var reached = new Dictionary<int, Lot>();
var pending = new Queue<int>(roots);
while (pending.Count > 0)
{
var id = pending.Dequeue();
if (reached.ContainsKey(id)) continue;
var lot = this[id];
reached[id] = lot;
if (lot.Origin is Produced produced)
{
pending.Enqueue(produced.Facility);
if (produced.Inputs != null)
foreach (var input in produced.Inputs)
pending.Enqueue(input);
}
}
return new ProvenanceLedger { NextLot = NextLot, Lots = reached };
}
}
// 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
{
[Key(0)] public CultRecordRef<ItemData> Design;
[Key(1)] public Provenance Origin;
[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: the part's, capped by the lot's own final assembly
// (quality-lean-assembly-hard-ceiling), so a part better than its assembly is worth the assembly. 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)
{
if (string.IsNullOrEmpty(role) || Roles == null) return Quality;
foreach (var fill in Roles)
if (fill.Role == role) return Math.Min(fill.Quality, Quality);
return Quality;
}
}
// One filled slot: the design's role name and how good the part that went into it turned out to be. Stats read it through
// Lot.QualityForRole, capped by the lot's assembly; the raw value is kept for disassembly (named-components-after-demo).
[MessagePackObject]
public class RoleFill
{
[Key(0)] public string Role;
[Key(1)] public float Quality;
}
// Who built a lot, where, and from what. Terminus mints Attributed placeholders; Produced and Extracted are the
// forward shape for backward generation, next scope.
[Union(0, typeof(Attributed)),
Union(1, typeof(Produced)),
Union(2, typeof(Extracted))]
public abstract class Provenance { }
// The Terminus placeholder: only the faction is known. No other level of the crafting pyramid is generated yet.
[MessagePackObject]
public class Attributed : Provenance
{
[Key(0)] public CultRecordRef<Faction> Faction;
}
// Built in place at a station, from a factory item's own lot and a set of input lots.
[MessagePackObject]
public class Produced : Provenance
{
[Key(0)] public CultRecordRef<Faction> Faction;
[Key(1)] public int Station;
[Key(2)] public int Facility;
[Key(3)] public int[] Inputs;
}
// A raw commodity pulled from a zone.
[MessagePackObject]
public class Extracted : Provenance
{
[Key(0)] public int Zone;
[Key(1)] public CultRecordRef<SimpleCommodityData> Commodity;
}