test: cover tests.builderOccasionallyRollsAPotionOfLevitationAsLoot

This commit is contained in:
dualfroz committed 2026-09-11 18:05:46 +02:00
1 parent 1a617008d4
commit 91171be9e1
1 file changed
+173
+173
View File
@@ -8261,6 +8261,179 @@ function tests.builderOccasionallyRollsAPotionOfBlindnessAsLoot()
end end
if found then break end if found then break end
end end
ok(found ~= nil, "a potion of blindness eventually turns up in the depth-2 loot table across a handful of seeds")
ok(Shop.priceOf(found) > 0, "a found blindness potion still carries a positive catalog price")
end
function tests.builderOccasionallyRollsAPotionOfLevitationAsLoot()
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 == "levitate" then found = ground.item end
end
if found then break end
end
ok(found ~= nil, "a potion of levitation eventually turns up in the depth-2 loot table across a handful of seeds")
ok(Shop.priceOf(found) > 0, "a found levitation potion still carries a positive catalog price")
end
function tests.eventsCatalogEntriesEachHaveDistinctStartAndEndFlavorText()
for _, key in ipairs(Events.order) do
local start = Events.describe(key)
local ending = Events.endDescribe(key)
ok(start ~= nil, key .. " has a start line")
ok(ending ~= nil, key .. " has an end line")
ok(start ~= ending, key .. "'s start and end lines read differently")
end
end
function tests.eventsMonsterDamageMultiplierOnlyFiresDuringBloodmoon()
eq(Events.monsterDamageMultiplier(nil), 1, "no active event leaves monster damage unscaled")
eq(Events.monsterDamageMultiplier({ key = Events.RICHVEINS }), 1, "a non-bloodmoon event leaves monster damage unscaled")
eq(Events.monsterDamageMultiplier({ key = Events.BLOODMOON }), Events.BLOODMOON_MULTIPLIER,
"bloodmoon scales monster damage by its fixed multiplier")
end
function tests.eventsEssenceMultiplierOnlyFiresDuringRichVeins()
eq(Events.essenceMultiplier(nil), 1, "no active event leaves essence unscaled")
eq(Events.essenceMultiplier({ key = Events.BLOODMOON }), 1, "a non-richveins event leaves essence unscaled")
eq(Events.essenceMultiplier({ key = Events.RICHVEINS }), Events.RICHVEINS_MULTIPLIER,
"rich veins scales essence by its fixed multiplier")
end
function tests.eventsSightPenaltyOnlyFiresDuringHush()
eq(Events.sightPenalty(nil), 0, "no active event carries no sight penalty")
eq(Events.sightPenalty({ key = Events.BLOODMOON }), 0, "a non-hush event carries no sight penalty")
eq(Events.sightPenalty({ key = Events.HUSH }), Events.HUSH_SIGHT_PENALTY, "hush carries its fixed sight penalty")
end
function tests.eventsStepNeverStartsOnTheFirstFloorRegardlessOfTheRoll()
for seed = 1, 100 do
local level = { depth = 1 }
eq(Events.step(level, Rng.new(seed)), nil, "a depth-one floor never rolls an event")
eq(level.event, nil, "a depth-one floor's event stays nil across every seed")
end
end
function tests.eventsStepEventuallyStartsFromDepthTwoAcrossAHandfulOfSeeds()
local started = false
for seed = 1, 400 do
local level = { depth = 2 }
if Events.step(level, Rng.new(seed)) then started = true break end
end
ok(started, "a depth-two floor eventually rolls a fresh event across a handful of seeds")
end
function tests.eventsStepCountsDownAndExpiresWithItsEndLine()
local level = { depth = 2, event = { key = Events.BLOODMOON, turnsLeft = 2 } }
eq(Events.step(level, Rng.new(1)), nil, "an ordinary tick of a running event reports no line")
eq(level.event.turnsLeft, 1, "the running event's clock ticks down by one")
eq(Events.step(level, Rng.new(1)), Events.endDescribe(Events.BLOODMOON),
"the event's expiry is reported the turn its clock runs out")
eq(level.event, nil, "an expired event clears itself from the level")
end
function tests.gameMonsterMeleeDamageScalesDuringBloodmoon()
local game = stagedGame(1, arena(21, 11), 3, 6)
local orc = Monsters.create(Monsters.byKey.orc, 1, 4, 6, 1)
local before = game.player.hp
game:monsterMelee(orc)
local plainDamage = before - game.player.hp
game.player.hp = before
game.level.event = { key = Events.BLOODMOON, turnsLeft = 5 }
game:monsterMelee(orc)
local bloodmoonDamage = before - game.player.hp
eq(bloodmoonDamage, math.floor(plainDamage * Events.BLOODMOON_MULTIPLIER),
"bloodmoon scales a monster's melee damage by its fixed multiplier")
end
function tests.gameMonsterShootDamageScalesDuringBloodmoon()
local game = stagedGame(1, arena(21, 11), 3, 6)
local dragon = Monsters.create(Monsters.byKey.dragon, 1, 6, 6, 1)
local before = game.player.hp
game:monsterShoot(dragon)
local plainDamage = before - game.player.hp
game.player.hp = before
game.player.statuses = {}
game.level.event = { key = Events.BLOODMOON, turnsLeft = 5 }
game:monsterShoot(dragon)
local bloodmoonDamage = before - game.player.hp
eq(bloodmoonDamage, math.floor(plainDamage * Events.BLOODMOON_MULTIPLIER),
"bloodmoon scales a monster's ranged damage by its fixed multiplier")
end
function tests.gameKillEssenceIsDoubledDuringRichVeins()
local game = stagedGame(1, arena(21, 11), 3, 6)
local rat = Monsters.create(Monsters.byKey.rat, 1, 4, 6, 1)
rat.hp = 1
game.level.entities = { rat }
game.level.event = { key = Events.RICHVEINS, turnsLeft = 5 }
local essenceBefore = game.player.essence
game:playerAttack(rat)
eq(game.player.essence, essenceBefore + Crafting.essenceFor(Monsters.xpFor(rat)) * Events.RICHVEINS_MULTIPLIER,
"rich veins doubles the essence earned from a kill")
end
function tests.gameEffectiveRadiusShrinksDuringHush()
local game = stagedGame(1, arena(21, 11), 3, 6)
local plain = game:effectiveRadius()
game.level.event = { key = Events.HUSH, turnsLeft = 5 }
eq(game:effectiveRadius(), plain - Events.HUSH_SIGHT_PENALTY, "hush dulls the player's effective sight radius")
end
function tests.gameEndPlayerTurnEventuallyRollsAFreshEventFromDepthTwo()
local triggered = false
for seed = 1, 400 do
local game = stagedGame(1, arena(21, 11), 3, 6)
game.level.depth = 2
game.rng = Rng.new(seed)
game:endPlayerTurn()
if game.level.event then triggered = true break end
end
ok(triggered, "ending a turn on a depth-two floor eventually rolls a fresh event across a handful of seeds")
end
function tests.gameEndPlayerTurnAnnouncesAnEventEndingInTheLog()
local game = stagedGame(1, arena(21, 11), 3, 6)
game.level.event = { key = Events.RICHVEINS, turnsLeft = 1 }
game:endPlayerTurn()
eq(game.level.event, nil, "the event's clock runs out and clears itself")
ok(contains(game.log:last(), "quiet"), "the event's end is reported in the message log")
end
function tests.renderPanelShowsTheActiveEventAndItsRemainingTurns()
local game = stagedGame(1, arena(21, 11), 3, 6)
ok(not contains(Render.panel(game), "turns left"), "no active event shows no event line at all")
game.level.event = { key = Events.BLOODMOON, turnsLeft = 7 }
ok(contains(Render.panel(game), "event bloodmoon (7 turns left)"),
"an active event reports its name and remaining turns")
end
function tests.eventFieldSurvivesSerialization()
local game = Game.new(3)
game.level.event = { key = Events.HUSH, turnsLeft = 9 }
local path = os.tmpname()
game:save(path)
local restored = Game.load(path)
os.remove(path)
eq(restored.level.event.key, Events.HUSH, "the active event's key survives a save/load round trip")
eq(restored.level.event.turnsLeft, 9, "the active event's remaining turns survive a save/load round trip")
end
-- graves.lua: cross-run bones.
function tests.gravesRecordAppendsNewestFirstAndTrimsToMaxRecorded()
local meta = Meta.new()
for i = 1, Graves.MAX_RECORDED + 5 do
Graves.record(meta, { className = "fighter", depth = i, gold = i, essence = 0, turn = i })
end
eq(#meta.graves, Graves.MAX_RECORDED, "recording past the cap trims the oldest graves off")
end
local order = { local order = {
"rngDeterminism", "mapDeterminism", "tilesRegistry", "fovKnownScene", "losSightLine", "rngDeterminism", "mapDeterminism", "tilesRegistry", "fovKnownScene", "losSightLine",