fix: battle screens keep their composition when they open a menu or prompt

BATTLE SIZE "fixed" draws the battle as a discrete letterbox rather than
filling the window, and BATTLE BG "world" composes it over the live map.
Everything the battle then opens broke out of that composition, because
each piece of the frame's geometry was read off a fact about THIS FRAME
instead of about the battle:

* Renderer:uiScale follows the survey zoom only while a world is behind
  the UI, gated on worldActive -- this frame's world pass.  PartyMenu and
  ListMenu are opaque, so pushing one makes StateStack:visibleBase skip
  the map, no world pass runs, and the menu loses the step-down and blits
  a whole integer scale larger than the battle it just covered.  Held
  with uiWorldHold, the same whole-stack rule uiFill and the battle dim
  already use.  ("fill" hid this: it overrides the scale outright.)

* Game:draw started the frame at visibleBase, so that same opaque menu
  cut the overworld -- and the world pass with it -- out of the frame
  entirely, collapsing a "world" backdrop to endFrame's flat black clear.
  A world-bg battle now keeps the frame starting from underneath itself
  (drawBaseInStack).  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 before.

* worldZones was keyed to that same clear base, so it came out nil for a
  frame whose world pass HAD run -- dropping endFrame's world blit onto
  the UI zone list instead, smearing the party menu's own HP-bar palettes
  across a world-canvas-sized image.  Keyed to whether the map drew.

* endFrame's letterbox clear read letterboxWhite off visibleBase alone,
  so an opaque menu over a BG "white" battle flipped its surround to
  black the same way.  Same whole-stack hold.

* ChoiceBox bottom-anchored unconditionally, docking it to the WINDOW
  edge.  That is only right when it rides the dialogue box beneath it,
  which is anchored there too; TextBox now passes the anchor and nothing
  else does, so the battle's switch offer and the shop/PC confirms stay
  over the screen that pushed them.

* TextBox anchors likewise: a battle is a self-contained SCREEN, not the
  window, and pokered prints its text box in the same 160x144 tilemap as
  the HUD.  The caught-mon nickname prompt was landing a whole letterbox
  below the blanked battle field it is printed on.  BattleState.holdsUI-
  Anchors holds setUIAnchor off while a battle is in the stack; the
  overworld's own dialogue box still docks to the screen edge.
This commit is contained in:
spiritsnails
2026-08-01 22:53:04 -06:00
parent e6c1ed8753
commit 46bd0f6709
6 changed files with 297 additions and 12 deletions
+8 -2
View File
@@ -16,6 +16,12 @@ function ChoiceBox.new(game, onChoose, opts)
self.index = (opts and opts.defaultNo) and 2 or 1
-- BIT_NO_MENU_BUTTON_SOUND: PC-session prompts stay silent
self.noSound = (opts and opts.noSound) or false
-- Only a choice box sitting on top of an ANCHORED dialogue box rides the
-- anchor with it (TextBox passes it). A bare one -- the battle's switch
-- offer, a shop or PC confirm -- belongs over the screen that pushed it,
-- and docking it to the window edge instead tears it off that screen by
-- however far the letterbox sits from the edge.
self.anchor = opts and opts.anchor or nil
local box = Theme.choiceBox
self.tx = (opts and opts.tx) or box.tx
self.ty = (opts and opts.ty) or box.ty
@@ -64,9 +70,9 @@ function ChoiceBox:draw()
local tx, ty, tw, th = self.tx, self.ty, self.tw, self.th
-- rides the same bottom anchor as the dialogue box it sits above, so the
-- pair travels together (the anchor keeps each element's gap from the edge)
local r = self.game and self.game.renderer
local r = self.anchor and self.game and self.game.renderer
if r and r.setUIAnchor then
r:setUIAnchor(tx * 8, ty * 8, tw * 8, th * 8, "bottom")
r:setUIAnchor(tx * 8, ty * 8, tw * 8, th * 8, self.anchor)
end
Font.drawBox(tx, ty, tw, th)
love.graphics.setColor(0, 0, 0, 1)