Port timing/parity fixes, seamless battle transitions, faithful-res lock, and zoom-aware UI anchoring

Ports from a downstream fork, hand-surgered hunk-by-hunk to exclude the
fork's randomizer/pokescript work and to skip a FixedStep jitter-tolerance
attempt that never fixed the stutter it targeted.

- src/core/Timing.lua: hardware-accurate frame-delay catalog ported from
  pret/pokered, feeding BattleState:waitNext, EffectRegistry's miss/crit
  beats, TextBox/ChoiceBox scroll and prompt holds, and the battle
  silhouette slide/shake/blink/faint timings.
- Seamless battle transitions: Renderer:drawBattleWipe replaces the old
  160x144-only cascade with one wipe drawn over the whole surface at any
  zoom or window size; BattleTransition's per-style frame lengths are
  corrected against pokered-c's derivation; Transition.battleReturn adds
  the post-battle GBFadeInFromWhite the port never had.
- BATTLE SIZE / BATTLE BG options (BattleState:wantsFillScale/bgMode,
  Game.fillScaleInStack/worldBgBattleDim): battle surface can fill the
  window instead of the fixed integer letterbox, and the area around it
  can show white/black/the dimmed overworld instead of only white.
- src/core/FaithfulRes.lua: locks the window to an exact 160x144 multiple.
- Zoom-aware UI anchoring: Renderer:uiScale steps the UI down with survey
  zoom (gated to worldActive so the title/intro never shrink);
  Renderer:setUIAnchor lets TextBox, ChoiceBox, and an opted-in Menu
  (the START menu) pin themselves to a screen edge instead of the
  zoomed-out letterbox.
This commit is contained in:
spiritsnails
2026-08-01 15:52:34 -06:00
parent 5e89e35e02
commit aedc63c40d
23 changed files with 2515 additions and 114 deletions
+27
View File
@@ -717,6 +717,33 @@ function OverworldState:pushBattle(battle)
if battle.computeMusicKind then
require("src.core.Music").playBattle(Game.data, battle:computeMusicKind())
end
-- Coming back from the battle screen is a fade, not a cut: EnterMap sees
-- BIT_BATTLE_OVER_OR_BLACKOUT set and runs MapEntryAfterBattle
-- (home/overworld.asm:22, :749-753) = GBFadeInFromWhite, behind the
-- `ld c, 10 / call DelayFrames` at :351-352.
--
-- It is wrapped around onFinish here, at the one funnel every battle goes
-- through, rather than inside afterBattle: a script-driven win defers
-- afterBattle into ctx.afterScript so an evolution screen cannot be buried
-- under the trainer's follow-up text (see Commands.start_battle), and the
-- fade inherited that deferral -- on a rival battle it fired after the
-- post-battle dialogue AND the walk-off, instead of when the battle ended.
--
-- The rest of onFinish runs as the fade's onDone, which is also the
-- hardware order: MapEntryAfterBattle fades the map back in, and only then
-- does the map script get to run. The overworld is frozen meanwhile --
-- StateStack updates the top state only -- so nothing moves under it.
local finish = battle.onFinish
battle.onFinish = function(result)
if result == "lose" then
-- the blackout path warps to the heal point with its own transition
if finish then finish(result) end
return
end
Game.stack:push(require("src.render.Transition").battleReturn(Game,
function() if finish then finish(result) end end))
end
Game.stack:push(BattleTransition.new(Game, function()
Game.stack:push(battle)
end, {