test: cover tests.nodesGoingFallowRefusesFurtherHarvestsUntilItReopens

This commit is contained in:
dualfroz committed 2026-09-09 14:11:32 +02:00
1 parent 1d0de98b92
commit 183e67da75
1 file changed
+173
+173
View File
@@ -3070,6 +3070,179 @@ function tests.nodesHarvestingAGemSeamYieldsARawTierOneGemOfItsElement()
eq(#game.player.inventory, 1, "a gem lands in the inventory")
local gem = game.player.inventory[1]
eq(gem.kind, Items.GEM, "the yield is a gem item")
eq(gem.element, Crafting.FIRE, "the gem matches the seam's own element")
eq(gem.tier, 1, "a freshly pried gem is raw, tier one")
end
function tests.nodesGoingFallowRefusesFurtherHarvestsUntilItReopens()
local node = Nodes.new(Nodes.ORE)
node.charges = 1
local game = nodeGame(9)
ok(Nodes.harvest(game, node), "the last charge still pays out")
eq(node.charges, 0, "spending the last charge empties the vein")
eq(node.cooldown, Nodes.REGEN_TURNS, "emptying a vein starts its regen cooldown")
local success, line = Nodes.harvest(game, node)
eq(success, false, "a fallow vein refuses to be harvested again")
ok(contains(line, "played out"), "the refusal is logged")
end
function tests.nodesStepCountsDownAndReopensAFallowNode()
local level = { nodes = { Nodes.new(Nodes.ORE) } }
local node = level.nodes[1]
node.charges = 0
node.cooldown = 2
Nodes.step(level)
eq(node.cooldown, 1, "each step counts the cooldown down by one")
eq(node.charges, 0, "the vein stays fallow until its cooldown reaches zero")
Nodes.step(level)
eq(node.cooldown, 0, "the cooldown reaches zero on schedule")
eq(node.charges, Nodes.MAX_CHARGES, "reaching zero cooldown fully reopens the vein")
end
function tests.nodesFieldsSurviveSerialization()
local level = arena(21, 11)
local node = Nodes.new(Nodes.GEM, Crafting.FROST)
node.x, node.y = 4, 4
node.charges = 1
node.cooldown = 5
level.nodes = { node }
local restored = Level.fromData(level:toData())
eq(#restored.nodes, 1, "the node list survives a round trip")
eq(restored.nodes[1].kind, Nodes.GEM, "a node's kind survives a round trip")
eq(restored.nodes[1].element, Crafting.FROST, "a node's element survives a round trip")
eq(restored.nodes[1].charges, 1, "a node's charges survive a round trip")
eq(restored.nodes[1].cooldown, 5, "a node's cooldown survives a round trip")
end
function tests.nodeRendersItsGlyphWhenVisible()
local rendered = stagedGame(1, arena(21, 11), 3, 6)
local ore = Nodes.new(Nodes.ORE)
ore.x, ore.y = 5, 6
rendered.level.nodes = { ore }
rendered:computeVisible()
ok(contains(rendered:render(), "O"), "an ore vein on a visible cell renders as its glyph")
end
function tests.gameHarvestNodeAtAnOreVeinAddsEssenceAndSpendsACharge()
local game = stagedGame(1, arena(21, 11), 5, 5)
local ore = Nodes.new(Nodes.ORE)
ore.x, ore.y = 5, 5
game.level.nodes = { ore }
local before = game.player.essence
ok(game:harvestNode(), "harvesting the ore vein underfoot succeeds")
ok(game.player.essence > before, "harvesting an ore vein raises the player's essence")
eq(ore.charges, Nodes.MAX_CHARGES - 1, "harvesting spends one of the vein's charges")
end
function tests.gameHarvestNodeFailsWithoutANodeUnderfoot()
local game = stagedGame(1, arena(21, 11), 5, 5)
ok(not game:harvestNode(), "harvesting empty ground fails")
end
function tests.gameHarvestNodeOnAFallowVeinFails()
local game = stagedGame(1, arena(21, 11), 5, 5)
local ore = Nodes.new(Nodes.ORE)
ore.x, ore.y, ore.charges = 5, 5, 0
game.level.nodes = { ore }
ok(not game:harvestNode(), "a fallow vein cannot be harvested")
end
function tests.autopilotHarvestsAnOreVeinWhenIdleAndStandingOnIt()
local game = stagedGame(1, arena(21, 11), 5, 5)
local ore = Nodes.new(Nodes.ORE)
ore.x, ore.y = 5, 5
game.level.nodes = { ore }
local before = game.player.essence
game:choosePlayerAction()
eq(ore.charges, Nodes.MAX_CHARGES - 1, "the autopilot harvests an unspent vein it stands on")
ok(game.player.essence > before, "the autopilot's harvest actually pays out essence")
end
function tests.autopilotSkipsAFallowNode()
local game = stagedGame(1, arena(21, 11), 5, 5)
local ore = Nodes.new(Nodes.ORE)
ore.x, ore.y, ore.charges = 5, 5, 0
game.level.nodes = { ore }
game:choosePlayerAction()
eq(ore.charges, 0, "the autopilot never touches a fallow node")
end
function tests.buildersNeverPlaceANodeOnTheEntranceFloor()
for seed = 1, 5 do
local level = Builder.build(1, Rng.new(seed), {})
eq(#level.nodes, 0, "no resource node appears on the entrance floor")
end
end
function tests.buildersAlwaysPlaceAtLeastOneNodeFromDepthTwoOn()
for seed = 1, 5 do
for depth = 2, 5 do
local level = Builder.build(depth, Rng.new(seed), {})
ok(#level.nodes >= 1, "every depth-2-and-deeper floor places at least one resource node")
ok(#level.nodes <= 2, "no floor places more than two resource nodes")
for _, node in ipairs(level.nodes) do
ok(node.kind == Nodes.ORE or node.kind == Nodes.GEM, "every placed node is a known kind")
end
end
end
end
local function corpsePlayer()
return { inventory = {}, statuses = {} }
end
function tests.corpsesSpawnsForAWildMonsterButNotForUndeadMimicOrBoss()
local level = { corpses = {} }
local rat = Monsters.create(Monsters.byKey.rat, 1, 5, 5, 1)
Corpses.spawn(level, rat)
eq(#level.corpses, 1, "a wild monster leaves a corpse behind")
local zombie = Monsters.create(Monsters.byKey.zombie, 2, 6, 6, 4)
Corpses.spawn(level, zombie)
eq(#level.corpses, 1, "an undead monster crumbles to dust, leaving no corpse")
local mimic = Mimics.reveal(3, 7, 7, 4)
Corpses.spawn(level, mimic)
eq(#level.corpses, 1, "a mimic was never alive, so it leaves no corpse")
local warlord = Bosses.create(Bosses.byKey.warlord, 4, 8, 8)
Corpses.spawn(level, warlord)
eq(#level.corpses, 1, "a boss's own scripted loot drop replaces a corpse")
end
function tests.corpsesNutritionScalesWithTheFelledSpeciesMaxHpClampedToAFixedBand()
local bat = Monsters.create(Monsters.byKey.bat, 1, 5, 5, 1)
local batCorpse = Corpses.new(bat)
eq(batCorpse.nutrition, Corpses.NUTRITION_MIN, "a frail species clamps to the nutrition floor")
local troll = Monsters.create(Monsters.byKey.troll, 2, 5, 5, 5)
local trollCorpse = Corpses.new(troll)
eq(trollCorpse.nutrition, troll.maxHp * Corpses.NUTRITION_PER_HP, "a tougher species scales nutrition with maxHp")
local dragon = Monsters.create(Monsters.byKey.dragon, 3, 5, 5, 6)
local dragonCorpse = Corpses.new(dragon)
eq(dragonCorpse.nutrition, Corpses.NUTRITION_MAX, "an especially tough species clamps to the nutrition ceiling")
end
function tests.corpsesStepCountsDownAndRotsAwayEntirely()
local rat = Monsters.create(Monsters.byKey.rat, 1, 5, 5, 1)
local level = { corpses = {} }
Corpses.spawn(level, rat)
local corpse = level.corpses[1]
for _ = 1, Corpses.DECAY_TURNS - 1 do Corpses.step(level) end
eq(#level.corpses, 1, "a corpse survives right up to its final round")
eq(corpse.decay, 1, "decay counts down by exactly one each step")
Corpses.step(level)
eq(#level.corpses, 0, "a corpse vanishes outright once its decay reaches zero")
end
function tests.corpsesGoesRottenPastItsThresholdButStaysButcherableUntilThen()
local rat = Monsters.create(Monsters.byKey.rat, 1, 5, 5, 1)
local corpse = Corpses.new(rat)
ok(not Corpses.isRotten(corpse), "a freshly spawned corpse is not yet rotten")
corpse.decay = Corpses.ROT_TURNS
ok(Corpses.isRotten(corpse), "a corpse right at its rot threshold counts as rotten")
corpse.decay = Corpses.ROT_TURNS + 1
end
local order = {