Files
gen1recomp/tests/drivers/gold_rock_smash_probe.lua
bryanthaboi ae6cac89e1 G2 support
2026-08-11 11:53:30 -04:00

160 lines
6.1 KiB
Lua

-- ROCK SMASH's wild encounter, and the beasts scattering on CONTINUE, driven
-- through the real game.
--
-- POKEPORT_IDENTITY=gold-dev POKEPORT_GAME=gold POKEPORT_SPEED=200 \
-- POKEPORT_GOLD_RESUME=13 \
-- POKEPORT_DRIVER=tests/drivers/gold_rock_smash_probe.lua love .
--
-- Two things a ROM-free suite cannot see, because both live behind World:load:
--
-- * the `readMem` hook. RockSmashScript is `callasm RockMonEncounter /
-- readmem wTempWildMonSpecies / iffalse .done / randomwildmon /
-- startbattle`, and the byte only reads back if World:load installed the
-- seam -- otherwise the VM answers out of its own sparse store, sees 0 and
-- skips the battle, which is what the port did for every smash in the game.
-- * `farcall JumpRoamMons` on the continue path
-- (engine/menus/intro_menu.asm), which is World:roamMonsOnContinue and runs
-- from inside World:load rather than from any map setup script.
--
-- Prints one PASS/FAIL line per claim and quits, so a killed run is visibly
-- incomplete rather than silently green.
local Bot = dofile("tests/drivers/gold/bot.lua")
local A = Bot.adapter
-- data/wild/treemon_maps.asm RockMonMaps: the rock at (16,14) of Dark Cave's
-- Violet entrance is object 2, reachable from (15,14).
local ROCK_MAP = "DARK_CAVE_VIOLET_ENTRANCE"
local ROCK_X, ROCK_Y = 16, 14
local STAND_X, STAND_Y = 15, 14
-- constants/pokemon_constants.asm; TreeMonSet_Rock is 90 KRABBY / 10 SHUCKLE.
local ROCK_SPECIES = { [98] = "KRABBY", [213] = "SHUCKLE" }
local results = {}
local function claim(ok, text)
results[#results + 1] = ok and true or false
print((ok and "[rocksmash] PASS " or "[rocksmash] FAIL ") .. text)
end
return function(game)
local bot = Bot.new(game)
for _ = 1, 3000 do
if A.ready(game) then break end
bot:wait(1)
end
local resume = os.getenv("POKEPORT_GOLD_RESUME") or "13"
local ok, err = A.loadCheckpoint(game, resume)
if not ok then
print(("[rocksmash] cannot resume %s: %s"):format(resume, tostring(err)))
love.event.quit()
return
end
for _ = 1, 3000 do
if A.ready(game) then break end
bot:wait(1)
end
-- ---- JumpRoamMons on CONTINUE -------------------------------------------
--
-- Park all three beasts on one map, then load the very same save again the
-- way the CONTINUE menu does. JumpRoamMon re-rolls off the player's own map
-- and picks one of sixteen otherwise, so all three staying put is a 1 in
-- 4096 coincidence rather than a passing implementation.
local save = game.save
if save and not save.roamers then
-- `special InitRoamMons`, which the Burned Tower basement runs when the
-- floor gives way. A checkpoint taken before that has no structs, and the
-- scatter below is about the CONTINUE path rather than about this.
require("src.core.gen2.Roamers").init(save, { force = true })
print("[rocksmash] note: seeded InitRoamMons for this checkpoint")
end
if not (save and save.roamers) then
claim(false, "the save has roamers to scatter")
else
for _, slot in ipairs(save.roamers) do
if slot.species then slot.map = "ROUTE_29" end
end
local continued = pcall(game.continueGame, game, save)
for _ = 1, 3000 do
if A.ready(game) then break end
bot:wait(1)
end
local moved = 0
for _, slot in ipairs(game.save.roamers or {}) do
if slot.species and slot.map ~= "ROUTE_29" then moved = moved + 1 end
end
claim(continued and moved > 0,
("CONTINUE scattered the beasts (%d of 3 left ROUTE_29)"):format(moved))
end
-- ---- RockMonEncounter ----------------------------------------------------
local world = game.world
world:setMap(ROCK_MAP, STAND_X, STAND_Y, "right")
bot:wait(30)
bot:clearDialogue(nil, 4000)
claim(A.mapId(game) == ROCK_MAP, "arrived at " .. ROCK_MAP)
-- HasRockSmash is CheckPartyMove: the lead has to know the move for
-- AskRockSmashScript to open at all. Teaching it is the setup, not the
-- thing under test.
local lead = game.save.party and game.save.party[1]
if lead then
lead.moves = lead.moves or {}
lead.moves[#lead.moves + 1] = { id = "ROCK_SMASH", pp = 15, maxPp = 15 }
end
claim(lead ~= nil, "the lead can be taught ROCK SMASH")
-- `ld a, 10 / RandomRange / cp 4` and then SelectTreeMon's 0..99: a zero
-- passes the 40 percent and lands in the 90 percent KRABBY bracket, so the
-- smash below is the deterministic case.
world.rockmonRandom = function() return 0 end
-- Dark Cave is a wild-encounter map; the walk to the rock must not be
-- interrupted by one, and the battle under test comes from the script.
world.noWildEncounters = true
-- The `startbattle` RockSmashScript ends on comes through here, and the bot
-- fights the battle out before any poll of its own could see it -- so the
-- species is read off the seam the script itself reaches.
local fought
local realScripted = world.startScriptedBattle
world.startScriptedBattle = function(self, record, wild, onDone)
if wild and wild.species then fought = wild.species end
return realScripted(self, record, wild, onDone)
end
local reached = bot:approachAndFace(ROCK_X, ROCK_Y)
claim(reached, "faced the rock at " .. ROCK_X .. "," .. ROCK_Y)
for attempt = 1, 4 do
bot:tap("a")
bot:wait(4)
for _ = 1, 40 do
if A.busy(game) then break end
bot:wait(1)
end
if A.busy(game) then break end
print("[rocksmash] tap " .. attempt .. " opened nothing")
end
-- AskRockSmashScript's yesorno, then the smash, the earthquake and the roll.
bot:clearDialogue({ "yes" }, 8000)
for _ = 1, 600 do
if fought or not A.busy(game) then break end
bot:wait(1)
end
claim(fought ~= nil, "the smash reached startbattle at all")
claim(fought ~= nil and ROCK_SPECIES[fought] ~= nil,
"and the wild mon came out of TREEMON_SET_ROCK (got "
.. tostring(fought and ROCK_SPECIES[fought] or fought) .. ")")
local failures = 0
for _, value in ipairs(results) do
if not value then failures = failures + 1 end
end
print(("[rocksmash] %d claims, %d failed"):format(#results, failures))
love.event.quit()
end