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

196 lines
8.5 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 PRET = os.getenv("POKEEMERALD") or "../pokeemerald"
local f = io.open(ROM_PATH, "rb")
if not f then
print("emerald_pokemon_pack_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"))
local Versions = require("src.import.gba.versions")
eq(Versions.active(), "emerald", "the ROM selects the Emerald key table")
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,
}
local ROOT = "R"
local function pack(rel) return assert(load(assert(files[ROOT .. "/pokemon/" .. rel], rel)))() end
local function file(rel) return files[ROOT .. "/pokemon/" .. rel] end
local PE = require("src.import.gba.pokemon_extract")
local plan = require("src.import.gba.plans.rse.pokemon_gfx")
for _, task in ipairs(plan.tasks) do
for _, step in ipairs(task.steps) do
local opts = { cacheRoot = ROOT }
for k, v in pairs(step.opts or {}) do opts[k] = v end
require("src.import.gba." .. step.name).run(rom, cache, opts)
end
end
local names, stats, meta = pack("names.lua"), pack("stats.lua"), pack("meta.lua")
eq(names[277], "TREECKO", "species 277 is TREECKO")
eq(names[410], "DEOXYS", "species 410 is DEOXYS")
local d = stats[410]
eq(table.concat({ d.hp, d.atk, d.def, d.spe, d.spa, d.spd }, "/"), "50/95/90/180/95/90",
"Deoxys in-game stats are the Emerald Speed forme")
eq(table.concat(meta[410].linkStats, "/"), "50/150/50/150/150/50", "Deoxys link stats are the normal forme")
local moves = require("src.import.gba.battle_moves_extract").extract(rom).moves
eq(moves[267].accuracy, 95, "move 267 Nature Power accuracy 95")
eq(moves[289].flags, 0x10, "move 289 Snatch flags 0x10")
eq(pack("move_names.lua")[1], "POUND", "move 1 POUND")
local hoenn, regional = pack("hoenn.lua"), pack("regional_dex.lua")
eq(hoenn.toHoenn[277], 1, "Treecko is Hoenn #1")
eq(hoenn.toSpecies[1], 277, "Hoenn #1 is species 277")
eq(regional.count, 202, "Hoenn dex count 202")
eq(regional.order[1], 252, "Hoenn #1 is national 252")
eq(pack("national.lua").toNational[277], 252, "Treecko is national 252")
eq(pack("type_names.lua")[13], "ELECTR", "type 13 ELECTR")
eq(pack("exp_table.lua")[0][100], 1000000, "medium fast level 100 exp")
eq(#pack("learnsets.lua")[277] > 0, true, "Treecko has a level-up learnset")
eq(pack("dex.lua")[252].category, "WOOD GECKO", "national 252 category")
local PIC = 64 * 64 * 4
local anim = file("front_anim/277.rgba")
eq(anim and #anim, 64 * 128 * 4, "front_anim/277 is a 64x128 RGBA sheet")
check(anim and anim:sub(1, PIC) ~= anim:sub(PIC + 1), "Treecko frame 1 differs from frame 0")
eq(file("front/277.rgba"), anim and anim:sub(1, PIC), "front/277 is frame 0 of the sheet")
check(file("front_anim_shiny/277.rgba") ~= anim, "shiny sheet differs from the normal sheet")
eq(#(file("front_still/277.rgba") or ""), PIC, "still front pic")
eq(#(file("back/277.rgba") or ""), PIC, "back pic")
local deoxysAnim = file("front_anim/410.rgba")
eq(file("front/410_handled.rgba"), deoxysAnim and deoxysAnim:sub(PIC + 1),
"DuplicateDeoxysTiles: the handled front is frame 1")
local handled = file("front_anim/410_handled.rgba") or ""
eq(handled:sub(1, PIC), handled:sub(PIC + 1), "handled Deoxys sheet repeats frame 1")
eq(#(file("back/410_handled.rgba") or ""), PIC, "handled Deoxys back pic")
check(file("icons/410_handled.rgba") ~= file("icons/410.rgba"), "the handled Deoxys icon is the second icon")
for form = 1, 3 do
check(file("front/385_" .. form .. ".rgba") ~= nil, "Castform front form " .. form)
check(file("back/385_" .. form .. ".rgba") ~= nil, "Castform back form " .. form)
end
check(file("front_anim/385.rgba") == nil, "Castform has no 2-frame sheet")
check(file("front_anim/439.rgba") ~= nil, "Unown ? has a 2-frame sheet")
local icons = 0
for rel in pairs(files) do
if rel:match("^R/pokemon/icons/%d+%.rgba$") then icons = icons + 1 end
end
eq(icons, Versions.MON_ICON_COUNT, "one icon per gMonIconTable entry")
eq(pack("icons.lua").count, 440, "icon palette index count")
local footprints = 0
for rel in pairs(files) do
if rel:match("^R/pokemon/footprints/%d+%.rgba$") then footprints = footprints + 1 end
end
eq(footprints, Versions.MON_FOOTPRINT_COUNT, "one footprint per gMonFootprintTable entry")
local coords = pack("pic_coords.lua")
eq(coords.count, 440, "pic coords rows")
eq(coords.front[1].size, 0x45, "Bulbasaur front coords size (Emerald)")
eq(coords.front[1].y, 14, "Bulbasaur front y_offset (Emerald)")
local anims = pack("front_anims.lua")
local tl = anims.lists[anims.species[277][2]]
eq(tl.name, "sAnim_Treecko_1", "Treecko anim list")
local seq = {}
for _, c in ipairs(tl.cmds) do seq[#seq + 1] = c.op or (c.frame .. ":" .. c.duration) end
eq(table.concat(seq, " "), "0:6 1:15 0:6 1:15 0:3 end", "Treecko anim cmds (front_pic_anims.h:2920)")
local delays, maxId = 0, 0
for sp, v in pairs(anims.animDelays) do delays = delays + 1 end
for _, v in pairs(anims.animIds) do if v > maxId then maxId = v end end
eq(delays, 411, "animation delay table covers every species")
check(maxId < #anims.functions + 1, "anim ids index sMonAnimFunctions")
eq(#pack("back_anims.lua").natureMods + 1, 25, "back anim nature mods")
eq(#(files[ROOT .. "/pokemon/egg/hatch.rgba"] or ""), 32 * 128 * 4, "egg hatch sheet")
eq(#(file("front/412.rgba") or ""), PIC, "egg front pic")
for _, rel in ipairs(PE.REQUIRED) do
check(files[ROOT .. "/" .. rel] ~= nil, "REQUIRED " .. rel)
end
check(PE.ready(cache, ROOT), "pokemon pack ready")
local function pngIndices(path)
local p = io.popen("python3 -c \"import sys;from PIL import Image;im=Image.open(sys.argv[1]);"
.. "sys.stdout.write(' '.join(map(str,im.getdata())))\" " .. path .. " 2>/dev/null")
if not p then return nil end
local out = p:read("*a")
p:close()
if out == "" then return nil end
local t = {}
for n in out:gmatch("%d+") do t[#t + 1] = tonumber(n) end
return t
end
local function palette(path)
local h = io.open(path, "r")
if not h then return nil end
local lines = {}
for line in h:lines() do lines[#lines + 1] = line end
h:close()
local pal = {}
for i = 0, 15 do
local r, g, b = lines[4 + i]:match("(%d+)%s+(%d+)%s+(%d+)")
local function c8(v) return math.floor(math.floor(tonumber(v) / 8) * 255 / 31 + 0.5) end
pal[i] = { c8(r), c8(g), c8(b) }
end
return pal
end
local function pixelExact(png, pal, rgba, limit)
local idx, colors = pngIndices(png), palette(pal)
if not (idx and colors) then return nil end
local bad = 0
for i, p in ipairs(idx) do
if limit and i > limit then break end
local a, b, c, al = rgba:byte((i - 1) * 4 + 1, (i - 1) * 4 + 4)
if p == 0 then
if al ~= 0 then bad = bad + 1 end
else
local e = colors[p]
if not (a == e[1] and b == e[2] and c == e[3] and al == 255) then bad = bad + 1 end
end
end
return bad, #idx
end
local bad, n = pixelExact(PRET .. "/graphics/pokemon/treecko/anim_front.png",
PRET .. "/graphics/pokemon/treecko/normal.pal", anim or "")
if bad then
eq(n, 64 * 128, "pret Treecko anim_front.png is 64x128")
eq(bad, 0, "Treecko front_anim is pixel-exact vs pret anim_front.png + normal.pal")
local sbad = pixelExact(PRET .. "/graphics/pokemon/treecko/anim_front.png",
PRET .. "/graphics/pokemon/treecko/shiny.pal", file("front_anim_shiny/277.rgba") or "")
eq(sbad, 0, "Treecko shiny sheet is pixel-exact vs shiny.pal")
local bbad = pixelExact(PRET .. "/graphics/pokemon/treecko/back.png",
PRET .. "/graphics/pokemon/treecko/normal.pal", file("back/277.rgba") or "")
eq(bbad, 0, "Treecko back is pixel-exact vs back.png")
local dbad = pixelExact(PRET .. "/graphics/pokemon/deoxys/anim_front.png",
PRET .. "/graphics/pokemon/deoxys/normal.pal", deoxysAnim or "")
eq(dbad, 0, "Deoxys sheet is pixel-exact vs anim_front.png")
local ebad = pixelExact(PRET .. "/graphics/pokemon/egg/anim_front.png",
PRET .. "/graphics/pokemon/egg/normal.pal", file("front/412.rgba") or "", 64 * 64)
eq(ebad, 0, "egg front pic is frame 0 of pret egg anim_front.png")
else
print("emerald_pokemon_pack_test: pixel checks skipped (python3 + PIL or pret graphics missing)")
end
T.finish("emerald_pokemon_pack_test")