590 lines
20 KiB
Swift
590 lines
20 KiB
Swift
//
|
|
// Bunch_O__DiceTests.swift
|
|
// Bunch O' DiceTests
|
|
//
|
|
// Created by Rory Hinnen on 22/2/20.
|
|
//
|
|
|
|
import Testing
|
|
@testable import DieHammer
|
|
|
|
// MARK: - Dice Total Calculation
|
|
|
|
@Suite("Dice Total Calculation")
|
|
struct DiceTotalTests {
|
|
|
|
// One die showing each face — a clean baseline for cutoff tests.
|
|
func oneEach() -> Dice {
|
|
var d = Dice()
|
|
d.one = 1; d.two = 1; d.three = 1; d.four = 1; d.five = 1; d.six = 1
|
|
return d
|
|
}
|
|
|
|
@Test("No modifier counts faces at or above cutoff")
|
|
func totalNoModifier() {
|
|
let d = oneEach()
|
|
#expect(d.total(cutoff: 6, modifier: .none) == 1)
|
|
#expect(d.total(cutoff: 5, modifier: .none) == 2)
|
|
#expect(d.total(cutoff: 4, modifier: .none) == 3)
|
|
#expect(d.total(cutoff: 3, modifier: .none) == 4)
|
|
#expect(d.total(cutoff: 2, modifier: .none) == 5)
|
|
}
|
|
|
|
@Test("Negative modifier effectively raises cutoff by 1")
|
|
func totalNegativeModifier() {
|
|
let d = oneEach()
|
|
#expect(d.total(cutoff: 5, modifier: .negative) == 1) // effectively 6+
|
|
#expect(d.total(cutoff: 4, modifier: .negative) == 2) // effectively 5+
|
|
#expect(d.total(cutoff: 3, modifier: .negative) == 3) // effectively 4+
|
|
#expect(d.total(cutoff: 2, modifier: .negative) == 4) // effectively 3+
|
|
#expect(d.total(cutoff: 1, modifier: .negative) == 5) // effectively 2+
|
|
}
|
|
|
|
@Test("Positive modifier effectively lowers cutoff by 1")
|
|
func totalPositiveModifier() {
|
|
let d = oneEach()
|
|
#expect(d.total(cutoff: 6, modifier: .positive) == 2) // effectively 5+
|
|
#expect(d.total(cutoff: 5, modifier: .positive) == 3) // effectively 4+
|
|
#expect(d.total(cutoff: 4, modifier: .positive) == 4) // effectively 3+
|
|
#expect(d.total(cutoff: 3, modifier: .positive) == 5) // effectively 2+
|
|
#expect(d.total(cutoff: 2, modifier: .positive) == 5) // already at 2+, no further shift
|
|
}
|
|
}
|
|
|
|
// MARK: - Armour Save Calculation
|
|
|
|
@Suite("Armour Save Calculation")
|
|
struct ArmourSaveTests {
|
|
|
|
func oneEach() -> Dice {
|
|
var d = Dice()
|
|
d.one = 1; d.two = 1; d.three = 1; d.four = 1; d.five = 1; d.six = 1
|
|
return d
|
|
}
|
|
|
|
@Test("Counts dice that fail the save")
|
|
func normalSaves() {
|
|
let d = oneEach()
|
|
#expect(d.armour(save: 6, armourPenetration: 0, invulnerable: false) == 5) // 1-5 fail
|
|
#expect(d.armour(save: 5, armourPenetration: 0, invulnerable: false) == 4) // 1-4 fail
|
|
#expect(d.armour(save: 4, armourPenetration: 0, invulnerable: false) == 3) // 1-3 fail
|
|
#expect(d.armour(save: 3, armourPenetration: 0, invulnerable: false) == 2) // 1-2 fail
|
|
#expect(d.armour(save: 2, armourPenetration: 0, invulnerable: false) == 1) // only 1 fails
|
|
}
|
|
|
|
@Test("AP reduces effective save value")
|
|
func armourPenetration() {
|
|
let d = oneEach()
|
|
// 3+ save with AP 1 → effective 4+: 1-3 fail
|
|
#expect(d.armour(save: 3, armourPenetration: 1, invulnerable: false) == 3)
|
|
// 3+ save with AP 2 → effective 5+: 1-4 fail
|
|
#expect(d.armour(save: 3, armourPenetration: 2, invulnerable: false) == 4)
|
|
}
|
|
|
|
@Test("Invulnerable save ignores AP")
|
|
func invulnerableSave() {
|
|
let d = oneEach()
|
|
// 4++ invulnerable with heavy AP should match a plain 4+ with no AP
|
|
let withInvuln = d.armour(save: 4, armourPenetration: 5, invulnerable: true)
|
|
let noAP = d.armour(save: 4, armourPenetration: 0, invulnerable: false)
|
|
#expect(withInvuln == noAP)
|
|
}
|
|
}
|
|
|
|
// MARK: - Roll Modifiers
|
|
|
|
@Suite("Roll Modifiers")
|
|
struct RollModifierTests {
|
|
|
|
@Test("add() clamps at positive")
|
|
func addModifier() {
|
|
#expect(Dice.RollModifiers.negative.add() == .none)
|
|
#expect(Dice.RollModifiers.none.add() == .positive)
|
|
#expect(Dice.RollModifiers.positive.add() == .positive)
|
|
}
|
|
|
|
@Test("remove() clamps at negative")
|
|
func removeModifier() {
|
|
#expect(Dice.RollModifiers.positive.remove() == .none)
|
|
#expect(Dice.RollModifiers.none.remove() == .negative)
|
|
#expect(Dice.RollModifiers.negative.remove() == .negative)
|
|
}
|
|
}
|
|
|
|
// MARK: - Wound Cutoff
|
|
|
|
@Suite("Wound Cutoff")
|
|
struct WoundCutoffTests {
|
|
|
|
@Test("S double or more T: wound on 2+")
|
|
func doubleStrength() {
|
|
let roll = Roll()
|
|
roll.strength = 10; roll.toughness = 5
|
|
#expect(roll.woundCutoff == 2)
|
|
roll.strength = 6; roll.toughness = 3
|
|
#expect(roll.woundCutoff == 2)
|
|
}
|
|
|
|
@Test("S greater than T but less than double: wound on 3+")
|
|
func strengthHigher() {
|
|
let roll = Roll()
|
|
roll.strength = 7; roll.toughness = 5
|
|
#expect(roll.woundCutoff == 3)
|
|
roll.strength = 4; roll.toughness = 3
|
|
#expect(roll.woundCutoff == 3)
|
|
}
|
|
|
|
@Test("S equals T: wound on 4+")
|
|
func equal() {
|
|
let roll = Roll()
|
|
roll.strength = 5; roll.toughness = 5
|
|
#expect(roll.woundCutoff == 4)
|
|
roll.strength = 3; roll.toughness = 3
|
|
#expect(roll.woundCutoff == 4)
|
|
}
|
|
|
|
@Test("T greater than S but less than double: wound on 5+")
|
|
func toughnessHigher() {
|
|
let roll = Roll()
|
|
roll.strength = 3; roll.toughness = 5
|
|
#expect(roll.woundCutoff == 5)
|
|
roll.strength = 4; roll.toughness = 5
|
|
#expect(roll.woundCutoff == 5)
|
|
}
|
|
|
|
@Test("T double or more S: wound on 6+")
|
|
func doubleToughness() {
|
|
let roll = Roll()
|
|
roll.strength = 2; roll.toughness = 5
|
|
#expect(roll.woundCutoff == 6)
|
|
roll.strength = 1; roll.toughness = 3
|
|
#expect(roll.woundCutoff == 6)
|
|
}
|
|
}
|
|
|
|
// MARK: - Reroll Logic
|
|
|
|
@Suite("Reroll Logic")
|
|
struct RerollTests {
|
|
|
|
@Test("rerollBelow: no-op when no dice fall below cutoff")
|
|
func rerollBelowNoop() {
|
|
var d = Dice()
|
|
d.four = 2; d.five = 3; d.six = 1
|
|
d.rerollBelow(cutoff: 4)
|
|
#expect(d.one == 0)
|
|
#expect(d.two == 0)
|
|
#expect(d.three == 0)
|
|
#expect(d.four == 2)
|
|
#expect(d.five == 3)
|
|
#expect(d.six == 1)
|
|
}
|
|
|
|
@Test("rerollBelow: preserves total dice count")
|
|
func rerollBelowPreservesCount() {
|
|
var d = Dice()
|
|
d.one = 3; d.two = 2; d.three = 1; d.four = 1; d.five = 1; d.six = 1
|
|
d.rerollBelow(cutoff: 4)
|
|
let total = d.one + d.two + d.three + d.four + d.five + d.six
|
|
#expect(total == 9)
|
|
}
|
|
|
|
@Test("rerollBelow: cutoff 1 is a no-op (empty range)")
|
|
func rerollBelowCutoffOne() {
|
|
var d = Dice()
|
|
d.one = 3; d.six = 2
|
|
d.rerollBelow(cutoff: 1)
|
|
#expect(d.one == 3)
|
|
#expect(d.six == 2)
|
|
}
|
|
|
|
@Test("rerollBelow: dice above cutoff are untouched")
|
|
func rerollBelowPreservesHighFaces() {
|
|
var d = Dice()
|
|
d.one = 2; d.two = 2; d.three = 2; d.six = 4
|
|
d.rerollBelow(cutoff: 4)
|
|
// The 4 sixes were not in the reroll range and should be unchanged
|
|
#expect(d.six == 4)
|
|
}
|
|
|
|
@Test("applyReroll: none mode leaves hasRerolled false")
|
|
func applyRerollNoneMode() {
|
|
let roll = Roll()
|
|
roll.state = .hit
|
|
roll.hitRerollMode = .none
|
|
roll.applyReroll()
|
|
#expect(roll.hitHasRerolled == false)
|
|
}
|
|
|
|
@Test("applyReroll: sets hasRerolled flag after use")
|
|
func applyRerollSetsFlag() {
|
|
let roll = Roll()
|
|
roll.state = .hit
|
|
roll.hitRerollMode = .ones
|
|
roll.dice.one = 3; roll.dice.six = 2
|
|
roll.total = 5
|
|
roll.applyReroll()
|
|
#expect(roll.hitHasRerolled == true)
|
|
}
|
|
|
|
@Test("applyReroll: guard blocks second call")
|
|
func applyRerollRespectsFlagAlreadySet() {
|
|
let roll = Roll()
|
|
roll.state = .hit
|
|
roll.hitRerollMode = .all
|
|
roll.hitHasRerolled = true // simulate already used
|
|
roll.dice.one = 5 // known state
|
|
roll.applyReroll() // should be blocked by guard
|
|
#expect(roll.dice.one == 5)
|
|
#expect(roll.dice.two == 0)
|
|
#expect(roll.dice.three == 0)
|
|
#expect(roll.dice.four == 0)
|
|
#expect(roll.dice.five == 0)
|
|
#expect(roll.dice.six == 0)
|
|
}
|
|
|
|
@Test("applyReroll: only sets flag for the active phase")
|
|
func applyRerollPerPhaseIsolation() {
|
|
let roll = Roll()
|
|
roll.state = .wound
|
|
roll.woundRerollMode = .ones
|
|
roll.applyReroll()
|
|
#expect(roll.woundHasRerolled == true)
|
|
#expect(roll.hitHasRerolled == false)
|
|
#expect(roll.saveHasRerolled == false)
|
|
}
|
|
|
|
@Test("applyReroll: failed mode preserves total dice count")
|
|
func applyRerollFailedModeCount() {
|
|
let roll = Roll()
|
|
roll.state = .hit
|
|
roll.hitCutoff = .four
|
|
roll.hitRerollMode = .failed
|
|
roll.dice.one = 1; roll.dice.two = 1; roll.dice.three = 1; roll.dice.six = 2
|
|
roll.total = 5
|
|
roll.applyReroll()
|
|
let total = roll.dice.one + roll.dice.two + roll.dice.three +
|
|
roll.dice.four + roll.dice.five + roll.dice.six
|
|
#expect(total == 5)
|
|
#expect(roll.dice.six == 2) // dice above cutoff are untouched
|
|
}
|
|
|
|
@Test("resetPool: clears hitHasRerolled flag")
|
|
func resetPoolClearsFlag() {
|
|
let roll = Roll()
|
|
roll.total = 5
|
|
roll.hitHasRerolled = true
|
|
roll.resetPool()
|
|
#expect(roll.hitHasRerolled == false)
|
|
}
|
|
|
|
@Test("resetPool: rolls dice equal to total count")
|
|
func resetPoolRollsCorrectCount() {
|
|
let roll = Roll()
|
|
roll.total = 10
|
|
roll.resetPool()
|
|
let total = roll.dice.one + roll.dice.two + roll.dice.three +
|
|
roll.dice.four + roll.dice.five + roll.dice.six
|
|
#expect(total == 10)
|
|
}
|
|
}
|
|
|
|
// MARK: - Blowup Logic
|
|
|
|
@Suite("Blowup Logic")
|
|
struct BlowupTests {
|
|
|
|
@Test("blowup six: total increases by the original six count")
|
|
func blowupSixIncreasesCount() {
|
|
var d = Dice()
|
|
d.one = 1; d.two = 1; d.six = 3
|
|
let originalTotal = 5
|
|
let originalSix = 3
|
|
d.blowup(dice: .six)
|
|
let newTotal = d.one + d.two + d.three + d.four + d.five + d.six
|
|
#expect(newTotal == originalTotal + originalSix)
|
|
}
|
|
|
|
@Test("blowup five: total increases by the original five and six count")
|
|
func blowupFiveIncreasesCount() {
|
|
var d = Dice()
|
|
d.one = 1; d.five = 2; d.six = 3
|
|
let originalTotal = 6
|
|
let originalFiveSix = 5
|
|
d.blowup(dice: .five)
|
|
let newTotal = d.one + d.two + d.three + d.four + d.five + d.six
|
|
#expect(newTotal == originalTotal + originalFiveSix)
|
|
}
|
|
|
|
@Test("blowup six: no-op when no sixes in pool")
|
|
func blowupSixNoop() {
|
|
var d = Dice()
|
|
d.one = 3; d.two = 2
|
|
d.blowup(dice: .six)
|
|
let total = d.one + d.two + d.three + d.four + d.five + d.six
|
|
#expect(total == 5)
|
|
}
|
|
|
|
@Test("applyBlowup: sets blowupUsed flag")
|
|
func applyBlowupSetsFlag() {
|
|
let roll = Roll()
|
|
roll.dice.six = 3
|
|
roll.applyBlowup(face: .six)
|
|
#expect(roll.blowupUsed == true)
|
|
}
|
|
|
|
@Test("applyBlowup: guard blocks second call")
|
|
func applyBlowupGuard() {
|
|
let roll = Roll()
|
|
roll.dice.six = 2
|
|
roll.blowupUsed = true // already used
|
|
let totalBefore = roll.dice.one + roll.dice.two + roll.dice.three +
|
|
roll.dice.four + roll.dice.five + roll.dice.six
|
|
roll.applyBlowup(face: .six)
|
|
let totalAfter = roll.dice.one + roll.dice.two + roll.dice.three +
|
|
roll.dice.four + roll.dice.five + roll.dice.six
|
|
#expect(totalAfter == totalBefore)
|
|
}
|
|
|
|
@Test("applyBlowup: using any face disables both")
|
|
func blowupAnyFaceDisablesBoth() {
|
|
let roll = Roll()
|
|
roll.dice.six = 2; roll.dice.five = 2
|
|
roll.applyBlowup(face: .six)
|
|
#expect(roll.blowupUsed == true)
|
|
// Five blowup should now be blocked
|
|
let totalBefore = roll.dice.one + roll.dice.two + roll.dice.three +
|
|
roll.dice.four + roll.dice.five + roll.dice.six
|
|
roll.applyBlowup(face: .five)
|
|
let totalAfter = roll.dice.one + roll.dice.two + roll.dice.three +
|
|
roll.dice.four + roll.dice.five + roll.dice.six
|
|
#expect(totalAfter == totalBefore)
|
|
}
|
|
}
|
|
|
|
// MARK: - Critical Trigger Computed Vars
|
|
|
|
@Suite("Critical Trigger Computed Vars")
|
|
struct CriticalTriggerVarTests {
|
|
|
|
@Test("autoWoundCrits: Lethal Hits returns dice.six")
|
|
func lethalHitsCount() {
|
|
let roll = Roll()
|
|
roll.dice.four = 2; roll.dice.six = 3
|
|
roll.lethalHits = true
|
|
#expect(roll.autoWoundCrits == 3)
|
|
}
|
|
|
|
@Test("autoWoundCrits: Anti-X returns dice at or above threshold")
|
|
func antiXCount() {
|
|
let roll = Roll()
|
|
roll.dice.four = 1; roll.dice.five = 2; roll.dice.six = 2
|
|
roll.antiX = true; roll.antiXThreshold = 5
|
|
#expect(roll.autoWoundCrits == 4) // five(2) + six(2)
|
|
}
|
|
|
|
@Test("autoWoundCrits: Anti-X is superset of Lethal Hits when threshold < 6")
|
|
func antiXSupersetOfLethal() {
|
|
let roll = Roll()
|
|
roll.dice.five = 2; roll.dice.six = 3
|
|
roll.lethalHits = true; roll.antiX = true; roll.antiXThreshold = 5
|
|
// Anti-X(5+) = 5, Lethal = 3 — Anti-X is the superset, no double-count
|
|
#expect(roll.autoWoundCrits == 5)
|
|
}
|
|
|
|
@Test("autoWoundCrits: Anti-X and Lethal Hits both on threshold 6 — no double-count")
|
|
func antiXAndLethalSameThreshold() {
|
|
let roll = Roll()
|
|
roll.dice.six = 3
|
|
roll.lethalHits = true; roll.antiX = true; roll.antiXThreshold = 6
|
|
#expect(roll.autoWoundCrits == 3)
|
|
}
|
|
|
|
@Test("autoWoundCrits: Torrent gates out to zero even with Lethal Hits")
|
|
func torrentGatesLethal() {
|
|
let roll = Roll()
|
|
roll.dice.six = 4
|
|
roll.lethalHits = true; roll.torrent = true
|
|
#expect(roll.autoWoundCrits == 0)
|
|
}
|
|
|
|
@Test("autoWoundCrits: Torrent gates out Anti-X")
|
|
func torrentGatesAntiX() {
|
|
let roll = Roll()
|
|
roll.dice.five = 2; roll.dice.six = 2
|
|
roll.antiX = true; roll.antiXThreshold = 5; roll.torrent = true
|
|
#expect(roll.autoWoundCrits == 0)
|
|
}
|
|
|
|
@Test("antiXCount: threshold 6 equals dice.six")
|
|
func antiXCountThresholdSix() {
|
|
let roll = Roll()
|
|
roll.dice.five = 2; roll.dice.six = 3
|
|
roll.antiX = true; roll.antiXThreshold = 6
|
|
#expect(roll.antiXCount == 3)
|
|
}
|
|
|
|
@Test("antiXCount: threshold 5 includes fives and sixes")
|
|
func antiXCountThresholdFive() {
|
|
let roll = Roll()
|
|
roll.dice.four = 1; roll.dice.five = 2; roll.dice.six = 2
|
|
roll.antiX = true; roll.antiXThreshold = 5
|
|
#expect(roll.antiXCount == 4)
|
|
}
|
|
|
|
@Test("antiXCount: returns zero when Anti-X is off")
|
|
func antiXCountWhenOff() {
|
|
let roll = Roll()
|
|
roll.dice.six = 5
|
|
roll.antiX = false
|
|
#expect(roll.antiXCount == 0)
|
|
}
|
|
|
|
@Test("critWoundCount: returns dice.six when Devastating Wounds is on")
|
|
func critWoundCountOn() {
|
|
let roll = Roll()
|
|
roll.dice.six = 3
|
|
roll.devastatingWounds = true
|
|
#expect(roll.critWoundCount == 3)
|
|
}
|
|
|
|
@Test("critWoundCount: returns zero when Devastating Wounds is off")
|
|
func critWoundCountOff() {
|
|
let roll = Roll()
|
|
roll.dice.six = 3
|
|
roll.devastatingWounds = false
|
|
#expect(roll.critWoundCount == 0)
|
|
}
|
|
}
|
|
|
|
// MARK: - Critical Phase Transitions
|
|
|
|
@Suite("Critical Phase Transitions")
|
|
struct CritPhaseTransitionTests {
|
|
|
|
// Helpers: set up a roll with known hit dice that avoid random interference
|
|
// roll.total is set during incrementState before dice.roll is called — testable directly.
|
|
|
|
@Test("Lethal Hits: auto-wounds carried equals crits, wound pool shrinks")
|
|
func lethalHitsTransition() {
|
|
let roll = Roll()
|
|
roll.state = .hit
|
|
roll.hitCutoff = .four
|
|
// hit = four(1) + five(1) + six(2) = 4; crits = 2
|
|
roll.dice.four = 1; roll.dice.five = 1; roll.dice.six = 2
|
|
roll.lethalHits = true
|
|
roll.incrementState()
|
|
#expect(roll.autoWoundsCarried == 2)
|
|
#expect(roll.total == 2) // 4 hits - 2 crits
|
|
#expect(roll.state == .wound)
|
|
}
|
|
|
|
@Test("Sustained Hits: wound pool grows by six-count * X")
|
|
func sustainedHitsTransition() {
|
|
let roll = Roll()
|
|
roll.state = .hit
|
|
roll.hitCutoff = .four
|
|
roll.sustainedHitsX = 2
|
|
// hit = 4; sixes = 2 → sustained extra = 2*2 = 4
|
|
roll.dice.four = 1; roll.dice.five = 1; roll.dice.six = 2
|
|
roll.sustainedHits = true
|
|
roll.incrementState()
|
|
#expect(roll.autoWoundsCarried == 0)
|
|
#expect(roll.total == 8) // 4 hits + 4 sustained extra
|
|
#expect(roll.state == .wound)
|
|
}
|
|
|
|
@Test("Sustained Hits + Lethal Hits: extra hits go to wound roll, crits auto-wound")
|
|
func sustainedPlusLethalTransition() {
|
|
let roll = Roll()
|
|
roll.state = .hit
|
|
roll.hitCutoff = .four
|
|
roll.sustainedHitsX = 1
|
|
// hit = 4; crits (lethal) = 2; sustained extra = 2*1 = 2
|
|
// wound pool = (4-2) + 2 = 4
|
|
roll.dice.four = 1; roll.dice.five = 1; roll.dice.six = 2
|
|
roll.sustainedHits = true; roll.lethalHits = true
|
|
roll.incrementState()
|
|
#expect(roll.autoWoundsCarried == 2)
|
|
#expect(roll.total == 4) // 2 normal hits + 2 sustained extras
|
|
#expect(roll.state == .wound)
|
|
}
|
|
|
|
@Test("Anti-X: dice at/above threshold are carried as auto-wounds")
|
|
func antiXTransition() {
|
|
let roll = Roll()
|
|
roll.state = .hit
|
|
roll.hitCutoff = .four
|
|
// hit = 5; antiX(5+) = 4
|
|
roll.dice.four = 1; roll.dice.five = 2; roll.dice.six = 2
|
|
roll.antiX = true; roll.antiXThreshold = 5
|
|
roll.incrementState()
|
|
#expect(roll.autoWoundsCarried == 4)
|
|
#expect(roll.total == 1) // 5 hits - 4 crits
|
|
#expect(roll.state == .wound)
|
|
}
|
|
|
|
@Test("Torrent + Lethal Hits: no crits carried, full pool goes to wound")
|
|
func torrentBlocksCrits() {
|
|
let roll = Roll()
|
|
roll.state = .hit
|
|
roll.total = 10
|
|
roll.torrent = true; roll.lethalHits = true
|
|
roll.dice.six = 5
|
|
roll.incrementState()
|
|
#expect(roll.autoWoundsCarried == 0)
|
|
#expect(roll.total == 10) // hit = total (Torrent), no crits subtracted
|
|
#expect(roll.state == .wound)
|
|
}
|
|
|
|
@Test("Devastating Wounds: mortals recorded, save pool reduced")
|
|
func devastatingWoundsTransition() {
|
|
let roll = Roll()
|
|
roll.state = .wound
|
|
roll.strength = 5; roll.toughness = 5 // wound on 4+
|
|
// wound = four(1) + five(1) + six(2) = 4; mortals = 2
|
|
roll.dice.four = 1; roll.dice.five = 1; roll.dice.six = 2
|
|
roll.devastatingWounds = true
|
|
roll.incrementState()
|
|
#expect(roll.mortalWoundsTotal == 2)
|
|
#expect(roll.total == 2) // 4 wounds - 2 mortals
|
|
#expect(roll.state == .armour)
|
|
}
|
|
|
|
@Test("Auto-wounds carried through wound phase: added to save pool")
|
|
func autoWoundsAddedToSavePool() {
|
|
let roll = Roll()
|
|
roll.state = .wound
|
|
roll.strength = 5; roll.toughness = 5
|
|
roll.autoWoundsCarried = 3
|
|
// wound = four(1) + five(1) = 2; no devastatingWounds
|
|
roll.dice.four = 1; roll.dice.five = 1; roll.dice.six = 0
|
|
roll.incrementState()
|
|
#expect(roll.total == 5) // 2 wounds + 3 carried
|
|
#expect(roll.autoWoundsCarried == 0) // cleared
|
|
#expect(roll.state == .armour)
|
|
}
|
|
|
|
@Test("Wound→armour clears autoWoundsCarried")
|
|
func woundPhaseClearsCarry() {
|
|
let roll = Roll()
|
|
roll.state = .wound
|
|
roll.strength = 5; roll.toughness = 5
|
|
roll.autoWoundsCarried = 5
|
|
roll.dice.four = 1
|
|
roll.incrementState()
|
|
#expect(roll.autoWoundsCarried == 0)
|
|
}
|
|
|
|
@Test("Armour→hit clears mortalWoundsTotal")
|
|
func armourPhaseClears() {
|
|
let roll = Roll()
|
|
roll.state = .armour
|
|
roll.mortalWoundsTotal = 4
|
|
roll.total = 5
|
|
roll.incrementState()
|
|
#expect(roll.mortalWoundsTotal == 0)
|
|
#expect(roll.state == .hit)
|
|
}
|
|
}
|