From 733450bf863d1d0c7febc6ac40c7c22d8431d03f Mon Sep 17 00:00:00 2001 From: Shane McGovern Date: Sun, 2 Aug 2026 16:34:02 +0100 Subject: [PATCH] Fix faint slide starting partway down (#671) The faint slide was shortened from 30 to Timing.FAINT_SLIDE (14) frames in the timing-parity pass, but fxFaintOffset still computed the offset with a stale (30 - frames) * 2. With frames starting at 14 the sprite teleported 32px down on the first frame and only slid the remaining 28px, cutting the animation short. SlideDownFaintedMonPic drops the pic one 8px row per 2-frame step, so the offset advances Timing.FAINT_SLIDE_STEP (4px) per frame at 1x and covers the full 56px PIC_HEIGHT over the 14-frame budget. --- src/battle/BattleState.lua | 6 +++++- src/core/Timing.lua | 1 + tests/engine/timing_parity.lua | 20 ++++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/battle/BattleState.lua b/src/battle/BattleState.lua index e5140bdf..3e7f729f 100644 --- a/src/battle/BattleState.lua +++ b/src/battle/BattleState.lua @@ -4542,11 +4542,15 @@ end -- pixels, so it scales with the pic's draw scale (the player's default 2x -- sinks 2x as fast to sink at the same visual rate); a mod scale composes -- the same way. scale defaults to the vanilla side scale when unknown. +-- SlideDownFaintedMonPic drops the pic one 8px row per 2-frame step, so +-- the offset advances Timing.FAINT_SLIDE_STEP (4px) per frame at 1x -- +-- the full 56px PIC_HEIGHT slide over the 14-frame budget (#671: the +-- old (30 - frames) math teleported the sprite 32px down on frame one). function BattleState:fxFaintOffset(battler, scale) local fx = self.fx if self:fxFaintActive(battler) then scale = scale or (battler.isPlayer and 2 or 1) - return (30 - fx.faint.frames) * 2 * scale + return (Timing.FAINT_SLIDE - fx.faint.frames) * Timing.FAINT_SLIDE_STEP * scale end return 0 end diff --git a/src/core/Timing.lua b/src/core/Timing.lua index f00d3d43..1a1e05bf 100644 --- a/src/core/Timing.lua +++ b/src/core/Timing.lua @@ -129,6 +129,7 @@ Timing.NO_MOVES_LEFT = 60 -- core.asm:2753-2754 Timing.TRAINER_VICTORY = 40 -- core.asm:940-941 Timing.PLAYER_BLACKOUT = 40 -- core.asm:1143-1144 Timing.FAINT_SLIDE_ROW = 2 -- core.asm:1216-1217, per row +Timing.FAINT_SLIDE_STEP = 8 / Timing.FAINT_SLIDE_ROW -- 4px per frame at 1x Timing.TRAINER_SLIDE_COL = 2 -- core.asm:1267-1268, per column -- HP bar (engine/gfx/hp_bar.asm) --------------------------------------------- diff --git a/tests/engine/timing_parity.lua b/tests/engine/timing_parity.lua index 9b09f8bd..e08f3a71 100644 --- a/tests/engine/timing_parity.lua +++ b/tests/engine/timing_parity.lua @@ -273,6 +273,26 @@ T.eq(Timing.BLINK_MON % 10, 0, -- SlideDownFaintedMonPic: b = PIC_HEIGHT slide steps of DelayFrames 2 T.eq(Timing.FAINT_SLIDE, 14, "the faint slide is 7 steps x 2 frames") +-- #671: the slide must start at the sprite's resting spot and sink the +-- full PIC_HEIGHT (7 rows x 8px = 56px at 1x) across those 14 frames. +-- The old (30 - frames) * 2 math teleported the pic 32px down on frame +-- one once the budget was shortened from 30 to 14 frames. +local faintBattle = newBattle() +faintBattle.fx = { faint = { battler = faintBattle.enemy, + frames = Timing.FAINT_SLIDE } } +T.eq(faintBattle:fxFaintOffset(faintBattle.enemy, 1), 0, + "the faint slide starts at offset 0 (#671)") +faintBattle.fx.faint.frames = Timing.FAINT_SLIDE - 7 +T.eq(faintBattle:fxFaintOffset(faintBattle.enemy, 1), + 7 * Timing.FAINT_SLIDE_STEP, + "the slide sinks one 8px row per step") +faintBattle.fx.faint.frames = 1 +T.eq(faintBattle:fxFaintOffset(faintBattle.enemy, 1), + (Timing.FAINT_SLIDE - 1) * Timing.FAINT_SLIDE_STEP, + "the slide reaches 52px by the last visible frame") +T.eq(Timing.FAINT_SLIDE * Timing.FAINT_SLIDE_STEP, 56, + "and covers the full 7-row pic height over the whole budget") + T.eq(Timing.MOVE_STATUS_OR_MISS, 30, "a status move or a miss holds DelayFrames 30 before its text")