-- Yellow's two extra pic rips and the plumbing that reaches for them. -- -- #557: LoadPlayerBackPic (pokeyellow engine/battle/core.asm:6384-6391) -- loads OldManPicBack for BATTLE_TYPE_OLD_MAN but ProfOakPicBack for -- BATTLE_TYPE_PIKACHU, the Pallet Town catch scene, and DisplayBattleMenu -- splits the displayed thrower name on the same wBattleType. The port -- carries that split as makeOldManDemo's name argument. -- -- #561: TalkToPikachu's framed portrait draws the chosen PikaPicAnimScript's -- own base frame (data/pikachu/pikachu_pic_animation.asm), not the battle -- front pic that stood in for all of them. -- -- Both rips are gated on manifest symbols, so tools/rom_manifest_yellow.json -- is as much a part of these fixes as the Lua is: without the symbol the -- extractor writes nothing, the runtime existence check falls back, and the -- screen looks exactly like the bug report. package.path = "./?.lua;./?/init.lua;" .. package.path local T = require("tests.modkit") local BattleState = require("src.battle.BattleState") local FieldDefaults = require("src.world.FieldDefaults") local Sprites = require("src.pokemon.Sprites") local PROF_BACK = "assets/generated/battle/profoakb.png" local OLDMAN_BACK = "assets/generated/battle/oldmanb.png" local RED_BACK = "assets/generated/battle/redb.png" 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 -- --------------------------------------------------------------------- -- #557: the back pic keys and the wBattleType split -- --------------------------------------------------------------------- T.eq(FieldDefaults.fieldValue(nil, "playerPics", "oakBack"), PROF_BACK, "playerPics carries a third back pic for the PROF.OAK demo") T.eq(FieldDefaults.fieldValue(nil, "playerPics", "demoBack"), OLDMAN_BACK, "the old man's back pic is untouched") -- The love stub's filesystem is a table of written files, so writing the -- path IS "this cache was imported after the rip landed". love.filesystem.write(PROF_BACK, "png") T.eq(Sprites.playerPath(nil, "back", { demo = true, oakDemo = true }), PROF_BACK, "the Pallet catch demo fights behind Oak's own back pic") T.eq(Sprites.playerPath(nil, "back", { demo = true }), OLDMAN_BACK, "the Route 5 catch tutorial still gets the old man") T.eq(Sprites.playerPath(nil, "back", {}), RED_BACK, "an ordinary battle still gets the player") love.filesystem.remove(PROF_BACK) T.eq(Sprites.playerPath(nil, "back", { demo = true, oakDemo = true }), OLDMAN_BACK, "a cache built before the rip falls back to the old man, not a missing file") -- makeOldManDemo only fills fields when a player battler already exists, so -- a stub with one stays clear of Pokemon.new and the fixture dataset. local oak = { player = true } BattleState.makeOldManDemo(oak, "PROF.OAK") T.eq(oak.demoName, "PROF.OAK", "the Yellow demo names PROF.OAK as the thrower") T.eq(oak.oakDemo, true, "and asks for his back pic") local oldMan = { player = true } BattleState.makeOldManDemo(oldMan) T.eq(oldMan.demoName, "OLD MAN", "the unnamed demo is still the old man") T.eq(oldMan.oakDemo, false, "and does not reach for Oak's pic") -- --------------------------------------------------------------------- -- the symbol tables the two rips are gated on -- --------------------------------------------------------------------- local extractor = readFile("src/import/RomExtractor.lua") T.check(extractor ~= nil, "src/import/RomExtractor.lua is readable") extractor = extractor or "" local yellowManifest = readFile("tools/rom_manifest_yellow.json") T.check(yellowManifest ~= nil, "tools/rom_manifest_yellow.json is readable") yellowManifest = yellowManifest or "" local generator = readFile("tools/make_yellow_manifest.py") T.check(generator ~= nil, "tools/make_yellow_manifest.py is readable") generator = generator or "" -- Every symbol below is real in pokeyellow.sym; what decides whether the -- extractor can see it is whether the generator injects it. Red never -- referenced any of them, so make_yellow_manifest's Red-name remap cannot -- pick them up on its own -- they have to be listed in YELLOW_EXTRA_SYMBOLS. -- One line per group rather than per label: a missing symbol list reads as -- one fault to fix, not fifty-odd. local function missingFrom(text, labels) local missing = {} for _, label in ipairs(labels) do if not text:find('"' .. label .. '"', 1, true) then missing[#missing + 1] = label end end return missing end local function requireSymbols(labels, why) local gone = missingFrom(yellowManifest, labels) T.check(#gone == 0, why .. ": rom_manifest_yellow.json is missing " .. (#gone == 0 and "nothing" or table.concat(gone, " "))) gone = missingFrom(generator, labels) T.check(#gone == 0, why .. ": make_yellow_manifest.py never asks for " .. (#gone == 0 and "nothing" or table.concat(gone, " ")) .. " (add them to YELLOW_EXTRA_SYMBOLS)") end T.check(extractor:find('self.symbols["ProfOakPicBack"]', 1, true) ~= nil, "the extractor gates Oak's back pic on the ProfOakPicBack symbol") requireSymbols({ "ProfOakPicBack" }, "#557") -- Read the label list out of the extractor itself rather than repeating it: -- the contract under test is that the manifest answers whatever -- extractField asks for, so a later edit to PIKAPIC_BASE stays covered. local pikapicBlock = extractor:match("local PIKAPIC_BASE = {(.-)\n }") T.check(pikapicBlock ~= nil, "RomExtractor's PIKAPIC_BASE table is readable") local pikapic, seen = {}, {} for label in (pikapicBlock or ""):gmatch('"([%w_]+)"') do if not seen[label] then seen[label] = true pikapic[#pikapic + 1] = label end end -- 28 PikaPicAnimScripts, script 26 sharing script 11's base pic T.eq(#pikapic, 27, "PIKAPIC_BASE names 27 distinct base pics") requireSymbols(pikapic, "#561") -- Red and Blue have neither pic, so neither manifest may grow one: a stray -- entry there would rip Yellow art out of the wrong ROM. 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("ProfOakPicBack", 1, true) == nil, path .. " has no ProfOakPicBack, matching pokered") end T.finish("yellow oak back pic and pikapic bases")