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

160 lines
7.1 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_field_assets_test: skipped (no ROM at " .. ROM_PATH .. ")")
os.exit(0)
end
local raw = f:read("*a")
f:close()
if raw:sub(0xAD, 0xB0) ~= "BPEE" then
print("emerald_field_assets_test: skipped (not an Emerald ROM)")
os.exit(0)
end
local rom = { size = #raw }
function rom:get(o) return raw:byte(o + 1) end
function rom:u16(o) local a, b = raw:byte(o + 1, o + 2); return a + b * 256 end
function rom:u32(o) local a, b, c, d = raw:byte(o + 1, o + 4); return a + b * 256 + c * 65536 + d * 16777216 end
function rom:readString(o, n) return raw:sub(o + 1, o + n) end
function rom:readBytes(o, n) return { raw:byte(o + 1, o + n) } end
function rom:ptrOffset(p) if p >= 0x08000000 and p < 0x0A000000 then return p - 0x08000000 end end
function rom:clearCache() end
local files = {}
local cache = {}
function cache:write(rel, body) files[rel] = body; return true end
function cache:read(rel) return files[rel] end
function cache:exists(rel) return files[rel] ~= nil end
local ROOT = "gba"
local Versions = require("src.import.gba.versions")
Versions.select("emerald")
local function manifest(rel)
local body = files[ROOT .. "/" .. rel]
if not body then return nil end
return assert(load(body, "=" .. rel, "t", {}))()
end
local function fnv(s)
local h = 2166136261
local M = 4294967296
for i = 1, #s do
h = bit.bxor(h, s:byte(i)) % M
h = (bit.lshift(h, 24) % M + h * 403) % M
end
return string.format("%08x", h)
end
local mods = {
"src.import.gba.field_effect_extract", "src.import.gba.door_anim_extract", "src.import.gba.weather_extract",
"src.import.gba.cave_transition_extract", "src.import.gba.rse.berry_tree_gfx_extract",
"src.import.gba.rse.rotating_gate_gfx_extract",
}
for _, m in ipairs(mods) do
local mod = require(m)
local ok, err = pcall(mod.run, rom, cache, { cacheRoot = ROOT })
check(ok, m .. " runs: " .. tostring(err))
if mod.ready then check(mod.ready(cache, ROOT), m .. " ready after run") end
for _, rel in ipairs(mod.REQUIRED or {}) do
check(files[ROOT .. "/" .. rel] ~= nil, m .. " wrote " .. rel)
end
end
local fx = manifest("field_effects/objects.lua")
eq(fx.count, 37, "37 FLDEFFOBJ templates walked")
eq(#fx.extras, 13, "13 named extra templates")
local byName, sheets, colored = {}, 0, 0
for _, list in ipairs({ fx.objects, fx.extras }) do
for _, e in ipairs(list) do
byName[e.name] = e
if e.file then sheets = sheets + 1 end
if e.rgba then colored = colored + 1 end
end
end
eq(sheets, 49, "every template with images has an index sheet")
eq(colored, 48, "every sheet but the unused-tag Deoxys fragment has an rgba bake")
eq(fx.objects[1].name, "shadow_small", "FLDEFFOBJ_SHADOW_S first")
eq(fx.objects[37].name, "rayquaza", "FLDEFFOBJ_RAYQUAZA last")
for name, dims in pairs({
tall_grass = "16x16x5", surf_blob = "32x32x3", long_grass = "16x16x4", sand_footprints = "16x16x2",
deep_sand_footprints = "16x16x2", bike_tire_tracks = "16x16x4", ash = "16x16x5", bubbles = "16x32x8",
sparkle = "16x16x6", tree_disguise = "16x32x7", bird = "32x32x1", shadow_extra_large = "64x32x1",
arrow = "16x16x8", sand_pile = "16x8x3", exclamation_question_mark = "16x16x2", heart_icon = "16x16x1",
}) do
local e = byName[name]
eq(e and (e.fw .. "x" .. e.fh .. "x" .. e.frames), dims, name .. " frame layout")
end
eq(byName.tall_grass.palette, "gFieldEffectObjectPalette1", "tall grass uses FLDEFF_PAL_TAG_GENERAL_1")
eq(byName.splash.palette, "gFieldEffectObjectPalette0", "splash uses FLDEFF_PAL_TAG_GENERAL_0")
eq(byName.surf_blob.palette, "gObjectEventPal_Brendan", "surf blob uses the player slot")
eq(byName.record_mix_lights.palette, "sRecordMixLights_Pal", "record mix lights own palette")
eq(byName.reflection_distortion.file, nil, "reflection distortion has no images")
eq(#byName.sparkle.anims[1] > 6, true, "sparkle anim read from ROM")
eq(#files[ROOT .. "/field_effects/field_move_streaks_outdoors.rgba"], 256 * 80 * 4, "outdoor streaks baked")
local doors = manifest("doors/manifest.lua")
eq(doors.family, "rse", "door manifest is rse")
eq(doors.count, 53, "53 door rows before the terminator")
eq(doors.by_mid[0x248].tile, "littleroot", "METATILE_Petalburg_Door_Littleroot")
eq(doors.by_mid[0x2AD].size_type, 2, "multi corridor door is size 2")
eq(doors.doors.battle_tower_multi_corridor.width, 32, "big door frame is 32 wide")
eq(doors.doors.littleroot.height, 96, "three 16x32 frames")
local pair = doors.pairs["gTileset_General|gTileset_Petalburg"]
check(pair and pair.doors[0x248] and pair.doors[0x248].file == "littleroot__petalburg.rgba",
"Littleroot door keyed by its tileset pair")
eq(#doors.by_mid[0x21].pairs, 30, "general door present in every General pair")
eq(doors.by_mid[0x3B0].pairs[1], nil, "unused frontier door matches no tileset")
eq(#doors.anim.open, 4, "sDoorOpenAnimFrames rows")
eq(doors.by_mid[0x61].sound, "sliding", "Pokemon Center door slides")
eq(doors.by_mid[0x21B].sound, "arena", "Battle Arena door sound")
local w = manifest("weather/manifest.lua")
eq(w.blobs.rain.w .. "x" .. w.blobs.rain.h, "16x192", "rain sheet")
eq(w.blobs.ash.w .. "x" .. w.blobs.ash.h, "64x128", "ash sheet")
eq(w.blobs.sandstorm.w .. "x" .. w.blobs.sandstorm.h, "64x80", "sandstorm sheet")
eq(w.blobs.cloud.palette, "clouds", "clouds palette")
eq(table.concat(w.cycles.route119, ","), "2,3,5,3", "sWeatherCycleRoute119")
eq(table.concat(w.cycles.route123, ","), "2,2,3,2", "sWeatherCycleRoute123")
eq(#w.color_map_types, 32, "sBasePaletteColorMapTypes")
eq(#files[ROOT .. "/weather/drought_colors.bin"], 49152, "drought color maps")
eq(#files[ROOT .. "/weather/fog_horizontal.rgba"], 64 * 64 * 4, "FR-compatible fog sheet")
eq(#files[ROOT .. "/cave_transition/screen.bin"], 240 * 160, "cave transition screen")
local berries = manifest("berry_trees/manifest.lua")
eq(berries.count, 43, "43 berry trees")
eq(berries.trees[1].sheet, "cheri", "berry 1 is Cheri")
eq(berries.trees[43].sheet, "durin", "Enigma reuses the Durin tree")
eq(#berries.trees[1].frames, 9, "dirt, 2 sprout, 6 tree frames")
eq(berries.trees[1].height, 240, "frames stacked")
eq(#berries.trees[1].stages, 5, "five growth stages")
eq(berries.trees[2].stages[3].paletteSlot, 2, "Chesto stage 3 palette slot")
local gates = manifest("rotating_gates/manifest.lua")
eq(gates.count, 8, "8 gate shapes")
eq(gates.sheets[1].w, 32, "L1 is 32x32")
eq(gates.sheets[2].w, 64, "L2 is 64x64")
eq(#gates.puzzles.fortree, 8, "Fortree gates")
eq(gates.puzzles.trick_house[1].x, 14, "Trick House first gate x")
for rel, hash in pairs({
["field_effects/tall_grass.idx"] = "92fa72d7",
["field_effects/surf_blob.idx"] = "43843211",
["doors/littleroot.idx"] = "d73a2b9b",
["weather/rain.idx"] = "0f4fba30",
["berry_trees/pecha.idx"] = "9c3eca38",
["rotating_gates/gate_5.idx"] = "5a20b264",
}) do
local body = files[ROOT .. "/" .. rel]
eq(body and fnv(body), hash, rel .. " matches the pixel-verified sheet")
end
Versions.select("firered")
T.finish("emerald_field_assets_test")