Files
gen1recomp/tests/game3_stitchimp_alt_layouts_test.lua

275 lines
9.4 KiB
Lua

#!/usr/bin/env luajit
-- pokefirered/src/scrcmd.c:711
package.path = "./?.lua;./?/init.lua;" .. package.path
local failed = 0
local function check(cond, msg)
if cond then
print("[ok] " .. msg)
else
failed = failed + 1
print("[FAIL] " .. msg)
end
end
local function eq(a, b, msg)
check(a == b, string.format("%s (%s == %s)", msg, tostring(a), tostring(b)))
end
local okAlt, AltLayouts = pcall(require, "src.import.gba.alt_layouts")
if not okAlt then
AltLayouts = {
ROWS = {},
KEY_PREFIX = "alt_",
key = function(id) return "alt_" .. tostring(id) end,
}
end
local Versions = require("src.import.gba.versions")
local MapCatalog = require("src.import.gba.map_catalog")
local NativePack = require("src.import.gba.native_pack")
MapCatalog.rebuildIndex()
-- pokefirered/include/constants/layouts.h:253,267,268,308
local EXPECT = {
[264] = {
map = "SevenIsland_House_Room1", w = 11, h = 9,
diffCells = 17,
-- pokefirered/include/constants/metatile_behaviors.h:72
behavior = 0x60, baseCount = 0, altCount = 1,
},
[278] = {
map = "SeafoamIslands_B3F", w = 38, h = 24,
diffCells = 92,
-- pokefirered/include/constants/metatile_behaviors.h:62-65
behavior = "current", baseCount = 56, altCount = 22,
},
[279] = {
map = "SeafoamIslands_B4F", w = 38, h = 24,
diffCells = 176,
behavior = "current", baseCount = 173, altCount = 15,
},
[319] = {
map = "ThreeIsland_DunsparceTunnel", w = 30, h = 7,
diffCells = 152,
-- pokefirered/include/constants/metatile_behaviors.h:8
behavior = 0x08, baseCount = 164, altCount = 86,
},
}
local ORDER = { 264, 278, 279, 319 }
print("[test] 1. the alt-layout contract")
check(okAlt, "src/import/gba/alt_layouts.lua is loadable")
eq(#AltLayouts.ROWS, 4, "four gMapLayouts entries are seeded")
for _, row in ipairs(AltLayouts.ROWS) do
local want = EXPECT[row.id]
check(want ~= nil, "layout " .. tostring(row.id) .. " is one of the four pret ids")
if want then
eq(row.map, want.map, "layout " .. row.id .. " owner map")
eq(row.width .. "x" .. row.height, want.w .. "x" .. want.h,
"layout " .. row.id .. " pret dimensions")
end
end
eq(AltLayouts.key(278), "alt_278", "cache key is alt_<layoutId>")
check(Versions.G_MAP_LAYOUTS ~= nil, "Versions.G_MAP_LAYOUTS is pinned")
print("[test] 2. CacheContract rejects a cache without them")
local Contract = require("src.import.CacheContract")
local required = {}
for _, path in ipairs(Contract.VERSION_REQUIRED_FILES_OVERRIDE.firered or {}) do
required[path] = true
end
for _, id in ipairs(ORDER) do
check(required["data/generated/gba/native/layouts/" .. AltLayouts.key(id) .. ".mid"],
"firered required list carries " .. AltLayouts.key(id) .. ".mid")
end
print("[test] 3. AltLayouts.build over the real ROM")
local ROM = "../pokefirered/pokefirered.gba"
local romFile = okAlt and io.open(ROM, "rb")
if not okAlt then
check(false, "AltLayouts.build reads gMapLayouts")
elseif not romFile then
print("[skip] " .. ROM .. " not present; ROM section skipped")
else
romFile:close()
local FileIO = require("src.import.gba.file_io")
local imports = FileIO.makeImports(
ROM, "41cb23d8dccc8ebd7c649cd8fbb58eeace6e2fdc", "firered")
local rom = assert(require("src.import.gba.rom").open(imports, "firered"))
local version = assert(Versions.lookup(rom.md5))
local MapTree = require("src.import.gba.map_tree")
local Maps = require("src.import.gba.maps")
local Extract = require("src.import.gba.extract_island1")
local census = assert(MapTree.walk(rom, version))
local order, byEngine = MapCatalog.allOrder(census)
MapCatalog.registerOrder(rom, version, order, byEngine)
local grids, borders = {}, {}
for _, id in ipairs(ORDER) do
local ownerId = MapCatalog.resolve(EXPECT[id].map)
local spec = Versions.MAPS[ownerId]
local layoutSpec = spec and version.layouts and version.layouts[spec.layout]
if layoutSpec then
local grid = Maps.loadGrid(rom, layoutSpec)
grid.map_id = ownerId
grid.kind = spec.kind
grid.pair = spec.pair
grid.environment = spec.environment
grids[ownerId] = Extract.padEven(grid)
end
end
local added = AltLayouts.build(rom, version, grids, borders, Extract.padEven)
eq(#added, 4, "build seeded four alt layouts")
for _, id in ipairs(ORDER) do
local want = EXPECT[id]
local key = AltLayouts.key(id)
local alt = grids[key]
local ownerId = MapCatalog.resolve(want.map)
local owner = grids[ownerId]
if not (alt and owner) then
check(false, key .. " built")
else
eq(alt.pair, owner.pair, key .. " renders in " .. tostring(ownerId) .. "'s pair")
eq(alt.width .. "x" .. alt.height, owner.width .. "x" .. owner.height,
key .. " matches the owner's padded size")
eq(alt.altOwner, ownerId, key .. " records its owner")
local tw = (alt.padded_from and alt.padded_from.width) or alt.width
local th = (alt.padded_from and alt.padded_from.height) or alt.height
eq(tw .. "x" .. th, want.w .. "x" .. want.h, key .. " true size is pret's")
local diff = 0
for y = 0, th - 1 do
for x = 0, tw - 1 do
local i = y * alt.width + x + 1
local a, b = alt.cells[i], owner.cells[i]
if a.mid ~= b.mid or a.coll ~= b.coll or a.elev ~= b.elev then
diff = diff + 1
end
end
end
eq(diff, want.diffCells, key .. " differs from the base layout in cells")
check(borders[key] ~= nil and #(borders[key].mids or {}) > 0,
key .. " carries its own border block")
end
end
imports:_close()
end
print("[test] 4. the baked cache")
local Cache = require("tests.game3_cache")
local root = Cache.root("native/manifest.lua")
if not root then
print("[skip] " .. tostring(Cache.reason))
else
local function readFile(rel)
local f = io.open(root .. "/" .. rel, "rb")
if not f then return nil end
local d = f:read("*a")
f:close()
return d
end
local function loadLua(rel)
local src = readFile(rel)
if not src then return nil end
local chunk = load(src, "@" .. rel, "t", {})
return chunk and chunk() or nil
end
if not readFile("native/layouts/" .. AltLayouts.key(ORDER[1]) .. ".mid") then
print("[skip] cache at " .. root ..
" predates alt-layout baking; reimport it before this test can check it")
else
local manifest = loadLua("native/manifest.lua") or {}
local midIndex = loadLua("mid_index.lua") or {}
local strays = {}
for mapId in pairs(manifest.layouts or {}) do
if mapId:sub(1, #AltLayouts.KEY_PREFIX) == AltLayouts.KEY_PREFIX then
strays[#strays + 1] = mapId
end
end
eq(#strays, 0, "alt layouts stay out of manifest.layouts")
local function behaviorOf(pair, mid)
local row = midIndex[pair] and midIndex[pair][mid]
return row and row.behavior
end
local function countBehavior(decoded, pair, want)
local tw = decoded.trueWidth or decoded.width
local th = decoded.trueHeight or decoded.height
local n = 0
for y = 0, th - 1 do
for x = 0, tw - 1 do
local b = behaviorOf(pair, decoded.cells[y * decoded.width + x + 1].mid)
if b and ((want == "current" and b >= 0x50 and b <= 0x53) or b == want) then
n = n + 1
end
end
end
return n
end
local pairAtlas = {}
local function atlasMids(pair)
if pairAtlas[pair] == nil then
local blob = readFile("native/" .. pair .. "/mids.idx")
local idx = blob and NativePack.decodeIdx(blob)
local set = {}
for _, mid in ipairs(idx and idx.midIds or {}) do set[mid] = true end
pairAtlas[pair] = set
end
return pairAtlas[pair]
end
for _, id in ipairs(ORDER) do
local want = EXPECT[id]
local key = AltLayouts.key(id)
local ownerId = MapCatalog.resolve(want.map)
local info = (manifest.layouts or {})[ownerId]
local altBlob = readFile("native/layouts/" .. key .. ".mid")
local baseBlob = readFile("native/layouts/" .. ownerId .. ".mid")
local alt = altBlob and NativePack.decodeMidLayout(altBlob)
local base = baseBlob and NativePack.decodeMidLayout(baseBlob)
if not (alt and base and info) then
check(false, key .. " and " .. tostring(ownerId) .. " are both baked")
else
eq(alt.width .. "x" .. alt.height, info.width .. "x" .. info.height,
key .. " passes the size gate in ops_a set_map_layout")
eq((alt.trueWidth or 0) .. "x" .. (alt.trueHeight or 0), want.w .. "x" .. want.h,
key .. " keeps pret's true size")
local missing = 0
local atlas = atlasMids(info.pair)
for _, cell in ipairs(alt.cells) do
if not atlas[cell.mid] then missing = missing + 1 end
end
eq(missing, 0, key .. " mids are all in the " .. info.pair .. " atlas")
local swapped = 0
for i = 1, alt.width * alt.height do
local a, b = alt.cells[i], base.cells[i]
if a.mid ~= b.mid or a.coll ~= b.coll or a.elev ~= b.elev then
swapped = swapped + 1
end
end
check(swapped > 0, key .. " swaps " .. swapped .. " cells into " .. ownerId)
eq(countBehavior(base, info.pair, want.behavior), want.baseCount,
ownerId .. " base behavior cells")
eq(countBehavior(alt, info.pair, want.behavior), want.altCount,
key .. " behavior cells")
end
end
end
end
if failed > 0 then
print("[test] FAILED " .. failed)
os.exit(1)
end
print("[test] all passed")