More work on STAXI.

This commit is contained in:
Scott Duensing 2026-09-06 18:22:45 -05:00
parent 91629dc5eb
commit cd2c5e6b09
130 changed files with 6859 additions and 3367 deletions

View file

@ -0,0 +1,160 @@
#!/usr/bin/env python3
# genC64Data.py -- emit the C64 game data the JoeyLib Space Taxi port
# needs at runtime as C source: the custom charset (rendering AND the
# exact sprite-to-background collision bits), the standard sprite
# bitmaps (cab, passenger, flame, warp, death, intro star, leaving
# cels), the demo-mode RNG table ($446A), the SID SFX programs (the
# 9-byte blocks fed to $42E9), the engine-flame pointer table ($6DB0)
# and the level-intro star velocity tables ($450C/$4513).
#
# Everything comes straight out of stuff/spacetaxi/raw.bin (a VICE
# "bank ram" dump, 2-byte load-address header) so the port has one
# source of truth: the original bytes. Output:
# examples/spacetaxi/stC64Data.c / stC64Data.h (committed, do not edit)
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
RAW = os.path.join(HERE, "..", "..", "..", "stuff", "spacetaxi", "raw.bin")
OUT_C = os.path.join(HERE, "..", "stC64Data.c")
OUT_H = os.path.join(HERE, "..", "stC64Data.h")
SPRITE_PTR_FIRST = 0xC0
SPRITE_PTR_LAST = 0xEC # $E9-$EC = passengers-leaving cels
SPRITE_BYTES = 63
# (enum name, address of the 9-byte program, comment)
SFX = [
("ST_SFX_GEAR", 0x6410, "landing gear toggle ($63DD)"),
("ST_SFX_CASH", 0x6DBB, "refuel ka-ching ($6E5B)"),
("ST_SFX_FUEL_LOW", 0x6DC4, "fuel below 3 cells ($6EBD)"),
("ST_SFX_FUEL_FULL", 0x6DCD, "tank full ($6E9E)"),
("ST_SFX_LAND_HARD", 0x6DD6, "hard landing ($6520)"),
("ST_SFX_IMPACT", 0x6DDF, "wreck hits the floor ($6AE1)"),
("ST_SFX_LAND_SOFT", 0x6DE8, "soft landing ($650F)"),
("ST_SFX_CRASH", 0x6DF1, "crash start ($6A56)"),
]
def load_raw():
with open(RAW, "rb") as fp:
raw = fp.read()
if len(raw) == 0x10002:
raw = raw[2:]
if len(raw) != 0x10000:
sys.exit(f"{RAW}: unexpected size {len(raw)}")
return raw
def c_bytes(data, per_line=16, indent=" "):
lines = []
for i in range(0, len(data), per_line):
chunk = data[i:i + per_line]
lines.append(indent + ", ".join(f"0x{b:02X}" for b in chunk) + ",")
return "\n".join(lines)
def main():
raw = load_raw()
sprite_count = SPRITE_PTR_LAST - SPRITE_PTR_FIRST + 1
h = []
h.append("// Generated by assets/genC64Data.py from stuff/spacetaxi/raw.bin.")
h.append("// Do not hand-edit; re-run the generator.")
h.append("")
h.append("#ifndef ST_C64_DATA_H")
h.append("#define ST_C64_DATA_H")
h.append("")
h.append("#include <stdint.h>")
h.append("")
h.append("// The game's custom character set ($2800-$2FFF): 256 glyphs x 8 rows,")
h.append("// bit 7 = leftmost pixel. Used to paint the screen AND for the")
h.append("// sprite-to-background collision test (a set bit is background data).")
h.append("#define ST_CHARSET_CHARS 256u")
h.append("extern const uint8_t kStCharset[ST_CHARSET_CHARS][8];")
h.append("")
h.append("// Standard sprite bitmaps (VIC bank 0, block ptr * 64). 21 rows x 3")
h.append("// bytes. Index = ptr - ST_SPRITE_PTR_FIRST.")
h.append(f"#define ST_SPRITE_PTR_FIRST 0x{SPRITE_PTR_FIRST:02X}u")
h.append(f"#define ST_SPRITE_PTR_LAST 0x{SPRITE_PTR_LAST:02X}u")
h.append(f"#define ST_SPRITE_COUNT {sprite_count}u")
h.append(f"#define ST_SPRITE_BYTES {SPRITE_BYTES}u")
h.append("extern const uint8_t kStSpriteBitmaps[ST_SPRITE_COUNT][ST_SPRITE_BYTES];")
h.append("")
h.append("// Demo-mode RNG lookup ($446A): rnd = table[t1] + t2 (see stSim.c).")
h.append("#define ST_RNG_TABLE_SIZE 64u")
h.append("extern const uint8_t kStRngTable[ST_RNG_TABLE_SIZE];")
h.append("")
h.append("// SID SFX programs, 9 bytes each, in $42E9 order: freq lo, freq hi,")
h.append("// pulse lo, pulse hi, control, AD, SR, release ticks, voice index.")
h.append("typedef enum {")
for name, addr, comment in SFX:
h.append(f" {name}, // ${addr:04X} {comment}")
h.append(" ST_SFX_COUNT")
h.append("} StSfxE;")
h.append("#define ST_SFX_PROGRAM_BYTES 9u")
h.append("extern const uint8_t kStSfxPrograms[ST_SFX_COUNT][ST_SFX_PROGRAM_BYTES];")
h.append("")
h.append("// Engine-flame cel pointer by direction mask ($6DB0, 1=UP 2=DOWN 4=LEFT")
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("#endif")
c = []
c.append("// Generated by assets/genC64Data.py from stuff/spacetaxi/raw.bin.")
c.append("// Do not hand-edit; re-run the generator.")
c.append("")
c.append('#include "stC64Data.h"')
c.append("")
c.append("const uint8_t kStCharset[ST_CHARSET_CHARS][8] = {")
for ch in range(256):
rows = raw[0x2800 + ch * 8:0x2800 + ch * 8 + 8]
c.append(" { " + ", ".join(f"0x{b:02X}" for b in rows) + " }, // $" + f"{ch:02X}")
c.append("};")
c.append("")
c.append("const uint8_t kStSpriteBitmaps[ST_SPRITE_COUNT][ST_SPRITE_BYTES] = {")
for ptr in range(SPRITE_PTR_FIRST, SPRITE_PTR_LAST + 1):
base = ptr * 64
c.append(f" {{ // ptr ${ptr:02X}")
for row in range(21):
b = raw[base + row * 3:base + row * 3 + 3]
c.append(" " + ", ".join(f"0x{x:02X}" for x in b) + ",")
c.append(" },")
c.append("};")
c.append("")
c.append("const uint8_t kStRngTable[ST_RNG_TABLE_SIZE] = {")
c.append(c_bytes(raw[0x446A:0x446A + 64]))
c.append("};")
c.append("")
c.append("const uint8_t kStSfxPrograms[ST_SFX_COUNT][ST_SFX_PROGRAM_BYTES] = {")
for name, addr, comment in SFX:
b = raw[addr:addr + 9]
c.append(" { " + ", ".join(f"0x{x:02X}" for x in b) + " }, // " + name)
c.append("};")
c.append("")
flame = list(raw[0x6DB0:0x6DB0 + 11]) + [0] * 5
c.append("const uint8_t kStFlameCelByDirMask[16] = {")
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("")
with open(OUT_H, "w", encoding="ascii") as fp:
fp.write("\n".join(h) + "\n")
with open(OUT_C, "w", encoding="ascii") as fp:
fp.write("\n".join(c) + "\n")
print(f"wrote {OUT_H} and {OUT_C}")
if __name__ == "__main__":
main()

View file

@ -1,13 +1,13 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Embed the C64 attract-demo recorded input streams (the four # Embed the C64 attract-demo recorded input streams (the four
# "SPACETAXI 0x" disk files -- RLE [joystick mask][tick count] pairs; # "SPACETAXI 0x" disk files -- RLE [joystick mask][tick count] pairs;
# mask bit0=UP bit1=DOWN bit2=LEFT bit3=RIGHT, bit7 always set, no # mask bit0=UP bit1=DOWN bit2=LEFT bit3=RIGHT bit4=FIRE, bit7 always
# fire bit -- which is why every recorded ride ends in a crash: the # set) as C arrays for stDemoStreams.h.
# demo cab can never lower its gear) as C arrays for stDemoStreams.h.
# #
# Every stream file is odd-length: the $9936 saver's end=$3F+3 # Every stream file is odd-length: the $9936 saver appends one
# off-by-one appends one dangling byte past the last complete pair, # dangling byte past the last complete pair. It is kept: the C64 reads
# trimmed here on all four. # it (and whatever follows in the buffer) when a ride outlasts its
# recording.
# #
# Rotation order is H -> W -> T -> X ($6FE2/$50C1 trace) with file # Rotation order is H -> W -> T -> X ($6FE2/$50C1 trace) with file
# mapping H=01, T=02, W=03, X=04 and demo levels H=8, W=23, T=20, # mapping H=01, T=02, W=03, X=04 and demo levels H=8, W=23, T=20,
@ -38,15 +38,26 @@ def main():
entries = [] entries = []
for fname, sym, level, letter in STREAMS: for fname, sym, level, letter in STREAMS:
data = open(os.path.join(EXTRACT, fname), "rb").read() data = open(os.path.join(EXTRACT, fname), "rb").read()
if len(data) % 2 == 1: # The whole file, dangling byte included: the C64 plays past
data = data[:-1] # trim the saver's dangling byte # the last pair into whatever the buffer held before.
f.write(f"// {letter}: {fname} ({len(data)} bytes after trim)\n") f.write(f"// {letter}: {fname} ({len(data)} bytes)\n")
f.write(f"static const uint8_t {sym}[{len(data)}] = {{\n") f.write(f"static const uint8_t {sym}[{len(data)}] = {{\n")
for i in range(0, len(data), 12): for i in range(0, len(data), 12):
row = ", ".join(f"0x{b:02X}" for b in data[i:i + 12]) row = ", ".join(f"0x{b:02X}" for b in data[i:i + 12])
f.write(f" {row},\n") f.write(f" {row},\n")
f.write("};\n\n") f.write("};\n\n")
entries.append((sym, len(data), level)) entries.append((sym, len(data), level))
# The playback buffer at $0902 as the boot leaves it (demo H
# loaded over older RAM): recordings shorter than the previous one
# leave its tail in place, and the game reads on into it.
raw = open(os.path.join(HERE, "..", "..", "..", "stuff", "spacetaxi", "raw.bin"), "rb").read()[2:]
buf = raw[0x0902:0x0902 + 640]
f.write("#define ST_DEMO_BUFFER_BYTES 640u\n")
f.write("static const uint8_t kDemoBufferInit[ST_DEMO_BUFFER_BYTES] = {\n")
for i in range(0, len(buf), 12):
row = ", ".join(f"0x{b:02X}" for b in buf[i:i + 12])
f.write(f" {row},\n")
f.write("};\n\n")
f.write("typedef struct {\n") f.write("typedef struct {\n")
f.write(" const uint8_t *stream;\n") f.write(" const uint8_t *stream;\n")
f.write(" uint16_t length;\n") f.write(" uint16_t length;\n")

Some files were not shown because too many files have changed in this diff Show more