-- Parity test: nothing expensive may ride the FLASH white-out's completion -- callback (#610). Transition.WhiteFlash is opaque and paints the whole -- 160x144 solid white, so whatever runs from its onDone runs with a blank -- white frame as the newest thing the player has been shown. Lighting the -- cave is exactly that kind of work: in the ADVANCED colour mode -- OverworldState:setDark drops every resident map and rebakes the tileset -- atlas pixel by pixel (#383), which on a phone is seconds of frozen white -- and reads as a lockup. engine/menus/start_sub_menus.asm .flash clears -- wMapPalOffset before PrintText and calls GBPalWhiteOutWithDelay3 last of -- all, so the port lights the cave as the message closes the menu and -- leaves the blink a plain 7-frame blink with no work attached. package.path = "./?.lua;./?/init.lua;" .. package.path if not _G.love then _G.love = require("tests.love_stub") end local Data = require("src.core.Data") if not (Data.maps and Data.maps.PALLET_TOWN) then Data:load() end local S = require("tests.harness").suite("parity flash blink bug610") local check, eq = S.check, S.eq require("src.render.Font").load(Data) local Game = require("src.core.Game") local Input = require("src.core.Input") local StateStack = require("src.core.StateStack") local Renderer = require("src.render.Renderer") local SaveData = require("src.core.SaveData") local Pokemon = require("src.pokemon.Pokemon") local PartyMenu = require("src.ui.PartyMenu") local OW = require("src.world.OverworldController") Game.data = Data Game.input = Input; Input:init() Game.renderer = Renderer; Renderer:init() Game.stack = StateStack; StateStack:init() Game.save = SaveData.newGame() local function frame(btns) Input.pressed = {} for _, b in ipairs(btns or {}) do Input.pressed[b] = true; Input.state[b] = true end StateStack:update(1 / 60) for _, b in ipairs(btns or {}) do Input.state[b] = false end end local function popAll() while Game.stack:top() do Game.stack:pop() end end local function mkMon(species, ...) local m = Pokemon.new(Data, species, 20) m.moves = {} for _, id in ipairs({ ... }) do m.moves[#m.moves + 1] = { id = id, pp = 15 } end return m end -- dismiss exactly one box, leaving whatever it pushed on top local function drainOne() local box = Game.stack:top() local guard = 0 while Game.stack:top() == box and guard < 400 do guard = guard + 1 frame({ "a" }) end end Game.save.flashLit = false Game.save.party = { mkMon("PIKACHU", "FLASH") } Game.save.inventory = { BOULDERBADGE = true } popAll() Game.stack:push(OW, "ROCK_TUNNEL_1F", 15, 4, "down") local ow = Game.stack:top() eq(ow.dark, true, "ROCK_TUNNEL_1F loads dark before FLASH") local pm = PartyMenu.new(Game) Game.stack:push(pm) frame({ "a" }) -- open the field-move submenu on PIKACHU frame({ "a" }) -- FLASH is the top row now (#768) drainOne() -- dismiss _FlashLightsAreaText local blink = Game.stack:top() check(blink ~= nil and blink.isOpaque == true and type(blink.frames) == "number", "the blink follows the FLASH message") -- the invariant this test exists for: the blink carries no work eq(blink.onDone, nil, "the FLASH blink has no completion callback (#610)") eq(ow.dark, false, "the cave is already lit when the blink starts") local guard = 0 while Game.stack:top() ~= ow and guard < 240 do guard = guard + 1 frame({}) end eq(Game.stack:top(), ow, "FLASH ends on the map") eq(ow.dark, false, "the cave stays lit after the blink") eq(Game.save.flashLit, true, "FLASH is recorded on the save") popAll() S.finish()