Files
gen1recomp/tests/drivers/em_anim_sweep.lua
T
bryanthaboi 690f0cd9a1 TOUCHDOWN
2026-09-27 16:07:31 -04:00

260 lines
9.7 KiB
Lua

local U = require("tests.drivers.util")
local DIR = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/em_anim_sweep"
local SHOTS = {
MORNING_SUN = 40, BLIZZARD = 60, TRANSFORM = 30, ROLE_PLAY = 40, SNATCH = 40, ODOR_SLEUTH = 30,
GLARE = 30, METEOR_MASH = 30, BLOCK = 30, TORMENT = 40, ENCORE = 30, DOOM_DESIRE = 60,
SURF = 40, PSYCHIC = 40, THUNDERBOLT = 20, FLAMETHROWER = 30, HYPER_BEAM = 40, EARTHQUAKE = 20,
}
return function(game)
local fails = 0
local function result(ok, label)
print((ok and "PASS " or "FAIL ") .. label)
if not ok then fails = fails + 1 end
end
local issues, issueSeen, current = {}, {}, "setup"
local function issue(kind, name)
local key = kind .. ":" .. tostring(name)
if issueSeen[key] then return end
issueSeen[key] = true
issues[#issues + 1] = key .. " (" .. current .. ")"
end
local realPrint = print
print = function(...)
local parts = {}
for i = 1, select("#", ...) do parts[#parts + 1] = tostring((select(i, ...))) end
local s = table.concat(parts, " ")
if s:find("[battle.anim]", 1, true) and not s:find("cap", 1, true) then issue("log", s) end
realPrint(...)
end
for _ = 1, 900 do
if game.phase == "boot" and game.boot then break end
U.wait(1)
end
game:_handleBootAction({ action = "new_game", name = "NICK", gender = 0 })
U.wait(240)
local Runtime = require("src.core.game3.runtime")
local Party = require("src.core.game3.party")
local BattleBridge = require("src.core.game3.battle_bridge")
local Battle = require("src.core.game3.battle")
local Anim = require("src.core.game3.battle.anim")
local AnimCtx = require("src.core.game3.battle.anim_ctx")
local AnimTasks = require("src.core.game3.battle.anim_tasks")
local AnimCallbacks = require("src.core.game3.battle.anim_callbacks")
local AnimSprites = require("src.core.game3.battle.anim_sprites")
local Ui = require("src.core.game3.battle.ui")
local Map = require("src.core.game3.map")
local GAME = require("src.core.GameVersion").get()
local C = require("src.core.game3.constants").of(GAME)
local session = Runtime.getSession()
result(session ~= nil, "field session exists")
if not session then love.event.quit(1) return end
local function sp(name) return C.species.byName["SPECIES_" .. name] end
local realSpawn = AnimTasks.spawn
AnimTasks.spawn = function(name, priority, args, vm)
local n = tostring(name or "stub")
local key = n:gsub("^g", "")
if not (AnimTasks.REGISTRY[n] or AnimTasks.REGISTRY[key] or AnimTasks.REGISTRY["AnimTask_" .. key]) then
issue("task", n)
end
return realSpawn(name, priority, args, vm)
end
local realGet = AnimCallbacks.get
AnimCallbacks.get = function(name)
if name and name ~= "" then
local clean = tostring(name):gsub("^Anim", "")
if not (rawget(AnimCallbacks, name) or AnimCallbacks[name] or AnimCallbacks[clean] or AnimCallbacks["Anim" .. clean]) then
issue("callback", name)
end
end
return realGet(name)
end
if GAME == "emerald" then
local ok, err = pcall(function() Map.load(nil, game, "EM_ROUTE101", { x = 10, y = 12, facing = "down" }) end)
result(ok, "load EM_ROUTE101 " .. tostring(err or ""))
end
U.wait(30)
session.party = {}
Party.giveMon(session, sp("SALAMENCE"), 50, "SALAMENCE")
session.party[1].hp = session.party[1].maxHp or session.party[1].hp
local started, serr = BattleBridge.startWild(Runtime._mod, game, { species = sp("ZIGZAGOON"), level = 50 }, { fade = false })
result(started == true, "wild battle started " .. tostring(serr or ""))
if not started then love.event.quit(1) return end
local ready = false
for _ = 1, 6000 do
if Battle.isActive() and Ui._mode == "menu" and not Anim.busy() then ready = true break end
if Ui.dialogPending and Ui.dialogPending() then U.tap(game, "a") else U.wait(1) end
end
result(ready, "battle reached the action menu")
if not ready then love.event.quit(1) return end
U.wait(20)
local pack = Anim._pack or (Anim.scriptForMove(1) and Anim._pack)
result(pack and pack.moves ~= nil, "Emerald anim pack loaded")
local moveCount = 0
for _ in pairs(pack.moves) do moveCount = moveCount + 1 end
result(moveCount == 355, "pack has 355 move scripts (" .. moveCount .. ")")
if GAME == "emerald" then
result(#pack.generalNames == 22 and pack.generalNames[4] == "POKEBLOCK_THROW", "23 general anims, 4 = POKEBLOCK_THROW")
end
local st = Battle._st
local pSpecies = st and st.player and st.player.mon and st.player.mon.species or sp("SALAMENCE")
local eSpecies = st and st.enemy and st.enemy.mon and st.enemy.mon.species or sp("ZIGZAGOON")
local function restore()
for _, s in ipairs({ "player", "enemy" }) do
local p = Anim.present(s)
if p then
p.visible = true
p.ox, p.oy, p.sx, p.sy, p.rotation = 0, 0, 1, 1, 0
p.blendCoeff = 0
p.invisible = nil
p.transformSpecies = nil
end
if p and p.substitute then Anim.setSubstitute(s, false) end
end
end
local function audit_sprites()
AnimSprites.init()
for i = 1, AnimSprites.MAX do
local s = AnimSprites._pool[i]
if s.active and s.tag and s.tag ~= "" and not s.image and not s.invisible and s.visible ~= false
and not (s._op and s._op.noGfx) then
issue("sprite-no-image", s.tag)
end
end
end
local RESTORED = { visible = true, ox = true, oy = true, sx = true, sy = true, rotation = true, blendCoeff = true }
local function snap()
local out = {}
for _, side in ipairs({ "player", "enemy" }) do
local p = Anim.present(side) or {}
for k, v in pairs(p) do
local t = type(v)
if (t == "number" or t == "boolean" or t == "string") and not RESTORED[k] then out[side .. "." .. k] = v end
end
end
return out
end
local baseline
local leaks = {}
local function check_leak(label)
local now = snap()
for k, v in pairs(now) do
if baseline[k] ~= v and not leaks[k] then
leaks[k] = true
print("[driver] leak " .. k .. " " .. tostring(baseline[k]) .. " -> " .. tostring(v) .. " after " .. label)
end
end
for k, v in pairs(baseline) do
if now[k] == nil and not leaks[k] then
leaks[k] = true
print("[driver] leak " .. k .. " " .. tostring(v) .. " -> nil after " .. label)
end
end
end
local timeouts, totalFrames, ran = 0, 0, 0
local function run(label, launch, shotAt)
current = label
local okL, e = pcall(launch)
if not okL then issue("launch", tostring(e)) return end
local f = 0
while f < 1500 do
U.wait(1)
f = f + 1
if f % 8 == 0 then audit_sprites() end
local want = shotAt and (type(shotAt) == "table" and shotAt[f] or shotAt == f)
if want then
local stem = label:lower():gsub("[^%w]+", "_")
U.still(game, DIR .. "/" .. stem .. (type(shotAt) == "table" and string.format("_%03d", f) or "") .. ".png")
end
if not Anim.busy() and (not shotAt or f > (type(shotAt) == "table" and shotAt.last or shotAt)) then break end
end
if Anim.busy() then
timeouts = timeouts + 1
issue("timeout", label)
end
totalFrames = totalFrames + f
ran = ran + 1
restore()
check_leak(label)
U.wait(4)
end
local function opts(side, turn, moveId)
local target = side == "player" and "enemy" or "player"
return {
attackerSide = side,
targetSide = target,
isReversed = side == "enemy",
attackerSpecies = side == "player" and pSpecies or eSpecies,
targetSpecies = side == "player" and eSpecies or pSpecies,
moveTurn = turn or 0,
turn = turn or 0,
ctx = AnimCtx.build(side, target, { moveId = moveId }),
}
end
baseline = snap()
local only = os.getenv("EM_ANIM_MOVES")
local shotList = nil
if os.getenv("EM_ANIM_SHOTS") then
shotList = { last = 0 }
for n in os.getenv("EM_ANIM_SHOTS"):gmatch("%d+") do
shotList[tonumber(n)] = true
if tonumber(n) > shotList.last then shotList.last = tonumber(n) end
end
end
local sides = os.getenv("EM_ANIM_SIDES") or "player,enemy"
for id = 1, 354 do
local name = (C:name("moves", id, "MOVE_") or ("MOVE_" .. id)):gsub("^MOVE_", "")
if not only or only:find(name, 1, true) then
for side in sides:gmatch("[^,]+") do
local shot = shotList or (side == "player" and SHOTS[name] or nil)
run(name .. "_" .. side, function() Anim.launchMove(id, opts(side, 0, id)) end, shot)
for _, op in ipairs(pack.moves[id]) do
if op.op == "choosetwoturnanim" or op.op == "jumpifmoveturn" then
run(name .. "_turn1_" .. side, function() Anim.launchMove(id, opts(side, 1, id)) end, nil)
break
end
end
end
end
end
if not only then
for i = 0, #pack.generalNames do
local n = pack.generalNames[i]
do
run("general_" .. n, function() Anim.launchGeneral(n, opts("player")) end, n == "STATS_CHANGE" and 20 or nil)
end
end
for i = 0, #pack.statusNames do
local n = pack.statusNames[i]
run("status_" .. n, function() Anim.launchStatus(i, opts("enemy")) end, n == "STATUS_FRZ" and 20 or nil)
end
for _, n in ipairs({ "LVL_UP", "SUBSTITUTE_TO_MON", "MON_TO_SUBSTITUTE" }) do
run("special_" .. n, function() Anim.launchSpecial(n, opts("player")) end, nil)
end
end
print = realPrint
print(string.format("[driver] ran %d anims, %d frames, %d timeouts", ran, totalFrames, timeouts))
for _, s in ipairs(issues) do print("[driver] issue " .. s) end
result(#issues == 0, "no unresolved task/callback, sprite without sheet, anim log or launch error (" .. #issues .. ")")
result(timeouts == 0, "every anim finishes within 1500 frames")
print(string.format("[driver] em_anim_sweep: %d failure(s)", fails))
love.event.quit(fails == 0 and 0 or 1)
end