Updates to match changes to JoeyLib.

This commit is contained in:
Scott Duensing 2026-10-14 21:18:12 -05:00
parent 57dab02e21
commit 4d73ec7e85
5 changed files with 49 additions and 46 deletions

View file

@ -2,14 +2,14 @@
A port of Space Taxi (John Kutcher, Muse Software, 1984), the Commodore 64
game, to every machine [JoeyLib](../joeylib) targets: the Apple IIgs,
Commodore Amiga, Atari ST, MS-DOS, Sharp X68000 and DVX. One C codebase
Commodore Amiga, Atari ST, MS-DOS, Sharp X68000, and DVX. One C codebase
builds for all six.
The game is a routine-for-routine re-expression of the C64 original in C,
written from its disassembly (MECHANICS.md): the flight physics, the fare
machine, the 24 levels' hook programs, the title and level intros, the
high-score table and name entry, the music (captured from the game's own
SID player) and the speech. The simulation (stSim.c) is checked tick for
SID player), and the speech. The simulation (stSim.c) is checked tick for
tick against the original game running in VICE: a ride on each of the 25
screens, A..X and the MYSTERY SCREEN, traces byte for byte as on the C64
(tools/stsim; VERIFIED.md), and the gates below hold it there.
@ -17,7 +17,7 @@ screens, A..X and the MYSTERY SCREEN, traces byte for byte as on the C64
## Setting up
Space Taxi builds with JoeyLib, which owns the cross compilers, the
library, the asset tools and every machine's build rules. Check JoeyLib out
library, the asset tools, and every machine's build rules. Check JoeyLib out
beside this repository and install its toolchains (its README covers the
installer):
@ -69,7 +69,7 @@ Everything lands in `build/<machine>/bin/`:
| Atari ST | `STAXI/STAXI.PRG` with its `DATA/` |
| MS-DOS | `STAXI/STAXI.EXE` with its `DATA/` |
| Sharp X68000 | `STAXI/STAXI.X` with its `DATA/` |
| DVX | `apps/joeylib/staxi/`: `staxi.app`, its help file and `DATA/` |
| DVX | `apps/joeylib/staxi/`: `staxi.app`, its help file, and `DATA/` |
The bakes (levels, the pre-compiled sprite cels, the speech) are made per
machine in `build/generated/<machine>/`. JoeyLib's library is built on
@ -100,8 +100,8 @@ $ scripts/verify.sh disk # or name the ones to run
|------|----------------|
| `stsim` | Every screen's ride (all 25: the four C64 attract recordings and twenty-one synthesised streams, up to 4000 ticks, crashes costing a cab as in a real game) traces exactly as the real C64 traced it -- `gateHashes.txt` holds the md5s of the C64's traces (`tools/stsim/gate.sh`) -- and every cel a ride shows is pre-compiled (`celGate.sh`) |
| `harness` | The screens above the simulation -- the high-score table, name entry, its save -- on JoeyLib's host BLANK port (`tools/staxiharness`) |
| `disk`, `disk01` | `STAXI.2mg` boots, launches the game and draws on a ROM 3 and a ROM 01 IIgs |
| `iigsaudio` | The IIgs sound interrupt is NTP's, the DOC is audible and its sound slots carry waveforms |
| `disk`, `disk01` | `STAXI.2mg` boots, launches the game, and draws on a ROM 3 and a ROM 01 IIgs |
| `iigsaudio` | The IIgs sound interrupt is NTP's, the DOC is audible, and its sound slots carry waveforms |
| `x68kaudio` | Every X68000 noise write is a code from the port's table, and the ADPCM runs at 7812 Hz to both speakers while the game speaks |
`scripts/verify.sh` exits 0 when every gate passed, 1 when one failed, and 3

View file

@ -524,26 +524,26 @@ static void gameOverEnter(void) {
static bool joyFire(void) {
return jlKeyDown(KEY_SPACE) || jlJoyDown(JOYSTICK_0, JOY_BUTTON_0);
return jlKeyDown(JL_KEY_SPACE) || jlJoyDown(JL_JOYSTICK_0, JL_JOY_BUTTON_0);
}
// The C64's EOR'd $DC00: bit0 UP, 1 DOWN, 2 LEFT, 3 RIGHT, 4 FIRE, bit 7 set.
static uint8_t joyMask(void) {
int8_t jx = jlJoystickX(JOYSTICK_0);
int8_t jy = jlJoystickY(JOYSTICK_0);
int8_t jx = jlJoystickX(JL_JOYSTICK_0);
int8_t jy = jlJoystickY(JL_JOYSTICK_0);
uint8_t mask = 0x80u;
if (jlKeyDown(KEY_UP) || jy < -32) {
if (jlKeyDown(JL_KEY_UP) || jy < -32) {
mask |= 0x01u;
}
if (jlKeyDown(KEY_DOWN) || jy > 32) {
if (jlKeyDown(JL_KEY_DOWN) || jy > 32) {
mask |= 0x02u;
}
if (jlKeyDown(KEY_LEFT) || jx < -32) {
if (jlKeyDown(JL_KEY_LEFT) || jx < -32) {
mask |= 0x04u;
}
if (jlKeyDown(KEY_RIGHT) || jx > 32) {
if (jlKeyDown(JL_KEY_RIGHT) || jx > 32) {
mask |= 0x08u;
}
if (joyFire()) {
@ -1030,11 +1030,12 @@ int main(void) {
// gets one; whatever does not fit draws interpreted). The 68k ports'
// cels are far larger (9-13 KB each) and their JIT is fast, so they
// keep a modest arena and compile at level entry.
#if defined(JOEYLIB_PLATFORM_IIGS) || defined(JOEYLIB_CPU_X86)
#if defined(JOEYLIB_PLATFORM_IIGS) || defined(JL_CPU_X86)
config.codegenBytes = 256UL * 1024;
#else
config.codegenBytes = 160UL * 1024;
#endif
config.noAudio = false;
if (!jlInit(&config)) {
return 1;
}
@ -1083,7 +1084,7 @@ int main(void) {
// here -- abandons the game for the title, and on the title it
// leaves the program (the C64 had nowhere else to go).
if (gGame.state != ST_STATE_JINGLE && gGame.state != ST_STATE_GAME_OVER && gGame.state != ST_STATE_NAME_ENTRY) {
if (jlKeyDown(KEY_LSHIFT) || jlKeyDown(KEY_RSHIFT)) {
if (jlKeyDown(JL_KEY_LSHIFT) || jlKeyDown(JL_KEY_RSHIFT)) {
if (!gGame.paused) {
gGame.paused = true;
stAudioSilence();
@ -1103,7 +1104,7 @@ int main(void) {
stAudioMusic(ST_SONG_INTRO, true); // $46B5
}
}
if (jlKeyPressed(KEY_ESCAPE)) {
if (jlKeyPressed(JL_KEY_ESCAPE)) {
if (gGame.state == ST_STATE_TITLE) {
break;
}

View file

@ -431,7 +431,6 @@ void stAudioTick(void) {
bool stAudioInit(void) {
bool ok;
(void)jlAudioInit();
gSpeechStage = (uint8_t *)jlAlloc(ST_SPEECH_STAGE_BYTES);
ok = (gSpeechStage != 0) && speechLoad();
gSfx.voice = ST_VOICE_SFX;
@ -454,7 +453,9 @@ void stAudioShutdown(void) {
voiceOff(&gSfx);
voiceOff(&gTone);
jlAudioNoise(0u, ST_ATTEN_OFF);
jlAudioShutdown();
// The engine runs until jlShutdown: the speech stream stops before the
// buffers it is filled from go.
jlAudioStopSfx(ST_SPEECH_SLOT);
jlFree(gSpeechStage);
gSpeechStage = 0;
jlFree(gSpeechBlob);

View file

@ -43,7 +43,7 @@
// Room for the baked cab/passenger/exhaust set (36) plus the largest
// level's hook cels (I: 30) at once.
#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 jlSpriteBackupLength(uint8_t, ST_SPRITE_TILES, ST_SPRITE_TILES)
#define ST_SPRITE_PX (8 * ST_SPRITE_TILES)
// Palette slots the title logo animation owns. The title screen draws
// in C64 colours 0, 1, 2, 6, 7, 11 and 12 only (plus colour 5 for the
@ -321,7 +321,7 @@ static bool celCreate(StSpriteCacheT *e, const uint8_t *bm, uint8_t multi, uint8
const uint8_t *chunk;
tx--;
chunk = &blob[(ty * ST_SPRITE_TILES + tx) * TILE_BYTES];
chunk = &blob[(ty * ST_SPRITE_TILES + tx) * JL_TILE_BYTES];
tileFromChunky(&tile, chunk);
jlTilePaste(gRender.scratch, tx, ty, &tile);
if (tileHasPixels(chunk)) {
@ -338,11 +338,11 @@ static bool celCreate(StSpriteCacheT *e, const uint8_t *bm, uint8_t multi, uint8
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));
e->offX = (uint8_t)(txMin * JL_TILE_PIXELS_PER_SIDE);
e->offY = (uint8_t)(tyMin * JL_TILE_PIXELS_PER_SIDE);
e->w = (uint8_t)((txMax - txMin + 1u) * JL_TILE_PIXELS_PER_SIDE);
e->h = (uint8_t)((tyMax - tyMin + 1u) * JL_TILE_PIXELS_PER_SIDE);
e->sprite = jlSpriteCreateFromSurface(gRender.scratch, (int16_t)e->offX, (int16_t)e->offY, (uint8_t)(e->w / JL_TILE_PIXELS_PER_SIDE), (uint8_t)(e->h / JL_TILE_PIXELS_PER_SIDE));
return e->sprite != 0;
}
@ -490,11 +490,11 @@ static bool dirtyUnderSprite(int16_t px, int16_t py) {
uint8_t colFirst;
uint8_t colLast;
if (x1 > SURFACE_WIDTH) {
x1 = SURFACE_WIDTH;
if (x1 > JL_SURFACE_WIDTH) {
x1 = JL_SURFACE_WIDTH;
}
if (y1 > SURFACE_HEIGHT) {
y1 = SURFACE_HEIGHT;
if (y1 > JL_SURFACE_HEIGHT) {
y1 = JL_SURFACE_HEIGHT;
}
// spriteOnScreen has already rejected anything fully off the stage,
// so only the top/left overhang needs clamping to row/column 0.
@ -546,15 +546,15 @@ static void dropCache(void) {
// repaint to draw the flip book.
static bool logoBuildTile(const StSimT *sim) {
uint8_t slotOfMask[1u << ST_LOGO_FRAMES];
uint8_t chunky[TILE_BYTES];
uint8_t chunky[JL_TILE_BYTES];
uint8_t row;
uint8_t col;
memset(slotOfMask, 0, sizeof(slotOfMask));
gRender.logoSlotCount = 0u;
for (row = 0u; row < TILE_PIXELS_PER_SIDE; row++) {
uint8_t *out = &chunky[row * TILE_BYTES_PER_ROW];
for (col = 0u; col < TILE_PIXELS_PER_SIDE; col++) {
for (row = 0u; row < JL_TILE_PIXELS_PER_SIDE; row++) {
uint8_t *out = &chunky[row * JL_TILE_BYTES_PER_ROW];
for (col = 0u; col < JL_TILE_PIXELS_PER_SIDE; col++) {
uint8_t mask = 0u;
uint8_t f;
uint8_t slot;
@ -606,7 +606,7 @@ static uint8_t logoFrameIndex(const StSimT *sim) {
// slot follows their own colour, which the cycle sets one step behind
// the logo's (the title's sprite table overwrites it on entry).
static void logoPaletteApply(const StSimT *sim, uint8_t frame) {
uint16_t pal[SURFACE_COLORS_PER_PALETTE];
uint16_t pal[JL_SURFACE_COLORS_PER_PALETTE];
uint16_t on = kC64Palette[sim->logoColor & 15u];
uint16_t off = kC64Palette[sim->bgColor & 15u];
uint8_t k;
@ -678,7 +678,7 @@ static void pasteCell(jlSurfaceT *stage, const StSimT *sim, uint16_t cell, uint8
// 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 pal[JL_SURFACE_COLORS_PER_PALETTE];
uint16_t color;
uint8_t behind;
uint8_t ring;
@ -696,7 +696,7 @@ static void starPaletteApply(void) {
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;
return px < JL_SURFACE_WIDTH && py < JL_SURFACE_HEIGHT && px > -ST_SPRITE_W && py > -(int16_t)ST_SPRITE_PX;
}
@ -704,15 +704,15 @@ static bool spriteOnScreen(int16_t px, int16_t py) {
// this port's jlTileT: a straight copy on the chunky ports, drawn and
// snapped through the scratch surface on the planar ones.
static void tileFromChunky(jlTileT *out, const uint8_t *chunky) {
#if defined(JOEYLIB_NATIVE_CHUNKY)
memcpy(out->pixels, chunky, TILE_BYTES);
#if defined(JL_NATIVE_CHUNKY)
memcpy(out->pixels, chunky, JL_TILE_BYTES);
#else
uint8_t row;
uint8_t k;
for (row = 0u; row < TILE_PIXELS_PER_SIDE; row++) {
for (k = 0u; k < TILE_BYTES_PER_ROW; k++) {
uint8_t b = chunky[row * TILE_BYTES_PER_ROW + k];
for (row = 0u; row < JL_TILE_PIXELS_PER_SIDE; row++) {
for (k = 0u; k < JL_TILE_BYTES_PER_ROW; k++) {
uint8_t b = chunky[row * JL_TILE_BYTES_PER_ROW + k];
jlDrawPixel(gRender.scratch, (int16_t)(k * 2u), (int16_t)row, (uint8_t)(b >> 4));
jlDrawPixel(gRender.scratch, (int16_t)(k * 2u + 1u), (int16_t)row, (uint8_t)(b & 0x0Fu));
}
@ -726,7 +726,7 @@ static void tileFromChunky(jlTileT *out, const uint8_t *chunky) {
static bool tileHasPixels(const uint8_t *chunky) {
uint8_t k;
for (k = 0u; k < TILE_BYTES; k++) {
for (k = 0u; k < JL_TILE_BYTES; k++) {
if (chunky[k] != 0u) {
return true;
}
@ -831,7 +831,7 @@ void stRenderFrame(jlSurfaceT *stage, StSimT *sim) {
// 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);
d->trusted = e->compiled && px >= 0 && py >= 0 && px <= (int16_t)(JL_SURFACE_WIDTH - e->w) && py <= (int16_t)(JL_SURFACE_HEIGHT - e->h);
if (d->trusted) {
jlSpriteSaveAndDrawTrusted(stage, e->sprite, px, py, &gRender.backup[k]);
} else {
@ -874,12 +874,12 @@ void stRenderInit(jlSurfaceT *stage) {
gRender.backup[k].bytes = (backupMem == 0) ? 0 : backupMem + (uint16_t)k * ST_SPRITE_BACKUP_BYTES;
}
jlPaletteSet(stage, 0u, kC64Palette);
jlScbSetRange(stage, 0u, (uint16_t)(SURFACE_HEIGHT - 1u), 0u);
jlScbSetRange(stage, 0u, (uint16_t)(JL_SURFACE_HEIGHT - 1u), 0u);
jlSurfaceClear(stage, 0u);
gRender.scratch = jlSurfaceCreate();
if (gRender.scratch != 0) {
jlPaletteSet(gRender.scratch, 0u, kC64Palette);
jlScbSetRange(gRender.scratch, 0u, (uint16_t)(SURFACE_HEIGHT - 1u), 0u);
jlScbSetRange(gRender.scratch, 0u, (uint16_t)(JL_SURFACE_HEIGHT - 1u), 0u);
jlSurfaceClear(gRender.scratch, 0u);
}
}
@ -1222,7 +1222,7 @@ static bool prewarmReport(uint16_t expect) {
ST_LOG("spacetaxi: ! only %u of %u cels built", used, expect);
return false;
}
#if defined(JOEYLIB_SPRITE_INTERPRETED)
#if defined(JL_SPRITE_INTERPRETED)
// A port with no sprite emitter (joey/platform.h) interprets every cel
// by design: none compiled is how it always runs.
return true;

View file

@ -95,6 +95,7 @@ int main(void) {
uint8_t k;
config.codegenBytes = 160UL * 1024;
config.noAudio = true;
if (!jlInit(&config)) {
printf("jlInit failed\n");
return 1;