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

133 lines
5.3 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
local ROM_PATH = os.getenv("POKEPORT_EMERALD_ROM") or "../pokeemerald/pokeemerald.gba"
local f = io.open(ROM_PATH, "rb")
if not f then
print("emerald_mon_anim_test: skipped (no ROM at " .. ROM_PATH .. ")")
os.exit(0)
end
local data = f:read("*a")
f:close()
local SHA = "f3ae088181bf583e55daf962a92bb46f4f1d07b7"
local rom = assert(require("src.import.gba.rom").open({
info = function() return { size = #data, md5 = SHA } end,
read = function(_, _, off, len) return data:sub(off + 1, off + len) end,
}, "emerald"))
require("src.core.GameVersion").set("emerald")
local files = {}
local cache = {
write = function(_, rel, b) files[rel] = b; return true end,
read = function(_, rel) return files[rel] end,
exists = function(_, rel) return files[rel] ~= nil end,
}
require("src.import.gba.mon_anim_extract").run(rom, cache, { cacheRoot = "data/generated/gba" })
local Data = require("src.core.game3.mon_anim_data")
Data.useCache({ read = function() return nil end })
check(not Data.available(), "no front_anims pack means mon anims are off (FireRed caches)")
Data.useCache(cache)
check(Data.available(), "Emerald cache enables mon anims")
local M = require("src.core.game3.mon_anim")
eq(Data.functionCount(), 151, "sMonAnimFunctions has 151 entries")
local ported = 0
for id = 0, 150 do
if pcall(M.animFunction, id) then ported = ported + 1 end
end
eq(ported, 151, "every sMonAnimFunctions entry has a port")
local C = require("src.core.game3.constants").of("emerald")
local EGG = C:require("species", "SPECIES_EGG")
eq(Data.functionName(Data.frontAnimId(EGG)), Data.functionName(Data.get().front.animDelays[1]),
"egg reads sMonFrontAnimIdsTable past its end into sMonAnimationDelayTable")
eq(Data.delay(EGG), 3, "egg delay reads gPPUpGetMask[0]")
local function run(sprite, tasksFirst)
local n = 0
while not M.done(sprite) and n < 2000 do
M.step(sprite, tasksFirst)
n = n + 1
end
return n
end
local stuck, moved = {}, {}
for sp = 1, EGG do
local s = M.newSprite(sp, { data = { [0] = 1, [2] = sp } })
M.battleFront(s, sp, false, 1, { cry = function() end })
if run(s) >= 2000 or s.data[2] ~= sp or s.data[0] ~= 1 or not s.animEnded then stuck[#stuck + 1] = "front " .. sp end
for flip = 0, 1 do
local q = M.newSprite(sp, { affineMode = "off", hFlip = flip == 0, data = { [0] = sp } })
q.callback = function(x) x.data[1] = flip; M.summary(x, sp, sp == EGG) end
q.summary = true
if run(q, true) >= 2000 or q.affineMode ~= "off" and q.affineMode ~= "normal" then
stuck[#stuck + 1] = "summary " .. sp .. "/" .. flip
end
end
if s.x2 ~= 0 then moved[#moved + 1] = sp end
end
for set = 0, 24 do
for nature = 0, 24 do
local s = M.newSprite(1, { data = { [0] = 0, [2] = 1 } })
for sp = 1, 411 do
if Data.backAnimSet(sp) == set then s = M.newSprite(sp, { data = { [0] = 0, [2] = sp } }) break end
end
M.battleBack(s, s.species, nature)
if run(s) >= 2000 then stuck[#stuck + 1] = "back " .. set .. "/" .. nature end
end
end
eq(#stuck, 0, "every front, summary and back anim ends (" .. table.concat(stuck, ",") .. ")")
eq(M.oob, 0, "no gSineTable read outside its 320 entries")
check(#moved == 2, "only the RapidHorizontalHops species keep x2 = 1 after the anim (" .. table.concat(moved, ",") .. ")")
local skip = M.newSprite(25, { data = { [2] = 25 } })
M.battleFront(skip, 25, true, 1, { noAnimations = true })
check(M.done(skip), "battle scene off skips the front anim")
local modes = { off = 0, normal = 1, double = 3 }
local function trunc(x) if x >= 0 then return math.floor(x) end return -math.floor(-x) end
local function mat(m)
local sx, sy = m.xScale / 256, m.yScale / 256
local th = math.floor(m.rotation / 256) / 128 * math.pi
local c, s = math.cos(th), math.sin(th)
return trunc(c * sx * 256), trunc(-s * sx * 256), trunc(s * sy * 256), trunc(c * sy * 256)
end
local golden = dofile("tests/data/emerald_mon_anim/golden.lua")
for _, g in ipairs(golden) do
local s, tasksFirst
if g.kind == "front" then
s = M.newSprite(g.species, { data = { [0] = 1, [2] = g.species } })
s.callback = function(x) M.battleFront(x, g.species, false, 1, { cry = function() end }) end
elseif g.kind == "back" then
s = M.newSprite(g.species, { data = { [0] = 0, [2] = g.species } })
s.callback = function(x) M.battleBack(x, g.species, g.nature) end
else
s = M.newSprite(g.species, { affineMode = "off", hFlip = g.hflip == 1, data = { [0] = g.species } })
s.callback = function(x) x.data[1] = g.noFlip; M.summary(x, g.species, false) end
tasksFirst = true
end
local bad
for i, r in ipairs(g.rows) do
M.step(s, tasksFirst)
local a, b, c, d = mat(s.matrix)
local ok = s.x2 == r[1] and s.y2 == r[2] and modes[s.affineMode] == r[3] and s.frame == r[8]
and M.callbackName(s) == r[9]
if r[3] ~= 0 then
ok = ok and math.abs(a - r[4]) <= 1 and math.abs(b - r[5]) <= 1 and math.abs(c - r[6]) <= 1
and math.abs(d - r[7]) <= 1
end
if not ok then
bad = string.format("frame %d: x2 %d/%d y2 %d/%d cb %s/%s", i, s.x2, r[1], s.y2, r[2], M.callbackName(s), r[9])
break
end
end
check(bad == nil, string.format("%s species %d matches the mGBA trace for %d frames%s", g.kind, g.species, #g.rows,
bad and (" (" .. bad .. ")") or ""))
end
T.finish("emerald_mon_anim_test")