Fix MSX2+ horizontal scroll and rework FH logo animation

- Enable single-page horizontal scrolling (R#25 SP2=1) so the scroll
  offset wraps within the 256px page. Without it the V9958 default
  (two-page) scroll revealed the wrong VRAM page, making new tiles
  appear on the opposite edge during horizontal moves.
- Replace the rectangle-approximated FH logo with strokes matching the
  original firehawk.sc5 artwork. The F+H is drawn as partitioned fills
  (crossbar arms, stem, right leg) with dedicated palette entries for
  the two stem/crossbar crossings, so each horizontal then each
  vertical shimmers in and out gap-free before the whole logo holds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 23:40:09 +02:00
co-authored by Claude Opus 4.8
parent f3340e86c6
commit cdc7bc545d
2 changed files with 56 additions and 29 deletions
Binary file not shown.
+56 -29
View File
@@ -726,25 +726,29 @@ static const u16 g_DefaultPal[16] = {
0x0171, 0x0373, 0x0661, 0x0664, 0x0411, 0x0265, 0x0555, 0x0777 0x0171, 0x0373, 0x0661, 0x0664, 0x0411, 0x0265, 0x0555, 0x0777
}; };
// Fade palette entry [idx] through brightness sequence 1..7..7..6..0 // Fade a set of palette entries up (black->white) and back down to black
// (mirrors BASIC GOSUBs 100/300/500/700: FOR I=1 TO 30: NEXT I ≈ 5 VBlanks) // together, so a stroke made of several palette entries shimmers as one shape.
static void LogoFade(u8 idx, u8 delay_halts) static void LogoFadeSet(const u8* idx, u8 n, u8 delay_halts)
{ {
static const u8 seq[15] = {1,2,3,4,5,6,7,7,6,5,4,3,2,1,0}; static const u8 seq[15] = {1,2,3,4,5,6,7,7,6,5,4,3,2,1,0};
for (u8 s = 0; s < 15; s++) { for (u8 s = 0; s < 15; s++) {
u8 lvl = seq[s]; u16 c = ((u16)seq[s] << 8) | ((u16)seq[s] << 4) | seq[s];
u16 c = ((u16)lvl << 8) | ((u16)lvl << 4) | lvl; for (u8 k = 0; k < n; k++) VDP_SetPaletteEntry(idx[k], c);
VDP_SetPaletteEntry(idx, c);
for (u8 h = 0; h < delay_halts; h++) Halt(); for (u8 h = 0; h < delay_halts; h++) Halt();
} }
} }
// GOSUB 1000 inner body: set all 4 palette entries to the same grey level, 5 Halts each. // The logo is an "F" and "H" combined, sharing the left vertical stroke and the
static void LogoFadeAllColors(u8 lvl) // middle crossbar. Each stroke shimmers in and out on its own before the whole
{ // logo lights up. Because the stem and crossbars overlap, the two crossing
u16 c = ((u16)lvl << 8) | ((u16)lvl << 4) | lvl; // points get their OWN palette entries (6, 7) that are lit during BOTH the
for (u8 idx = 2; idx <= 5; idx++) { VDP_SetPaletteEntry(idx, c); Halt(); Halt(); Halt(); Halt(); Halt(); } // crossbar's turn and the stem's turn — so neither the horizontals nor the
} // verticals ever show a gap. Strokes are partitioned (no shared pixels):
// pal2 = top crossbar arms pal3 = stem (excl. crossings) pal6 = top crossing
// pal4 = middle crossbar arms pal5 = right leg pal7 = middle crossing
// Origin X is even so the byte-aligned HMMV fills land on pixel boundaries.
#define LOGO_OX 110 // logo origin X (even: keeps HMMV fills pixel-aligned)
#define LOGO_OY 73 // logo origin Y
static void ShowLogoAnimation(void) static void ShowLogoAnimation(void)
{ {
@@ -756,30 +760,45 @@ static void ShowLogoAnimation(void)
VDP_ClearVRAM(); VDP_ClearVRAM();
VDP_DisableSprite(); VDP_DisableSprite();
// Blank all 16 palette entries before enabling display // Blank all 16 palette entries so the strokes stay invisible until faded in.
for (u8 i = 0; i < 16; i++) for (u8 i = 0; i < 16; i++)
VDP_SetPaletteEntry(i, 0); VDP_SetPaletteEntry(i, 0);
// Draw FH logo using HMMV fills. // Draw the strokes. They are partitioned so no two share a pixel; the two
// pal2=f-crossbar, pal3=spine, pal4=h-crossbar, pal5=h-right-leg. // stem/crossbar crossings are separate fills (pal6, pal7). Local x12..21 is
// Draw crossbars first, then spine overwrites their centre, then right leg. // the stem column; crossbars are split into left/right arms around it.
VDP_CommandHMMV(118, 75, 28, 5, 0x22); VDP_CommandWait(); // f-crossbar VDP_CommandHMMV(LOGO_OX + 12, LOGO_OY + 0, 10, 2, 0x33); VDP_CommandWait(); // stem: above top bar
VDP_CommandHMMV(118, 90, 28, 4, 0x44); VDP_CommandWait(); // h-crossbar VDP_CommandHMMV(LOGO_OX + 12, LOGO_OY + 7, 10, 10, 0x33); VDP_CommandWait(); // stem: between bars
VDP_CommandHMMV(122, 73, 10, 34, 0x33); VDP_CommandWait(); // spine VDP_CommandHMMV(LOGO_OX + 12, LOGO_OY + 21, 10, 13, 0x33); VDP_CommandWait(); // stem: below mid bar
VDP_CommandHMMV(140, 88, 6, 19, 0x55); VDP_CommandWait(); // h right leg VDP_CommandHMMV(LOGO_OX + 8, LOGO_OY + 2, 4, 5, 0x22); VDP_CommandWait(); // top bar: left arm
VDP_CommandHMMV(LOGO_OX + 22, LOGO_OY + 2, 12, 5, 0x22); VDP_CommandWait(); // top bar: right arm
VDP_CommandHMMV(LOGO_OX + 12, LOGO_OY + 2, 10, 5, 0x66); VDP_CommandWait(); // top crossing
VDP_CommandHMMV(LOGO_OX + 8, LOGO_OY + 17, 4, 4, 0x44); VDP_CommandWait(); // mid bar: left arm
VDP_CommandHMMV(LOGO_OX + 22, LOGO_OY + 17, 8, 4, 0x44); VDP_CommandWait(); // mid bar: right arm
VDP_CommandHMMV(LOGO_OX + 12, LOGO_OY + 17, 10, 4, 0x77); VDP_CommandWait(); // middle crossing
VDP_CommandHMMV(LOGO_OX + 30, LOGO_OY + 15, 4, 19, 0x55); VDP_CommandWait(); // right leg
VDP_EnableDisplay(TRUE); VDP_EnableDisplay(TRUE);
// Phase 1: verticals spine then right leg, each fades in and out // Reveal order: each horizontal shimmers, then each vertical, then the total.
LogoFade(3, 5); // Each crossbar lights its arms + its crossing; the stem lights its body +
LogoFade(5, 5); // both crossings — so every stroke appears as a complete shape.
static const u8 fadeTop[2] = {2, 6}; // top crossbar
static const u8 fadeMid[2] = {4, 7}; // middle crossbar
static const u8 fadeStem[3] = {3, 6, 7}; // left stem
static const u8 fadeLeg[1] = {5}; // right leg
static const u8 fadeAll[6] = {2, 3, 4, 5, 6, 7};
LogoFadeSet(fadeTop, 2, 5);
LogoFadeSet(fadeMid, 2, 5);
LogoFadeSet(fadeStem, 3, 5);
LogoFadeSet(fadeLeg, 1, 5);
// Phase 2: horizontals f-crossbar then h-crossbar, each fades in and out // The total: all strokes fade up together and hold.
LogoFade(2, 5); for (u8 lvl = 1; lvl <= 7; lvl++) {
LogoFade(4, 5); u16 c = ((u16)lvl << 8) | ((u16)lvl << 4) | lvl;
for (u8 k = 0; k < 6; k++) VDP_SetPaletteEntry(fadeAll[k], c);
// Phase 3: complete FH all four colors fade in together and hold for (u8 h = 0; h < 5; h++) Halt();
for (u8 lvl = 1; lvl <= 7; lvl++) LogoFadeAllColors(lvl); }
for (u8 h = 0; h < 150; h++) Halt(); for (u8 h = 0; h < 150; h++) Halt();
// Restore MSX2 default palette so SCREEN 0 text mode remains visible. // Restore MSX2 default palette so SCREEN 0 text mode remains visible.
@@ -961,6 +980,14 @@ void main(void)
VDP_ClearVRAM(); VDP_ClearVRAM();
VDP_DisableSprite(); // Software-only sprites: disable VDP hardware sprite layer VDP_DisableSprite(); // Software-only sprites: disable VDP hardware sprite layer
// Single-page horizontal scrolling (R#25 SP2=1). The default SP2=0 scrolls
// across TWO pages (512px), so a non-zero R#26/R#27 offset wraps the *next*
// VRAM page into the entering edge instead of column 0..63 of this page —
// which made the entering strip appear on the wrong side during horizontal
// scroll. SP2=1 makes the offset wrap within one 256px page, which is the
// behaviour the horizontal path in ScrollDraw() relies on.
VDP_SetHorizontalMode(R25_SP2);
// Software sprite: initialize direction and frame for this game. // Software sprite: initialize direction and frame for this game.
g_SprDir = SPR_SOUTH; // face south (toward exit) at game start g_SprDir = SPR_SOUTH; // face south (toward exit) at game start
g_SprFrame = 0; g_SprFrame = 0;