Merge branch 'dev' into feat/switch-nx

This commit is contained in:
Andrew Quenehen
2026-08-03 13:03:25 -03:00
25 changed files with 579 additions and 192 deletions
+8 -1
View File
@@ -105,7 +105,14 @@ for path in scripts:lines() do
end
end
scripts:close()
check(#TRADE_ROWS == 7, "the nine-row trade table's seven reachable trades are scripted")
check(#TRADE_ROWS == 8, "all eight scripted trade rows are found")
-- trade slot 6 is scripted twice: Red's youngster and Yellow's gate cook
-- share the index and its done flag, one row per version (#651)
local slot6 = 0
for _, row in ipairs(TRADE_ROWS) do
if row.index == 6 then slot6 = slot6 + 1 end
end
eq(slot6, 2, "trade slot 6 has a row for each version")
-- ------------------------------------------------------------------
-- round trips
+130
View File
@@ -0,0 +1,130 @@
-- Parity: a branched flavor talk script shows exactly ONE text per branch
-- (#719). The buggy layout was
--
-- check_flag X / jump_if_true <jumpRow> / show before / jump <afterRow> /
-- show after
--
-- where the fall-through "jump" landed ON the after-text row instead of
-- ending the script, so talking to the Silph Co. workers (and the other
-- NPCs below) before Giovanni showed the worried line and then the
-- relieved line right after it. ScriptRunner executes the row a numeric
-- jump targets, so the fixed scripts point jump_if_true at the after-text
-- row directly and end the before-branch with jump "end".
--
-- The walker below is a minimal read of ScriptRunner's jump semantics:
-- checks are forced to one outcome, every non-control command is a no-op,
-- and show_text/ask rows are recorded in order.
package.path = "./?.lua;./?/init.lua;" .. package.path
local S = require("tests.harness").suite("parity flavor talk branches")
local check, eq = S.check, S.eq
local scripts = require("data.scripts.init")
local function textsShown(script, checkResult)
local texts, pc, steps = {}, 1, 0
local last = nil
while pc <= #script do
steps = steps + 1
if steps > 200 then error("script did not terminate", 0) end
local row = script[pc]
local cmd = row[1]
if cmd == "show_text" or cmd == "ask" then
texts[#texts + 1] = row[2]
pc = pc + 1
elseif cmd == "check_flag" or cmd == "check_item" then
last = checkResult
pc = pc + 1
elseif cmd == "jump" then
if row[2] == "end" then break end
pc = row[2]
elseif cmd == "jump_if_true" or cmd == "jump_if_false" then
local take = (cmd == "jump_if_true") == (last == true)
if take then
if row[2] == "end" then break end
pc = row[2]
else
pc = pc + 1
end
else
pc = pc + 1
end
end
return texts
end
-- { mapId, textConst, before-text, after-text }
local cases = {
{ "SILPH_CO_3F", "TEXT_SILPHCO3F_SILPH_WORKER_M",
"_SilphCo3FSilphWorkerMWhatShouldIDoText",
"_SilphCo3FSilphWorkerMYouSavedUsText" },
{ "SILPH_CO_4F", "TEXT_SILPHCO4F_SILPH_WORKER_M",
"_SilphCo4FSilphWorkerMImHidingText",
"_SilphCo4FSilphWorkerMTeamRocketIsGoneText" },
{ "SILPH_CO_5F", "TEXT_SILPHCO5F_SILPH_WORKER_M",
"_SilphCo5FSilphWorkerMThatsYouRightText",
"_SilphCo5FSilphWorkerMYoureOurHeroText" },
{ "SILPH_CO_6F", "TEXT_SILPHCO6F_SILPH_WORKER_M1",
"_SilphCo6FSilphWorkerM1TookOverTheBuildingText",
"_SilphCo6FSilphWorkerM1BackToWorkText" },
{ "SILPH_CO_6F", "TEXT_SILPHCO6F_SILPH_WORKER_M2",
"_SilphCo6FSilphWorkerMHelpMePleaseText",
"_SilphCo6FSilphWorkerMWeGotEngagedText" },
{ "SILPH_CO_6F", "TEXT_SILPHCO6F_SILPH_WORKER_F1",
"_SilphCo6FSilphWorkerF1SuchACowardText",
"_SilphCo6FSilphWorkerF1HaveToMarryHimText" },
{ "SILPH_CO_6F", "TEXT_SILPHCO6F_SILPH_WORKER_F2",
"_SilphCo6FSilphWorkerF2TeamRocketConquerWorldText",
"_SilphCo6FSilphWorkerF2TeamRocketRanText" },
{ "SILPH_CO_6F", "TEXT_SILPHCO6F_SILPH_WORKER_M3",
"_SilphCo6FSilphWorkerM3TargetedSilphText",
"_SilphCo6FSilphWorkerM3WorkForSilphText" },
{ "SILPH_CO_7F", "TEXT_SILPHCO7F_SILPH_WORKER_M2",
"_SilphCo7FSilphWorkerM2AfterTheMasterBallText",
"_SilphCo7FSilphWorkerM2CancelledMasterBallText" },
{ "SILPH_CO_7F", "TEXT_SILPHCO7F_SILPH_WORKER_M3",
"_SilphCo7FSilphWorkerM3ItWouldBeBadText",
"_SilphCo7FSilphWorkerM3YouChasedOffTeamRocketText" },
{ "SILPH_CO_7F", "TEXT_SILPHCO7F_SILPH_WORKER_M4",
"_SilphCo7FSilphWorkerM4ItsReallyDangerousHereText",
"_SilphCo7FSilphWorkerM4SafeAtLastText" },
{ "SILPH_CO_8F", "TEXT_SILPHCO8F_SILPH_WORKER_M",
"_SilphCo8FSilphWorkerMSilphIsFinishedText",
"_SilphCo8FSilphWorkerMThanksForSavingUsText" },
{ "SILPH_CO_10F", "TEXT_SILPHCO10F_SILPH_WORKER_F",
"_SilphCo10FSilphWorkerFImScaredText",
"_SilphCo10FSilphWorkerFQuietAboutMyCryingText" },
{ "CERULEAN_TRASHED_HOUSE", "TEXT_CERULEANTRASHEDHOUSE_FISHING_GURU",
"_CeruleanTrashedHouseFishingGuruTheyStoleATMText",
"_CeruleanTrashedHouseFishingGuruWhatsLostIsLostText" },
{ "LAVENDER_MART", "TEXT_LAVENDERMART_COOLTRAINER_M",
"_LavenderMartCooltrainerMReviveText",
"_LavenderMartCooltrainerMNuggetText" },
{ "ROUTE_16_GATE_1F", "TEXT_ROUTE16GATE1F_GUARD",
"_Route16Gate1FGuardNoPedestriansAllowedText",
"_Route16Gate1FGuardCyclingRoadExplanationText" },
{ "ROUTE_18_GATE_1F", "TEXT_ROUTE18GATE1F_GUARD",
"_Route18Gate1FGuardYouNeedABicycleText",
"_Route18Gate1FGuardCyclingRoadUphillText" },
{ "MR_FUJIS_HOUSE", "TEXT_MRFUJISHOUSE_SUPER_NERD",
"_MrFujisHouseSuperNerdMrFujiIsntHereText",
"_MrFujisHouseSuperNerdMrFujiHadBeenPrayingText" },
{ "MR_FUJIS_HOUSE", "TEXT_MRFUJISHOUSE_LITTLE_GIRL",
"_MrFujisHouseLittleGirlThisIsMrFujisHouseText",
"_MrFujisHouseLittleGirlPokemonAreNiceToHugText" },
}
for _, case in ipairs(cases) do
local mapId, textConst, beforeText, afterText = case[1], case[2], case[3], case[4]
local script = scripts.talkScript(mapId, textConst)
check(script ~= nil, mapId .. "/" .. textConst .. " has a talk script")
if script then
local before = textsShown(script, false)
eq(#before, 1, textConst .. " shows exactly one text before the event")
eq(before[1], beforeText, textConst .. " shows the before-text")
local after = textsShown(script, true)
eq(#after, 1, textConst .. " shows exactly one text after the event")
eq(after[1], afterText, textConst .. " shows the after-text")
end
end
S.finish()
+70
View File
@@ -0,0 +1,70 @@
-- Parity: the Fuchsia City exhibit signs open the exhibited species' Pokédex
-- entry and mark it seen (pokered/scripts/FuchsiaCity.asm). The fossil sign
-- shows the fossil the player did NOT take at Mt. Moon, and opens no entry
-- at all before either fossil is picked up.
package.path = "./?.lua;./?/init.lua;" .. package.path
local S = require("tests.harness").suite("parity fuchsia exhibit signs")
local check, eq = S.check, S.eq
local scripts = require("data.scripts.init")
local exhibits = {
TEXT_FUCHSIACITY_CHANSEY_SIGN = { "_FuchsiaCityChanseySignText", "CHANSEY" },
TEXT_FUCHSIACITY_VOLTORB_SIGN = { "_FuchsiaCityVoltorbSignText", "VOLTORB" },
TEXT_FUCHSIACITY_KANGASKHAN_SIGN = { "_FuchsiaCityKangaskhanSignText", "KANGASKHAN" },
TEXT_FUCHSIACITY_SLOWPOKE_SIGN = { "_FuchsiaCitySlowpokeSignText", "SLOWPOKE" },
TEXT_FUCHSIACITY_LAPRAS_SIGN = { "_FuchsiaCityLaprasSignText", "LAPRAS" },
}
for textConst, exhibit in pairs(exhibits) do
local script = scripts.talkScript("FUCHSIA_CITY", textConst)
check(script ~= nil, textConst .. " has a talk script")
if script then
eq(script[1][1], "show_text", textConst .. " prints its line first")
eq(script[1][2], exhibit[1], textConst .. " prints its own sign text")
eq(script[2][1], "mark_seen", textConst .. " marks the species seen")
eq(script[2][2], exhibit[2], textConst .. " marks " .. exhibit[2] .. " seen")
eq(script[3][1], "push_screen", textConst .. " opens the Pokédex entry")
eq(script[3][2], "DexEntryMenu", textConst .. " uses DexEntryMenu")
eq(script[3][3], exhibit[2], textConst .. " shows " .. exhibit[2])
end
end
local fossil = scripts.talkScript("FUCHSIA_CITY", "TEXT_FUCHSIACITY_FOSSIL_SIGN")
check(fossil ~= nil, "the fossil sign has a talk script")
if fossil then
eq(fossil[1][1], "check_flag", "the fossil sign checks the Dome Fossil event")
eq(fossil[1][2], "EVENT_GOT_DOME_FOSSIL", "Dome Fossil event first")
eq(fossil[3][1], "check_flag", "the fossil sign checks the Helix Fossil event")
eq(fossil[3][2], "EVENT_GOT_HELIX_FOSSIL", "Helix Fossil event second")
-- neither fossil taken: only the undetermined line, no dex entry
eq(fossil[5][1], "show_text", "the undetermined branch prints its line")
eq(fossil[5][2], "_FuchsiaCityFossilSignUndeterminedText",
"the undetermined branch uses the undetermined text")
eq(fossil[6][1], "jump", "the undetermined branch stops there")
check(fossil[6][2] == "end", "the undetermined branch never reaches a dex entry")
-- Dome Fossil taken: the exhibit holds Omanyte
eq(fossil[2][1], "jump_if_true", "the Dome branch jumps when the event is set")
eq(fossil[2][2], 7, "the Dome branch lands on the Omanyte rows")
eq(fossil[7][2], "_FuchsiaCityFossilSignOmanyteText", "Dome taken: Omanyte text")
eq(fossil[8][1], "mark_seen", "Dome taken: marks the exhibited species seen")
eq(fossil[8][2], "OMANYTE", "Dome taken: marks Omanyte seen")
eq(fossil[9][1], "push_screen", "Dome taken: opens the Pokédex entry")
eq(fossil[9][3], "OMANYTE", "Dome taken: shows Omanyte")
-- Helix Fossil taken: the exhibit holds Kabuto
eq(fossil[4][1], "jump_if_true", "the Helix branch jumps when the event is set")
eq(fossil[4][2], 11, "the Helix branch lands on the Kabuto rows")
eq(fossil[11][2], "_FuchsiaCityFossilSignKabutoText", "Helix taken: Kabuto text")
eq(fossil[12][2], "KABUTO", "Helix taken: marks Kabuto seen")
eq(fossil[13][3], "KABUTO", "Helix taken: shows Kabuto")
end
local save = { pokedex = { seen = {}, owned = {} } }
require("src.script.Commands").mark_seen({ save = save }, "OMANYTE")
check(save.pokedex.seen.OMANYTE, "the preview records the species as seen")
check(not save.pokedex.owned.OMANYTE, "the preview does not mark it owned")
S.finish()
@@ -0,0 +1,58 @@
-- Parity: Yellow's Route 18 Gate 2F trader is a cook offering SPIKE
-- (TANGELA -> PARASECT), not Red's youngster (pokeyellow/scripts/
-- Route18Gate2F.asm Route18Gate2FCookText, TRADE_FOR_SPIKE). The port
-- only wired TEXT_ROUTE18GATE2F_YOUNGSTER, so on Yellow the cook had no
-- talk script at all and never answered (#651).
package.path = "./?.lua;./?/init.lua;" .. package.path
if not _G.love then _G.love = require("tests.love_stub") end
local S = require("tests.harness").suite("parity Yellow route 18 gate trade")
local check, eq = S.check, S.eq
local scripts = require("data.scripts.init")
local cook = scripts.talkScript("ROUTE_18_GATE_2F", "TEXT_ROUTE18GATE2F_COOK")
check(cook ~= nil, "the Yellow cook has a talk script")
if cook then
eq(cook[2][1], "trade", "the cook runs a trade")
eq(cook[2][2], 6, "the cook's trade is TRADE_FOR_SPIKE (index 6)")
eq(cook[2][3], "EVENT_TRADED_SLOWBRO_FOR_LICKITUNG",
"the cook shares slot 6's done flag, so a converted .sav stays done")
end
-- Red's youngster row stays put: trade 6 is MARC on Red's table and the
-- two rows share the index across versions.
local youngster = scripts.talkScript("ROUTE_18_GATE_2F", "TEXT_ROUTE18GATE2F_YOUNGSTER")
check(youngster ~= nil, "Red's youngster keeps his talk script")
if youngster then
eq(youngster[2][1], "trade", "the youngster runs a trade")
eq(youngster[2][2], 6, "the youngster's trade stays index 6")
end
-- Yellow's map only has the cook, so the new key is the one that fires.
local manifestFile = assert(io.open("tools/rom_manifest_yellow.json", "r"))
local manifest = manifestFile:read("*a")
manifestFile:close()
check(manifest:find('"name": "ROUTE18GATE2F_COOK"', 1, true) ~= nil,
"Yellow's Route18Gate2F object is the cook")
check(manifest:find('"TEXT_ROUTE18GATE2F_COOK"', 1, true) ~= nil,
"the cook carries TEXT_ROUTE18GATE2F_COOK")
check(manifest:find("ROUTE18GATE2F_YOUNGSTER", 1, true) == nil,
"Yellow's Route18Gate2F has no youngster")
-- The Yellow table makes index 6 the SPIKE row (TANGELA -> PARASECT).
local Data = require("src.core.Data")
if not (Data.field and Data.field.trades) then Data:load() end
local GameVersion = require("src.core.GameVersion")
local oldVersion = GameVersion.get()
local oldTrades = Data.field.trades
GameVersion.set("yellow")
Data:applyVersionedFieldData()
local spike = Data.field.trades[6]
check(spike.give == "TANGELA" and spike.get == "PARASECT"
and spike.nickname == "SPIKE",
"on Yellow trade 6 is SPIKE (TANGELA -> PARASECT)")
Data.field.trades = oldTrades
GameVersion.set(oldVersion)
S.finish()