mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
250 lines
9.1 KiB
Lua
250 lines
9.1 KiB
Lua
-- Independent verification probe for the Gold battle screen lane.
|
|
-- Temporary: attacks the same claims from a different angle than
|
|
-- tests/drivers/gold_battle_screen_probe.lua.
|
|
--
|
|
-- POKEPORT_GAME=gold POKEPORT_DRIVER=tests/drivers/verify_battle_screen.lua \
|
|
-- POKEPORT_VPROBE=switch1 POKEPORT_SHOT_DIR=/tmp/verify-battle/v love .
|
|
|
|
local U = require("tests.drivers.util")
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
|
|
local OUT = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/verify-battle/v"
|
|
|
|
local hostVisible = love.visible
|
|
love.visible = function(v) if hostVisible then pcall(hostVisible, v) end end
|
|
|
|
local function tap(game, button, frames)
|
|
game.input.pressQueue[#game.input.pressQueue + 1] = button
|
|
game.input.state[button] = true
|
|
U.wait(2)
|
|
game.input.state[button] = false
|
|
U.wait(frames or 6)
|
|
end
|
|
|
|
local function openBattle(game, opts)
|
|
assert(game.world:startBattle(opts), "startBattle failed")
|
|
for _ = 1, 900 do
|
|
local top = game.stack:top()
|
|
if top and top.battle then return top end
|
|
U.wait(1)
|
|
end
|
|
error("battle screen never came up")
|
|
end
|
|
|
|
local function toMenu(game, battle, limit)
|
|
for _ = 1, (limit or 400) do
|
|
if battle.phase == "menu" then return true end
|
|
if battle.battle.over then return false end
|
|
tap(game, "a", 2)
|
|
end
|
|
return battle.phase == "menu"
|
|
end
|
|
|
|
local V = {}
|
|
|
|
-- Bug 4, the real player route: after the faint, move the cursor to a HEALTHY
|
|
-- mon and press A exactly ONCE. The claim under test is that the switch takes
|
|
-- on that press; the author's probe only ever proved the refusal LINE.
|
|
function V.switch1(game)
|
|
local weak = Mon.new(game.data, "CYNDAQUIL", 5)
|
|
weak.hp = 1
|
|
game.save.party = { weak, Mon.new(game.data, "TOTODILE", 30) }
|
|
game.save.inventory = {}
|
|
local entry = game.world:trainerParty(36, 1)
|
|
local Trainers = require("src.world.gen2.Trainers")
|
|
entry.party = Trainers.party(game.data, entry)
|
|
local battle = openBattle(game, { trainer = entry })
|
|
assert(toMenu(game, battle), "never reached the battle menu")
|
|
|
|
local aPresses, listOpens = 0, 0
|
|
local realOpen = battle.openParty
|
|
battle.openParty = function(self, forced)
|
|
if forced then listOpens = listOpens + 1 end
|
|
return realOpen(self, forced)
|
|
end
|
|
|
|
local switched, moved = false, false
|
|
for _ = 1, 1200 do
|
|
if battle.battle.over then break end
|
|
local top = game.stack:top()
|
|
if top ~= battle then
|
|
-- The forced list. Move down to the healthy mon FIRST, then one A.
|
|
if not moved then tap(game, "down", 4); moved = true end
|
|
aPresses = aPresses + 1
|
|
tap(game, "a", 6)
|
|
U.wait(20)
|
|
if battle.battle.player and (battle.battle.player.hp or 0) > 0
|
|
and battle.battle.player.species == "TOTODILE" then
|
|
switched = true
|
|
break
|
|
end
|
|
elseif battle.phase == "menu" then
|
|
tap(game, "a", 2); U.wait(3); tap(game, "a", 2)
|
|
else
|
|
tap(game, "a", 2)
|
|
end
|
|
end
|
|
print(("[v] switch1: listOpens=%d aPressesOnList=%d switched=%s active=%s")
|
|
:format(listOpens, aPresses, tostring(switched),
|
|
tostring(battle.battle.player and battle.battle.player.species)))
|
|
U.shot(game, OUT .. "/switch1-after.png")
|
|
end
|
|
|
|
-- Bug 3, the NORMAL save: currentBox is 1, the value every fresh Gold save and
|
|
-- every save the engine itself writes carries. If the catch only reached a box
|
|
-- because of the clamp, this is where that shows.
|
|
function V.box1(game)
|
|
local party = {}
|
|
for _ = 1, 6 do party[#party + 1] = Mon.new(game.data, "CYNDAQUIL", 30) end
|
|
game.save.party = party
|
|
game.save.boxes = nil
|
|
game.save.currentBox = tonumber(os.getenv("POKEPORT_VBOX") or "1")
|
|
game.save.inventory = { MASTER_BALL = 5 }
|
|
local battle = openBattle(game, { wild = Mon.new(game.data, "PIDGEY", 5) })
|
|
assert(toMenu(game, battle), "never reached the battle menu")
|
|
battle:useItem("MASTER_BALL")
|
|
for _ = 1, 600 do
|
|
if not battle.anim then break end
|
|
U.wait(1)
|
|
end
|
|
for _ = 1, 300 do
|
|
if battle.phase == "ask-nickname" then tap(game, "b", 4)
|
|
else tap(game, "a", 3) end
|
|
if battle.phase == "done" or not game.stack:top() then break end
|
|
end
|
|
local total, where = 0, "nowhere"
|
|
for i, box in pairs(game.save.boxes or {}) do
|
|
total = total + #box
|
|
if #box > 0 then where = "box" .. tostring(i) end
|
|
end
|
|
print(("[v] box1: currentBox=%s party=%d anywhere=%d landedIn=%s boxFilled=%s")
|
|
:format(tostring(game.save.currentBox), #game.save.party, total, where,
|
|
tostring(battle.battle.boxFilled)))
|
|
end
|
|
|
|
-- The picHidden REGRESSION risk: a trainer whose first mon faints must send its
|
|
-- SECOND one out and that mon must be visible. A latch that a send-out fails
|
|
-- to clear leaves an invisible opponent for the rest of the battle.
|
|
function V.secondmon(game)
|
|
game.save.party = { Mon.new(game.data, "TOTODILE", 40) }
|
|
game.save.inventory = {}
|
|
-- Find a trainer entry that actually carries two mons.
|
|
local Trainers = require("src.world.gen2.Trainers")
|
|
local entry, size
|
|
for class = 1, 60 do
|
|
for member = 1, 6 do
|
|
local ok, e = pcall(game.world.trainerParty, game.world, class, member)
|
|
if ok and e then
|
|
local party = Trainers.party(game.data, e)
|
|
if party and #party >= 2 then
|
|
entry, size = e, #party
|
|
e.party = party
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if entry then break end
|
|
end
|
|
assert(entry, "no multi-mon trainer found")
|
|
print(("[v] secondmon: trainer class=%s members=%d")
|
|
:format(tostring(entry.classId or entry.class), size))
|
|
local battle = openBattle(game, { trainer = entry })
|
|
assert(toMenu(game, battle), "never reached the battle menu")
|
|
|
|
local sawSecond, hiddenAtSecond, shot = false, nil, 0
|
|
for _ = 1, 2000 do
|
|
if battle.battle.over then break end
|
|
if battle.battle.enemyIndex and battle.battle.enemyIndex > 1
|
|
and not sawSecond then
|
|
sawSecond = true
|
|
-- Let the send-out settle, then look at the latch and shoot it.
|
|
U.wait(90)
|
|
hiddenAtSecond = battle.picHidden.enemy
|
|
U.shot(game, OUT .. "/secondmon-out.png")
|
|
end
|
|
if battle.phase == "menu" then
|
|
tap(game, "a", 2); U.wait(3); tap(game, "a", 2)
|
|
else
|
|
if shot < 3 and battle.faintSlide then
|
|
U.shot(game, ("%s/secondmon-faint-%02d.png"):format(OUT,
|
|
battle.faintSlide.frames))
|
|
shot = shot + 1
|
|
end
|
|
tap(game, "a", 2)
|
|
end
|
|
end
|
|
print(("[v] secondmon: sawSecond=%s picHidden.enemy@second=%s outcome=%s")
|
|
:format(tostring(sawSecond), tostring(hiddenAtSecond),
|
|
tostring(battle.battle.outcome)))
|
|
U.shot(game, OUT .. "/secondmon-end.png")
|
|
end
|
|
|
|
-- Bug 2, the half that is cache-side: what the DEFAULT identity's Gold cache
|
|
-- actually carries, and what the class key resolves to.
|
|
function V.trainerpic(game)
|
|
local hud = game.data.gen2MenuGfx and game.data.gen2MenuGfx.battleHud
|
|
local pics = hud and hud.trainerPics
|
|
local n = 0
|
|
local sample
|
|
for k in pairs(pics or {}) do n = n + 1; sample = sample or k end
|
|
print(("[v] trainerpic: cache trainerPics=%s count=%d sample=%s")
|
|
:format(tostring(pics ~= nil), n, tostring(sample)))
|
|
local Trainers = require("src.world.gen2.Trainers")
|
|
local entry = game.world:trainerParty(36, 1)
|
|
print(("[v] trainerpic: lookup(36,1) class=%s classId=%s className=%s")
|
|
:format(tostring(entry and entry.class), tostring(entry and entry.classId),
|
|
tostring(entry and entry.className)))
|
|
entry.party = Trainers.party(game.data, entry)
|
|
local battle = openBattle(game, { trainer = entry })
|
|
print(("[v] trainerpic: enemyTrainerClass=%s showEnemyTrainer=%s path=%s")
|
|
:format(tostring(battle.enemyTrainerClass),
|
|
tostring(battle.showEnemyTrainer), tostring(battle.enemyTrainerPath)))
|
|
for i = 0, 8 do
|
|
U.shot(game, ("%s/trainerpic-%02d.png"):format(OUT, i))
|
|
U.wait(10)
|
|
end
|
|
end
|
|
|
|
-- The counterfactual to bug 1: a ball that FAILS. BattleAnim_ThrowPokeBall's
|
|
-- break-out arm puts the mon back on the field, so a latch set anywhere but on
|
|
-- the caught arm would make a wild mon vanish for the rest of the battle.
|
|
function V.missball(game)
|
|
game.save.party = { Mon.new(game.data, "TOTODILE", 40) }
|
|
game.save.inventory = { POKE_BALL = 30 }
|
|
local wild = Mon.new(game.data, "ONIX", 40) -- full HP, low catch rate
|
|
local battle = openBattle(game, { wild = wild })
|
|
assert(toMenu(game, battle), "never reached the battle menu")
|
|
local tries, escaped = 0, false
|
|
for _ = 1, 20 do
|
|
tries = tries + 1
|
|
battle:useItem("POKE_BALL")
|
|
for _ = 1, 600 do
|
|
if not battle.anim then break end
|
|
U.wait(1)
|
|
end
|
|
U.wait(40)
|
|
if battle.battle.outcome ~= "caught" and not battle.battle.over then
|
|
escaped = true
|
|
break
|
|
end
|
|
if battle.battle.over then break end
|
|
end
|
|
print(("[v] missball: tries=%d escaped=%s picHidden.enemy=%s outcome=%s")
|
|
:format(tries, tostring(escaped), tostring(battle.picHidden.enemy),
|
|
tostring(battle.battle.outcome)))
|
|
for i = 0, 3 do
|
|
U.shot(game, ("%s/missball-%02d.png"):format(OUT, i))
|
|
tap(game, "a", 8)
|
|
end
|
|
end
|
|
|
|
local name = os.getenv("POKEPORT_VPROBE") or "switch1"
|
|
return function(game)
|
|
U.wait(45)
|
|
assert(game.world and game.world.map, "gold world did not boot")
|
|
print("[v] ---- " .. name)
|
|
assert(V[name], "no such probe: " .. name)(game)
|
|
print("[v] done, shots in " .. OUT)
|
|
love.event.quit()
|
|
end
|