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,
};
}
}