Files
Christopher Wiebe b451f83174 Initial commit: Theriapolis baseline at port/godot branch point
Captures the pre-Godot-port state of the codebase. This is the rollback
anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md).
All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:40:51 -07:00

111 lines
3.9 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Theriapolis.Core;
using Theriapolis.Core.Data;
using Theriapolis.Core.Rules.Reputation;
using Xunit;
namespace Theriapolis.Tests.Reputation;
/// <summary>
/// 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.
/// </summary>
public sealed class FactionOppositionTests
{
private static IReadOnlyDictionary<string, FactionDef> 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);
}
}