Build standalone against JoeyLib.
This commit is contained in:
parent
a05958bd86
commit
786ff257c3
34 changed files with 1277 additions and 123 deletions
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Everything make writes: programs, bakes, disk images, the harness.
|
||||
build/
|
||||
|
||||
# The original game's memory image (scripts/importData.sh installs it as
|
||||
# stuff/raw.bin) and the disassembly work around it. A copy of a commercial
|
||||
# game: never committed.
|
||||
stuff/
|
||||
|
||||
# stsim's host binaries and its captured golden traces (gate.sh capture;
|
||||
# gateHashes.txt is the committed reference).
|
||||
tools/stsim/stsim
|
||||
tools/stsim/recorder
|
||||
tools/stsim/golden/
|
||||
|
||||
screenshots/
|
||||
__pycache__/
|
||||
|
||||
# Editor / OS cruft
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
12
MECHANICS.md
12
MECHANICS.md
|
|
@ -1,8 +1,8 @@
|
|||
# Space Taxi Mechanics (research notes from C64 disassembly)
|
||||
|
||||
Source: VICE memory dumps in `stuff/spacetaxi/` decoded via the
|
||||
Source: VICE memory dumps in `stuff/` decoded via the
|
||||
`dis6502.py` / `disLive.py` helpers. Full live-RAM listings are at
|
||||
`stuff/spacetaxi/live-level1.lst` (40,069 instructions) and
|
||||
`stuff/live-level1.lst` (40,069 instructions) and
|
||||
`live-raw.lst` (titlescreen snapshot). This doc captures observed
|
||||
behavior in plain English to drive the JoeyLib port without dragging
|
||||
the original 6502 source into chat or comments.
|
||||
|
|
@ -68,7 +68,7 @@ acceleration always applied to Y.
|
|||
### Per-frame algorithm (`$6032`, full trace) -- VERIFIED via emulator
|
||||
|
||||
Verified by running `$6032` through the 6502 emulator with various
|
||||
joystick / sprite-ptr states. See `stuff/spacetaxi/trace.py`.
|
||||
joystick / sprite-ptr states. See `stuff/trace.py`.
|
||||
|
||||
```
|
||||
1. Clear instant accel: $7148/49 = 0; $714A/4B = 0 [$6032-$603F]
|
||||
|
|
@ -1069,7 +1069,7 @@ IEC fast-loader (raw `$FFA8` CIOUT / `$FFAE` UNLSN serial traffic)
|
|||
that pulls the song file off the disk. **The music DATA is not in
|
||||
the resident program at all: each song ships as its own disk file**
|
||||
-- the `SPACETAXI 01..04` / `SPACETAXI HI` PRGs (present in
|
||||
`stuff/spacetaxi/extract/`). Whatever song was resident when the
|
||||
`stuff/extract/`). Whatever song was resident when the
|
||||
raw.bin dump was taken is what sits in its load area there.
|
||||
|
||||
Timing base: `$A2` in the tick compare is the KERNAL jiffy clock low
|
||||
|
|
@ -1770,7 +1770,7 @@ Char-code interpretation (via $4345 validation):
|
|||
- Other chars: undefined behavior
|
||||
|
||||
Verified by running `$4354` through the 6502 emulator (see
|
||||
`stuff/spacetaxi/trace.py`) with each blob against an all-blank
|
||||
`stuff/trace.py`) with each blob against an all-blank
|
||||
HUD. The actual byte-level result for each:
|
||||
|
||||
| Blob | HUD after BCD-add (chars) | Numeric effect |
|
||||
|
|
@ -2098,7 +2098,7 @@ Key gameplay-affecting observations:
|
|||
- **Border/BG colors** vary; most levels are black (0/0). Level S
|
||||
has yellow/orange (`$07/$08`), level L is dark gray on dark gray.
|
||||
|
||||
The .dat files in `examples/spacetaxi/generated/levels/` carry the
|
||||
The .dat files in `generated/levels/` carry the
|
||||
same xAccel/yAccel/xGrav/yGrav values verbatim (verified by parsing
|
||||
the binary headers).
|
||||
|
||||
|
|
|
|||
26
Makefile
Normal file
26
Makefile
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Space Taxi, built with JoeyLib: a JoeyLib checkout (JOEYLIB, beside this
|
||||
# one by default) owns the toolchains, the library and every target's rules,
|
||||
# and program.mk describes the game to them.
|
||||
#
|
||||
# make every target whose toolchain is installed
|
||||
# make <port> one target: iigs amiga atarist dos x68000 dvx
|
||||
# make check build, then run the gates (scripts/verify.sh)
|
||||
# make clean remove build/
|
||||
# make help JoeyLib's project targets
|
||||
#
|
||||
# README.md covers setting up JoeyLib, importing the game data and running.
|
||||
|
||||
JOEYLIB ?= ../joeylib
|
||||
include $(JOEYLIB)/make/project.mk
|
||||
|
||||
include $(JOEY_PROJECT)/program.mk
|
||||
|
||||
# What DVX's help and resource texts call the program, and its author.
|
||||
DVX_KIND := JoeyLib game
|
||||
DVX_AUTHOR := Scott Duensing
|
||||
|
||||
include $(JOEYLIB)/make/programs.mk
|
||||
|
||||
.PHONY: check
|
||||
check: all
|
||||
JOEYLIB="$(abspath $(JOEYLIB))" $(JOEY_PROJECT)/scripts/verify.sh
|
||||
4
PLAN.md
4
PLAN.md
|
|
@ -15,7 +15,7 @@ Same loop every time, so it stays cheap to iterate:
|
|||
2. **Read** -- pull the routine into a window via `disLive.py
|
||||
<dump> <out> <start> <end>`, study the annotated listing.
|
||||
3. **Document** -- append behavior summary to `MECHANICS.md` in
|
||||
plain English. Add new labels/vars to `stuff/spacetaxi/labels.txt`.
|
||||
plain English. Add new labels/vars to `stuff/labels.txt`.
|
||||
4. **Reannotate** -- `python3 disLive.py mem0000-level1.bin
|
||||
live-level1.lst` to regenerate the (annotated) listing from
|
||||
the dump + labels.txt in one step. The .lst file is the only
|
||||
|
|
@ -30,7 +30,7 @@ Same loop every time, so it stays cheap to iterate:
|
|||
|
||||
## Phase 0 -- foundation (DONE)
|
||||
|
||||
- Disassembly toolchain in `stuff/spacetaxi/`
|
||||
- Disassembly toolchain in `stuff/`
|
||||
- Full live-RAM listing + annotated variant
|
||||
- `MECHANICS.md` baseline
|
||||
- Asset pipeline (`tools/assetbake/assetbake.py`, extractor realigned)
|
||||
|
|
|
|||
123
README.md
Normal file
123
README.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
# Space Taxi
|
||||
|
||||
A port of Space Taxi (John Kutcher, Muse Software, 1984), the Commodore 64
|
||||
game, to every machine [JoeyLib](../joeylib) targets: the Apple IIgs,
|
||||
Commodore Amiga, Atari ST, MS-DOS, Sharp X68000 and DVX. One C codebase
|
||||
builds for all six.
|
||||
|
||||
The game is a routine-for-routine re-expression of the C64 original in C,
|
||||
written from its disassembly (MECHANICS.md): the flight physics, the fare
|
||||
machine, the 24 levels' hook programs, the title and level intros, the
|
||||
high-score table and name entry, the music (captured from the game's own
|
||||
SID player) and the speech. The simulation (stSim.c) is verified tick for
|
||||
tick against VICE traces of the C64's four attract-demo rides (VERIFIED.md),
|
||||
and the gates below hold it there.
|
||||
|
||||
## Setting up
|
||||
|
||||
Space Taxi builds with JoeyLib, which owns the cross compilers, the
|
||||
library, the asset tools and every machine's build rules. Check JoeyLib out
|
||||
beside this repository and install its toolchains (its README covers the
|
||||
installer):
|
||||
|
||||
```
|
||||
games/
|
||||
joeylib/ a JoeyLib checkout, set up with ./install.sh
|
||||
spacetaxi/ this repository
|
||||
```
|
||||
|
||||
```console
|
||||
$ cd ../joeylib
|
||||
$ ./install.sh install toolchains # the cross compilers
|
||||
$ ./install.sh install emulators # to run it and for the gates
|
||||
```
|
||||
|
||||
`JOEYLIB=PATH` on the make command line (or in the environment, for the
|
||||
scripts) points at a checkout somewhere else.
|
||||
|
||||
## The game data
|
||||
|
||||
The levels and the title screen are extracted at build time from a memory
|
||||
image of the original C64 game. It is a copy of a commercial game, so it is
|
||||
not in the repository; supply your own:
|
||||
|
||||
```console
|
||||
$ scripts/importData.sh PATH...
|
||||
```
|
||||
|
||||
PATH is the image itself, a folder holding it, or a `.zip` or `.tar.gz`
|
||||
with it inside. It is recognised by its checksum and installed as
|
||||
`stuff/raw.bin`, which git ignores. Without it the game still builds; only
|
||||
the levels (and the per-level sprite banks baked from them) are left out,
|
||||
and make says so.
|
||||
|
||||
## Building
|
||||
|
||||
```console
|
||||
$ make # every machine whose toolchain is installed
|
||||
$ make dos # one: iigs amiga atarist dos x68000 dvx
|
||||
$ make clean
|
||||
```
|
||||
|
||||
Everything lands in `build/<machine>/bin/`:
|
||||
|
||||
| Machine | Program |
|
||||
|---------|---------|
|
||||
| Apple IIgs | `STAXI`, and `STAXI.2mg`: a bootable 2 MB volume (stripped GS/OS that launches the game from its own folder) |
|
||||
| Amiga | `Taxi/Taxi` with its `DATA/` |
|
||||
| Atari ST | `STAXI/STAXI.PRG` with its `DATA/` |
|
||||
| MS-DOS | `STAXI/STAXI.EXE` with its `DATA/` |
|
||||
| Sharp X68000 | `STAXI/STAXI.X` with its `DATA/` |
|
||||
| DVX | `apps/joeylib/staxi/`: `staxi.app`, its help file and `DATA/` |
|
||||
|
||||
The bakes (levels, the pre-compiled sprite cels, the speech) are made per
|
||||
machine in `build/generated/<machine>/`. JoeyLib's library is built on
|
||||
demand in the JoeyLib checkout's own `build/`.
|
||||
|
||||
## Running
|
||||
|
||||
JoeyLib's `joey-run` starts a build in the right emulator:
|
||||
|
||||
```console
|
||||
$ ../joeylib/joey-run --project . --port dos staxi
|
||||
$ ../joeylib/joey-run --project . --port amiga taxi
|
||||
$ ../joeylib/joey-run build/iigs/bin/STAXI.2mg
|
||||
$ ../joeylib/joey-run --headless --seconds 90 --snapshot taxi.png --project . --port atarist staxi
|
||||
```
|
||||
|
||||
The IIgs volume is larger than an 800 KB floppy, so MAME boots it off a
|
||||
CFFA2 card (`mame apple2gs -sl7 cffa2 -hard1 build/iigs/bin/STAXI.2mg`).
|
||||
|
||||
## The gates
|
||||
|
||||
```console
|
||||
$ make check # build, then every gate
|
||||
$ scripts/verify.sh disk # or name the ones to run
|
||||
```
|
||||
|
||||
| Gate | What it proves |
|
||||
|------|----------------|
|
||||
| `stsim` | Every hook level's 4000-tick simulation trace is identical to its golden (`tools/stsim/gate.sh`; the four C64 attract rides and thirteen synthesised ones), and every cel a ride shows is pre-compiled (`celGate.sh`) |
|
||||
| `harness` | The screens above the simulation -- the high-score table, name entry, its save -- on JoeyLib's host BLANK port (`tools/staxiharness`) |
|
||||
| `disk`, `disk01` | `STAXI.2mg` boots, launches the game and draws on a ROM 3 and a ROM 01 IIgs |
|
||||
| `iigsaudio` | The IIgs sound interrupt is NTP's, the DOC is audible and its sound slots carry waveforms |
|
||||
| `x68kaudio` | Every X68000 noise write is a code from the port's table, and the ADPCM runs at 7812 Hz to both speakers while the game speaks |
|
||||
|
||||
`scripts/verify.sh` exits 0 when every gate passed, 1 when one failed, and 3
|
||||
when none failed but one was skipped (an emulator or a machine's build
|
||||
missing, or the game data not imported).
|
||||
|
||||
## Tools
|
||||
|
||||
- `tools/stsim` -- the simulation on the host: prints the per-tick state a
|
||||
VICE trace records, so `compare.py` can diff the port against the C64.
|
||||
- `tools/staxiharness` -- the front-end screen checks.
|
||||
- `tools/staxibake` -- writes the cel bank the build pre-compiles per machine.
|
||||
- `assets/` -- the generators behind the committed C64 tables
|
||||
(`genC64Data.py`, `genDemoStreams.py`, `genSongData.py`, `extractSpeech.py`)
|
||||
and the level extractor the build runs (`romToLevel.py`).
|
||||
- `scripts/bench-staxi-title.sh`, `scripts/diag-staxi-screen.sh` -- IIgs
|
||||
measurement and diagnosis under headless MAME.
|
||||
|
||||
MECHANICS.md and VERIFIED.md are the disassembly notes; TODO.md lists what is
|
||||
still open.
|
||||
21
TODO.md
Normal file
21
TODO.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Space Taxi -- open items
|
||||
|
||||
Things worth doing, not commitments. Started 2026-10-05, when Space Taxi
|
||||
moved out of JoeyLib's tree into this repository; JoeyLib's own list
|
||||
(library and platform work) stays in JoeyLib.
|
||||
|
||||
- [ ] **The C64's flown-sprite warp.** The level-name intro animates its
|
||||
warp by cycling the colours of a static star field painted into
|
||||
screen RAM, on every target (stTitle.c, `starPaletteApply` in
|
||||
stRender.c). The C64 flies seven star sprites out along its rays
|
||||
instead. That version, carry-flag exact ($44F8/$4500/$450C/$4513/
|
||||
$467A, the star velocity tables back in genC64Data.py), is kept in
|
||||
`patches/spriteWarp.patch` for reference: it was made against the
|
||||
working tree of 2026-10-05 before the warp's layout and pacing were
|
||||
redone, so it no longer applies as it stands. Measured then, the
|
||||
sprite warp ran 16.6-17.0 fps on the IIgs against the palette's 29.2
|
||||
(the bar was 30); ST 29.2 against the palette's 23.3, Amiga PAL 12.3
|
||||
against 18.8/13.6, DOS ~29.9 both ways. On the IIgs the cost is
|
||||
per-call overhead in save-and-draw and restore (~1.75k and ~1.25k
|
||||
cycles a star), not bytes moved, so it becomes feasible with a leaner
|
||||
save/draw/restore path for small cels in JoeyLib.
|
||||
10
VERIFIED.md
10
VERIFIED.md
|
|
@ -1,7 +1,7 @@
|
|||
# Space Taxi disassembly: verified claims
|
||||
|
||||
Each row below references a claim in `MECHANICS.md` and the trace
|
||||
that verifies it. Trace scripts live in `stuff/spacetaxi/trace.py`
|
||||
that verifies it. Trace scripts live in `stuff/trace.py`
|
||||
plus the inline scripts run during verification sessions.
|
||||
|
||||
## VERIFIED via emulator trace
|
||||
|
|
@ -81,10 +81,10 @@ plus the inline scripts run during verification sessions.
|
|||
|
||||
## TRACE INFRASTRUCTURE
|
||||
|
||||
- `stuff/spacetaxi/trace.py`: instrumented wrapper around `cpu6502.Cpu6502`. `Tracer.from_dump('raw.bin').call(addr)` runs a subroutine and snapshots memory + JSR chain.
|
||||
- `examples/spacetaxi/assets/cpu6502.py`: minimal 6502 emulator. Sufficient for all routines tested above; throws `NotImplementedError` for unsupported opcodes (none hit so far in gameplay paths).
|
||||
- `examples/spacetaxi/assets/dumpScenes.py` + `stuff/spacetaxi/dumpAllLevels.py`: pre-existing scene-decompress drivers.
|
||||
- `examples/spacetaxi/assets/romToLevel.py`: emits the `.dat` files from raw.bin via the same emulator.
|
||||
- `stuff/trace.py`: instrumented wrapper around `cpu6502.Cpu6502`. `Tracer.from_dump('raw.bin').call(addr)` runs a subroutine and snapshots memory + JSR chain.
|
||||
- `assets/cpu6502.py`: minimal 6502 emulator. Sufficient for all routines tested above; throws `NotImplementedError` for unsupported opcodes (none hit so far in gameplay paths).
|
||||
- `assets/dumpScenes.py` + `stuff/dumpAllLevels.py`: pre-existing scene-decompress drivers.
|
||||
- `assets/romToLevel.py`: emits the `.dat` files from raw.bin via the same emulator.
|
||||
|
||||
## METHODOLOGY (for future verification sessions)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ entry, and the depacked image IS the RAM -- $A000-$BFFF included.
|
|||
Feed the result to extractSpeech.py.
|
||||
|
||||
Usage: depackSpeechRam.py [spaceTaxiPlus.prg] [out.bin]
|
||||
Defaults resolve inside stuff/spacetaxi/ (gitignored research
|
||||
Defaults resolve inside stuff/ (gitignored research
|
||||
area, same as genC64Data.py's inputs).
|
||||
"""
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ import os
|
|||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
STUFF = os.path.join(HERE, "..", "..", "..", "stuff", "spacetaxi")
|
||||
STUFF = os.path.join(HERE, "..", "stuff")
|
||||
|
||||
# The BASIC stub is `0B08 D307 9E "2059" 00` -- SYS 2059.
|
||||
ENTRY = 0x080B
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"""Extract Space Taxi's digitised speech from a C64 memory dump.
|
||||
|
||||
FORMAT (reverse-engineered from bankedSfxLoad at $9802, see
|
||||
stuff/spacetaxi/live-level1.lst). It is NOT 4-bit $D418 digi and it is
|
||||
stuff/live-level1.lst). It is NOT 4-bit $D418 digi and it is
|
||||
NOT the SID speech synth. It is 1-BIT RUN-LENGTH audio:
|
||||
|
||||
* An index table at $9B00 holds [code, ptrLo, ptrHi] triples,
|
||||
|
|
|
|||
|
|
@ -8,16 +8,16 @@
|
|||
# (The level-intro star velocity tables, $450C/$4513, went with the moving
|
||||
# warp; claudeDocs/spacetaxiSpriteWarp.patch puts them back.)
|
||||
#
|
||||
# Everything comes straight out of stuff/spacetaxi/raw.bin (a VICE
|
||||
# Everything comes straight out of stuff/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)
|
||||
# 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")
|
||||
RAW = os.path.join(HERE, "..", "stuff", "raw.bin")
|
||||
OUT_C = os.path.join(HERE, "..", "stC64Data.c")
|
||||
OUT_H = os.path.join(HERE, "..", "stC64Data.h")
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ def main():
|
|||
sprite_count = SPRITE_PTR_LAST - SPRITE_PTR_FIRST + 1
|
||||
|
||||
h = []
|
||||
h.append("// Generated by assets/genC64Data.py from stuff/spacetaxi/raw.bin.")
|
||||
h.append("// Generated by assets/genC64Data.py from stuff/raw.bin.")
|
||||
h.append("// Do not hand-edit; re-run the generator.")
|
||||
h.append("")
|
||||
h.append("#ifndef ST_C64_DATA_H")
|
||||
|
|
@ -125,7 +125,7 @@ def main():
|
|||
h.append("#endif")
|
||||
|
||||
c = []
|
||||
c.append("// Generated by assets/genC64Data.py from stuff/spacetaxi/raw.bin.")
|
||||
c.append("// Generated by assets/genC64Data.py from stuff/raw.bin.")
|
||||
c.append("// Do not hand-edit; re-run the generator.")
|
||||
c.append("")
|
||||
c.append('#include "stC64Data.h"')
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
import os
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
EXTRACT = os.path.join(HERE, "..", "..", "..", "stuff", "spacetaxi", "extract")
|
||||
EXTRACT = os.path.join(HERE, "..", "stuff", "extract")
|
||||
OUT = os.path.join(HERE, "..", "stDemoStreams.h")
|
||||
|
||||
# (file, symbol, 0-based level index, screen letter) in ROTATION order.
|
||||
|
|
@ -50,7 +50,7 @@ def main():
|
|||
# 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:]
|
||||
raw = open(os.path.join(HERE, "..", "stuff", "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")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
#!/usr/bin/env python3
|
||||
# Bake examples/spacetaxi/assets/songs/song*.song into stSongData.h.
|
||||
# Bake assets/songs/song*.song into stSongData.h with JoeyLib's songbake
|
||||
# (JOEYLIB names the checkout, ../joeylib by default).
|
||||
#
|
||||
# python3 examples/spacetaxi/assets/genSongData.py
|
||||
# python3 assets/genSongData.py
|
||||
#
|
||||
# The .song sources come from the game's own player run under a 6502
|
||||
# emulator: assets/sidCapture.py logs every SID write per PAL frame and
|
||||
|
|
@ -12,8 +13,8 @@ import sys
|
|||
import tempfile
|
||||
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
repo = os.path.abspath(os.path.join(here, "..", "..", ".."))
|
||||
songbake = os.path.join(repo, "tools", "songbake", "songbake.py")
|
||||
joeylib = os.environ.get("JOEYLIB", os.path.join(here, "..", "..", "joeylib"))
|
||||
songbake = os.path.join(joeylib, "tools", "songbake", "songbake.py")
|
||||
out = os.path.join(here, "..", "stSongData.h")
|
||||
count = 9
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Space Taxi level .dat format (STL2)
|
||||
|
||||
A small custom binary format that `stLevelLoad()` reads at scene
|
||||
boot. Output files land in `examples/spacetaxi/generated/levels/`
|
||||
boot. Output files land in `generated/levels/`
|
||||
(from `romToLevel.py` for the 24 canonical C64 levels, or from
|
||||
`mkstlevel` for hand-authored levels in `assets/levels/*.txt`). The
|
||||
build copies them under `DATA/levels/levelNN.dat` in the runtime
|
||||
|
|
@ -74,15 +74,15 @@ without changes.
|
|||
1. Author a 256-tile (or smaller) tile sheet as an indexed PNG. Each
|
||||
tile is 8x8 px. Arrange in a grid; the bank loader assumes row-
|
||||
major, tile-index = `ty * tilesPerRow + tx`. Drop the PNG at
|
||||
`examples/spacetaxi/assets/tiles/tbank<N>.png`.
|
||||
`assets/tiles/tbank<N>.png`.
|
||||
2. The Makefile bakes per-target via `tools/assetbake/assetbake.py
|
||||
--type tile --target <port> tbank<N>.png tbank<N>.tbk`. Output
|
||||
lands in `examples/spacetaxi/generated/<port>/tiles/` and is
|
||||
lands in `generated/<port>/tiles/` and is
|
||||
staged into the runtime tree at `build/<port>/.../DATA/tiles/`.
|
||||
3. Author each level layout in a text editor or grid tool, save as a
|
||||
.dat per this spec. The helper `examples/spacetaxi/mkstlevel`
|
||||
.dat per this spec. The helper `mkstlevel`
|
||||
converts from a human-readable text grid to .dat. The 24 canonical
|
||||
C64 levels are emitted by `examples/spacetaxi/assets/romToLevel.py` directly
|
||||
C64 levels are emitted by `assets/romToLevel.py` directly
|
||||
from a raw VICE dump of the original .prg.
|
||||
|
||||
## Naming
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env python3
|
||||
# Convert ROM-extracted Space Taxi scenes directly into the runtime
|
||||
# STL1 binary format (see examples/spacetaxi/stLevel.c). Uses the
|
||||
# STL1 binary format (see stLevel.c). Uses the
|
||||
# 6502 emulator + raw RAM dump to decompress each scene, then writes
|
||||
# screen + color RAM bytes straight into the .dat layout the JoeyLib
|
||||
# port loads at runtime.
|
||||
|
|
@ -25,7 +25,7 @@ from cpu6502 import Cpu6502
|
|||
from dumpScenes import load_dump, run_scene
|
||||
|
||||
|
||||
# STL2 binary layout (see examples/spacetaxi/stLevel.c):
|
||||
# STL2 binary layout (see stLevel.c):
|
||||
# "STL2"
|
||||
# nameLen + name
|
||||
# tileBank, music, bgColor, borderColor, taxiSpawnX, taxiSpawnY
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
# Space Taxi's level source: a memory image of the original Commodore 64
|
||||
# game. Optional -- without it every port still builds, skipping only the
|
||||
# Space Taxi data -- so it is not part of `all`; install it by name.
|
||||
|
||||
compDescription="Space Taxi C64 memory image (private, optional: bundle or import)"
|
||||
compGroups="staxi optional"
|
||||
compRequires=""
|
||||
compHostDeps=""
|
||||
compPrivateFile="stuff/spacetaxi/raw.bin"
|
||||
compPin="$(privateSha1For "$compPrivateFile")"
|
||||
|
||||
|
||||
compInstall() {
|
||||
privateEnsure "$compPrivateFile"
|
||||
}
|
||||
|
||||
|
||||
compInstalled() {
|
||||
privatePresent "$compPrivateFile"
|
||||
}
|
||||
|
||||
|
||||
compVerify() {
|
||||
privatePresent "$compPrivateFile"
|
||||
}
|
||||
|
||||
|
||||
compEnv() {
|
||||
:
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// mkstlevel: convert a human-readable text level definition into the
|
||||
// STL3 binary format read by examples/spacetaxi/stLevel.c.
|
||||
// STL3 binary format read by stLevel.c.
|
||||
//
|
||||
// Usage:
|
||||
// mkstlevel input.txt output.dat
|
||||
|
|
|
|||
617
patches/spriteWarp.patch
Normal file
617
patches/spriteWarp.patch
Normal file
|
|
@ -0,0 +1,617 @@
|
|||
diff -ruN a/assets/genC64Data.py b/assets/genC64Data.py
|
||||
--- a/assets/genC64Data.py 2026-10-05 14:05:44.479069311 -0500
|
||||
+++ b/assets/genC64Data.py 2026-10-05 14:12:10.086072322 -0500
|
||||
@@ -67,6 +67,8 @@
|
||||
("uint8_t stC64RngByte(uint8_t idx);", "kStRngTable[idx]"),
|
||||
("const uint8_t *stC64SfxProgram(uint8_t idx);", "kStSfxPrograms[idx]"),
|
||||
("uint8_t stC64FlameCel(uint8_t dirMask);", "kStFlameCelByDirMask[dirMask]"),
|
||||
+ ("int8_t stC64IntroStarDx(uint8_t idx);", "kStIntroStarDx[idx]"),
|
||||
+ ("int8_t stC64IntroStarDy(uint8_t idx);", "kStIntroStarDy[idx]"),
|
||||
]
|
||||
|
||||
|
||||
@@ -115,6 +117,10 @@
|
||||
h.append("// 8=RIGHT); 0 = no cel for that combination.")
|
||||
h.append("extern const uint8_t kStFlameCelByDirMask[16];")
|
||||
h.append("")
|
||||
+ h.append("// Level-intro star velocities per sprite 0..6 ($450C / $4513).")
|
||||
+ h.append("extern const int8_t kStIntroStarDx[7];")
|
||||
+ h.append("extern const int8_t kStIntroStarDy[7];")
|
||||
+ h.append("")
|
||||
h.append("// Cross-segment-safe accessors (see stC64Data.c): reach the tables above")
|
||||
h.append("// by an inter-segment call on the IIgs, where a direct 2-byte data")
|
||||
h.append("// reference from another bank is not relocatable.")
|
||||
@@ -160,6 +166,11 @@
|
||||
c.append(c_bytes(flame))
|
||||
c.append("};")
|
||||
c.append("")
|
||||
+ dx = [b - 256 if b >= 128 else b for b in raw[0x450C:0x450C + 7]]
|
||||
+ dy = [b - 256 if b >= 128 else b for b in raw[0x4513:0x4513 + 7]]
|
||||
+ c.append("const int8_t kStIntroStarDx[7] = { " + ", ".join(str(v) for v in dx) + " };")
|
||||
+ c.append("const int8_t kStIntroStarDy[7] = { " + ", ".join(str(v) for v in dy) + " };")
|
||||
+ c.append("")
|
||||
c.append("")
|
||||
c.append("// ---------------------------------------------------------------------------")
|
||||
c.append("// Accessors. On the IIgs the program is split across banks and a 2-byte")
|
||||
diff -ruN a/spacetaxi.c b/spacetaxi.c
|
||||
--- a/spacetaxi.c 2026-10-05 14:05:44.477069267 -0500
|
||||
+++ b/spacetaxi.c 2026-10-05 14:12:10.087072342 -0500
|
||||
@@ -925,8 +925,6 @@
|
||||
static void startIntro(void) {
|
||||
stIntroEnter(&gSim, &gLevel);
|
||||
stRenderSceneChanged(&gSim);
|
||||
- // After the scene change, which turns it back off.
|
||||
- stRenderIntroStars();
|
||||
// $459C: the intro's tune plays under the fly-in, not blocking; the
|
||||
// C64 leaves it to the IRQ player and stops it at $4709.
|
||||
stAudioMusic(ST_SONG_INTRO, true);
|
||||
diff -ruN a/spacetaxi.h b/spacetaxi.h
|
||||
--- a/spacetaxi.h 2026-10-05 14:05:44.478069289 -0500
|
||||
+++ b/spacetaxi.h 2026-10-05 14:12:10.088072362 -0500
|
||||
@@ -52,10 +52,6 @@
|
||||
// cycle are palette writes instead of repainting 103 (and 407) cells.
|
||||
// Call after stRenderSceneChanged, which turns it back off.
|
||||
void stRenderTitleLogo(StSimT *sim);
|
||||
-// Level intro only: cycle the starfield's ring palette slots so the warp
|
||||
-// animates without a single pixel moving. Call after
|
||||
-// stRenderSceneChanged, which turns it back off.
|
||||
-void stRenderIntroStars(void);
|
||||
// Fill the stage with a two-line message and present it. Used when an
|
||||
// asset the game ships with will not load, so a player sees why the
|
||||
// game stopped instead of a silently degraded ride.
|
||||
diff -ruN a/stC64Data.c b/stC64Data.c
|
||||
--- a/stC64Data.c 2026-10-05 14:05:44.475069223 -0500
|
||||
+++ b/stC64Data.c 2026-10-05 14:12:10.085072302 -0500
|
||||
@@ -1322,6 +1322,9 @@
|
||||
0x00, 0xD8, 0xD4, 0x00, 0xD5, 0xD2, 0xD1, 0x00, 0xD7, 0xD3, 0xD6, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
+const int8_t kStIntroStarDx[7] = { 0, 6, 6, 6, -6, -6, -6 };
|
||||
+const int8_t kStIntroStarDy[7] = { -4, -4, 0, 4, 4, 0, -4 };
|
||||
+
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Accessors. On the IIgs the program is split across banks and a 2-byte
|
||||
@@ -1354,3 +1357,13 @@
|
||||
uint8_t stC64FlameCel(uint8_t dirMask) {
|
||||
return kStFlameCelByDirMask[dirMask];
|
||||
}
|
||||
+
|
||||
+
|
||||
+int8_t stC64IntroStarDx(uint8_t idx) {
|
||||
+ return kStIntroStarDx[idx];
|
||||
+}
|
||||
+
|
||||
+
|
||||
+int8_t stC64IntroStarDy(uint8_t idx) {
|
||||
+ return kStIntroStarDy[idx];
|
||||
+}
|
||||
diff -ruN a/stC64Data.h b/stC64Data.h
|
||||
--- a/stC64Data.h 2026-10-05 14:05:44.476069245 -0500
|
||||
+++ b/stC64Data.h 2026-10-05 14:12:10.086072322 -0500
|
||||
@@ -44,6 +44,10 @@
|
||||
// 8=RIGHT); 0 = no cel for that combination.
|
||||
extern const uint8_t kStFlameCelByDirMask[16];
|
||||
|
||||
+// Level-intro star velocities per sprite 0..6 ($450C / $4513).
|
||||
+extern const int8_t kStIntroStarDx[7];
|
||||
+extern const int8_t kStIntroStarDy[7];
|
||||
+
|
||||
// Cross-segment-safe accessors (see stC64Data.c): reach the tables above
|
||||
// by an inter-segment call on the IIgs, where a direct 2-byte data
|
||||
// reference from another bank is not relocatable.
|
||||
@@ -52,5 +56,7 @@
|
||||
uint8_t stC64RngByte(uint8_t idx);
|
||||
const uint8_t *stC64SfxProgram(uint8_t idx);
|
||||
uint8_t stC64FlameCel(uint8_t dirMask);
|
||||
+int8_t stC64IntroStarDx(uint8_t idx);
|
||||
+int8_t stC64IntroStarDy(uint8_t idx);
|
||||
|
||||
#endif
|
||||
diff -ruN a/stCels.c b/stCels.c
|
||||
--- a/stCels.c 2026-10-05 14:06:02.361459928 -0500
|
||||
+++ b/stCels.c 2026-10-05 14:12:10.090072402 -0500
|
||||
@@ -2,9 +2,9 @@
|
||||
// See stCels.h. Ordered exactly as stRenderPrewarm built the cels: the
|
||||
// cab set (flying, wreck and the intro decorations) and passenger set in
|
||||
// multicolour at the constant level colours (cab/passenger 6, shared
|
||||
-// $D025/$D026 = 2/7), then the flame set in multicolour colour 7. No
|
||||
-// intro star: the level intro paints its starfield into the character
|
||||
-// screen (stTitle.c), so the star sprite is never drawn.
|
||||
+// $D025/$D026 = 2/7), then the flame set in multicolour colour 7. The
|
||||
+// intro star is not here: a bank's cels share one size, and the star is
|
||||
+// built cropped to its one tile at boot (prewarmStar in stRender.c).
|
||||
|
||||
#include "stCels.h"
|
||||
|
||||
diff -ruN a/stRender.c b/stRender.c
|
||||
--- a/stRender.c 2026-10-05 14:07:17.861084192 -0500
|
||||
+++ b/stRender.c 2026-10-05 14:12:10.098072560 -0500
|
||||
@@ -37,7 +37,7 @@
|
||||
// The IIgs reaches its globals DBR-relative, so all of BSS must fit the
|
||||
// entry bank below the I/O window; the caches are sized down there. The
|
||||
// sprite cache must still hold every cel stRenderPrewarm builds (the
|
||||
-// cab, exhaust and passenger sets) or a cel change mid-play rebuilds
|
||||
+// cab, exhaust and passenger sets and the intro star) or a cel change mid-play rebuilds
|
||||
// and recompiles a sprite. Glyphs are direct-mapped by character, so
|
||||
// the count is a power of two.
|
||||
// Room for the baked cab/passenger/exhaust set (36) plus the largest
|
||||
@@ -96,9 +96,6 @@
|
||||
// Title logo (stRenderTitleLogo): all four flip-book frames in one
|
||||
// tile, animated by rewriting logoSlotMask[]'s palette slots.
|
||||
bool logoMode;
|
||||
- bool starMode; // level intro: cycling the starfield
|
||||
- uint8_t starPhase; // which ring the bright band is on
|
||||
- uint8_t starTick; // frames until the band steps outward
|
||||
bool logoTileOk; // the live glyph is one of the frames
|
||||
uint8_t logoSlotCount;
|
||||
uint8_t logoSlotMask[ST_LOGO_PAL_SLOTS]; // frames each slot is lit in
|
||||
@@ -141,16 +138,6 @@
|
||||
// four of these are used; the rest are headroom if the frames change.
|
||||
static const uint8_t kLogoPalSlot[ST_LOGO_PAL_SLOTS] = { 4u, 8u, 9u, 10u, 13u, 14u, 15u };
|
||||
|
||||
-// Level-intro warp: how a star ring looks at each distance BEHIND the
|
||||
-// bright band, as C64 colour indices (white, light grey, grey, dark
|
||||
-// grey, then black). A ring this far behind the band or further is
|
||||
-// unlit, so a lit head with a short fading tail sweeps outward and the
|
||||
-// rest of the field stays dark.
|
||||
-static const uint8_t kStarRamp[] = { 1u, 15u, 12u, 11u };
|
||||
-#define ST_STAR_RAMP_LEN (sizeof(kStarRamp) / sizeof(kStarRamp[0]))
|
||||
-// Frames per ring step. The intro runs at the game's 30 Hz tick, so a
|
||||
-// step every other frame sweeps the eight rings in about half a second.
|
||||
-#define ST_STAR_STEP_FRAMES 2u
|
||||
|
||||
|
||||
static StSpriteCacheT *cachedSprite(StSimT *sim, uint8_t idx);
|
||||
@@ -175,8 +162,8 @@
|
||||
static void paintCells(jlSurfaceT *stage, StSimT *sim);
|
||||
static void pasteCell(jlSurfaceT *stage, const StSimT *sim, uint16_t cell, uint8_t bx, uint8_t by);
|
||||
static bool prewarmReport(uint16_t expect);
|
||||
+static void prewarmStar(StSimT *sim);
|
||||
static bool spriteOnScreen(int16_t px, int16_t py);
|
||||
-static void starPaletteApply(void);
|
||||
static void tileFromChunky(jlTileT *out, const uint8_t *chunky);
|
||||
static bool tileHasPixels(const uint8_t *chunky);
|
||||
|
||||
@@ -677,29 +664,6 @@
|
||||
}
|
||||
|
||||
|
||||
-// Light the starfield's ring colours for the current phase: the ring
|
||||
-// the band is on takes kStarRamp[0], the ones just inside it the rest
|
||||
-// of the ramp, and everything else the background. One palette write
|
||||
-// per step and not a single pixel moves -- that is the whole point of
|
||||
-// ST_STAR_RING_FIRST (stSim.h).
|
||||
-static void starPaletteApply(void) {
|
||||
- uint16_t pal[SURFACE_COLORS_PER_PALETTE];
|
||||
- uint16_t color;
|
||||
- uint8_t behind;
|
||||
- uint8_t ring;
|
||||
-
|
||||
- jlPaletteGet(gRender.stage, 0u, pal);
|
||||
- for (ring = 0u; ring < ST_STAR_RING_COUNT; ring++) {
|
||||
- // How far this ring sits behind the band, wrapping so the band
|
||||
- // re-enters at the centre as it leaves the edge.
|
||||
- behind = (uint8_t)((gRender.starPhase - ring) & (ST_STAR_RING_COUNT - 1u));
|
||||
- color = (behind < ST_STAR_RAMP_LEN) ? kC64Palette[kStarRamp[behind]] : kC64Palette[0];
|
||||
- pal[ST_STAR_RING_FIRST + ring] = color;
|
||||
- }
|
||||
- jlPaletteSet(gRender.stage, 0u, pal);
|
||||
-}
|
||||
-
|
||||
-
|
||||
static bool spriteOnScreen(int16_t px, int16_t py) {
|
||||
return px < SURFACE_WIDTH && py < SURFACE_HEIGHT && px > -ST_SPRITE_W && py > -(int16_t)ST_SPRITE_PX;
|
||||
}
|
||||
@@ -769,17 +733,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
- if (gRender.starMode) {
|
||||
- // The warp is the only thing animating here, and it animates in
|
||||
- // the palette: no cells change, no sprite moves for it, so the
|
||||
- // present that ends this frame copies no pixels at all.
|
||||
- gRender.starTick++;
|
||||
- if (gRender.starTick >= ST_STAR_STEP_FRAMES) {
|
||||
- gRender.starTick = 0u;
|
||||
- gRender.starPhase = (uint8_t)((gRender.starPhase + 1u) & (ST_STAR_RING_COUNT - 1u));
|
||||
- starPaletteApply();
|
||||
- }
|
||||
- }
|
||||
collectGlyphs(sim);
|
||||
dirtyBands(sim);
|
||||
for (k = 0u; k < ST_HW_SPRITES; k++) {
|
||||
@@ -1148,7 +1101,7 @@
|
||||
static const uint8_t kCabPtrs[] = { 0xC0, 0xC1, 0xDC, 0xDD, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8 };
|
||||
static const uint8_t kPassPtrs[] = { 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xD9 };
|
||||
static const uint8_t kFlamePtrs[] = { 0xD8, 0xD4, 0xD5, 0xD2, 0xD1, 0xD7, 0xD3, 0xD6 };
|
||||
- uint8_t total = (uint8_t)(sizeof(kCabPtrs) + sizeof(kPassPtrs) + sizeof(kFlamePtrs));
|
||||
+ uint8_t total = (uint8_t)(sizeof(kCabPtrs) + sizeof(kPassPtrs) + sizeof(kFlamePtrs) + 1u);
|
||||
uint8_t done = 0u;
|
||||
bool ok;
|
||||
uint8_t k;
|
||||
@@ -1158,7 +1111,8 @@
|
||||
// runtime build below on any port without a matching .spc.
|
||||
if (loadPrecompiledCels()) {
|
||||
ST_LOG("spacetaxi: cels from sprites/staxicels.spc");
|
||||
- return prewarmReport(kStCelCount);
|
||||
+ prewarmStar(sim);
|
||||
+ return prewarmReport((uint16_t)(kStCelCount + 1u));
|
||||
}
|
||||
ST_LOG("spacetaxi: ! no usable sprites/staxicels.spc -- JIT prewarm");
|
||||
saved = sim->frame;
|
||||
@@ -1189,6 +1143,7 @@
|
||||
}
|
||||
sim->frame = saved;
|
||||
gPrewarming = false;
|
||||
+ prewarmStar(sim);
|
||||
ok = prewarmReport(total);
|
||||
jlFillRect(stage, 60, 110, 200u, 6u, 0u);
|
||||
return ok;
|
||||
@@ -1231,15 +1186,32 @@
|
||||
}
|
||||
|
||||
|
||||
+// The intro star is built and compiled here rather than baked: a bank's
|
||||
+// cels all share one size (3x3 tiles), and the star's nine pixels fit
|
||||
+// one tile, so a baked star would save, draw, restore and present nine
|
||||
+// times its area -- seven times a frame. A moving (idx <= 2) cel, so it
|
||||
+// compiles; the interpreter cannot keep up with seven of them.
|
||||
+static void prewarmStar(StSimT *sim) {
|
||||
+ StFrameT saved = sim->frame;
|
||||
+
|
||||
+ gPrewarming = true;
|
||||
+ sim->frame.multiMask = 0u;
|
||||
+ sim->frame.ptr[2] = ST_INTRO_STAR_PTR;
|
||||
+ sim->frame.color[2] = ST_INTRO_STAR_COLOR;
|
||||
+ (void)cachedSprite(sim, 2u);
|
||||
+ sim->frame = saved;
|
||||
+ gPrewarming = false;
|
||||
+}
|
||||
+
|
||||
+
|
||||
// A new scene: nothing drawn is valid any more.
|
||||
void stRenderSceneChanged(StSimT *sim) {
|
||||
uint8_t k;
|
||||
|
||||
- if (gRender.logoMode || gRender.starMode) {
|
||||
+ if (gRender.logoMode) {
|
||||
// The borrowed slots go back to being C64 colours.
|
||||
gRender.logoMode = false;
|
||||
gRender.logoTileOk = false;
|
||||
- gRender.starMode = false;
|
||||
jlPaletteSet(gRender.stage, 0u, kC64Palette);
|
||||
}
|
||||
for (k = 0u; k < ST_HW_SPRITES; k++) {
|
||||
@@ -1269,18 +1241,6 @@
|
||||
}
|
||||
|
||||
|
||||
-// Turn on the level intro's palette-cycled starfield. Call it after
|
||||
-// stRenderSceneChanged (which turns it back off) on entry to the level
|
||||
-// intro. The cells were painted by stIntroEnter; from here the warp is
|
||||
-// one palette write every ST_STAR_STEP_FRAMES frames.
|
||||
-void stRenderIntroStars(void) {
|
||||
- gRender.starMode = true;
|
||||
- gRender.starPhase = 0u;
|
||||
- gRender.starTick = 0u;
|
||||
- starPaletteApply();
|
||||
-}
|
||||
-
|
||||
-
|
||||
void stRenderShutdown(void) {
|
||||
dropCache();
|
||||
if (gRender.scratch != 0) {
|
||||
diff -ruN a/stSim.h b/stSim.h
|
||||
--- a/stSim.h 2026-10-05 14:05:53.055256946 -0500
|
||||
+++ b/stSim.h 2026-10-05 14:12:10.089072382 -0500
|
||||
@@ -82,42 +82,16 @@
|
||||
#define ST_CHAR_ELECTROID 0x6Fu // level O's scrolling barrier glyph
|
||||
#define ST_FARE_DEST_UP 0x0Bu // $7D8E: the fare aboard wants UP
|
||||
|
||||
-// ---- level-intro starfield --------------------------------------------
|
||||
+// ---- level-intro star warp --------------------------------------------
|
||||
//
|
||||
-// The C64's level intro streams seven star SPRITES out of the centre of
|
||||
-// the screen. That is the most expensive thing this port asks a present
|
||||
-// for: eight sprites are save-under drawn and restored every frame, and
|
||||
-// because a row's dirty band is one min..max interval, two stars far
|
||||
-// apart on the same row widen that row to nearly full width. Radially
|
||||
-// spreading stars maximise exactly that, so the warp ends up presenting
|
||||
-// close to the whole framebuffer every frame -- which is what makes it
|
||||
-// the slowest thing the IIgs does, and wasted work everywhere else.
|
||||
-//
|
||||
-// So the warp is not animated in pixels at all. The starfield is painted
|
||||
-// ONCE into screen RAM and animated by cycling the colours its cells are
|
||||
-// drawn in -- the same trick the title logo already uses
|
||||
-// (stRenderTitleLogo). A frame costs one palette upload and NO dirty
|
||||
-// pixel rows.
|
||||
-//
|
||||
-// The contract between the two halves is just this colour range:
|
||||
-// stTitle.c colours a star's cell ST_STAR_RING_FIRST + ring (ring 0 is
|
||||
-// the centre), and the renderer cycles that range. These are C64 colour
|
||||
-// indices the intro screen never uses -- it draws in 0, 3 and, in demo
|
||||
-// mode, 4 and 5; the cab sprite adds 2, 6 and 7.
|
||||
-#define ST_STAR_RING_FIRST 8u
|
||||
-#define ST_STAR_RING_COUNT 8u
|
||||
-
|
||||
-// The renderer wraps the sweep's phase with a mask rather than a
|
||||
-// modulo, so the ring count has to stay a power of two. Changing it to
|
||||
-// something else must fail to compile here instead of quietly making
|
||||
-// the warp skip rings.
|
||||
-typedef char stStarRingCountIsPow2[((ST_STAR_RING_COUNT & (ST_STAR_RING_COUNT - 1u)) == 0u) ? 1 : -1];
|
||||
-// Ring 0 is the centre, which in a warp is the VANISHING POINT: those
|
||||
-// stars are the far ones and get the small glyph. The big glyph belongs
|
||||
-// to the outer rings, the stars about to rush past the viewer.
|
||||
-#define ST_STAR_GLYPH_NEAR 0x2Au // '*', the outer rings
|
||||
-#define ST_STAR_GLYPH_FAR 0x2Eu // '.', the inner rings
|
||||
-#define ST_STAR_FAR_RINGS 5u // rings 0..4 are still distant
|
||||
+// The C64's level intro ($4666) streams seven hires star sprites --
|
||||
+// hardware sprites 0..6, all showing block $DA -- out of the centre of
|
||||
+// the screen: each waits out a random spawn delay ($44F8), then flies on
|
||||
+// its own fixed velocity ($450C / $4513) for $20 frames ($4500) and
|
||||
+// respawns near the centre.
|
||||
+#define ST_INTRO_STARS 7u
|
||||
+#define ST_INTRO_STAR_PTR 0xDAu // a hires 5x3-pixel star
|
||||
+#define ST_INTRO_STAR_COLOR 0x07u
|
||||
|
||||
// VIC sprite block pointers the game selects by name. The cab's bit 0 is
|
||||
// the landing gear, so each facing is a pair.
|
||||
@@ -423,6 +397,10 @@
|
||||
// 407 cells of colour RAM); part of the picture, so it survives the
|
||||
// same way colour RAM does.
|
||||
uint8_t logoColor;
|
||||
+ // $4500: frames until each intro star respawns. Neither $5EC4 nor the
|
||||
+ // intro ($4523 seeds only the spawn delays) clears it, so a star whose
|
||||
+ // delay draws 0 flies on whatever the previous intro left here.
|
||||
+ uint8_t starReload[ST_INTRO_STARS];
|
||||
|
||||
// ---- cleared by stSimNewGame from here down ----
|
||||
|
||||
@@ -550,6 +528,11 @@
|
||||
uint8_t introLoopCount; // $4508
|
||||
uint8_t introCabDx; // $450A
|
||||
uint8_t introCabDxTimer; // $450B
|
||||
+ uint8_t starTimer[ST_INTRO_STARS]; // $44F8: frames until the star first shows
|
||||
+ // The 6502 carry flag as the $4666 star loop finds it. The respawn
|
||||
+ // column is an ADC that never clears it first ($4684), so the flag
|
||||
+ // left by whatever ran before is part of where a star appears.
|
||||
+ uint8_t introCarry;
|
||||
uint8_t spriteMc0; // $D025
|
||||
uint8_t spriteMc1; // $D026
|
||||
|
||||
diff -ruN a/stTitle.c b/stTitle.c
|
||||
--- a/stTitle.c 2026-10-05 14:05:44.474069201 -0500
|
||||
+++ b/stTitle.c 2026-10-05 14:12:10.084072282 -0500
|
||||
@@ -11,11 +11,6 @@
|
||||
// Level intro ($4523..$4727): a black screen with the level name, the
|
||||
// cab rising from the bottom and seven star sprites streaming out of
|
||||
// the centre, until the cab reaches mid-screen.
|
||||
-//
|
||||
-// The seven star sprites are replaced by a static starfield whose
|
||||
-// colours are cycled -- see ST_STAR_RING_FIRST in stSim.h for why and
|
||||
-// how. The cab still flies, and it is still what ends the intro, so the
|
||||
-// sequence keeps its length and its cue; only the warp changes.
|
||||
|
||||
#include <string.h>
|
||||
|
||||
@@ -39,48 +34,36 @@
|
||||
#define ST_TITLE_CAB_LIFT_ROW 0x14u
|
||||
#define ST_TITLE_SPARKLE_TICKS 0x5Au
|
||||
#define ST_TITLE_HOVER_COL 0x28u
|
||||
-#define ST_INTRO_STAR_PTR 0xDAu
|
||||
+#define ST_INTRO_STAR_START_ROW 0x8Cu
|
||||
+#define ST_INTRO_STAR_START_COL 0xAAu
|
||||
+#define ST_INTRO_STAR_DELAY 0x20u // $4080 range of the spawn delay
|
||||
+#define ST_INTRO_STAR_RELOAD 0x20u // frames a star flies before respawning
|
||||
+// $467F..$4694: a respawning star's position comes from star 0's row --
|
||||
+// its low three bits pick the column, bits 4..5 the row.
|
||||
+#define ST_INTRO_SPAWN_COL 0xA6u
|
||||
+#define ST_INTRO_SPAWN_COL_BITS 0x07u
|
||||
+#define ST_INTRO_SPAWN_ROW 0x89u
|
||||
+#define ST_INTRO_SPAWN_ROW_BITS 0x30u
|
||||
+#define ST_INTRO_SPAWN_SHIFT 4u
|
||||
+#define ST_INTRO_CAB_START_ROW 0xE4u
|
||||
#define ST_INTRO_CAB_END_ROW 0x94u
|
||||
#define ST_INTRO_CAB_DX_RELOAD 0x28u
|
||||
|
||||
-// The palette-cycled starfield. Seven stars per ring so the bright band
|
||||
-// stays the same weight all the way out, placed at fixed pseudo-random
|
||||
-// angles and clear of the three lines the intro screen writes (the level
|
||||
-// name on row 12, and the demo hints on rows 3 and 24).
|
||||
-typedef struct {
|
||||
- uint8_t col;
|
||||
- uint8_t row;
|
||||
- uint8_t ring;
|
||||
-} StStarT;
|
||||
-
|
||||
-static const StStarT kIntroStar[] = {
|
||||
- { 18u, 10u, 0u }, { 19u, 10u, 0u }, { 17u, 11u, 0u }, { 18u, 13u, 0u },
|
||||
- { 21u, 13u, 0u }, { 22u, 14u, 0u }, { 18u, 15u, 0u }, { 18u, 8u, 1u },
|
||||
- { 19u, 8u, 1u }, { 14u, 10u, 1u }, { 25u, 14u, 1u }, { 21u, 15u, 1u },
|
||||
- { 25u, 15u, 1u }, { 20u, 17u, 1u }, { 26u, 7u, 2u }, { 12u, 10u, 2u },
|
||||
- { 29u, 11u, 2u }, { 11u, 13u, 2u }, { 29u, 14u, 2u }, { 21u, 17u, 2u },
|
||||
- { 23u, 18u, 2u }, { 21u, 4u, 3u }, { 24u, 4u, 3u }, { 11u, 8u, 3u },
|
||||
- { 12u, 8u, 3u }, { 10u, 10u, 3u }, { 30u, 16u, 3u }, { 14u, 17u, 3u },
|
||||
- { 15u, 2u, 4u }, { 8u, 8u, 4u }, { 32u, 9u, 4u }, { 9u, 17u, 4u },
|
||||
- { 32u, 17u, 4u }, { 32u, 18u, 4u }, { 24u, 22u, 4u }, { 16u, 1u, 5u },
|
||||
- { 28u, 1u, 5u }, { 31u, 2u, 5u }, { 36u, 13u, 5u }, { 6u, 15u, 5u },
|
||||
- { 4u, 16u, 5u }, { 5u, 19u, 5u }, { 8u, 0u, 6u }, { 30u, 0u, 6u },
|
||||
- { 1u, 8u, 6u }, { 2u, 12u, 6u }, { 37u, 16u, 6u }, { 7u, 21u, 6u },
|
||||
- { 32u, 22u, 6u }, { 0u, 4u, 7u }, { 39u, 7u, 7u }, { 0u, 10u, 7u },
|
||||
- { 38u, 20u, 7u }, { 38u, 21u, 7u }, { 4u, 22u, 7u }, { 35u, 22u, 7u },
|
||||
-};
|
||||
-#define ST_INTRO_STAR_COUNT (sizeof(kIntroStar) / sizeof(kIntroStar[0]))
|
||||
-
|
||||
-
|
||||
-static void addToRow(StSimT *sim, uint8_t idx, uint8_t delta);
|
||||
-static void logoColorCycle(StSimT *sim);
|
||||
-static void logoFlip(StSimT *sim);
|
||||
-static void paintStarfield(StSimT *sim);
|
||||
-
|
||||
-
|
||||
-// $4113
|
||||
-static void addToRow(StSimT *sim, uint8_t idx, uint8_t delta) {
|
||||
- sim->spr[idx].row = (uint8_t)(sim->spr[idx].row + delta);
|
||||
+
|
||||
+static uint8_t addToRow(StSimT *sim, uint8_t idx, uint8_t delta);
|
||||
+static void logoColorCycle(StSimT *sim);
|
||||
+static void logoFlip(StSimT *sim);
|
||||
+static void starRespawn(StSimT *sim, uint8_t idx, uint8_t carry);
|
||||
+static void starsStep(StSimT *sim);
|
||||
+
|
||||
+
|
||||
+// $4113 -- CLC; ADC row. The carry it leaves is the 6502 flag the star
|
||||
+// loop goes on with (see introCarry in stSim.h).
|
||||
+static uint8_t addToRow(StSimT *sim, uint8_t idx, uint8_t delta) {
|
||||
+ uint16_t row = (uint16_t)((uint16_t)sim->spr[idx].row + delta);
|
||||
+
|
||||
+ sim->spr[idx].row = (uint8_t)row;
|
||||
+ return (uint8_t)(row >> 8);
|
||||
}
|
||||
|
||||
|
||||
@@ -124,20 +107,47 @@
|
||||
}
|
||||
|
||||
|
||||
-// Paint the level-intro starfield into screen RAM, once. A star's cell
|
||||
-// is coloured ST_STAR_RING_FIRST + its ring and never touched again --
|
||||
-// the renderer animates the warp by cycling those colours, which costs
|
||||
-// no dirty rows at all. See ST_STAR_RING_FIRST in stSim.h.
|
||||
-static void paintStarfield(StSimT *sim) {
|
||||
- const StStarT *star;
|
||||
- uint8_t one[2];
|
||||
- uint8_t k;
|
||||
-
|
||||
- one[1] = 0u; // stSimDrawText stops below screen code 6
|
||||
- for (k = 0u; k < (uint8_t)ST_INTRO_STAR_COUNT; k++) {
|
||||
- star = &kIntroStar[k];
|
||||
- one[0] = (star->ring < ST_STAR_FAR_RINGS) ? ST_STAR_GLYPH_FAR : ST_STAR_GLYPH_NEAR;
|
||||
- stSimDrawText(sim, star->col, star->row, one, (uint8_t)(ST_STAR_RING_FIRST + star->ring));
|
||||
+// $467A -- a star (re)appears near the centre, placed from star 0's
|
||||
+// row. The column's ADC adds the carry the loop arrived with; the row's
|
||||
+// adds the bit the last LSR shifted out, which the mask keeps clear, and
|
||||
+// its sum stays below $100, so the loop goes on with the carry clear.
|
||||
+static void starRespawn(StSimT *sim, uint8_t idx, uint8_t carry) {
|
||||
+ uint8_t seed = sim->spr[0].row;
|
||||
+
|
||||
+ sim->spr[idx].msb = 0u;
|
||||
+ sim->spr[idx].col = (uint8_t)((seed & ST_INTRO_SPAWN_COL_BITS) + ST_INTRO_SPAWN_COL + carry);
|
||||
+ sim->spr[idx].row = (uint8_t)(((seed & ST_INTRO_SPAWN_ROW_BITS) >> ST_INTRO_SPAWN_SHIFT) + ST_INTRO_SPAWN_ROW);
|
||||
+ sim->starReload[idx] = ST_INTRO_STAR_RELOAD;
|
||||
+}
|
||||
+
|
||||
+
|
||||
+// $4662..$46A2 -- the seven stars, sprite 6 down to sprite 0. A star
|
||||
+// counts down its spawn delay and appears ($4675); from then on it flies
|
||||
+// on its own velocity ($4711) and respawns when its reload runs out.
|
||||
+// The carry threads through the loop exactly as the 6502 leaves it: a
|
||||
+// flight's row add sets it, a respawn clears it, a waiting star keeps it.
|
||||
+static void starsStep(StSimT *sim) {
|
||||
+ uint8_t carry = sim->introCarry;
|
||||
+ uint8_t k;
|
||||
+
|
||||
+ for (k = 0u; k < ST_INTRO_STARS; k++) {
|
||||
+ uint8_t idx = (uint8_t)(ST_INTRO_STARS - 1u - k);
|
||||
+ if (sim->starTimer[idx] == 0u) {
|
||||
+ stSimSpriteAddX(sim, idx, (uint8_t)stC64IntroStarDx(idx));
|
||||
+ carry = addToRow(sim, idx, (uint8_t)stC64IntroStarDy(idx));
|
||||
+ sim->starReload[idx]--;
|
||||
+ if (sim->starReload[idx] == 0u) {
|
||||
+ starRespawn(sim, idx, carry);
|
||||
+ carry = 0u;
|
||||
+ }
|
||||
+ continue;
|
||||
+ }
|
||||
+ sim->starTimer[idx]--;
|
||||
+ if (sim->starTimer[idx] == 0u) {
|
||||
+ sim->spr[idx].enable = 1u;
|
||||
+ starRespawn(sim, idx, carry);
|
||||
+ carry = 0u;
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,23 +168,20 @@
|
||||
sim->spr[7].col = 0xAAu;
|
||||
sim->spr[7].msb = 0u;
|
||||
sim->spr[7].color = 0x06u;
|
||||
- sim->spr[7].row = 0xE4u;
|
||||
+ sim->spr[7].row = ST_INTRO_CAB_START_ROW;
|
||||
sim->introCabDxTimer = 0x14u;
|
||||
sim->introCabDx = 0x01u;
|
||||
- for (k = 0u; k < 7u; k++) {
|
||||
- uint8_t idx = (uint8_t)(6u - k);
|
||||
- sim->spr[idx].row = 0x8Cu;
|
||||
+ for (k = 0u; k < ST_INTRO_STARS; k++) {
|
||||
+ uint8_t idx = (uint8_t)(ST_INTRO_STARS - 1u - k);
|
||||
+ sim->spr[idx].row = ST_INTRO_STAR_START_ROW;
|
||||
sim->spr[idx].msb = 0u;
|
||||
- sim->spr[idx].col = 0xAAu;
|
||||
- sim->spr[idx].color = 0x07u;
|
||||
+ sim->spr[idx].col = ST_INTRO_STAR_START_COL;
|
||||
+ sim->spr[idx].color = ST_INTRO_STAR_COLOR;
|
||||
sim->spr[idx].ptr = ST_INTRO_STAR_PTR;
|
||||
sim->spr[idx].enable = 0u;
|
||||
- // $44F8/$4500 held each star's spawn timer and reload. Nothing
|
||||
- // reads them now that the warp is painted rather than flown, but
|
||||
- // the seven RNG draws MUST stay: they advance the shared RNG and
|
||||
- // the level that follows draws from it, so dropping them would
|
||||
- // change the game that comes after the intro.
|
||||
- (void)stSimRng(sim, 0x20u);
|
||||
+ // These draws also advance the shared RNG the level that follows
|
||||
+ // draws from, so the next ride depends on them too.
|
||||
+ sim->starTimer[idx] = stSimRng(sim, ST_INTRO_STAR_DELAY);
|
||||
}
|
||||
sim->borderColor = 0u;
|
||||
sim->bgColor = 0u;
|
||||
@@ -188,23 +195,26 @@
|
||||
sim->charDirtyAll = true;
|
||||
sim->charDirtyCount = 0u;
|
||||
stSimDrawText(sim, 10u, 12u, level->name, 3u);
|
||||
- paintStarfield(sim);
|
||||
if (sim->demoMode != 0u && sim->postMortem == 0u) {
|
||||
stSimDrawText(sim, 7u, 3u, kTextDemoExit, 5u);
|
||||
stSimDrawText(sim, 3u, 24u, kTextScreens, 4u);
|
||||
}
|
||||
sim->introLoopCount = 0u;
|
||||
+ // $4662 is reached from $41C2's RTS, whose CMP #$06 on the text's
|
||||
+ // zero terminator leaves the carry clear.
|
||||
+ sim->introCarry = 0u;
|
||||
}
|
||||
|
||||
|
||||
-// $4666 -- one frame of the star field; true when the cab has risen
|
||||
+// $4662 -- one frame of the star field; true when the cab has risen
|
||||
// to mid-screen and everything is hidden again.
|
||||
bool stIntroStep(StSimT *sim) {
|
||||
uint8_t k;
|
||||
|
||||
+ starsStep(sim);
|
||||
stSimMarshal(sim);
|
||||
sim->introLoopCount = (uint8_t)((sim->introLoopCount + 1u) & 3u);
|
||||
- addToRow(sim, 7u, (uint8_t)kIntroCabDy[sim->introLoopCount]);
|
||||
+ (void)addToRow(sim, 7u, (uint8_t)kIntroCabDy[sim->introLoopCount]);
|
||||
if ((sim->introLoopCount & 1u) == 0u) {
|
||||
stSimSpriteAddX(sim, 7u, sim->introCabDx);
|
||||
sim->introCabDxTimer--;
|
||||
@@ -214,6 +224,9 @@
|
||||
sim->introCabDx ^= 0xFEu;
|
||||
}
|
||||
}
|
||||
+ // $46FC: CMP #$94. Its carry is what the next frame's star loop
|
||||
+ // starts with; the cab is still above the end row, so it is set.
|
||||
+ sim->introCarry = (sim->spr[7].row >= ST_INTRO_CAB_END_ROW) ? 1u : 0u;
|
||||
if (sim->spr[7].row != ST_INTRO_CAB_END_ROW) {
|
||||
return false;
|
||||
}
|
||||
@@ -300,7 +313,7 @@
|
||||
break;
|
||||
case ST_STAGE_RIDING:
|
||||
// $4A48: lift off under UP thrust until above row $14.
|
||||
- addToRow(sim, 0u, 0xFEu);
|
||||
+ (void)addToRow(sim, 0u, 0xFEu);
|
||||
sim->dirMask = 0x01u;
|
||||
sim->spr[0].ptr = 0xC0u;
|
||||
stSimFlameUpdate(sim);
|
||||
145
program.mk
Normal file
145
program.mk
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
# Space Taxi, described for JoeyLib's make/programs.mk (which lists what each
|
||||
# variable means): its sources, how it ships on each port, and how its DATA
|
||||
# is baked. Included by the Makefile beside it, on JoeyLib's make/project.mk.
|
||||
|
||||
STAXI_HOME := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
|
||||
|
||||
# The levels are extracted from a memory image of the original C64 game,
|
||||
# which is not part of the repository (scripts/importData.sh installs it as
|
||||
# stuff/raw.bin; STAXI_RAW_DUMP=FILE names another); without it the game
|
||||
# still builds, and only the levels and the per-level sprite banks made from
|
||||
# them are left out.
|
||||
STAXI_RAW_DUMP ?= $(STAXI_HOME)/stuff/raw.bin
|
||||
|
||||
PROGRAMS += STAXI
|
||||
STAXI_SRCS := $(addprefix $(STAXI_HOME)/,spacetaxi.c stLevel.c stLevelFile.c stRender.c stSim.c stFare.c stHooks.c stTitle.c stC64Data.c stCels.c stAudio.c stLevelCels.c)
|
||||
# The IIgs links in an order of its own, the one its images were measured in.
|
||||
STAXI_IIGS_SRCS := $(addprefix $(STAXI_HOME)/,stC64Data.c stCels.c stSim.c spacetaxi.c stLevel.c stLevelFile.c stRender.c stFare.c stHooks.c stTitle.c stAudio.c stLevelCels.c)
|
||||
# In a folder of its own with its DATA, so its megabyte of data never rides
|
||||
# onto every other program's disk.
|
||||
STAXI_FOLDER := yes
|
||||
STAXI_AMIGA_NAME := Taxi
|
||||
STAXI_TITLE := Space Taxi
|
||||
STAXI_HELP := Space Taxi: fly the taxi, pick up the fares, take them where they ask to go.
|
||||
# IIgs: large overflow segments. 26 bank-aligned segments starved the Memory
|
||||
# Manager, so NTP's 34K NewHandle failed and the IIgs build ran SILENT
|
||||
# (gNTPReady stayed 0). Freeing that memory lets audio initialise; the
|
||||
# latent crash that then surfaced is fixed in src/iigs/audio_full.c
|
||||
# (sfxTrigger now requires a running replayer).
|
||||
STAXI_IIGS_OVERFLOW_CAP := 0xF000
|
||||
STAXI_IIGS_GAME_DISK := yes
|
||||
# X68000: stripped, because the image it ships on is a 1232 KB Human68k
|
||||
# floppy and the unstripped binary is most of the volume on its own.
|
||||
STAXI_X68000_LDFLAGS := -s
|
||||
|
||||
# ---- DATA ---------------------------------------------------------------------
|
||||
# Each bake is made in STAXI_GEN (per target; safe to delete, the next build
|
||||
# recreates it), then what the game loads is copied into its DATA: the
|
||||
# levels, the cel bank, the per-level cel banks and the speech. Tiles and the
|
||||
# font come from the C64 data compiled into the binary (stC64Data.c).
|
||||
STAXI_GEN := $(PROJECT_BUILD)/generated/$(ASSET_PLATFORM)
|
||||
STAXI_RUN := $(call programDataDir,STAXI)
|
||||
STAXI_OPT := $(call programOptionalDir,STAXI)
|
||||
|
||||
# 24 lettered levels (A..X) from scenes 0..23 plus the title from scene 24,
|
||||
# extracted by assets/romToLevel.py, which runs the captured decompressor
|
||||
# through a Python 6502 emulator over the raw RAM dump and writes STL2 .dat
|
||||
# files directly. (mkstlevel + title.txt is no longer used: the C64 title
|
||||
# encodes its screen with the game's custom charset, so a hand-authored tile
|
||||
# layout with a different glyph mapping renders as garbage.)
|
||||
STAXI_LEVEL_NUMS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
||||
STAXI_LEVELS := $(foreach n,$(STAXI_LEVEL_NUMS),levels/level$(n).dat) levels/title.dat
|
||||
ROMTOLEVEL := $(STAXI_HOME)/assets/romToLevel.py
|
||||
|
||||
# Per-level hook-sprite banks: the cels stLevelCelList names for each level
|
||||
# that has hook sprites, baked from its level file and pre-compiled for this
|
||||
# target, so no cel is JIT-compiled mid-ride. Levels without hook sprites
|
||||
# have no bank, and a missing bank only makes the game compile that level's
|
||||
# cels at entry -- so they are optional data, which the IIgs data disk packs
|
||||
# last, while there is room.
|
||||
# ...except on the X68000, where they do not fit. The Human68k floppy is
|
||||
# 1232 KB and these eight banks are 787 KB of it (level09 alone is 236 KB,
|
||||
# level10 175, level17 165), which with the 296 KB binary, the levels and
|
||||
# the speech put the disk about 300 KB over however small speech gets.
|
||||
# stRenderLevelCels already handles their absence the right way: it builds
|
||||
# and compiles the level's cels at ENTRY, behind the LOADING bar, so the
|
||||
# ride itself still never compiles. That is also what spacetaxi.c's
|
||||
# codegen-arena comment says the 68k ports are meant to do -- their cels
|
||||
# are big and their JIT is fast. Ship them there again if the payload ever
|
||||
# moves to a hard disk image.
|
||||
STAXI_CEL_LEVELS := $(if $(filter x68000,$(PLATFORM)),,07 08 09 10 12 16 17 21)
|
||||
|
||||
# The C64-exact cel bank, generated by staxibake (from the shared cel table
|
||||
# stCels.c + C64 bitmaps) and compiled for this target by the host
|
||||
# spritebake, so the game loads its cels already compiled
|
||||
# (jlSpriteBankLoadPrecompiled) instead of JIT-compiling them at boot. The ST
|
||||
# is the one port whose .spc carries a reloc list (the shifted-DRAW thunk's
|
||||
# jmp to stSpriteDrawShiftWalker), re-stamped by the loader.
|
||||
STAXIBAKE := $(PROJECT_BUILD)/tools/staxibake
|
||||
STAXIBAKE_SRCS := $(STAXI_HOME)/tools/staxibake/staxibake.c $(addprefix $(STAXI_HOME)/,stCels.c stC64Data.c stLevelCels.c stLevelFile.c)
|
||||
|
||||
# Speech baked to sample bytes per passenger pitch (assets/genSpeechPcm.py):
|
||||
# baking them here instead of expanding bits at play time is what keeps "HEY
|
||||
# TAXI" from stalling the ride; the C64 run-length source stays in
|
||||
# stSpeechData.c as the input.
|
||||
# --single one polarity, 60 KB instead of 120 KB; the player negates the
|
||||
# samples itself when the running polarity is odd. Measured on a
|
||||
# stock 1 MB ST the two-polarity file cannot be allocated at all
|
||||
# ("speech alloc FAILED (120828 bytes)": the whole game mute),
|
||||
# and the X68000's 1232 KB floppy came up 18 KB short of it.
|
||||
# --biased unsigned samples, as the IIgs DOC plays them.
|
||||
STAXI_SPEECH_FLAGS_atarist := --single
|
||||
STAXI_SPEECH_FLAGS_x68000 := --single
|
||||
STAXI_SPEECH_FLAGS_iigs := --biased
|
||||
|
||||
STAXI_DATA := $(STAXI_RUN)/sprites/staxicels.spc $(STAXI_RUN)/speech.bin
|
||||
ifneq ($(wildcard $(STAXI_RAW_DUMP)),)
|
||||
STAXI_DATA += $(addprefix $(STAXI_RUN)/,$(STAXI_LEVELS)) $(foreach n,$(STAXI_CEL_LEVELS),$(STAXI_OPT)/sprites/level$(n).spc)
|
||||
else
|
||||
STAXI_DATA += staxi-no-levels
|
||||
endif
|
||||
|
||||
.PHONY: staxi-no-levels
|
||||
staxi-no-levels:
|
||||
@echo "note: Space Taxi levels skipped: $(STAXI_RAW_DUMP) is not present"
|
||||
|
||||
# The bakes are kept: they are what the DATA copies come from.
|
||||
.SECONDARY: $(addprefix $(STAXI_GEN)/,$(STAXI_LEVELS) sprites/staxicels.spr sprites/staxicels.spc speech.bin) \
|
||||
$(foreach n,$(STAXI_CEL_LEVELS),$(STAXI_GEN)/sprites/level$(n).spr $(STAXI_GEN)/sprites/level$(n).spc)
|
||||
|
||||
# Every level and the title in one run of the extractor: a grouped target
|
||||
# (&:, GNU Make 4.3+) runs its recipe once for the whole list.
|
||||
$(addprefix $(STAXI_GEN)/,$(STAXI_LEVELS)) &: $(STAXI_RAW_DUMP) $(ROMTOLEVEL)
|
||||
@mkdir -p $(STAXI_GEN)/levels
|
||||
python3 $(ROMTOLEVEL) $(STAXI_RAW_DUMP) $(STAXI_GEN)/levels
|
||||
|
||||
$(STAXI_GEN)/sprites/staxicels.spr: $(STAXIBAKE) $(STAXI_HOME)/stCels.c $(STAXI_HOME)/stC64Data.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(STAXIBAKE) $@
|
||||
|
||||
$(STAXI_GEN)/sprites/level%.spr: $(STAXI_GEN)/levels/level%.dat $(STAXIBAKE) $(STAXI_HOME)/stLevelCels.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(STAXIBAKE) --level $< $@
|
||||
|
||||
$(STAXI_GEN)/sprites/%.spc: $(STAXI_GEN)/sprites/%.spr $(SPRITEBAKE)
|
||||
@mkdir -p $(dir $@)
|
||||
$(SPRITEBAKE) $< $@
|
||||
|
||||
$(STAXI_GEN)/speech.bin: $(STAXI_HOME)/stSpeechData.c $(STAXI_HOME)/assets/genSpeechPcm.py
|
||||
@mkdir -p $(dir $@)
|
||||
python3 $(STAXI_HOME)/assets/genSpeechPcm.py $< $@ $(STAXI_SPEECH_FLAGS_$(ASSET_PLATFORM))
|
||||
|
||||
$(STAXI_RUN)/%: $(STAXI_GEN)/%
|
||||
@mkdir -p $(dir $@)
|
||||
cp $< $@
|
||||
|
||||
ifneq ($(STAXI_OPT),$(STAXI_RUN))
|
||||
$(STAXI_OPT)/%: $(STAXI_GEN)/%
|
||||
@mkdir -p $(dir $@)
|
||||
cp $< $@
|
||||
endif
|
||||
|
||||
# A plain host tool: the .spr it writes is the same for every target.
|
||||
$(STAXIBAKE): $(STAXIBAKE_SRCS)
|
||||
@mkdir -p $(dir $@)
|
||||
$(HOST_CC) $(HOST_CFLAGS) -I$(STAXI_HOME) $^ -o $@
|
||||
|
|
@ -4,10 +4,9 @@
|
|||
# jlTilePasteMono calls, intended every 4th frame -- stRender.c
|
||||
# titleAnimateLogo's `& 3u` divider mirroring the C64 original).
|
||||
#
|
||||
# Boots GS/OS under headless MAME with a WORK COPY of joey.2mg (must
|
||||
# contain STAXI + its DATA tree; build with
|
||||
# JOEY_DISK_EXAMPLES="STAXI" scripts/make-iigs-disk.sh),
|
||||
# launches STAXI via the standard Finder keystroke timeline, then
|
||||
# Boots GS/OS under headless MAME with a WORK COPY of the game's data
|
||||
# disk, build/iigs/bin/STAXI-data.2mg (STAXI + its DATA tree; make iigs
|
||||
# builds it), launches STAXI via the standard Finder keystroke timeline, then
|
||||
# samples 8 known logo tile cells from the SHR framebuffer EVERY frame.
|
||||
# Each frame the sampled 256 bytes fold into a rolling hash; a hash
|
||||
# change means the logo visibly changed on screen (the flip-book tile
|
||||
|
|
@ -22,10 +21,12 @@
|
|||
# MAME_ROMPATH override ROM dir (default ~/.mame/roms)
|
||||
# STAXI_MAX_FRAMES frame cap (default 15000)
|
||||
# STAXI_MAX_CHANGES stop after this many logo changes (default 80)
|
||||
# JOEYLIB the JoeyLib checkout (default ../joeylib)
|
||||
set -euo pipefail
|
||||
|
||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
source "$repo/scripts/lib/joeyEnv.sh"
|
||||
JOEYLIB=${JOEYLIB:-$repo/../joeylib}
|
||||
source "$JOEYLIB/scripts/lib/joeyEnv.sh"
|
||||
joeyNeed LLVM816_ROOT toolchain:iigs
|
||||
joeyNeed CADIUS cadius
|
||||
joeyNeed MAME mame
|
||||
|
|
@ -33,7 +34,7 @@ joeyNeed IIGS_ROMPATH iigsFirmware
|
|||
joeyNeed GSOS_SYSTEM_DISK iigsFirmware
|
||||
|
||||
sys_disk=$GSOS_SYSTEM_DISK
|
||||
data_disk=$repo/build/iigs/bin/joey.2mg
|
||||
data_disk=$repo/build/iigs/bin/STAXI-data.2mg
|
||||
rompath="$IIGS_ROMPATH"
|
||||
maxFrames="${STAXI_MAX_FRAMES:-15000}"
|
||||
maxChanges="${STAXI_MAX_CHANGES:-80}"
|
||||
|
|
@ -43,7 +44,7 @@ for f in "$sys_disk" "$data_disk"; do
|
|||
[ -f "$f" ] || { echo "bench-staxi-title: missing $f" >&2; exit 2; }
|
||||
done
|
||||
if ! "$CADIUS" CATALOG "$data_disk" 2>/dev/null | grep -qE '^ STAXI +S16'; then
|
||||
echo "bench-staxi-title: STAXI not on joey.2mg (JOEY_DISK_EXAMPLES=\"STAXI\" scripts/make-iigs-disk.sh)" >&2
|
||||
echo "bench-staxi-title: STAXI not on $data_disk (make iigs)" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,15 @@
|
|||
# diag-staxi-screen.sh - one-off diagnostic for the STAXI title test:
|
||||
# boot + launch STAXI, dump the full SHR framebuffer + palettes as hex
|
||||
# at a target frame, then extract JOEYLOG.TXT from the work disk so the
|
||||
# game's own init/asset-load log is visible. Outputs land in
|
||||
# build/iigs/bin/ (staxi-shr.hex, staxi-joeylog.txt).
|
||||
# game's own init/asset-load log is visible. Boots a work copy of the
|
||||
# game's data disk, build/iigs/bin/STAXI-data.2mg (make iigs). Outputs land
|
||||
# in build/iigs/bin/ (staxi-shr.hex, staxi-joeylog.txt). JOEYLIB names the
|
||||
# JoeyLib checkout (default ../joeylib).
|
||||
set -euo pipefail
|
||||
|
||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
source "$repo/scripts/lib/joeyEnv.sh"
|
||||
JOEYLIB=${JOEYLIB:-$repo/../joeylib}
|
||||
source "$JOEYLIB/scripts/lib/joeyEnv.sh"
|
||||
joeyNeed LLVM816_ROOT toolchain:iigs
|
||||
joeyNeed CADIUS cadius
|
||||
joeyNeed MAME mame
|
||||
|
|
@ -15,7 +18,7 @@ joeyNeed IIGS_ROMPATH iigsFirmware
|
|||
joeyNeed GSOS_SYSTEM_DISK iigsFirmware
|
||||
|
||||
sys_disk=$GSOS_SYSTEM_DISK
|
||||
data_disk=$repo/build/iigs/bin/joey.2mg
|
||||
data_disk=$repo/build/iigs/bin/STAXI-data.2mg
|
||||
rompath="$IIGS_ROMPATH"
|
||||
dumpFrame="${STAXI_DUMP_FRAME:-9000}"
|
||||
|
||||
|
|
|
|||
92
scripts/importData.sh
Executable file
92
scripts/importData.sh
Executable file
|
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/env bash
|
||||
# importData.sh - install the one file Space Taxi needs that is not in the
|
||||
# repository: a memory image of the original Commodore 64 game, which the
|
||||
# build extracts the 24 levels and the title screen from (program.mk). It is
|
||||
# never downloaded from anywhere: you supply your copy, and it is recognised
|
||||
# by its checksum, so its file name does not matter.
|
||||
#
|
||||
# Usage: scripts/importData.sh PATH...
|
||||
# PATH the image itself, a folder holding it, or a .zip or
|
||||
# .tar.gz archive with it inside (such as the owner's
|
||||
# private bundle)
|
||||
# Installs it as stuff/raw.bin. stuff/ is never committed: the image is a
|
||||
# copy of a commercial game.
|
||||
set -euo pipefail
|
||||
|
||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
dest=$repo/stuff/raw.bin
|
||||
# sha1 of the C64 memory image the levels are extracted from.
|
||||
wantSha1=5bfa4ba70431135575dab610a73dde02b8095953
|
||||
work=""
|
||||
# The PATH being scanned, for the messages.
|
||||
origin=""
|
||||
|
||||
|
||||
# cleanup -- remove the scratch folder archives are unpacked into.
|
||||
cleanup() {
|
||||
if [ -n "$work" ]; then
|
||||
rm -rf "$work"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# isImage FILE -- true when FILE is the image.
|
||||
isImage() {
|
||||
[ "$(sha1sum < "$1" | cut -d' ' -f1)" = "$wantSha1" ]
|
||||
}
|
||||
|
||||
|
||||
# scanFile FILE -- install FILE if it is the image, or the image from inside
|
||||
# FILE when it is an archive.
|
||||
scanFile() {
|
||||
local unpacked
|
||||
if isImage "$1"; then
|
||||
install -D -m 644 "$1" "$dest"
|
||||
echo "importData: installed $dest from $origin"
|
||||
exit 0
|
||||
fi
|
||||
case "$1" in
|
||||
*.zip|*.ZIP|*.tar.gz|*.tgz)
|
||||
unpacked=$(mktemp -d -p "$work")
|
||||
case "$1" in
|
||||
*.zip|*.ZIP) unzip -qq -o "$1" -d "$unpacked" 2>/dev/null || true ;;
|
||||
*) tar -xzf "$1" -C "$unpacked" 2>/dev/null || true ;;
|
||||
esac
|
||||
scanTree "$unpacked"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
# scanTree DIR -- install the image if any file under DIR is it.
|
||||
scanTree() {
|
||||
local file
|
||||
while IFS= read -r -d '' file; do
|
||||
scanFile "$file"
|
||||
done < <(find "$1" -type f -print0)
|
||||
}
|
||||
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
sed -n '2,13p' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 2
|
||||
fi
|
||||
if [ -f "$dest" ] && isImage "$dest"; then
|
||||
echo "importData: $dest is already in place"
|
||||
exit 0
|
||||
fi
|
||||
work=$(mktemp -d)
|
||||
trap cleanup EXIT
|
||||
for path in "$@"; do
|
||||
origin=$path
|
||||
if [ -d "$path" ]; then
|
||||
scanTree "$path"
|
||||
elif [ -f "$path" ]; then
|
||||
scanFile "$path"
|
||||
else
|
||||
echo "importData: no such file or folder: $path" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
echo "importData: none of $* is the Space Taxi image (sha1 $wantSha1)" >&2
|
||||
exit 1
|
||||
100
scripts/verify.sh
Executable file
100
scripts/verify.sh
Executable file
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env bash
|
||||
# verify.sh - Space Taxi's gates, run one after another, with a summary.
|
||||
#
|
||||
# stsim every hook level's 4000-tick simulation trace is identical to
|
||||
# its golden (tools/stsim/gate.sh), and every cel a ride shows
|
||||
# is baked (tools/stsim/celGate.sh)
|
||||
# harness the front-end screens above the simulation -- the high-score
|
||||
# table, name entry and its save -- on JoeyLib's host BLANK
|
||||
# port (tools/staxiharness)
|
||||
# disk the bootable IIgs volume boots on a ROM 3 machine, launches
|
||||
# the game and draws (JoeyLib's verify-iigs-game-disk.sh)
|
||||
# disk01 the same on ROM 01: the volume keeps the ROM 01 tool patches
|
||||
# iigsaudio the IIgs sound interrupt is NTP's, the DOC's master volume is
|
||||
# up and the sound slots carry waveforms (JoeyLib's
|
||||
# verify-iigs-audio.sh)
|
||||
# x68kaudio every YM2151 noise write is a code from the port's table and
|
||||
# the ADPCM is fed at 7812 Hz to both speakers (JoeyLib's
|
||||
# verify-x68000-audio.sh); the attract demo plays the whole
|
||||
# noise curve and speaks
|
||||
#
|
||||
# Usage: scripts/verify.sh [gate...] every gate when none is named
|
||||
# Env: JOEYLIB the JoeyLib checkout (default ../joeylib)
|
||||
# The gates check what is built: run make first (make check does both).
|
||||
#
|
||||
# Exit codes:
|
||||
# 0 every gate passed
|
||||
# 1 a gate failed
|
||||
# 3 none failed, but one was skipped for a missing prerequisite (an
|
||||
# emulator, a target not built, the game data not imported) -- not 0,
|
||||
# so a run that tested nothing cannot look green
|
||||
# A gate exits 2 to say it was skipped.
|
||||
set -uo pipefail
|
||||
|
||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
JOEYLIB=$(cd "${JOEYLIB:-$repo/../joeylib}" && pwd) || { echo "verify: no JoeyLib checkout (set JOEYLIB)" >&2; exit 2; }
|
||||
export JOEYLIB
|
||||
bin=$repo/build
|
||||
harnessLevels=$bin/generated/dos/levels/title.dat
|
||||
|
||||
# name command...: every gate, in run order.
|
||||
GATES=$(cat <<EOF
|
||||
stsim tools/stsim/gate.sh check
|
||||
harness [ -f $harnessLevels ] || { echo "harness: the DOS bake is not built (make dos)" >&2; exit 2; }; make -s -C tools/staxiharness JOEYLIB=$JOEYLIB || exit 1
|
||||
disk $JOEYLIB/scripts/verify-iigs-game-disk.sh $bin/iigs/bin/STAXI.2mg
|
||||
disk01 GAME_VERIFY_MACHINE=apple2gsr1 $JOEYLIB/scripts/verify-iigs-game-disk.sh $bin/iigs/bin/STAXI.2mg
|
||||
iigsaudio $JOEYLIB/scripts/verify-iigs-audio.sh $bin/iigs/bin/STAXI
|
||||
x68kaudio $JOEYLIB/scripts/verify-x68000-audio.sh $bin/x68000/bin/STAXI/STAXI.X
|
||||
EOF
|
||||
)
|
||||
|
||||
names=$(echo "$GATES" | awk '{ print $1 }' | tr '\n' ' ')
|
||||
for want in "$@"; do
|
||||
case " $names " in
|
||||
*" $want "*) ;;
|
||||
*) echo "verify: unknown gate '$want' (choose from: $names)" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
selected=${*:-$names}
|
||||
|
||||
gateNames=()
|
||||
gateVerdicts=()
|
||||
gateSeconds=()
|
||||
passed=0
|
||||
failed=0
|
||||
skipped=0
|
||||
|
||||
while read -r name cmd; do
|
||||
case " $selected " in *" $name "*) ;; *) continue ;; esac
|
||||
echo
|
||||
echo "=== verify: $name ($cmd) ==="
|
||||
start=$SECONDS
|
||||
( cd "$repo" && bash -c "$cmd" ) < /dev/null
|
||||
case $? in
|
||||
0) verdict=PASS; passed=$((passed + 1)) ;;
|
||||
2) verdict="SKIP (prerequisite missing)"; skipped=$((skipped + 1)) ;;
|
||||
*) verdict=FAIL; failed=$((failed + 1)) ;;
|
||||
esac
|
||||
gateNames+=("$name")
|
||||
gateVerdicts+=("$verdict")
|
||||
gateSeconds+=("$((SECONDS - start))")
|
||||
done <<< "$GATES"
|
||||
|
||||
echo
|
||||
echo "================ verify summary ================"
|
||||
for i in "${!gateNames[@]}"; do
|
||||
printf " %-10s %-28s %4ds\n" "${gateNames[$i]}" "${gateVerdicts[$i]}" "${gateSeconds[$i]}"
|
||||
done
|
||||
echo " ----"
|
||||
printf " %d passed, %d failed, %d skipped\n" "$passed" "$failed" "$skipped"
|
||||
|
||||
if [ "$failed" -gt 0 ]; then
|
||||
echo "verify: FAIL" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$skipped" -gt 0 ]; then
|
||||
echo "verify: INCOMPLETE - some gates skipped (see above)" >&2
|
||||
exit 3
|
||||
fi
|
||||
echo "verify: PASS (all $passed gates)"
|
||||
exit 0
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Generated by assets/genC64Data.py from stuff/spacetaxi/raw.bin.
|
||||
// Generated by assets/genC64Data.py from stuff/raw.bin.
|
||||
// Do not hand-edit; re-run the generator.
|
||||
|
||||
#include "stC64Data.h"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Generated by assets/genC64Data.py from stuff/spacetaxi/raw.bin.
|
||||
// Generated by assets/genC64Data.py from stuff/raw.bin.
|
||||
// Do not hand-edit; re-run the generator.
|
||||
|
||||
#ifndef ST_C64_DATA_H
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Space Taxi -- STL4 level file parser (plain stdio, no JoeyLib), shared
|
||||
// by the game and the host-side simulation harness.
|
||||
//
|
||||
// Layout (all bytes, written by examples/spacetaxi/assets/romToLevel.py):
|
||||
// Layout (all bytes, written by assets/romToLevel.py):
|
||||
// "STL4"
|
||||
// name[22] $7D78 level name in screen codes
|
||||
// header[9] $7D00 VIC colour block
|
||||
|
|
|
|||
2
stSim.h
2
stSim.h
|
|
@ -14,7 +14,7 @@
|
|||
// declared at the bottom, which each host implements.
|
||||
//
|
||||
// Address comments ($xxxx) name the original variable or routine a
|
||||
// field or function mirrors; see stuff/spacetaxi/labels.txt.
|
||||
// field or function mirrors; see stuff/labels.txt.
|
||||
|
||||
#ifndef ST_SIM_H
|
||||
#define ST_SIM_H
|
||||
|
|
|
|||
|
|
@ -1,35 +1,42 @@
|
|||
# Host-side checks of Space Taxi's front-end screens (the parts tools/stsim
|
||||
# cannot reach: they live in spacetaxi.c, above the simulation). Builds the
|
||||
# game on the BLANK port with spacetaxi.c INCLUDED into the harness so its
|
||||
# statics are reachable, and wraps the keyboard queue and the save file
|
||||
# game on JoeyLib's BLANK port with spacetaxi.c INCLUDED into the harness so
|
||||
# its statics are reachable, and wraps the keyboard queue and the save file
|
||||
# with scripted stand-ins.
|
||||
#
|
||||
# make -C tools/staxiharness build + run
|
||||
#
|
||||
# JOEYLIB is the JoeyLib checkout, as an absolute path (default: joeylib
|
||||
# beside this repository). The title screen comes from the game's DOS bake,
|
||||
# so build the game for DOS first.
|
||||
|
||||
REPO := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/../..)
|
||||
ST := $(REPO)/examples/spacetaxi
|
||||
OUT := $(REPO)/build/tools/staxiharness
|
||||
SRCS := $(filter-out $(ST)/spacetaxi.c,$(wildcard $(ST)/*.c)) \
|
||||
$(wildcard $(REPO)/src/core/*.c) $(wildcard $(REPO)/src/generic/*.c) \
|
||||
$(REPO)/src/blank/blank.c
|
||||
CFLAGS := -O0 -g -w -DJOEYLIB_PLATFORM_BLANK -I$(REPO)/include -I$(REPO)/src/core \
|
||||
-I$(REPO)/src/codegen -I$(ST)
|
||||
WRAPS := -Wl,--wrap=jlInputGetChar -Wl,--wrap=jlSaveWrite -Wl,--wrap=jlSaveRead
|
||||
REPO := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/../..)
|
||||
JOEYLIB ?= $(REPO)/../joeylib
|
||||
JL := $(abspath $(JOEYLIB))
|
||||
GEN := $(REPO)/build/generated/dos
|
||||
OUT := $(REPO)/build/tools/staxiharness
|
||||
SRCS := $(filter-out $(REPO)/spacetaxi.c,$(wildcard $(REPO)/*.c)) \
|
||||
$(wildcard $(JL)/src/core/*.c) $(wildcard $(JL)/src/generic/*.c) \
|
||||
$(JL)/src/blank/blank.c
|
||||
CFLAGS := -O0 -g -w -DJOEYLIB_PLATFORM_BLANK -I$(JL)/include -I$(JL)/src/core \
|
||||
-I$(JL)/src/codegen -I$(REPO)
|
||||
WRAPS := -Wl,--wrap=jlInputGetChar -Wl,--wrap=jlSaveWrite -Wl,--wrap=jlSaveRead
|
||||
|
||||
.PHONY: all run clean
|
||||
|
||||
all: run
|
||||
|
||||
$(OUT)/harness: harness.c $(ST)/spacetaxi.c $(SRCS)
|
||||
$(OUT)/harness: harness.c $(REPO)/spacetaxi.c $(SRCS)
|
||||
@mkdir -p $(OUT)
|
||||
$(CC) $(CFLAGS) harness.c $(SRCS) $(WRAPS) -o $@
|
||||
|
||||
# The title screen's level file is read through DATA/levels (the DOS bake
|
||||
# is the cross-platform STL4 form), so run from a directory that has one.
|
||||
run: $(OUT)/harness
|
||||
@test -f $(GEN)/levels/title.dat || { echo "staxiharness: no $(GEN)/levels/title.dat (build the game for DOS first)" >&2; exit 2; }
|
||||
@mkdir -p $(OUT)/DATA
|
||||
@ln -sfn $(ST)/generated/dos/levels $(OUT)/DATA/levels
|
||||
@ln -sfn $(ST)/generated/dos/speech.bin $(OUT)/DATA/speech.bin
|
||||
@ln -sfn $(GEN)/levels $(OUT)/DATA/levels
|
||||
@ln -sfn $(GEN)/speech.bin $(OUT)/DATA/speech.bin
|
||||
cd $(OUT) && ./harness
|
||||
|
||||
clean:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# Host build of the Space Taxi simulation core for trace comparison.
|
||||
ST := ../../examples/spacetaxi
|
||||
ST := ../..
|
||||
CC ?= gcc
|
||||
CFLAGS := -O1 -g -Wall -Wextra -std=c99 -I$(ST)
|
||||
SRCS := stsim.c $(ST)/stSim.c $(ST)/stFare.c $(ST)/stHooks.c $(ST)/stLevelFile.c $(ST)/stTitle.c $(ST)/stC64Data.c $(ST)/stLevelCels.c
|
||||
|
|
|
|||
|
|
@ -9,12 +9,7 @@
|
|||
set -u
|
||||
|
||||
here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
repo=$(cd "$here/../.." && pwd)
|
||||
levels=$repo/examples/spacetaxi/generated/iigs/levels
|
||||
ticks=4000
|
||||
pairs="H:08 T:20 W:23 X:24 G:07 I:09 K:11 O:15 Q:17 R:18 U:21 V:22 E:05 J:10 L:12 P:16 S:19"
|
||||
|
||||
make -C "$here" >/dev/null || { echo "celGate: build failed" >&2; exit 2; }
|
||||
source "$here/gateLib.sh"
|
||||
|
||||
fail=0
|
||||
for p in $pairs; do
|
||||
|
|
|
|||
|
|
@ -10,23 +10,19 @@
|
|||
# gateStreams.h -- a regression gate only, not evidence of C64 fidelity.
|
||||
#
|
||||
# gate.sh capture [dir] record goldens from the CURRENT tree
|
||||
# gate.sh check [dir] compare the current tree against them
|
||||
# gate.sh check [dir] compare the current tree against them, or, with
|
||||
# none captured, against gateHashes.txt (the md5
|
||||
# of each golden trace, the committed reference)
|
||||
# gate.sh hashes print one md5 per level (compact reference)
|
||||
#
|
||||
# Capture from a tree you trust, refactor, then check.
|
||||
# Capture from a tree you trust, refactor, then check. The rides run on the
|
||||
# game's level bake, so build the game first (gateLib.sh finds it).
|
||||
set -u
|
||||
|
||||
here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
repo=$(cd "$here/../.." && pwd)
|
||||
levels=$repo/examples/spacetaxi/generated/iigs/levels
|
||||
mode=${1:-check}
|
||||
dir=${2:-$here/golden}
|
||||
ticks=4000
|
||||
|
||||
# level letter -> level file number
|
||||
pairs="H:08 T:20 W:23 X:24 G:07 I:09 K:11 O:15 Q:17 R:18 U:21 V:22 E:05 J:10 L:12 P:16 S:19"
|
||||
|
||||
make -C "$here" >/dev/null || { echo "gate: build failed" >&2; exit 2; }
|
||||
source "$here/gateLib.sh"
|
||||
|
||||
case "$mode" in
|
||||
capture)
|
||||
|
|
@ -44,14 +40,23 @@ check)
|
|||
l=${p%%:*}; n=${p##*:}
|
||||
got=$(mktemp)
|
||||
"$here/stsim" "$levels/level$n.dat" "$l" "$ticks" > "$got" 2>/dev/null
|
||||
if [ ! -f "$dir/demo$l.jsonl" ]; then
|
||||
echo " $l: no golden (run: gate.sh capture)"
|
||||
elif cmp -s "$dir/demo$l.jsonl" "$got"; then
|
||||
printf ' %s: IDENTICAL (%s ticks)\n' "$l" "$(wc -l < "$got")"
|
||||
if [ -f "$dir/demo$l.jsonl" ]; then
|
||||
if cmp -s "$dir/demo$l.jsonl" "$got"; then
|
||||
printf ' %s: IDENTICAL (%s ticks)\n' "$l" "$(wc -l < "$got")"
|
||||
else
|
||||
echo " $l: *** DIFFERS ***"
|
||||
diff "$dir/demo$l.jsonl" "$got" | head -3
|
||||
fail=1
|
||||
fi
|
||||
else
|
||||
echo " $l: *** DIFFERS ***"
|
||||
diff "$dir/demo$l.jsonl" "$got" | head -3
|
||||
fail=1
|
||||
want=$(awk -v l="$l" '$1 == l { print $2 }' "$here/gateHashes.txt")
|
||||
have=$(md5sum < "$got" | cut -d' ' -f1)
|
||||
if [ "$have" = "$want" ]; then
|
||||
printf ' %s: IDENTICAL (%s ticks, md5 as gateHashes.txt)\n' "$l" "$(wc -l < "$got")"
|
||||
else
|
||||
echo " $l: *** DIFFERS *** (md5 $have, gateHashes.txt ${want:-has none}; capture goldens from a trusted tree to see where)"
|
||||
fail=1
|
||||
fi
|
||||
fi
|
||||
rm -f "$got"
|
||||
done
|
||||
|
|
|
|||
25
tools/stsim/gateLib.sh
Normal file
25
tools/stsim/gateLib.sh
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Sourced by gate.sh and celGate.sh, which set $here first: the rides both
|
||||
# gates replay, and the levels they replay them on.
|
||||
|
||||
# Every hook level as letter:level file number, and how long each ride runs.
|
||||
ticks=4000
|
||||
pairs="H:08 T:20 W:23 X:24 G:07 I:09 K:11 O:15 Q:17 R:18 U:21 V:22 E:05 J:10 L:12 P:16 S:19"
|
||||
|
||||
# The game's own level bake (program.mk). It is the same STL4 bytes for every
|
||||
# target, so whichever target was built first will do; STSIM_LEVELS names
|
||||
# another folder.
|
||||
levels=${STSIM_LEVELS:-}
|
||||
if [ -z "$levels" ]; then
|
||||
for port in iigs dos amiga atarist x68000; do
|
||||
if [ -f "$here/../../build/generated/$port/levels/level01.dat" ]; then
|
||||
levels=$(cd "$here/../../build/generated/$port/levels" && pwd)
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if [ -z "$levels" ]; then
|
||||
echo "$(basename "$0"): no level bake in build/generated/<port>/levels (scripts/importData.sh, then make)" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
make -C "$here" >/dev/null || { echo "$(basename "$0"): building stsim failed" >&2; exit 2; }
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Synthesised demo input streams -- a TEST FIXTURE, not game content.
|
||||
//
|
||||
// The C64 ships attract recordings for four screens only (H, W, T and X);
|
||||
// those live in examples/spacetaxi/stDemoStreams.h and are the streams
|
||||
// those live in stDemoStreams.h and are the streams
|
||||
// checked against VICE traces. The thirteen streams below were searched for
|
||||
// by tools/stsim's recorder against this port's own simulation, scored on
|
||||
// how long the cab survives and how much of the level's hook state, fare
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
//
|
||||
// Loads an STL4 level, attaches a recorded demo input stream and runs
|
||||
// the game tick, printing one line of state per tick in the same shape
|
||||
// the VICE tracer records (see stuff/spacetaxi and the compare script),
|
||||
// the VICE tracer records (see stuff/ and the compare script),
|
||||
// so the port's engine can be diffed against the real machine.
|
||||
//
|
||||
// stsim <level.dat> <level letter> <ticks>
|
||||
|
|
@ -14,9 +14,9 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../../examples/spacetaxi/stSim.h"
|
||||
#include "../../examples/spacetaxi/stCels.h"
|
||||
#include "../../examples/spacetaxi/stDemoStreams.h"
|
||||
#include "stSim.h"
|
||||
#include "stCels.h"
|
||||
#include "stDemoStreams.h"
|
||||
#include "gateStreams.h"
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue