mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
235 lines
8.9 KiB
Lua
235 lines
8.9 KiB
Lua
#!/usr/bin/env luajit
|
|
-- src/event_object_movement.c:4835, src/fieldmap.c:357, src/field_control_avatar.c:987
|
|
|
|
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 function finish()
|
|
if failed > 0 then
|
|
print(string.format("[result] %d CHECK(S) FAILED", failed))
|
|
os.exit(1)
|
|
end
|
|
print("[result] all checks passed")
|
|
os.exit(0)
|
|
end
|
|
|
|
local NativePack = require("src.import.gba.native_pack")
|
|
local Permissions = require("src.world.gen2.Permissions")
|
|
local ScriptColl = require("src.core.game3.scripting.collision")
|
|
|
|
local BLOCKED = ScriptColl.seed("BLOCKED")
|
|
local CAVE = ScriptColl.seed("CAVE")
|
|
|
|
print("[test] 1. the layout collision bit wins over the metatile classify")
|
|
-- src/event_object_movement.c:4835
|
|
local RULE = {
|
|
{ CAVE, 0, nil, CAVE, "a cave floor with collision 0 keeps COLL_CAVE" },
|
|
{ CAVE, 1, nil, BLOCKED, "a cave wall with collision 1 is blocked" },
|
|
{ CAVE, 2, nil, BLOCKED, "collision 2 blocks as well" },
|
|
{ ScriptColl.seed("TALL_GRASS"), 1, nil, BLOCKED, "grass with collision 1 is blocked" },
|
|
{ ScriptColl.seed("TOWN_PATH"), 1, nil, BLOCKED, "path with collision 1 is blocked" },
|
|
{ ScriptColl.seed("ROCK_DECK"), 1, nil, BLOCKED, "a mountain top with collision 1 is blocked" },
|
|
{ BLOCKED, 1, nil, BLOCKED, "an already blocked cell stays blocked" },
|
|
{ ScriptColl.seed("SIGN"), 1, nil, ScriptColl.seed("SIGN"), "a sign keeps COLL_SIGN" },
|
|
{ ScriptColl.seed("PC"), 1, nil, ScriptColl.seed("PC"), "a PC keeps COLL_PC" },
|
|
{ ScriptColl.seed("COUNTER"), 1, nil, ScriptColl.seed("COUNTER"), "a counter keeps its byte" },
|
|
{ ScriptColl.seed("WATER"), 1, nil, ScriptColl.seed("WATER"), "water keeps COLL_WATER" },
|
|
{ ScriptColl.seed("LEDGE", "S"), 1, nil, ScriptColl.seed("LEDGE", "S"),
|
|
"a ledge keeps its hop byte, the jump is checked after the collision" },
|
|
-- src/field_control_avatar.c:987
|
|
{ ScriptColl.seed("DOOR"), 1, true, ScriptColl.seed("DOOR"),
|
|
"a door with a warp event stays enterable" },
|
|
{ ScriptColl.seed("DOOR"), 1, nil, BLOCKED, "a door with no warp event is a wall" },
|
|
{ ScriptColl.seed("DOOR", nil, "indoor"), 1, true, ScriptColl.seed("DOOR", nil, "indoor"),
|
|
"an indoor door mat with a warp event keeps its byte" },
|
|
{ ScriptColl.seed("DOOR", nil, "indoor"), 1, nil, BLOCKED,
|
|
"an indoor door mat with no warp event is a wall" },
|
|
{ CAVE, 1, true, BLOCKED, "a warp event does not open a non-warp metatile" },
|
|
}
|
|
for _, row in ipairs(RULE) do
|
|
eq(NativePack.resolveLayoutColl(row[1], row[2], row[3]), row[4], row[5])
|
|
end
|
|
eq(NativePack.warpKey(7, 2), 2 * NativePack.WARP_KEY_STRIDE + 7, "warpKey packs y then x")
|
|
check(NativePack.WARP_KEY_STRIDE > 1024,
|
|
"the warp key stride clears the widest FRLG layout")
|
|
|
|
print("[test] 2. writeExtract applies the rule to the cells it bakes")
|
|
local writes = {}
|
|
local fakeCache = {
|
|
write = function(_, rel, blob) writes[rel] = blob end,
|
|
read = function() return nil end,
|
|
}
|
|
local BEH_CAVE = 0x08
|
|
local grid = {
|
|
width = 2, height = 2, kind = "indoor", pair = "p", map_id = "M",
|
|
cells = {
|
|
{ mid = 860, coll = 1, elev = 0 }, { mid = 641, coll = 0, elev = 3 },
|
|
{ mid = 641, coll = 0, elev = 3 }, { mid = 641, coll = 1, elev = 3 },
|
|
},
|
|
}
|
|
NativePack.writeExtract(fakeCache, "root", { p = {} }, { M = grid },
|
|
{ M = { width = 1, height = 1, mids = { 0 } } }, {}, nil,
|
|
function() return BEH_CAVE end, ScriptColl.fromCell, nil,
|
|
{ M = { [NativePack.warpKey(1, 0)] = true } })
|
|
local baked = NativePack.decodeMidLayout(writes["root/native/layouts/M.mid"])
|
|
check(baked ~= nil, "writeExtract baked the layout")
|
|
if baked then
|
|
eq(baked.cells[1].coll, BLOCKED, "the collision 1 cave cell baked as blocked")
|
|
eq(baked.cells[2].coll, CAVE, "the collision 0 cave cell kept COLL_CAVE")
|
|
eq(baked.cells[3].coll, CAVE, "the second floor cell kept COLL_CAVE")
|
|
eq(baked.cells[4].coll, BLOCKED, "the collision 1 floor cell baked as blocked")
|
|
end
|
|
|
|
print("[test] 3. the baked Mt Ember braille wall")
|
|
local Cache = require("tests.game3_cache")
|
|
local root = Cache.root("native/manifest.lua", { native = true })
|
|
if not root then
|
|
print("[skip] the baked layout checks: " .. tostring(Cache.reason))
|
|
finish()
|
|
end
|
|
print("[info] FireRed cache at " .. root)
|
|
|
|
local function slurp(path)
|
|
local f = io.open(path, "rb")
|
|
if not f then return nil end
|
|
local d = f:read("*a")
|
|
f:close()
|
|
return d
|
|
end
|
|
|
|
local function layoutOf(mapId)
|
|
local blob = slurp(root .. "/native/layouts/" .. mapId .. ".mid")
|
|
return blob and NativePack.decodeMidLayout(blob)
|
|
end
|
|
|
|
local function collAt(dec, x, y)
|
|
local c = dec.cells[y * dec.width + x + 1]
|
|
return c and c.coll
|
|
end
|
|
|
|
local B5F = "FR_MT_EMBER_RUBY_PATH_B5F"
|
|
local ember = layoutOf(B5F)
|
|
check(ember ~= nil, B5F .. " is baked")
|
|
if ember then
|
|
eq(collAt(ember, 7, 2), BLOCKED, "the braille wall at (7,2) is impassable")
|
|
check(not Permissions.isWalkable(collAt(ember, 7, 2)),
|
|
"Permissions agrees the braille wall is not land")
|
|
local row = 0
|
|
for x = 0, (ember.trueWidth or ember.width) - 1 do
|
|
for y = 0, 2 do
|
|
if Permissions.isWalkable(collAt(ember, x, y)) then row = row + 1 end
|
|
end
|
|
end
|
|
eq(row, 0, "no cell in rows y=0..2 is walkable, map.bin gives them all collision 1")
|
|
eq(collAt(ember, 7, 3), CAVE, "the cave floor the player stands on is untouched")
|
|
eq(collAt(ember, 5, 3), CAVE, "the floor beside it is untouched too")
|
|
check(Permissions.isWalkable(collAt(ember, 7, 3)),
|
|
"the player can still stand in front of the braille wall")
|
|
end
|
|
|
|
print("[test] 4. every baked coll plane agrees with map.bin's collision bit")
|
|
local PRET = "../pokefirered"
|
|
local layoutsJson = slurp(PRET .. "/data/layouts/layouts.json")
|
|
if not layoutsJson then
|
|
print("[skip] the pret sweep: " .. PRET .. "/data/layouts/layouts.json is not present")
|
|
finish()
|
|
end
|
|
|
|
local warps = {}
|
|
do
|
|
local src = slurp(root .. "/warps.lua")
|
|
local chunk = src and (loadstring or load)(src)
|
|
local ok, t = pcall(chunk or function() end)
|
|
if ok and type(t) == "table" then warps = t end
|
|
end
|
|
check(next(warps) ~= nil, "warps.lua loaded, the door exception needs it")
|
|
|
|
local MapCatalog = require("src.import.gba.map_catalog")
|
|
MapCatalog.rebuildIndex()
|
|
|
|
local SAMPLE = {
|
|
"DiglettsCave_B1F", "NavelRock_Fork", "PowerPlant", "VictoryRoad_1F",
|
|
"FourIsland_IcefallCave_Entrance", "SixIsland_AlteringCave",
|
|
"MtEmber_RubyPath_B3F", "OneIsland_KindleRoad_EmberSpa",
|
|
"MtEmber_RubyPath_1F", "FourIsland_IcefallCave_Back",
|
|
"MtEmber_RubyPath_B5F", "CeladonCity", "PalletTown", "ViridianForest",
|
|
"CeladonCity_DepartmentStore_1F", "UndergroundPath_NorthSouthTunnel",
|
|
}
|
|
|
|
local function layoutBlock(layoutId)
|
|
local body
|
|
for chunk in layoutsJson:gmatch("{(.-)}") do
|
|
if chunk:find('"id"%s*:%s*"' .. layoutId .. '"') then body = chunk end
|
|
end
|
|
return body
|
|
end
|
|
|
|
local function pretGrid(mapName)
|
|
local mapJson = slurp(PRET .. "/data/maps/" .. mapName .. "/map.json")
|
|
if not mapJson then return nil end
|
|
local layoutId = mapJson:match('"layout"%s*:%s*"([%w_]+)"')
|
|
local body = layoutId and layoutBlock(layoutId)
|
|
if not body then return nil end
|
|
local w = tonumber(body:match('"width"%s*:%s*(%d+)'))
|
|
local h = tonumber(body:match('"height"%s*:%s*(%d+)'))
|
|
local blocks = slurp(PRET .. "/" .. (body:match('"blockdata_filepath"%s*:%s*"([^"]+)"') or ""))
|
|
if not (w and h and blocks and #blocks >= w * h * 2) then return nil end
|
|
return w, h, blocks
|
|
end
|
|
|
|
local checkedMaps, checkedCells, openCells = 0, 0, 0
|
|
for _, mapName in ipairs(SAMPLE) do
|
|
local mapId = MapCatalog.resolve(mapName)
|
|
local dec = mapId and layoutOf(mapId)
|
|
local w, h, blocks = pretGrid(mapName)
|
|
if dec and w then
|
|
checkedMaps = checkedMaps + 1
|
|
local mapWarps = {}
|
|
for _, warp in ipairs(warps[mapId] or {}) do
|
|
mapWarps[warp.y * NativePack.WARP_KEY_STRIDE + warp.x] = true
|
|
end
|
|
local bad = 0
|
|
for y = 0, h - 1 do
|
|
for x = 0, w - 1 do
|
|
local i = (y * w + x) * 2
|
|
local word = blocks:byte(i + 1) + blocks:byte(i + 2) * 256
|
|
local mapColl = math.floor(word / 1024) % 4
|
|
local coll = collAt(dec, x, y)
|
|
if mapColl ~= 0 and coll then
|
|
checkedCells = checkedCells + 1
|
|
local doorWarp = mapWarps[y * NativePack.WARP_KEY_STRIDE + x]
|
|
and coll >= 0x60 and coll <= 0x7F
|
|
if doorWarp then
|
|
openCells = openCells + 1
|
|
elseif Permissions.isWalkable(coll) and not Permissions.isLedge(coll) then
|
|
bad = bad + 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
eq(bad, 0, mapName .. " has no walkable cell where map.bin says collision 1")
|
|
else
|
|
check(false, mapName .. " resolved to a baked layout and a pret map.bin")
|
|
end
|
|
end
|
|
eq(checkedMaps, #SAMPLE, "every sampled map was checked")
|
|
check(checkedCells > 15000,
|
|
"the sweep read a real number of collision 1 cells (" .. checkedCells .. ")")
|
|
check(openCells > 0,
|
|
"the door exception kept " .. openCells .. " warp cells enterable")
|
|
|
|
finish()
|