From 90eb53b00c46b9b08aba4c991188297af67b08cf Mon Sep 17 00:00:00 2001 From: syybott Date: Sat, 15 Aug 2026 23:29:04 -0500 Subject: [PATCH] Keep WIDE battle visible beneath opaque menus --- src/core/Game.lua | 22 ++++++---- .../fixed_extended_world_bag_overlay_test.lua | 44 +++++++++++++++++++ tests/engine/battle_fixed_menu_scale.lua | 10 ++++- 3 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 tests/drivers/fixed_extended_world_bag_overlay_test.lua diff --git a/src/core/Game.lua b/src/core/Game.lua index a66912a9..86e89a0c 100644 --- a/src/core/Game.lua +++ b/src/core/Game.lua @@ -423,13 +423,13 @@ function Game.uiAnchorsHeldInStack(stack) end -- Where Game:draw starts drawing this frame. Normally the topmost opaque --- state (StateStack:visibleBase) -- but BATTLE BG "world" composes the battle --- over the LIVE map, and an opaque state pushed on top of it (the party menu, --- the bag) becomes that base, cutting the overworld -- and with it the world --- pass -- out of the frame entirely. The backdrop the battle established --- then collapses to endFrame's flat black clear for as long as the menu is --- up. So a world-bg battle keeps the frame starting from underneath itself --- until it leaves the stack, the same hold uiFill and the dim already use. +-- state (StateStack:visibleBase) -- but an opaque menu pushed over a WIDE +-- battle must not prevent that battle from drawing. Native WIDE battles own +-- the 304x144 surround around a centred classic menu, while external arena +-- providers establish their window-sized scene from BattleState:draw. If the +-- menu becomes the draw base, neither owner runs and the menu's white field +-- replaces the whole presentation. BATTLE BG "world" additionally needs the +-- overworld below the battle, as before. -- -- Only the START of the draw moves. The clear stays keyed to the real -- visibleBase, so the menu still gets its opaque canvas and draws exactly as @@ -440,9 +440,13 @@ function Game.drawBaseInStack(stack, visibleBase) local states = stack and stack.states or {} for i = visibleBase - 1, 1, -1 do local state = states[i] - if state and state.bgMode and state:bgMode() == "world" then + local worldBattle = state and state.bgMode and state:bgMode() == "world" + local wideBattle = state and state.isWideBattleLayout + and state:isWideBattleLayout() + if worldBattle or wideBattle then -- restart the search from under the battle: the highest opaque state at - -- or below it (the overworld), not the menu sitting over it + -- or below it (the battle itself for white/black WIDE, the overworld for + -- a non-opaque world-backed battle), not the menu sitting over it for j = i, 1, -1 do if states[j].isOpaque then return j end end diff --git a/tests/drivers/fixed_extended_world_bag_overlay_test.lua b/tests/drivers/fixed_extended_world_bag_overlay_test.lua new file mode 100644 index 00000000..95855569 --- /dev/null +++ b/tests/drivers/fixed_extended_world_bag_overlay_test.lua @@ -0,0 +1,44 @@ +-- Visual regression coverage for Professor Oak's scripted Yellow capture Bag +-- over the WIDE + FIXED + EXTENDED + WORLD composition. +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + local BattleState = require("src.battle.BattleState") + local Pokemon = require("src.pokemon.Pokemon") + + love.window.setMode(2048, 1152, { resizable = true }) + U.wait(3) + + local options = game.save.options + options.battleLayout = "wide" + options.battleFit = "fixed" + options.battleHud = "extended" + options.battleBg = "world" + + game.save.party = { Pokemon.new(game.data, "PIKACHU", 20) } + U.teleport(game, "ROUTE_1", 5, 5, "down") + U.wait(30) + + local demo = BattleState.newWild(game, "CHARMANDER", 5) + demo:makeOldManDemo("PROF.OAK") + demo.onFinish = function() end + game.overworld:pushBattle(demo) + + for _ = 1, 100 do + if demo.phase == "menu" and (demo.demoTimer or 0) > 5 then break end + U.tap(game, "a") + U.wait(4) + end + for _ = 1, 180 do + if game.stack:top() ~= demo then break end + U.wait(1) + end + U.wait(3) + + local path = DIR .. "/fixed_extended_world_oak_charmander_bag.png" + os.remove(path) + local ok = game.stack:top() ~= demo and U.shot(game, path) + U.log(ok and "FIXED_EXTENDED_WORLD_BAG_PASS" + or "FIXED_EXTENDED_WORLD_BAG_FAIL") + love.event.quit(ok and 0 or 1) +end diff --git a/tests/engine/battle_fixed_menu_scale.lua b/tests/engine/battle_fixed_menu_scale.lua index fd5e27d2..33e79812 100644 --- a/tests/engine/battle_fixed_menu_scale.lua +++ b/tests/engine/battle_fixed_menu_scale.lua @@ -92,6 +92,11 @@ local menu = { isOpaque = true } -- PartyMenu / ListMenu local whiteBattle = setmetatable( { game = { save = { options = { battleBg = "white" } } } }, { __index = BattleState }) +local wideWhiteBattle = setmetatable( + { game = { save = { options = { + battleBg = "white", battleLayout = "wide", + } } } }, + { __index = BattleState }) local function stack(...) return { states = { ... }, visibleBase = function(self) for i = #self.states, 1, -1 do @@ -111,7 +116,10 @@ T.eq(s2:visibleBase(), 1, "the battle alone already drew from the overworld") T.eq(Game.drawBaseInStack(s2, s2:visibleBase()), 1, "and still does") local s3 = stack(overworld, whiteBattle, menu) T.eq(Game.drawBaseInStack(s3, s3:visibleBase()), 3, - "a white-bg battle has no map to hold, so nothing moves") + "a classic white-bg battle has no presentation to hold, so nothing moves") +local s3wide = stack(overworld, wideWhiteBattle, menu) +T.eq(Game.drawBaseInStack(s3wide, s3wide:visibleBase()), 2, + "an opaque WIDE battle still draws beneath its classic menu") local s4 = stack(overworld, menu) T.eq(Game.drawBaseInStack(s4, s4:visibleBase()), 2, "and a menu outside a battle is untouched")