commit b4d57fb0d043e17263036f0a7972200ee6b8a916
Author: jens <jens.se@icloud.com>
AuthorDate: Sat Aug 22 08:13:02 2026 +0200
Commit: jens <jens.se@icloud.com>
CommitDate: Sat Aug 22 08:13:02 2026 +0200
Scale the finale/cast-crawl screens for the larger buffer
Same root cause as the earlier menu/intermission/status bar fix:
f_finale.c's text screen, cast-call screen, and end-episode art were
still laid out in native ORIGWIDTH x ORIGHEIGHT (320x200) terms and
only filled a corner of the real (now larger) buffer. Applies the same
scaled-draw approach (V_DrawPatchScaled/V_DrawPatchFlippedScaled) via
a file-wide macro redirect, plus fixes the handful of SCREENWIDTH-as-
native-space bugs in F_TextWrite's line wrap and F_CastPrint/
F_CastDrawer's centering.
F_BunnyScroll's scrolling mirror effect needed different treatment:
it was already reading past its source patches' actual (native-width)
column data once the real screen exceeded that width - not just a
scaling bug but a latent out-of-bounds read. F_DrawPatchCol now takes
an explicit scale and maps each real column back to its native source
column instead of assuming they're the same width.
Verified via a normal-gameplay smoke test (build is clean, no
regressions in code paths outside the finale itself) - the finale
screens themselves need a full DOOM2/Ultimate Doom IWAD to reach
in-game (cast call needs MAP30, bunny-scroll needs episode 4), which
isn't available in this environment; verified by careful code review
instead, following the same pattern already proven correct for the
menu/intermission/status bar fixes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
TODO.md | 3 --
src/doom/f_finale.c | 97 ++++++++++++++++++++++++++++++++++++-----------------
2 files changed, 67 insertions(+), 33 deletions(-)
diff --git a/TODO.md b/TODO.md
index 1e42f9ff..29c1dac3 100644
--- a/TODO.md
+++ b/TODO.md
@@ -28,9 +28,6 @@ and unstructured wish list of features and improvements. The bug tracker
- Screensaver mode
* Infinite splitscreen fork:
- - Finale/cast-crawl screen (f_finale.c) is still native-resolution and
- too small, same root cause as the now-fixed menu/intermission/status
- bar (see m_menu.c, wi_stuff.c, st_stuff.c/st_lib.c, i_video.h).
- A player dying and respawning resets the whole game for everybody
instead of just that player.
- Losing connection or reloading the phone controller page spawns a new
diff --git a/src/doom/f_finale.c b/src/doom/f_finale.c
index 0acd6e39..5ab1c542 100644
--- a/src/doom/f_finale.c
+++ b/src/doom/f_finale.c
@@ -25,6 +25,7 @@
#include "deh_main.h"
#include "i_system.h"
#include "i_swap.h"
+#include "i_video.h"
#include "z_zone.h"
#include "v_video.h"
#include "w_wad.h"
@@ -38,6 +39,23 @@
#include "doomstat.h"
#include "r_state.h"
+// The finale screens are laid out in ORIGWIDTH x ORIGHEIGHT (320x200)
+// terms, same as vanilla (see i_video.h for why SCREENWIDTH/SCREENHEIGHT
+// no longer match that). Redirect every V_DrawPatch/V_DrawPatchFlipped
+// call in this file to the scaled variant instead of touching each call
+// site individually. This file's own layout math also uses
+// SCREENWIDTH/SCREENHEIGHT in a few places standing in for
+// ORIGWIDTH/ORIGHEIGHT (F_TextWrite's line-wrap check, F_CastPrint/
+// F_CastDrawer's centering) - those are fixed at each use instead of
+// here. The tiled background fills (F_TextWrite, F_BunnyScroll's
+// scrolling column blit) are raw pixel loops, not patch draws, and
+// are handled separately since they're already resolution-independent
+// or need genuinely different (not just scaled) logic.
+#define V_DrawPatch(x, y, patch) \
+ V_DrawPatchScaled((x), (y), (patch), (SCREENWIDTH / ORIGWIDTH))
+#define V_DrawPatchFlipped(x, y, patch) \
+ V_DrawPatchFlippedScaled((x), (y), (patch), (SCREENWIDTH / ORIGWIDTH))
+
typedef enum
{
F_STAGE_TEXT,
@@ -284,12 +302,12 @@ void F_TextWrite (void)
}
w = SHORT (hu_font[c]->width);
- if (cx+w > SCREENWIDTH)
+ if (cx+w > ORIGWIDTH)
break;
V_DrawPatch(cx, cy, hu_font[c]);
cx+=w;
}
-
+
}
//
@@ -512,7 +530,7 @@ void F_CastPrint (const char *text)
}
// draw it
- cx = SCREENWIDTH/2-width/2;
+ cx = ORIGWIDTH/2-width/2;
ch = text;
while (ch)
{
@@ -559,41 +577,52 @@ void F_CastDrawer (void)
patch = W_CacheLumpNum (lump+firstspritelump, PU_CACHE);
if (flip)
- V_DrawPatchFlipped(SCREENWIDTH/2, 170, patch);
+ V_DrawPatchFlipped(ORIGWIDTH/2, 170, patch);
else
- V_DrawPatch(SCREENWIDTH/2, 170, patch);
+ V_DrawPatch(ORIGWIDTH/2, 170, patch);
}
//
// F_DrawPatchCol
+// Draws one column of a native-space (ORIGWIDTH-wide) patch, scaled -
+// each source pixel becomes a scale x scale block, matching
+// V_DrawPatchScaled's approach (see v_video.c) but for one column at a
+// specific real-screen x rather than a whole patch at a scaled
+// position. Used only by F_BunnyScroll's scrolling mirror effect,
+// which needs to pick a different source column per real x rather than
+// draw a whole patch at once.
//
void
F_DrawPatchCol
( int x,
patch_t* patch,
- int col )
+ int col,
+ int scale )
{
column_t* column;
byte* source;
pixel_t* dest;
- pixel_t* desttop;
int count;
-
+ int i;
+ int dy;
+
+ if (col < 0 || col >= SHORT(patch->width))
+ return;
+
column = (column_t *)((byte *)patch + LONG(patch->columnofs[col]));
- desttop = I_VideoBuffer + x;
// step through the posts in a column
while (column->topdelta != 0xff )
{
source = (byte *)column + 3;
- dest = desttop + column->topdelta*SCREENWIDTH;
count = column->length;
-
- while (count--)
+
+ for (i = 0 ; i < count ; i++)
{
- *dest = *source++;
- dest += SCREENWIDTH;
+ dest = I_VideoBuffer + (column->topdelta + i) * scale * SCREENWIDTH + x;
+ for (dy = 0 ; dy < scale ; dy++, dest += SCREENWIDTH)
+ *dest = source[i];
}
column = (column_t *)( (byte *)column + column->length + 4 );
}
@@ -607,42 +636,50 @@ void F_BunnyScroll (void)
{
signed int scrolled;
int x;
+ int nativecol;
+ int scale;
patch_t* p1;
patch_t* p2;
char name[10];
int stage;
static int laststage;
-
+
+ scale = SCREENWIDTH / ORIGWIDTH;
+
p1 = W_CacheLumpName (DEH_String("PFUB2"), PU_LEVEL);
p2 = W_CacheLumpName (DEH_String("PFUB1"), PU_LEVEL);
V_MarkRect (0, 0, SCREENWIDTH, SCREENHEIGHT);
-
- scrolled = (SCREENWIDTH - ((signed int) finalecount-230)/2);
- if (scrolled > SCREENWIDTH)
- scrolled = SCREENWIDTH;
+
+ // In native (ORIGWIDTH) terms, matching vanilla's timing - p1/p2
+ // are native-width patches, and each real column below maps back
+ // down to a native source column (x / scale) to read from them.
+ scrolled = (ORIGWIDTH - ((signed int) finalecount-230)/2);
+ if (scrolled > ORIGWIDTH)
+ scrolled = ORIGWIDTH;
if (scrolled < 0)
scrolled = 0;
-
+
for ( x=0 ; x<SCREENWIDTH ; x++)
{
- if (x+scrolled < SCREENWIDTH)
- F_DrawPatchCol (x, p1, x+scrolled);
+ nativecol = x / scale;
+ if (nativecol+scrolled < ORIGWIDTH)
+ F_DrawPatchCol (x, p1, nativecol+scrolled, scale);
else
- F_DrawPatchCol (x, p2, x+scrolled - SCREENWIDTH);
+ F_DrawPatchCol (x, p2, nativecol+scrolled - ORIGWIDTH, scale);
}
-
+
if (finalecount < 1130)
return;
if (finalecount < 1180)
{
- V_DrawPatch((SCREENWIDTH - 13 * 8) / 2,
- (SCREENHEIGHT - 8 * 8) / 2,
+ V_DrawPatch((ORIGWIDTH - 13 * 8) / 2,
+ (ORIGHEIGHT - 8 * 8) / 2,
W_CacheLumpName(DEH_String("END0"), PU_CACHE));
laststage = 0;
return;
}
-
+
stage = (finalecount-1180) / 5;
if (stage > 6)
stage = 6;
@@ -651,10 +688,10 @@ void F_BunnyScroll (void)
S_StartSound (NULL, sfx_pistol);
laststage = stage;
}
-
+
DEH_snprintf(name, 10, "END%i", stage);
- V_DrawPatch((SCREENWIDTH - 13 * 8) / 2,
- (SCREENHEIGHT - 8 * 8) / 2,
+ V_DrawPatch((ORIGWIDTH - 13 * 8) / 2,
+ (ORIGHEIGHT - 8 * 8) / 2,
W_CacheLumpName (name,PU_CACHE));
}