From 66e7d9432ea44bec753ff476dd4d306b00e48c48 Mon Sep 17 00:00:00 2001 From: Shane McGovern Date: Fri, 7 Aug 2026 10:51:50 +0100 Subject: [PATCH] Fix Yellow Oak speech showing Nidorino instead of Pikachu (#915) Yellow's field.oakSpeech manifest carried only shrink frames, so OakSpeech.lua's `oakGfx.demoSpecies or "NIDORINO"` fallback fired and the opening speech showed Nidorino's sprite and cry instead of the player's Pikachu. - Stamp demoSpecies "PIKACHU" in the Yellow import manifest (source of truth for fresh ROM imports and developer builds). - Stamp it in make_yellow_manifest.py so regeneration keeps the value. - Repair stale Yellow caches in Data:applyVersionedFieldData() with a fill-if-absent block, matching the #617 oldManBattle RATTATA pattern. - Add parity test (manifest carries PIKACHU; stale cache filled; pre-stamped value left alone). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/core/Data.lua | 9 ++++++ tests/parity_yellow_oak_speech.lua | 51 ++++++++++++++++++++++++++++++ tools/make_yellow_manifest.py | 6 ++++ tools/rom_manifest_yellow.json | 1 + 4 files changed, 67 insertions(+) create mode 100644 tests/parity_yellow_oak_speech.lua diff --git a/src/core/Data.lua b/src/core/Data.lua index a6ec72e8..405fe107 100644 --- a/src/core/Data.lua +++ b/src/core/Data.lua @@ -84,6 +84,15 @@ function Data:applyVersionedFieldData() -- Yellow caches carry the wrong demo species too. The fixed import -- manifest below stamps RATTATA for fresh imports. self.field.oldManBattle = { species = "RATTATA", level = 5 } + -- The Oak-speech show-off mon is the player's Pikachu in Yellow + -- (engine/battle/core.asm BATTLE_TYPE_PIKACHU / the ProfOak demo) + -- but caches imported before the manifest carried demoSpecies fell + -- back to Red's NIDORINO (#915). The fixed import manifest below + -- stamps PIKACHU for fresh imports; fill it here for stale caches. + local oakSpeech = self.field.oakSpeech + if type(oakSpeech) == "table" and not oakSpeech.demoSpecies then + oakSpeech.demoSpecies = "PIKACHU" + end end end diff --git a/tests/parity_yellow_oak_speech.lua b/tests/parity_yellow_oak_speech.lua new file mode 100644 index 00000000..2b74da3f --- /dev/null +++ b/tests/parity_yellow_oak_speech.lua @@ -0,0 +1,51 @@ +-- Pokemon Yellow's Oak-speech show-off mon is the player's Pikachu, not +-- Red/Blue's NIDORINO (engine/battle/core.asm BATTLE_TYPE_PIKACHU, the +-- ProfOak demo; engine/movie/oak_speech/oak_speech.asm). The import +-- manifest must carry field.oakSpeech.demoSpecies, and +-- Data:applyVersionedFieldData repairs Yellow caches made before the +-- manifest carried it (#915). +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end + +local Data = require("src.core.Data") +if not (Data.field and Data.field.oakSpeech) then Data:load() end +local GameVersion = require("src.core.GameVersion") +local S = require("tests.harness").suite("parity Yellow Oak speech") +local check, eq = S.check, S.eq + +local oldVersion = GameVersion.get() +local oldTrades = Data.field.trades +local oldOldManBattle = Data.field.oldManBattle + +local manifestFile = assert(io.open("tools/rom_manifest_yellow.json", "r")) +local manifest = manifestFile:read("*a") +manifestFile:close() + +check(manifest:find('"demoSpecies": "PIKACHU"') ~= nil, + "Yellow manifest stamps field.oakSpeech.demoSpecies as PIKACHU") + +-- a stale Yellow cache carries shrink frames but no demoSpecies +local stale = { shrink1 = "assets/generated/intro/shrink1.png", + shrink2 = "assets/generated/intro/shrink2.png" } +local oldOakSpeech = Data.field.oakSpeech +Data.field.oakSpeech = stale + +GameVersion.set("yellow") +Data:applyVersionedFieldData() +eq(Data.field.oakSpeech.demoSpecies, "PIKACHU", + "applyVersionedFieldData fills a stale Yellow cache with PIKACHU") + +-- fill-if-absent: an importer that learns to stamp the key wins +local preStamped = { demoSpecies = "RAICHU", + shrink1 = "assets/generated/intro/shrink1.png" } +Data.field.oakSpeech = preStamped +Data:applyVersionedFieldData() +eq(Data.field.oakSpeech.demoSpecies, "RAICHU", + "applyVersionedFieldData leaves an already-stamped demoSpecies alone") + +Data.field.oakSpeech = oldOakSpeech +Data.field.trades = oldTrades +Data.field.oldManBattle = oldOldManBattle +GameVersion.set(oldVersion) + +return S:finish() diff --git a/tools/make_yellow_manifest.py b/tools/make_yellow_manifest.py index 4e0e3782..de491f63 100755 --- a/tools/make_yellow_manifest.py +++ b/tools/make_yellow_manifest.py @@ -481,6 +481,12 @@ def derive(red, pokeyellow, symbols_path): for i, name in enumerate(yellow_bubbles)], } + # The Oak-speech show-off mon is the player's Pikachu in Yellow + # (engine/battle/core.asm BATTLE_TYPE_PIKACHU / the ProfOak demo); + # the deep-copied Red field.oakSpeech has no demoSpecies, so stamp + # it or the import falls back to NIDORINO (#915). + yellow["field"]["oakSpeech"]["demoSpecies"] = "PIKACHU" + # Ensure Melanie / Summer Beach town-map entries exist after rebuild. locations = yellow["field"]["townMap"]["locations"] if "CERULEAN_MELANIES_HOUSE" not in locations \ diff --git a/tools/rom_manifest_yellow.json b/tools/rom_manifest_yellow.json index 9bda56d2..9fa9e696 100644 --- a/tools/rom_manifest_yellow.json +++ b/tools/rom_manifest_yellow.json @@ -6566,6 +6566,7 @@ } ], "oakSpeech": { + "demoSpecies": "PIKACHU", "shrink1": "assets/generated/intro/shrink1.png", "shrink2": "assets/generated/intro/shrink2.png" },