mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
302 lines
12 KiB
Lua
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()
|