Fix Gen 1/2 battle, menu, PC and audio parity gaps; wire luacheck into CI

CLOSES #1484, CLOSES #1486, CLOSES #1513, CLOSES #1517, CLOSES #1556, CLOSES #1564, CLOSES #1567
This commit is contained in:
bryanthaboi
2026-08-21 19:22:34 -04:00
parent 087a275189
commit c0f4315cd8
45 changed files with 2487 additions and 168 deletions
+142
View File
@@ -0,0 +1,142 @@
-- #1556, the two battle jingles: _NewDexDataText's sound_slot_machine_start
-- (data/text/common_3.asm:285) on a first catch, and _LearnedMoveText's
-- sound_dex_fanfare_50_79 (data/text/common_3.asm:119) on a level-up move.
--
-- POKEPORT_IDENTITY=gold-dev POKEPORT_GAME=gold POKEPORT_TOUCH=0 \
-- POKEPORT_DRIVER=tests/drivers/gold_bug1556_battle.lua \
-- perl -e 'alarm 420; exec @ARGV' \
-- python3 -c "import pty; pty.spawn(['love','.'])"
-- POKEPORT_SHOT_DIR=/tmp/gold-bug1556-battle (default)
--
-- Both ride the message queue's `sfx` + `waitSfx` keys, which the "Gotcha!"
-- line and the "grew to level" line already used -- the two lines next door
-- to them did not. So the ear is comparing neighbours: the catch jingle must
-- be followed by a SECOND, different one over the #DEX line, and the level-up
-- fanfare by a second over "learned".
local U = require("tests.drivers.util")
local Mon = require("src.battle.gen2.Mon")
local Sound = require("src.core.Sound")
return function(game)
local out = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/gold-bug1556-battle"
local heard = {}
local realPlay = Sound.play
Sound.play = function(data, name)
heard[#heard + 1] = name
return realPlay(data, name)
end
local function reset() heard = {} end
local function report(label, want)
U.log(label, #heard > 0 and table.concat(heard, ", ") or "(silence)")
U.log(" want:", want)
end
U.wait(45)
local world = game.world
assert(world and world.map, "gold world did not boot")
local save, data = game.save, game.data
local function battleScreen()
local top = game.stack:top()
return (top and top.battle) and top or nil
end
local function tapUntil(predicate, tries, btn)
for _ = 1, tries or 400 do
if predicate() then return true end
U.tap(game, btn or "a")
U.wait(2)
end
return predicate()
end
-- ---- 1. a first catch: "Gotcha!" then the #DEX line ---------------------
save.party = { Mon.new(data, "CYNDAQUIL", 20) }
save.inventory = { MASTER_BALL = 1 }
save.pokedexReceived = true
save.pokedex = { seen = {}, caught = {} }
local wild = Mon.new(data, "PIDGEY", 3)
assert(wild, "the cache carries no PIDGEY")
assert(world:startBattle({ wild = wild }), "the wild battle refused to start")
assert(tapUntil(function()
local screen = battleScreen()
return screen ~= nil and screen.phase == "menu"
end), "the battle never reached BattleMenu")
local screen = battleScreen()
-- BattleMenuHeader's 2x2 grid: 1 FIGHT / 2 PkMn, 3 PACK / 4 RUN.
if screen.menuIndex % 2 == 0 then U.tap(game, "left") U.wait(3) end
if screen.menuIndex <= 2 then U.tap(game, "down") U.wait(3) end
U.tap(game, "a")
U.wait(6)
local pack = game.stack:top()
assert(pack and pack.rows, "the battle PACK did not open")
local ballRow
for index, row in ipairs(pack.rows) do
if row.id == "MASTER_BALL" then ballRow = index end
end
assert(ballRow, "the battle PACK does not show the MASTER BALL")
for _ = 2, ballRow do U.tap(game, "down") U.wait(2) end
reset()
U.tap(game, "a")
U.wait(180) -- the throw, the wobbles, "Gotcha!"
report("01 catch:",
"Sfx_CaughtMon, then Sfx_SlotMachineStart over the #DEX line")
U.shot(game, out .. "/01-caught.png")
for _ = 1, 6 do U.tap(game, "a") U.wait(20) end
U.shot(game, out .. "/02-dex-line.png")
for _ = 1, 30 do
if not battleScreen() then break end
U.tap(game, "a")
U.wait(10)
end
-- ---- 2. a level-up that teaches a move ---------------------------------
local SPECIES = "CYNDAQUIL"
local def = data.pokemon and data.pokemon[SPECIES]
local target
for _, entry in ipairs((def and def.levelMoves) or {}) do
if entry.level and entry.level > 6 and not target then target = entry.level end
end
if not target then
U.log("SKIP 02 -- no level-up move row for " .. SPECIES)
else
local hero = Mon.new(data, SPECIES, target - 1)
local growth = Mon.growthFor(data, def.growthRate)
hero.experience = Mon.experienceForLevel(growth, target) - 1
-- Only three moves, so LearnMove takes the free slot rather than ForgetMove
-- (engine/pokemon/learn.asm:23-29).
while hero.moves and #hero.moves > 3 do table.remove(hero.moves) end
save.party = { hero }
save.inventory = {}
U.log(("levelling %s %d -> %d for its %s row")
:format(SPECIES, hero.level, target, tostring(target)))
local prey = Mon.new(data, "PIDGEY", 2)
prey.hp = 1
assert(world:startBattle({ wild = prey }), "the second battle refused")
assert(tapUntil(function()
local s = battleScreen()
return s ~= nil and s.phase == "menu"
end), "the second battle never reached BattleMenu")
screen = battleScreen()
if screen.menuIndex % 2 == 0 then U.tap(game, "left") U.wait(3) end
if screen.menuIndex > 2 then U.tap(game, "up") U.wait(3) end
reset()
U.tap(game, "a") -- FIGHT
U.wait(6)
U.tap(game, "a") -- the first move
U.wait(240)
report("02 level-up move:",
"Sfx_DexFanfare5079 twice: \"grew to level\" and \"learned\"")
U.shot(game, out .. "/03-level-up.png")
for _ = 1, 8 do U.tap(game, "a") U.wait(30) end
U.shot(game, out .. "/04-learned.png")
end
Sound.play = realPlay
U.log("done -- the controls are yours")
end