mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
185 lines
8.0 KiB
Lua
185 lines
8.0 KiB
Lua
-- Eye check: BATTLE BG = WORLD dims the surround only, never the battle's own
|
|
-- 160x144 field (#777, dup #772). Renderer:endFrame used to fill the WHOLE
|
|
-- window with the 55% veil; the classic battle hid that under its opaque paper
|
|
-- field, but a pipeline that stages the fight on the map and keys the field
|
|
-- out (the Dramatic Shape Voxel Mod) got the veil straight onto its sprites
|
|
-- and HP boxes. On hardware there is nothing behind the battle to dim at all:
|
|
-- _InitBattleCommon calls ClearScreen (pokered home/copy2.asm) over the whole
|
|
-- tilemap before the battle draws.
|
|
-- POKEPORT_DRIVER=tests/drivers/battle_bg_world_dim_bug777_test.lua POKEPORT_IDENTITY=bug777 POKEPORT_TOUCH=0 SHOT_DIR=/tmp/shots love .
|
|
-- POKEPORT_TOUCH=0 matters: the on-screen controls draw after endFrame and
|
|
-- would land in the void samples. No POKEPORT_SPEED around the shots.
|
|
return function(game)
|
|
local U = dofile("tests/drivers/util.lua")
|
|
local DIR = os.getenv("SHOT_DIR") or "/tmp/shots"
|
|
local Game = require("src.core.Game")
|
|
local Renderer = require("src.render.Renderer")
|
|
local Pokemon = require("src.pokemon.Pokemon")
|
|
local BattleState = require("src.battle.BattleState")
|
|
|
|
-- pokered data/maps/objects/Route1.asm puts both youngsters at (5,24) and
|
|
-- (15,13) and the sign at (9,27), so the top of the road is empty; the
|
|
-- battle is pushed straight in, the cell is only somewhere to stand.
|
|
local MAP = "ROUTE_1"
|
|
local STAND = { x = 5, y = 6, facing = "down" }
|
|
local PARTY = { { "BULBASAUR", 12 }, { "PIDGEOTTO", 18 } }
|
|
|
|
local function check(label, ok)
|
|
U.log(ok and "PASS" or "FAIL", label)
|
|
return ok
|
|
end
|
|
|
|
-- Load a captured PNG back as ImageData. love.image cannot read absolute
|
|
-- paths, so go through io.open + newFileData.
|
|
local function loadShot(path)
|
|
local f = io.open(path, "rb")
|
|
if not f then return nil end
|
|
local bytes = f:read("*a")
|
|
f:close()
|
|
local ok, img = pcall(function()
|
|
return love.image.newImageData(
|
|
love.filesystem.newFileData(bytes, "shot.png"))
|
|
end)
|
|
return ok and img or nil
|
|
end
|
|
|
|
-- Mean brightness of a pixel rect. Whole-region averages, so NPC steps and
|
|
-- flower/water animation between two shots wash out instead of flipping a
|
|
-- single-pixel compare.
|
|
local function regionMean(img, x, y, w, h)
|
|
local sum, n = 0, 0
|
|
local x2 = math.min(x + w, img:getWidth()) - 1
|
|
local y2 = math.min(y + h, img:getHeight()) - 1
|
|
for yy = math.max(y, 0), y2 do
|
|
for xx = math.max(x, 0), x2 do
|
|
local r, g, b = img:getPixel(xx, yy)
|
|
sum = sum + (r + g + b) / 3
|
|
n = n + 1
|
|
end
|
|
end
|
|
return n > 0 and sum / n or 0, n
|
|
end
|
|
|
|
-- The UI letterbox in framebuffer pixels, the same math endFrame uses for
|
|
-- uox/uoy/uvpw/uvph (screenshots are framebuffer-sized, so no dpi divide).
|
|
local function uiBox(img)
|
|
local pw, ph = img:getWidth(), img:getHeight()
|
|
local uiw, uih = Renderer:uiSize()
|
|
local Up = Renderer:uiScale()
|
|
local bw, bh = math.floor(uiw * Up + 0.5), math.floor(uih * Up + 0.5)
|
|
local bx = math.floor((pw - bw) / 2)
|
|
local by = math.floor((ph - bh) / 2)
|
|
return bx, by, bw, bh, pw, ph
|
|
end
|
|
|
|
game.save.options = game.save.options or {}
|
|
game.save.options.battleFit = nil -- classic letterbox, the geometry under test
|
|
|
|
game.save.party = {}
|
|
for _, slot in ipairs(PARTY) do
|
|
table.insert(game.save.party, Pokemon.new(game.data, slot[1], slot[2]))
|
|
end
|
|
U.teleport(game, MAP, STAND.x, STAND.y, STAND.facing)
|
|
U.wait(30)
|
|
local ow = game.overworld
|
|
check("the overworld is up on " .. MAP, ow ~= nil and ow.map.id == MAP)
|
|
|
|
-- ---- machine half: veil geometry, no battle needed -----------------------
|
|
-- In the overworld the UI canvas is transparent over the map, so the veil
|
|
-- is not hidden by an opaque field the way the classic battle hides it.
|
|
-- Arm the dim through the real per-frame wiring (Game:draw reads
|
|
-- Game.worldBgBattleDim every frame) and compare against an unarmed shot:
|
|
-- the surround must darken, the letterbox interior must not.
|
|
local baseShot = DIR .. "/bug777_0_overworld.png"
|
|
local veilShot = DIR .. "/bug777_0_overworld_veiled.png"
|
|
U.shot(game, baseShot)
|
|
local realDim = Game.worldBgBattleDim
|
|
Game.worldBgBattleDim = function() return BattleState.BG_WORLD_DIM end
|
|
U.wait(2)
|
|
U.shot(game, veilShot)
|
|
Game.worldBgBattleDim = realDim
|
|
U.wait(2)
|
|
|
|
local base, veil = loadShot(baseShot), loadShot(veilShot)
|
|
if check("both overworld shots decoded", base ~= nil and veil ~= nil) then
|
|
local bx, by, bw, bh, pw = uiBox(base)
|
|
local inBase = regionMean(base, bx, by, bw, bh)
|
|
local inVeil = regionMean(veil, bx, by, bw, bh)
|
|
U.log((" letterbox %dx%d at (%d, %d), interior mean %.3f -> %.3f")
|
|
:format(bw, bh, bx, by, inBase, inVeil))
|
|
-- 0.75 sits between "unchanged" and the 0.55 veil's 0.45x; whole-box
|
|
-- means make a frame of tile animation worth far less than that gap.
|
|
check("the veil leaves the battle box alone (#777)",
|
|
inVeil >= inBase * 0.75)
|
|
if bx >= 8 then
|
|
local outBase = regionMean(base, 0, 0, bx, base:getHeight())
|
|
local outVeil = regionMean(veil, 0, 0, bx, veil:getHeight())
|
|
U.log((" left void strip mean %.3f -> %.3f"):format(outBase, outVeil))
|
|
check("the veil still dims the surround", outVeil <= outBase * 0.75)
|
|
else
|
|
U.log(" window has no side voids at this scale, surround check skipped")
|
|
end
|
|
end
|
|
|
|
-- ---- the real battle, all three BATTLE BG modes --------------------------
|
|
local battle = BattleState.newWild(game, "RATTATA", 5)
|
|
battle.onFinish = function() end
|
|
ow:pushBattle(battle)
|
|
for _ = 1, 400 do
|
|
if game.stack:top() == battle and (battle.introSlide or 0) == 0 then break end
|
|
U.wait(1)
|
|
end
|
|
check("the battle reached the screen", game.stack:top() == battle)
|
|
for _ = 1, 120 do
|
|
if battle.phase == "menu" then break end
|
|
U.tap(game, "a")
|
|
U.wait(6)
|
|
end
|
|
check("the battle reached its FIGHT/PKMN/ITEM/RUN menu",
|
|
battle.phase == "menu")
|
|
|
|
-- bgMode reads save.options.battleBg per frame, so one battle covers all
|
|
-- three modes; the menu is idle between shots.
|
|
local shots = {}
|
|
for _, mode in ipairs({ "white", "black", "world" }) do
|
|
game.save.options.battleBg = mode
|
|
U.wait(5)
|
|
local path = DIR .. "/bug777_" .. mode .. ".png"
|
|
U.shot(game, path)
|
|
shots[mode] = loadShot(path)
|
|
end
|
|
|
|
if check("all three battle shots decoded",
|
|
shots.white ~= nil and shots.black ~= nil and shots.world ~= nil) then
|
|
local bx, by, bw, bh = uiBox(shots.white)
|
|
local inWhite = regionMean(shots.white, bx, by, bw, bh)
|
|
local inWorld = regionMean(shots.world, bx, by, bw, bh)
|
|
U.log((" battle box mean, WHITE %.3f vs WORLD %.3f"):format(inWhite, inWorld))
|
|
check("WORLD leaves the battle's own screen at WHITE's brightness",
|
|
math.abs(inWhite - inWorld) < 0.02)
|
|
if bx >= 8 then
|
|
local outWhite = regionMean(shots.white, 0, 0, bx, shots.white:getHeight())
|
|
local outWorld = regionMean(shots.world, 0, 0, bx, shots.world:getHeight())
|
|
U.log((" void strip mean, WHITE %.3f vs WORLD %.3f"):format(outWhite, outWorld))
|
|
check("WORLD's surround is the dimmed map, not paper",
|
|
outWorld < outWhite - 0.05)
|
|
end
|
|
end
|
|
|
|
-- ---- over to you ---------------------------------------------------------
|
|
U.log("You are at the battle menu with BATTLE BG = WORLD: the frozen Route 1")
|
|
U.log("map sits around the battle at 55% brightness. Put bug777_white.png and")
|
|
U.log("bug777_world.png side by side: inside the battle box they must match")
|
|
U.log("exactly, same paper, same pics, same HP bars; only the frame around it")
|
|
U.log("changes. #777 dimmed the whole window instead, which with a mod that")
|
|
U.log("stages the fight on the map dropped the veil straight over the sprites")
|
|
U.log("and health boxes. The near miss to look for is the opposite failure:")
|
|
U.log("a surround that is NOT dimmed at all, which means the veil got lost")
|
|
U.log("rather than scoped.")
|
|
U.log("Shots: " .. DIR .. "/bug777_*.png")
|
|
|
|
while true do
|
|
coroutine.yield()
|
|
end
|
|
end
|