mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
200 lines
7.7 KiB
Lua
200 lines
7.7 KiB
Lua
#!/usr/bin/env luajit
|
|
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 Plaza = require("src.core.game3.link.union_plaza_map")
|
|
|
|
print("[test] contract")
|
|
eq(Plaza.MAP_ID, "FR_UNION_ROOM_PLAZA", "map id")
|
|
eq(Plaza.CAP, 40, "cap")
|
|
eq(#Plaza.CELLS, 41, "41 cells on the grid")
|
|
eq(#Plaza.ROLES, 25, "role grid is 25 rows")
|
|
local badRow = nil
|
|
for y, row in ipairs(Plaza.ROLES) do
|
|
if #row ~= 25 then badRow = y end
|
|
for i = 1, #row do
|
|
if not Plaza.SOURCE[row:sub(i, i)] then badRow = y end
|
|
end
|
|
end
|
|
check(badRow == nil, "every role row is 25 wide with a known role: " .. tostring(badRow))
|
|
|
|
print("[test] slot table")
|
|
local seen, unique = {}, 0
|
|
for slot = 1, Plaza.CAP do
|
|
local x, y, facing = Plaza.cellFor(slot)
|
|
check(x ~= nil and y ~= nil and facing ~= nil, "slot " .. slot .. " has a cell")
|
|
local k = tostring(x) .. "," .. tostring(y)
|
|
if not seen[k] then unique = unique + 1 end
|
|
seen[k] = true
|
|
eq(Plaza.slotAt(x, y), slot, "slotAt inverts slot " .. slot)
|
|
end
|
|
eq(unique, 40, "40 unique cells")
|
|
check(Plaza.cellFor(0) == nil and Plaza.cellFor(41) == nil, "slots outside 1..40 have no cell")
|
|
check(Plaza.slotAt(12, 24) == nil and Plaza.slotAt(0, 0) == nil, "slotAt is nil off the cell table")
|
|
|
|
local ex, ey = Plaza.entry()
|
|
local function dist(slot)
|
|
local x, y = Plaza.cellFor(slot)
|
|
return math.abs(x - ex) + math.abs(y - (ey - 1))
|
|
end
|
|
local sorted = true
|
|
for slot = 2, Plaza.CAP do
|
|
if dist(slot) < dist(slot - 1) then sorted = false end
|
|
end
|
|
check(sorted, "fill order moves outward from the entrance")
|
|
local x1, y1 = Plaza.cellFor(1)
|
|
eq(x1, 12, "slot 1 sits on the entrance column")
|
|
eq(y1, 17, "slot 1 is the first cell up from the door")
|
|
local sx, sy = Plaza.cellFor(40)
|
|
check(sy == 5, "slot 40 is on the far row")
|
|
|
|
print("[test] geometry")
|
|
eq(ex, 12, "entry x")
|
|
eq(ey, 24, "entry y")
|
|
eq(Plaza.roleAt(ex, ey), "X", "entry is the exit mat")
|
|
local attX, attY = 3, 2
|
|
local blocked = {}
|
|
local function key(x, y) return y * 64 + x end
|
|
for _, c in ipairs(Plaza.CELLS) do blocked[key(c.x, c.y)] = true end
|
|
blocked[key(attX, attY)] = true
|
|
local function walkable(x, y)
|
|
local r = Plaza.roleAt(x, y)
|
|
return r ~= nil and not Plaza.BLOCKED[r] and not blocked[key(x, y)]
|
|
end
|
|
local reach = { [key(ex, ey)] = true }
|
|
local queue = { { ex, ey } }
|
|
local head = 1
|
|
while queue[head] do
|
|
local p = queue[head]
|
|
head = head + 1
|
|
for _, d in ipairs({ { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } }) do
|
|
local nx, ny = p[1] + d[1], p[2] + d[2]
|
|
if walkable(nx, ny) and not reach[key(nx, ny)] then
|
|
reach[key(nx, ny)] = true
|
|
queue[#queue + 1] = { nx, ny }
|
|
end
|
|
end
|
|
end
|
|
local allSides, onBad = true, {}
|
|
for _, c in ipairs(Plaza.CELLS) do
|
|
for _, d in ipairs({ { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } }) do
|
|
if not reach[key(c.x + d[1], c.y + d[2])] then
|
|
allSides = false
|
|
print((" cell %d,%d side %d,%d unreachable"):format(c.x, c.y, d[1], d[2]))
|
|
end
|
|
end
|
|
local r = Plaza.roleAt(c.x, c.y)
|
|
if Plaza.BLOCKED[r] or c.y <= 3 or c.y >= 23 or (c.x == ex and c.y >= 19) then
|
|
onBad[#onBad + 1] = c.x .. "," .. c.y
|
|
end
|
|
end
|
|
check(allSides, "every cell is reachable from all 4 sides with the room full")
|
|
eq(#onBad, 0, "no cell on a wall, the counter rows or the exit lane")
|
|
check(reach[key(2, 2)] == true, "the trading board spot below the counter is reachable")
|
|
check(reach[key(3, 3)] == true, "the attendant is reachable from the front")
|
|
|
|
print("[test] a cache that cannot build the plaza is a hard error")
|
|
eq(Plaza.shouldUse, nil, "no switch back to the cart room")
|
|
local function fails(game, want, msg)
|
|
local ok, err = pcall(Plaza.ensure, game)
|
|
check(not ok and tostring(err):find(want, 1, true) ~= nil, msg .. ": " .. tostring(err))
|
|
end
|
|
local realMap = package.loaded["src.core.game3.map"]
|
|
package.loaded["src.core.game3.map"] = { ensureMidLayout = function() end }
|
|
fails({ data = {} }, "no map table", "no map table")
|
|
fails({ data = { maps = {} } }, "FR_UNION_ROOM is missing from the cache", "no FR_UNION_ROOM")
|
|
fails({ data = { maps = { FR_UNION_ROOM = { warps = {} } } } }, "FR_UNION_ROOM has no layout",
|
|
"FR_UNION_ROOM without a layout")
|
|
local tiny = { width = 4, height = 4, cellAt = function() return { mid = 1, coll = 0, elev = 0 } end }
|
|
fails({ data = { maps = { FR_UNION_ROOM = { warps = {}, midLayout = tiny } } } }, "layout has no cell",
|
|
"a truncated FR_UNION_ROOM layout")
|
|
local Adapters = require("src.core.game3.scripting.adapters")
|
|
local broken = Adapters.host(nil, { data = { maps = { FR_UNION_ROOM = { warps = {} } } } }, nil)
|
|
local realVersions = package.loaded["src.import.gba.versions"]
|
|
package.loaded["src.import.gba.versions"] = {
|
|
frMapFor = function() return "FR_UNION_ROOM" end,
|
|
seviiMapFor = function() return nil end,
|
|
}
|
|
local okWarp, warpErr = pcall(broken.warp, 1, 1, 0xFF, 7, 11, function() end, "warpspinenter")
|
|
package.loaded["src.import.gba.versions"] = realVersions
|
|
package.loaded["src.core.game3.map"] = realMap
|
|
check(not okWarp and tostring(warpErr):find("FR_UNION_ROOM has no layout", 1, true) ~= nil,
|
|
"the Union Room warp errors instead of landing in the cart room: " .. tostring(warpErr))
|
|
|
|
print("[test] built from the imported cache")
|
|
local Cache = require("tests.game3_cache")
|
|
local root = Cache.mount("meta.json", { native = true })
|
|
if not root then
|
|
print("[skip] plaza cache build: " .. tostring(Cache.reason))
|
|
else
|
|
local Dataset = require("src.core.game3.dataset")
|
|
local maps = Dataset.buildMaps()
|
|
local src = maps.FR_UNION_ROOM
|
|
check(src ~= nil, "FR_UNION_ROOM is in the cache")
|
|
Dataset.attachMidLayouts({ FR_UNION_ROOM = src }, Cache.cache())
|
|
local game = { data = { maps = maps } }
|
|
local def = Plaza.ensure(game)
|
|
check(def ~= nil and maps[Plaza.MAP_ID] == def, "ensure registers the plaza def")
|
|
eq(Plaza.ensure(game), def, "ensure is idempotent")
|
|
if def and src and src.midLayout then
|
|
local L = def.midLayout
|
|
eq(L.width, 25, "layout width")
|
|
eq(L.height, 25, "layout height")
|
|
eq(def.width, 25, "def width")
|
|
eq(def.height, 25, "def height")
|
|
eq(def.pair, src.pair, "same tileset pair as the cart room")
|
|
local srcMids = {}
|
|
local SL = src.midLayout
|
|
for y = 0, (SL.trueHeight or SL.height) - 1 do
|
|
for x = 0, (SL.trueWidth or SL.width) - 1 do
|
|
srcMids[SL:midAt(x, y)] = true
|
|
end
|
|
end
|
|
local foreign, collMismatch = 0, 0
|
|
for y = 0, 24 do
|
|
for x = 0, 24 do
|
|
if not srcMids[L:midAt(x, y)] then foreign = foreign + 1 end
|
|
local c = L:collAt(x, y)
|
|
local r = Plaza.roleAt(x, y)
|
|
if (c ~= 0) ~= (Plaza.BLOCKED[r] == true or r == "X") then
|
|
collMismatch = collMismatch + 1
|
|
end
|
|
end
|
|
end
|
|
eq(foreign, 0, "every metatile comes from the Union Room layout")
|
|
eq(collMismatch, 0, "blocked roles match the cart collision")
|
|
for slot = 1, Plaza.CAP do
|
|
local x, y = Plaza.cellFor(slot)
|
|
if L:collAt(x, y) ~= 0 then check(false, "slot " .. slot .. " cell is floor") end
|
|
end
|
|
eq(#def.warps, 1, "one warp")
|
|
local w, sw = def.warps[1], src.warps[1]
|
|
eq(w.x, 12, "warp x")
|
|
eq(w.y, 24, "warp y")
|
|
eq(w.mapGroup, sw.mapGroup, "warp group matches FR_UNION_ROOM")
|
|
eq(w.mapNum, sw.mapNum, "warp num matches FR_UNION_ROOM")
|
|
eq(w.destWarp, sw.destWarp, "warp dest matches FR_UNION_ROOM")
|
|
eq(w.destMap, sw.destMap, "warp destMap matches FR_UNION_ROOM")
|
|
eq(L:collAt(12, 24), SL:collAt(7, 11), "exit tile keeps the cart warp collision")
|
|
eq(def.music, src.music, "music copied")
|
|
end
|
|
end
|
|
|
|
if failed > 0 then
|
|
print(("[FAIL] %d check(s) failed"):format(failed))
|
|
os.exit(1)
|
|
end
|
|
print("[PASS] union plaza map")
|