-- Yellow's Jessie & James battle behind their own pic while keeping the -- ROCKET class and the name "ROCKET" (#439). pokeyellow -- home/trainers2.asm:36-50 IsFightingJessieJames swaps wTrainerPicPointer -- for JessieJamesPic when wTrainerClass is ROCKET and wTrainerNo >= $2a -- (parties 42-45), and GetTrainerName right below it is untouched, so only -- the pic changes. Red has no such symbol, so the swap must stay Yellow's. package.path = "./?.lua;./?/init.lua;" .. package.path local T = require("tests.modkit") local BattleState = require("src.battle.BattleState") local CLASS_PIC = "assets/generated/battle/trainers/rocket.png" local DUO_PIC = "assets/generated/battle/trainers/jessie_james.png" -- a Yellow cache extracted after #439: OPP_ROCKET carries both pics local yellow = { name = "ROCKET", pic = CLASS_PIC, picJessieJames = DUO_PIC } -- Red/Blue, and any Yellow cache built before #439, carry only the class pic local grunt = { name = "ROCKET", pic = CLASS_PIC } for _, party in ipairs({ 42, 43, 44, 45 }) do T.eq(BattleState.trainerPicPath(nil, yellow, "OPP_ROCKET", party), DUO_PIC, "party " .. party .. " fights behind the duo pic") end T.eq(yellow.name, "ROCKET", "the duo keeps the class name") -- $2a is the first duo party; every grunt below it keeps the class pic T.eq(BattleState.trainerPicPath(nil, yellow, "OPP_ROCKET", 41), CLASS_PIC, "party 41 is still a lone grunt") T.eq(BattleState.trainerPicPath(nil, yellow, "OPP_ROCKET", 1), CLASS_PIC, "the first ROCKET party is still a lone grunt") T.eq(BattleState.trainerPicPath(nil, yellow, "OPP_ROCKET", nil), CLASS_PIC, "an unnumbered ROCKET falls back to party 1") T.eq(BattleState.trainerPicPath(nil, { pic = "assets/generated/battle/trainers/super_nerd.png", picJessieJames = DUO_PIC }, "OPP_SUPER_NERD", 42), "assets/generated/battle/trainers/super_nerd.png", "the class gate holds: only ROCKET swaps") T.eq(BattleState.trainerPicPath(nil, grunt, "OPP_ROCKET", 42), CLASS_PIC, "a cache with no duo pic keeps the grunt instead of a nil pic") -- The extractor writes jessie_james.png only when the symbol is in the -- manifest, so the symbol table is the other half of the contract. local function readFile(path) local handle = io.open(path, "r") if not handle then return nil end local text = handle:read("*a") handle:close() return text end local yellowManifest = readFile("tools/rom_manifest_yellow.json") T.check(yellowManifest ~= nil, "tools/rom_manifest_yellow.json is readable") if yellowManifest then local bank, addr = yellowManifest:match( '"JessieJamesPic"%s*:%s*%[%s*(%d+)%s*,%s*(%d+)%s*%]') T.eq(bank, "19", "JessieJamesPic sits in bank 0x13") T.eq(addr, "31873", "JessieJamesPic sits at 0x7c81 in that bank") end for _, path in ipairs({ "tools/rom_manifest.json", "tools/rom_manifest_blue.json" }) do local text = readFile(path) T.check(text ~= nil, path .. " is readable") T.check(text == nil or text:find("JessieJamesPic", 1, true) == nil, path .. " has no such symbol, matching pokered") end -- the manifest is generated, so the generator has to keep asking for it local gen = readFile("tools/make_yellow_manifest.py") T.check(gen ~= nil, "tools/make_yellow_manifest.py is readable") T.check(gen == nil or gen:find('"JessieJamesPic"', 1, true) ~= nil, "a regenerated Yellow manifest keeps JessieJamesPic") T.finish("jessie james pic")