Files
gen1recomp/tests/drivers/intro_og_obj_palette_bug2264_test.lua

205 lines
7.5 KiB
Lua

-- POKEPORT_IDENTITY=red-sep04 POKEPORT_DRIVER=tests/drivers/intro_og_obj_palette_bug2264_test.lua POKEPORT_SHOT_DIR=/tmp/shots love .
return function(game)
local U = dofile("tests/drivers/util.lua")
local P = require("src.render.PaletteFX")
local GameVersion = require("src.core.GameVersion")
local IntroMovie = require("src.ui.IntroMovie")
local SHOT_DIR = os.getenv("POKEPORT_SHOT_DIR") or os.getenv("SHOT_DIR") or "/tmp/shots"
local version = GameVersion.get()
local tag = "2264_" .. tostring(version)
local fails = 0
local function check(label, ok, detail)
U.log(ok and "PASS" or "FAIL", label, detail or "")
if not ok then fails = fails + 1 end
return ok
end
local function finish()
P.applyOptions({ colors = "ogred" })
if fails > 0 then
U.log("FAIL " .. tag .. ": " .. fails .. " check(s), shots in " .. SHOT_DIR)
love.event.quit(1)
else
U.log("PASS " .. tag .. ": all checks, shots in " .. SHOT_DIR)
love.event.quit(0)
end
while true do coroutine.yield() end
end
local rendered = 0
local hostDraw = love.draw
love.draw = function(...)
local r = hostDraw(...)
rendered = rendered + 1
return r
end
local function waitRenderedFrames(frames)
local target = rendered + (frames or 2)
for _ = 1, 2400 do
if rendered >= target then return true end
U.wait(1)
end
return false
end
local function waitFor(pred, limit)
for _ = 1, limit or 3000 do
if pred() then return true end
U.wait(1)
end
return false
end
local function near(R, G, B, c)
return math.abs(R - c[1]) <= 12 and math.abs(G - c[2]) <= 12
and math.abs(B - c[3]) <= 12
end
local objRamp, bgRamp
local function letterbox(img)
local W, H = img:getWidth(), img:getHeight()
local x1, y1, x2, y2 = W, H, -1, -1
for y = 0, H - 1 do
for x = 0, W - 1 do
local r, g, b = img:getPixel(x, y)
if r > 0.02 or g > 0.02 or b > 0.02 then
if x < x1 then x1 = x end
if x > x2 then x2 = x end
if y < y1 then y1 = y end
if y > y2 then y2 = y end
end
end
end
local bw, bh = x2 - x1 + 1, y2 - y1 + 1
if x2 >= x1 and math.abs(bw / 160 - bh / 144) < 0.05 then
return x1, y1, bh / 144
end
local s = math.min(W / 160, H / 144)
return math.floor((W - 160 * s) / 2), math.floor((H - 144 * s) / 2), s
end
local shotImg = {}
local function load(path)
if shotImg[path] ~= nil then return shotImg[path] end
local f = io.open(path, "rb")
if not f then shotImg[path] = false return false end
local bytes = f:read("*a")
f:close()
local ok, img = pcall(function()
return love.image.newImageData(love.filesystem.newFileData(bytes, "shot.png"))
end)
shotImg[path] = ok and img or false
return shotImg[path]
end
local function census(path, bx1, by1, bx2, by2)
local img = load(path)
if not img then return nil end
local W, H = img:getWidth(), img:getHeight()
local ox, oy, scale = letterbox(img)
local x1 = ox + math.floor(bx1 * scale + 0.5) + 1
local y1 = oy + math.floor(by1 * scale + 0.5) + 1
local x2 = ox + math.floor(bx2 * scale + 0.5) - 2
local y2 = oy + math.floor(by2 * scale + 0.5) - 2
local c = { obj = 0, bg = 0, black = 0, total = 0 }
for y = math.max(0, y1), math.min(H - 1, y2) do
for x = math.max(0, x1), math.min(W - 1, x2) do
local r, g, b = img:getPixel(x, y)
local R = math.floor(r * 255 + 0.5)
local G = math.floor(g * 255 + 0.5)
local B = math.floor(b * 255 + 0.5)
c.total = c.total + 1
if R < 20 and G < 20 and B < 20 then c.black = c.black + 1 end
if near(R, G, B, objRamp[1]) or near(R, G, B, objRamp[2]) then c.obj = c.obj + 1 end
if near(R, G, B, bgRamp[1]) or near(R, G, B, bgRamp[2]) then c.bg = c.bg + 1 end
end
end
local d = scale * scale
c.obj = math.floor(c.obj / d + 0.5)
c.bg = math.floor(c.bg / d + 0.5)
return c
end
local movie
check(tag .. " intro is on the stack", waitFor(function()
movie = game.stack:top()
return getmetatable(movie) == IntroMovie
end, 600))
if getmetatable(movie) ~= IntroMovie then finish() end
game.save.options.colors = "ogred"
P.applyOptions(game.save.options)
check(tag .. " COLORS is ogred", P.mode == "ogred", P.mode)
local blue = GameVersion.isBlue()
local obj = blue and P.GBC_OBJ_BLUE or P.GBC_OBJ
local bg = P.ogBg()
objRamp = { obj[2], obj[3] }
bgRamp = { bg[2], bg[3] }
check(tag .. " scrollIn finished", waitFor(function()
return movie.phase == 3 and movie.opIndex > 1 and movie.nidoX == 72
end, 3000), tostring(movie.nidoX))
waitRenderedFrames(3)
local nidoX, nidoY, gengarX = movie.nidoX, movie.nidoY, movie.gengarX
local shot1 = SHOT_DIR .. "/" .. tag .. "_01_fight_after_scroll_in.png"
check(tag .. " replay recorded", #P.uiSpriteRedraws() >= 2, "#" .. #P.uiSpriteRedraws())
if check(tag .. " shot 01", U.shot(game, shot1) and load(shot1)) then
local nido = census(shot1, math.max(nidoX, gengarX + 56), nidoY, nidoX + 48, 112)
local gengar = census(shot1, gengarX, 56, math.min(gengarX + 56, nidoX), 112)
local topBar = census(shot1, 0, 0, 160, 32)
local botBar = census(shot1, 0, 112, 160, 144)
U.log(string.format("nido band obj=%d bg=%d | gengar band obj=%d bg=%d",
nido.obj, nido.bg, gengar.obj, gengar.bg))
check(tag .. " front mon wears the boot-ROM OBJ ramp", nido.obj > 150, nido.obj)
check(tag .. " front mon carries no BG ramp", nido.bg < 20, nido.bg)
check(tag .. " Gengar keeps the BG ramp", gengar.bg > 150, gengar.bg)
check(tag .. " Gengar carries no OBJ ramp", gengar.obj < 20, gengar.obj)
check(tag .. " top bar stays solid black", topBar.black == topBar.total,
topBar.black .. "/" .. topBar.total)
check(tag .. " bottom bar covers the OAM_PRIO sprite", botBar.black == botBar.total,
botBar.black .. "/" .. botBar.total)
end
local function near1(path, c, bx1, by1, bx2, by2)
local img = load(path)
if not img then return 0, 1 end
local ox, oy, scale = letterbox(img)
local hit, total = 0, 0
for y = oy + math.floor(by1 * scale) + 1, oy + math.floor(by2 * scale) - 2 do
for x = ox + math.floor(bx1 * scale) + 1, ox + math.floor(bx2 * scale) - 2 do
local r, g, b = img:getPixel(x, y)
total = total + 1
if near(math.floor(r * 255 + 0.5), math.floor(g * 255 + 0.5),
math.floor(b * 255 + 0.5), c) then hit = hit + 1 end
end
end
return hit, total
end
for step, want in ipairs({ { name = "fadepal6_bars_bg_shade2", c = bg[3] },
{ name = "fadepal7_bars_bg_shade1", c = bg[2] } }) do
check(tag .. " reached fade step " .. step, waitFor(function()
return movie.fadeStep == step or movie.phase == 4
end, 3000), tostring(movie.fadeStep))
if movie.phase ~= 3 then break end
local upd = movie.update
movie.update = function() end
waitRenderedFrames(2)
local shot = SHOT_DIR .. "/" .. tag .. "_0" .. (step + 1) .. "_" .. want.name .. ".png"
if check(tag .. " shot fade step " .. step, U.shot(game, shot) and load(shot)) then
local hit, total = near1(shot, want.c, 0, 112, 160, 144)
check(tag .. " fade step " .. step .. " bottom bar is BG shade " .. (3 - step),
hit == total, hit .. "/" .. total)
hit, total = near1(shot, want.c, 0, 0, 160, 32)
check(tag .. " fade step " .. step .. " top bar is BG shade " .. (3 - step),
hit == total, hit .. "/" .. total)
end
movie.update = upd
end
finish()
end