Files
gen1recomp/tests/game3_warp_behaviors_test.lua
bryanthaboi 72a17b27dd CLOSES #2444
2026-09-24 08:39:43 -04:00

302 lines
12 KiB
Lua

#!/usr/bin/env luajit
-- pokefirered/src/field_control_avatar.c:825,856,901,944 and
-- pokefirered/src/overworld.c:910.
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 finish()
if failed > 0 then
print("[test] FAILED " .. failed)
os.exit(1)
end
print("[test] all passed")
os.exit(0)
end
local MB = {
CAVE_DOOR = 0x60, LADDER = 0x61,
EAST_ARROW = 0x62, WEST_ARROW = 0x63, NORTH_ARROW = 0x64, SOUTH_ARROW = 0x65,
FALL = 0x66, REGULAR = 0x67, LAVARIDGE_1F = 0x68, WARP_DOOR = 0x69,
UP_ESCALATOR = 0x6A, DOWN_ESCALATOR = 0x6B,
UP_RIGHT_STAIR = 0x6C, UP_LEFT_STAIR = 0x6D,
DOWN_RIGHT_STAIR = 0x6E, DOWN_LEFT_STAIR = 0x6F,
UNION_ROOM = 0x71,
}
print("[test] 1. the classifier seeds every live warp behavior as a warp cell")
local ScriptColl = require("src.core.game3.scripting.collision")
local SEEDED = {
[MB.FALL] = 646, [MB.LAVARIDGE_1F] = 646, [MB.UNION_ROOM] = 750,
}
for beh, mid in pairs(SEEDED) do
local byte = ScriptColl.fromCell(mid, 0, beh, "indoor")
check(byte ~= nil and byte >= 0x60 and byte <= 0x7F,
string.format("behavior 0x%02X seeds a warp COLL byte (0x%02X)", beh, byte or 0))
local Permissions = require("src.world.gen2.Permissions")
check(Permissions.isWalkable(byte) == true,
string.format("behavior 0x%02X is walkable (0x%02X)", beh, byte or 0))
end
local fallByte = ScriptColl.fromCell(646, 0, MB.FALL, "indoor")
local Permissions = require("src.world.gen2.Permissions")
check(Permissions.warpFacesDown(fallByte) ~= true,
string.format("MB_FALL_WARP does not seed a face-down warp (0x%02X)", fallByte))
-- pokefirered/src/metatile_behavior.c:5
for beh = 0x50, 0x53 do
local byte = ScriptColl.fromCell(646, 0, beh, "indoor")
check(byte == 0x29,
string.format("current behavior 0x%02X seeds the water COLL byte (0x%02X)", beh, byte))
end
print("[test] 2. the metatile_behavior.c predicates")
local Collision = require("src.core.game3.collision")
local PREDS = {
isWarpDoor = { MB.WARP_DOOR },
isLadder = { MB.LADDER },
isNonAnimDoor = { MB.CAVE_DOOR },
isLavaridge1FWarp = { MB.LAVARIDGE_1F },
isWarpPad = { MB.REGULAR },
isUnionRoomWarp = { MB.UNION_ROOM },
isFallWarp = { MB.FALL },
isEscalator = { MB.UP_ESCALATOR, MB.DOWN_ESCALATOR },
isArrowWarpBehavior = { MB.EAST_ARROW, MB.WEST_ARROW, MB.NORTH_ARROW, MB.SOUTH_ARROW },
-- pokefirered/src/metatile_behavior.c:204
isSurfable = {
0x10, 0x11, 0x12, 0x13, 0x15, 0x1A, 0x1B, 0x50, 0x51, 0x52, 0x53,
},
-- pokefirered/src/field_control_avatar.c:901 IsWarpMetatileBehavior
isStepWarpBehavior = {
MB.WARP_DOOR, MB.LADDER, MB.UP_ESCALATOR, MB.DOWN_ESCALATOR, MB.CAVE_DOOR,
MB.LAVARIDGE_1F, MB.REGULAR, MB.FALL, MB.UNION_ROOM,
},
}
for name, want in pairs(PREDS) do
local fn = Collision[name]
if type(fn) ~= "function" then
check(false, "Collision." .. name .. " exists")
else
local set = {}
for _, beh in ipairs(want) do set[beh] = true end
local wrong = 0
for beh = 0x00, 0xFF do
if (fn(beh) and true or false) ~= (set[beh] == true) then wrong = wrong + 1 end
end
check(wrong == 0, name .. " matches pret over every behavior byte, wrong=" .. wrong)
end
end
-- pokefirered/src/metatile_behavior.c:202 is a FRLG stub.
check(type(Collision.isDeepSouthWarp) == "function" and Collision.isDeepSouthWarp(0x00) == false,
"isDeepSouthWarp is always false in FRLG")
check(type(Collision.isStepWarpBehavior) == "function"
and Collision.isStepWarpBehavior(nil) == false, "isStepWarpBehavior(nil) is false")
print("[test] 3. arrow warps carry their own press direction")
if type(Collision.arrowWarpDir) ~= "function" then
check(false, "Collision.arrowWarpDir exists")
else
check(Collision.arrowWarpDir(MB.EAST_ARROW) == "right", "MB_EAST_ARROW_WARP wants right")
check(Collision.arrowWarpDir(MB.WEST_ARROW) == "left", "MB_WEST_ARROW_WARP wants left")
check(Collision.arrowWarpDir(MB.NORTH_ARROW) == "up", "MB_NORTH_ARROW_WARP wants up")
check(Collision.arrowWarpDir(MB.SOUTH_ARROW) == "down", "MB_SOUTH_ARROW_WARP wants down")
check(Collision.arrowWarpDir(MB.LADDER) == nil, "MB_LADDER has no arrow direction")
end
print("[test] 4. GetAdjustedInitialDirection")
if type(Collision.arrivalFacing) ~= "function" then
check(false, "Collision.arrivalFacing exists")
else
local WANT = {
[MB.CAVE_DOOR] = "down", [MB.WARP_DOOR] = "down",
[MB.SOUTH_ARROW] = "up", [MB.NORTH_ARROW] = "down",
[MB.WEST_ARROW] = "right", [MB.EAST_ARROW] = "left",
[MB.UP_RIGHT_STAIR] = "left", [MB.DOWN_RIGHT_STAIR] = "left",
[MB.UP_LEFT_STAIR] = "right", [MB.DOWN_LEFT_STAIR] = "right",
}
for beh, want in pairs(WANT) do
check(Collision.arrivalFacing(beh, "left") == want,
string.format("landing on 0x%02X faces %s", beh, want))
end
check(Collision.arrivalFacing(MB.LADDER, "left") == "left",
"a ladder keeps the direction the player left with")
check(Collision.arrivalFacing(MB.LADDER, "up") == "up",
"a ladder reached walking up keeps facing up")
check(Collision.arrivalFacing(MB.LADDER, nil) == "down",
"a ladder with no stored direction faces south")
check(Collision.arrivalFacing(0x00, "left") == "down",
"hasDirectionSet is cleared, so a plain cell faces south")
check(Collision.arrivalFacing(MB.FALL, "up") == "down",
"a fall hole lands facing south")
end
print("[test] 5. cache-backed maps")
local Cache = require("tests.game3_cache")
local cacheRoot = Cache.mount("scripts/events.lua", { native = true })
if not cacheRoot then
print("[skip] game3_warp_behaviors_test map checks: " .. tostring(Cache.reason))
finish()
end
print("[info] FireRed cache at " .. cacheRoot)
local Dataset = require("src.core.game3.dataset")
local game = { data = {} }
Dataset.hydrate(game)
local function bind(mapId)
local def = game.data.maps[mapId]
if not def then
check(false, mapId .. " is in the cache")
return nil
end
local Map = require("src.core.game3.map")
if Map.ensureMidLayout then pcall(Map.ensureMidLayout, game, mapId, def) end
Collision.bindMap(game, mapId, def)
return def
end
print("[test] 5a. Seafoam Islands 1F drop holes are live warps")
if bind("FR_SEAFOAM_ISLANDS_1F") then
for _, hole in ipairs({ { 21, 8, 21, 8 }, { 30, 8, 29, 8 } }) do
local x, y, dx, dy = hole[1], hole[2], hole[3], hole[4]
check(Collision.behavior(x, y) == MB.FALL,
string.format("Seafoam 1F (%d,%d) carries MB_FALL_WARP", x, y))
check(Collision.isWalkable(x, y) == true,
string.format("Seafoam 1F (%d,%d) is walkable", x, y))
local w = Collision.warpAt(x, y)
check(w ~= nil, string.format("Seafoam 1F (%d,%d) indexes its warp event", x, y))
if w then
check(tostring(w.destMap) == "FR_SEAFOAM_ISLANDS_B1F",
string.format("(%d,%d) drops to B1F (%s)", x, y, tostring(w.destMap)))
local b1 = game.data.maps["FR_SEAFOAM_ISLANDS_B1F"]
local landing = b1 and b1.warps and b1.warps[tonumber(w.destWarp)]
check(landing ~= nil and tonumber(landing.x) == dx and tonumber(landing.y) == dy,
string.format("(%d,%d) lands on B1F (%d,%d), got (%s,%s)", x, y, dx, dy,
tostring(landing and landing.x), tostring(landing and landing.y)))
end
end
end
print("[test] 5b. Victory Road 3F drop hole")
if bind("FR_VICTORY_ROAD_3F") then
check(Collision.behavior(34, 18) == MB.FALL, "Victory Road 3F (34,18) carries MB_FALL_WARP")
check(Collision.isWalkable(34, 18) == true, "Victory Road 3F (34,18) is walkable")
local w = Collision.warpAt(34, 18)
check(w ~= nil, "Victory Road 3F (34,18) indexes its warp event")
if w then
local vr2 = game.data.maps["FR_VICTORY_ROAD_2F"]
local landing = vr2 and vr2.warps and vr2.warps[tonumber(w.destWarp)]
check(tostring(w.destMap) == "FR_VICTORY_ROAD_2F"
and landing ~= nil and tonumber(landing.x) == 34 and tonumber(landing.y) == 19,
"Victory Road 3F (34,18) lands on 2F (34,19)")
end
end
print("[test] 5c. Union Room warp pad")
if bind("FR_UNION_ROOM") or game.data.maps["FR_UNION_ROOM"] == nil then
local def = game.data.maps["FR_UNION_ROOM"]
if def then
check(Collision.behavior(7, 11) == MB.UNION_ROOM, "Union Room (7,11) carries MB_UNION_ROOM_WARP")
check(Collision.isWalkable(7, 11) == true, "Union Room (7,11) is walkable")
else
print("[info] FR_UNION_ROOM is not a game3 map in this cache")
end
end
print("[test] 5d. Saffron Gym teleport pads are MB_REGULAR_WARP, not a map-name guess")
if bind("FR_SAFFRON_CITY_GYM") then
check(Collision.behavior(18, 20) == MB.REGULAR, "Saffron Gym (18,20) carries MB_REGULAR_WARP")
check(type(Collision.isWarpPad) == "function"
and Collision.isWarpPad(Collision.behavior(18, 20)) == true,
"Saffron Gym (18,20) is a warp pad")
check(Collision.warpAt(18, 20) ~= nil, "Saffron Gym (18,20) indexes its warp event")
-- pokefirered/src/field_control_avatar.c:879
local pads, indexed = 0, 0
for y = 0, Collision._heightCells - 1 do
for x = 0, Collision._widthCells - 1 do
if Collision.behavior(x, y) == MB.REGULAR then
pads = pads + 1
if Collision.warpAt(x, y) then indexed = indexed + 1 end
end
end
end
check(pads >= 2, "Saffron Gym carries " .. pads .. " MB_REGULAR_WARP pads")
check(pads == indexed,
string.format("every Saffron Gym warp pad indexes a warp event (%d/%d)", indexed, pads))
end
print("[test] 5e. an exit mat only warps on a press in its own direction")
if bind("FR_VIRIDIAN_CITY_POKEMON_CENTER_1F") or true then
local MATMAP = "FR_VIRIDIAN_CITY_POKEMON_CENTER_1F"
if game.data.maps[MATMAP] then
bind(MATMAP)
local mx, my
for y = 0, Collision._heightCells - 1 do
for x = 0, Collision._widthCells - 1 do
if Collision.behavior(x, y) == MB.SOUTH_ARROW and Collision.warpAt(x, y) then
mx, my = x, y
break
end
end
if mx then break end
end
check(mx ~= nil, MATMAP .. " has an indexed MB_SOUTH_ARROW_WARP mat")
if mx then
check(Collision.tryWarpAt(game, mx, my, "down") == false,
string.format("landing on the mat at (%d,%d) does not warp by itself", mx, my))
check(type(Collision.isArrowWarp) == "function"
and Collision.isArrowWarp(game, mx, my, "down") ~= nil,
"pressing down on the mat is an arrow warp")
check(type(Collision.isArrowWarp) == "function"
and Collision.isArrowWarp(game, mx, my, "up") == nil,
"pressing up on a south-arrow mat is not an arrow warp")
end
else
print("[info] " .. MATMAP .. " is not in this cache")
end
end
print("[test] 5f. a west-arrow gate tile does not warp on landing")
if bind("FR_ROUTE_7_EAST_ENTRANCE") then
check(Collision.behavior(1, 5) == MB.WEST_ARROW, "Route 7 east entrance (1,5) is MB_WEST_ARROW_WARP")
check(Collision.warpAt(1, 5) ~= nil, "(1,5) indexes its warp event")
local ok, res = pcall(Collision.tryWarpAt, game, 1, 5, "down")
check(ok and res == false, "stepping onto the west-arrow tile from the north does not warp")
check(type(Collision.isArrowWarp) == "function"
and Collision.isArrowWarp(game, 1, 5, "left") ~= nil,
"pressing west on it is an arrow warp")
check(type(Collision.isArrowWarp) == "function"
and Collision.isArrowWarp(game, 1, 5, "down") == nil,
"pressing south on a west-arrow tile is not an arrow warp")
end
print("[test] 5g. the Seafoam fall landings are currents, and currents are water")
-- pokefirered/src/overworld.c:898 MetatileBehavior_IsSurfableInSeafoamIslands
local SEAFOAM_LANDINGS = {
{ "FR_SEAFOAM_ISLANDS_B3F", 23, 9, 0x53 },
{ "FR_SEAFOAM_ISLANDS_B3F", 24, 9, 0x53 },
{ "FR_SEAFOAM_ISLANDS_B4F", 8, 17, 0x52 },
{ "FR_SEAFOAM_ISLANDS_B4F", 9, 17, 0x52 },
}
for _, land in ipairs(SEAFOAM_LANDINGS) do
local mapId, lx, ly, want = land[1], land[2], land[3], land[4]
if bind(mapId) then
local beh = Collision.behavior(lx, ly)
check(beh == want,
string.format("%s (%d,%d) is behavior 0x%02X, got 0x%02X", mapId, lx, ly, want, beh or 255))
check(type(Collision.isSurfable) == "function" and Collision.isSurfable(beh) == true,
string.format("%s (%d,%d) is surfable in sBehaviorSurfable", mapId, lx, ly))
check(Collision.isWater(lx, ly) == true,
string.format("%s (%d,%d) bakes as water, not walkable floor", mapId, lx, ly))
end
end
finish()