617 lines
27 KiB
Diff
617 lines
27 KiB
Diff
diff -ruN a/assets/genC64Data.py b/assets/genC64Data.py
|
|
--- a/assets/genC64Data.py 2026-10-05 14:05:44.479069311 -0500
|
|
+++ b/assets/genC64Data.py 2026-10-05 14:12:10.086072322 -0500
|
|
@@ -67,6 +67,8 @@
|
|
("uint8_t stC64RngByte(uint8_t idx);", "kStRngTable[idx]"),
|
|
("const uint8_t *stC64SfxProgram(uint8_t idx);", "kStSfxPrograms[idx]"),
|
|
("uint8_t stC64FlameCel(uint8_t dirMask);", "kStFlameCelByDirMask[dirMask]"),
|
|
+ ("int8_t stC64IntroStarDx(uint8_t idx);", "kStIntroStarDx[idx]"),
|
|
+ ("int8_t stC64IntroStarDy(uint8_t idx);", "kStIntroStarDy[idx]"),
|
|
]
|
|
|
|
|
|
@@ -115,6 +117,10 @@
|
|
h.append("// 8=RIGHT); 0 = no cel for that combination.")
|
|
h.append("extern const uint8_t kStFlameCelByDirMask[16];")
|
|
h.append("")
|
|
+ h.append("// Level-intro star velocities per sprite 0..6 ($450C / $4513).")
|
|
+ h.append("extern const int8_t kStIntroStarDx[7];")
|
|
+ h.append("extern const int8_t kStIntroStarDy[7];")
|
|
+ h.append("")
|
|
h.append("// Cross-segment-safe accessors (see stC64Data.c): reach the tables above")
|
|
h.append("// by an inter-segment call on the IIgs, where a direct 2-byte data")
|
|
h.append("// reference from another bank is not relocatable.")
|
|
@@ -160,6 +166,11 @@
|
|
c.append(c_bytes(flame))
|
|
c.append("};")
|
|
c.append("")
|
|
+ dx = [b - 256 if b >= 128 else b for b in raw[0x450C:0x450C + 7]]
|
|
+ dy = [b - 256 if b >= 128 else b for b in raw[0x4513:0x4513 + 7]]
|
|
+ c.append("const int8_t kStIntroStarDx[7] = { " + ", ".join(str(v) for v in dx) + " };")
|
|
+ c.append("const int8_t kStIntroStarDy[7] = { " + ", ".join(str(v) for v in dy) + " };")
|
|
+ c.append("")
|
|
c.append("")
|
|
c.append("// ---------------------------------------------------------------------------")
|
|
c.append("// Accessors. On the IIgs the program is split across banks and a 2-byte")
|
|
diff -ruN a/spacetaxi.c b/spacetaxi.c
|
|
--- a/spacetaxi.c 2026-10-05 14:05:44.477069267 -0500
|
|
+++ b/spacetaxi.c 2026-10-05 14:12:10.087072342 -0500
|
|
@@ -925,8 +925,6 @@
|
|
static void startIntro(void) {
|
|
stIntroEnter(&gSim, &gLevel);
|
|
stRenderSceneChanged(&gSim);
|
|
- // After the scene change, which turns it back off.
|
|
- stRenderIntroStars();
|
|
// $459C: the intro's tune plays under the fly-in, not blocking; the
|
|
// C64 leaves it to the IRQ player and stops it at $4709.
|
|
stAudioMusic(ST_SONG_INTRO, true);
|
|
diff -ruN a/spacetaxi.h b/spacetaxi.h
|
|
--- a/spacetaxi.h 2026-10-05 14:05:44.478069289 -0500
|
|
+++ b/spacetaxi.h 2026-10-05 14:12:10.088072362 -0500
|
|
@@ -52,10 +52,6 @@
|
|
// cycle are palette writes instead of repainting 103 (and 407) cells.
|
|
// Call after stRenderSceneChanged, which turns it back off.
|
|
void stRenderTitleLogo(StSimT *sim);
|
|
-// Level intro only: cycle the starfield's ring palette slots so the warp
|
|
-// animates without a single pixel moving. Call after
|
|
-// stRenderSceneChanged, which turns it back off.
|
|
-void stRenderIntroStars(void);
|
|
// Fill the stage with a two-line message and present it. Used when an
|
|
// asset the game ships with will not load, so a player sees why the
|
|
// game stopped instead of a silently degraded ride.
|
|
diff -ruN a/stC64Data.c b/stC64Data.c
|
|
--- a/stC64Data.c 2026-10-05 14:05:44.475069223 -0500
|
|
+++ b/stC64Data.c 2026-10-05 14:12:10.085072302 -0500
|
|
@@ -1322,6 +1322,9 @@
|
|
0x00, 0xD8, 0xD4, 0x00, 0xD5, 0xD2, 0xD1, 0x00, 0xD7, 0xD3, 0xD6, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
};
|
|
|
|
+const int8_t kStIntroStarDx[7] = { 0, 6, 6, 6, -6, -6, -6 };
|
|
+const int8_t kStIntroStarDy[7] = { -4, -4, 0, 4, 4, 0, -4 };
|
|
+
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Accessors. On the IIgs the program is split across banks and a 2-byte
|
|
@@ -1354,3 +1357,13 @@
|
|
uint8_t stC64FlameCel(uint8_t dirMask) {
|
|
return kStFlameCelByDirMask[dirMask];
|
|
}
|
|
+
|
|
+
|
|
+int8_t stC64IntroStarDx(uint8_t idx) {
|
|
+ return kStIntroStarDx[idx];
|
|
+}
|
|
+
|
|
+
|
|
+int8_t stC64IntroStarDy(uint8_t idx) {
|
|
+ return kStIntroStarDy[idx];
|
|
+}
|
|
diff -ruN a/stC64Data.h b/stC64Data.h
|
|
--- a/stC64Data.h 2026-10-05 14:05:44.476069245 -0500
|
|
+++ b/stC64Data.h 2026-10-05 14:12:10.086072322 -0500
|
|
@@ -44,6 +44,10 @@
|
|
// 8=RIGHT); 0 = no cel for that combination.
|
|
extern const uint8_t kStFlameCelByDirMask[16];
|
|
|
|
+// Level-intro star velocities per sprite 0..6 ($450C / $4513).
|
|
+extern const int8_t kStIntroStarDx[7];
|
|
+extern const int8_t kStIntroStarDy[7];
|
|
+
|
|
// Cross-segment-safe accessors (see stC64Data.c): reach the tables above
|
|
// by an inter-segment call on the IIgs, where a direct 2-byte data
|
|
// reference from another bank is not relocatable.
|
|
@@ -52,5 +56,7 @@
|
|
uint8_t stC64RngByte(uint8_t idx);
|
|
const uint8_t *stC64SfxProgram(uint8_t idx);
|
|
uint8_t stC64FlameCel(uint8_t dirMask);
|
|
+int8_t stC64IntroStarDx(uint8_t idx);
|
|
+int8_t stC64IntroStarDy(uint8_t idx);
|
|
|
|
#endif
|
|
diff -ruN a/stCels.c b/stCels.c
|
|
--- a/stCels.c 2026-10-05 14:06:02.361459928 -0500
|
|
+++ b/stCels.c 2026-10-05 14:12:10.090072402 -0500
|
|
@@ -2,9 +2,9 @@
|
|
// See stCels.h. Ordered exactly as stRenderPrewarm built the cels: the
|
|
// cab set (flying, wreck and the intro decorations) and passenger set in
|
|
// multicolour at the constant level colours (cab/passenger 6, shared
|
|
-// $D025/$D026 = 2/7), then the flame set in multicolour colour 7. No
|
|
-// intro star: the level intro paints its starfield into the character
|
|
-// screen (stTitle.c), so the star sprite is never drawn.
|
|
+// $D025/$D026 = 2/7), then the flame set in multicolour colour 7. The
|
|
+// intro star is not here: a bank's cels share one size, and the star is
|
|
+// built cropped to its one tile at boot (prewarmStar in stRender.c).
|
|
|
|
#include "stCels.h"
|
|
|
|
diff -ruN a/stRender.c b/stRender.c
|
|
--- a/stRender.c 2026-10-05 14:07:17.861084192 -0500
|
|
+++ b/stRender.c 2026-10-05 14:12:10.098072560 -0500
|
|
@@ -37,7 +37,7 @@
|
|
// The IIgs reaches its globals DBR-relative, so all of BSS must fit the
|
|
// entry bank below the I/O window; the caches are sized down there. The
|
|
// sprite cache must still hold every cel stRenderPrewarm builds (the
|
|
-// cab, exhaust and passenger sets) or a cel change mid-play rebuilds
|
|
+// cab, exhaust and passenger sets and the intro star) or a cel change mid-play rebuilds
|
|
// and recompiles a sprite. Glyphs are direct-mapped by character, so
|
|
// the count is a power of two.
|
|
// Room for the baked cab/passenger/exhaust set (36) plus the largest
|
|
@@ -96,9 +96,6 @@
|
|
// Title logo (stRenderTitleLogo): all four flip-book frames in one
|
|
// tile, animated by rewriting logoSlotMask[]'s palette slots.
|
|
bool logoMode;
|
|
- bool starMode; // level intro: cycling the starfield
|
|
- uint8_t starPhase; // which ring the bright band is on
|
|
- uint8_t starTick; // frames until the band steps outward
|
|
bool logoTileOk; // the live glyph is one of the frames
|
|
uint8_t logoSlotCount;
|
|
uint8_t logoSlotMask[ST_LOGO_PAL_SLOTS]; // frames each slot is lit in
|
|
@@ -141,16 +138,6 @@
|
|
// four of these are used; the rest are headroom if the frames change.
|
|
static const uint8_t kLogoPalSlot[ST_LOGO_PAL_SLOTS] = { 4u, 8u, 9u, 10u, 13u, 14u, 15u };
|
|
|
|
-// Level-intro warp: how a star ring looks at each distance BEHIND the
|
|
-// bright band, as C64 colour indices (white, light grey, grey, dark
|
|
-// grey, then black). A ring this far behind the band or further is
|
|
-// unlit, so a lit head with a short fading tail sweeps outward and the
|
|
-// rest of the field stays dark.
|
|
-static const uint8_t kStarRamp[] = { 1u, 15u, 12u, 11u };
|
|
-#define ST_STAR_RAMP_LEN (sizeof(kStarRamp) / sizeof(kStarRamp[0]))
|
|
-// Frames per ring step. The intro runs at the game's 30 Hz tick, so a
|
|
-// step every other frame sweeps the eight rings in about half a second.
|
|
-#define ST_STAR_STEP_FRAMES 2u
|
|
|
|
|
|
static StSpriteCacheT *cachedSprite(StSimT *sim, uint8_t idx);
|
|
@@ -175,8 +162,8 @@
|
|
static void paintCells(jlSurfaceT *stage, StSimT *sim);
|
|
static void pasteCell(jlSurfaceT *stage, const StSimT *sim, uint16_t cell, uint8_t bx, uint8_t by);
|
|
static bool prewarmReport(uint16_t expect);
|
|
+static void prewarmStar(StSimT *sim);
|
|
static bool spriteOnScreen(int16_t px, int16_t py);
|
|
-static void starPaletteApply(void);
|
|
static void tileFromChunky(jlTileT *out, const uint8_t *chunky);
|
|
static bool tileHasPixels(const uint8_t *chunky);
|
|
|
|
@@ -677,29 +664,6 @@
|
|
}
|
|
|
|
|
|
-// Light the starfield's ring colours for the current phase: the ring
|
|
-// the band is on takes kStarRamp[0], the ones just inside it the rest
|
|
-// of the ramp, and everything else the background. One palette write
|
|
-// per step and not a single pixel moves -- that is the whole point of
|
|
-// ST_STAR_RING_FIRST (stSim.h).
|
|
-static void starPaletteApply(void) {
|
|
- uint16_t pal[SURFACE_COLORS_PER_PALETTE];
|
|
- uint16_t color;
|
|
- uint8_t behind;
|
|
- uint8_t ring;
|
|
-
|
|
- jlPaletteGet(gRender.stage, 0u, pal);
|
|
- for (ring = 0u; ring < ST_STAR_RING_COUNT; ring++) {
|
|
- // How far this ring sits behind the band, wrapping so the band
|
|
- // re-enters at the centre as it leaves the edge.
|
|
- behind = (uint8_t)((gRender.starPhase - ring) & (ST_STAR_RING_COUNT - 1u));
|
|
- color = (behind < ST_STAR_RAMP_LEN) ? kC64Palette[kStarRamp[behind]] : kC64Palette[0];
|
|
- pal[ST_STAR_RING_FIRST + ring] = color;
|
|
- }
|
|
- jlPaletteSet(gRender.stage, 0u, pal);
|
|
-}
|
|
-
|
|
-
|
|
static bool spriteOnScreen(int16_t px, int16_t py) {
|
|
return px < SURFACE_WIDTH && py < SURFACE_HEIGHT && px > -ST_SPRITE_W && py > -(int16_t)ST_SPRITE_PX;
|
|
}
|
|
@@ -769,17 +733,6 @@
|
|
}
|
|
}
|
|
}
|
|
- if (gRender.starMode) {
|
|
- // The warp is the only thing animating here, and it animates in
|
|
- // the palette: no cells change, no sprite moves for it, so the
|
|
- // present that ends this frame copies no pixels at all.
|
|
- gRender.starTick++;
|
|
- if (gRender.starTick >= ST_STAR_STEP_FRAMES) {
|
|
- gRender.starTick = 0u;
|
|
- gRender.starPhase = (uint8_t)((gRender.starPhase + 1u) & (ST_STAR_RING_COUNT - 1u));
|
|
- starPaletteApply();
|
|
- }
|
|
- }
|
|
collectGlyphs(sim);
|
|
dirtyBands(sim);
|
|
for (k = 0u; k < ST_HW_SPRITES; k++) {
|
|
@@ -1148,7 +1101,7 @@
|
|
static const uint8_t kCabPtrs[] = { 0xC0, 0xC1, 0xDC, 0xDD, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8 };
|
|
static const uint8_t kPassPtrs[] = { 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xD9 };
|
|
static const uint8_t kFlamePtrs[] = { 0xD8, 0xD4, 0xD5, 0xD2, 0xD1, 0xD7, 0xD3, 0xD6 };
|
|
- uint8_t total = (uint8_t)(sizeof(kCabPtrs) + sizeof(kPassPtrs) + sizeof(kFlamePtrs));
|
|
+ uint8_t total = (uint8_t)(sizeof(kCabPtrs) + sizeof(kPassPtrs) + sizeof(kFlamePtrs) + 1u);
|
|
uint8_t done = 0u;
|
|
bool ok;
|
|
uint8_t k;
|
|
@@ -1158,7 +1111,8 @@
|
|
// runtime build below on any port without a matching .spc.
|
|
if (loadPrecompiledCels()) {
|
|
ST_LOG("spacetaxi: cels from sprites/staxicels.spc");
|
|
- return prewarmReport(kStCelCount);
|
|
+ prewarmStar(sim);
|
|
+ return prewarmReport((uint16_t)(kStCelCount + 1u));
|
|
}
|
|
ST_LOG("spacetaxi: ! no usable sprites/staxicels.spc -- JIT prewarm");
|
|
saved = sim->frame;
|
|
@@ -1189,6 +1143,7 @@
|
|
}
|
|
sim->frame = saved;
|
|
gPrewarming = false;
|
|
+ prewarmStar(sim);
|
|
ok = prewarmReport(total);
|
|
jlFillRect(stage, 60, 110, 200u, 6u, 0u);
|
|
return ok;
|
|
@@ -1231,15 +1186,32 @@
|
|
}
|
|
|
|
|
|
+// The intro star is built and compiled here rather than baked: a bank's
|
|
+// cels all share one size (3x3 tiles), and the star's nine pixels fit
|
|
+// one tile, so a baked star would save, draw, restore and present nine
|
|
+// times its area -- seven times a frame. A moving (idx <= 2) cel, so it
|
|
+// compiles; the interpreter cannot keep up with seven of them.
|
|
+static void prewarmStar(StSimT *sim) {
|
|
+ StFrameT saved = sim->frame;
|
|
+
|
|
+ gPrewarming = true;
|
|
+ sim->frame.multiMask = 0u;
|
|
+ sim->frame.ptr[2] = ST_INTRO_STAR_PTR;
|
|
+ sim->frame.color[2] = ST_INTRO_STAR_COLOR;
|
|
+ (void)cachedSprite(sim, 2u);
|
|
+ sim->frame = saved;
|
|
+ gPrewarming = false;
|
|
+}
|
|
+
|
|
+
|
|
// A new scene: nothing drawn is valid any more.
|
|
void stRenderSceneChanged(StSimT *sim) {
|
|
uint8_t k;
|
|
|
|
- if (gRender.logoMode || gRender.starMode) {
|
|
+ if (gRender.logoMode) {
|
|
// The borrowed slots go back to being C64 colours.
|
|
gRender.logoMode = false;
|
|
gRender.logoTileOk = false;
|
|
- gRender.starMode = false;
|
|
jlPaletteSet(gRender.stage, 0u, kC64Palette);
|
|
}
|
|
for (k = 0u; k < ST_HW_SPRITES; k++) {
|
|
@@ -1269,18 +1241,6 @@
|
|
}
|
|
|
|
|
|
-// Turn on the level intro's palette-cycled starfield. Call it after
|
|
-// stRenderSceneChanged (which turns it back off) on entry to the level
|
|
-// intro. The cells were painted by stIntroEnter; from here the warp is
|
|
-// one palette write every ST_STAR_STEP_FRAMES frames.
|
|
-void stRenderIntroStars(void) {
|
|
- gRender.starMode = true;
|
|
- gRender.starPhase = 0u;
|
|
- gRender.starTick = 0u;
|
|
- starPaletteApply();
|
|
-}
|
|
-
|
|
-
|
|
void stRenderShutdown(void) {
|
|
dropCache();
|
|
if (gRender.scratch != 0) {
|
|
diff -ruN a/stSim.h b/stSim.h
|
|
--- a/stSim.h 2026-10-05 14:05:53.055256946 -0500
|
|
+++ b/stSim.h 2026-10-05 14:12:10.089072382 -0500
|
|
@@ -82,42 +82,16 @@
|
|
#define ST_CHAR_ELECTROID 0x6Fu // level O's scrolling barrier glyph
|
|
#define ST_FARE_DEST_UP 0x0Bu // $7D8E: the fare aboard wants UP
|
|
|
|
-// ---- level-intro starfield --------------------------------------------
|
|
+// ---- level-intro star warp --------------------------------------------
|
|
//
|
|
-// The C64's level intro streams seven star SPRITES out of the centre of
|
|
-// the screen. That is the most expensive thing this port asks a present
|
|
-// for: eight sprites are save-under drawn and restored every frame, and
|
|
-// because a row's dirty band is one min..max interval, two stars far
|
|
-// apart on the same row widen that row to nearly full width. Radially
|
|
-// spreading stars maximise exactly that, so the warp ends up presenting
|
|
-// close to the whole framebuffer every frame -- which is what makes it
|
|
-// the slowest thing the IIgs does, and wasted work everywhere else.
|
|
-//
|
|
-// So the warp is not animated in pixels at all. The starfield is painted
|
|
-// ONCE into screen RAM and animated by cycling the colours its cells are
|
|
-// drawn in -- the same trick the title logo already uses
|
|
-// (stRenderTitleLogo). A frame costs one palette upload and NO dirty
|
|
-// pixel rows.
|
|
-//
|
|
-// The contract between the two halves is just this colour range:
|
|
-// stTitle.c colours a star's cell ST_STAR_RING_FIRST + ring (ring 0 is
|
|
-// the centre), and the renderer cycles that range. These are C64 colour
|
|
-// indices the intro screen never uses -- it draws in 0, 3 and, in demo
|
|
-// mode, 4 and 5; the cab sprite adds 2, 6 and 7.
|
|
-#define ST_STAR_RING_FIRST 8u
|
|
-#define ST_STAR_RING_COUNT 8u
|
|
-
|
|
-// The renderer wraps the sweep's phase with a mask rather than a
|
|
-// modulo, so the ring count has to stay a power of two. Changing it to
|
|
-// something else must fail to compile here instead of quietly making
|
|
-// the warp skip rings.
|
|
-typedef char stStarRingCountIsPow2[((ST_STAR_RING_COUNT & (ST_STAR_RING_COUNT - 1u)) == 0u) ? 1 : -1];
|
|
-// Ring 0 is the centre, which in a warp is the VANISHING POINT: those
|
|
-// stars are the far ones and get the small glyph. The big glyph belongs
|
|
-// to the outer rings, the stars about to rush past the viewer.
|
|
-#define ST_STAR_GLYPH_NEAR 0x2Au // '*', the outer rings
|
|
-#define ST_STAR_GLYPH_FAR 0x2Eu // '.', the inner rings
|
|
-#define ST_STAR_FAR_RINGS 5u // rings 0..4 are still distant
|
|
+// The C64's level intro ($4666) streams seven hires star sprites --
|
|
+// hardware sprites 0..6, all showing block $DA -- out of the centre of
|
|
+// the screen: each waits out a random spawn delay ($44F8), then flies on
|
|
+// its own fixed velocity ($450C / $4513) for $20 frames ($4500) and
|
|
+// respawns near the centre.
|
|
+#define ST_INTRO_STARS 7u
|
|
+#define ST_INTRO_STAR_PTR 0xDAu // a hires 5x3-pixel star
|
|
+#define ST_INTRO_STAR_COLOR 0x07u
|
|
|
|
// VIC sprite block pointers the game selects by name. The cab's bit 0 is
|
|
// the landing gear, so each facing is a pair.
|
|
@@ -423,6 +397,10 @@
|
|
// 407 cells of colour RAM); part of the picture, so it survives the
|
|
// same way colour RAM does.
|
|
uint8_t logoColor;
|
|
+ // $4500: frames until each intro star respawns. Neither $5EC4 nor the
|
|
+ // intro ($4523 seeds only the spawn delays) clears it, so a star whose
|
|
+ // delay draws 0 flies on whatever the previous intro left here.
|
|
+ uint8_t starReload[ST_INTRO_STARS];
|
|
|
|
// ---- cleared by stSimNewGame from here down ----
|
|
|
|
@@ -550,6 +528,11 @@
|
|
uint8_t introLoopCount; // $4508
|
|
uint8_t introCabDx; // $450A
|
|
uint8_t introCabDxTimer; // $450B
|
|
+ uint8_t starTimer[ST_INTRO_STARS]; // $44F8: frames until the star first shows
|
|
+ // The 6502 carry flag as the $4666 star loop finds it. The respawn
|
|
+ // column is an ADC that never clears it first ($4684), so the flag
|
|
+ // left by whatever ran before is part of where a star appears.
|
|
+ uint8_t introCarry;
|
|
uint8_t spriteMc0; // $D025
|
|
uint8_t spriteMc1; // $D026
|
|
|
|
diff -ruN a/stTitle.c b/stTitle.c
|
|
--- a/stTitle.c 2026-10-05 14:05:44.474069201 -0500
|
|
+++ b/stTitle.c 2026-10-05 14:12:10.084072282 -0500
|
|
@@ -11,11 +11,6 @@
|
|
// Level intro ($4523..$4727): a black screen with the level name, the
|
|
// cab rising from the bottom and seven star sprites streaming out of
|
|
// the centre, until the cab reaches mid-screen.
|
|
-//
|
|
-// The seven star sprites are replaced by a static starfield whose
|
|
-// colours are cycled -- see ST_STAR_RING_FIRST in stSim.h for why and
|
|
-// how. The cab still flies, and it is still what ends the intro, so the
|
|
-// sequence keeps its length and its cue; only the warp changes.
|
|
|
|
#include <string.h>
|
|
|
|
@@ -39,48 +34,36 @@
|
|
#define ST_TITLE_CAB_LIFT_ROW 0x14u
|
|
#define ST_TITLE_SPARKLE_TICKS 0x5Au
|
|
#define ST_TITLE_HOVER_COL 0x28u
|
|
-#define ST_INTRO_STAR_PTR 0xDAu
|
|
+#define ST_INTRO_STAR_START_ROW 0x8Cu
|
|
+#define ST_INTRO_STAR_START_COL 0xAAu
|
|
+#define ST_INTRO_STAR_DELAY 0x20u // $4080 range of the spawn delay
|
|
+#define ST_INTRO_STAR_RELOAD 0x20u // frames a star flies before respawning
|
|
+// $467F..$4694: a respawning star's position comes from star 0's row --
|
|
+// its low three bits pick the column, bits 4..5 the row.
|
|
+#define ST_INTRO_SPAWN_COL 0xA6u
|
|
+#define ST_INTRO_SPAWN_COL_BITS 0x07u
|
|
+#define ST_INTRO_SPAWN_ROW 0x89u
|
|
+#define ST_INTRO_SPAWN_ROW_BITS 0x30u
|
|
+#define ST_INTRO_SPAWN_SHIFT 4u
|
|
+#define ST_INTRO_CAB_START_ROW 0xE4u
|
|
#define ST_INTRO_CAB_END_ROW 0x94u
|
|
#define ST_INTRO_CAB_DX_RELOAD 0x28u
|
|
|
|
-// The palette-cycled starfield. Seven stars per ring so the bright band
|
|
-// stays the same weight all the way out, placed at fixed pseudo-random
|
|
-// angles and clear of the three lines the intro screen writes (the level
|
|
-// name on row 12, and the demo hints on rows 3 and 24).
|
|
-typedef struct {
|
|
- uint8_t col;
|
|
- uint8_t row;
|
|
- uint8_t ring;
|
|
-} StStarT;
|
|
-
|
|
-static const StStarT kIntroStar[] = {
|
|
- { 18u, 10u, 0u }, { 19u, 10u, 0u }, { 17u, 11u, 0u }, { 18u, 13u, 0u },
|
|
- { 21u, 13u, 0u }, { 22u, 14u, 0u }, { 18u, 15u, 0u }, { 18u, 8u, 1u },
|
|
- { 19u, 8u, 1u }, { 14u, 10u, 1u }, { 25u, 14u, 1u }, { 21u, 15u, 1u },
|
|
- { 25u, 15u, 1u }, { 20u, 17u, 1u }, { 26u, 7u, 2u }, { 12u, 10u, 2u },
|
|
- { 29u, 11u, 2u }, { 11u, 13u, 2u }, { 29u, 14u, 2u }, { 21u, 17u, 2u },
|
|
- { 23u, 18u, 2u }, { 21u, 4u, 3u }, { 24u, 4u, 3u }, { 11u, 8u, 3u },
|
|
- { 12u, 8u, 3u }, { 10u, 10u, 3u }, { 30u, 16u, 3u }, { 14u, 17u, 3u },
|
|
- { 15u, 2u, 4u }, { 8u, 8u, 4u }, { 32u, 9u, 4u }, { 9u, 17u, 4u },
|
|
- { 32u, 17u, 4u }, { 32u, 18u, 4u }, { 24u, 22u, 4u }, { 16u, 1u, 5u },
|
|
- { 28u, 1u, 5u }, { 31u, 2u, 5u }, { 36u, 13u, 5u }, { 6u, 15u, 5u },
|
|
- { 4u, 16u, 5u }, { 5u, 19u, 5u }, { 8u, 0u, 6u }, { 30u, 0u, 6u },
|
|
- { 1u, 8u, 6u }, { 2u, 12u, 6u }, { 37u, 16u, 6u }, { 7u, 21u, 6u },
|
|
- { 32u, 22u, 6u }, { 0u, 4u, 7u }, { 39u, 7u, 7u }, { 0u, 10u, 7u },
|
|
- { 38u, 20u, 7u }, { 38u, 21u, 7u }, { 4u, 22u, 7u }, { 35u, 22u, 7u },
|
|
-};
|
|
-#define ST_INTRO_STAR_COUNT (sizeof(kIntroStar) / sizeof(kIntroStar[0]))
|
|
-
|
|
-
|
|
-static void addToRow(StSimT *sim, uint8_t idx, uint8_t delta);
|
|
-static void logoColorCycle(StSimT *sim);
|
|
-static void logoFlip(StSimT *sim);
|
|
-static void paintStarfield(StSimT *sim);
|
|
-
|
|
-
|
|
-// $4113
|
|
-static void addToRow(StSimT *sim, uint8_t idx, uint8_t delta) {
|
|
- sim->spr[idx].row = (uint8_t)(sim->spr[idx].row + delta);
|
|
+
|
|
+static uint8_t addToRow(StSimT *sim, uint8_t idx, uint8_t delta);
|
|
+static void logoColorCycle(StSimT *sim);
|
|
+static void logoFlip(StSimT *sim);
|
|
+static void starRespawn(StSimT *sim, uint8_t idx, uint8_t carry);
|
|
+static void starsStep(StSimT *sim);
|
|
+
|
|
+
|
|
+// $4113 -- CLC; ADC row. The carry it leaves is the 6502 flag the star
|
|
+// loop goes on with (see introCarry in stSim.h).
|
|
+static uint8_t addToRow(StSimT *sim, uint8_t idx, uint8_t delta) {
|
|
+ uint16_t row = (uint16_t)((uint16_t)sim->spr[idx].row + delta);
|
|
+
|
|
+ sim->spr[idx].row = (uint8_t)row;
|
|
+ return (uint8_t)(row >> 8);
|
|
}
|
|
|
|
|
|
@@ -124,20 +107,47 @@
|
|
}
|
|
|
|
|
|
-// Paint the level-intro starfield into screen RAM, once. A star's cell
|
|
-// is coloured ST_STAR_RING_FIRST + its ring and never touched again --
|
|
-// the renderer animates the warp by cycling those colours, which costs
|
|
-// no dirty rows at all. See ST_STAR_RING_FIRST in stSim.h.
|
|
-static void paintStarfield(StSimT *sim) {
|
|
- const StStarT *star;
|
|
- uint8_t one[2];
|
|
- uint8_t k;
|
|
-
|
|
- one[1] = 0u; // stSimDrawText stops below screen code 6
|
|
- for (k = 0u; k < (uint8_t)ST_INTRO_STAR_COUNT; k++) {
|
|
- star = &kIntroStar[k];
|
|
- one[0] = (star->ring < ST_STAR_FAR_RINGS) ? ST_STAR_GLYPH_FAR : ST_STAR_GLYPH_NEAR;
|
|
- stSimDrawText(sim, star->col, star->row, one, (uint8_t)(ST_STAR_RING_FIRST + star->ring));
|
|
+// $467A -- a star (re)appears near the centre, placed from star 0's
|
|
+// row. The column's ADC adds the carry the loop arrived with; the row's
|
|
+// adds the bit the last LSR shifted out, which the mask keeps clear, and
|
|
+// its sum stays below $100, so the loop goes on with the carry clear.
|
|
+static void starRespawn(StSimT *sim, uint8_t idx, uint8_t carry) {
|
|
+ uint8_t seed = sim->spr[0].row;
|
|
+
|
|
+ sim->spr[idx].msb = 0u;
|
|
+ sim->spr[idx].col = (uint8_t)((seed & ST_INTRO_SPAWN_COL_BITS) + ST_INTRO_SPAWN_COL + carry);
|
|
+ sim->spr[idx].row = (uint8_t)(((seed & ST_INTRO_SPAWN_ROW_BITS) >> ST_INTRO_SPAWN_SHIFT) + ST_INTRO_SPAWN_ROW);
|
|
+ sim->starReload[idx] = ST_INTRO_STAR_RELOAD;
|
|
+}
|
|
+
|
|
+
|
|
+// $4662..$46A2 -- the seven stars, sprite 6 down to sprite 0. A star
|
|
+// counts down its spawn delay and appears ($4675); from then on it flies
|
|
+// on its own velocity ($4711) and respawns when its reload runs out.
|
|
+// The carry threads through the loop exactly as the 6502 leaves it: a
|
|
+// flight's row add sets it, a respawn clears it, a waiting star keeps it.
|
|
+static void starsStep(StSimT *sim) {
|
|
+ uint8_t carry = sim->introCarry;
|
|
+ uint8_t k;
|
|
+
|
|
+ for (k = 0u; k < ST_INTRO_STARS; k++) {
|
|
+ uint8_t idx = (uint8_t)(ST_INTRO_STARS - 1u - k);
|
|
+ if (sim->starTimer[idx] == 0u) {
|
|
+ stSimSpriteAddX(sim, idx, (uint8_t)stC64IntroStarDx(idx));
|
|
+ carry = addToRow(sim, idx, (uint8_t)stC64IntroStarDy(idx));
|
|
+ sim->starReload[idx]--;
|
|
+ if (sim->starReload[idx] == 0u) {
|
|
+ starRespawn(sim, idx, carry);
|
|
+ carry = 0u;
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+ sim->starTimer[idx]--;
|
|
+ if (sim->starTimer[idx] == 0u) {
|
|
+ sim->spr[idx].enable = 1u;
|
|
+ starRespawn(sim, idx, carry);
|
|
+ carry = 0u;
|
|
+ }
|
|
}
|
|
}
|
|
|
|
@@ -158,23 +168,20 @@
|
|
sim->spr[7].col = 0xAAu;
|
|
sim->spr[7].msb = 0u;
|
|
sim->spr[7].color = 0x06u;
|
|
- sim->spr[7].row = 0xE4u;
|
|
+ sim->spr[7].row = ST_INTRO_CAB_START_ROW;
|
|
sim->introCabDxTimer = 0x14u;
|
|
sim->introCabDx = 0x01u;
|
|
- for (k = 0u; k < 7u; k++) {
|
|
- uint8_t idx = (uint8_t)(6u - k);
|
|
- sim->spr[idx].row = 0x8Cu;
|
|
+ for (k = 0u; k < ST_INTRO_STARS; k++) {
|
|
+ uint8_t idx = (uint8_t)(ST_INTRO_STARS - 1u - k);
|
|
+ sim->spr[idx].row = ST_INTRO_STAR_START_ROW;
|
|
sim->spr[idx].msb = 0u;
|
|
- sim->spr[idx].col = 0xAAu;
|
|
- sim->spr[idx].color = 0x07u;
|
|
+ sim->spr[idx].col = ST_INTRO_STAR_START_COL;
|
|
+ sim->spr[idx].color = ST_INTRO_STAR_COLOR;
|
|
sim->spr[idx].ptr = ST_INTRO_STAR_PTR;
|
|
sim->spr[idx].enable = 0u;
|
|
- // $44F8/$4500 held each star's spawn timer and reload. Nothing
|
|
- // reads them now that the warp is painted rather than flown, but
|
|
- // the seven RNG draws MUST stay: they advance the shared RNG and
|
|
- // the level that follows draws from it, so dropping them would
|
|
- // change the game that comes after the intro.
|
|
- (void)stSimRng(sim, 0x20u);
|
|
+ // These draws also advance the shared RNG the level that follows
|
|
+ // draws from, so the next ride depends on them too.
|
|
+ sim->starTimer[idx] = stSimRng(sim, ST_INTRO_STAR_DELAY);
|
|
}
|
|
sim->borderColor = 0u;
|
|
sim->bgColor = 0u;
|
|
@@ -188,23 +195,26 @@
|
|
sim->charDirtyAll = true;
|
|
sim->charDirtyCount = 0u;
|
|
stSimDrawText(sim, 10u, 12u, level->name, 3u);
|
|
- paintStarfield(sim);
|
|
if (sim->demoMode != 0u && sim->postMortem == 0u) {
|
|
stSimDrawText(sim, 7u, 3u, kTextDemoExit, 5u);
|
|
stSimDrawText(sim, 3u, 24u, kTextScreens, 4u);
|
|
}
|
|
sim->introLoopCount = 0u;
|
|
+ // $4662 is reached from $41C2's RTS, whose CMP #$06 on the text's
|
|
+ // zero terminator leaves the carry clear.
|
|
+ sim->introCarry = 0u;
|
|
}
|
|
|
|
|
|
-// $4666 -- one frame of the star field; true when the cab has risen
|
|
+// $4662 -- one frame of the star field; true when the cab has risen
|
|
// to mid-screen and everything is hidden again.
|
|
bool stIntroStep(StSimT *sim) {
|
|
uint8_t k;
|
|
|
|
+ starsStep(sim);
|
|
stSimMarshal(sim);
|
|
sim->introLoopCount = (uint8_t)((sim->introLoopCount + 1u) & 3u);
|
|
- addToRow(sim, 7u, (uint8_t)kIntroCabDy[sim->introLoopCount]);
|
|
+ (void)addToRow(sim, 7u, (uint8_t)kIntroCabDy[sim->introLoopCount]);
|
|
if ((sim->introLoopCount & 1u) == 0u) {
|
|
stSimSpriteAddX(sim, 7u, sim->introCabDx);
|
|
sim->introCabDxTimer--;
|
|
@@ -214,6 +224,9 @@
|
|
sim->introCabDx ^= 0xFEu;
|
|
}
|
|
}
|
|
+ // $46FC: CMP #$94. Its carry is what the next frame's star loop
|
|
+ // starts with; the cab is still above the end row, so it is set.
|
|
+ sim->introCarry = (sim->spr[7].row >= ST_INTRO_CAB_END_ROW) ? 1u : 0u;
|
|
if (sim->spr[7].row != ST_INTRO_CAB_END_ROW) {
|
|
return false;
|
|
}
|
|
@@ -300,7 +313,7 @@
|
|
break;
|
|
case ST_STAGE_RIDING:
|
|
// $4A48: lift off under UP thrust until above row $14.
|
|
- addToRow(sim, 0u, 0xFEu);
|
|
+ (void)addToRow(sim, 0u, 0xFEu);
|
|
sim->dirMask = 0x01u;
|
|
sim->spr[0].ptr = 0xC0u;
|
|
stSimFlameUpdate(sim);
|