test: cover tests.sanityTickRecoversAndAnnouncesClimbingBackOutOfBroken

This commit is contained in:
dualfroz committed 2026-09-11 17:37:50 +02:00
1 parent 0db5c1683d
commit 1a617008d4
1 file changed
+173
+173
View File
@@ -8089,6 +8089,179 @@ function tests.sanityTickAnnouncesReachingTerrifiedAndBroken()
eq(game.player.sanity, 0, "broken is a floor, not a negative value") eq(game.player.sanity, 0, "broken is a floor, not a negative value")
end end
function tests.sanityTickRecoversAndAnnouncesClimbingBackOutOfBroken()
local game = stagedGame(1, arena(21, 11), 3, 6)
game.player.sanity = 0
eq(Sanity.tick(game), "your nerves start to steady",
"recovering out of broken is announced as steadying, never as fresh dread")
eq(game.player.sanity, 1, "a calm turn only ever recovers its small fixed amount")
end
function tests.sanityTickAnnouncesFullyRegainingComposure()
local game = stagedGame(1, arena(21, 11), 3, 6)
game.player.sanity = 60
eq(Sanity.tick(game), "your mind is your own again", "climbing back above the shaken ceiling is announced")
end
function tests.sanityTickNeverClimbsPastTheMaxOrReportsAPhantomCrossing()
local game = stagedGame(1, arena(21, 11), 3, 6)
game.player.sanity = 100
eq(Sanity.tick(game), nil, "an already-composed mind with nothing to recover reports no line")
eq(game.player.sanity, 100, "recovery never climbs the meter past its own maximum")
end
function tests.sanityRollPanicNeverFiresAboveBroken()
local entity = { sanity = 1 }
for seed = 1, 50 do
ok(not Sanity.rollPanic(Rng.new(seed), entity), "terrified never risks losing the turn to panic")
end
end
function tests.sanityRollPanicRollsAgainstItsFixedChanceOnceBroken()
local entity = { sanity = 0 }
local sawPanic, sawComposure = false, false
for seed = 1, 50 do
if Sanity.rollPanic(Rng.new(seed), entity) then sawPanic = true else sawComposure = true end
end
ok(sawPanic, "broken panics on at least some rolls across a handful of seeds")
ok(sawComposure, "broken does not panic on every single roll")
end
function tests.sanityRollScreamNeverFiresAboveTerrified()
local game = stagedGame(1, arena(21, 11), 3, 6)
game.player.sanity = 31
for seed = 1, 50 do
game.rng = Rng.new(seed)
ok(not Sanity.rollScream(game.rng, game), "merely shaken never risks an involuntary scream")
end
end
function tests.sanityRollScreamAlertsEveryDormantMonsterWithinItsRadius()
local game = stagedGame(1, arena(21, 11), 3, 6)
game.player.sanity = 0
local zombie = Monsters.create(Monsters.byKey.zombie, 1, 5, 6, 1)
zombie.awareness = Stealth.DORMANT
game.level.entities = { zombie }
local triggered = false
for seed = 1, 80 do
game.rng = Rng.new(seed)
if Sanity.rollScream(game.rng, game) then triggered = true break end
end
ok(triggered, "a broken mind eventually screams across a handful of seeds")
eq(zombie.awareness, Stealth.HUNTING, "the scream startles a nearby dormant monster fully awake")
eq(zombie.lastKnownX, game.player.x, "the alerted monster now knows exactly where the scream came from")
end
function tests.sanityFieldSurvivesSerialization()
local game = Game.new(3)
game.player.sanity = 37
local path = os.tmpname()
game:save(path)
local restored = Game.load(path)
os.remove(path)
eq(restored.player.sanity, 37, "the player's sanity survives a save/load round trip")
end
function tests.gameEndPlayerTurnTicksSanityAndTheLogReportsATierCrossing()
local game = stagedGame(1, arenaDark(21, 11), 3, 6)
game.player.light.fuel = 0
game.player.sanity = 63
game:endPlayerTurn()
eq(game.player.sanity, 61, "ending a turn on a dark, torchless floor drains the fixed darkness dread")
game:endPlayerTurn()
ok(contains(game.log:last(), "fray"), "crossing into shaken is reported in the message log")
eq(game.player:effectiveAttack(), game.player.attack - 1, "the fresh shaken penalty is already live in effectiveAttack")
end
function tests.gameAutoStepSkipsThePlayerTurnWhenPanicFires()
local game = stagedGame(1, arena(21, 11), 3, 6)
game.player.sanity = 0
local px, py = game.player.x, game.player.y
local sawPanic = false
for seed = 1, 80 do
game.rng = Rng.new(seed)
game.player.x, game.player.y = px, py
game.player.sanity = 0
-- endPlayerTurn's own Sanity.tick line (recovery or otherwise) always
-- lands after any panic line within the same turn, so the panic line
-- itself has to be found among the turn's recent lines rather than
-- assumed to be the very last one.
game:autoStep()
if contains(table.concat(game.log:recent(3), " "), "terror seizes you") then sawPanic = true break end
end
ok(sawPanic, "a broken mind eventually loses a whole turn to panic across a handful of seeds")
eq(game.player.x, px, "a panicked turn never moves the player")
eq(game.player.y, py, "a panicked turn never moves the player")
end
function tests.gameUseItemPotionOfClarityRestoresSanity()
local game = stagedGame(1, arena(21, 11), 3, 6)
game.player.sanity = 40
game.player.inventory = { Items.potionOf("clarity", 30) }
ok(game:useItem(1), "drinking the potion of clarity succeeds")
eq(game.player.sanity, 70, "clarity restores its fixed power to the drinker's sanity")
ok(contains(game.log:last(), "steadies"), "the log reports the mind steadying")
end
function tests.renderPanelShowsSanityOnlyOnceItDropsBelowComposed()
local game = stagedGame(1, arena(21, 11), 3, 6)
ok(not contains(Render.panel(game), "sanity"), "a fully composed player shows no sanity line at all")
game.player.sanity = 30
ok(contains(Render.panel(game), "sanity 30/100 (terrified)"), "a shaken mind is reported in the status panel")
end
-- Disease used to be invisible outside the transient log lines Disease.tick
-- emits (the eruption, a damage tick, an escalation) - once one of those
-- scrolled off the log, a player mid-incubation or mid-plague had no
-- persistent way to check their own infection at all, unlike every timed
-- status statusTags already folds into this same line.
function tests.renderPanelShowsTheDiseaseStageOnceThePlayerIsInfectedAndNothingBeforeInfection()
local game = stagedGame(1, arena(21, 11), 3, 6)
ok(not contains(Render.panel(game), "parasites"), "an uninfected player's status line mentions no disease")
Disease.infect(game.player, Disease.PARASITES)
ok(contains(Render.panel(game), "gut parasites(incubating)"),
"an incubating infection shows up in the status panel even before it erupts")
game.player.disease.stage = 2
ok(contains(Render.panel(game), "gut parasites(stage 2)"),
"an erupted infection reports its current stage in the status panel")
end
function tests.builderOccasionallyRollsAPotionOfClarityAsLoot()
local found
for seed = 1, 200 do
local level = Builder.build(2, Rng.new(seed), {})
for _, ground in ipairs(level.floorItems) do
if ground.item.kind == Items.POTION and ground.item.effect == "clarity" then found = ground.item end
end
if found then break end
end
ok(found ~= nil, "a potion of clarity eventually turns up in the depth-2 loot table across a handful of seeds")
ok(Shop.priceOf(found) > 0, "a found clarity potion still carries a positive catalog price")
end
function tests.builderOccasionallyRollsAPotionOfConfusionAsLoot()
local found
for seed = 1, 200 do
local level = Builder.build(2, Rng.new(seed), {})
for _, ground in ipairs(level.floorItems) do
if ground.item.kind == Items.POTION and ground.item.effect == "confuse" then found = ground.item end
end
if found then break end
end
ok(found ~= nil, "a potion of confusion eventually turns up in the depth-2 loot table across a handful of seeds")
ok(Shop.priceOf(found) > 0, "a found confusion potion still carries a positive catalog price")
end
function tests.builderOccasionallyRollsAPotionOfBlindnessAsLoot()
local found
for seed = 1, 200 do
local level = Builder.build(2, Rng.new(seed), {})
for _, ground in ipairs(level.floorItems) do
if ground.item.kind == Items.POTION and ground.item.effect == "blind" then found = ground.item end
end
if found then break end
end
local order = { local order = {
"rngDeterminism", "mapDeterminism", "tilesRegistry", "fovKnownScene", "losSightLine", "rngDeterminism", "mapDeterminism", "tilesRegistry", "fovKnownScene", "losSightLine",
"astarPathing", "combatResolution", "itemsAndEquipment", "affixes", "statusEffects", "astarPathing", "combatResolution", "itemsAndEquipment", "affixes", "statusEffects",