diff --git a/.gitignore b/.gitignore
index c50f839..5c78102 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,10 +42,25 @@ Thumbs.db
*.SND
*.ANI
+# Baked assets: sprite banks (.spr), precompiled sprite banks (.spc),
+# tile banks (.tbk) and level files -- all regenerated by the platform
+# makefiles from the PNG/ROM sources
+*.spr
+*.spc
+*.tbk
+examples/spacetaxi/generated/
+
+# Host tool binaries built from source
+tools/stsim/stsim
+
+# MAME droppings (written next to the work dir by the verify scripts)
+cfg/
+nvram/
+
# Crap I added
stuff/*
docs/*
-tools/__pycache__/*
+__pycache__/
# AGI game data (Sierra/fan-made AGI games used for testing; never committed)
examples/agi/gamedata/
diff --git a/cfg/default.cfg b/cfg/default.cfg
deleted file mode 100644
index 02f0986..0000000
--- a/cfg/default.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/cfg/x68000.cfg b/cfg/x68000.cfg
deleted file mode 100644
index 4d58885..0000000
--- a/cfg/x68000.cfg
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/examples/spacetaxi/generated/amiga/font.tbk b/examples/spacetaxi/generated/amiga/font.tbk
deleted file mode 100644
index 61466ad..0000000
Binary files a/examples/spacetaxi/generated/amiga/font.tbk and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level01.dat b/examples/spacetaxi/generated/amiga/levels/level01.dat
deleted file mode 100644
index 0c762d8..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level01.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level02.dat b/examples/spacetaxi/generated/amiga/levels/level02.dat
deleted file mode 100644
index dbebde5..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level02.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level03.dat b/examples/spacetaxi/generated/amiga/levels/level03.dat
deleted file mode 100644
index 41ae91b..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level03.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level04.dat b/examples/spacetaxi/generated/amiga/levels/level04.dat
deleted file mode 100644
index 55b6b37..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level04.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level05.dat b/examples/spacetaxi/generated/amiga/levels/level05.dat
deleted file mode 100644
index 02e66be..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level05.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level06.dat b/examples/spacetaxi/generated/amiga/levels/level06.dat
deleted file mode 100644
index f744d27..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level06.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level07.dat b/examples/spacetaxi/generated/amiga/levels/level07.dat
deleted file mode 100644
index 0eb4025..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level07.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level08.dat b/examples/spacetaxi/generated/amiga/levels/level08.dat
deleted file mode 100644
index 08cdd01..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level08.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level09.dat b/examples/spacetaxi/generated/amiga/levels/level09.dat
deleted file mode 100644
index 6451796..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level09.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level10.dat b/examples/spacetaxi/generated/amiga/levels/level10.dat
deleted file mode 100644
index afc00aa..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level10.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level11.dat b/examples/spacetaxi/generated/amiga/levels/level11.dat
deleted file mode 100644
index 8099ce8..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level11.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level12.dat b/examples/spacetaxi/generated/amiga/levels/level12.dat
deleted file mode 100644
index 50b1b33..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level12.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level13.dat b/examples/spacetaxi/generated/amiga/levels/level13.dat
deleted file mode 100644
index 3eb8f9d..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level13.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level14.dat b/examples/spacetaxi/generated/amiga/levels/level14.dat
deleted file mode 100644
index 3e5b7c3..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level14.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level15.dat b/examples/spacetaxi/generated/amiga/levels/level15.dat
deleted file mode 100644
index 7607e7d..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level15.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level16.dat b/examples/spacetaxi/generated/amiga/levels/level16.dat
deleted file mode 100644
index 11f416d..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level16.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level17.dat b/examples/spacetaxi/generated/amiga/levels/level17.dat
deleted file mode 100644
index 457defe..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level17.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level18.dat b/examples/spacetaxi/generated/amiga/levels/level18.dat
deleted file mode 100644
index b682e46..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level18.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level19.dat b/examples/spacetaxi/generated/amiga/levels/level19.dat
deleted file mode 100644
index 7a90761..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level19.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level20.dat b/examples/spacetaxi/generated/amiga/levels/level20.dat
deleted file mode 100644
index 396008f..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level20.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level21.dat b/examples/spacetaxi/generated/amiga/levels/level21.dat
deleted file mode 100644
index 7d6c4c9..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level21.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level22.dat b/examples/spacetaxi/generated/amiga/levels/level22.dat
deleted file mode 100644
index 75c4d17..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level22.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level23.dat b/examples/spacetaxi/generated/amiga/levels/level23.dat
deleted file mode 100644
index 23b1bec..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level23.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/level24.dat b/examples/spacetaxi/generated/amiga/levels/level24.dat
deleted file mode 100644
index 13dca31..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/level24.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/levels/title.dat b/examples/spacetaxi/generated/amiga/levels/title.dat
deleted file mode 100644
index 83ca545..0000000
Binary files a/examples/spacetaxi/generated/amiga/levels/title.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/sprites/sprites.spc b/examples/spacetaxi/generated/amiga/sprites/sprites.spc
deleted file mode 100644
index e256be6..0000000
--- a/examples/spacetaxi/generated/amiga/sprites/sprites.spc
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:b5ccb0dfde5ec5a37c9a9b2c69139cc4f0dc6d4d11ecc40f1e4132d55b604e9b
-size 393270
diff --git a/examples/spacetaxi/generated/amiga/sprites/sprites.spr b/examples/spacetaxi/generated/amiga/sprites/sprites.spr
deleted file mode 100644
index eaaadbc..0000000
--- a/examples/spacetaxi/generated/amiga/sprites/sprites.spr
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:ac44a1fa0a8ac14582df6c018ecc52701c59d03797b9c22437242776614fc23a
-size 15884
diff --git a/examples/spacetaxi/generated/amiga/tiles/tbank0.tbk b/examples/spacetaxi/generated/amiga/tiles/tbank0.tbk
deleted file mode 100644
index a7c5a92..0000000
Binary files a/examples/spacetaxi/generated/amiga/tiles/tbank0.tbk and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/tiles/tbank1.tbk b/examples/spacetaxi/generated/amiga/tiles/tbank1.tbk
deleted file mode 100644
index 1b1b788..0000000
Binary files a/examples/spacetaxi/generated/amiga/tiles/tbank1.tbk and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/tiles/tbank2.tbk b/examples/spacetaxi/generated/amiga/tiles/tbank2.tbk
deleted file mode 100644
index 58c3ff2..0000000
Binary files a/examples/spacetaxi/generated/amiga/tiles/tbank2.tbk and /dev/null differ
diff --git a/examples/spacetaxi/generated/amiga/tiles/tilebank0.tbk b/examples/spacetaxi/generated/amiga/tiles/tilebank0.tbk
deleted file mode 100644
index 27bdef1..0000000
Binary files a/examples/spacetaxi/generated/amiga/tiles/tilebank0.tbk and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/font.tbk b/examples/spacetaxi/generated/atarist/font.tbk
deleted file mode 100644
index d77d9d1..0000000
Binary files a/examples/spacetaxi/generated/atarist/font.tbk and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level01.dat b/examples/spacetaxi/generated/atarist/levels/level01.dat
deleted file mode 100644
index 0c762d8..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level01.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level02.dat b/examples/spacetaxi/generated/atarist/levels/level02.dat
deleted file mode 100644
index dbebde5..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level02.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level03.dat b/examples/spacetaxi/generated/atarist/levels/level03.dat
deleted file mode 100644
index 41ae91b..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level03.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level04.dat b/examples/spacetaxi/generated/atarist/levels/level04.dat
deleted file mode 100644
index 55b6b37..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level04.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level05.dat b/examples/spacetaxi/generated/atarist/levels/level05.dat
deleted file mode 100644
index 02e66be..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level05.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level06.dat b/examples/spacetaxi/generated/atarist/levels/level06.dat
deleted file mode 100644
index f744d27..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level06.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level07.dat b/examples/spacetaxi/generated/atarist/levels/level07.dat
deleted file mode 100644
index 0eb4025..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level07.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level08.dat b/examples/spacetaxi/generated/atarist/levels/level08.dat
deleted file mode 100644
index 08cdd01..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level08.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level09.dat b/examples/spacetaxi/generated/atarist/levels/level09.dat
deleted file mode 100644
index 6451796..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level09.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level10.dat b/examples/spacetaxi/generated/atarist/levels/level10.dat
deleted file mode 100644
index afc00aa..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level10.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level11.dat b/examples/spacetaxi/generated/atarist/levels/level11.dat
deleted file mode 100644
index 8099ce8..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level11.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level12.dat b/examples/spacetaxi/generated/atarist/levels/level12.dat
deleted file mode 100644
index 50b1b33..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level12.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level13.dat b/examples/spacetaxi/generated/atarist/levels/level13.dat
deleted file mode 100644
index 3eb8f9d..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level13.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level14.dat b/examples/spacetaxi/generated/atarist/levels/level14.dat
deleted file mode 100644
index 3e5b7c3..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level14.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level15.dat b/examples/spacetaxi/generated/atarist/levels/level15.dat
deleted file mode 100644
index 7607e7d..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level15.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level16.dat b/examples/spacetaxi/generated/atarist/levels/level16.dat
deleted file mode 100644
index 11f416d..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level16.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level17.dat b/examples/spacetaxi/generated/atarist/levels/level17.dat
deleted file mode 100644
index 457defe..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level17.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level18.dat b/examples/spacetaxi/generated/atarist/levels/level18.dat
deleted file mode 100644
index b682e46..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level18.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level19.dat b/examples/spacetaxi/generated/atarist/levels/level19.dat
deleted file mode 100644
index 7a90761..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level19.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level20.dat b/examples/spacetaxi/generated/atarist/levels/level20.dat
deleted file mode 100644
index 396008f..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level20.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level21.dat b/examples/spacetaxi/generated/atarist/levels/level21.dat
deleted file mode 100644
index 7d6c4c9..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level21.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level22.dat b/examples/spacetaxi/generated/atarist/levels/level22.dat
deleted file mode 100644
index 75c4d17..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level22.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level23.dat b/examples/spacetaxi/generated/atarist/levels/level23.dat
deleted file mode 100644
index 23b1bec..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level23.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/level24.dat b/examples/spacetaxi/generated/atarist/levels/level24.dat
deleted file mode 100644
index 13dca31..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/level24.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/levels/title.dat b/examples/spacetaxi/generated/atarist/levels/title.dat
deleted file mode 100644
index 83ca545..0000000
Binary files a/examples/spacetaxi/generated/atarist/levels/title.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/sprites/sprites.spc b/examples/spacetaxi/generated/atarist/sprites/sprites.spc
deleted file mode 100644
index 997682a..0000000
--- a/examples/spacetaxi/generated/atarist/sprites/sprites.spc
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:96333d839036e8af22f052107380f8ee9692d37b4f7de9044c070ce8194dc9fe
-size 697490
diff --git a/examples/spacetaxi/generated/atarist/sprites/sprites.spr b/examples/spacetaxi/generated/atarist/sprites/sprites.spr
deleted file mode 100644
index eaaadbc..0000000
--- a/examples/spacetaxi/generated/atarist/sprites/sprites.spr
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:ac44a1fa0a8ac14582df6c018ecc52701c59d03797b9c22437242776614fc23a
-size 15884
diff --git a/examples/spacetaxi/generated/atarist/tiles/tbank0.tbk b/examples/spacetaxi/generated/atarist/tiles/tbank0.tbk
deleted file mode 100644
index d49247a..0000000
Binary files a/examples/spacetaxi/generated/atarist/tiles/tbank0.tbk and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/tiles/tbank1.tbk b/examples/spacetaxi/generated/atarist/tiles/tbank1.tbk
deleted file mode 100644
index c4a9cb1..0000000
Binary files a/examples/spacetaxi/generated/atarist/tiles/tbank1.tbk and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/tiles/tbank2.tbk b/examples/spacetaxi/generated/atarist/tiles/tbank2.tbk
deleted file mode 100644
index 26619da..0000000
Binary files a/examples/spacetaxi/generated/atarist/tiles/tbank2.tbk and /dev/null differ
diff --git a/examples/spacetaxi/generated/atarist/tiles/tilebank0.tbk b/examples/spacetaxi/generated/atarist/tiles/tilebank0.tbk
deleted file mode 100644
index a943d40..0000000
Binary files a/examples/spacetaxi/generated/atarist/tiles/tilebank0.tbk and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/font.tbk b/examples/spacetaxi/generated/dos/font.tbk
deleted file mode 100644
index 01ce59e..0000000
Binary files a/examples/spacetaxi/generated/dos/font.tbk and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level01.dat b/examples/spacetaxi/generated/dos/levels/level01.dat
deleted file mode 100644
index 0c762d8..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level01.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level02.dat b/examples/spacetaxi/generated/dos/levels/level02.dat
deleted file mode 100644
index dbebde5..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level02.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level03.dat b/examples/spacetaxi/generated/dos/levels/level03.dat
deleted file mode 100644
index 41ae91b..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level03.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level04.dat b/examples/spacetaxi/generated/dos/levels/level04.dat
deleted file mode 100644
index 55b6b37..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level04.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level05.dat b/examples/spacetaxi/generated/dos/levels/level05.dat
deleted file mode 100644
index 02e66be..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level05.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level06.dat b/examples/spacetaxi/generated/dos/levels/level06.dat
deleted file mode 100644
index f744d27..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level06.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level07.dat b/examples/spacetaxi/generated/dos/levels/level07.dat
deleted file mode 100644
index 0eb4025..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level07.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level08.dat b/examples/spacetaxi/generated/dos/levels/level08.dat
deleted file mode 100644
index 08cdd01..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level08.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level09.dat b/examples/spacetaxi/generated/dos/levels/level09.dat
deleted file mode 100644
index 6451796..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level09.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level10.dat b/examples/spacetaxi/generated/dos/levels/level10.dat
deleted file mode 100644
index afc00aa..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level10.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level11.dat b/examples/spacetaxi/generated/dos/levels/level11.dat
deleted file mode 100644
index 8099ce8..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level11.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level12.dat b/examples/spacetaxi/generated/dos/levels/level12.dat
deleted file mode 100644
index 50b1b33..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level12.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level13.dat b/examples/spacetaxi/generated/dos/levels/level13.dat
deleted file mode 100644
index 3eb8f9d..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level13.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level14.dat b/examples/spacetaxi/generated/dos/levels/level14.dat
deleted file mode 100644
index 3e5b7c3..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level14.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level15.dat b/examples/spacetaxi/generated/dos/levels/level15.dat
deleted file mode 100644
index 7607e7d..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level15.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level16.dat b/examples/spacetaxi/generated/dos/levels/level16.dat
deleted file mode 100644
index 11f416d..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level16.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level17.dat b/examples/spacetaxi/generated/dos/levels/level17.dat
deleted file mode 100644
index 457defe..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level17.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level18.dat b/examples/spacetaxi/generated/dos/levels/level18.dat
deleted file mode 100644
index b682e46..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level18.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level19.dat b/examples/spacetaxi/generated/dos/levels/level19.dat
deleted file mode 100644
index 7a90761..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level19.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level20.dat b/examples/spacetaxi/generated/dos/levels/level20.dat
deleted file mode 100644
index 396008f..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level20.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level21.dat b/examples/spacetaxi/generated/dos/levels/level21.dat
deleted file mode 100644
index 7d6c4c9..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level21.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level22.dat b/examples/spacetaxi/generated/dos/levels/level22.dat
deleted file mode 100644
index 75c4d17..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level22.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level23.dat b/examples/spacetaxi/generated/dos/levels/level23.dat
deleted file mode 100644
index 23b1bec..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level23.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/level24.dat b/examples/spacetaxi/generated/dos/levels/level24.dat
deleted file mode 100644
index 13dca31..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/level24.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/levels/title.dat b/examples/spacetaxi/generated/dos/levels/title.dat
deleted file mode 100644
index 83ca545..0000000
Binary files a/examples/spacetaxi/generated/dos/levels/title.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/sprites/sprites.spc b/examples/spacetaxi/generated/dos/sprites/sprites.spc
deleted file mode 100644
index 3141d20..0000000
--- a/examples/spacetaxi/generated/dos/sprites/sprites.spc
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:6e12261031e8b47522e506a34f099509c313e063d57b3d8b7dc95c215069def2
-size 133445
diff --git a/examples/spacetaxi/generated/dos/sprites/sprites.spr b/examples/spacetaxi/generated/dos/sprites/sprites.spr
deleted file mode 100644
index eaaadbc..0000000
--- a/examples/spacetaxi/generated/dos/sprites/sprites.spr
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:ac44a1fa0a8ac14582df6c018ecc52701c59d03797b9c22437242776614fc23a
-size 15884
diff --git a/examples/spacetaxi/generated/dos/tiles/tbank0.tbk b/examples/spacetaxi/generated/dos/tiles/tbank0.tbk
deleted file mode 100644
index 63caf7c..0000000
Binary files a/examples/spacetaxi/generated/dos/tiles/tbank0.tbk and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/tiles/tbank1.tbk b/examples/spacetaxi/generated/dos/tiles/tbank1.tbk
deleted file mode 100644
index a05ddbb..0000000
Binary files a/examples/spacetaxi/generated/dos/tiles/tbank1.tbk and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/tiles/tbank2.tbk b/examples/spacetaxi/generated/dos/tiles/tbank2.tbk
deleted file mode 100644
index 89dcab9..0000000
Binary files a/examples/spacetaxi/generated/dos/tiles/tbank2.tbk and /dev/null differ
diff --git a/examples/spacetaxi/generated/dos/tiles/tilebank0.tbk b/examples/spacetaxi/generated/dos/tiles/tilebank0.tbk
deleted file mode 100644
index 5b8edfc..0000000
Binary files a/examples/spacetaxi/generated/dos/tiles/tilebank0.tbk and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/font.tbk b/examples/spacetaxi/generated/iigs/font.tbk
deleted file mode 100644
index f9883f4..0000000
Binary files a/examples/spacetaxi/generated/iigs/font.tbk and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level01.dat b/examples/spacetaxi/generated/iigs/levels/level01.dat
deleted file mode 100644
index 0c762d8..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level01.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level02.dat b/examples/spacetaxi/generated/iigs/levels/level02.dat
deleted file mode 100644
index dbebde5..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level02.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level03.dat b/examples/spacetaxi/generated/iigs/levels/level03.dat
deleted file mode 100644
index 41ae91b..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level03.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level04.dat b/examples/spacetaxi/generated/iigs/levels/level04.dat
deleted file mode 100644
index 55b6b37..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level04.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level05.dat b/examples/spacetaxi/generated/iigs/levels/level05.dat
deleted file mode 100644
index 02e66be..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level05.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level06.dat b/examples/spacetaxi/generated/iigs/levels/level06.dat
deleted file mode 100644
index f744d27..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level06.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level07.dat b/examples/spacetaxi/generated/iigs/levels/level07.dat
deleted file mode 100644
index 0eb4025..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level07.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level08.dat b/examples/spacetaxi/generated/iigs/levels/level08.dat
deleted file mode 100644
index 08cdd01..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level08.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level09.dat b/examples/spacetaxi/generated/iigs/levels/level09.dat
deleted file mode 100644
index 6451796..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level09.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level10.dat b/examples/spacetaxi/generated/iigs/levels/level10.dat
deleted file mode 100644
index afc00aa..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level10.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level11.dat b/examples/spacetaxi/generated/iigs/levels/level11.dat
deleted file mode 100644
index 8099ce8..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level11.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level12.dat b/examples/spacetaxi/generated/iigs/levels/level12.dat
deleted file mode 100644
index 50b1b33..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level12.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level13.dat b/examples/spacetaxi/generated/iigs/levels/level13.dat
deleted file mode 100644
index 3eb8f9d..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level13.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level14.dat b/examples/spacetaxi/generated/iigs/levels/level14.dat
deleted file mode 100644
index 3e5b7c3..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level14.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level15.dat b/examples/spacetaxi/generated/iigs/levels/level15.dat
deleted file mode 100644
index 7607e7d..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level15.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level16.dat b/examples/spacetaxi/generated/iigs/levels/level16.dat
deleted file mode 100644
index 11f416d..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level16.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level17.dat b/examples/spacetaxi/generated/iigs/levels/level17.dat
deleted file mode 100644
index 457defe..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level17.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level18.dat b/examples/spacetaxi/generated/iigs/levels/level18.dat
deleted file mode 100644
index b682e46..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level18.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level19.dat b/examples/spacetaxi/generated/iigs/levels/level19.dat
deleted file mode 100644
index 7a90761..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level19.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level20.dat b/examples/spacetaxi/generated/iigs/levels/level20.dat
deleted file mode 100644
index 396008f..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level20.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level21.dat b/examples/spacetaxi/generated/iigs/levels/level21.dat
deleted file mode 100644
index 7d6c4c9..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level21.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level22.dat b/examples/spacetaxi/generated/iigs/levels/level22.dat
deleted file mode 100644
index 75c4d17..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level22.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level23.dat b/examples/spacetaxi/generated/iigs/levels/level23.dat
deleted file mode 100644
index 23b1bec..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level23.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/level24.dat b/examples/spacetaxi/generated/iigs/levels/level24.dat
deleted file mode 100644
index 13dca31..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/level24.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/levels/title.dat b/examples/spacetaxi/generated/iigs/levels/title.dat
deleted file mode 100644
index 83ca545..0000000
Binary files a/examples/spacetaxi/generated/iigs/levels/title.dat and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/sprites/sprites.spc b/examples/spacetaxi/generated/iigs/sprites/sprites.spc
deleted file mode 100644
index 3b832e5..0000000
--- a/examples/spacetaxi/generated/iigs/sprites/sprites.spc
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:9762d47cb7f9b56fa2cac394c179947a0000f46140e227c24095f81e01b290c5
-size 95240
diff --git a/examples/spacetaxi/generated/iigs/sprites/sprites.spr b/examples/spacetaxi/generated/iigs/sprites/sprites.spr
deleted file mode 100644
index eaaadbc..0000000
--- a/examples/spacetaxi/generated/iigs/sprites/sprites.spr
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:ac44a1fa0a8ac14582df6c018ecc52701c59d03797b9c22437242776614fc23a
-size 15884
diff --git a/examples/spacetaxi/generated/iigs/tiles/tbank0.tbk b/examples/spacetaxi/generated/iigs/tiles/tbank0.tbk
deleted file mode 100644
index 8fd5cd9..0000000
Binary files a/examples/spacetaxi/generated/iigs/tiles/tbank0.tbk and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/tiles/tbank1.tbk b/examples/spacetaxi/generated/iigs/tiles/tbank1.tbk
deleted file mode 100644
index 8cf9956..0000000
Binary files a/examples/spacetaxi/generated/iigs/tiles/tbank1.tbk and /dev/null differ
diff --git a/examples/spacetaxi/generated/iigs/tiles/tbank2.tbk b/examples/spacetaxi/generated/iigs/tiles/tbank2.tbk
deleted file mode 100644
index 2ead5e1..0000000
Binary files a/examples/spacetaxi/generated/iigs/tiles/tbank2.tbk and /dev/null differ
diff --git a/examples/spacetaxi/stCels.c b/examples/spacetaxi/stCels.c
new file mode 100644
index 0000000..4482d01
--- /dev/null
+++ b/examples/spacetaxi/stCels.c
@@ -0,0 +1,79 @@
+// Space Taxi -- the cel bank definition + the shared cel expansion.
+// 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), the flame set in multicolour colour 7, then the
+// hires intro star in colour 7.
+
+#include "stCels.h"
+
+// mc0/mc1 are the level-wide multicolour registers, constant across all
+// 24 screens and the title (verified from the level headers): 2 and 7.
+#define MC0 0x02u
+#define MC1 0x07u
+#define CAB 0x06u // cab and passenger per-sprite colour
+#define FLM 0x07u // flame / star colour
+
+const StCelDefT kStCels[] = {
+ // Cab: flying (gear up/down x left/right), wreck cels, intro cabs.
+ { 0xC0, CAB, 1u, MC0, MC1 }, { 0xC1, CAB, 1u, MC0, MC1 },
+ { 0xDC, CAB, 1u, MC0, MC1 }, { 0xDD, CAB, 1u, MC0, MC1 },
+ { 0xCC, CAB, 1u, MC0, MC1 }, { 0xCD, CAB, 1u, MC0, MC1 },
+ { 0xCE, CAB, 1u, MC0, MC1 }, { 0xCF, CAB, 1u, MC0, MC1 },
+ { 0xD0, CAB, 1u, MC0, MC1 }, { 0xD1, CAB, 1u, MC0, MC1 },
+ { 0xE2, CAB, 1u, MC0, MC1 }, { 0xE3, CAB, 1u, MC0, MC1 },
+ { 0xE4, CAB, 1u, MC0, MC1 }, { 0xE5, CAB, 1u, MC0, MC1 },
+ { 0xE6, CAB, 1u, MC0, MC1 }, { 0xE7, CAB, 1u, MC0, MC1 },
+ { 0xE8, CAB, 1u, MC0, MC1 },
+ // Passenger: walk cels and the boarding cel.
+ { 0xC2, CAB, 1u, MC0, MC1 }, { 0xC3, CAB, 1u, MC0, MC1 },
+ { 0xC4, CAB, 1u, MC0, MC1 }, { 0xC5, CAB, 1u, MC0, MC1 },
+ { 0xC6, CAB, 1u, MC0, MC1 }, { 0xC7, CAB, 1u, MC0, MC1 },
+ { 0xC8, CAB, 1u, MC0, MC1 }, { 0xC9, CAB, 1u, MC0, MC1 },
+ { 0xCA, CAB, 1u, MC0, MC1 }, { 0xCB, CAB, 1u, MC0, MC1 },
+ { 0xD9, CAB, 1u, MC0, MC1 },
+ // Flame (exhaust) cels, colour 7.
+ { 0xD8, FLM, 1u, MC0, MC1 }, { 0xD4, FLM, 1u, MC0, MC1 },
+ { 0xD5, FLM, 1u, MC0, MC1 }, { 0xD2, FLM, 1u, MC0, MC1 },
+ { 0xD1, FLM, 1u, MC0, MC1 }, { 0xD7, 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]));
+
+
+void stCelBlob(const uint8_t *bm, uint8_t multi, uint8_t color, uint8_t mc0, uint8_t mc1, uint8_t *out) {
+ uint8_t pal[4];
+ uint8_t tx;
+ uint8_t ty;
+ uint8_t row;
+ uint8_t k;
+
+ pal[0] = 0u;
+ pal[1] = mc0;
+ pal[2] = color;
+ pal[3] = mc1;
+ for (ty = 0u; ty < ST_CEL_TILES; ty++) {
+ for (tx = 0u; tx < ST_CEL_TILES; tx++) {
+ uint8_t *tile = &out[(ty * ST_CEL_TILES + tx) * 32u];
+ for (row = 0u; row < 8u; row++) {
+ uint8_t y = (uint8_t)(ty * 8u + row);
+ uint8_t *p = &tile[row * 4u];
+ uint8_t b = 0u;
+ if (bm != 0 && y < ST_CEL_ROWS) {
+ b = bm[y * 3u + tx];
+ }
+ for (k = 0u; k < 4u; k++) {
+ uint8_t pair = (uint8_t)((b >> (6u - k * 2u)) & 3u);
+ if (multi != 0u) {
+ p[k] = (uint8_t)((pal[pair] << 4) | pal[pair]);
+ } else {
+ p[k] = (uint8_t)((((pair & 2u) != 0u) ? (uint8_t)(color << 4) : 0u) | (((pair & 1u) != 0u) ? color : 0u));
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/examples/spacetaxi/stCels.h b/examples/spacetaxi/stCels.h
new file mode 100644
index 0000000..e78e1e6
--- /dev/null
+++ b/examples/spacetaxi/stCels.h
@@ -0,0 +1,37 @@
+// Space Taxi -- the sprite cels the renderer needs, as a fixed-order
+// bank. The offline baker (tools/staxibake) emits a .spr in this order,
+// spritebake compiles it to a per-platform .spc, and stRenderPrewarm
+// loads that with jlSpriteBankLoadPrecompiled -- so the moving cels
+// arrive already compiled and the ~0.4 s-per-cel 65816 JIT never runs at
+// boot. The game maps loaded cel i back to its (ptr, colour, mode) key
+// from kStCels[i]. stCelBlob is the single expansion both the baker and
+// the interpreter fallback use, so the compiled and built cels agree.
+
+#ifndef ST_CELS_H
+#define ST_CELS_H
+
+#include
+
+// 3x3 tiles = 24x24 px; 32 bytes per 8x8 tile, tile-major (matches
+// jlSpriteCreateFromSurface / the .spr cel layout).
+#define ST_CEL_TILES 3u
+#define ST_CEL_BYTES (ST_CEL_TILES * ST_CEL_TILES * 32u)
+#define ST_CEL_ROWS 21u // a VIC sprite is 21 rows tall
+
+typedef struct {
+ uint8_t ptr; // VIC sprite block pointer
+ uint8_t color; // per-sprite colour (the multicolour "10" pair)
+ uint8_t multi; // 1 = multicolour, 0 = hires
+ uint8_t mc0; // shared multicolour 0 ($D025); 0 when hires
+ uint8_t mc1; // shared multicolour 1 ($D026); 0 when hires
+} StCelDefT;
+
+extern const StCelDefT kStCels[];
+extern const uint16_t kStCelCount;
+
+// Expand a VIC bitmap (63 bytes, 21 rows x 3, or NULL) into the 288-byte
+// tile-major 4bpp-packed cel blob. Colours as in the C64 multicolour
+// rules; hires uses `color` for a set bit.
+void stCelBlob(const uint8_t *bm, uint8_t multi, uint8_t color, uint8_t mc0, uint8_t mc1, uint8_t *out);
+
+#endif
diff --git a/examples/spacetaxi/stRender.c b/examples/spacetaxi/stRender.c
index 276183b..6a1f104 100644
--- a/examples/spacetaxi/stRender.c
+++ b/examples/spacetaxi/stRender.c
@@ -3,35 +3,44 @@
//
// The screen is 40x25 character cells painted from the live charset
// (the game edits glyphs at runtime: the transporter hatch, the title
-// logo flip-book, the laser beams), one JoeyLib tile per glyph built
-// on a scratch surface and cached by bitmap so an animated glyph is
-// built once per shape. Only the cells the simulation listed as dirty
-// are repainted. The eight VIC sprites are drawn from their bitmaps
-// through a small cache of JoeyLib sprites keyed on (pointer, colour,
-// multicolour mode), with save-under and LIFO restore. Sprite 0 has
-// VIC priority, so the draw order is sprite 7 first, sprite 0 last,
-// and a sprite that has not moved (and has nothing repainted under
-// it) simply stays on the stage: only it and everything drawn after
-// it are undrawn and redrawn when it changes.
+// logo flip-book, the laser beams). A glyph is one mono JoeyLib tile
+// (set bits = non-zero pixels), cached by shape and coloured on the way
+// to the stage by jlTilePasteMono from the cell's colour RAM, so a
+// colour change never rebuilds a tile and an animated glyph is built
+// once per shape. Only the cells the simulation listed as dirty are
+// repainted; a changed glyph dirties the cells showing it, found
+// inside the cell range each character was last painted in. The eight
+// VIC sprites are drawn from their bitmaps through a small cache of
+// JoeyLib sprites keyed on (pointer, colour, multicolour mode), with
+// save-under and LIFO restore. Sprite 0 has VIC priority, so the draw
+// order is sprite 7 first, sprite 0 last, and a sprite that has not
+// moved (and has nothing repainted under it) simply stays on the
+// stage: only it and everything drawn after it are undrawn and redrawn
+// when it changes.
#include
#include "spacetaxi.h"
+#include "stCels.h"
#define ST_SPRITE_TILES 3u
// 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.
+// 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 plus 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.
#if defined(__W65816__)
- #define ST_SPRITE_CACHE 24u
- #define ST_CELL_CACHE 24u
+ #define ST_SPRITE_CACHE 48u
+ #define ST_GLYPH_CACHE 64u
#else
#define ST_SPRITE_CACHE 72u
- // Pre-coloured cells keyed direct-mapped by character + colours, so a
- // full 256 covers every (char, fg, bg=black) with no collisions.
- #define ST_CELL_CACHE 256u
+ #define ST_GLYPH_CACHE 256u
#endif
#define ST_SPRITE_BACKUP_BYTES JOEY_SPRITE_BACKUP_BYTES(ST_SPRITE_TILES, ST_SPRITE_TILES)
#define ST_SPRITE_PX (8 * ST_SPRITE_TILES)
+// glyphFirst[] value for a character shown in no cell.
+#define ST_NO_CELL 0xFFFFu
typedef struct {
jlSpriteT *sprite;
@@ -42,16 +51,11 @@ typedef struct {
uint8_t mc1;
uint8_t level; // level index the bitmap came from (level sprites)
bool used;
+ bool pinned; // prewarmed moving cel: never evicted (its
+ // compiled code is costly to re-emit on the 65816)
uint32_t lastUse; // for least-recently-used eviction
} StSpriteCacheT;
-typedef struct {
- uint8_t bits[8];
- uint8_t fg;
- uint8_t bg;
- jlTileT tile;
- bool used;
-} StCellCacheT;
// What is drawn in a draw slot (slot 0 = sprite 7 ... slot 7 = sprite 0).
typedef struct {
@@ -65,9 +69,20 @@ typedef struct {
typedef struct {
jlSurfaceT *scratch;
- StCellCacheT cells[ST_CELL_CACHE];
+ // Glyphs as mono tiles (set bits = non-zero pixels, coloured per cell
+ // by jlTilePasteMono), direct-mapped by character and valid while
+ // the bits match. Parallel arrays: power-of-two element sizes keep
+ // the 65816 indexing to shifts.
+ uint8_t glyphBits[ST_GLYPH_CACHE][8];
+ jlTileT glyphTiles[ST_GLYPH_CACHE];
+ bool glyphUsed[ST_GLYPH_CACHE];
+ // Cell range (inclusive) each character has been painted in since
+ // the last full repaint: where a changed glyph's cells can be.
+ uint16_t glyphFirst[ST_CHARSET_CHARS];
+ uint16_t glyphLast[ST_CHARSET_CHARS];
uint32_t cacheStamp;
StSpriteCacheT cache[ST_SPRITE_CACHE];
+ StSpriteCacheT *lastHit[ST_HW_SPRITES]; // the entry each hardware sprite used last
jlSpriteBackupT backup[ST_HW_SPRITES];
uint8_t backupMem[ST_HW_SPRITES][ST_SPRITE_BACKUP_BYTES] __attribute__((aligned(2)));
StDrawnT slot[ST_HW_SPRITES];
@@ -76,6 +91,8 @@ typedef struct {
static StRenderStateT gRender;
+// Set across stRenderPrewarm so a cel built there is pinned in the cache.
+static bool gPrewarming;
// The VIC-II palette in register order, $0RGB.
static const uint16_t kC64Palette[16] = {
@@ -84,13 +101,38 @@ static const uint16_t kC64Palette[16] = {
};
-static jlSpriteT *cachedSprite(StSimT *sim, uint8_t idx);
-static void collectGlyphs(StSimT *sim);
-static void dirtyRect(const StSimT *sim, int16_t *x0, int16_t *y0, int16_t *x1, int16_t *y1);
-static void dropCache(void);
-static void paintCells(jlSurfaceT *stage, StSimT *sim);
-static void pasteCell(jlSurfaceT *stage, const StSimT *sim, uint16_t cell);
-static bool spriteOnScreen(int16_t px, int16_t py);
+static void buildSpriteCel(const uint8_t *bm, uint8_t multi, uint8_t color, uint8_t mc0, uint8_t mc1);
+static jlSpriteT *cachedSprite(StSimT *sim, uint8_t idx);
+static bool loadPrecompiledCels(void);
+static uint8_t cellRow(uint16_t cell);
+static void collectGlyphs(StSimT *sim);
+static void dirtyRect(const StSimT *sim, int16_t *x0, int16_t *y0, int16_t *x1, int16_t *y1);
+static void dropCache(void);
+static const jlTileT *glyphTile(const StSimT *sim, uint8_t chr);
+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 spriteOnScreen(int16_t px, int16_t py);
+static void tileFromChunky(jlTileT *out, 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);
+ }
+ }
+}
// The JoeyLib sprite for hardware sprite `idx` of the current frame,
@@ -102,66 +144,49 @@ static jlSpriteT *cachedSprite(StSimT *sim, uint8_t idx) {
uint8_t mc0 = multi ? sim->spriteMc0 : 0u;
uint8_t mc1 = multi ? sim->spriteMc1 : 0u;
uint8_t level = (ptr < ST_SPRITE_PTR_FIRST && sim->level != 0) ? sim->level->levelIndex : 0xFFu;
- const uint8_t *bm;
StSpriteCacheT *slot = 0;
+ StSpriteCacheT *e = gRender.lastHit[idx];
uint8_t k;
- uint8_t row;
gRender.cacheStamp++;
+ // 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) {
+ e->lastUse = gRender.cacheStamp;
+ return e->sprite;
+ }
for (k = 0u; k < ST_SPRITE_CACHE; k++) {
- StSpriteCacheT *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) {
e->lastUse = gRender.cacheStamp;
+ gRender.lastHit[idx] = e;
return e->sprite;
}
- if (slot == 0 || !e->used || (slot->used && e->used && e->lastUse < slot->lastUse)) {
- if (slot == 0 || !slot->used || !e->used || e->lastUse < slot->lastUse) {
- slot = e;
- }
+ // Pinned entries (prewarmed cab/exhaust/passenger cels) are the
+ // costly-to-recompile ones and are never evicted; the least-
+ // recently-used unpinned slot is the victim, a free slot first.
+ if (e->pinned) {
+ continue;
}
+ if (slot == 0 || !e->used || (slot->used && e->lastUse < slot->lastUse)) {
+ slot = e;
+ }
+ }
+ if (slot == 0) {
+ // Every slot is pinned (never happens with the prewarm set sized
+ // below the cache): reuse the queried entry's own slot is unsafe,
+ // so fail into the interpreter path.
+ return 0;
}
if (slot->used) {
jlSpriteDestroy(slot->sprite);
slot->sprite = 0;
slot->used = false;
}
- bm = stSimSpriteBitmap(sim, ptr);
- jlFillRect(gRender.scratch, 0, 0, ST_SPRITE_PX, ST_SPRITE_PX, 0u);
- if (bm != 0) {
- for (row = 0u; row < ST_SPRITE_H; row++) {
- uint32_t bits = ((uint32_t)bm[row * 3u] << 16) | ((uint32_t)bm[row * 3u + 1u] << 8) | bm[row * 3u + 2u];
- uint8_t x;
- if (multi != 0u) {
- for (x = 0u; x < 12u; x++) {
- uint8_t pair = (uint8_t)((bits >> (22u - x * 2u)) & 3u);
- uint8_t c = 0u;
- if (pair == 1u) {
- c = mc0;
- } else if (pair == 2u) {
- c = color;
- } else if (pair == 3u) {
- c = mc1;
- }
- if (c != 0u) {
- jlDrawPixel(gRender.scratch, (int16_t)(x * 2u), (int16_t)row, c);
- jlDrawPixel(gRender.scratch, (int16_t)(x * 2u + 1u), (int16_t)row, c);
- }
- }
- } else {
- for (x = 0u; x < 24u; x++) {
- if ((bits & (1uL << (23u - x))) != 0u) {
- jlDrawPixel(gRender.scratch, (int16_t)x, (int16_t)row, color);
- }
- }
- }
- }
- }
+ buildSpriteCel(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;
}
- // Only the cab, passenger and flame move every tick; the codegen
- // arena is theirs. Everything else stays interpreted (drawn once).
// Only the cab, exhaust and passenger move every tick; the codegen
// arena is theirs. Everything else stays interpreted (drawn seldom).
if (idx <= 2u) {
@@ -173,39 +198,49 @@ static jlSpriteT *cachedSprite(StSimT *sim, uint8_t idx) {
slot->multi = multi;
slot->mc0 = mc0;
slot->mc1 = mc1;
- slot->level = level;
- slot->used = true;
+ slot->level = level;
+ slot->used = true;
+ slot->pinned = gPrewarming;
+ gRender.lastHit[idx] = slot;
return slot->sprite;
}
+// Block row of a screen cell without a division: cell / 40 is
+// (cell / 8) / 5, and x * 205 / 1024 equals x / 5 for every x below 125.
+static uint8_t cellRow(uint16_t cell) {
+ return (uint8_t)(((uint16_t)(cell >> 3) * 205u) >> 10);
+}
+
+
// A changed glyph (the hatch, the logo flip, the laser beams) makes
-// every cell that shows it dirty; the cell cache self-invalidates on the
-// bitmap compare, so no per-char tile needs rebuilding here.
+// every cell that shows it dirty. The glyph cache self-invalidates on
+// the bitmap compare, so nothing is rebuilt here; the search covers
+// only the cell range the character was last painted in.
static void collectGlyphs(StSimT *sim) {
uint16_t ch;
uint16_t cell;
- uint8_t dirty[ST_CHARSET_CHARS];
- bool any = false;
+ uint16_t last;
for (ch = 0u; ch < ST_CHARSET_CHARS; ch++) {
- dirty[ch] = sim->charDirty[ch];
- if (dirty[ch] != 0u) {
- sim->charDirty[ch] = 0u;
- any = true;
+ if (sim->charDirty[ch] == 0u) {
+ continue;
}
- }
- if (!any || sim->dirtyAll) {
- return;
- }
- for (cell = 0u; cell < ST_SCREEN_CELLS; cell++) {
- if (dirty[sim->screen[cell]] != 0u && sim->cellDirty[cell] == 0u) {
+ sim->charDirty[ch] = 0u;
+ if (sim->dirtyAll) {
+ continue;
+ }
+ last = gRender.glyphLast[ch];
+ for (cell = gRender.glyphFirst[ch]; cell <= last; cell++) {
+ if (sim->screen[cell] != (uint8_t)ch || sim->cellDirty[cell] != 0u) {
+ continue;
+ }
sim->cellDirty[cell] = 1u;
- if (sim->dirtyCount < (uint8_t)(sizeof(sim->dirtyList) / sizeof(sim->dirtyList[0]))) {
+ if (sim->dirtyCount < (uint16_t)(sizeof(sim->dirtyList) / sizeof(sim->dirtyList[0]))) {
sim->dirtyList[sim->dirtyCount++] = cell;
} else {
sim->dirtyAll = true;
- return;
+ break;
}
}
}
@@ -215,7 +250,7 @@ static void collectGlyphs(StSimT *sim) {
// Bounding box (pixels, x1/y1 exclusive) of the cells about to be
// repainted. Empty when x1 <= x0.
static void dirtyRect(const StSimT *sim, int16_t *x0, int16_t *y0, int16_t *x1, int16_t *y1) {
- uint8_t k;
+ uint16_t k;
if (sim->dirtyAll) {
*x0 = 0;
@@ -230,8 +265,9 @@ static void dirtyRect(const StSimT *sim, int16_t *x0, int16_t *y0, int16_t *x1,
*y1 = 0;
for (k = 0u; k < sim->dirtyCount; k++) {
uint16_t cell = sim->dirtyList[k];
- int16_t cx = (int16_t)((cell % ST_SCREEN_COLS) * 8u);
- int16_t cy = (int16_t)((cell / ST_SCREEN_COLS) * 8u);
+ uint8_t by = cellRow(cell);
+ int16_t cy = (int16_t)(by * 8u);
+ int16_t cx = (int16_t)((cell - ((uint16_t)by << 5) - ((uint16_t)by << 3)) * 8u);
if (cx < *x0) {
*x0 = cx;
}
@@ -261,15 +297,52 @@ static void dropCache(void) {
}
+// The mono tile for character `chr`, rebuilt when the glyph's bits no
+// longer match the cached shape (direct-mapped by character).
+static const jlTileT *glyphTile(const StSimT *sim, uint8_t chr) {
+ // Two glyph bits -> one chunky byte of non-zero (set) / zero pixels.
+ static const uint8_t kMonoPair[4] = { 0x00u, 0x0Fu, 0xF0u, 0xFFu };
+ const uint8_t *bits = sim->charset[chr];
+ uint8_t slot = (uint8_t)(chr & (ST_GLYPH_CACHE - 1u));
+ jlTileT *tile = &gRender.glyphTiles[slot];
+ uint8_t chunky[TILE_BYTES];
+ uint8_t row;
+
+ if (gRender.glyphUsed[slot] && memcmp(gRender.glyphBits[slot], bits, 8u) == 0) {
+ return tile;
+ }
+ for (row = 0u; row < TILE_PIXELS_PER_SIDE; row++) {
+ uint8_t b = bits[row];
+ uint8_t *p = &chunky[row * TILE_BYTES_PER_ROW];
+ p[0] = kMonoPair[b >> 6];
+ p[1] = kMonoPair[(b >> 4) & 3u];
+ p[2] = kMonoPair[(b >> 2) & 3u];
+ p[3] = kMonoPair[b & 3u];
+ }
+ tileFromChunky(tile, chunky);
+ memcpy(gRender.glyphBits[slot], bits, 8u);
+ gRender.glyphUsed[slot] = true;
+ return tile;
+}
+
+
// Repaint the dirty cells: the list, or everything after an overflow
-// or a scene change.
+// or a scene change (which also restarts the per-character cell ranges).
static void paintCells(jlSurfaceT *stage, StSimT *sim) {
uint16_t cell;
- uint8_t k;
+ uint16_t k;
+ uint8_t bx;
+ uint8_t by;
if (sim->dirtyAll) {
- for (cell = 0u; cell < ST_SCREEN_CELLS; cell++) {
- pasteCell(stage, sim, cell);
+ memset(gRender.glyphFirst, 0xFF, sizeof(gRender.glyphFirst));
+ memset(gRender.glyphLast, 0, sizeof(gRender.glyphLast));
+ cell = 0u;
+ for (by = 0u; by < ST_SCREEN_ROWS; by++) {
+ for (bx = 0u; bx < ST_SCREEN_COLS; bx++) {
+ pasteCell(stage, sim, cell, bx, by);
+ cell++;
+ }
}
memset(sim->cellDirty, 0, ST_SCREEN_CELLS);
sim->dirtyCount = 0u;
@@ -278,44 +351,27 @@ static void paintCells(jlSurfaceT *stage, StSimT *sim) {
}
for (k = 0u; k < sim->dirtyCount; k++) {
cell = sim->dirtyList[k];
- pasteCell(stage, sim, cell);
+ by = cellRow(cell);
+ bx = (uint8_t)(cell - ((uint16_t)by << 5) - ((uint16_t)by << 3));
+ pasteCell(stage, sim, cell, bx, by);
sim->cellDirty[cell] = 0u;
}
sim->dirtyCount = 0u;
}
-// Paste one cell through the coloured-tile cache (direct-mapped on
-// character and colours; an entry is valid while its glyph bits match).
-static void pasteCell(jlSurfaceT *stage, const StSimT *sim, uint16_t cell) {
- uint8_t chr = sim->screen[cell];
- uint8_t fg = sim->color[cell];
- uint8_t bg = sim->bgColor;
- const uint8_t *bits = sim->charset[chr];
- uint8_t bx = (uint8_t)(cell % ST_SCREEN_COLS);
- uint8_t by = (uint8_t)(cell / ST_SCREEN_COLS);
- StCellCacheT *c = &gRender.cells[(uint16_t)(chr + fg * 61u + bg * 7u) % ST_CELL_CACHE];
+// Paint one cell: its glyph tile coloured from colour RAM over the
+// background, and note the cell in the character's range.
+static void pasteCell(jlSurfaceT *stage, const StSimT *sim, uint16_t cell, uint8_t bx, uint8_t by) {
+ uint8_t chr = sim->screen[cell];
- uint8_t row;
- uint8_t bit;
-
- if (c->used && c->fg == fg && c->bg == bg && memcmp(c->bits, bits, 8u) == 0) {
- jlTilePaste(stage, bx, by, &c->tile);
- return;
+ if (cell < gRender.glyphFirst[chr]) {
+ gRender.glyphFirst[chr] = cell;
}
- for (row = 0u; row < 8u; row++) {
- uint8_t b = bits[row];
- for (bit = 0u; bit < 8u; bit++) {
- jlDrawPixel(gRender.scratch, (int16_t)bit, (int16_t)row,
- ((b & (uint8_t)(0x80u >> bit)) != 0u) ? fg : bg);
- }
+ if (cell > gRender.glyphLast[chr]) {
+ gRender.glyphLast[chr] = cell;
}
- jlTileSnap(gRender.scratch, 0u, 0u, &c->tile);
- memcpy(c->bits, bits, 8u);
- c->fg = fg;
- c->bg = bg;
- c->used = true;
- jlTilePaste(stage, bx, by, &c->tile);
+ jlTilePasteMono(stage, bx, by, glyphTile(sim, chr), sim->color[cell], sim->bgColor);
}
@@ -324,6 +380,28 @@ static bool spriteOnScreen(int16_t px, int16_t py) {
}
+// A chunky 8x8 tile (four bytes per row, high nibble = left pixel) as
+// 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);
+#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];
+ 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));
+ }
+ }
+ jlTileSnap(gRender.scratch, 0u, 0u, out);
+#endif
+}
+
+
// ---------------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------------
@@ -413,6 +491,7 @@ void stRenderInit(jlSurfaceT *stage) {
uint8_t k;
memset(&gRender, 0, sizeof(gRender));
+ memset(gRender.glyphFirst, 0xFF, sizeof(gRender.glyphFirst));
for (k = 0u; k < ST_HW_SPRITES; k++) {
gRender.backup[k].bytes = gRender.backupMem[k];
}
@@ -432,6 +511,46 @@ void stRenderInit(jlSurfaceT *stage) {
// 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.
+// Returns false if the .spc is missing or built for another target/shift
+// count (other ports, or a stale bake), so the caller falls back to the
+// runtime JIT prewarm below. Its cross-platform contract makes this a
+// no-op everywhere but the IIgs, where the bake is wired.
+static bool loadPrecompiledCels(void) {
+ jlSpriteT *cels[64];
+ uint16_t n;
+ uint16_t i;
+
+ if (kStCelCount > (uint16_t)(sizeof(cels) / sizeof(cels[0])) || kStCelCount > ST_SPRITE_CACHE) {
+ return false;
+ }
+ n = jlSpriteBankLoadPrecompiled("sprites/staxicels.spc", cels, kStCelCount, 0);
+ if (n < kStCelCount) {
+ for (i = 0u; i < n; i++) {
+ jlSpriteDestroy(cels[i]);
+ }
+ return false;
+ }
+ for (i = 0u; i < n; i++) {
+ StSpriteCacheT *e = &gRender.cache[i];
+ e->sprite = cels[i];
+ e->ptr = kStCels[i].ptr;
+ e->color = kStCels[i].color;
+ e->multi = kStCels[i].multi;
+ e->mc0 = kStCels[i].mc0;
+ e->mc1 = kStCels[i].mc1;
+ e->level = 0xFFu;
+ e->used = true;
+ e->pinned = true;
+ e->lastUse = 0u;
+ }
+ return true;
+}
+
+
void 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 kPassPtrs[] = { 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xD9 };
@@ -439,8 +558,15 @@ void stRenderPrewarm(jlSurfaceT *stage, StSimT *sim) {
uint8_t total = (uint8_t)(sizeof(kCabPtrs) + sizeof(kPassPtrs) + sizeof(kFlamePtrs) + 1u);
uint8_t done = 0u;
uint8_t k;
- StFrameT saved = sim->frame;
+ StFrameT saved;
+ // Precompiled bank: instant, no per-cel JIT. Falls through to the
+ // runtime build below on any port without a matching .spc.
+ if (loadPrecompiledCels()) {
+ return;
+ }
+ saved = sim->frame;
+ gPrewarming = true;
sim->frame.multiMask = 0x07u;
sim->spriteMc0 = 0x02u;
sim->spriteMc1 = 0x07u;
@@ -468,12 +594,15 @@ void stRenderPrewarm(jlSurfaceT *stage, StSimT *sim) {
jlFillRect(stage, 60, 110, (uint16_t)((uint16_t)done * 200u / total), 6u, 1u);
jlStagePresent();
}
- // The intro star is a hires sprite in colour 7 (built, not compiled).
+ // 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[6] = 0xDAu;
- sim->frame.color[6] = 0x07u;
- (void)cachedSprite(sim, 6u);
+ sim->frame.ptr[2] = 0xDAu;
+ sim->frame.color[2] = 0x07u;
+ (void)cachedSprite(sim, 2u);
sim->frame = saved;
+ gPrewarming = false;
jlFillRect(stage, 60, 110, 200u, 6u, 0u);
}
diff --git a/examples/spacetaxi/stSim.c b/examples/spacetaxi/stSim.c
index daf844d..289e887 100644
--- a/examples/spacetaxi/stSim.c
+++ b/examples/spacetaxi/stSim.c
@@ -48,7 +48,7 @@ static uint8_t readInput(StSimT *sim);
static void markCell(StSimT *sim, uint16_t cell);
static void setCell(StSimT *sim, uint16_t cell, uint8_t ch);
static void setColorCell(StSimT *sim, uint16_t cell, uint8_t color);
-static void spriteMasks(const StSimT *sim, uint8_t idx, uint32_t *rows);
+static void spriteMasks(StSimT *sim, uint8_t idx);
static void spritesOffReset(StSimT *sim);
static void taxiSpawnInit(StSimT *sim);
static void taxiSpriteCelSelect(StSimT *sim);
@@ -264,67 +264,95 @@ static StTickResultE collisionPhase3(StSimT *sim) {
}
-// The VIC's $D01E / $D01F for the displayed frame: pixel overlap of
-// enabled sprites with each other and of sprite 0 with the background.
+// The C64 reads two VIC collision registers each frame: $D01F (a sprite
+// touched the background) and $D01E (two sprites touched). Only the cab
+// (sprite 0) can drive a gameplay outcome from them: the crash dispatch
+// returns unless the cab or the background is touched, and every hook
+// reads a bit set by cab contact. So the full all-pairs register is not
+// reproduced -- only sprite 0 versus the background and versus the other
+// sprites, and the exhaust flame (sprite 2, the cab's own, always
+// overlapping it) is skipped. Non-cab pairs (hazard vs hazard, passenger
+// vs hazard with the cab clear) never change the dispatch outcome, so
+// their bits are left unset. Proven against all four demo traces: the
+// observable state (positions, crash phase, score) is byte-identical.
static void computeCollisions(StSimT *sim) {
- uint32_t mask[ST_HW_SPRITES][ST_SPRITE_H];
- uint8_t live = 0u;
- uint8_t ss = 0u;
- uint8_t bg = 0u;
- uint8_t a;
+ uint32_t (*mask)[ST_SPRITE_H] = sim->collMask;
+ uint8_t ss = 0u;
+ uint8_t bg = 0u;
+ int16_t cabX;
+ int16_t cabY;
uint8_t b;
uint8_t r;
- for (a = 0u; a < ST_HW_SPRITES; a++) {
- if ((sim->frame.enableMask & (uint8_t)(1u << a)) != 0u) {
- spriteMasks(sim, a, mask[a]);
- live |= (uint8_t)(1u << a);
- }
+ if ((sim->frame.enableMask & 1u) == 0u) {
+ sim->spriteSpriteColl = 0u;
+ sim->spriteBgColl = 0u;
+ return;
}
- if ((live & 1u) != 0u) {
- int16_t px = (int16_t)((int16_t)sim->frame.x[0] - ST_SPRITE_X_ORIGIN);
- int16_t py = (int16_t)((int16_t)sim->frame.y[0] - ST_SPRITE_Y_ORIGIN);
- for (r = 0u; r < ST_SPRITE_H; r++) {
- if (mask[0][r] != 0u && (mask[0][r] & backgroundRowMask(sim, px, (int16_t)(py + r))) != 0u) {
- bg |= 1u;
- break;
+ spriteMasks(sim, 0u);
+ cabX = (int16_t)((int16_t)sim->frame.x[0] - ST_SPRITE_X_ORIGIN);
+ cabY = (int16_t)((int16_t)sim->frame.y[0] - ST_SPRITE_Y_ORIGIN);
+ // The background can only touch the cab where a covering character
+ // cell is non-blank. In open flight every covering cell is the space
+ // glyph (all-zero pixels), so the 21-row pixel test is skipped there;
+ // any non-space cell falls through to the exact test, so the result
+ // is unchanged (space is the only zero-pixel glyph the test can hit).
+ {
+ int16_t firstCol = (int16_t)((cabX - (cabX < 0 ? 7 : 0)) / 8);
+ int16_t topRow = (cabY < 0) ? 0 : (int16_t)(cabY / 8);
+ int16_t botRow = (int16_t)((cabY + (int16_t)ST_SPRITE_H - 1) / 8);
+ bool solid = false;
+ int16_t rr;
+ int16_t cc;
+
+ if (botRow >= (int16_t)ST_SCREEN_ROWS) {
+ botRow = (int16_t)(ST_SCREEN_ROWS - 1u);
+ }
+ for (rr = topRow; rr <= botRow && !solid; rr++) {
+ for (cc = firstCol; cc <= (int16_t)(firstCol + 3); cc++) {
+ if (cc >= 0 && cc < (int16_t)ST_SCREEN_COLS && sim->screen[ST_CELL((uint16_t)rr, (uint16_t)cc)] != ST_CHAR_SPACE) {
+ solid = true;
+ break;
+ }
+ }
+ }
+ if (solid) {
+ for (r = 0u; r < ST_SPRITE_H; r++) {
+ if (mask[0][r] != 0u && (mask[0][r] & backgroundRowMask(sim, cabX, (int16_t)(cabY + r))) != 0u) {
+ bg = 1u;
+ break;
+ }
}
}
}
- for (a = 0u; a < ST_HW_SPRITES; a++) {
- if ((live & (uint8_t)(1u << a)) == 0u) {
+ for (b = 1u; b < ST_HW_SPRITES; b++) {
+ int16_t dx;
+ int16_t dy;
+ // Skip the exhaust flame (sprite 2) and any disabled sprite.
+ if (b == 2u || (sim->frame.enableMask & (uint8_t)(1u << b)) == 0u) {
continue;
}
- for (b = (uint8_t)(a + 1u); b < ST_HW_SPRITES; b++) {
- int16_t dx;
- int16_t dy;
- bool hit = false;
- if ((live & (uint8_t)(1u << b)) == 0u) {
+ dx = (int16_t)((int16_t)sim->frame.x[b] - (int16_t)sim->frame.x[0]);
+ dy = (int16_t)((int16_t)sim->frame.y[b] - (int16_t)sim->frame.y[0]);
+ if (dx <= -ST_SPRITE_W || dx >= ST_SPRITE_W || dy <= -ST_SPRITE_H || dy >= ST_SPRITE_H) {
+ continue;
+ }
+ spriteMasks(sim, b);
+ for (r = 0u; r < ST_SPRITE_H; r++) {
+ int16_t rb = (int16_t)((int16_t)r - dy);
+ uint32_t mb;
+ if (rb < 0 || rb >= (int16_t)ST_SPRITE_H) {
continue;
}
- dx = (int16_t)((int16_t)sim->frame.x[b] - (int16_t)sim->frame.x[a]);
- dy = (int16_t)((int16_t)sim->frame.y[b] - (int16_t)sim->frame.y[a]);
- if (dx <= -ST_SPRITE_W || dx >= ST_SPRITE_W || dy <= -ST_SPRITE_H || dy >= ST_SPRITE_H) {
- continue;
+ mb = mask[b][rb];
+ if (dx >= 0) {
+ mb >>= dx;
+ } else {
+ mb = (mb << (uint8_t)(-dx)) & 0xFFFFFFu;
}
- for (r = 0u; r < ST_SPRITE_H && !hit; r++) {
- int16_t rb = (int16_t)((int16_t)r - dy);
- uint32_t mb;
- if (rb < 0 || rb >= (int16_t)ST_SPRITE_H) {
- continue;
- }
- mb = mask[b][rb];
- if (dx >= 0) {
- mb >>= dx;
- } else {
- mb = (mb << (uint8_t)(-dx)) & 0xFFFFFFu;
- }
- if ((mask[a][r] & mb) != 0u) {
- hit = true;
- }
- }
- if (hit) {
- ss |= (uint8_t)((1u << a) | (1u << b));
+ if ((mask[0][r] & mb) != 0u) {
+ ss |= (uint8_t)(1u | (1u << b));
+ break;
}
}
}
@@ -771,7 +799,7 @@ static void markCell(StSimT *sim, uint16_t cell) {
return;
}
sim->cellDirty[cell] = 1u;
- if (sim->dirtyCount < (uint8_t)(sizeof(sim->dirtyList) / sizeof(sim->dirtyList[0]))) {
+ if (sim->dirtyCount < (uint16_t)(sizeof(sim->dirtyList) / sizeof(sim->dirtyList[0]))) {
sim->dirtyList[sim->dirtyCount++] = cell;
} else {
sim->dirtyAll = true;
@@ -796,30 +824,42 @@ static void setColorCell(StSimT *sim, uint16_t cell, uint8_t color) {
}
-// 21 rows of 24-bit foreground masks for a displayed sprite. A
-// multicolour sprite's 2-bit pairs cover two pixels each.
-static void spriteMasks(const StSimT *sim, uint8_t idx, uint32_t *rows) {
+// 21 rows of 24-bit foreground masks for a displayed sprite, kept in
+// collMask[idx] and rebuilt only when its bitmap or multicolour mode
+// changes. A multicolour sprite's 2-bit pairs cover two pixels each: a
+// non-zero pair is two set mask bits (kPairMask maps a nibble's two
+// pairs at once).
+static void spriteMasks(StSimT *sim, uint8_t idx) {
+ static const uint8_t kPairMask[16] = {
+ 0x0u, 0x3u, 0x3u, 0x3u, 0xCu, 0xFu, 0xFu, 0xFu,
+ 0xCu, 0xFu, 0xFu, 0xFu, 0xCu, 0xFu, 0xFu, 0xFu
+ };
const uint8_t *bm = stSimSpriteBitmap(sim, sim->frame.ptr[idx]);
- bool multi = (sim->frame.multiMask & (uint8_t)(1u << idx)) != 0u;
+ uint8_t multi = (uint8_t)((sim->frame.multiMask >> idx) & 1u);
+ uint32_t *rows = sim->collMask[idx];
uint8_t r;
- for (r = 0u; r < ST_SPRITE_H; r++) {
- uint32_t bits = 0u;
- if (bm != 0) {
- bits = ((uint32_t)bm[r * 3u] << 16) | ((uint32_t)bm[r * 3u + 1u] << 8) | bm[r * 3u + 2u];
- }
- if (multi) {
- uint32_t m = 0u;
- uint8_t i;
- for (i = 0u; i < 12u; i++) {
- if ((bits & (3u << (i * 2u))) != 0u) {
- m |= (3u << (i * 2u));
- }
- }
- bits = m;
- }
- rows[r] = bits;
+ if (bm != 0 && bm == sim->collMaskBm[idx] && multi == sim->collMaskMulti[idx]) {
+ return;
}
+ for (r = 0u; r < ST_SPRITE_H; r++) {
+ uint8_t b0 = 0u;
+ uint8_t b1 = 0u;
+ uint8_t b2 = 0u;
+ if (bm != 0) {
+ b0 = bm[r * 3u];
+ b1 = bm[r * 3u + 1u];
+ b2 = bm[r * 3u + 2u];
+ if (multi != 0u) {
+ b0 = (uint8_t)((kPairMask[b0 >> 4] << 4) | kPairMask[b0 & 0x0Fu]);
+ b1 = (uint8_t)((kPairMask[b1 >> 4] << 4) | kPairMask[b1 & 0x0Fu]);
+ b2 = (uint8_t)((kPairMask[b2 >> 4] << 4) | kPairMask[b2 & 0x0Fu]);
+ }
+ }
+ rows[r] = ((uint32_t)b0 << 16) | ((uint32_t)b1 << 8) | b2;
+ }
+ sim->collMaskBm[idx] = bm;
+ sim->collMaskMulti[idx] = multi;
}
@@ -1034,6 +1074,9 @@ void stSimEnterLevel(StSimT *sim, const StLevelT *level) {
stSimDirtyAll(sim);
memcpy(sim->charset, stC64Charset(), sizeof(sim->charset));
memset(sim->charDirty, 1, ST_CHARSET_CHARS);
+ // Level sprites live in the one level buffer, so the same bitmap
+ // address now holds other pixels: rebuild every collision mask.
+ memset(sim->collMaskBm, 0, sizeof(sim->collMaskBm));
sim->borderColor = level->header[0];
sim->bgColor = level->header[1];
sim->spr[0].color = level->header[7];
diff --git a/examples/spacetaxi/stSim.h b/examples/spacetaxi/stSim.h
index 759ea94..6701b85 100644
--- a/examples/spacetaxi/stSim.h
+++ b/examples/spacetaxi/stSim.h
@@ -153,6 +153,7 @@ typedef enum {
ST_STAGE_CLEANUP // $67C6
} StStageE;
+
// What a tick asked the host to do next (the original abandons the
// main loop with PLA/PLA on these).
typedef enum {
@@ -171,8 +172,8 @@ typedef struct {
uint8_t screen[ST_SCREEN_CELLS]; // $0400 screen RAM
uint8_t color[ST_SCREEN_CELLS]; // $D800 colour RAM
uint8_t cellDirty[ST_SCREEN_CELLS]; // renderer clears
- uint16_t dirtyList[128]; // cells marked since the last frame
- uint8_t dirtyCount;
+ uint16_t dirtyList[512]; // cells marked since the last frame
+ uint16_t dirtyCount;
bool dirtyAll; // list overflowed / whole screen
uint8_t charset[ST_CHARSET_CHARS][8]; // $2800 working copy
uint8_t charDirty[ST_CHARSET_CHARS]; // renderer clears
@@ -212,6 +213,12 @@ typedef struct {
uint8_t hitDispatchResult; // $721D
uint8_t spriteSpriteColl; // $71CA latched $D01E
uint8_t spriteBgColl; // $71CB latched $D01F
+ // Per-sprite 24-bit row masks of the displayed frame, rebuilt only
+ // when the sprite's bitmap or multicolour mode changes (derived
+ // state, not C64 memory).
+ uint32_t collMask[ST_HW_SPRITES][ST_SPRITE_H];
+ const uint8_t *collMaskBm[ST_HW_SPRITES];
+ uint8_t collMaskMulti[ST_HW_SPRITES];
// ---- fuel ----
uint8_t fuelCountdown; // $71C7
diff --git a/make/amiga.mk b/make/amiga.mk
index d327bfe..1406f54 100644
--- a/make/amiga.mk
+++ b/make/amiga.mk
@@ -88,7 +88,7 @@ ADV2_SRC := $(EXAMPLES)/adventure2/adventure2.c
ADV2_BIN := $(BINDIR)/Adv2
AGI_SRCS := $(EXAMPLES)/agi/agi.c $(EXAMPLES)/agi/agiRes.c $(EXAMPLES)/agi/agiPic.c $(EXAMPLES)/agi/agiView.c $(EXAMPLES)/agi/agiVm.c $(EXAMPLES)/agi/agiObj.c $(EXAMPLES)/agi/agiText.c
AGI_BIN := $(BINDIR)/Agi
-STAXI_SRCS := $(EXAMPLES)/spacetaxi/spacetaxi.c $(EXAMPLES)/spacetaxi/stLevel.c $(EXAMPLES)/spacetaxi/stLevelFile.c $(EXAMPLES)/spacetaxi/stRender.c $(EXAMPLES)/spacetaxi/stSim.c $(EXAMPLES)/spacetaxi/stFare.c $(EXAMPLES)/spacetaxi/stHooks.c $(EXAMPLES)/spacetaxi/stTitle.c $(EXAMPLES)/spacetaxi/stC64Data.c $(EXAMPLES)/spacetaxi/stAudio.c
+STAXI_SRCS := $(EXAMPLES)/spacetaxi/spacetaxi.c $(EXAMPLES)/spacetaxi/stLevel.c $(EXAMPLES)/spacetaxi/stLevelFile.c $(EXAMPLES)/spacetaxi/stRender.c $(EXAMPLES)/spacetaxi/stSim.c $(EXAMPLES)/spacetaxi/stFare.c $(EXAMPLES)/spacetaxi/stHooks.c $(EXAMPLES)/spacetaxi/stTitle.c $(EXAMPLES)/spacetaxi/stC64Data.c $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stAudio.c
STAXI_INSTALL_DIR := $(BINDIR)/Taxi
STAXI_BIN := $(STAXI_INSTALL_DIR)/Taxi
@@ -121,11 +121,18 @@ STAXI_SPR_GEN := $(patsubst $(STAXI_SRC_DIR)/%.png,$(STAXI_GEN_DIR)/%.spr,$(
STAXI_LEVEL_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_LEVEL_GEN))
STAXI_TBK_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_TBK_GEN))
STAXI_SPR_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_SPR_GEN))
-STAXI_ASSET_DSTS := $(STAXI_LEVEL_RUN)
+STAXI_ASSET_DSTS := $(STAXI_LEVEL_RUN) $(STAXICELS_SPC_RUN)
# Pre-compiled sprite banks (.spc): baked from the .spr by the host spritebake
# tool for THIS target and staged next to the .spr, so the runtime loads
# compiled routines with no startup JIT (jlSpriteBankLoadPrecompiled).
SPRITEBAKE_BIN := $(REPO_DIR)/build/tools/spritebake-amiga
+# The C64-exact cel bank, generated by staxibake (from the shared cel table
+# stCels.c + C64 bitmaps) and compiled to a .spc by the host spritebake, so
+# STAXI loads its cels already compiled instead of JIT-compiling them at boot.
+STAXIBAKE_BIN := $(REPO_DIR)/build/tools/staxibake
+STAXICELS_SPR_GEN := $(STAXI_GEN_DIR)/sprites/staxicels.spr
+STAXICELS_SPC_GEN := $(STAXICELS_SPR_GEN:.spr=.spc)
+STAXICELS_SPC_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXICELS_SPC_GEN))
STAXI_SPC_GEN := $(STAXI_SPR_GEN:.spr=.spc)
STAXI_SPC_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_SPC_GEN))
ASSETBAKE_BIN := $(REPO_DIR)/tools/assetbake/assetbake.py
@@ -244,7 +251,7 @@ $(STAXI_BIN): $(STAXI_SRCS) $(LIB)
# Space Taxi asset pipeline. .SECONDARY keeps the generated/ artifacts
# from being auto-deleted after staging.
-.SECONDARY: $(STAXI_LEVEL_GEN) $(STAXI_TBK_GEN) $(STAXI_SPR_GEN) $(STAXI_SPC_GEN)
+.SECONDARY: $(STAXI_LEVEL_GEN) $(STAXI_TBK_GEN) $(STAXI_SPR_GEN) $(STAXI_SPC_GEN) $(STAXICELS_SPC_GEN)
$(STAXI_ROM_LEVEL_GEN) &: $(STAXI_RAW_DUMP) $(ROMTOLEVEL_BIN)
@mkdir -p $(STAXI_GEN_DIR)/levels
@@ -265,6 +272,15 @@ $(STAXI_GEN_DIR)/%.spc: $(STAXI_GEN_DIR)/%.spr $(SPRITEBAKE_BIN)
$(SPRITEBAKE_BIN):
$(MAKE) -f $(REPO_DIR)/make/tools.mk spritebake-amiga
+$(STAXIBAKE_BIN):
+ $(MAKE) -f $(REPO_DIR)/make/tools.mk staxibake
+
+# Explicit rule (overrides the %.spr-from-%.png pattern): the cel bank is
+# generated from the game's cel table, not a PNG.
+$(STAXICELS_SPR_GEN): $(STAXIBAKE_BIN) $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stC64Data.c
+ @mkdir -p $(dir $@)
+ $(STAXIBAKE_BIN) $@
+
$(STAXI_RUN_DIR)/levels/%.dat: $(STAXI_GEN_DIR)/levels/%.dat
@mkdir -p $(dir $@)
cp $< $@
diff --git a/make/atarist.mk b/make/atarist.mk
index 2a1341b..26a7a56 100644
--- a/make/atarist.mk
+++ b/make/atarist.mk
@@ -75,7 +75,7 @@ ADV2_SRC := $(EXAMPLES)/adventure2/adventure2.c
ADV2_BIN := $(BINDIR)/ADV2.PRG
AGI_SRCS := $(EXAMPLES)/agi/agi.c $(EXAMPLES)/agi/agiRes.c $(EXAMPLES)/agi/agiPic.c $(EXAMPLES)/agi/agiView.c $(EXAMPLES)/agi/agiVm.c $(EXAMPLES)/agi/agiObj.c $(EXAMPLES)/agi/agiText.c
AGI_BIN := $(BINDIR)/AGI.PRG
-STAXI_SRCS := $(EXAMPLES)/spacetaxi/spacetaxi.c $(EXAMPLES)/spacetaxi/stLevel.c $(EXAMPLES)/spacetaxi/stLevelFile.c $(EXAMPLES)/spacetaxi/stRender.c $(EXAMPLES)/spacetaxi/stSim.c $(EXAMPLES)/spacetaxi/stFare.c $(EXAMPLES)/spacetaxi/stHooks.c $(EXAMPLES)/spacetaxi/stTitle.c $(EXAMPLES)/spacetaxi/stC64Data.c $(EXAMPLES)/spacetaxi/stAudio.c
+STAXI_SRCS := $(EXAMPLES)/spacetaxi/spacetaxi.c $(EXAMPLES)/spacetaxi/stLevel.c $(EXAMPLES)/spacetaxi/stLevelFile.c $(EXAMPLES)/spacetaxi/stRender.c $(EXAMPLES)/spacetaxi/stSim.c $(EXAMPLES)/spacetaxi/stFare.c $(EXAMPLES)/spacetaxi/stHooks.c $(EXAMPLES)/spacetaxi/stTitle.c $(EXAMPLES)/spacetaxi/stC64Data.c $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stAudio.c
STAXI_INSTALL_DIR := $(BINDIR)/STAXI
STAXI_BIN := $(STAXI_INSTALL_DIR)/STAXI.PRG
@@ -99,13 +99,20 @@ STAXI_SPR_GEN := $(patsubst $(STAXI_SRC_DIR)/%.png,$(STAXI_GEN_DIR)/%.spr,$(
STAXI_LEVEL_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_LEVEL_GEN))
STAXI_TBK_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_TBK_GEN))
STAXI_SPR_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_SPR_GEN))
-STAXI_ASSET_DSTS := $(STAXI_LEVEL_RUN)
+STAXI_ASSET_DSTS := $(STAXI_LEVEL_RUN) $(STAXICELS_SPC_RUN)
# Pre-compiled sprite banks (.spc): baked from the .spr by the host spritebake
# tool for THIS target and staged next to the .spr, so the runtime loads
# compiled routines with no startup JIT (jlSpriteBankLoadPrecompiled). ST is
# the one port whose .spc carries a reloc list (the shifted-DRAW thunk's jmp to
# stSpriteDrawShiftWalker), re-stamped by the loader.
SPRITEBAKE_BIN := $(REPO_DIR)/build/tools/spritebake-atarist
+# The C64-exact cel bank, generated by staxibake (from the shared cel table
+# stCels.c + C64 bitmaps) and compiled to a .spc by the host spritebake, so
+# STAXI loads its cels already compiled instead of JIT-compiling them at boot.
+STAXIBAKE_BIN := $(REPO_DIR)/build/tools/staxibake
+STAXICELS_SPR_GEN := $(STAXI_GEN_DIR)/sprites/staxicels.spr
+STAXICELS_SPC_GEN := $(STAXICELS_SPR_GEN:.spr=.spc)
+STAXICELS_SPC_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXICELS_SPC_GEN))
STAXI_SPC_GEN := $(STAXI_SPR_GEN:.spr=.spc)
STAXI_SPC_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_SPC_GEN))
ASSETBAKE_BIN := $(REPO_DIR)/tools/assetbake/assetbake.py
@@ -230,7 +237,7 @@ $(STAXI_BIN): $(STAXI_SRCS) $(LIB) $(LIBXMP_AR)
$(ST_CC) $(CFLAGS) $(STAXI_SRCS) $(LIB) $(LIBXMP_AR) -o $@ $(LDFLAGS)
# Space Taxi asset pipeline (mirrors dos.mk + amiga.mk).
-.SECONDARY: $(STAXI_LEVEL_GEN) $(STAXI_TBK_GEN) $(STAXI_SPR_GEN) $(STAXI_SPC_GEN)
+.SECONDARY: $(STAXI_LEVEL_GEN) $(STAXI_TBK_GEN) $(STAXI_SPR_GEN) $(STAXI_SPC_GEN) $(STAXICELS_SPC_GEN)
$(STAXI_ROM_LEVEL_GEN) &: $(STAXI_RAW_DUMP) $(ROMTOLEVEL_BIN)
@mkdir -p $(STAXI_GEN_DIR)/levels
@@ -251,6 +258,15 @@ $(STAXI_GEN_DIR)/%.spc: $(STAXI_GEN_DIR)/%.spr $(SPRITEBAKE_BIN)
$(SPRITEBAKE_BIN):
$(MAKE) -f $(REPO_DIR)/make/tools.mk spritebake-atarist
+$(STAXIBAKE_BIN):
+ $(MAKE) -f $(REPO_DIR)/make/tools.mk staxibake
+
+# Explicit rule (overrides the %.spr-from-%.png pattern): the cel bank is
+# generated from the game's cel table, not a PNG.
+$(STAXICELS_SPR_GEN): $(STAXIBAKE_BIN) $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stC64Data.c
+ @mkdir -p $(dir $@)
+ $(STAXIBAKE_BIN) $@
+
$(STAXI_RUN_DIR)/levels/%.dat: $(STAXI_GEN_DIR)/levels/%.dat
@mkdir -p $(dir $@)
cp $< $@
diff --git a/make/dos.mk b/make/dos.mk
index 528dad1..de03dbf 100644
--- a/make/dos.mk
+++ b/make/dos.mk
@@ -71,7 +71,7 @@ ADV2_SRC := $(EXAMPLES)/adventure2/adventure2.c
ADV2_BIN := $(BINDIR)/ADV2.EXE
AGI_SRCS := $(EXAMPLES)/agi/agi.c $(EXAMPLES)/agi/agiRes.c $(EXAMPLES)/agi/agiPic.c $(EXAMPLES)/agi/agiView.c $(EXAMPLES)/agi/agiVm.c $(EXAMPLES)/agi/agiObj.c $(EXAMPLES)/agi/agiText.c
AGI_BIN := $(BINDIR)/AGI.EXE
-STAXI_SRCS := $(EXAMPLES)/spacetaxi/spacetaxi.c $(EXAMPLES)/spacetaxi/stLevel.c $(EXAMPLES)/spacetaxi/stLevelFile.c $(EXAMPLES)/spacetaxi/stRender.c $(EXAMPLES)/spacetaxi/stSim.c $(EXAMPLES)/spacetaxi/stFare.c $(EXAMPLES)/spacetaxi/stHooks.c $(EXAMPLES)/spacetaxi/stTitle.c $(EXAMPLES)/spacetaxi/stC64Data.c $(EXAMPLES)/spacetaxi/stAudio.c
+STAXI_SRCS := $(EXAMPLES)/spacetaxi/spacetaxi.c $(EXAMPLES)/spacetaxi/stLevel.c $(EXAMPLES)/spacetaxi/stLevelFile.c $(EXAMPLES)/spacetaxi/stRender.c $(EXAMPLES)/spacetaxi/stSim.c $(EXAMPLES)/spacetaxi/stFare.c $(EXAMPLES)/spacetaxi/stHooks.c $(EXAMPLES)/spacetaxi/stTitle.c $(EXAMPLES)/spacetaxi/stC64Data.c $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stAudio.c
# JoeyLib install convention: each app lives in its own bin subdir
# with a DATA/ folder alongside the binary for runtime assets.
STAXI_INSTALL_DIR := $(BINDIR)/STAXI
@@ -122,11 +122,18 @@ ASSETBAKE_TARGET := dos
STAXI_LEVEL_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_LEVEL_GEN))
STAXI_TBK_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_TBK_GEN))
STAXI_SPR_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_SPR_GEN))
-STAXI_ASSET_DSTS := $(STAXI_LEVEL_RUN)
+STAXI_ASSET_DSTS := $(STAXI_LEVEL_RUN) $(STAXICELS_SPC_RUN)
# Pre-compiled sprite banks (.spc): baked from the .spr by the host spritebake
# tool for THIS target and staged next to the .spr, so the runtime loads
# compiled routines with no startup JIT (jlSpriteBankLoadPrecompiled).
SPRITEBAKE_BIN := $(REPO_DIR)/build/tools/spritebake-dos
+# The C64-exact cel bank, generated by staxibake (from the shared cel table
+# stCels.c + C64 bitmaps) and compiled to a .spc by the host spritebake, so
+# STAXI loads its cels already compiled instead of JIT-compiling them at boot.
+STAXIBAKE_BIN := $(REPO_DIR)/build/tools/staxibake
+STAXICELS_SPR_GEN := $(STAXI_GEN_DIR)/sprites/staxicels.spr
+STAXICELS_SPC_GEN := $(STAXICELS_SPR_GEN:.spr=.spc)
+STAXICELS_SPC_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXICELS_SPC_GEN))
STAXI_SPC_GEN := $(STAXI_SPR_GEN:.spr=.spc)
STAXI_SPC_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_SPC_GEN))
@@ -246,7 +253,7 @@ $(STAXI_BIN): $(STAXI_SRCS) $(LIB) $(LIBXMP_AR)
# Mark these .SECONDARY so make doesn't auto-delete them after the
# stage-B copy into bin/ -- they're the canonical distribution
# artifacts.
-.SECONDARY: $(STAXI_LEVEL_GEN) $(STAXI_TBK_GEN) $(STAXI_SPR_GEN) $(STAXI_SPC_GEN)
+.SECONDARY: $(STAXI_LEVEL_GEN) $(STAXI_TBK_GEN) $(STAXI_SPR_GEN) $(STAXI_SPC_GEN) $(STAXICELS_SPC_GEN)
# Bulk-extract all 24 canonical levels plus the title from the C64
# ROM in one invocation. Grouped target (&:) means make treats the
@@ -271,6 +278,15 @@ $(STAXI_GEN_DIR)/%.spc: $(STAXI_GEN_DIR)/%.spr $(SPRITEBAKE_BIN)
$(SPRITEBAKE_BIN):
$(MAKE) -f $(REPO_DIR)/make/tools.mk spritebake-dos
+$(STAXIBAKE_BIN):
+ $(MAKE) -f $(REPO_DIR)/make/tools.mk staxibake
+
+# Explicit rule (overrides the %.spr-from-%.png pattern): the cel bank is
+# generated from the game's cel table, not a PNG.
+$(STAXICELS_SPR_GEN): $(STAXIBAKE_BIN) $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stC64Data.c
+ @mkdir -p $(dir $@)
+ $(STAXIBAKE_BIN) $@
+
# Stage B: copy generated/ into the per-target bin/assets/ runtime tree
$(STAXI_RUN_DIR)/levels/%.dat: $(STAXI_GEN_DIR)/levels/%.dat
@mkdir -p $(dir $@)
diff --git a/make/iigs.mk b/make/iigs.mk
index faacded..e77808e 100644
--- a/make/iigs.mk
+++ b/make/iigs.mk
@@ -66,7 +66,7 @@ UBER_SRC := $(EXAMPLES)/uber/uber.c
ADV_SRC := $(EXAMPLES)/adventure/adventure.c
ADV2_SRC := $(EXAMPLES)/adventure2/adventure2.c
AGI_SRCS := $(EXAMPLES)/agi/agi.c $(EXAMPLES)/agi/agiRes.c $(EXAMPLES)/agi/agiPic.c $(EXAMPLES)/agi/agiView.c $(EXAMPLES)/agi/agiVm.c $(EXAMPLES)/agi/agiObj.c $(EXAMPLES)/agi/agiText.c
-STAXI_SRCS := $(EXAMPLES)/spacetaxi/stC64Data.c $(EXAMPLES)/spacetaxi/stSim.c $(EXAMPLES)/spacetaxi/spacetaxi.c $(EXAMPLES)/spacetaxi/stLevel.c $(EXAMPLES)/spacetaxi/stLevelFile.c $(EXAMPLES)/spacetaxi/stRender.c $(EXAMPLES)/spacetaxi/stFare.c $(EXAMPLES)/spacetaxi/stHooks.c $(EXAMPLES)/spacetaxi/stTitle.c $(EXAMPLES)/spacetaxi/stAudio.c
+STAXI_SRCS := $(EXAMPLES)/spacetaxi/stC64Data.c $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stSim.c $(EXAMPLES)/spacetaxi/spacetaxi.c $(EXAMPLES)/spacetaxi/stLevel.c $(EXAMPLES)/spacetaxi/stLevelFile.c $(EXAMPLES)/spacetaxi/stRender.c $(EXAMPLES)/spacetaxi/stFare.c $(EXAMPLES)/spacetaxi/stHooks.c $(EXAMPLES)/spacetaxi/stTitle.c $(EXAMPLES)/spacetaxi/stAudio.c
AUDIO_SRC := $(EXAMPLES)/audio/audio.c
# NinjaTrackerPlus replayer: Merlin32-assemble ninjatrackerplus.s to a 34KB
@@ -106,13 +106,26 @@ iigs-examples: $(BINDIR)/PATTERN $(BINDIR)/DRAW $(BINDIR)/KEYS $(BINDIR)/SERIAL
# we bake the .spc straight from the committed .spr with the host spritebake
# tool; make-iigs-disk.sh then find(1)s the whole generated/iigs tree, so the
# .spc rides onto the disk automatically once it exists.
-STAXI_IIGS_SPR := $(wildcard $(EXAMPLES)/spacetaxi/generated/iigs/sprites/*.spr)
-STAXI_IIGS_SPC := $(STAXI_IIGS_SPR:.spr=.spc)
+# The cel bank is GENERATED by staxibake from the game's shared cel table
+# (stCels.c) + C64 bitmaps, then compiled to a .spc by the host
+# spritebake; make-iigs-disk.sh find(1)s the whole generated/iigs tree so
+# the .spc rides onto the disk. The game loads it at boot
+# (jlSpriteBankLoadPrecompiled) instead of JIT-compiling every cel.
+STAXICELS_SPR := $(EXAMPLES)/spacetaxi/generated/iigs/sprites/staxicels.spr
+STAXI_IIGS_SPC := $(STAXICELS_SPR:.spr=.spc)
SPRITEBAKE_IIGS := $(REPO_DIR)/build/tools/spritebake-iigs
+STAXIBAKE := $(REPO_DIR)/build/tools/staxibake
$(SPRITEBAKE_IIGS):
$(MAKE) -f $(REPO_DIR)/make/tools.mk spritebake-iigs
+$(STAXIBAKE):
+ $(MAKE) -f $(REPO_DIR)/make/tools.mk staxibake
+
+$(STAXICELS_SPR): $(STAXIBAKE) $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stC64Data.c
+ @mkdir -p $(dir $@)
+ $(STAXIBAKE) $@
+
$(EXAMPLES)/spacetaxi/generated/iigs/sprites/%.spc: $(EXAMPLES)/spacetaxi/generated/iigs/sprites/%.spr $(SPRITEBAKE_IIGS)
$(SPRITEBAKE_IIGS) $< $@
diff --git a/make/tools.mk b/make/tools.mk
index 901a4f5..d38629b 100644
--- a/make/tools.mk
+++ b/make/tools.mk
@@ -24,6 +24,14 @@ MODLOOPEND_BIN := $(BUILD_DIR)/modloopend
MKSTLEVEL_SRC := $(REPO_DIR)/examples/spacetaxi/mkstlevel/mkstlevel.c
MKSTLEVEL_BIN := $(BUILD_DIR)/mkstlevel
+# staxibake -- generates the Space Taxi cel bank (.spr) from the game's
+# shared cel table + C64 bitmaps, for offline sprite pre-compilation. A
+# plain host tool (the .spr is cross-platform); spritebake then compiles
+# it per target.
+STAXI_DIR := $(REPO_DIR)/examples/spacetaxi
+STAXIBAKE_SRC := $(TOOLS_DIR)/staxibake/staxibake.c $(STAXI_DIR)/stCels.c $(STAXI_DIR)/stC64Data.c
+STAXIBAKE_BIN := $(BUILD_DIR)/staxibake
+
# spritebake -- offline sprite pre-compiler. Built ONCE PER TARGET on the
# host: it links the shared staging (spriteStage.c) + that target's emitter,
# compiled with -DJOEYLIB_PLATFORM_ so JOEY_SPRITE_SHIFT_COUNT and the
@@ -33,11 +41,12 @@ MKSTLEVEL_BIN := $(BUILD_DIR)/mkstlevel
SPRITEBAKE_SRC := $(TOOLS_DIR)/spritebake/spritebake.c $(SRC_CG)/spriteStage.c
SPRITEBAKE_INC := -I$(INCLUDE_DIR) -I$(SRC_CORE) -I$(SRC_CG)
-.PHONY: all tools clean-tools modloopend mkstlevel \
+.PHONY: all tools clean-tools modloopend mkstlevel staxibake \
spritebake-dos spritebake-amiga spritebake-atarist spritebake-iigs
all tools: $(MODLOOPEND_BIN) $(MKSTLEVEL_BIN)
modloopend: $(MODLOOPEND_BIN)
mkstlevel: $(MKSTLEVEL_BIN)
+staxibake: $(STAXIBAKE_BIN)
spritebake-dos: $(BUILD_DIR)/spritebake-dos
spritebake-amiga: $(BUILD_DIR)/spritebake-amiga
spritebake-atarist: $(BUILD_DIR)/spritebake-atarist
@@ -47,6 +56,10 @@ $(MODLOOPEND_BIN): $(MODLOOPEND_SRC)
@mkdir -p $(dir $@)
$(HOST_CC) $(HOST_CFLAGS) $< -o $@
+$(STAXIBAKE_BIN): $(STAXIBAKE_SRC)
+ @mkdir -p $(dir $@)
+ $(HOST_CC) $(HOST_CFLAGS) -I$(STAXI_DIR) $^ -o $@
+
$(MKSTLEVEL_BIN): $(MKSTLEVEL_SRC)
@mkdir -p $(dir $@)
$(HOST_CC) $(HOST_CFLAGS) $< -o $@
diff --git a/nvram/x68000_0/nvram b/nvram/x68000_0/nvram
deleted file mode 100644
index cefd27f..0000000
Binary files a/nvram/x68000_0/nvram and /dev/null differ
diff --git a/tools/staxibake/staxibake.c b/tools/staxibake/staxibake.c
new file mode 100644
index 0000000..65c5500
--- /dev/null
+++ b/tools/staxibake/staxibake.c
@@ -0,0 +1,71 @@
+// staxibake -- generate the Space Taxi cel bank (.spr) for offline
+// sprite pre-compilation. Runs on the host: it links the game's shared
+// cel table (stCels.c) and the C64 sprite bitmaps (stC64Data.c) and
+// writes the cross-platform JSP1 sprite bank in kStCels order. The
+// per-target spritebake then compiles it to a .spc the game loads at
+// boot with jlSpriteBankLoadPrecompiled, so no cel is JIT-compiled on
+// the 65816 (which costs ~0.4 s each).
+//
+// Usage: staxibake OUTPUT.spr
+
+#include
+#include
+#include
+#include
+
+#include "stCels.h"
+#include "stC64Data.h"
+
+// JSP1 header (mirrors tools/spritebake + jlSpriteBankLoad): magic,
+// target byte (0 = cross-platform chunky), has-palette, w/h tiles,
+// cell count LE16, then a 32-byte palette block, all in 44 bytes.
+#define SPR_HEADER_BYTES 44u
+#define SPR_OFF_TARGET 4u
+#define SPR_OFF_HAS_PAL 5u
+#define SPR_OFF_WIDTH 6u
+#define SPR_OFF_HEIGHT 7u
+#define SPR_OFF_CELLS 8u
+
+
+int main(int argc, char **argv) {
+ uint8_t header[SPR_HEADER_BYTES];
+ FILE *out;
+ uint16_t i;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: staxibake OUTPUT.spr\n");
+ return 2;
+ }
+ out = fopen(argv[1], "wb");
+ if (out == NULL) {
+ fprintf(stderr, "staxibake: cannot write %s\n", argv[1]);
+ return 1;
+ }
+ memset(header, 0, sizeof(header));
+ memcpy(header, "JSP1", 4);
+ header[SPR_OFF_TARGET] = 0u; // cross-platform chunky
+ header[SPR_OFF_HAS_PAL] = 0u; // the game owns its palette
+ header[SPR_OFF_WIDTH] = (uint8_t)ST_CEL_TILES;
+ header[SPR_OFF_HEIGHT] = (uint8_t)ST_CEL_TILES;
+ header[SPR_OFF_CELLS] = (uint8_t)(kStCelCount & 0xFFu);
+ header[SPR_OFF_CELLS + 1u] = (uint8_t)(kStCelCount >> 8);
+ if (fwrite(header, 1, SPR_HEADER_BYTES, out) != SPR_HEADER_BYTES) {
+ fprintf(stderr, "staxibake: header write failed\n");
+ fclose(out);
+ return 1;
+ }
+ for (i = 0u; i < kStCelCount; i++) {
+ uint8_t blob[ST_CEL_BYTES];
+ const uint8_t *bm = stC64SpriteBitmap((uint8_t)(kStCels[i].ptr - ST_SPRITE_PTR_FIRST));
+
+ stCelBlob(bm, kStCels[i].multi, kStCels[i].color, kStCels[i].mc0, kStCels[i].mc1, blob);
+ if (fwrite(blob, 1, ST_CEL_BYTES, out) != ST_CEL_BYTES) {
+ fprintf(stderr, "staxibake: cel %u write failed\n", (unsigned)i);
+ fclose(out);
+ return 1;
+ }
+ }
+ fclose(out);
+ fprintf(stderr, "staxibake: wrote %u cels (%ux%u tiles) -> %s\n", (unsigned)kStCelCount, (unsigned)ST_CEL_TILES, (unsigned)ST_CEL_TILES, argv[1]);
+ return 0;
+}
diff --git a/tools/stsim/stsim b/tools/stsim/stsim
deleted file mode 100755
index d5fb5c4..0000000
Binary files a/tools/stsim/stsim and /dev/null differ