More bug fixing and optimizations.
This commit is contained in:
parent
efa9fecad9
commit
a05958bd86
7 changed files with 338 additions and 178 deletions
|
|
@ -4,8 +4,9 @@
|
||||||
# exact sprite-to-background collision bits), the standard sprite
|
# exact sprite-to-background collision bits), the standard sprite
|
||||||
# bitmaps (cab, passenger, flame, warp, death, intro star, leaving
|
# bitmaps (cab, passenger, flame, warp, death, intro star, leaving
|
||||||
# cels), the demo-mode RNG table ($446A), the SID SFX programs (the
|
# cels), the demo-mode RNG table ($446A), the SID SFX programs (the
|
||||||
# 9-byte blocks fed to $42E9), the engine-flame pointer table ($6DB0)
|
# 9-byte blocks fed to $42E9) and the engine-flame pointer table ($6DB0).
|
||||||
# and the level-intro star velocity tables ($450C/$4513).
|
# (The level-intro star velocity tables, $450C/$4513, went with the moving
|
||||||
|
# warp; claudeDocs/spacetaxiSpriteWarp.patch puts them back.)
|
||||||
#
|
#
|
||||||
# Everything comes straight out of stuff/spacetaxi/raw.bin (a VICE
|
# Everything comes straight out of stuff/spacetaxi/raw.bin (a VICE
|
||||||
# "bank ram" dump, 2-byte load-address header) so the port has one
|
# "bank ram" dump, 2-byte load-address header) so the port has one
|
||||||
|
|
|
||||||
90
spacetaxi.c
90
spacetaxi.c
|
|
@ -27,6 +27,15 @@
|
||||||
|
|
||||||
#define ST_TICK_HZ 30u
|
#define ST_TICK_HZ 30u
|
||||||
#define ST_MAX_CATCHUP_TICKS 4u
|
#define ST_MAX_CATCHUP_TICKS 4u
|
||||||
|
// The C64 runs the game every other frame but the level intro ($4666)
|
||||||
|
// every frame.
|
||||||
|
#define ST_C64_FRAMES_PER_TICK 2u
|
||||||
|
// The intro's loop count ($4508) runs 0..3 and the cab's judder ($470D)
|
||||||
|
// steps -2 into count 0; batches that cannot show every frame end on the
|
||||||
|
// counts either side of that jump (runIntro).
|
||||||
|
#define ST_INTRO_LOOP_MASK 3u
|
||||||
|
#define ST_INTRO_SHOW_BEFORE 3u
|
||||||
|
#define ST_INTRO_SHOW_AFTER 0u
|
||||||
// Screens A..X, then screen Y -- the 25th, " MYSTERY SCREEN !!! " -- which
|
// Screens A..X, then screen Y -- the 25th, " MYSTERY SCREEN !!! " -- which
|
||||||
// $5167 loads once the 24-hour shifts have cleared all of A..X. Files are
|
// $5167 loads once the 24-hour shifts have cleared all of A..X. Files are
|
||||||
// levels/level01.dat .. level25.dat, so Y is index 24.
|
// levels/level01.dat .. level25.dat, so Y is index 24.
|
||||||
|
|
@ -85,6 +94,8 @@ typedef struct {
|
||||||
bool fireArmed; // $445D: FIRE must be released first
|
bool fireArmed; // $445D: FIRE must be released first
|
||||||
uint8_t lastFrame;
|
uint8_t lastFrame;
|
||||||
uint16_t paceAcc;
|
uint16_t paceAcc;
|
||||||
|
uint16_t frameAcc; // C64 frames owed, in paceAcc's units
|
||||||
|
int8_t introOwed; // intro frames owed to (+) or run ahead of (-) the next batch
|
||||||
StStateE afterJingle; // where ST_STATE_JINGLE returns to
|
StStateE afterJingle; // where ST_STATE_JINGLE returns to
|
||||||
bool gameOverTune; // $5C24: song 5 started after the wait
|
bool gameOverTune; // $5C24: song 5 started after the wait
|
||||||
uint8_t hsSlot; // $4D51 table slot being named
|
uint8_t hsSlot; // $4D51 table slot being named
|
||||||
|
|
@ -189,11 +200,11 @@ static bool joyFire(void);
|
||||||
static uint8_t joyMask(void);
|
static uint8_t joyMask(void);
|
||||||
static bool loadLevel(uint8_t index);
|
static bool loadLevel(uint8_t index);
|
||||||
static uint8_t nextLevelForShift(void);
|
static uint8_t nextLevelForShift(void);
|
||||||
static uint8_t paceTicks(void);
|
static uint8_t paceTicks(uint8_t *c64Frames);
|
||||||
static bool serviceJingle(void);
|
static bool serviceJingle(void);
|
||||||
static void runCabbiesMenu(uint8_t ticks);
|
static void runCabbiesMenu(uint8_t ticks);
|
||||||
static void runDemoEnd(void);
|
static void runDemoEnd(void);
|
||||||
static void runIntro(uint8_t ticks);
|
static void runIntro(uint8_t c64Frames);
|
||||||
static void runNameEntry(uint8_t ticks);
|
static void runNameEntry(uint8_t ticks);
|
||||||
static bool nameEntryStart(void);
|
static bool nameEntryStart(void);
|
||||||
static void runPlaying(uint8_t ticks);
|
static void runPlaying(uint8_t ticks);
|
||||||
|
|
@ -604,22 +615,35 @@ static uint8_t nextLevelForShift(void) {
|
||||||
// Game ticks due this rendered frame, from the vertical blank count:
|
// Game ticks due this rendered frame, from the vertical blank count:
|
||||||
// one tick per two frames on 50/60 Hz displays, three per seven on
|
// one tick per two frames on 50/60 Hz displays, three per seven on
|
||||||
// the 70 Hz VGA mode. jlFrameCount is 8-bit-wrapped so a stall of any
|
// the 70 Hz VGA mode. jlFrameCount is 8-bit-wrapped so a stall of any
|
||||||
// length just clamps to the catch-up cap.
|
// length just clamps to the catch-up cap. *c64Frames gets the C64 frames
|
||||||
static uint8_t paceTicks(void) {
|
// due as well, ST_C64_FRAMES_PER_TICK a tick: one per display frame at
|
||||||
uint16_t hz = jlFrameHz();
|
// 50/60 Hz, so the level intro shows every frame the C64 does -- the
|
||||||
uint8_t now = (uint8_t)jlFrameCount();
|
// cab's up-and-down judder ($470D) is a one-frame pattern that stepping
|
||||||
uint8_t frames = (uint8_t)(now - gGame.lastFrame);
|
// two frames at a time cancels out.
|
||||||
uint8_t ticks = 0u;
|
static uint8_t paceTicks(uint8_t *c64Frames) {
|
||||||
|
uint16_t hz = jlFrameHz();
|
||||||
|
uint8_t now = (uint8_t)jlFrameCount();
|
||||||
|
uint8_t frames = (uint8_t)(now - gGame.lastFrame);
|
||||||
|
uint8_t ticks = 0u;
|
||||||
|
uint16_t perTick = ST_TICK_HZ;
|
||||||
|
|
||||||
gGame.lastFrame = now;
|
gGame.lastFrame = now;
|
||||||
if (frames > 16u) {
|
if (frames > 16u) {
|
||||||
frames = 16u;
|
frames = 16u;
|
||||||
}
|
}
|
||||||
if (hz == 50u || hz == 60u) {
|
if (hz == 50u || hz == 60u) {
|
||||||
gGame.paceAcc += (uint16_t)(frames * 35u);
|
perTick = 35u;
|
||||||
hz = 70u;
|
hz = 70u;
|
||||||
} else {
|
}
|
||||||
gGame.paceAcc += (uint16_t)(frames * ST_TICK_HZ);
|
gGame.paceAcc += (uint16_t)(frames * perTick);
|
||||||
|
gGame.frameAcc += (uint16_t)(frames * perTick * ST_C64_FRAMES_PER_TICK);
|
||||||
|
*c64Frames = 0u;
|
||||||
|
while (gGame.frameAcc >= hz && *c64Frames < ST_MAX_CATCHUP_TICKS * ST_C64_FRAMES_PER_TICK) {
|
||||||
|
gGame.frameAcc = (uint16_t)(gGame.frameAcc - hz);
|
||||||
|
(*c64Frames)++;
|
||||||
|
}
|
||||||
|
if (gGame.frameAcc > (uint16_t)(hz * ST_MAX_CATCHUP_TICKS * ST_C64_FRAMES_PER_TICK)) {
|
||||||
|
gGame.frameAcc = (uint16_t)(hz * ST_MAX_CATCHUP_TICKS * ST_C64_FRAMES_PER_TICK);
|
||||||
}
|
}
|
||||||
// Ticks not run this frame stay owed (a slow frame catches up over
|
// Ticks not run this frame stay owed (a slow frame catches up over
|
||||||
// the next ones); a stall longer than the cap's worth is forgiven.
|
// the next ones); a stall longer than the cap's worth is forgiven.
|
||||||
|
|
@ -674,15 +698,42 @@ static void runDemoEnd(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// $4666 loop: two frames per game tick; joystick ends a demo.
|
// $4666 loop: once per C64 frame; joystick ends a demo. A port that shows
|
||||||
static void runIntro(uint8_t ticks) {
|
// every frame shows the cab's one-frame judder ($470D) as the C64 does. One
|
||||||
|
// that shows only some would sample it at a fixed phase -- every second frame
|
||||||
|
// shows -1, 0, -1, 0, a smooth climb -- so when two or more frames are due
|
||||||
|
// the batch is cut or stretched by one (evened out on the next batch) to end
|
||||||
|
// on the loop counts either side of the -2 jump: the frames shown then cross
|
||||||
|
// it and the cab judders, at the same average speed.
|
||||||
|
static void runIntro(uint8_t c64Frames) {
|
||||||
|
static const int8_t kTry[] = { 0, -1, 1 };
|
||||||
|
int16_t due;
|
||||||
|
uint8_t steps;
|
||||||
|
uint8_t end;
|
||||||
|
uint8_t t;
|
||||||
uint8_t k;
|
uint8_t k;
|
||||||
|
|
||||||
if (gGame.demo && (joyMask() & 0x1Fu) != 0u) {
|
if (gGame.demo && (joyMask() & 0x1Fu) != 0u) {
|
||||||
runDemoEnd();
|
runDemoEnd();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (k = 0u; k < (uint8_t)(ticks * 2u); k++) {
|
due = (int16_t)(c64Frames + gGame.introOwed);
|
||||||
|
gGame.introOwed = 0;
|
||||||
|
steps = (uint8_t)((due > 0) ? due : 0);
|
||||||
|
if (c64Frames >= ST_C64_FRAMES_PER_TICK) {
|
||||||
|
for (t = 0u; t < (uint8_t)sizeof(kTry); t++) {
|
||||||
|
if (due + kTry[t] < 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
end = (uint8_t)((gSim.introLoopCount + due + kTry[t]) & ST_INTRO_LOOP_MASK);
|
||||||
|
if (end == ST_INTRO_SHOW_BEFORE || end == ST_INTRO_SHOW_AFTER) {
|
||||||
|
steps = (uint8_t)(due + kTry[t]);
|
||||||
|
gGame.introOwed = (int8_t)(-kTry[t]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (k = 0u; k < steps; k++) {
|
||||||
if (stIntroStep(&gSim)) {
|
if (stIntroStep(&gSim)) {
|
||||||
jlMusicStop(); // $4709
|
jlMusicStop(); // $4709
|
||||||
stSimEnterLevel(&gSim, &gLevel);
|
stSimEnterLevel(&gSim, &gLevel);
|
||||||
|
|
@ -930,7 +981,8 @@ static void startIntro(void) {
|
||||||
// $459C: the intro's tune plays under the fly-in, not blocking; the
|
// $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.
|
// C64 leaves it to the IRQ player and stops it at $4709.
|
||||||
stAudioMusic(ST_SONG_INTRO, true);
|
stAudioMusic(ST_SONG_INTRO, true);
|
||||||
gGame.state = ST_STATE_INTRO;
|
gGame.introOwed = 0;
|
||||||
|
gGame.state = ST_STATE_INTRO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1018,6 +1070,7 @@ int main(void) {
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
uint8_t ticks;
|
uint8_t ticks;
|
||||||
|
uint8_t c64Frames;
|
||||||
|
|
||||||
if (gGame.fatal) {
|
if (gGame.fatal) {
|
||||||
fatalHold();
|
fatalHold();
|
||||||
|
|
@ -1045,6 +1098,7 @@ int main(void) {
|
||||||
gGame.paused = false;
|
gGame.paused = false;
|
||||||
gGame.lastFrame = (uint8_t)jlFrameCount(); // the held time is not owed
|
gGame.lastFrame = (uint8_t)jlFrameCount(); // the held time is not owed
|
||||||
gGame.paceAcc = 0u;
|
gGame.paceAcc = 0u;
|
||||||
|
gGame.frameAcc = 0u;
|
||||||
if (gGame.state == ST_STATE_INTRO) {
|
if (gGame.state == ST_STATE_INTRO) {
|
||||||
stAudioMusic(ST_SONG_INTRO, true); // $46B5
|
stAudioMusic(ST_SONG_INTRO, true); // $46B5
|
||||||
}
|
}
|
||||||
|
|
@ -1058,7 +1112,7 @@ int main(void) {
|
||||||
enterTitle();
|
enterTitle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ticks = paceTicks();
|
ticks = paceTicks(&c64Frames);
|
||||||
switch (gGame.state) {
|
switch (gGame.state) {
|
||||||
case ST_STATE_TITLE:
|
case ST_STATE_TITLE:
|
||||||
runTitle(ticks);
|
runTitle(ticks);
|
||||||
|
|
@ -1090,7 +1144,7 @@ int main(void) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ST_STATE_INTRO:
|
case ST_STATE_INTRO:
|
||||||
runIntro(ticks);
|
runIntro(c64Frames);
|
||||||
break;
|
break;
|
||||||
case ST_STATE_PLAYING:
|
case ST_STATE_PLAYING:
|
||||||
runPlaying(ticks);
|
runPlaying(ticks);
|
||||||
|
|
|
||||||
|
|
@ -323,8 +323,8 @@ static uint32_t speechFill(void *ctx, int8_t *dst, uint32_t count) {
|
||||||
}
|
}
|
||||||
n = (gSpeechStageCount < want) ? gSpeechStageCount : want;
|
n = (gSpeechStageCount < want) ? gSpeechStageCount : want;
|
||||||
if (n != 0u) {
|
if (n != 0u) {
|
||||||
// jlMemCopy (MVN on the IIgs; libc memcpy is a byte loop there),
|
// jlMemCopy (MVN on the IIgs), in two runs when the unread bytes
|
||||||
// in two runs when the unread bytes wrap the ring's end.
|
// wrap the ring's end.
|
||||||
uint8_t *to = (uint8_t *)dst;
|
uint8_t *to = (uint8_t *)dst;
|
||||||
uint16_t left = n;
|
uint16_t left = n;
|
||||||
|
|
||||||
|
|
|
||||||
9
stCels.c
9
stCels.c
|
|
@ -2,8 +2,9 @@
|
||||||
// See stCels.h. Ordered exactly as stRenderPrewarm built the cels: the
|
// See stCels.h. Ordered exactly as stRenderPrewarm built the cels: the
|
||||||
// cab set (flying, wreck and the intro decorations) and passenger set in
|
// cab set (flying, wreck and the intro decorations) and passenger set in
|
||||||
// multicolour at the constant level colours (cab/passenger 6, shared
|
// multicolour at the constant level colours (cab/passenger 6, shared
|
||||||
// $D025/$D026 = 2/7), the flame set in multicolour colour 7, then the
|
// $D025/$D026 = 2/7), then the flame set in multicolour colour 7. No
|
||||||
// hires intro star in colour 7.
|
// intro star: the level intro paints its starfield into the character
|
||||||
|
// screen (stTitle.c), so the star sprite is never drawn.
|
||||||
|
|
||||||
#include "stCels.h"
|
#include "stCels.h"
|
||||||
|
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
#define MC0 0x02u
|
#define MC0 0x02u
|
||||||
#define MC1 0x07u
|
#define MC1 0x07u
|
||||||
#define CAB 0x06u // cab and passenger per-sprite colour
|
#define CAB 0x06u // cab and passenger per-sprite colour
|
||||||
#define FLM 0x07u // flame / star colour
|
#define FLM 0x07u // flame colour
|
||||||
|
|
||||||
const StCelDefT kStCels[] = {
|
const StCelDefT kStCels[] = {
|
||||||
// Cab: flying (gear up/down x left/right), wreck cels, intro cabs.
|
// Cab: flying (gear up/down x left/right), wreck cels, intro cabs.
|
||||||
|
|
@ -37,8 +38,6 @@ const StCelDefT kStCels[] = {
|
||||||
{ 0xD5, FLM, 1u, MC0, MC1 }, { 0xD2, FLM, 1u, MC0, MC1 },
|
{ 0xD5, FLM, 1u, MC0, MC1 }, { 0xD2, FLM, 1u, MC0, MC1 },
|
||||||
{ 0xD1, FLM, 1u, MC0, MC1 }, { 0xD7, FLM, 1u, MC0, MC1 },
|
{ 0xD1, FLM, 1u, MC0, MC1 }, { 0xD7, FLM, 1u, MC0, MC1 },
|
||||||
{ 0xD3, FLM, 1u, MC0, MC1 }, { 0xD6, FLM, 1u, MC0, MC1 },
|
{ 0xD3, FLM, 1u, MC0, MC1 }, { 0xD6, FLM, 1u, MC0, MC1 },
|
||||||
// Intro star, hires colour 7.
|
|
||||||
{ 0xDA, FLM, 0u, 0u, 0u },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const uint16_t kStCelCount = (uint16_t)(sizeof(kStCels) / sizeof(kStCels[0]));
|
const uint16_t kStCelCount = (uint16_t)(sizeof(kStCels) / sizeof(kStCels[0]));
|
||||||
|
|
|
||||||
238
stRender.c
238
stRender.c
|
|
@ -37,10 +37,10 @@
|
||||||
// The IIgs reaches its globals DBR-relative, so all of BSS must fit the
|
// 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
|
// entry bank below the I/O window; the caches are sized down there. The
|
||||||
// sprite cache must still hold every cel stRenderPrewarm builds (the
|
// sprite cache must still hold every cel stRenderPrewarm builds (the
|
||||||
// cab, exhaust and passenger sets plus the intro star) or a cel change
|
// cab, exhaust and passenger sets) or a cel change mid-play rebuilds
|
||||||
// mid-play rebuilds and recompiles a sprite. Glyphs are direct-mapped
|
// and recompiles a sprite. Glyphs are direct-mapped by character, so
|
||||||
// by character, so the count is a power of two.
|
// the count is a power of two.
|
||||||
// Room for the baked cab/passenger/exhaust set (37) plus the largest
|
// Room for the baked cab/passenger/exhaust set (36) plus the largest
|
||||||
// level's hook cels (I: 30) at once.
|
// level's hook cels (I: 30) at once.
|
||||||
#define ST_SPRITE_CACHE 80u
|
#define ST_SPRITE_CACHE 80u
|
||||||
#define ST_SPRITE_BACKUP_BYTES JOEY_SPRITE_BACKUP_BYTES(ST_SPRITE_TILES, ST_SPRITE_TILES)
|
#define ST_SPRITE_BACKUP_BYTES JOEY_SPRITE_BACKUP_BYTES(ST_SPRITE_TILES, ST_SPRITE_TILES)
|
||||||
|
|
@ -66,6 +66,10 @@ typedef struct {
|
||||||
uint8_t mc0;
|
uint8_t mc0;
|
||||||
uint8_t mc1;
|
uint8_t mc1;
|
||||||
uint8_t level; // level index the bitmap came from (level sprites)
|
uint8_t level; // level index the bitmap came from (level sprites)
|
||||||
|
uint8_t offX; // where the (cropped) cel sits in the 24x24
|
||||||
|
uint8_t offY; // VIC frame, in pixels -- see celCreate
|
||||||
|
uint8_t w; // the cel's size in pixels
|
||||||
|
uint8_t h;
|
||||||
bool used;
|
bool used;
|
||||||
bool pinned; // prewarmed moving cel: never evicted (its
|
bool pinned; // prewarmed moving cel: never evicted (its
|
||||||
// compiled code is costly to re-emit on the 65816)
|
// compiled code is costly to re-emit on the 65816)
|
||||||
|
|
@ -78,6 +82,7 @@ typedef struct {
|
||||||
// What is drawn in a draw slot (slot 0 = sprite 7 ... slot 7 = sprite 0).
|
// What is drawn in a draw slot (slot 0 = sprite 7 ... slot 7 = sprite 0).
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool drawn;
|
bool drawn;
|
||||||
|
bool trusted; // backed up by the compiled, unclipped save
|
||||||
uint16_t x;
|
uint16_t x;
|
||||||
uint8_t y;
|
uint8_t y;
|
||||||
uint8_t ptr;
|
uint8_t ptr;
|
||||||
|
|
@ -92,8 +97,7 @@ typedef struct {
|
||||||
// tile, animated by rewriting logoSlotMask[]'s palette slots.
|
// tile, animated by rewriting logoSlotMask[]'s palette slots.
|
||||||
bool logoMode;
|
bool logoMode;
|
||||||
bool starMode; // level intro: cycling the starfield
|
bool starMode; // level intro: cycling the starfield
|
||||||
uint8_t starPhase; // which ring the bright band is on
|
uint8_t starPhase; // which step of every flight is lit
|
||||||
uint8_t starTick; // frames until the band steps outward
|
|
||||||
bool logoTileOk; // the live glyph is one of the frames
|
bool logoTileOk; // the live glyph is one of the frames
|
||||||
uint8_t logoSlotCount;
|
uint8_t logoSlotCount;
|
||||||
uint8_t logoSlotMask[ST_LOGO_PAL_SLOTS]; // frames each slot is lit in
|
uint8_t logoSlotMask[ST_LOGO_PAL_SLOTS]; // frames each slot is lit in
|
||||||
|
|
@ -136,22 +140,18 @@ static const uint16_t kC64Palette[16] = {
|
||||||
// four of these are used; the rest are headroom if the frames change.
|
// 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 };
|
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
|
// Level-intro warp: how a star step looks at each distance BEHIND the
|
||||||
// bright band, as C64 colour indices (white, light grey, grey, dark
|
// lit step, as C64 colour indices; further behind is unlit. One yellow
|
||||||
// grey, then black). A ring this far behind the band or further is
|
// star per ray, as the C64's star sprites (colour 7, $4523) fly with no
|
||||||
// unlit, so a lit head with a short fading tail sweeps outward and the
|
// trail.
|
||||||
// rest of the field stays dark.
|
static const uint8_t kStarRamp[] = { 7u };
|
||||||
static const uint8_t kStarRamp[] = { 1u, 15u, 12u, 11u };
|
|
||||||
#define ST_STAR_RAMP_LEN (sizeof(kStarRamp) / sizeof(kStarRamp[0]))
|
#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 void buildSpriteCel(const uint8_t *bm, uint8_t multi, uint8_t color, uint8_t mc0, uint8_t mc1);
|
static StSpriteCacheT *cachedSprite(StSimT *sim, uint8_t idx);
|
||||||
static jlSpriteT *cachedSprite(StSimT *sim, uint8_t idx);
|
|
||||||
static void cacheCompile(StSpriteCacheT *e);
|
static void cacheCompile(StSpriteCacheT *e);
|
||||||
static StSpriteCacheT *cacheFreeSlot(void);
|
static StSpriteCacheT *cacheFreeSlot(void);
|
||||||
|
static bool celCreate(StSpriteCacheT *e, const uint8_t *bm, uint8_t multi, uint8_t color, uint8_t mc0, uint8_t mc1);
|
||||||
static void levelCelsEvict(void);
|
static void levelCelsEvict(void);
|
||||||
static const uint8_t *levelSpriteBitmap(const StLevelT *level, uint8_t ptr);
|
static const uint8_t *levelSpriteBitmap(const StLevelT *level, uint8_t ptr);
|
||||||
static void loadingBar(jlSurfaceT *stage, uint8_t done, uint8_t total);
|
static void loadingBar(jlSurfaceT *stage, uint8_t done, uint8_t total);
|
||||||
|
|
@ -173,26 +173,7 @@ static bool prewarmReport(uint16_t expect);
|
||||||
static bool spriteOnScreen(int16_t px, int16_t py);
|
static bool spriteOnScreen(int16_t px, int16_t py);
|
||||||
static void starPaletteApply(void);
|
static void starPaletteApply(void);
|
||||||
static void tileFromChunky(jlTileT *out, const uint8_t *chunky);
|
static void tileFromChunky(jlTileT *out, const uint8_t *chunky);
|
||||||
|
static bool tileHasPixels(const uint8_t *chunky);
|
||||||
|
|
||||||
// Paint a VIC sprite bitmap onto the scratch surface's top-left 3x3
|
|
||||||
// tiles from the shared cel expansion (stCels.c, the same one the
|
|
||||||
// offline baker uses), so a cel built here for the interpreter fallback
|
|
||||||
// matches the precompiled .spc byte for byte.
|
|
||||||
static void buildSpriteCel(const uint8_t *bm, uint8_t multi, uint8_t color, uint8_t mc0, uint8_t mc1) {
|
|
||||||
uint8_t blob[ST_CEL_BYTES];
|
|
||||||
jlTileT tile;
|
|
||||||
uint8_t tx;
|
|
||||||
uint8_t ty;
|
|
||||||
|
|
||||||
stCelBlob(bm, multi, color, mc0, mc1, blob);
|
|
||||||
for (ty = 0u; ty < ST_SPRITE_TILES; ty++) {
|
|
||||||
for (tx = 0u; tx < ST_SPRITE_TILES; tx++) {
|
|
||||||
tileFromChunky(&tile, &blob[(ty * ST_SPRITE_TILES + tx) * TILE_BYTES]);
|
|
||||||
jlTilePaste(gRender.scratch, tx, ty, &tile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Compile a cached cel if it is not already, backing off after a failure
|
// Compile a cached cel if it is not already, backing off after a failure
|
||||||
|
|
@ -210,9 +191,10 @@ static void cacheCompile(StSpriteCacheT *e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// The JoeyLib sprite for hardware sprite `idx` of the current frame,
|
// The cached cel for hardware sprite `idx` of the current frame, built
|
||||||
// built on first use from its VIC bitmap and colours.
|
// on first use from its VIC bitmap and colours; NULL when there is
|
||||||
static jlSpriteT *cachedSprite(StSimT *sim, uint8_t idx) {
|
// nothing to draw.
|
||||||
|
static StSpriteCacheT *cachedSprite(StSimT *sim, uint8_t idx) {
|
||||||
uint8_t ptr = sim->frame.ptr[idx];
|
uint8_t ptr = sim->frame.ptr[idx];
|
||||||
uint8_t color = drawColor(sim, idx);
|
uint8_t color = drawColor(sim, idx);
|
||||||
uint8_t multi = (uint8_t)((sim->frame.multiMask & kStBit[idx]) != 0u ? 1u : 0u);
|
uint8_t multi = (uint8_t)((sim->frame.multiMask & kStBit[idx]) != 0u ? 1u : 0u);
|
||||||
|
|
@ -227,14 +209,14 @@ static jlSpriteT *cachedSprite(StSimT *sim, uint8_t idx) {
|
||||||
// Most frames a hardware sprite shows the cel it showed last time.
|
// Most frames a hardware sprite shows the cel it showed last time.
|
||||||
if (e != 0 && e->used && e->ptr == ptr && e->color == color && e->multi == multi && e->mc0 == mc0 && e->mc1 == mc1 && e->level == level) {
|
if (e != 0 && e->used && e->ptr == ptr && e->color == color && e->multi == multi && e->mc0 == mc0 && e->mc1 == mc1 && e->level == level) {
|
||||||
e->lastUse = gRender.cacheStamp;
|
e->lastUse = gRender.cacheStamp;
|
||||||
return e->sprite;
|
return e;
|
||||||
}
|
}
|
||||||
for (k = 0u; k < ST_SPRITE_CACHE; k++) {
|
for (k = 0u; k < ST_SPRITE_CACHE; k++) {
|
||||||
e = &gRender.cache[k];
|
e = &gRender.cache[k];
|
||||||
if (e->used && e->ptr == ptr && e->color == color && e->multi == multi && e->mc0 == mc0 && e->mc1 == mc1 && e->level == level) {
|
if (e->used && e->ptr == ptr && e->color == color && e->multi == multi && e->mc0 == mc0 && e->mc1 == mc1 && e->level == level) {
|
||||||
e->lastUse = gRender.cacheStamp;
|
e->lastUse = gRender.cacheStamp;
|
||||||
gRender.lastHit[idx] = e;
|
gRender.lastHit[idx] = e;
|
||||||
return e->sprite;
|
return e;
|
||||||
}
|
}
|
||||||
// Pinned entries (prewarmed cab/exhaust/passenger cels) are the
|
// Pinned entries (prewarmed cab/exhaust/passenger cels) are the
|
||||||
// costly-to-recompile ones and are never evicted; the least-
|
// costly-to-recompile ones and are never evicted; the least-
|
||||||
|
|
@ -264,9 +246,7 @@ static jlSpriteT *cachedSprite(StSimT *sim, uint8_t idx) {
|
||||||
slot->used = false;
|
slot->used = false;
|
||||||
slot->compiled = false;
|
slot->compiled = false;
|
||||||
}
|
}
|
||||||
buildSpriteCel(stSimSpriteBitmap(sim, ptr), multi, color, mc0, mc1);
|
if (!celCreate(slot, stSimSpriteBitmap(sim, ptr), multi, color, mc0, mc1)) {
|
||||||
slot->sprite = jlSpriteCreateFromSurface(gRender.scratch, 0, 0, ST_SPRITE_TILES, ST_SPRITE_TILES);
|
|
||||||
if (slot->sprite == 0) {
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// Nothing compiles mid-ride: a compile is ~0.5 s on the 65816, a
|
// Nothing compiles mid-ride: a compile is ~0.5 s on the 65816, a
|
||||||
|
|
@ -293,7 +273,7 @@ static jlSpriteT *cachedSprite(StSimT *sim, uint8_t idx) {
|
||||||
slot->used = true;
|
slot->used = true;
|
||||||
slot->pinned = gPrewarming;
|
slot->pinned = gPrewarming;
|
||||||
gRender.lastHit[idx] = slot;
|
gRender.lastHit[idx] = slot;
|
||||||
return slot->sprite;
|
return slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -312,9 +292,61 @@ static const uint8_t kCellRow[125] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Column of a cell whose row is known: cell - row * 40 through a table,
|
// Build a VIC bitmap's cel into a cache entry from the shared cel
|
||||||
// because the two-shift form of the multiply came back as a __mulhi3
|
// expansion (stCels.c, the same one the offline baker uses), cropped to
|
||||||
// library call on the 65816.
|
// the tiles that hold pixels. Most VIC sprites leave whole tiles of their
|
||||||
|
// 24x21 frame empty, and every one of those would otherwise be saved,
|
||||||
|
// drawn, restored and presented each time the sprite moves. A bitmap
|
||||||
|
// with no pixels keeps the whole frame.
|
||||||
|
//
|
||||||
|
// The tiles go onto the scratch surface last to first: on the planar
|
||||||
|
// ports tileFromChunky converts through the scratch's top-left tile, so
|
||||||
|
// that tile has to be the one written last.
|
||||||
|
static bool celCreate(StSpriteCacheT *e, const uint8_t *bm, uint8_t multi, uint8_t color, uint8_t mc0, uint8_t mc1) {
|
||||||
|
uint8_t blob[ST_CEL_BYTES];
|
||||||
|
jlTileT tile;
|
||||||
|
uint8_t txMin = ST_SPRITE_TILES;
|
||||||
|
uint8_t txMax = 0u;
|
||||||
|
uint8_t tyMin = ST_SPRITE_TILES;
|
||||||
|
uint8_t tyMax = 0u;
|
||||||
|
uint8_t tx;
|
||||||
|
uint8_t ty;
|
||||||
|
|
||||||
|
stCelBlob(bm, multi, color, mc0, mc1, blob);
|
||||||
|
ty = ST_SPRITE_TILES;
|
||||||
|
while (ty > 0u) {
|
||||||
|
ty--;
|
||||||
|
tx = ST_SPRITE_TILES;
|
||||||
|
while (tx > 0u) {
|
||||||
|
const uint8_t *chunk;
|
||||||
|
|
||||||
|
tx--;
|
||||||
|
chunk = &blob[(ty * ST_SPRITE_TILES + tx) * TILE_BYTES];
|
||||||
|
tileFromChunky(&tile, chunk);
|
||||||
|
jlTilePaste(gRender.scratch, tx, ty, &tile);
|
||||||
|
if (tileHasPixels(chunk)) {
|
||||||
|
txMin = (tx < txMin) ? tx : txMin;
|
||||||
|
txMax = (tx > txMax) ? tx : txMax;
|
||||||
|
tyMin = (ty < tyMin) ? ty : tyMin;
|
||||||
|
tyMax = (ty > tyMax) ? ty : tyMax;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (txMin > txMax) {
|
||||||
|
txMin = 0u;
|
||||||
|
tyMin = 0u;
|
||||||
|
txMax = ST_SPRITE_TILES - 1u;
|
||||||
|
tyMax = ST_SPRITE_TILES - 1u;
|
||||||
|
}
|
||||||
|
e->offX = (uint8_t)(txMin * TILE_PIXELS_PER_SIDE);
|
||||||
|
e->offY = (uint8_t)(tyMin * TILE_PIXELS_PER_SIDE);
|
||||||
|
e->w = (uint8_t)((txMax - txMin + 1u) * TILE_PIXELS_PER_SIDE);
|
||||||
|
e->h = (uint8_t)((tyMax - tyMin + 1u) * TILE_PIXELS_PER_SIDE);
|
||||||
|
e->sprite = jlSpriteCreateFromSurface(gRender.scratch, (int16_t)e->offX, (int16_t)e->offY, (uint8_t)(e->w / TILE_PIXELS_PER_SIDE), (uint8_t)(e->h / TILE_PIXELS_PER_SIDE));
|
||||||
|
return e->sprite != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// True for a cel that is MEANT to be built here rather than pre-listed,
|
// True for a cel that is MEANT to be built here rather than pre-listed,
|
||||||
// so the warning below stays about real misses. Only the title screen's
|
// so the warning below stays about real misses. Only the title screen's
|
||||||
// decorations qualify: logoColorCycle recolours them every cycle step,
|
// decorations qualify: logoColorCycle recolours them every cycle step,
|
||||||
|
|
@ -335,6 +367,9 @@ static bool celDrawsInterpreted(uint8_t ptr, uint8_t color) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Column of a cell whose row is known: cell - row * 40 through a table,
|
||||||
|
// because the two-shift form of the multiply came back as a __mulhi3
|
||||||
|
// library call on the 65816.
|
||||||
static uint8_t cellCol(uint16_t cell, uint8_t row) {
|
static uint8_t cellCol(uint16_t cell, uint8_t row) {
|
||||||
static const uint16_t kRowBase[ST_SCREEN_ROWS] = {
|
static const uint16_t kRowBase[ST_SCREEN_ROWS] = {
|
||||||
0, 40, 80, 120, 160, 200, 240, 280, 320, 360, 400, 440, 480,
|
0, 40, 80, 120, 160, 200, 240, 280, 320, 360, 400, 440, 480,
|
||||||
|
|
@ -425,8 +460,7 @@ static void dirtyBands(const StSimT *sim) {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// A plain loop, not memset: 25 entries do not pay for the 65816
|
// One loop over both 25-entry arrays.
|
||||||
// libc's far-called byte loop.
|
|
||||||
for (row = 0u; row < ST_SCREEN_ROWS; row++) {
|
for (row = 0u; row < ST_SCREEN_ROWS; row++) {
|
||||||
gRender.dirtyColMin[row] = 0xFFu;
|
gRender.dirtyColMin[row] = 0xFFu;
|
||||||
gRender.dirtyColMax[row] = 0u;
|
gRender.dirtyColMax[row] = 0u;
|
||||||
|
|
@ -688,6 +722,19 @@ static void tileFromChunky(jlTileT *out, const uint8_t *chunky) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// True when a chunky 8x8 tile has any non-transparent (non-zero) pixel.
|
||||||
|
static bool tileHasPixels(const uint8_t *chunky) {
|
||||||
|
uint8_t k;
|
||||||
|
|
||||||
|
for (k = 0u; k < TILE_BYTES; k++) {
|
||||||
|
if (chunky[k] != 0u) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Public
|
// Public
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -718,13 +765,13 @@ void stRenderFrame(jlSurfaceT *stage, StSimT *sim) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (gRender.starMode) {
|
if (gRender.starMode) {
|
||||||
// The warp is the only thing animating here, and it animates in
|
// The warp animates in the palette: no cells change and no sprite
|
||||||
// the palette: no cells change, no sprite moves for it, so the
|
// moves for it. The lit step follows the intro's own frame count,
|
||||||
// present that ends this frame copies no pixels at all.
|
// so the stars keep the C64's speed whatever the frame rate.
|
||||||
gRender.starTick++;
|
uint8_t phase = (uint8_t)((sim->introStepCount / ST_STAR_FRAMES_PER_STEP) & (ST_STAR_RING_COUNT - 1u));
|
||||||
if (gRender.starTick >= ST_STAR_STEP_FRAMES) {
|
|
||||||
gRender.starTick = 0u;
|
if (phase != gRender.starPhase) {
|
||||||
gRender.starPhase = (uint8_t)((gRender.starPhase + 1u) & (ST_STAR_RING_COUNT - 1u));
|
gRender.starPhase = phase;
|
||||||
starPaletteApply();
|
starPaletteApply();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -752,17 +799,21 @@ void stRenderFrame(jlSurfaceT *stage, StSimT *sim) {
|
||||||
for (k = ST_HW_SPRITES; k > first; k--) {
|
for (k = ST_HW_SPRITES; k > first; k--) {
|
||||||
StDrawnT *d = &gRender.slot[k - 1u];
|
StDrawnT *d = &gRender.slot[k - 1u];
|
||||||
if (d->drawn) {
|
if (d->drawn) {
|
||||||
jlSpriteRestoreUnder(stage, &gRender.backup[k - 1u]);
|
if (d->trusted) {
|
||||||
|
jlSpriteRestoreUnderTrusted(stage, &gRender.backup[k - 1u]);
|
||||||
|
} else {
|
||||||
|
jlSpriteRestoreUnder(stage, &gRender.backup[k - 1u]);
|
||||||
|
}
|
||||||
d->drawn = false;
|
d->drawn = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
paintCells(stage, sim);
|
paintCells(stage, sim);
|
||||||
for (k = first; k < ST_HW_SPRITES; k++) {
|
for (k = first; k < ST_HW_SPRITES; k++) {
|
||||||
uint8_t idx = (uint8_t)(ST_HW_SPRITES - 1u - k);
|
uint8_t idx = (uint8_t)(ST_HW_SPRITES - 1u - k);
|
||||||
StDrawnT *d = &gRender.slot[k];
|
StDrawnT *d = &gRender.slot[k];
|
||||||
jlSpriteT *sp;
|
StSpriteCacheT *e;
|
||||||
int16_t px;
|
int16_t px;
|
||||||
int16_t py;
|
int16_t py;
|
||||||
if ((sim->frame.enableMask & kStBit[idx]) == 0u) {
|
if ((sim->frame.enableMask & kStBit[idx]) == 0u) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -771,11 +822,21 @@ void stRenderFrame(jlSurfaceT *stage, StSimT *sim) {
|
||||||
if (!spriteOnScreen(px, py)) {
|
if (!spriteOnScreen(px, py)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sp = cachedSprite(sim, idx);
|
e = cachedSprite(sim, idx);
|
||||||
if (sp == 0) {
|
if (e == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
jlSpriteSaveAndDraw(stage, sp, px, py, &gRender.backup[k]);
|
px = (int16_t)(px + e->offX);
|
||||||
|
py = (int16_t)(py + e->offY);
|
||||||
|
// A compiled cel wholly on the stage takes the trusted calls,
|
||||||
|
// which skip the per-call geometry checks; one that is clipped
|
||||||
|
// or interpreted needs the checked ones, and so does its restore.
|
||||||
|
d->trusted = e->compiled && px >= 0 && py >= 0 && px <= (int16_t)(SURFACE_WIDTH - e->w) && py <= (int16_t)(SURFACE_HEIGHT - e->h);
|
||||||
|
if (d->trusted) {
|
||||||
|
jlSpriteSaveAndDrawTrusted(stage, e->sprite, px, py, &gRender.backup[k]);
|
||||||
|
} else {
|
||||||
|
jlSpriteSaveAndDraw(stage, e->sprite, px, py, &gRender.backup[k]);
|
||||||
|
}
|
||||||
d->drawn = true;
|
d->drawn = true;
|
||||||
d->x = sim->frame.x[idx];
|
d->x = sim->frame.x[idx];
|
||||||
d->y = sim->frame.y[idx];
|
d->y = sim->frame.y[idx];
|
||||||
|
|
@ -824,14 +885,6 @@ void stRenderInit(jlSurfaceT *stage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Build (and compile) the cels the game draws every screen -- cab,
|
|
||||||
// exhaust, passenger, wreck, warp, intro cab and star -- up front, so
|
|
||||||
// no frame pays for a sprite build mid-play. A bar on the stage shows
|
|
||||||
// progress on the slower ports.
|
|
||||||
// Load the offline-baked, already-compiled cel bank into the pinned
|
|
||||||
// cache so no cel is JIT-compiled at boot (each 65816 compile costs
|
|
||||||
// ~0.4 s). Every kStCels entry becomes a pinned cache slot keyed exactly
|
|
||||||
// as the play lookups: system ptr (level 0xFF), the level-wide colours.
|
|
||||||
// The first unused cache slot, or NULL when the cache is full.
|
// The first unused cache slot, or NULL when the cache is full.
|
||||||
static StSpriteCacheT *cacheFreeSlot(void) {
|
static StSpriteCacheT *cacheFreeSlot(void) {
|
||||||
uint8_t k;
|
uint8_t k;
|
||||||
|
|
@ -936,15 +989,17 @@ bool stRenderLevelCels(jlSurfaceT *stage, const StLevelT *level) {
|
||||||
}
|
}
|
||||||
if (got == n) {
|
if (got == n) {
|
||||||
slot->sprite = cels[k];
|
slot->sprite = cels[k];
|
||||||
slot->compiled = true;
|
slot->offX = 0u;
|
||||||
|
slot->offY = 0u;
|
||||||
|
slot->w = ST_SPRITE_PX;
|
||||||
|
slot->h = ST_SPRITE_PX;
|
||||||
|
slot->compiled = (jlSpriteCompiledSize(cels[k]) != 0u);
|
||||||
} else {
|
} else {
|
||||||
const uint8_t *bm = levelSpriteBitmap(level, list[k].ptr);
|
const uint8_t *bm = levelSpriteBitmap(level, list[k].ptr);
|
||||||
if (bm == 0) {
|
if (bm == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
buildSpriteCel(bm, list[k].multi, list[k].color, list[k].mc0, list[k].mc1);
|
if (!celCreate(slot, bm, list[k].multi, list[k].color, list[k].mc0, list[k].mc1)) {
|
||||||
slot->sprite = jlSpriteCreateFromSurface(gRender.scratch, 0, 0, ST_SPRITE_TILES, ST_SPRITE_TILES);
|
|
||||||
if (slot->sprite == 0) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
slot->compiled = false;
|
slot->compiled = false;
|
||||||
|
|
@ -975,6 +1030,10 @@ bool stRenderLevelCels(jlSurfaceT *stage, const StLevelT *level) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Load the offline-baked, already-compiled cel bank into the pinned
|
||||||
|
// cache so no cel is JIT-compiled at boot (each 65816 compile costs
|
||||||
|
// ~0.4 s). Every kStCels entry becomes a pinned cache slot keyed exactly
|
||||||
|
// as the play lookups: system ptr (level 0xFF), the level-wide colours.
|
||||||
// Returns false if the .spc is missing or built for another target/shift
|
// Returns false if the .spc is missing or built for another target/shift
|
||||||
// count (a stale bake), so the caller falls back to the runtime JIT
|
// count (a stale bake), so the caller falls back to the runtime JIT
|
||||||
// prewarm below. Every port stages its own bake (make/<port>.mk).
|
// prewarm below. Every port stages its own bake (make/<port>.mk).
|
||||||
|
|
@ -1002,6 +1061,10 @@ static bool loadPrecompiledCels(void) {
|
||||||
e->mc0 = kStCels[i].mc0;
|
e->mc0 = kStCels[i].mc0;
|
||||||
e->mc1 = kStCels[i].mc1;
|
e->mc1 = kStCels[i].mc1;
|
||||||
e->level = 0xFFu;
|
e->level = 0xFFu;
|
||||||
|
e->offX = 0u;
|
||||||
|
e->offY = 0u;
|
||||||
|
e->w = ST_SPRITE_PX;
|
||||||
|
e->h = ST_SPRITE_PX;
|
||||||
e->used = true;
|
e->used = true;
|
||||||
e->pinned = true;
|
e->pinned = true;
|
||||||
e->lastUse = 0u;
|
e->lastUse = 0u;
|
||||||
|
|
@ -1071,11 +1134,16 @@ void stRenderLoading(jlSurfaceT *stage) {
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Build (and compile) the cels the game draws every screen -- cab,
|
||||||
|
// exhaust, passenger, wreck and intro cab -- up front, so no frame pays
|
||||||
|
// for a sprite build mid-play. A bar on the stage shows progress on the
|
||||||
|
// slower ports.
|
||||||
bool stRenderPrewarm(jlSurfaceT *stage, StSimT *sim) {
|
bool stRenderPrewarm(jlSurfaceT *stage, StSimT *sim) {
|
||||||
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 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 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 };
|
static const uint8_t kFlamePtrs[] = { 0xD8, 0xD4, 0xD5, 0xD2, 0xD1, 0xD7, 0xD3, 0xD6 };
|
||||||
uint8_t total = (uint8_t)(sizeof(kCabPtrs) + sizeof(kPassPtrs) + sizeof(kFlamePtrs) + 1u);
|
uint8_t total = (uint8_t)(sizeof(kCabPtrs) + sizeof(kPassPtrs) + sizeof(kFlamePtrs));
|
||||||
uint8_t done = 0u;
|
uint8_t done = 0u;
|
||||||
bool ok;
|
bool ok;
|
||||||
uint8_t k;
|
uint8_t k;
|
||||||
|
|
@ -1114,13 +1182,6 @@ bool stRenderPrewarm(jlSurfaceT *stage, StSimT *sim) {
|
||||||
done++;
|
done++;
|
||||||
loadingBar(stage, done, total);
|
loadingBar(stage, done, total);
|
||||||
}
|
}
|
||||||
// The intro star is a hires sprite in colour 7. Build it as a moving
|
|
||||||
// (idx <= 2) cel so it compiles: the level-name intro draws seven of
|
|
||||||
// them every frame, and the interpreter cannot keep up.
|
|
||||||
sim->frame.multiMask = 0u;
|
|
||||||
sim->frame.ptr[2] = 0xDAu;
|
|
||||||
sim->frame.color[2] = 0x07u;
|
|
||||||
(void)cachedSprite(sim, 2u);
|
|
||||||
sim->frame = saved;
|
sim->frame = saved;
|
||||||
gPrewarming = false;
|
gPrewarming = false;
|
||||||
ok = prewarmReport(total);
|
ok = prewarmReport(total);
|
||||||
|
|
@ -1206,11 +1267,10 @@ void stRenderTitleLogo(StSimT *sim) {
|
||||||
// Turn on the level intro's palette-cycled starfield. Call it after
|
// Turn on the level intro's palette-cycled starfield. Call it after
|
||||||
// stRenderSceneChanged (which turns it back off) on entry to the level
|
// stRenderSceneChanged (which turns it back off) on entry to the level
|
||||||
// intro. The cells were painted by stIntroEnter; from here the warp is
|
// intro. The cells were painted by stIntroEnter; from here the warp is
|
||||||
// one palette write every ST_STAR_STEP_FRAMES frames.
|
// one palette write every ST_STAR_FRAMES_PER_STEP intro frames.
|
||||||
void stRenderIntroStars(void) {
|
void stRenderIntroStars(void) {
|
||||||
gRender.starMode = true;
|
gRender.starMode = true;
|
||||||
gRender.starPhase = 0u;
|
gRender.starPhase = 0u;
|
||||||
gRender.starTick = 0u;
|
|
||||||
starPaletteApply();
|
starPaletteApply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
28
stSim.h
28
stSim.h
|
|
@ -100,8 +100,9 @@
|
||||||
// pixel rows.
|
// pixel rows.
|
||||||
//
|
//
|
||||||
// The contract between the two halves is just this colour range:
|
// 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
|
// stTitle.c colours a star's cell ST_STAR_RING_FIRST + the step of its
|
||||||
// the centre), and the renderer cycles that range. These are C64 colour
|
// ray's flight (offset by the ray's launch delay), and the renderer
|
||||||
|
// cycles that range. These are C64 colour
|
||||||
// indices the intro screen never uses -- it draws in 0, 3 and, in demo
|
// 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.
|
// mode, 4 and 5; the cab sprite adds 2, 6 and 7.
|
||||||
#define ST_STAR_RING_FIRST 8u
|
#define ST_STAR_RING_FIRST 8u
|
||||||
|
|
@ -112,12 +113,14 @@
|
||||||
// something else must fail to compile here instead of quietly making
|
// something else must fail to compile here instead of quietly making
|
||||||
// the warp skip rings.
|
// the warp skip rings.
|
||||||
typedef char stStarRingCountIsPow2[((ST_STAR_RING_COUNT & (ST_STAR_RING_COUNT - 1u)) == 0u) ? 1 : -1];
|
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
|
// A C64 star flies $20 frames ($4500) and the painted field marks every
|
||||||
// stars are the far ones and get the small glyph. The big glyph belongs
|
// ST_STAR_FRAMES_PER_STEP of them, so the renderer steps the lit colour on
|
||||||
// to the outer rings, the stars about to rush past the viewer.
|
// that many intro frames (introStepCount): the warp keeps the C64's speed at
|
||||||
#define ST_STAR_GLYPH_NEAR 0x2Au // '*', the outer rings
|
// any frame rate.
|
||||||
#define ST_STAR_GLYPH_FAR 0x2Eu // '.', the inner rings
|
#define ST_INTRO_STAR_FLIGHT 0x20u
|
||||||
#define ST_STAR_FAR_RINGS 5u // rings 0..4 are still distant
|
#define ST_STAR_FRAMES_PER_STEP (ST_INTRO_STAR_FLIGHT / ST_STAR_RING_COUNT)
|
||||||
|
// Every star is the small glyph: the C64's star is a 5x3 speck.
|
||||||
|
#define ST_STAR_GLYPH 0x2Eu // '.'
|
||||||
|
|
||||||
// VIC sprite block pointers the game selects by name. The cab's bit 0 is
|
// VIC sprite block pointers the game selects by name. The cab's bit 0 is
|
||||||
// the landing gear, so each facing is a pair.
|
// the landing gear, so each facing is a pair.
|
||||||
|
|
@ -309,7 +312,11 @@ typedef struct {
|
||||||
uint8_t hookData[ST_HOOK_BYTES]; // $7D98..$7FFF as shipped
|
uint8_t hookData[ST_HOOK_BYTES]; // $7D98..$7FFF as shipped
|
||||||
} StLevelT;
|
} StLevelT;
|
||||||
|
|
||||||
// One VIC sprite as the game sees it through its shadow tables.
|
// One VIC sprite as the game sees it through its shadow tables. Padded
|
||||||
|
// to eight bytes: the 65816 compiler indexes a 6-byte element through
|
||||||
|
// a call to its 32-bit multiply routine (sim is a far pointer), and
|
||||||
|
// every sim->spr[idx] in the game paid for one; a power-of-two stride
|
||||||
|
// is three shifts.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t col; // $7175,X X low byte
|
uint8_t col; // $7175,X X low byte
|
||||||
uint8_t msb; // $7185,X X high bit (any non-zero value = set)
|
uint8_t msb; // $7185,X X high bit (any non-zero value = set)
|
||||||
|
|
@ -317,7 +324,9 @@ typedef struct {
|
||||||
uint8_t enable; // $718E,X
|
uint8_t enable; // $718E,X
|
||||||
uint8_t ptr; // $7197,X block pointer
|
uint8_t ptr; // $7197,X block pointer
|
||||||
uint8_t color; // $719F,X
|
uint8_t color; // $719F,X
|
||||||
|
uint8_t strideToPow2[2];
|
||||||
} StSpriteT;
|
} StSpriteT;
|
||||||
|
typedef char stSpriteStrideIsPow2[((sizeof(StSpriteT) & (sizeof(StSpriteT) - 1u)) == 0u) ? 1 : -1];
|
||||||
|
|
||||||
// The frame the VIC last displayed ($4253 marshal + $4293 flush): the
|
// The frame the VIC last displayed ($4253 marshal + $4293 flush): the
|
||||||
// renderer draws this and the collision test evaluates it.
|
// renderer draws this and the collision test evaluates it.
|
||||||
|
|
@ -542,6 +551,7 @@ typedef struct {
|
||||||
uint8_t logoCycleAux; // $48A5
|
uint8_t logoCycleAux; // $48A5
|
||||||
uint8_t logoDiv; // $48A6
|
uint8_t logoDiv; // $48A6
|
||||||
uint8_t introLoopCount; // $4508
|
uint8_t introLoopCount; // $4508
|
||||||
|
uint8_t introStepCount; // port: intro frames run, for the painted warp
|
||||||
uint8_t introCabDx; // $450A
|
uint8_t introCabDx; // $450A
|
||||||
uint8_t introCabDxTimer; // $450B
|
uint8_t introCabDxTimer; // $450B
|
||||||
uint8_t spriteMc0; // $D025
|
uint8_t spriteMc0; // $D025
|
||||||
|
|
|
||||||
142
stTitle.c
142
stTitle.c
|
|
@ -14,8 +14,10 @@
|
||||||
//
|
//
|
||||||
// The seven star sprites are replaced by a static starfield whose
|
// 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
|
// 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
|
// how. The stars sit on the seven rays the C64's stars fly, where those
|
||||||
// sequence keeps its length and its cue; only the warp changes.
|
// stars are every few frames of a flight, so the field plays the
|
||||||
|
// original's paths. The cab still flies, and it is still what ends the
|
||||||
|
// intro, so the sequence keeps its length and its cue.
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
|
@ -40,42 +42,35 @@ static const uint8_t kTextScreens[] = "THIS IS 1 OF 25 DIFFERENT SCREENS!";
|
||||||
#define ST_TITLE_SPARKLE_TICKS 0x5Au
|
#define ST_TITLE_SPARKLE_TICKS 0x5Au
|
||||||
#define ST_TITLE_HOVER_COL 0x28u
|
#define ST_TITLE_HOVER_COL 0x28u
|
||||||
#define ST_INTRO_STAR_PTR 0xDAu
|
#define ST_INTRO_STAR_PTR 0xDAu
|
||||||
|
#define ST_INTRO_STAR_COL 0xAAu // where a star launches ($4523)
|
||||||
|
#define ST_INTRO_STAR_ROW 0x8Cu
|
||||||
|
#define ST_INTRO_STAR_DELAYS 0x20u // launch delays drawn from 0..$1F ($44F8)
|
||||||
#define ST_INTRO_CAB_END_ROW 0x94u
|
#define ST_INTRO_CAB_END_ROW 0x94u
|
||||||
#define ST_INTRO_CAB_DX_RELOAD 0x28u
|
#define ST_INTRO_CAB_DX_RELOAD 0x28u
|
||||||
|
|
||||||
// The palette-cycled starfield. Seven stars per ring so the bright band
|
// The palette-cycled starfield. The C64 flies one star sprite along each
|
||||||
// stays the same weight all the way out, placed at fixed pseudo-random
|
// of seven rays at these velocities ($450C / $4513, pixels a frame) for
|
||||||
// angles and clear of the three lines the intro screen writes (the level
|
// $20 frames ($4500), then launches it again from the start. The painted
|
||||||
// name on row 12, and the demo hints on rows 3 and 24).
|
// field puts a star where each flight is every ST_STAR_FRAMES_PER_STEP
|
||||||
typedef struct {
|
// frames; the renderer lights one step of every ray at a time, so a star
|
||||||
uint8_t col;
|
// hops out along its ray. A star is drawn where the sprite's image ($DA:
|
||||||
uint8_t row;
|
// a 5x3 speck centred on pixel 11, 9 of the sprite) would be.
|
||||||
uint8_t ring;
|
#define ST_INTRO_STARS 7u
|
||||||
} StStarT;
|
#define ST_INTRO_STAR_IMAGE_X 11 // the speck's centre in its sprite
|
||||||
|
#define ST_INTRO_STAR_IMAGE_Y 9
|
||||||
|
#define ST_SCREEN_WIDTH_PX ((int16_t)(ST_SCREEN_COLS * 8u))
|
||||||
|
#define ST_SCREEN_HEIGHT_PX ((int16_t)(ST_SCREEN_ROWS * 8u))
|
||||||
|
#define ST_CELL_SHIFT 3 // 8 pixels a cell
|
||||||
|
|
||||||
static const StStarT kIntroStar[] = {
|
static const int8_t kIntroStarDx[ST_INTRO_STARS] = { 0, 6, 6, 6, -6, -6, -6 };
|
||||||
{ 18u, 10u, 0u }, { 19u, 10u, 0u }, { 17u, 11u, 0u }, { 18u, 13u, 0u },
|
static const int8_t kIntroStarDy[ST_INTRO_STARS] = { -4, -4, 0, 4, 4, 0, -4 };
|
||||||
{ 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 addToRow(StSimT *sim, uint8_t idx, uint8_t delta);
|
||||||
static void logoColorCycle(StSimT *sim);
|
static void logoColorCycle(StSimT *sim);
|
||||||
static void logoFlip(StSimT *sim);
|
static void logoFlip(StSimT *sim);
|
||||||
static void paintStarfield(StSimT *sim);
|
static void paintStarfield(StSimT *sim, const uint8_t *delay);
|
||||||
|
static bool rowHasText(const StSimT *sim, uint8_t row);
|
||||||
|
|
||||||
|
|
||||||
// $4113
|
// $4113
|
||||||
|
|
@ -124,21 +119,60 @@ static void logoFlip(StSimT *sim) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Paint the level-intro starfield into screen RAM, once. A star's cell
|
// Paint the level-intro starfield into screen RAM, once. A star's cell is
|
||||||
// is coloured ST_STAR_RING_FIRST + its ring and never touched again --
|
// coloured ST_STAR_RING_FIRST + the step of its flight it marks, offset by
|
||||||
// the renderer animates the warp by cycling those colours, which costs
|
// its ray's launch delay so the rays fire out of step as on the C64, and is
|
||||||
// no dirty rows at all. See ST_STAR_RING_FIRST in stSim.h.
|
// never touched again -- the renderer animates the warp by cycling those
|
||||||
static void paintStarfield(StSimT *sim) {
|
// colours, which costs no dirty rows at all (ST_STAR_RING_FIRST, stSim.h).
|
||||||
const StStarT *star;
|
// Step 0 is the launch point, which every ray shares, and steps off the
|
||||||
uint8_t one[2];
|
// screen are left out, as are rows that carry the intro's text: on the C64
|
||||||
uint8_t k;
|
// the stars fly over the text, but a cell star there reads as part of it --
|
||||||
|
// a gap in the level name, or a full stop after the demo hint.
|
||||||
|
static void paintStarfield(StSimT *sim, const uint8_t *delay) {
|
||||||
|
static const uint8_t one[2] = { ST_STAR_GLYPH, 0u }; // stSimDrawText stops below screen code 6
|
||||||
|
bool textRow[ST_SCREEN_ROWS];
|
||||||
|
int16_t originX;
|
||||||
|
int16_t originY;
|
||||||
|
int16_t x;
|
||||||
|
int16_t y;
|
||||||
|
uint8_t row;
|
||||||
|
uint8_t ray;
|
||||||
|
uint8_t step;
|
||||||
|
uint8_t slot;
|
||||||
|
|
||||||
one[1] = 0u; // stSimDrawText stops below screen code 6
|
// Before any star goes down, or the first star on a row would hide the
|
||||||
for (k = 0u; k < (uint8_t)ST_INTRO_STAR_COUNT; k++) {
|
// rest of that row's.
|
||||||
star = &kIntroStar[k];
|
for (row = 0u; row < ST_SCREEN_ROWS; row++) {
|
||||||
one[0] = (star->ring < ST_STAR_FAR_RINGS) ? ST_STAR_GLYPH_FAR : ST_STAR_GLYPH_NEAR;
|
textRow[row] = rowHasText(sim, row);
|
||||||
stSimDrawText(sim, star->col, star->row, one, (uint8_t)(ST_STAR_RING_FIRST + star->ring));
|
|
||||||
}
|
}
|
||||||
|
originX = (int16_t)(ST_INTRO_STAR_COL - ST_SPRITE_X_ORIGIN + ST_INTRO_STAR_IMAGE_X);
|
||||||
|
originY = (int16_t)(ST_INTRO_STAR_ROW - ST_SPRITE_Y_ORIGIN + ST_INTRO_STAR_IMAGE_Y);
|
||||||
|
for (ray = 0u; ray < ST_INTRO_STARS; ray++) {
|
||||||
|
for (step = 1u; step < ST_STAR_RING_COUNT; step++) {
|
||||||
|
x = (int16_t)(originX + kIntroStarDx[ray] * (int16_t)(step * ST_STAR_FRAMES_PER_STEP));
|
||||||
|
y = (int16_t)(originY + kIntroStarDy[ray] * (int16_t)(step * ST_STAR_FRAMES_PER_STEP));
|
||||||
|
if (x < 0 || x >= ST_SCREEN_WIDTH_PX || y < 0 || y >= ST_SCREEN_HEIGHT_PX || textRow[y >> ST_CELL_SHIFT]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
slot = (uint8_t)((step + delay[ray] / ST_STAR_FRAMES_PER_STEP) & (ST_STAR_RING_COUNT - 1u));
|
||||||
|
stSimDrawText(sim, (uint8_t)(x >> ST_CELL_SHIFT), (uint8_t)(y >> ST_CELL_SHIFT), one, (uint8_t)(ST_STAR_RING_FIRST + slot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// True when screen row `row` holds anything but blanks.
|
||||||
|
static bool rowHasText(const StSimT *sim, uint8_t row) {
|
||||||
|
const uint8_t *cell;
|
||||||
|
uint8_t col;
|
||||||
|
|
||||||
|
cell = &sim->screen[(uint16_t)row * ST_SCREEN_COLS];
|
||||||
|
for (col = 0u; col < ST_SCREEN_COLS; col++) {
|
||||||
|
if (cell[col] != ST_CHAR_SPACE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -148,6 +182,7 @@ static void paintStarfield(StSimT *sim) {
|
||||||
|
|
||||||
// $4523..$4665 -- set up the level-name screen.
|
// $4523..$4665 -- set up the level-name screen.
|
||||||
void stIntroEnter(StSimT *sim, const StLevelT *level) {
|
void stIntroEnter(StSimT *sim, const StLevelT *level) {
|
||||||
|
uint8_t delay[ST_INTRO_STARS];
|
||||||
uint8_t k;
|
uint8_t k;
|
||||||
|
|
||||||
sim->level = level;
|
sim->level = level;
|
||||||
|
|
@ -161,20 +196,19 @@ void stIntroEnter(StSimT *sim, const StLevelT *level) {
|
||||||
sim->spr[7].row = 0xE4u;
|
sim->spr[7].row = 0xE4u;
|
||||||
sim->introCabDxTimer = 0x14u;
|
sim->introCabDxTimer = 0x14u;
|
||||||
sim->introCabDx = 0x01u;
|
sim->introCabDx = 0x01u;
|
||||||
for (k = 0u; k < 7u; k++) {
|
for (k = 0u; k < ST_INTRO_STARS; k++) {
|
||||||
uint8_t idx = (uint8_t)(6u - k);
|
uint8_t idx = (uint8_t)(ST_INTRO_STARS - 1u - k);
|
||||||
sim->spr[idx].row = 0x8Cu;
|
sim->spr[idx].row = ST_INTRO_STAR_ROW;
|
||||||
sim->spr[idx].msb = 0u;
|
sim->spr[idx].msb = 0u;
|
||||||
sim->spr[idx].col = 0xAAu;
|
sim->spr[idx].col = ST_INTRO_STAR_COL;
|
||||||
sim->spr[idx].color = 0x07u;
|
sim->spr[idx].color = 0x07u;
|
||||||
sim->spr[idx].ptr = ST_INTRO_STAR_PTR;
|
sim->spr[idx].ptr = ST_INTRO_STAR_PTR;
|
||||||
sim->spr[idx].enable = 0u;
|
sim->spr[idx].enable = 0u;
|
||||||
// $44F8/$4500 held each star's spawn timer and reload. Nothing
|
// $44F8: each star's launch delay. The painted field uses it to
|
||||||
// reads them now that the warp is painted rather than flown, but
|
// put the rays out of step, and the seven RNG draws MUST stay in
|
||||||
// the seven RNG draws MUST stay: they advance the shared RNG and
|
// any case: they advance the shared RNG and the level that
|
||||||
// the level that follows draws from it, so dropping them would
|
// follows draws from it.
|
||||||
// change the game that comes after the intro.
|
delay[idx] = stSimRng(sim, ST_INTRO_STAR_DELAYS);
|
||||||
(void)stSimRng(sim, 0x20u);
|
|
||||||
}
|
}
|
||||||
sim->borderColor = 0u;
|
sim->borderColor = 0u;
|
||||||
sim->bgColor = 0u;
|
sim->bgColor = 0u;
|
||||||
|
|
@ -188,12 +222,13 @@ void stIntroEnter(StSimT *sim, const StLevelT *level) {
|
||||||
sim->charDirtyAll = true;
|
sim->charDirtyAll = true;
|
||||||
sim->charDirtyCount = 0u;
|
sim->charDirtyCount = 0u;
|
||||||
stSimDrawText(sim, 10u, 12u, level->name, 3u);
|
stSimDrawText(sim, 10u, 12u, level->name, 3u);
|
||||||
paintStarfield(sim);
|
|
||||||
if (sim->demoMode != 0u && sim->postMortem == 0u) {
|
if (sim->demoMode != 0u && sim->postMortem == 0u) {
|
||||||
stSimDrawText(sim, 7u, 3u, kTextDemoExit, 5u);
|
stSimDrawText(sim, 7u, 3u, kTextDemoExit, 5u);
|
||||||
stSimDrawText(sim, 3u, 24u, kTextScreens, 4u);
|
stSimDrawText(sim, 3u, 24u, kTextScreens, 4u);
|
||||||
}
|
}
|
||||||
|
paintStarfield(sim, delay);
|
||||||
sim->introLoopCount = 0u;
|
sim->introLoopCount = 0u;
|
||||||
|
sim->introStepCount = 0u;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -203,6 +238,7 @@ bool stIntroStep(StSimT *sim) {
|
||||||
uint8_t k;
|
uint8_t k;
|
||||||
|
|
||||||
stSimMarshal(sim);
|
stSimMarshal(sim);
|
||||||
|
sim->introStepCount++;
|
||||||
sim->introLoopCount = (uint8_t)((sim->introLoopCount + 1u) & 3u);
|
sim->introLoopCount = (uint8_t)((sim->introLoopCount + 1u) & 3u);
|
||||||
addToRow(sim, 7u, (uint8_t)kIntroCabDy[sim->introLoopCount]);
|
addToRow(sim, 7u, (uint8_t)kIntroCabDy[sim->introLoopCount]);
|
||||||
if ((sim->introLoopCount & 1u) == 0u) {
|
if ((sim->introLoopCount & 1u) == 0u) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue