test: cover tests.pickFieldSurvivesSerialization

This commit is contained in:
dualfroz committed 2026-09-11 16:31:36 +02:00
1 parent 7679ba954e
commit 0db5c1683d
1 file changed
+173
+173
View File
@@ -7914,6 +7914,179 @@ end
function tests.gameMonsterTunnelsThroughAWallToReachAndAttackThePlayer()
local level = arena(21, 11)
for y = 2, 10 do level.map:set(10, y, Tiles.WALL) end
local game = stagedGame(1, level, 8, 6)
local burrower = Entity.new({
id = 1, x = 12, y = 6, glyph = "b", name = "rock burrower", kind = "monster",
ai = "tunnel", hp = 20, maxHp = 20, attack = 5, defense = 0, speed = 100,
})
game.level.entities = { burrower }
local hpBefore = game.player.hp
local sawRubble = false
for _ = 1, 10 do
game.rng = Rng.new(1)
Ai.act(game, burrower)
if game.level.map:get(10, 6) ~= Tiles.WALL then sawRubble = true end
if game.player.hp < hpBefore then break end
end
ok(sawRubble, "the burrower actually carves through the partition wall")
ok(game.player.hp < hpBefore, "the burrower eventually reaches and melees the player through its own tunnel")
end
function tests.pickFieldSurvivesSerialization()
local game = Game.new(3)
game.player.pick = Items.pick("saved pick", 7)
local path = os.tmpname()
game:save(path)
local restored = Game.load(path)
os.remove(path)
eq(restored.player.pick.name, "saved pick", "the equipped pick's name survives a save/load round trip")
eq(restored.player.pick.uses, 7, "the equipped pick's remaining durability survives a save/load round trip")
end
function tests.builderNeverPlacesAPickBeforeDepthTwo()
for seed = 1, 20 do
local level = Builder.build(1, Rng.new(seed), {})
for _, ground in ipairs(level.floorItems) do
ok(ground.item.kind ~= Items.PICK, "a pick never turns up on the entrance floor")
end
end
end
function tests.builderOccasionallyPlacesAPickFromDepthTwo()
local level = Builder.build(2, Rng.new(6), {})
local found
for _, ground in ipairs(level.floorItems) do
if ground.item.kind == Items.PICK then found = ground.item end
end
ok(found ~= nil, "this seed's depth-2 floor places a miner's pick")
ok(Shop.priceOf(found) > 0, "the placed pick still carries a positive catalog price")
end
function tests.sanityCurrentDefaultsToMaxForAFreshEntity()
eq(Sanity.current({}), Sanity.MAX, "an entity with no sanity field reads as fully composed")
end
function tests.sanityTierOfClassifiesEachBandAtItsBoundaries()
eq(Sanity.tierOf({ sanity = 100 }), Sanity.COMPOSED, "a full meter is composed")
eq(Sanity.tierOf({ sanity = 61 }), Sanity.COMPOSED, "just above the shaken ceiling is still composed")
eq(Sanity.tierOf({ sanity = 60 }), Sanity.SHAKEN, "the shaken ceiling itself reads as shaken")
eq(Sanity.tierOf({ sanity = 31 }), Sanity.SHAKEN, "just above the terrified ceiling is still shaken")
eq(Sanity.tierOf({ sanity = 30 }), Sanity.TERRIFIED, "the terrified ceiling itself reads as terrified")
eq(Sanity.tierOf({ sanity = 1 }), Sanity.TERRIFIED, "one point above zero is still terrified, never broken")
eq(Sanity.tierOf({ sanity = 0 }), Sanity.BROKEN, "zero itself is broken")
end
function tests.sanityAdjustClampsToTheZeroAndMaxBounds()
local entity = { sanity = 95 }
eq(Sanity.adjust(entity, 30), Sanity.MAX, "adjusting past the ceiling clamps to max")
eq(Sanity.adjust(entity, -1000), 0, "adjusting far past the floor clamps to zero")
eq(Sanity.adjust(entity, 5), 5, "an ordinary adjustment lands exactly where expected")
end
function tests.sanityAttackAndDefensePenaltiesEscalateByTier()
eq(Sanity.attackPenalty({ sanity = 100 }), 0, "a composed mind carries no attack penalty")
eq(Sanity.attackPenalty({ sanity = 60 }), -1, "shaken costs a point of attack")
eq(Sanity.attackPenalty({ sanity = 30 }), -3, "terrified costs three points of attack")
eq(Sanity.attackPenalty({ sanity = 0 }), -5, "broken costs five points of attack")
eq(Sanity.defensePenalty({ sanity = 100 }), 0, "a composed mind carries no defense penalty")
eq(Sanity.defensePenalty({ sanity = 60 }), -1, "shaken costs a point of defense")
eq(Sanity.defensePenalty({ sanity = 30 }), -2, "terrified costs two points of defense")
eq(Sanity.defensePenalty({ sanity = 0 }), -4, "broken costs four points of defense")
end
function tests.entityEffectiveAttackAndDefenseFoldInTheSanityPenalty()
local entity = Entity.new({ x = 1, y = 1, glyph = "@", name = "you", kind = "player", hp = 20, attack = 6, defense = 4 })
eq(entity:effectiveAttack(), 6, "a fresh entity with no sanity field carries no penalty")
entity.sanity = 0
eq(entity:effectiveAttack(), 1, "a broken mind's attack penalty is live in effectiveAttack")
eq(entity:effectiveDefense(), 0, "a broken mind's defense penalty is live in effectiveDefense")
end
function tests.sanityDarknessDreadFiresOnlyOnADarkFloorWithASpentTorch()
local dark = stagedGame(1, arenaDark(21, 11), 3, 6)
dark.player.light.fuel = 0
eq(Sanity.darknessDread(dark), Sanity.DARKNESS_DREAD, "a dark floor with a spent torch dreads the mind")
dark.player.light.fuel = 50
eq(Sanity.darknessDread(dark), 0, "a lit torch on a dark floor holds the dread off entirely")
local lit = stagedGame(1, arena(21, 11), 3, 6)
lit.player.light.fuel = 0
eq(Sanity.darknessDread(lit), 0, "an ordinary floor never dreads the mind regardless of torch fuel")
end
function tests.sanityCorpseDreadCountsOnlyCorpsesAdjacentToThePlayer()
local game = stagedGame(1, arena(21, 11), 3, 6)
game.level.corpses = { { x = 2, y = 5 } }
eq(Sanity.corpseDread(game), Sanity.CORPSE_DREAD_PER, "one adjacent corpse contributes its fixed share")
game.level.corpses[#game.level.corpses + 1] = { x = 10, y = 6 }
eq(Sanity.corpseDread(game), Sanity.CORPSE_DREAD_PER, "a corpse well outside arm's reach adds nothing")
end
function tests.sanityCorpseDreadCapsAtItsFixedCeiling()
local game = stagedGame(1, arena(21, 11), 3, 6)
game.level.corpses = {
{ x = 2, y = 5 }, { x = 2, y = 6 }, { x = 2, y = 7 }, { x = 3, y = 5 }, { x = 3, y = 7 },
}
eq(Sanity.corpseDread(game), Sanity.CORPSE_DREAD_CAP, "five adjacent corpses still cap at the fixed ceiling")
end
function tests.sanityUndeadDreadCountsOnlyVisibleUndeadAndCapsAtItsCeiling()
local game = stagedGame(1, arena(21, 11), 3, 6)
local zombie = Monsters.create(Monsters.byKey.zombie, 1, 4, 6, 1)
game.level.entities = { zombie }
eq(Sanity.undeadDread(game), Sanity.UNDEAD_DREAD_PER, "one visible undead contributes its fixed share")
game.visible = {}
eq(Sanity.undeadDread(game), 0, "an undead outside the player's sight adds nothing")
game:computeVisible()
local orc = Monsters.create(Monsters.byKey.orc, 2, 5, 6, 1)
game.level.entities[#game.level.entities + 1] = orc
eq(Sanity.undeadDread(game), Sanity.UNDEAD_DREAD_PER, "a visible living monster never counts as undead dread")
for i = 3, 6 do
game.level.entities[#game.level.entities + 1] = Monsters.create(Monsters.byKey.zombie, i, 6, 6, 1)
end
eq(Sanity.undeadDread(game), Sanity.UNDEAD_DREAD_CAP, "a crowd of visible undead still caps at the fixed ceiling")
end
function tests.sanityCurseDreadFiresOnlyForARevealedCurse()
local player = { ring = Curses.afflict(Items.ring("ring of power", 0, 0), Curses.pool.ring[1]) }
eq(Sanity.curseDread(player), 0, "a curse still hidden behind an ordinary appearance dreads nothing")
Curses.reveal(player.ring)
eq(Sanity.curseDread(player), Sanity.CURSE_DREAD, "a revealed curse gnaws at the mind")
end
function tests.sanityWeatherDreadFiresOnlyForInfestedWeather()
eq(Sanity.weatherDread(Atmosphere.INFESTED), Sanity.WEATHER_DREAD, "an infested floor dreads the mind")
eq(Sanity.weatherDread(Atmosphere.FOGGY), 0, "a merely foggy floor is not itself dreadful")
eq(Sanity.weatherDread(nil), 0, "a clear floor carries no weather dread")
end
function tests.sanityTotalDreadSumsEverySourceAtOnce()
local dark = stagedGame(1, arenaDark(21, 11), 3, 6)
dark.player.light.fuel = 0
dark.level.weather = Atmosphere.INFESTED
dark.level.corpses = { { x = 2, y = 5 } }
eq(Sanity.totalDread(dark), Sanity.DARKNESS_DREAD + Sanity.WEATHER_DREAD + Sanity.CORPSE_DREAD_PER,
"every currently-active dread source is summed at once")
end
function tests.sanityTickAppliesDreadAndLogsOnlyOnATierCrossing()
local game = stagedGame(1, arenaDark(21, 11), 3, 6)
game.player.light.fuel = 0
game.player.sanity = 63
eq(Sanity.tick(game), nil, "a dread turn that stays within its tier reports no line")
eq(game.player.sanity, 61, "the dread was still deducted even without a tier crossing")
eq(Sanity.tick(game), "your nerves begin to fray", "crossing down into shaken is announced")
eq(game.player.sanity, 59, "the meter keeps draining turn over turn")
end
function tests.sanityTickAnnouncesReachingTerrifiedAndBroken()
local game = stagedGame(1, arenaDark(21, 11), 3, 6)
game.player.light.fuel = 0
game.player.sanity = 32
eq(Sanity.tick(game), "dread claws at the edges of your sanity", "crossing down into terrified is announced")
game.player.sanity = 2
eq(Sanity.tick(game), "your mind gives way to raw terror", "crossing down into broken is announced")
eq(game.player.sanity, 0, "broken is a floor, not a negative value")
end
local order = {