mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-25 23:11:15 +02:00
1905261c5b
CLOSES #1483, CLOSES #1610, CLOSES #1615, CLOSES #1646, CLOSES #1649, CLOSES #1651, CLOSES #1653, CLOSES #1656, CLOSES #1683, CLOSES #1685, CLOSES #1686, CLOSES #1687, CLOSES #1688, CLOSES #1689, CLOSES #1690, CLOSES #1693, CLOSES #1694, CLOSES #1695, CLOSES #1696, CLOSES #1702, CLOSES #1704, CLOSES #1705, CLOSES #1706, CLOSES #1707, CLOSES #1708, CLOSES #1710, CLOSES #1711, CLOSES #1712, CLOSES #1713, CLOSES #1716, CLOSES #1717, CLOSES #1718, CLOSES #1719, CLOSES #1720, CLOSES #1721, CLOSES #1725, CLOSES #1732, CLOSES #1745, CLOSES #1748, CLOSES #1749, CLOSES #1751, CLOSES #1754
280 lines
11 KiB
Lua
280 lines
11 KiB
Lua
-- #1693: the mon SUMMARY's lower half never took the selected page's colour.
|
|
-- StatsScreen_LoadGFX .LoadPals farcalls LoadStatsScreenPals with the page
|
|
-- index, and that writes one gfx/stats/stats.pal colour over colour 0 of BG
|
|
-- palette 0 AND BG palette 2 (engine/gfx/color.asm:373-393); WipeAttrmap
|
|
-- leaves rows 8-17 on palette 0 with the exp strip at (10,16) on palette 2
|
|
-- (engine/gfx/cgb_layouts.asm:219-229), so both writes land below the rule.
|
|
-- Never add POKEPORT_SPEED here: fast-forward scales the logic clock only, so
|
|
-- the cry and the page redraw stop landing in the order a reader is judging.
|
|
--
|
|
-- POKEPORT_IDENTITY=gold-dev POKEPORT_GAME=gold POKEPORT_TOUCH=0 \
|
|
-- POKEPORT_DRIVER=tests/drivers/gold_stats_page_tint_bug1693_test.lua \
|
|
-- POKEPORT_SHOT_DIR=/tmp/gold-bug1693 \
|
|
-- perl -e 'alarm 240; exec @ARGV' \
|
|
-- python3 -c "import pty; pty.spawn(['love','.'])"
|
|
local U = require("tests.drivers.util")
|
|
|
|
local GbcPalette = require("src.render.GbcPalette")
|
|
local HpBar = require("src.battle.gen2.HpBar")
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
local SummaryMenu = require("src.ui.gen2.SummaryMenu")
|
|
|
|
-- gfx/stats/stats.pal, 5-bit the way the ROM keeps it: pink, green, blue
|
|
local ROM_TINTS = { { 31, 19, 31 }, { 21, 31, 14 }, { 17, 31, 31 } }
|
|
local PAGE_NAMES = { "pink", "green", "blue" }
|
|
|
|
local function up(v) return math.floor(v * 255 / 31 + 0.5) end
|
|
local function pct(v) return math.floor(v + 0.5) end
|
|
|
|
return function(game)
|
|
local out = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/gold-bug1693"
|
|
local fails = 0
|
|
|
|
local function ok(cond, msg)
|
|
if cond then U.log("PASS", msg) else fails = fails + 1 U.log("FAIL", msg) end
|
|
return cond and true or false
|
|
end
|
|
local function tap(btn) U.tap(game, btn) U.wait(4) end
|
|
local function top() return game.stack:top() end
|
|
local function shot(name)
|
|
return ok(U.shot(game, ("%s/%s.png"):format(out, name)),
|
|
name .. " reached disk")
|
|
end
|
|
|
|
U.wait(45)
|
|
if not ok(game.world and game.world.map ~= nil,
|
|
"gold booted into the world") then
|
|
error("gold stats tint: no world, nothing to open a summary over")
|
|
end
|
|
|
|
-- ---- the party the pink page needs ---------------------------------------
|
|
-- Mon.new is the only Gen 2 party builder; a mon out of Gen 1's Pokemon.new
|
|
-- comes back with no moves and no growth rate, so both bars would be empty.
|
|
local save = game.save
|
|
save.player.name = "GOLD"
|
|
save.player.id = 12345
|
|
save.party = {
|
|
Mon.new(game.data, "CYNDAQUIL", 22),
|
|
Mon.new(game.data, "TOTODILE", 18),
|
|
}
|
|
local lead = save.party[1]
|
|
lead.item = "BERRY"
|
|
lead.hp = math.max(1, math.floor((lead.maxHp or 1) * 0.45))
|
|
local def = game.data.pokemon[lead.species]
|
|
local rates = game.data.pokemon.growthRates
|
|
local rate = rates and def and rates[def.growthRate]
|
|
if rate then
|
|
local base = Mon.experienceForLevel(rate, lead.level)
|
|
local next_ = Mon.experienceForLevel(rate, lead.level + 1)
|
|
lead.experience = base + math.floor((next_ - base) * 0.55)
|
|
end
|
|
ok(game.world.giveEgg and game.world:giveEgg(175, 5) and save.party[3]
|
|
and save.party[3].isEgg == true, "slot 3 holds an egg")
|
|
|
|
-- ---- everything the eye cannot check -------------------------------------
|
|
for page = 1, 3 do
|
|
local tint = SummaryMenu.PAGE_TINTS and SummaryMenu.PAGE_TINTS[page]
|
|
ok(tint ~= nil and tint[1] == up(ROM_TINTS[page][1])
|
|
and tint[2] == up(ROM_TINTS[page][2])
|
|
and tint[3] == up(ROM_TINTS[page][3]),
|
|
("the %s tint is stats.pal's own colour"):format(PAGE_NAMES[page]))
|
|
local probe = setmetatable({ page = page }, { __index = SummaryMenu })
|
|
local lower = probe.lowerColors and probe:lowerColors()
|
|
ok(lower ~= nil and lower[1] == tint and lower[4][1] == 0,
|
|
("the %s lower palette is that tint over black ink"):format(
|
|
PAGE_NAMES[page]))
|
|
end
|
|
ok(GbcPalette.available(), "the GBC shade-remap shader compiled")
|
|
|
|
-- COLOR is the port's own display row and DMG flattens every palette in the
|
|
-- game, so a run started on it looks exactly like the bug.
|
|
game.options = game.options or {}
|
|
local startedOn = game.options.color or "gbc"
|
|
local function setColor(mode)
|
|
game.options.color = mode
|
|
if game.save then game.save.options = game.options end
|
|
GbcPalette.applyOptions(game.options)
|
|
return GbcPalette.mode
|
|
end
|
|
if startedOn ~= "gbc" then
|
|
U.log("COLOR was on " .. GbcPalette.modeLabel(startedOn) .. ", which would")
|
|
U.log("grey every shot below for the wrong reason. forcing it to GBC.")
|
|
end
|
|
ok(setColor("gbc") == "gbc", "COLOR is GBC for the run")
|
|
|
|
-- ---- the live screen, counted rather than admired ------------------------
|
|
-- The summary draws at GB coordinates, so a 160x144 canvas at scale 1 makes
|
|
-- a GB pixel a pixel and the tint countable.
|
|
local function frameOf(screen)
|
|
local G = love.graphics
|
|
local canvas = G.newCanvas(160, 144)
|
|
G.setCanvas(canvas)
|
|
G.clear(0, 0, 0, 1)
|
|
G.setColor(1, 1, 1, 1)
|
|
screen:draw()
|
|
G.setCanvas()
|
|
G.setColor(1, 1, 1, 1)
|
|
return canvas:newImageData()
|
|
end
|
|
|
|
local function near(a, b) return math.abs(a - b) <= 2 end
|
|
|
|
-- Counted against stats.pal itself, not the port's table, so a run that has
|
|
-- no PAGE_TINTS cannot agree with itself.
|
|
local function tintFor(page)
|
|
local rom = ROM_TINTS[page] or ROM_TINTS[1]
|
|
return { up(rom[1]), up(rom[2]), up(rom[3]) }
|
|
end
|
|
|
|
local function scan(img, want, x0, y0, x1, y1)
|
|
local hit, white, total = 0, 0, 0
|
|
for y = y0, y1 do
|
|
for x = x0, x1 do
|
|
local r, g, b = img:getPixel(x, y)
|
|
r, g, b = r * 255, g * 255, b * 255
|
|
total = total + 1
|
|
if near(r, want[1]) and near(g, want[2]) and near(b, want[3]) then
|
|
hit = hit + 1
|
|
end
|
|
if near(r, 255) and near(g, 255) and near(b, 255) then
|
|
white = white + 1
|
|
end
|
|
end
|
|
end
|
|
return hit / total * 100, white / total * 100
|
|
end
|
|
|
|
-- StatsScreen_LoadGFX .ClearBox: hlcoord 0, 8 / lb bc, 10, 20
|
|
local LOWER = { 0, 64, 159, 143 }
|
|
local function report(label, hit, white)
|
|
U.log((" %-24s %3d%% tinted %3d%% white"):format(label, pct(hit),
|
|
pct(white)))
|
|
end
|
|
|
|
local function zone(img, tint, label, rect, wantTint)
|
|
local hit, white = scan(img, tint, rect[1], rect[2], rect[3], rect[4])
|
|
report(label, hit, white)
|
|
ok(hit >= wantTint, label .. " carries the page tint")
|
|
ok(white <= 2, label .. " has no white left in it")
|
|
end
|
|
|
|
local function inspect(screen, name)
|
|
local img = frameOf(screen)
|
|
local tint = tintFor(screen.page)
|
|
zone(img, tint, "rows 8-17", LOWER, 40)
|
|
if screen.page == SummaryMenu.PINK_PAGE then
|
|
-- DrawPlayerHP: "HP:" at (0,9), six bar cells from (2,9)
|
|
zone(img, tint, "the HP bar's cells", { 16, 72, 63, 79 }, 8)
|
|
-- LoadPinkPage's divider column, and FillInExpBar at (11,16)
|
|
zone(img, tint, "the divider, column 9", { 72, 64, 79, 143 }, 20)
|
|
zone(img, tint, "the exp bar", { 88, 128, 151, 135 }, 8)
|
|
elseif screen.page == SummaryMenu.BLUE_PAGE then
|
|
zone(img, tint, "the divider, column 10", { 80, 64, 87, 143 }, 20)
|
|
end
|
|
-- The upper half is on the mon palette, which the cart never tints.
|
|
local _, white = scan(img, tint, 0, 0, 159, 55)
|
|
ok(white >= 20, "the upper half stayed white")
|
|
shot(name)
|
|
end
|
|
|
|
-- ---- walk in through the pad ---------------------------------------------
|
|
tap("start")
|
|
local menu = top()
|
|
if not ok(menu and menu.screenId == "Gen2StartMenu",
|
|
"START opened the menu") then
|
|
error("gold stats tint: no start menu, cannot reach the summary")
|
|
end
|
|
for _ = 1, 10 do
|
|
if menu.list:current().value == "pokemon" then break end
|
|
tap("down")
|
|
end
|
|
ok(menu.list:current().value == "pokemon", "the cursor found the party row")
|
|
tap("a")
|
|
local party = top()
|
|
if not ok(party and party.screenId == "Gen2PartyMenu",
|
|
"that opened the party list") then
|
|
error("gold stats tint: no party list, cannot reach the summary")
|
|
end
|
|
|
|
local function openStats()
|
|
tap("a")
|
|
ok(party.submenu ~= nil and party.submenu.items[1].id == "STATS",
|
|
"the mon submenu leads with STATS")
|
|
tap("a")
|
|
local screen = top()
|
|
ok(screen and screen.screenId == "Gen2SummaryMenu",
|
|
"STATS opened the summary")
|
|
return screen
|
|
end
|
|
|
|
local summary = openStats()
|
|
ok(summary.hud and summary.hud:available(),
|
|
"the HUD sheet is cached, so the bars are the cart's own tiles")
|
|
ok(summary.statsTiles and summary:statsTiles() ~= nil,
|
|
"menu_gfx.stats is cached, so the divider and caps are too")
|
|
local fraction = HpBar.expFraction(summary.mon, summary:growth(),
|
|
Mon.experienceForLevel)
|
|
ok(fraction > 0.05 and fraction < 0.95,
|
|
"the lead is part way to its next level, so the exp bar is half drawn")
|
|
ok((summary.mon.hp or 0) > 0 and summary.mon.hp < (summary.mon.maxHp or 0),
|
|
"and hurt, so the HP bar has empty cells as well as full ones")
|
|
|
|
inspect(summary, "01-pink-page")
|
|
tap("right")
|
|
ok(summary.page == SummaryMenu.GREEN_PAGE, "right reached the green page")
|
|
inspect(summary, "02-green-page")
|
|
tap("right")
|
|
ok(summary.page == SummaryMenu.BLUE_PAGE, "right reached the blue page")
|
|
inspect(summary, "03-blue-page")
|
|
tap("right")
|
|
ok(summary.page == SummaryMenu.PINK_PAGE, "and wrapped back to pink")
|
|
|
|
-- ---- the two screens that must stay white --------------------------------
|
|
tap("b")
|
|
ok(top() == party, "b came back to the list")
|
|
tap("down")
|
|
tap("down")
|
|
ok(party.index == 3, "the cursor reached the egg")
|
|
local egg = openStats()
|
|
ok(egg.mon and egg.mon.isEgg == true, "on the egg")
|
|
local eggTint, eggWhite = scan(frameOf(egg), tintFor(SummaryMenu.PINK_PAGE),
|
|
LOWER[1], LOWER[2], LOWER[3], LOWER[4])
|
|
report("the egg's rows 8-17", eggTint, eggWhite)
|
|
-- EggStatsScreen never calls LoadStatsScreenPals (stats_screen.asm:786)
|
|
ok(eggTint <= 1 and eggWhite >= 40, "the egg page stayed white")
|
|
shot("04-egg-white")
|
|
|
|
tap("b")
|
|
tap("up")
|
|
tap("up")
|
|
ok(setColor("dmg") == "dmg", "COLOR switched to DMG")
|
|
local dmg = openStats()
|
|
local dmgTint, dmgWhite = scan(frameOf(dmg), tintFor(dmg.page),
|
|
LOWER[1], LOWER[2], LOWER[3], LOWER[4])
|
|
report("DMG's rows 8-17", dmgTint, dmgWhite)
|
|
-- LoadStatsScreenPals opens `call CheckCGB / ret z` (color.asm:373-375)
|
|
ok(dmgTint <= 1 and dmgWhite >= 40, "DMG flattened the tint back to white")
|
|
shot("05-dmg-white")
|
|
tap("b")
|
|
ok(setColor("gbc") == "gbc", "COLOR is back on GBC for the handoff")
|
|
|
|
-- ---- what a reader is looking at -----------------------------------------
|
|
openStats()
|
|
U.wait(4)
|
|
|
|
if fails > 0 then
|
|
U.log(("%d check(s) failed above. do not judge the colours -- the run is"):
|
|
format(fails))
|
|
U.log("not showing you what it claims to.")
|
|
end
|
|
U.log("shots are in " .. out .. ". the summary is open on the pink page and")
|
|
U.log("left/right turn it: everything under the horizontal rule should be")
|
|
U.log("washed pink, then green, then cyan, matching whichever page square is")
|
|
U.log("the large one, while the pic, nickname and level stay white.")
|
|
U.log("a partial fix leaves white in one of three places -- an 8px stripe")
|
|
U.log("down the divider column, the bands above and below the HP bar's fill,")
|
|
U.log("or the exp bar's interior inside its black frame.")
|
|
|
|
while true do coroutine.yield() end
|
|
end
|