using System.Collections.Generic;
using AcDream.Core.Spells;
namespace AcDream.Core.Tests.Spells;
///
/// Tests for . Issue #6 architecture
/// validation — confirms the family-stacking dedup + identity
/// semantics work correctly.
///
///
/// Note: until ISSUES.md #12 lands the wire-format extension that
/// captures StatMod (type/key/val) on ,
/// the per-enchantment modifier value isn't aggregated yet — we
/// always return .
/// These tests confirm the architectural shape is correct for the
/// follow-up wire wiring.
///
///
public sealed class EnchantmentMathTests
{
[Fact]
public void Empty_ReturnsIdentity()
{
var mod = EnchantmentMath.GetMod(
new List(),
SpellTable.Empty,
EnchantmentMath.StatKey.MaxStamina);
Assert.Equal(EnchantmentMath.VitalMod.Identity, mod);
}
[Fact]
public void NoMatchingTableEntries_ReturnsIdentity()
{
// Active enchantments exist but none of them have entries in the
// SpellTable (so we can't read Family) — they're skipped.
var enchantments = new[]
{
new ActiveEnchantmentRecord(SpellId: 9999u, LayerId: 1u, Duration: 60f, CasterGuid: 0u),
new ActiveEnchantmentRecord(SpellId: 8888u, LayerId: 2u, Duration: 60f, CasterGuid: 0u),
};
var mod = EnchantmentMath.GetMod(enchantments, SpellTable.Empty,
EnchantmentMath.StatKey.MaxStamina);
Assert.Equal(EnchantmentMath.VitalMod.Identity, mod);
}
[Fact]
public void StatKey_ConstantsMatchAceEnum()
{
// ACE PropertyAttribute2nd enum: MaxHealth=1, MaxStamina=3, MaxMana=5.
// Verified against named-retail/acclient.h line 37287-37301.
Assert.Equal(1u, EnchantmentMath.StatKey.MaxHealth);
Assert.Equal(3u, EnchantmentMath.StatKey.MaxStamina);
Assert.Equal(5u, EnchantmentMath.StatKey.MaxMana);
}
[Fact]
public void Identity_IsOneAndZero()
{
Assert.Equal(1.0f, EnchantmentMath.VitalMod.Identity.Multiplier);
Assert.Equal(0.0f, EnchantmentMath.VitalMod.Identity.Additive);
}
[Fact]
public void FamilyStacking_DeduplicatesByFamily_KeepsHigherSpellId()
{
// Build a SpellTable with 2 spells in the same Family (e.g.
// Strength I and Strength VII, both Family=1). Both are in the
// active enchantment list; only the higher spell id should
// survive the family-stacking dedup.
// The aggregator currently returns Identity regardless (see
// class doc), but the dedup behaviour is observable by
// counting which records would be folded — exercised here to
// pin the architecture even before ISSUES.md #12 wires data.
// Family=1 strength buffs example.
var table = LoadTable(
(1u, "Strength I", 1u),
(132u, "Strength VII", 1u)); // same family
var enchantments = new[]
{
new ActiveEnchantmentRecord(SpellId: 1u, LayerId: 100u, Duration: 60f, CasterGuid: 0u),
new ActiveEnchantmentRecord(SpellId: 132u, LayerId: 101u, Duration: 60f, CasterGuid: 0u),
};
// Currently: identity result (StatMod data not yet on records).
// Test demonstrates the call doesn't throw + returns identity.
var mod = EnchantmentMath.GetMod(enchantments, table,
EnchantmentMath.StatKey.MaxStamina);
Assert.Equal(EnchantmentMath.VitalMod.Identity, mod);
}
[Fact]
public void Family_Zero_DoesNotDedup()
{
// Family 0 means "no stacking bucket" — each enchantment is
// its own bucket (synthetic key per layer). When ISSUES.md #12
// lands and we aggregate StatMods, both these buffs will
// contribute simultaneously.
var table = LoadTable(
(10u, "Buff A", 0u),
(20u, "Buff B", 0u));
var enchantments = new[]
{
new ActiveEnchantmentRecord(SpellId: 10u, LayerId: 100u, Duration: 60f, CasterGuid: 0u),
new ActiveEnchantmentRecord(SpellId: 20u, LayerId: 101u, Duration: 60f, CasterGuid: 0u),
};
var mod = EnchantmentMath.GetMod(enchantments, table,
EnchantmentMath.StatKey.MaxStamina);
// Identity for now; architecture confirmed via no-throw + result shape.
Assert.Equal(EnchantmentMath.VitalMod.Identity, mod);
}
private static SpellTable LoadTable(params (uint id, string name, uint family)[] rows)
{
// Build a synthetic CSV with just enough columns for SpellTable to
// resolve Family on each spell id.
var sb = new System.Text.StringBuilder();
sb.AppendLine("Spell ID,Spell ID [Hex],Name,SortKey,IconId [Hex],Difficulty,Duration,Family,Flags [Hex],Generation,IsDebuff,IsFastWindup,IsFellowship,IsIrresistible,IsOffensive,IsUntargetted,Mana,School,Speed,Spell Words,CasterEffect,TargetEffect,TargetMask [Hex],Type,Description,Unknown1,Unknown2,Unknown3,Unknown4,Unknown5,Unknown6,Unknown7,Unknown8,Unknown9,Unknown10");
foreach (var (id, name, family) in rows)
{
sb.Append(id).Append(',').Append("0x").Append(id.ToString("X")).Append(',')
.Append(name).Append(",0,0x0,1,1,").Append(family).Append(",0x0,1,False,False,False,False,False,False,1,War Magic,0,Words,0,0,0x0,1,Desc,0,0,0,0,0,0,0,0,0,0")
.AppendLine();
}
return SpellTable.LoadFromReader(new System.IO.StringReader(sb.ToString()));
}
}