Files
gen1recomp/tests/emerald_story_scripts_test.lua
T
bryanthaboi 690f0cd9a1 TOUCHDOWN
2026-09-27 16:07:31 -04:00

174 lines
6.9 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
local function cacheRoot()
local explicit = os.getenv("POKEPORT_EMERALD_CACHE")
if explicit and explicit ~= "" then return explicit end
local identity = os.getenv("POKEPORT_IDENTITY")
local home = os.getenv("HOME")
if not (identity and identity ~= "" and home) then return nil end
for _, base in ipairs({ home .. "/Library/Application Support/LOVE/", home .. "/.local/share/love/" }) do
local root = base .. identity .. "/emerald"
local f = io.open(root .. "/data/generated/gba/scripts/scripts.lua", "rb")
if f then
f:close()
return root
end
end
return nil
end
local ROOT = cacheRoot()
if not ROOT then
print("emerald_story_scripts_test: skipped (set POKEPORT_IDENTITY to an identity with an Emerald cache)")
os.exit(0)
end
local function loadLua(rel)
local f = io.open(ROOT .. "/" .. rel, "rb")
if not f then return nil end
local src = f:read("*a")
f:close()
local chunk = load(src, "@" .. rel, "t", {})
return chunk and chunk() or nil
end
local GameVersion = require("src.core.GameVersion")
local prevVersion = GameVersion.get()
GameVersion.set("emerald")
local Opcodes = require("src.core.game3.scripting.opcodes")
local Natives = require("src.core.game3.scripting.natives")
local Movement = require("src.core.game3.scripting.movement")
local Constants = require("src.core.game3.constants")
local C = Constants.of("emerald")
local S = "data/generated/gba/scripts/"
local scripts = assert(loadLua(S .. "scripts.lua"), "scripts.lua")
local events = assert(loadLua(S .. "events.lua"), "events.lua")
local labels = assert(loadLua(S .. "labels.lua"), "labels.lua")
local movements = assert(loadLua(S .. "movements.lua"), "movements.lua")
local MAPS = {
"EM_INSIDE_OF_TRUCK", "EM_LITTLEROOT_TOWN", "EM_LITTLEROOT_TOWN_BRENDANS_HOUSE_1F",
"EM_LITTLEROOT_TOWN_BRENDANS_HOUSE_2F", "EM_LITTLEROOT_TOWN_MAYS_HOUSE_1F", "EM_LITTLEROOT_TOWN_MAYS_HOUSE_2F",
"EM_ROUTE101", "EM_LITTLEROOT_TOWN_PROFESSOR_BIRCHS_LAB",
}
local seeds, seen = {}, {}
local function add(k)
if type(k) == "string" and not seen[k] then
seen[k] = true
seeds[#seeds + 1] = k
end
end
for _, m in ipairs(MAPS) do
local e = events[m]
check(e ~= nil, m .. " has events")
for _, o in ipairs(e and e.objects or {}) do add(o.scriptKey) end
for _, o in ipairs(e and e.bgEvents or {}) do add(o.scriptKey) end
for _, o in ipairs(e and e.coordEvents or {}) do add(o.scriptKey) end
for _, v in pairs(e and e.mapScripts or {}) do
if type(v) == "string" then add(v) else for _, r in ipairs(v) do add(r.script) end end
end
end
add(labels.EventScript_TV)
add("EventScript_ResetAllMapFlags")
local opNames = {}
local set = Opcodes.forGame("emerald")
for byte = 0, set.MAX do
local r = set:get(byte)
if r then opNames[r.name] = true end
end
for _, alias in ipairs({ "goto_if", "call_if", "callstd_if", "gotostd_if", "vgoto_if", "vcall_if" }) do opNames[alias] = true end
local specials, unknownOps, moves = {}, {}, {}
local i = 1
while i <= #seeds do
local key = seeds[i]
i = i + 1
for _, r in ipairs(scripts[key] or {}) do
if not opNames[r.op] then unknownOps[r.op] = true end
if r.op == "special" then specials[tonumber(r.id or r[1])] = true end
if r.op == "specialvar" then specials[tonumber(r.id or r[2])] = true end
if r.op == "callstd" or r.op == "gotostd" then add("std:" .. tostring(r.std or r[1])) end
if r.op == "applymovement" then moves[#moves + 1] = r.movement or r[2] end
for _, v in pairs(r) do
if type(v) == "string" and v:match("^g3:") and scripts[v] then add(v) end
end
end
end
print(string.format("emerald_story_scripts_test: %d scripts reachable from the opening maps", #seeds))
check(#seeds > 300, "the opening maps reach more than 300 scripts")
local unk = {}
for k in pairs(unknownOps) do unk[#unk + 1] = k end
table.sort(unk)
eq(#unk, 0, "every op on the opening path is an Emerald opcode (" .. table.concat(unk, ",") .. ")")
Natives.bind("emerald")
local OPENING = {
"ChooseStarter", "GetPlayerBigGuyGirlString", "GetRivalSonDaughterString", "ResetTVShowState",
"CheckForPlayersHouseNews", "IsGabbyAndTyShowOnTheAir", "GetRandomActiveShowIdx", "GetNextActiveShowIfMassOutbreak",
"GetSelectedTVShow", "GetMomOrDadStringForTVMessage", "TurnOffTVScreen", "TurnOnTVScreen", "DoTVShow", "DoPokeNews",
"DoTVShowInSearchOfTrainers", "InitSecretBaseDecorationSprites", "StartWallClock", "Special_ViewWallClock",
"HealPlayerParty", "ChangePokemonNickname", "ChangeBoxPokemonNickname", "EnableNationalPokedex",
}
local LATER = {
BedroomPC = "W3 player PC",
DoPCTurnOnEffect = "W3 PC metatile effect",
GetPCBoxToSendMon = "W3 storage (party full path)",
ShouldShowBoxWasFullMessage = "W3 storage (party full path)",
HasAllHoennMons = "W3 dex (post-game lab)",
ScriptGetPokedexInfo = "W3 dex rating",
ShowPokedexRatingMessage = "W3 dex rating",
SetUnlockedPokedexFlags = "W3 dex",
InitRoamer = "E21 roamers (post-game)",
}
local opening = {}
for _, n in ipairs(OPENING) do opening[n] = true end
local unbound = {}
for id in pairs(specials) do
local name = C.specials.byId[id]
check(name ~= nil, string.format("special 0x%X has a pret name", id))
local bound = Natives.ALLOW["special:" .. id] ~= nil
if opening[name] then
check(bound, "opening special " .. tostring(name) .. " is bound")
elseif not bound then
unbound[#unbound + 1] = name
check(LATER[name] ~= nil, "unbound special " .. tostring(name) .. " is a documented later-wave item")
end
end
table.sort(unbound)
for _, n in ipairs(unbound) do print(" later: " .. n .. " (" .. tostring(LATER[n]) .. ")") end
local nops = 0
for _, key in ipairs(moves) do
local stream = movements[key]
for _, b in ipairs(stream or {}) do
if b >= 0x100 and Movement.decodeAction(b).kind == "nop" then nops = nops + 1 end
end
end
eq(nops, 0, "no canonical RSE movement action on the opening path decodes to nop")
local man = loadLua("data/generated/gba/starter_choose/manifest.lua")
if check(man ~= nil, "starter_choose manifest in the cache") then
-- pokeemerald/src/starter_choose.c:113
eq(man.species[1], C.species.byName.SPECIES_TREECKO, "starter 0 is Treecko")
eq(man.species[2], C.species.byName.SPECIES_TORCHIC, "starter 1 is Torchic")
eq(man.species[3], C.species.byName.SPECIES_MUDKIP, "starter 2 is Mudkip")
eq(man.pokeballCoords[2][1], 120, "middle ball x (starter_choose.c:99)")
eq(man.pokeballCoords[2][2], 88, "middle ball y")
eq(man.cursorCoords[3][1], 180, "right cursor x (starter_choose.c:204)")
eq(man.windows.confirm.tilemapLeft, 24, "YES/NO window left (starter_choose.c:77)")
eq(man.windows.message.width, 24, "message window width (starter_choose.c:63)")
eq(man.affine.circle[1].xScale, 20, "circle affine starts at 20 (starter_choose.c:274)")
eq(man.sprites.pokeball.frames, 3, "pokeball sheet has the still + two wobble frames")
end
Natives.bind("firered")
GameVersion.set(prevVersion)
T.finish()