From 88e2ec204283ab434b5b83f511b5f386609f17f9 Mon Sep 17 00:00:00 2001 From: Bart in 't Veld Date: Sat, 1 Aug 2026 15:25:54 +0200 Subject: [PATCH] Fix encounter silhouette slide speed and blackout (#577) The battle intro slide ran at 4px/frame over 40 frames -- twice the original speed -- and drew the pics in their normal palette instead of as black silhouettes. SlidePlayerAndEnemySilhouettesOnScreen scrolls SCX from $90 to 0 at 2px/frame (~72 frames) under the %11100100 silhouette palette, only running SET_PAL_BATTLE once the pics land. Match that: introSlide starts at 80 with a 2px/frame offset (80 frames over the full 160px width), and picImage bakes both pics through PAL_BLACK while introSlide > 0, exactly like the evolution movie (#279). parity_battle_intro_chrome waited a hardcoded 45 frames for the slide to land; bumped to 85 to cover the slower slide. --- src/battle/BattleState.lua | 24 +++++++++++++++++------- src/battle/WideBattle.lua | 2 +- tests/parity_battle_intro_chrome.lua | 7 ++++--- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/battle/BattleState.lua b/src/battle/BattleState.lua index 21eaf971..dfa915e7 100644 --- a/src/battle/BattleState.lua +++ b/src/battle/BattleState.lua @@ -291,10 +291,17 @@ function BattleState:picImage(img) or PaletteFX.mode == "classic" if self.grayPics or mono then return grayImage(img) end -- SET_PAL_BATTLE_BLACK covers every battle palette slot, so the pics go - -- dark with the HP bars while the blackout text is up (#292). Below the - -- mono check on purpose: the forced-mono modes re-threshold the whole - -- frame downstream, and the DMG had no SGB darkening to begin with. - if self.blackedOut then return blackImage(self.data, img) end + -- dark with the HP bars while the blackout text is up (#292). The intro + -- silhouette slide (SlidePlayerAndEnemySilhouettesOnScreen) darkens the + -- same way: the original slides both pics in under the %11100100 + -- silhouette palette and only runs SET_PAL_BATTLE once they have landed, + -- so a still-sliding pic reads as a black silhouette, exactly like the + -- evolution movie's PAL_BLACK (#577). Below the mono check on purpose: + -- the forced-mono modes re-threshold the whole frame downstream, and the + -- DMG had no SGB darkening to begin with. + if self.blackedOut or (self.introSlide or 0) > 0 then + return blackImage(self.data, img) + end return fadeImage(img, self:activeBgp()) end @@ -1241,8 +1248,11 @@ function BattleState:enter() -- without a transition (link battles, scripted pushes) Music.playBattle(self.data, self.musicKind) -- intro presentation (SlidePlayerAndEnemySilhouettesOnScreen): both - -- sides slide in; the trainer pics stay up until the send-outs - self.introSlide = 40 + -- sides slide in as black silhouettes. The original scrolls SCX from + -- $90 to 0 two pixels per frame (72 frames); the port covers the full + -- 160px screen width, so 2px/frame is an 80-frame slide (slide offset is + -- introSlide*2 below). The trainer pics stay up until the send-outs. + self.introSlide = 80 self.showEnemyTrainer = self.kind == "trainer" and self.trainerPic ~= nil -- DrawAllPokeballs (common_text.asm:27) puts the party ball rows AND the -- HUD corner/underline tiles under them (PlacePlayerHUDTiles / @@ -5220,7 +5230,7 @@ function BattleState:drawClassic() if sx == 0 and sy == 0 and fx and fx.shake and fx.shake > 0 then sx = self.frame % 4 < 2 and 2 or -2 end - local slide = (self.introSlide or 0) * 4 -- intro slide-in offset + local slide = (self.introSlide or 0) * 2 -- intro slide-in offset (2px/frame) if self:colorMode() then -- SGB pipeline: gray BG canvas -> (wavy) -> zone recolor with the diff --git a/src/battle/WideBattle.lua b/src/battle/WideBattle.lua index f66d6894..fa455ae6 100644 --- a/src/battle/WideBattle.lua +++ b/src/battle/WideBattle.lua @@ -320,7 +320,7 @@ function WideBattle.draw(battle) if sx == 0 and sy == 0 and fx and fx.shake and fx.shake > 0 then sx = battle.frame % 4 < 2 and 2 or -2 end - local slide = (battle.introSlide or 0) * 4 + local slide = (battle.introSlide or 0) * 2 -- Each side keeps its original sprite pixels and placement math: the two -- 160x144 OAM regions are translated apart and clipped into the wider diff --git a/tests/parity_battle_intro_chrome.lua b/tests/parity_battle_intro_chrome.lua index 368c357b..af1b12f8 100644 --- a/tests/parity_battle_intro_chrome.lua +++ b/tests/parity_battle_intro_chrome.lua @@ -98,8 +98,9 @@ check(wild.queue[1] and wild.queue[1].fn ~= nil, check(wild.queue[2] and wild.queue[2].text == wild.introText, "the intro text is still the second queue row") --- let the silhouette slide land so the intro box is genuinely up -for _ = 1, 45 do wild:update(1 / 60) end +-- let the silhouette slide land so the intro box is genuinely up (the slide +-- now runs 80 frames at 2px/frame, matching the original ~2px/frame) +for _ = 1, 85 do wild:update(1 / 60) end eq(wild.introSlide or 0, 0, "the silhouette slide has landed") eq(wild.introBalls, true, "the window is still open under the intro text") eq(currentText(wild), wild.introText, "the intro box is the row on screen") @@ -164,7 +165,7 @@ local tr = BattleState.newTrainer(game2, "OPP_YOUNGSTER", 1) tr.onFinish = function() end tr:enter() eq(tr.introBalls, true, "the trainer intro opens the same window") -for _ = 1, 45 do tr:update(1 / 60) end +for _ = 1, 85 do tr:update(1 / 60) end local ok3, err3, rows3 = snapshotHUD(tr) check(ok3, "drawHUDs runs during the trainer intro: " .. tostring(err3))