test: cover tests.craftingFieldsSurviveSerialization

This commit is contained in:
dualfroz committed 2026-09-09 13:36:21 +02:00
1 parent bf8645b0ad
commit 1d0de98b92
1 file changed
+173
+173
View File
@@ -2897,6 +2897,179 @@ function tests.gameCraftingWrappersDriveTheFullPipeline()
end end
function tests.monsterKillsGrantEssenceScaledToTheirXpReward() function tests.monsterKillsGrantEssenceScaledToTheirXpReward()
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 }
local essenceBefore = game.player.essence
game:playerAttack(rat)
eq(game.player.essence, essenceBefore + Crafting.essenceFor(Monsters.xpFor(rat)),
"a kill grants essence scaled to the monster's xp reward")
end
function tests.craftingFieldsSurviveSerialization()
local game = Game.new(5)
local weapon = Items.weapon("dagger", 2)
Items.equipWeapon(weapon, game.player)
Crafting.socket(game.player, weapon, Crafting.gem(Crafting.VENOM, 2))
game.player.essence = 37
game.player.inventory[#game.player.inventory + 1] = Crafting.gem(Crafting.SHOCK, 1)
local path = os.tmpname()
game:save(path)
local restored = Game.load(path)
os.remove(path)
eq(restored.player.essence, 37, "essence survives a save/load round trip")
eq(restored.player.weapon.socket.element, Crafting.VENOM, "a socketed gem's element survives a save/load round trip")
eq(restored.player.weapon.socket.tier, 2, "a socketed gem's tier survives a save/load round trip")
local restoredGem
for _, item in ipairs(restored.player.inventory) do
if item.kind == Items.GEM then restoredGem = item end
end
ok(restoredGem ~= nil, "a loose gem in the inventory survives a save/load round trip")
eq(restoredGem.element, Crafting.SHOCK, "the loose gem's element survives a save/load round trip")
end
function tests.alchemyBrewResolvesAKnownPairToItsFixedPotion()
local potion = Alchemy.brew(Alchemy.SUNPETAL, Alchemy.SUNPETAL)
eq(potion.effect, "heal", "a matched pair of the same herb brews that herb's aspect")
eq(potion.power, 18, "a doubled herb brews a stronger potion than a mixed pair")
end
function tests.alchemyBrewIsOrderIndependent()
local forward = Alchemy.brew(Alchemy.BLOODROOT, Alchemy.NIGHTCAP)
local backward = Alchemy.brew(Alchemy.NIGHTCAP, Alchemy.BLOODROOT)
eq(forward.effect, backward.effect, "brewing a pair does not depend on which herb is named first")
eq(forward.power, backward.power, "a brewed potion's power does not depend on herb order either")
end
function tests.alchemyBrewCurdlesOnAnOpposedPairing()
local potion, reason = Alchemy.brew(Alchemy.SUNPETAL, Alchemy.NIGHTCAP)
eq(potion, nil, "light and shade herbs curdle rather than brewing a potion")
eq(reason, "curdle", "a curdled brew names its failure")
end
function tests.alchemyLookupRejectsAnUnknownHerb()
eq(Alchemy.lookup("mud", Alchemy.SUNPETAL), nil, "a nonexistent herb key has no recipe")
end
function tests.gameBrewAtCombinesTwoHerbsIntoAPotionAtACauldron()
local game = stagedGame(1, arena(21, 11), 5, 5)
game.level.cauldrons = { { x = 5, y = 5 } }
game.player.inventory = { Alchemy.herb(Alchemy.SUNPETAL), Alchemy.herb(Alchemy.BLOODROOT) }
ok(game:brewAt(1, 2), "brewing two herbs at a cauldron underfoot succeeds")
eq(#game.player.inventory, 1, "both source herbs are spent and one potion is added")
local brewed = game.player.inventory[1]
eq(brewed.kind, Items.POTION, "brewing yields an ordinary catalog potion")
eq(brewed.effect, "heal", "the brewed potion matches the herb pair's fixed recipe")
eq(brewed.power, 10, "the brewed potion's power matches the recipe, not the catalog default")
ok(Identify.isKnown(game.identify, brewed), "brewing a potion yourself identifies its type for free")
end
function tests.gameBrewAtFailsWithoutACauldronUnderfoot()
local game = stagedGame(1, arena(21, 11), 5, 5)
game.player.inventory = { Alchemy.herb(Alchemy.SUNPETAL), Alchemy.herb(Alchemy.BLOODROOT) }
ok(not game:brewAt(1, 2), "brewing away from any cauldron fails")
eq(#game.player.inventory, 2, "a failed brew for lack of a cauldron spends nothing")
end
function tests.gameBrewAtRequiresTwoHerbItems()
local game = stagedGame(1, arena(21, 11), 5, 5)
game.level.cauldrons = { { x = 5, y = 5 } }
game.player.inventory = { Alchemy.herb(Alchemy.SUNPETAL) }
ok(not game:brewAt(1, 1), "brewing a herb against itself by index is rejected")
game.player.inventory = { Alchemy.herb(Alchemy.SUNPETAL), Items.potion(10) }
ok(not game:brewAt(1, 2), "a non-herb item cannot stand in for the second half of a brew")
eq(#game.player.inventory, 2, "a rejected brew attempt spends nothing")
end
function tests.gameBrewAtCurdlesIntoGasInsteadOfAPotion()
local game = stagedGame(1, arena(21, 11), 5, 5)
game.level.cauldrons = { { x = 5, y = 5 } }
game.player.inventory = { Alchemy.herb(Alchemy.SUNPETAL), Alchemy.herb(Alchemy.NIGHTCAP) }
ok(not game:brewAt(1, 2), "a curdled brew reports failure")
eq(#game.player.inventory, 0, "a curdled brew still spends both herbs")
ok(Hazards.at(game.level, 5, 5, Hazards.GAS) ~= nil, "a curdled brew vents a gas hazard at the brewer's feet")
ok(contains(game.log:recent(1)[1] or "", "curdle"), "the curdle is logged")
end
function tests.buildersScatterHerbsFromTheFirstFloor()
local level = Builder.build(1, Rng.new(1), {})
local herbs = 0
for _, ground in ipairs(level.floorItems) do
if ground.item.kind == Items.HERB then herbs = herbs + 1 end
end
ok(herbs >= 2, "the entrance floor already scatters foraging herbs")
end
function tests.cauldronsStayOffTheEntranceFloor()
for seed = 1, 5 do
local level = Builder.build(1, Rng.new(seed), {})
eq(#level.cauldrons, 0, "no cauldron appears on the entrance floor")
end
end
function tests.buildersOccasionallyPlaceACauldronFromDepthTwo()
local level = Builder.build(2, Rng.new(14), {})
eq(#level.cauldrons, 1, "this seed's depth-2 floor places a cauldron")
end
function tests.cauldronFieldsSurviveSerialization()
local level = arena(21, 11)
level.cauldrons = { { x = 4, y = 4 } }
local restored = Level.fromData(level:toData())
eq(#restored.cauldrons, 1, "the cauldron list survives a round trip")
eq(restored.cauldrons[1].x, 4, "a cauldron's position survives a round trip")
end
function tests.cauldronRendersItsGlyphWhenVisible()
local rendered = stagedGame(1, arena(21, 11), 3, 6)
rendered.level.cauldrons = { { x = 5, y = 6 } }
rendered:computeVisible()
ok(contains(rendered:render(), "U"), "a cauldron on a visible cell renders as its glyph")
end
local function nodePlayer()
return { essence = 0, inventory = {} }
end
local function nodeGame(seed)
local game = { player = nodePlayer(), rng = Rng.new(seed), level = { event = nil }, ascension = {} }
function game:essenceMultiplier()
return Events.essenceMultiplier(self.level.event) * Ascension.essenceMultiplier(self.ascension)
end
return game
end
function tests.nodesHarvestingAnOreVeinPaysEssenceAndSpendsACharge()
local node = Nodes.new(Nodes.ORE)
local game = nodeGame(3)
local success, line = Nodes.harvest(game, node)
ok(success, "harvesting a fresh ore vein succeeds")
eq(node.charges, Nodes.MAX_CHARGES - 1, "a harvest spends exactly one charge")
ok(game.player.essence >= Nodes.ORE_YIELD_MIN and game.player.essence <= Nodes.ORE_YIELD_MAX,
"the essence paid out falls within the vein's yield range")
ok(contains(line, "essence"), "the harvest is logged")
end
function tests.nodesHarvestingAnOreVeinDoublesItsYieldDuringRichVeins()
local node = Nodes.new(Nodes.ORE)
local game = nodeGame(3)
game.level.event = { key = Events.RICHVEINS, turnsLeft = 5 }
Nodes.harvest(game, node)
ok(game.player.essence >= Nodes.ORE_YIELD_MIN * 2 and game.player.essence <= Nodes.ORE_YIELD_MAX * 2,
"rich veins doubles the vein's ordinary yield range")
end
function tests.nodesHarvestingAGemSeamYieldsARawTierOneGemOfItsElement()
local node = Nodes.new(Nodes.GEM, Crafting.FIRE)
local game = nodeGame(3)
local success = Nodes.harvest(game, node)
ok(success, "harvesting a fresh gem seam succeeds")
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")
end end
local order = { local order = {