namespace Theriapolis.Core.Rules.Reputation; /// /// Phase 6 M2 — append-only event log surfaced by the reputation screen /// ("why does so-and-so hate me?"). Bounded to a reasonable tail so the /// save file stays small even after a 100-hour playthrough. /// /// Phase 6 M5 layers propagation on top: each entry will be re-walked /// per game-day to fan out into other settlements with distance/time /// decay. /// public sealed class RepLedger { public const int MaxEntries = 256; private readonly List _entries = new(); private int _nextSeq = 1; public IReadOnlyList Entries => _entries; public int Count => _entries.Count; /// /// Append to the ledger. If /// is 0, a fresh monotone id is assigned; otherwise the supplied id /// is preserved (used by the save-restore path). /// public RepEvent Append(RepEvent ev) { if (ev.SequenceId == 0) ev = ev with { SequenceId = _nextSeq++ }; else if (ev.SequenceId >= _nextSeq) _nextSeq = ev.SequenceId + 1; _entries.Add(ev); if (_entries.Count > MaxEntries) _entries.RemoveAt(0); return ev; } public void Clear() { _entries.Clear(); _nextSeq = 1; } /// Largest issued so far. 0 = empty ledger. public int HighestSequenceId => _nextSeq - 1; /// Most recent N events affecting . public IEnumerable ForFaction(string factionId, int count = 8) { int yielded = 0; for (int i = _entries.Count - 1; i >= 0 && yielded < count; i--) { if (string.Equals(_entries[i].FactionId, factionId, System.StringComparison.OrdinalIgnoreCase)) { yielded++; yield return _entries[i]; } } } /// Most recent N events affecting . public IEnumerable ForRole(string roleTag, int count = 8) { int yielded = 0; for (int i = _entries.Count - 1; i >= 0 && yielded < count; i--) { if (string.Equals(_entries[i].RoleTag, roleTag, System.StringComparison.OrdinalIgnoreCase)) { yielded++; yield return _entries[i]; } } } }