Files
gen1recomp/tests/engine/game3_rs_scripts_test.lua
T
bryanthaboi 6774889505 Add Pokemon Ruby and Sapphire
Ruby and Sapphire 1.0/1.1/1.2 import, play, save, convert and link. Gen 3 shaders, braille, weather and reflection parity, link room players, RS custom carts and launcher support, Gen 3 link fixes for Emerald and FireRed/LeafGreen.
2026-10-05 05:07:42 -04:00

102 lines
5.7 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local GameVersion = require("src.core.GameVersion")
local Opcodes = require("src.core.game3.scripting.opcodes")
local Disasm = require("src.core.game3.scripting.disasm")
local TextIR = require("src.core.game3.scripting.text_ir")
local Native = require("src.core.game3.constants.ruby.script_cmds")
local Builds = require("src.import.gba.rs_builds")
local Movement = require("src.import.gba.movement_emerald")
local rs = Opcodes.forGame("ruby")
T.eq(Opcodes.forGame("sapphire"), rs, "both RS editions use native RS bytecode")
T.eq(rs.MAX, 0xC5, "RS ends at waitmoncry")
T.eq(rs:get(0xC6), nil, "Emerald's bufferboxname is absent in RS")
T.eq(rs.STD.REGISTER_MATCH_CALL, nil, "RS has no Match Call standard script")
T.eq(rs.STD.PUT_ITEM_AWAY, nil, "RS has no FRLG standard script 8")
T.eq(rs.brailleFormatSize, 6, "RS braille format is six bytes")
local moves = Movement.forGame("ruby")
local rawMoves = require("src.core.game3.constants").of("ruby").movement.byId.MOVEMENT_ACTION_
for id, name in pairs(rawMoves) do T.check(moves.toCanon[id] ~= nil, "native RS movement translates: " .. name) end
T.eq(moves.toCanon[0x2D], Movement.canonOf("MOVEMENT_ACTION_WALK_FASTER_DOWN"), "RS Fastest is cart Faster")
T.eq(moves.toCanon[0x61], Movement.canonOf("MOVEMENT_ACTION_WALK_DOWN_AFFINE"), "RS affine suffix has the same action")
for byte = 0, Native.count - 1 do
if byte ~= 0x5C then
T.eq(rs:get(byte).size, Native.byId[byte].size, "native width at opcode " .. byte)
end
end
for _, spec in ipairs({
{ bytes = { 0x93, 1, 2 }, op = "showmoneybox", args = 2 },
{ bytes = { 0x95, 1, 2 }, op = "updatemoneybox", args = 2 },
{ bytes = { 0x72, 1, 2, 3, 4 }, op = "drawbox", args = 4 },
{ bytes = { 0xB1, 1, 2, 0, 3, 0, 4, 0 }, op = "addelevmenuitem", args = 4 },
}) do
local stream = {}
for _, b in ipairs(spec.bytes) do stream[#stream + 1] = b end
for _, b in ipairs({ 0x16, 0, 0x40, 0x34, 0x12, 0x02 }) do stream[#stream + 1] = b end
local rows = Disasm.decode(stream, 1, nil, rs)
T.eq(rows[1].op, spec.op, "RS decodes " .. spec.op)
T.eq(rows[1][spec.args], spec.bytes[#spec.bytes - (spec.op == "addelevmenuitem" and 1 or 0)], "last operand survives")
T.eq(rows[2].op, "setvar", "next opcode stays aligned after " .. spec.op)
T.eq(rows[2][2], 0x1234, "next value stays aligned after " .. spec.op)
T.eq(rows[3].op, "end", "stream terminates after " .. spec.op)
end
local old = GameVersion.get()
for _, game in ipairs({ "ruby", "sapphire" }) do
GameVersion.set(game)
T.eq(Opcodes.active(), rs, game .. " selects the RS decoder")
T.eq(TextIR.dialectOf(), "rs", game .. " selects native text dialect")
local d = TextIR.dialect("rs")
T.eq(d.B_TXT[2], "B_PLAYER_MON1_NAME", "RS code 2 is the player mon, not COPY_VAR_1")
T.eq(d.B_TXT_CODE.B_CURRENT_MOVE, 0x11, "RS current move code")
T.eq(d.B_TXT_CODE.B_BUFF3, 0x2A, "RS buffer 3 code")
local ir = TextIR.decode({ 0xFD, 8, 0xFD, 9, 0xFF }, { dialect = "rs" })
T.eq(ir[1].name, "EVIL_TEAM", "RS code 8 is the edition's evil team")
T.eq(ir[2].name, "GOOD_TEAM", "RS code 9 is the edition's good team")
local V = require("src.import.gba.games." .. game)
for _, build in ipairs(Builds[game]) do
V.select(build.sha1)
T.eq(V.SYMS.game, build.build, "native symbol revision")
T.eq(V.NAMED_SCRIPTS.EventScript_TV, V.sym("Event_TV"), "TV canonical alias uses native script")
T.eq(V.TEXT_PLACEHOLDERS.EVIL_TEAM, V.sym("gExpandedPlaceholder_" .. (game == "ruby" and "Magma" or "Aqua")), "edition's evil team")
T.eq(V.TEXT_PLACEHOLDERS.GOOD_LEGENDARY, V.sym("gExpandedPlaceholder_" .. (game == "ruby" and "Kyogre" or "Groudon")), "edition's other legendary")
local found
for _, row in ipairs(V.SCRIPT_LABELS()) do if row.name == "EventScript_PC" then found = row.off end end
T.eq(found, V.sym("EventScript_PC"), "script labels preserve the active revision")
local tableRow
for _, row in ipairs(V.TEXT_TABLES) do if row.name == "gBattleStringsTable" then tableRow = row end end
T.eq(tableRow and tableRow.count, 351, "all native battle strings")
T.eq(tableRow and tableRow.ids, 12, "RS battle table starts at ID 12")
T.eq(V.BATTLE_STRING_IDS[380], "STRINGID_TRAINER2WINTEXT", "last native battle text ID survives")
end
V.select(game)
end
GameVersion.set(old)
local Versions = require("src.import.gba.versions")
for _, game in ipairs({ "ruby", "sapphire" }) do
for _, build in ipairs(Builds[game]) do
Versions.select(game)
Versions.selectCache(game, { read = function() return string.format('{"version":%q,"romSha1":%q,"md5":%q}', game, build.sha1, build.sha1) end })
T.eq(Versions.BUILD, build.build, "runtime restores cached native revision")
end
end
local previous = Versions.BUILD
for _, raw in ipairs({ '{}', '{"romSha1":"deadbeef"}',
string.format('{"version":"ruby","romSha1":%q}', Builds.sapphire[1].sha1),
string.format('{"romSha1":%q,"md5":%q}', Builds.ruby[1].sha1, Builds.ruby[2].sha1) }) do
local ok = pcall(Versions.selectCache, "ruby", { read = function() return raw end })
T.eq(ok, false, "reject missing, unknown, conflicting or other-edition native identity")
T.eq(Versions.BUILD, previous, "bad cache does not change selected revision")
end
Versions.select("firered")
local Plans = require("src.import.gba.plans.registry")
local plan = Plans.of("ruby")
T.eq(plan, Plans.of("sapphire"), "RS editions share a native import plan")
T.eq(plan.id, "rs", "RS does not run Emerald's plan")
for _, module in ipairs(Plans.modules(plan)) do
T.check(not module:find("frontier", 1, true) and not module:find("mon_anim", 1, true)
and not module:find("mystery_gift", 1, true), "RS plan excludes Emerald-only extraction")
end
T.finish("game3_rs_scripts_test")