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

182 lines
7.3 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
local PRET = os.getenv("POKEPORT_POKEEMERALD") or "../pokeemerald"
local ROM_PATH = os.getenv("POKEPORT_EMERALD_ROM") or (PRET .. "/pokeemerald.gba")
local f = io.open(ROM_PATH, "rb")
if not f then
print("emerald_maps_test: skipped (no ROM at " .. ROM_PATH .. ")")
os.exit(0)
end
local data = f:read("*a")
f:close()
local rom = { md5 = "f3ae088181bf583e55daf962a92bb46f4f1d07b7" }
function rom:get(o) return data:byte(o + 1) end
function rom:u16(o) local a, b = data:byte(o + 1, o + 2); return a + b * 256 end
function rom:u32(o)
local a, b, c, d = data:byte(o + 1, o + 4)
return a + b * 256 + c * 65536 + d * 16777216
end
function rom:readString(o, n) return data:sub(o + 1, o + n) end
function rom:readBytes(o, n)
local t = {}
for i = 1, n do t[i] = data:byte(o + i) end
return t
end
function rom:ptrOffset(p)
if not p or p < 0x08000000 or p >= 0x0A000000 then return nil end
return p - 0x08000000
end
local function readFile(path)
local h = io.open(path, "rb")
if not h then return nil end
local s = h:read("*a")
h:close()
return s
end
local GameVersion = require("src.core.GameVersion")
local before = GameVersion.get()
GameVersion.set("emerald")
local Versions = require("src.import.gba.versions")
Versions.select("emerald")
local Family = require("src.import.gba.family")
local MapTree = require("src.import.gba.map_tree")
local MapTreeExtract = require("src.import.gba.map_tree_extract")
local MapCatalog = require("src.import.gba.map_catalog")
local Tileset = require("src.import.gba.tileset")
local Metatile = require("src.import.gba.metatile")
local Lz77 = require("src.import.gba.lz77")
local F = Family.active()
eq(F.name, "rse", "emerald session uses the rse family")
local census = assert(MapTree.walk(rom, Versions.lookup(rom.md5)))
eq(census.map_count, 518, "census has every Emerald map")
eq(#census.groups, 34, "34 map groups")
eq(census.tileset_count, 73, "73 tileset structs referenced by layouts")
local dirs, kinds, objs, clones = {}, {}, 0, 0
local hiddenFlagsOk, berry = true, 0
for _, m in ipairs(census.maps) do
for _, c in ipairs(m.connections) do dirs[c.dir] = (dirs[c.dir] or 0) + 1 end
for _, b in ipairs(m.events and m.events.bgEvents or {}) do
kinds[b.kind] = (kinds[b.kind] or 0) + 1
if b.type == "hidden_item" and (b.flag < 0x1F4 or b.quantity ~= nil) then hiddenFlagsOk = false end
end
for _, o in ipairs(m.events and m.events.objects or {}) do
objs = objs + 1
if o.cloneTarget then clones = clones + 1 end
if o.berryTreeId then berry = berry + 1 end
end
end
eq(dirs.dive, 7, "7 dive connections")
eq(dirs.emerge, 7, "7 emerge connections")
eq(dirs.north, 27, "27 north connections")
eq(dirs.east, 40, "40 east connections")
eq(kinds[7], 112, "112 hidden items")
eq(kinds[8], 75, "75 secret base bg events")
check(hiddenFlagsOk, "hidden item flags start at 0x1F4 with no quantity")
eq(objs, 2941, "2941 object events")
eq(clones, 0, "no clone objects")
check(berry > 0, "berry trees carry berryTreeId")
local petal = census.maps[1]
eq(petal.pretName, "PetalburgCity", "0:0 is Petalburg")
eq(petal.layout.width, 30, "Petalburg 30 wide")
eq(petal.layout.borderWidth, 2, "border is always 2x2")
local byName = {}
for _, ts in pairs(census.tilesets) do
local name = F:tilesetName(ts.structOff)
check(name ~= nil, "tileset struct " .. ts.id .. " has a pret name")
if name then byName[name] = ts end
end
eq(byName.general.mid_count, 512, "General has 512 metatiles")
eq(byName.general.attr_bytes, 1024, "General attributes are u16")
check(byName.general.compressed, "General tiles are compressed")
check(not byName.secret_base_brown_cave.compressed, "SecretBaseBrownCave tiles are raw")
local compared = 0
for name, ts in pairs(byName) do
local dir = PRET .. "/data/tilesets/" .. (ts.secondary and "secondary/" or "primary/") .. name
local mt = readFile(dir .. "/metatiles.bin")
local at = readFile(dir .. "/metatile_attributes.bin")
if mt and at then
compared = compared + 1
eq(rom:readString(rom:ptrOffset(ts.metatilesPtr), ts.metatile_bytes), mt, name .. " metatiles.bin byte exact")
eq(rom:readString(rom:ptrOffset(ts.attributesPtr), ts.attr_bytes), at, name .. " metatile_attributes.bin byte exact")
end
end
check(compared >= 60, "compared metatile blobs against pret for " .. compared .. " tilesets")
local function pngSize(path)
local s = readFile(path)
if not s then return nil end
local function be32(o) local a, b, c, d = s:byte(o, o + 3); return ((a * 256 + b) * 256 + c) * 256 + d end
return be32(17), be32(21)
end
local tileChecks = 0
for name, ts in pairs(byName) do
local w, h = pngSize(PRET .. "/data/tilesets/" .. (ts.secondary and "secondary/" or "primary/") .. name .. "/tiles.png")
if w then
local tilesOff = rom:ptrOffset(ts.tilesPtr)
local n
if ts.compressed then
n = #Lz77.decompressString(rom, tilesOff)
else
n = F:uncompressedTileBytes(rom, tilesOff, rom:ptrOffset(ts.palettesPtr), ts.secondary)
end
local sheet = w * h / 2
check(n <= sheet and n > sheet - (w / 8) * 32, name .. " tile bytes fill pret tiles.png up to its last row (" .. n .. "/" .. sheet .. ")")
tileChecks = tileChecks + 1
end
end
check(tileChecks >= 60, "tile sheet sizes checked for " .. tileChecks .. " tilesets")
MapCatalog.rebuildIndex()
local order, byEngine = MapCatalog.allOrder(census)
eq(#order, 518, "catalog orders every map")
eq(order[1], "EM_PETALBURG_CITY", "catalog engine id for 0:0")
local registered = MapCatalog.registerOrder(rom, Versions.lookup(rom.md5), order, byEngine)
check(#registered >= 517, "registered " .. #registered .. " maps with a tileset pair")
eq(Versions.MAPS.EM_LITTLEROOT_TOWN.pair, "general__petalburg", "Littleroot uses General + Petalburg")
check((Versions.TILESETS.secret_base_brown_cave.tiles_bytes or 0) > 0, "uncompressed tileset has tile bytes")
do
local bundle = assert(Tileset.loadPair(rom, Versions.lookup(rom.md5), "general__petalburg"))
eq(bundle.primaryTiles.count, 512, "General tiles decode to 512")
eq(bundle.primaryAttr.count, 512, "General attrs count 512")
-- pokeemerald/include/constants/metatile_labels.h:240
local beh = Tileset.behaviorOf(bundle, 0x00D)
eq(beh, 0x02, "General TallGrass is MB_TALL_GRASS")
local idx = Metatile.compositeIndexedBottom(bundle, 0x00D)
local nonzero = 0
for i = 1, 256 do if idx[i] ~= 0 then nonzero = nonzero + 1 end end
check(nonzero > 0, "tall grass composites to pixels")
end
local files = {}
local cache = {
write = function(_, rel, bytes) files[rel] = bytes end,
exists = function(_, rel) return files[rel] ~= nil end,
}
local out = MapTreeExtract.run(rom, cache, { cacheRoot = "gba", strict = true })
eq(out, true, "strict map tree extract runs")
check(files["gba/map_tree/census.json"] ~= nil, "census.json written")
check(MapTreeExtract.ready(cache, "gba"), "ready after run")
local lr = files["gba/map_tree/maps/0_9/header.json"]
check(lr and lr:find('"allowCycling":1', 1, true) ~= nil, "Littleroot header has allowCycling")
check(lr and not lr:find('"floorNum"', 1, true), "RSE header has no floorNum")
local r124 = files["gba/map_tree/maps/0_39/header.json"]
check(r124 and r124:find('"dive"', 1, true) ~= nil, "Route 124 header lists its dive connection")
GameVersion.set(before)
T.finish("emerald_maps_test")