Files
gen1recomp/tests/gen2_evolution_anim_test.lua
T
bryanthaboi ae6cac89e1 G2 support
2026-08-11 11:53:30 -04:00

96 lines
3.7 KiB
Lua

-- The evolution screen survives being pushed onto a stack.
--
-- luajit tests/gen2_evolution_anim_test.lua
--
-- `enter` is the stack's lifecycle hook: StateStack:push calls `state:enter(...)`
-- (src/core/StateStack.lua:18), and Gold runs that same stack
-- (src/core/Game2.lua:makeStack). EvolutionAnim used to define `enter(phase)`
-- as its own phase-transition method, so pushing it called that method with
-- no argument, set phase to nil,
-- and left update() falling through every branch -- decrementing its timer
-- forever while World:busy() stayed true. A post-battle evolution became an
-- unrecoverable hang; the Gold route bot found it the first time its starter
-- reached level 14, roughly two hours into a run.
--
-- The regression this pins is not the rename but the PROPERTY: after a push,
-- the screen still runs to completion.
package.path = "./?.lua;./?/init.lua;" .. package.path
local S = require("tests.harness").suite("gen2 evolution anim")
local check, eq = S.check, S.eq
local EvolutionAnim = require("src.ui.gen2.EvolutionAnim")
-- No screen may use the stack's hook name for its own purposes.
check(rawget(EvolutionAnim, "enter") == nil,
"EvolutionAnim does not define `enter` (the stack lifecycle hook)")
check(type(rawget(EvolutionAnim, "setPhase")) == "function",
"its phase transition has a name of its own")
local cache = os.getenv("GOLD_CACHE")
if not cache then
cache = (os.getenv("HOME") or "")
.. "/Library/Application Support/LOVE/gold-dev/gold"
end
local function loadGen(name)
local path = cache .. "/data/generated/" .. name
local fh = io.open(path, "r")
if not fh then return nil end
fh:close()
return assert(loadfile(path))()
end
local pokemon = loadGen("pokemon.lua")
if not pokemon then
check(true, "gold cache absent : name check only (SKIP the run-through)")
S.finish()
return
end
local Evolution = require("src.core.gen2.Evolution")
local data = { pokemon = pokemon, moves = loadGen("moves.lua"), audio = {},
gen2Palettes = loadGen("palettes.lua") }
-- CYNDAQUIL -> QUILAVA at 14, the exact evolution the bot wedged on.
local mon = { species = "CYNDAQUIL", level = 14, hp = 20, maxHp = 20,
moves = { { id = "EMBER", pp = 10, maxPp = 25 } } }
local save = { party = { mon }, pokedex = { seen = {}, caught = {} } }
local game = { data = data, save = save }
local plan = Evolution.plan(data, { mon }, { [1] = true }, { timeOfDay = 1 })
eq(#plan, 1, "CYNDAQUIL at 14 has an evolution to play")
local done = false
local anim = EvolutionAnim.new(game, {
mon = plan[1].mon, entry = plan[1].entry, index = plan[1].index,
party = save.party, save = save,
onDone = function() done = true end,
})
-- The push, exactly as Screens.push does it: no extra arguments. This is the
-- line that used to kill the screen.
-- StateStack is used as a singleton (Game calls StateStack:init()), so the
-- test drives it directly rather than inventing an instance it does not have.
local StateStack = require("src.core.StateStack")
StateStack:init()
StateStack:push(anim)
eq(anim.phase, "evolving", "being pushed does not clear the phase")
-- Mashing A is what a bot (or an impatient player) does; it must not stall it.
game.input = { wasPressed = function(_, b) return b == "a" end }
local frames = 0
for _ = 1, 5000 do
if done then break end
frames = frames + 1
check(anim.phase ~= nil, "phase stays set while the animation runs")
if anim.phase == nil then break end
anim:update(1 / 60)
end
check(done, "the evolution runs to completion and calls onDone")
check(frames < 2000, "and finishes promptly (" .. frames .. " frames)")
eq(save.party[1].species, "QUILAVA", "the party record is written back")
S.finish()