namespace Theriapolis.Core.Rules.Reputation; /// /// Phase 6 M2 — Layer 3 of the disposition stack: how a *specific* NPC /// feels about the player based on direct personal experience. Per /// reputation.md §I-3, only NPCs the player has actually /// interacted with accumulate one of these — generic shopkeepers walked /// past don't bloat state. /// /// Keyed by role tag (anchor-prefixed for named NPCs: /// "millhaven.innkeeper"). Generic NPCs that the player talks to register /// briefly under their generic tag but typically reset on chunk evict — /// only named NPCs carry a stable id across reloads. /// public sealed class PersonalDisposition { /// Role tag identifying which NPC this record belongs to. public string RoleTag { get; init; } = ""; /// Score relative to neutral, clamped to ±C.REP_MAX. public int Score { get; set; } /// Trust ladder accumulated across the interaction log. public TrustLevel Trust { get; set; } = TrustLevel.Stranger; /// True after the player betrayed this specific NPC. Sticky — only narrative /// effects can clear it. public bool Betrayed { get; set; } /// Last interaction time in WorldClock seconds. 0 = never interacted. public long LastInteractionSeconds { get; set; } /// Free-form memory tags ("saved-my-kit", "lied-about-rawfang", ...). public HashSet Memory { get; } = new(); /// Last N events affecting this specific NPC. Bounded — see . public List Log { get; } = new(); public const int MaxLogEntries = 32; /// Append a personal event and apply its magnitude to . public void Apply(RepEvent ev) { Score = System.Math.Clamp(Score + ev.Magnitude, C.REP_MIN, C.REP_MAX); if (ev.Kind == RepEventKind.Betrayal && ev.Magnitude < 0) Betrayed = true; Log.Add(ev); if (Log.Count > MaxLogEntries) Log.RemoveAt(0); LastInteractionSeconds = ev.TimestampSeconds; Trust = ComputeTrust(); } /// /// Trust ladder derived from positive interaction count. Negative events /// don't promote; betrayal demotes to Stranger regardless of history. /// private TrustLevel ComputeTrust() { if (Betrayed) return TrustLevel.Stranger; int positives = 0; foreach (var e in Log) if (e.Magnitude > 0) positives++; return positives switch { >= 12 => TrustLevel.Bonded, >= 7 => TrustLevel.Trusted, >= 3 => TrustLevel.Familiar, >= 1 => TrustLevel.Acquaintance, _ => TrustLevel.Stranger, }; } }