test: cover tests.autopilotRepairsBrokenGearAutomaticallyWhenAffordable

This commit is contained in:
dualfroz committed 2026-09-11 19:18:42 +02:00
1 parent 117119b11e
commit d16250b6f9
1 file changed
+173
+173
View File
@@ -9125,6 +9125,179 @@ function tests.gameRepairEquippedFailsAndLogsWhenGoldIsShort()
local success = game:repairEquipped("weapon")
eq(success, false, "an empty purse cannot repair")
eq(game.player.weapon.durability, 0, "the weapon stays broken")
ok(contains(game.log:last(), "cannot repair"), "the failure is logged")
end
function tests.autopilotRepairsBrokenGearAutomaticallyWhenAffordable()
local game = stagedGame(1, arena(21, 11), 3, 6)
Items.equipWeapon(Items.weapon("sword", 4), game.player)
game.player.weapon.durability = 0
game.player.gold = 1000
game.level.entities = {}
game:choosePlayerAction()
eq(game.player.weapon.durability, Durability.MAX,
"the autopilot repairs a broken weapon on sight once it can afford to")
end
function tests.durabilityFieldsSurviveSerialization()
local game = Game.new(3)
Items.equipWeapon(Items.weapon("sword", 4), game.player)
game.player.weapon.durability = 7
local path = os.tmpname()
game:save(path)
local restored = Game.load(path)
os.remove(path)
eq(restored.player.weapon.durability, 7, "a weapon's own durability survives a save/load round trip")
eq(restored.player.weapon.maxDurability, Durability.MAX, "a weapon's max durability survives a save/load round trip")
end
function tests.renderPanelShowsWornGearOnlyOnceItHasTakenDamage()
local game = stagedGame(1, arena(21, 11), 3, 6)
Items.equipWeapon(Items.weapon("sword", 4), game.player)
ok(not contains(Render.panel(game), "sword ("), "a pristine weapon's label carries no wear indicator")
game.player.weapon.durability = 10
ok(contains(Render.panel(game), "sword (10/30)"), "a dinged weapon's label reports its remaining durability")
game.player.weapon.durability = 0
ok(contains(Render.panel(game), "sword (broken)"), "a broken weapon's label says so plainly")
end
-- disarm.lua: passive trap sense and deliberate disarming.
function tests.disarmSweepDetectsAHiddenTrapWithinOneStepEventuallyAcrossAHandfulOfSeeds()
local detected = false
for seed = 1, 100 do
local level = { traps = { { x = 5, y = 5, effect = "dart", revealed = false } } }
if #Disarm.sweep(level, Rng.new(seed), 5, 6) > 0 then detected = true break end
end
ok(detected, "an adjacent hidden trap is eventually detected across a handful of seeds")
end
function tests.disarmSweepNeverDetectsATrapTwoStepsAway()
for seed = 1, 200 do
local level = { traps = { { x = 5, y = 5, effect = "dart", revealed = false } } }
eq(#Disarm.sweep(level, Rng.new(seed), 7, 7), 0,
"a trap two steps away is never detected regardless of the roll")
end
end
function tests.disarmSweepNeverRedetectsAnAlreadyDetectedOrRevealedTrap()
local level = { traps = {
{ x = 5, y = 5, effect = "dart", revealed = false, detected = true },
{ x = 5, y = 6, effect = "dart", revealed = true },
} }
for seed = 1, 50 do
eq(#Disarm.sweep(level, Rng.new(seed), 5, 5), 0,
"neither an already-detected nor an already-sprung trap is swept again")
end
end
function tests.disarmOddsForClimbWithLevelButNeverReachTheDenominator()
local num1, den1 = Disarm.oddsFor({ level = 1 })
eq(num1, Disarm.DISARM_BASE_NUMERATOR, "a fresh level-one player rolls the catalog's base odds")
local num2, den2 = Disarm.oddsFor({ level = 100 })
eq(den2, den1, "the denominator never changes with level")
ok(num2 < den2, "even an extremely high level never reaches a guaranteed disarm")
eq(num2, Disarm.DISARM_BASE_DENOMINATOR - 1, "the numerator caps a single point shy of the denominator")
end
function tests.disarmAttemptOnSuccessRemovesTheTrapAndPaysSalvage()
local succeeded = false
for seed = 1, 300 do
local game = stagedGame(seed, arena(21, 11), 3, 6)
local trap = { x = 4, y = 6, effect = "dart", revealed = false, detected = true }
game.level.traps = { trap }
local essenceBefore = game.player.essence
local success, line = Disarm.attempt(game, trap)
if success then
eq(#game.level.traps, 0, "a successfully disarmed trap is removed from the level")
ok(game.player.essence > essenceBefore, "a successful disarm pays out salvage essence")
ok(contains(line, "disarm"), "the success is reported")
succeeded = true
break
end
end
ok(succeeded, "disarming eventually succeeds across a handful of seeds")
end
function tests.disarmAttemptOnFailureSpringsTheTrapOnThePlayer()
local failed = false
for seed = 1, 300 do
local game = stagedGame(seed, arena(21, 11), 3, 6)
local trap = { x = 4, y = 6, effect = "dart", revealed = false, detected = true }
game.level.traps = { trap }
local hpBefore = game.player.hp
local success = Disarm.attempt(game, trap)
if not success then
eq(trap.revealed, true, "a failed disarm springs the trap, revealing it")
ok(game.player.hp < hpBefore, "a failed disarm's spring wounds the player exactly like a footstep would")
failed = true
break
end
end
ok(failed, "disarming eventually fails across a handful of seeds")
end
function tests.gameDisarmTrapRefusesAnUndetectedOrAlreadySprungTrap()
local game = stagedGame(1, arena(21, 11), 3, 6)
local undetected = { x = 4, y = 6, effect = "dart", revealed = false }
game.level.traps = { undetected }
eq(game:disarmTrap(undetected), false, "an undetected trap refuses disarming outright")
eq(#game.level.traps, 1, "the untouched trap is still there")
local sprung = { x = 5, y = 6, effect = "dart", revealed = true, detected = true }
eq(game:disarmTrap(sprung), false, "an already-sprung trap refuses disarming outright")
end
function tests.gameDisarmTrapSucceedsOrSpringsExactlyLikeDisarmAttempt()
local sawSuccess, sawFailure = false, false
for seed = 1, 300 do
if sawSuccess and sawFailure then break end
local game = stagedGame(seed, arena(21, 11), 3, 6)
local trap = { x = 4, y = 6, effect = "dart", revealed = false, detected = true }
game.level.traps = { trap }
local hpBefore = game.player.hp
if game:disarmTrap(trap) then
eq(#game.level.traps, 0, "a successful disarm through the game wrapper removes the trap")
sawSuccess = true
else
eq(trap.revealed, true, "a failed disarm through the game wrapper springs the trap")
ok(game.player.hp <= hpBefore, "a failed disarm through the game wrapper can wound the player")
sawFailure = true
end
end
ok(sawSuccess, "the game wrapper eventually succeeds across a handful of seeds")
ok(sawFailure, "the game wrapper eventually fails across a handful of seeds")
end
function tests.gameEndPlayerTurnEventuallyDetectsAnAdjacentTrapAcrossAHandfulOfSeeds()
local noticed = false
for seed = 1, 100 do
local game = stagedGame(1, arena(21, 11), 3, 6)
game.rng = Rng.new(seed)
game.level.traps = { { x = 4, y = 6, effect = "dart", revealed = false } }
game:endPlayerTurn()
if game.level.traps[1].detected then
local reported = false
for _, line in ipairs(game.log:recent(6)) do
if contains(line, "notice") then reported = true end
end
ok(reported, "a freshly detected trap is reported somewhere in the turn's log lines")
noticed = true
break
end
end
ok(noticed, "an adjacent hidden trap is eventually detected during a normal turn across a handful of seeds")
end
function tests.trapDetectedFieldSurvivesSerialization()
local game = Game.new(3)
game.level.traps = { { x = 1, y = 1, effect = "dart", revealed = false, detected = true } }
local path = os.tmpname()
game:save(path)
local restored = Game.load(path)
os.remove(path)
eq(restored.level.traps[1].detected, true, "a trap's detected flag survives a save/load round trip")
end
end
local order = {