using AcDream.Core.CharGen; namespace AcDream.Core.Tests.CharGen; /// /// Tests for — retail's skill-credit /// spend port (CharGenState::UpdateRemainingSkillCredits @ /// 0x005C37C0). /// public sealed class ChargenSkillCreditMathTests { private static readonly Dictionary Costs = new() { [1u] = new ChargenSkillCost(1u, NormalCost: 4, PrimaryCost: 12), // Axe [11u] = new ChargenSkillCost(11u, NormalCost: 4, PrimaryCost: 12), // Sword [24u] = new ChargenSkillCost(24u, NormalCost: 1, PrimaryCost: 3), // Run // Deliberately no entry for skill id 2 (Bow) — heritage doesn't offer it. }; [Fact] public void ComputeSpent_IgnoresInactiveAndUntrainedSkills() { var advancement = new ChargenSkillAdvancementSet { [1u] = ChargenSkillAdvancementClass.Inactive, [11u] = ChargenSkillAdvancementClass.Untrained, }; Assert.Equal(0, ChargenSkillCreditMath.ComputeSpent(advancement, Costs)); } [Fact] public void ComputeSpent_ChargesNormalCostForTrainedSkills() { var advancement = new ChargenSkillAdvancementSet { [1u] = ChargenSkillAdvancementClass.Trained }; Assert.Equal(4, ChargenSkillCreditMath.ComputeSpent(advancement, Costs)); } [Fact] public void ComputeSpent_ChargesPrimaryCostInsteadOfNormalCostForSpecializedSkills() { // Retail adds exactly one of NormalCost/PrimaryCost per skill, never // both — PrimaryCost is the TOTAL cost to reach Specialized. var advancement = new ChargenSkillAdvancementSet { [1u] = ChargenSkillAdvancementClass.Specialized }; Assert.Equal(12, ChargenSkillCreditMath.ComputeSpent(advancement, Costs)); } [Fact] public void ComputeSpent_SumsAcrossMultipleTrainedAndSpecializedSkills() { var advancement = new ChargenSkillAdvancementSet { [1u] = ChargenSkillAdvancementClass.Trained, // 4 [11u] = ChargenSkillAdvancementClass.Specialized, // 12 [24u] = ChargenSkillAdvancementClass.Trained, // 1 }; Assert.Equal(17, ChargenSkillCreditMath.ComputeSpent(advancement, Costs)); } [Fact] public void ComputeSpent_SkillWithNoCostEntryIsSkippedDefensively() { var advancement = new ChargenSkillAdvancementSet { [2u] = ChargenSkillAdvancementClass.Trained }; Assert.Equal(0, ChargenSkillCreditMath.ComputeSpent(advancement, Costs)); } [Fact] public void RemainingCredits_IsTotalMinusSpent_AndMayGoNegativeUnlikeAttributes() { var advancement = new ChargenSkillAdvancementSet { [1u] = ChargenSkillAdvancementClass.Trained, [11u] = ChargenSkillAdvancementClass.Specialized, }; Assert.Equal(84, ChargenSkillCreditMath.RemainingCredits(100u, advancement, Costs)); // Retail's Finish gate never checks remainingSkillCredits, so // overspending relative to the (small, synthetic) budget below is a // representable state, not a thrown exception. Assert.Equal(-16, ChargenSkillCreditMath.RemainingCredits(0u, advancement, Costs)); } }