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
@@ -0,0 +1,120 @@
-- Driver: #1517, a Gen 2 stat block in a Gen 1 party.
--
-- Gen 1's party_struct carries one Special word (macros/ram.asm:28-37) and
-- PrintStatsBox reads four fixed cells, wLoadedMonAttack/Defense/Speed/Special
-- (engine/pokemon/status_screen.asm:255-296). Gen 2 splits that word into
-- SpclAtk/SpclDef (pokegold macros/ram.asm:29-42), so a mod that wrote a Gen 2
-- block over a Yellow party leaves the port with no `special` to print.
--
-- cp -R ~/Library/Application\ Support/LOVE/pokemon-love2d/yellow \
-- ~/Library/Application\ Support/LOVE/bug1517/yellow
-- POKEPORT_VERSION=yellow POKEPORT_GAME=yellow POKEPORT_IDENTITY=bug1517 \
-- POKEPORT_TOUCH=0 POKEPORT_SPEED=8 SHOT_DIR=/tmp/bug1517 \
-- POKEPORT_DRIVER=tests/drivers/summary_stats_bug1517_test.lua love .
--
-- The copy is not optional: a fresh POKEPORT_IDENTITY has no cache and the
-- boot hangs with no output.
--
-- Pre-fix the LOVE window vanishes the moment STATS is chosen, with no error
-- screen and nothing on stdout (that silence is the other half of #1517).
-- Post-fix the status screen opens and SPECIAL reads the CalcStats value
-- logged below. The run ends on the open screen, so a human takes the
-- controls at exactly the frame the reporter's build died on.
return function(game)
local U = dofile("tests/drivers/util.lua")
local DIR = os.getenv("SHOT_DIR") or "/tmp/bug1517"
local Pokemon = require("src.pokemon.Pokemon")
local Stats = require("src.pokemon.Stats")
local SaveData = require("src.core.SaveData")
local SummaryMenu = require("src.ui.SummaryMenu")
local function top() return game.stack:top() end
U.newGame(game)
U.wait(20)
-- BUTTERFREE because the reporter's party led with one, and CONFUSION is a
-- special move, so the same block would have crashed Damage.applyStage.
local species = game.data.pokemon.BUTTERFREE and "BUTTERFREE" or "PIDGEY"
local def = game.data.pokemon[species]
local mon = Pokemon.new(game.data, species, 21)
local want = Stats.calc(def, 21, mon.dvs, mon.statExp)
-- the shape src/battle/gen2/Mon.lua Mon.stats produces: no `special`
mon.stats = {
hp = want.hp, attack = want.attack, defense = want.defense,
speed = want.speed, specialAttack = want.special, specialDefense = want.special,
}
mon.hp = mon.stats.hp
game.save.party = { mon }
U.log(("[1517] fixture: %s :L21, stats.special = %s, CalcStats says %d")
:format(species, tostring(mon.stats.special), want.special))
SaveData.validate(game.save, game.data)
local after = game.save.party[1].stats
U.log("[1517] after SaveData.validate, special = " .. tostring(after.special)
.. ", specialAttack = " .. tostring(after.specialAttack))
U.log("[1517] the repair layer is SaveData.validate -> scrubKnownMon -> "
.. "Stats.ensure; a nil above means it did not run")
U.teleport(game, "PALLET_TOWN", 10, 8, "down")
U.wait(10)
local function cursorTo(menu, field, wanted)
for _ = 1, 40 do
if not menu or menu[field] == wanted then return menu and menu[field] == wanted end
U.tap(game, menu[field] < wanted and "down" or "up")
U.wait(3)
end
return menu[field] == wanted
end
U.tap(game, "start")
U.wait(10)
local menu = top()
if not (menu and menu.screenId == "StartMenu") then
U.log("[1517] FAIL no start menu; top = " .. tostring(menu and menu.screenId))
while true do coroutine.yield() end
end
local row
for i, it in ipairs(menu.items or {}) do
if it.label == "POKéMON" then row = i break end
end
if not (row and cursorTo(menu, "index", row)) then
U.log("[1517] FAIL could not reach the POKéMON row")
while true do coroutine.yield() end
end
U.tap(game, "a")
U.wait(10)
local party = top()
if not (party and party.screenId == "PartyMenu") then
U.log("[1517] FAIL no party menu; top = " .. tostring(party and party.screenId))
while true do coroutine.yield() end
end
U.shot(game, DIR .. "/01-party-list.png")
U.tap(game, "a")
U.wait(8)
local subRow
for i, it in ipairs(party.subItems or {}) do
if it.action == "stats" then subRow = i break end
end
if not (subRow and cursorTo(party, "subIndex", subRow)) then
U.log("[1517] FAIL could not reach the STATS row")
while true do coroutine.yield() end
end
U.shot(game, DIR .. "/02-stats-selected.png")
U.log("[1517] opening STATS -- pre-fix the app dies here, silently")
U.tap(game, "a")
U.wait(30)
local screen = top()
local opened = getmetatable(screen) == SummaryMenu or screen.screenId == "SummaryMenu"
U.log("[1517] SURVIVED, top = " .. tostring(screen and screen.screenId))
U.shot(game, DIR .. "/03-status-screen.png")
U.log(("[1517] %s: SPECIAL on screen must read %d")
:format(opened and "read it off the frame" or "WRONG SCREEN", want.special))
U.log("[1517] shots in " .. DIR .. " -- the status screen is yours")
while true do coroutine.yield() end
end