using Theriapolis.Core; using Theriapolis.Core.Data; using Theriapolis.Core.Rules.Reputation; using Xunit; namespace Theriapolis.Tests.Reputation; /// /// Phase 6 M2 — opposition matrix application. /// /// The plan §I-2 spells out the exact multipliers; this suite verifies /// the cascade fires in both directions (gains and losses) and stays /// inside the clamp range. /// public sealed class FactionOppositionTests { private static IReadOnlyDictionary LoadFactions() => new ContentResolver(new ContentLoader(TestHelpers.DataDirectory)).Factions; [Fact] public void GainWithInheritors_CascadesIntoOppositionLosses() { var factions = LoadFactions(); var standing = new FactionStanding(); var applied = standing.Apply("inheritors", 10, factions); // Per the doc: +10 with Inheritors should yield -5 Enforcers, // -2 Thorn Council, -3 Hybrid Underground, -3 Unsheathed. Assert.Equal( 10, standing.Get("inheritors")); Assert.Equal( -5, standing.Get("covenant_enforcers")); Assert.Equal( -2, standing.Get("thorn_council")); Assert.Equal( -3, standing.Get("hybrid_underground")); Assert.Equal( -3, standing.Get("unsheathed")); Assert.Equal( 0, standing.Get("merchant_guilds")); // multiplier 0 // The applied list reports every actually-changed faction. Assert.Contains(applied, t => t.FactionId == "inheritors" && t.Delta == 10); Assert.Contains(applied, t => t.FactionId == "covenant_enforcers" && t.Delta == -5); } [Fact] public void LossWithInheritors_CascadesIntoOppositionGains() { var factions = LoadFactions(); var standing = new FactionStanding(); standing.Apply("inheritors", -20, factions); // -20 × -0.5 = +10 with Enforcers. Assert.Equal(-20, standing.Get("inheritors")); Assert.Equal( 10, standing.Get("covenant_enforcers")); Assert.Equal( 4, standing.Get("thorn_council")); Assert.Equal( 6, standing.Get("hybrid_underground")); Assert.Equal( 6, standing.Get("unsheathed")); } [Fact] public void Standing_IsClampedToRepRange() { var factions = LoadFactions(); var standing = new FactionStanding(); standing.Apply("inheritors", 200, factions); Assert.Equal(C.REP_MAX, standing.Get("inheritors")); // Cascaded -100 is below the floor — verify clamping. Assert.Equal(C.REP_MIN, standing.Get("covenant_enforcers")); } [Fact] public void ZeroDelta_DoesNothing() { var factions = LoadFactions(); var standing = new FactionStanding(); standing.Set("inheritors", 25); standing.Apply("inheritors", 0, factions); Assert.Equal(25, standing.Get("inheritors")); Assert.Equal( 0, standing.Get("covenant_enforcers")); } [Fact] public void UnknownFaction_NoCascade() { var factions = LoadFactions(); var standing = new FactionStanding(); // No throw; standing accumulates against the unknown id. standing.Apply("not_a_real_faction", 10, factions); Assert.Equal(10, standing.Get("not_a_real_faction")); Assert.Equal( 0, standing.Get("covenant_enforcers")); } [Fact] public void SubmitEvent_AppliesFactionAndPersonal() { var factions = LoadFactions(); var rep = new PlayerReputation(); rep.Submit(new RepEvent { Kind = RepEventKind.Quest, FactionId = "inheritors", RoleTag = "test.someone", Magnitude = 10, Note = "test event", }, factions); Assert.Equal(10, rep.Factions.Get("inheritors")); Assert.Equal(-5, rep.Factions.Get("covenant_enforcers")); Assert.Equal(10, rep.PersonalFor("test.someone").Score); Assert.Single(rep.Ledger.Entries); } }