Tile generation tests.
This commit is contained in:
parent
fe84be766f
commit
2c8d726ed7
84 changed files with 4137 additions and 2318 deletions
12
Makefile
12
Makefile
|
|
@ -8,6 +8,7 @@
|
||||||
# make dos builds MS-DOS only
|
# make dos builds MS-DOS only
|
||||||
# make x68000 builds Sharp X68000 only
|
# make x68000 builds Sharp X68000 only
|
||||||
# make docs renders the manual to build/docs (HTML + PDF)
|
# make docs renders the manual to build/docs (HTML + PDF)
|
||||||
|
# make verify builds every port, then runs every gate (needs emulators)
|
||||||
# make verify-audio runs every port's audio gate (needs emulators)
|
# make verify-audio runs every port's audio gate (needs emulators)
|
||||||
# make verify-serial runs every port's serial loopback gate
|
# make verify-serial runs every port's serial loopback gate
|
||||||
# make clean removes all build outputs
|
# make clean removes all build outputs
|
||||||
|
|
@ -44,7 +45,7 @@ ifeq ($(HAVE_X68000),1)
|
||||||
ALL_TARGETS += x68000
|
ALL_TARGETS += x68000
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: all iigs iigs-disk iigs-verify amiga atarist dos x68000 blank-check tools clean help status verify-audio verify-serial docs
|
.PHONY: all iigs iigs-disk iigs-verify amiga atarist dos x68000 blank-check tools clean help status verify verify-audio verify-serial docs
|
||||||
|
|
||||||
all: $(ALL_TARGETS) tools
|
all: $(ALL_TARGETS) tools
|
||||||
ifeq ($(ALL_TARGETS),)
|
ifeq ($(ALL_TARGETS),)
|
||||||
|
|
@ -87,6 +88,14 @@ blank-check:
|
||||||
docs:
|
docs:
|
||||||
@$(REPO_DIR)/scripts/build-docs.sh $(FORMAT)
|
@$(REPO_DIR)/scripts/build-docs.sh $(FORMAT)
|
||||||
|
|
||||||
|
# EVERYTHING: build every port, then run every gate -- host checks, UBER
|
||||||
|
# golden hashes, the IIgs example boots, input, audio, serial, the X68000
|
||||||
|
# mouse, IIgs save / serial loss / game disks -- sequentially, with a
|
||||||
|
# per-gate summary. Same exit codes as below. Takes a subset of ports:
|
||||||
|
# make verify PORTS="host dos"
|
||||||
|
verify:
|
||||||
|
@$(REPO_DIR)/scripts/verify-all.sh all $(PORTS)
|
||||||
|
|
||||||
# Every port's audio gate, run sequentially (the recording gates grade
|
# Every port's audio gate, run sequentially (the recording gates grade
|
||||||
# fixed time windows, so a competing emulator can slide a phase boundary).
|
# fixed time windows, so a competing emulator can slide a phase boundary).
|
||||||
# Exits 1 on a real failure and 3 if a gate SKIPPED for an unbuilt port --
|
# Exits 1 on a real failure and 3 if a gate SKIPPED for an unbuilt port --
|
||||||
|
|
@ -116,6 +125,7 @@ help:
|
||||||
@echo "Targets: iigs amiga atarist dos x68000 all clean status help"
|
@echo "Targets: iigs amiga atarist dos x68000 all clean status help"
|
||||||
@echo " iigs-disk iigs-verify (IIgs MAME visual check)"
|
@echo " iigs-disk iigs-verify (IIgs MAME visual check)"
|
||||||
@echo " blank-check (host-cc smoke test of the new-port template + gnu89 lint)"
|
@echo " blank-check (host-cc smoke test of the new-port template + gnu89 lint)"
|
||||||
|
@echo " verify (build every port, then run every gate; PORTS= subset)"
|
||||||
@echo " verify-audio (all 5 audio gates; PORTS=\"atarist amiga\" for a subset)"
|
@echo " verify-audio (all 5 audio gates; PORTS=\"atarist amiga\" for a subset)"
|
||||||
@echo " verify-serial (host check + all 5 serial loopback gates; PORTS= subset)"
|
@echo " verify-serial (host check + all 5 serial loopback gates; PORTS= subset)"
|
||||||
@echo " docs (manual to build/docs; FORMAT=html for HTML only)"
|
@echo " docs (manual to build/docs; FORMAT=html for HTML only)"
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ hand, `source toolchains/env.sh` sets the same variables.
|
||||||
|
|
||||||
`make` builds `libjoey.a` for every port whose toolchain is installed, plus
|
`make` builds `libjoey.a` for every port whose toolchain is installed, plus
|
||||||
the example programs (`hello`, `pattern`, `draw`, `keys`, `serial`,
|
the example programs (`hello`, `pattern`, `draw`, `keys`, `serial`,
|
||||||
`sertest`, `save`, `joy`, `sprite`, `audio`, `uber`, `adventure`,
|
`sertest`, `save`, `tilebank`, `joy`, `sprite`, `audio`, `uber`, `adventure`,
|
||||||
`adventure2`, `agi`, `spacetaxi`) for each. The IIgs is the exception:
|
`adventure2`, `agi`, `spacetaxi`) for each. The IIgs is the exception:
|
||||||
`make iigs` builds the library, and `make iigs-disk` builds its examples and
|
`make iigs` builds the library, and `make iigs-disk` builds its examples and
|
||||||
packages them onto a disk image.
|
packages them onto a disk image.
|
||||||
|
|
@ -161,7 +161,6 @@ is a quick reference. Every entry point is plain C, no C++ extensions.
|
||||||
```c
|
```c
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t codegenBytes; // runtime compiled-sprite cache size
|
uint32_t codegenBytes; // runtime compiled-sprite cache size
|
||||||
uint32_t audioBytes; // reserved; not yet consulted by any engine
|
|
||||||
} jlConfigT;
|
} jlConfigT;
|
||||||
|
|
||||||
bool jlInit (const jlConfigT *config);
|
bool jlInit (const jlConfigT *config);
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ Declared in `<joey/core.h>`, included by `<joey/joey.h>`.
|
||||||
----
|
----
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t codegenBytes;
|
uint32_t codegenBytes;
|
||||||
uint32_t audioBytes;
|
|
||||||
} jlConfigT;
|
} jlConfigT;
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
@ -23,10 +22,6 @@ typedef struct {
|
||||||
one Memory Manager block that may not span a 64 KB bank boundary, so values
|
one Memory Manager block that may not span a 64 KB bank boundary, so values
|
||||||
above 65536 are clamped to 65536, and even a clamped request can fail if no
|
above 65536 are clamped to 65536, and even a clamped request can fail if no
|
||||||
free bank exists. Size it against `jlSpriteCompiledSize()`.
|
free bank exists. Size it against `jlSpriteCompiledSize()`.
|
||||||
|
|
||||||
|`audioBytes`
|
|
||||||
|`uint32_t`
|
|
||||||
|Reserved. Not yet consulted by any audio engine.
|
|
||||||
|===
|
|===
|
||||||
|
|
||||||
=== jlInit
|
=== jlInit
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,8 @@ Appends a message to the log.
|
||||||
void jlLogF(const char *fmt, ...);
|
void jlLogF(const char *fmt, ...);
|
||||||
----
|
----
|
||||||
|
|
||||||
Appends a formatted message, `printf` style.
|
Appends a formatted message, `printf` style, as one line: like `jlLog`, the
|
||||||
|
library ends the line, so the format needs no trailing `\n`.
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|
|
@ -87,7 +88,6 @@ int main(void) {
|
||||||
jlConfigT config;
|
jlConfigT config;
|
||||||
|
|
||||||
config.codegenBytes = 32UL * 1024;
|
config.codegenBytes = 32UL * 1024;
|
||||||
config.audioBytes = 0;
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -143,16 +143,15 @@ and `JL_SERIAL_PRINTER` both mean the default port. `JL_SERIAL_DEFAULT` selects
|
||||||
the IIgs modem port, DOS COM1, the ST Modem port, Amiga unit 0 or the X68000's
|
the IIgs modem port, DOS COM1, the ST Modem port, Amiga unit 0 or the X68000's
|
||||||
RS-232C port.
|
RS-232C port.
|
||||||
|
|
||||||
On the X68000 the line is set by the RS-232C routines in charge. With
|
On the X68000 the serial add-on *requires* Human68k's RS-232C driver,
|
||||||
Human68k's RS-232C driver, RSDRV.SYS, loaded (the stock Human68k 3.02 system
|
RSDRV.SYS. Every Human68k system disk has it and the stock CONFIG.SYS loads
|
||||||
disk's CONFIG.SYS loads it, and so do JoeyLib's own X68000 boot disks), the
|
it (`DEVICE = \SYS\RSDRV.SYS`), as do JoeyLib's own X68000 boot disks; a
|
||||||
port runs at 75 to 38400 bps and `JL_SERIAL_FLOW_RTSCTS` makes transmit wait
|
game shipped to boot on its own must keep it. Without it `jlSerialOpen`
|
||||||
|
returns `false` and `jlLastError` says the driver is missing. With it the
|
||||||
|
port runs at 75 to 38400 bps, and `JL_SERIAL_FLOW_RTSCTS` makes transmit wait
|
||||||
for CTS; RTS stays up, since JoeyLib's receive interrupt, not the driver's,
|
for CTS; RTS stays up, since JoeyLib's receive interrupt, not the driver's,
|
||||||
fills the receive ring. 38400 is really 39062 bps, 1.7% fast, within the
|
fills the receive ring. 38400 is really 39062 bps, 1.7% fast, within the
|
||||||
usual tolerance of a UART at the far end. Without RSDRV.SYS the ROM's
|
usual tolerance of a UART at the far end.
|
||||||
routines allow 75 to 9600 bps: a faster request clamps to 9600, and
|
|
||||||
`JL_SERIAL_FLOW_RTSCTS` falls back to no handshaking. JoeyLib checks for the
|
|
||||||
driver at every `jlSerialOpen`.
|
|
||||||
|
|
||||||
`unit` disambiguates `JL_SERIAL_SLOT` and is ignored otherwise: an IIgs Super
|
`unit` disambiguates `JL_SERIAL_SLOT` and is ignored otherwise: an IIgs Super
|
||||||
Serial Card slot 1..7, a DOS COM number 1..4, or an Amiga `serial.device` unit.
|
Serial Card slot 1..7, a DOS COM number 1..4, or an Amiga `serial.device` unit.
|
||||||
|
|
@ -175,7 +174,9 @@ open without an intervening close fails.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* `true` on success. `false` if the port cannot be opened or the
|
*Returns:* `true` on success. `false` if the port cannot be opened or the
|
||||||
platform has no serial support -- and then every other call no-ops.
|
platform has no serial support -- and then every other call no-ops. On the
|
||||||
|
X68000 it is also `false` when RSDRV.SYS is not loaded, and `jlLastError`
|
||||||
|
says so.
|
||||||
|
|
||||||
=== jlSerialClose
|
=== jlSerialClose
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,8 +131,10 @@ against the build platform and mismatches are refused.
|
||||||
|`path` |`const char *` |File path.
|
|`path` |`const char *` |File path.
|
||||||
|`outTiles` |`jlTileT *` |Receives the tiles. Caller-owned.
|
|`outTiles` |`jlTileT *` |Receives the tiles. Caller-owned.
|
||||||
|`maxTiles` |`uint16_t` |Capacity of `outTiles`.
|
|`maxTiles` |`uint16_t` |Capacity of `outTiles`.
|
||||||
|`outValid` |`bool *` |Optional, may be `NULL`. Set `true` per loaded slot, so
|
|`outValid` |`bool *` |Optional, may be `NULL`. One flag per slot up to
|
||||||
loaded tiles can be told from leftovers in a fixed-size array.
|
`maxTiles`: `true` for each loaded tile, `false` for the rest (all of them when
|
||||||
|
the file is refused), so loaded tiles can be told from leftovers in a
|
||||||
|
fixed-size array.
|
||||||
|`outPalette` |`uint16_t *` |Optional, may be `NULL`. Receives the embedded
|
|`outPalette` |`uint16_t *` |Optional, may be `NULL`. Receives the embedded
|
||||||
16-entry `$0RGB` palette if the file has one.
|
16-entry `$0RGB` palette if the file has one.
|
||||||
|===
|
|===
|
||||||
|
|
|
||||||
|
|
@ -288,13 +288,19 @@ And a bake rule looks like:
|
||||||
----
|
----
|
||||||
$(DATA_DIR)/font.tbk: $(MY_ASSETS)/font.png
|
$(DATA_DIR)/font.tbk: $(MY_ASSETS)/font.png
|
||||||
@mkdir -p $(DATA_DIR)
|
@mkdir -p $(DATA_DIR)
|
||||||
python3 $(ASSETBAKE_BIN) --type tile --target atarist $< $@
|
python3 $(REPO_DIR)/tools/assetbake/assetbake.py --type tile --target atarist $< $@
|
||||||
----
|
----
|
||||||
|
|
||||||
Add the baked files to the port's `all` target so they are built alongside the
|
Add the baked files to the port's `all` target so they are built alongside the
|
||||||
binaries. Remember that tile banks and `.spc` files need one rule *per
|
binaries. Remember that tile banks and `.spc` files need one rule *per
|
||||||
platform*, while `.spr`, `.jym` and `.sfx` files are shared.
|
platform*, while `.spr`, `.jym` and `.sfx` files are shared.
|
||||||
|
|
||||||
|
`examples/tilebank` is a working reference: every port's makefile bakes the
|
||||||
|
same PNG for its own target into `DATA/tilebank` (its `TILEBANK_DATA` rule),
|
||||||
|
and the program loads that bank both ways and shows the loader refusing the
|
||||||
|
other targets' bakes. Its `scripts/verify-<port>-tilebank.sh` gates run it on
|
||||||
|
every port.
|
||||||
|
|
||||||
=== Loading Assets at Run Time
|
=== Loading Assets at Run Time
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,12 @@ against your own program too.
|
||||||
|`make iigs-verify`
|
|`make iigs-verify`
|
||||||
|The IIgs actually reached the program and drew something.
|
|The IIgs actually reached the program and drew something.
|
||||||
|
|
||||||
|
|`scripts/verify-<port>-tilebank.sh`
|
||||||
|
|The port's own tile-bank bake loads pixel-exact both ways, with the same
|
||||||
|
surface hash as the host check `scripts/check-tilebank.sh`, and banks baked
|
||||||
|
for the other machines are refused. `scripts/verify-all.sh tilebank` runs
|
||||||
|
every port's.
|
||||||
|
|
||||||
|`scripts/bench-<port>.sh`
|
|`scripts/bench-<port>.sh`
|
||||||
|Per-operation throughput for that port.
|
|Per-operation throughput for that port.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -229,7 +229,6 @@ int main(void) {
|
||||||
jlConfigT config;
|
jlConfigT config;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
config.audioBytes = 64UL * 1024;
|
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
@ -272,7 +271,6 @@ int main(void) {
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
config.audioBytes = 0;
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
@ -368,6 +366,17 @@ example an AGI game's files.
|
||||||
| `--debug`
|
| `--debug`
|
||||||
| Apple IIgs and X68000: start MAME's debugger.
|
| Apple IIgs and X68000: start MAME's debugger.
|
||||||
|
|
||||||
|
| `--serial HOST:PORT`
|
||||||
|
| Connect the machine's serial port to the TCP server at `HOST:PORT`. See
|
||||||
|
<<Serial Ports>>.
|
||||||
|
|
||||||
|
| `--serial-listen PORT`
|
||||||
|
| The machine's serial port waits for a TCP connection on `PORT` instead.
|
||||||
|
|
||||||
|
| `--serial-baud N`
|
||||||
|
| Apple IIgs and X68000: the rate MAME's end of the wire runs at. The
|
||||||
|
default is 9600.
|
||||||
|
|
||||||
| `-- ARGS`
|
| `-- ARGS`
|
||||||
| Pass `ARGS` to the program. DOS only: the other machines' autostart cannot
|
| Pass `ARGS` to the program. DOS only: the other machines' autostart cannot
|
||||||
pass arguments.
|
pass arguments.
|
||||||
|
|
@ -389,6 +398,70 @@ GS/OS beside it and you open the program in the Finder. Given a bare program,
|
||||||
`build/iigs/bin/DRAW`, it builds both disks itself, which needs the IIgs
|
`build/iigs/bin/DRAW`, it builds both disks itself, which needs the IIgs
|
||||||
toolchain.
|
toolchain.
|
||||||
|
|
||||||
|
==== Serial Ports
|
||||||
|
|
||||||
|
`--serial HOST:PORT` plugs the emulated machine's serial port into a TCP
|
||||||
|
connection: to a BBS, a program of your own, `nc -l PORT`, or another
|
||||||
|
`joey-run` started with `--serial-listen PORT`, which waits for the
|
||||||
|
connection instead. `HOST` is a name or an IPv4 address, and a listening
|
||||||
|
port accepts connections on every network interface. The link carries raw
|
||||||
|
bytes, with no telnet negotiation. `examples/sertest` sends a banner and then
|
||||||
|
echoes whatever it receives, which makes it a quick test of a link:
|
||||||
|
|
||||||
|
[source,console]
|
||||||
|
----
|
||||||
|
$ nc -l 2323 # in one terminal
|
||||||
|
$ ./joey-run --serial localhost:2323 --port dos sertest
|
||||||
|
----
|
||||||
|
|
||||||
|
The port is each machine's usual one:
|
||||||
|
|
||||||
|
[cols="1,2,3",options="header"]
|
||||||
|
|===
|
||||||
|
| Machine | Port | How the link is made
|
||||||
|
|
||||||
|
| Apple IIgs
|
||||||
|
| The modem port
|
||||||
|
| MAME connects by itself. To listen, `socat` holds the TCP port and MAME
|
||||||
|
connects to it. The printer port and a Super Serial Card are not connected.
|
||||||
|
|
||||||
|
| X68000
|
||||||
|
| The RS-232C port
|
||||||
|
| As on the IIgs.
|
||||||
|
|
||||||
|
| Atari ST
|
||||||
|
| The Modem port
|
||||||
|
| Hatari's serial port can only be a device, so `socat` joins a
|
||||||
|
pseudo-terminal to the network both ways.
|
||||||
|
|
||||||
|
| Amiga
|
||||||
|
| `serial.device` unit 0
|
||||||
|
| FS-UAE listens by itself, and holds the whole machine until the connection
|
||||||
|
arrives. To connect out, `socat` joins a pseudo-terminal to the network.
|
||||||
|
|
||||||
|
| DOS
|
||||||
|
| COM1
|
||||||
|
| DOSBox does both by itself.
|
||||||
|
|===
|
||||||
|
|
||||||
|
`socat` comes from your distribution: `./install.sh deps host --install`
|
||||||
|
fetches it with the other host tools, and `joey-run` says so when a run needs
|
||||||
|
it and it is missing.
|
||||||
|
|
||||||
|
A few differences are worth knowing:
|
||||||
|
|
||||||
|
* On the IIgs and X68000 MAME's null modem clocks the wire, at 9600 baud,
|
||||||
|
8N1, unless `--serial-baud` says otherwise. A program that opens the port at
|
||||||
|
another rate receives garbage. The other emulators pass bytes at whatever
|
||||||
|
rate the program sets.
|
||||||
|
* When nothing answers at `HOST:PORT`, `joey-run` stops, except on the IIgs
|
||||||
|
and X68000, where MAME prints `Unable to load image` and runs with the port
|
||||||
|
unplugged.
|
||||||
|
* While a port is listening, what the program sends waits for the connection,
|
||||||
|
except on DOS, which drops it just as a cable with nothing on the other end
|
||||||
|
would. `examples/sertest` sends its banner at once, so on DOS a client that
|
||||||
|
connects late sees only the echo.
|
||||||
|
|
||||||
==== Firmware that is not downloaded
|
==== Firmware that is not downloaded
|
||||||
|
|
||||||
Almost everything the emulators need is downloaded: EmuTOS for the Atari ST,
|
Almost everything the emulators need is downloaded: EmuTOS for the Atari ST,
|
||||||
|
|
@ -432,6 +505,7 @@ built for every platform:
|
||||||
|`sprite` |Sprite loading, compiling, save-under and restore.
|
|`sprite` |Sprite loading, compiling, save-under and restore.
|
||||||
|`audio` |Tracker music, `.MOD` playback and digital sound effects.
|
|`audio` |Tracker music, `.MOD` playback and digital sound effects.
|
||||||
|`save` |Save files and the disk-space query.
|
|`save` |Save files and the disk-space query.
|
||||||
|
|`tilebank` |Loading a baked tile bank both ways, and a bank for another machine refused.
|
||||||
|`serial` |The optional RS-232 add-on.
|
|`serial` |The optional RS-232 add-on.
|
||||||
|`adventure` |A small complete game.
|
|`adventure` |A small complete game.
|
||||||
|`agi` |A Sierra AGI interpreter, showing the library under real load.
|
|`agi` |A Sierra AGI interpreter, showing the library under real load.
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ copy.
|
||||||
jlConfigT config;
|
jlConfigT config;
|
||||||
|
|
||||||
config.codegenBytes = 32UL * 1024; // sprite blitter cache
|
config.codegenBytes = 32UL * 1024; // sprite blitter cache
|
||||||
config.audioBytes = 0; // reserved, not yet consulted
|
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
// jlLastError() explains why. There is no display yet, so on a
|
// jlLastError() explains why. There is no display yet, so on a
|
||||||
|
|
@ -273,7 +272,6 @@ int main(void) {
|
||||||
jlConfigT config;
|
jlConfigT config;
|
||||||
|
|
||||||
config.codegenBytes = 32UL * 1024;
|
config.codegenBytes = 32UL * 1024;
|
||||||
config.audioBytes = 0;
|
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
||||||
|
|
@ -270,13 +270,6 @@ static uint16_t rgb(uint8_t r, uint8_t g, uint8_t b) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* This single-file example can blow the 65816's 64 KB code-bank
|
|
||||||
* limit, so on the IIgs we split code across segments at section
|
|
||||||
* boundaries to spread the load across multiple banks. */
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
* Section 4 -- Procedural sprite art
|
* Section 4 -- Procedural sprite art
|
||||||
*
|
*
|
||||||
|
|
@ -1097,10 +1090,6 @@ static bool buildAllSprites(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
* Section 7 -- Room definitions
|
* Section 7 -- Room definitions
|
||||||
*
|
*
|
||||||
|
|
@ -1646,10 +1635,6 @@ static bool findPath(RoomE room, int16_t sx, int16_t sy, int16_t tx, int16_t ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
* Section 9 -- Ego animation / movement
|
* Section 9 -- Ego animation / movement
|
||||||
*
|
*
|
||||||
|
|
@ -1911,10 +1896,6 @@ static void drawInventoryBar(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
* Section 13 -- Per-frame compositor
|
* Section 13 -- Per-frame compositor
|
||||||
*
|
*
|
||||||
|
|
@ -1976,10 +1957,6 @@ static void composeFrame(int16_t cursorX, int16_t cursorY) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
* Section 14 -- Verb / item interactions
|
* Section 14 -- Verb / item interactions
|
||||||
*
|
*
|
||||||
|
|
@ -2253,10 +2230,6 @@ static bool buildBackgrounds(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
* Section 18 -- main()
|
* Section 18 -- main()
|
||||||
* =================================================================== */
|
* =================================================================== */
|
||||||
|
|
@ -2278,7 +2251,6 @@ int main(void) {
|
||||||
#else
|
#else
|
||||||
config.codegenBytes = 256UL * 1024;
|
config.codegenBytes = 256UL * 1024;
|
||||||
#endif
|
#endif
|
||||||
config.audioBytes = 64UL * 1024;
|
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
|
||||||
|
|
@ -235,13 +235,6 @@ static bool isTrunkChar(char c) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* One TU's code can overflow the 65816's 64 KB code bank if it lands
|
|
||||||
* in a single segment. Split the heavier sections across segments to
|
|
||||||
* spread the load across multiple banks. */
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
* Section 4 -- Sprite ASCII art (ego + cursors)
|
* Section 4 -- Sprite ASCII art (ego + cursors)
|
||||||
* =================================================================== */
|
* =================================================================== */
|
||||||
|
|
@ -720,10 +713,6 @@ static void programScbForView(DebugViewE view) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
* Section 8 -- Painting the three room screens (visual / priority /
|
* Section 8 -- Painting the three room screens (visual / priority /
|
||||||
* control)
|
* control)
|
||||||
|
|
@ -926,10 +915,6 @@ static void drawEgoMasked(int16_t x, int16_t y, DirE dir, uint8_t frame) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
* Section 10 -- Pathfinding via the control buffer
|
* Section 10 -- Pathfinding via the control buffer
|
||||||
*
|
*
|
||||||
|
|
@ -1209,10 +1194,6 @@ static void drawInventoryBar(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
* Section 13 -- Frame compositor
|
* Section 13 -- Frame compositor
|
||||||
* =================================================================== */
|
* =================================================================== */
|
||||||
|
|
@ -1322,10 +1303,6 @@ static void handleClick(int16_t mx, int16_t my, bool leftClick, bool rightClick)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
* Section 15 -- main()
|
* Section 15 -- main()
|
||||||
* =================================================================== */
|
* =================================================================== */
|
||||||
|
|
@ -1340,7 +1317,6 @@ int main(void) {
|
||||||
* be small. We need 4 surfaces (stage + visual + priority +
|
* be small. We need 4 surfaces (stage + visual + priority +
|
||||||
* control). */
|
* control). */
|
||||||
config.codegenBytes = 4UL * 1024;
|
config.codegenBytes = 4UL * 1024;
|
||||||
config.audioBytes = 0;
|
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
// whatever logic.0 picks", same as omitting the argument.)
|
// whatever logic.0 picks", same as omitting the argument.)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#include "agi.h"
|
#include "agi.h"
|
||||||
|
|
||||||
#include "joey/audio.h"
|
#include "joey/audio.h"
|
||||||
|
|
@ -314,9 +313,6 @@ static uint32_t gAgiTickLastWallMs;
|
||||||
static uint32_t gAgiV11SubTickAccum; // counts AGI ticks toward next v11++
|
static uint32_t gAgiV11SubTickAccum; // counts AGI ticks toward next v11++
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// ----- Internal helpers (alphabetical) -----
|
// ----- Internal helpers (alphabetical) -----
|
||||||
|
|
||||||
static void cbAddToPic(void *ctx, uint8_t viewId, uint8_t loop, uint8_t cel,
|
static void cbAddToPic(void *ctx, uint8_t viewId, uint8_t loop, uint8_t cel,
|
||||||
|
|
@ -1884,9 +1880,6 @@ static void updateEgoFromInput(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// ----- Entry point -----
|
// ----- Entry point -----
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
|
|
@ -1903,7 +1896,6 @@ int main(int argc, char **argv) {
|
||||||
// through the Sound Blaster auto-init DMA path (see agiSynthFill
|
// through the Sound Blaster auto-init DMA path (see agiSynthFill
|
||||||
// and src/port/dos/audio.c). The audio pool covers the 4 KB
|
// and src/port/dos/audio.c). The audio pool covers the 4 KB
|
||||||
// DMA buffer + libxmp-lite SMIX state.
|
// DMA buffer + libxmp-lite SMIX state.
|
||||||
cfg.audioBytes = 32u * 1024u;
|
|
||||||
if (!jlInit(&cfg)) {
|
if (!jlInit(&cfg)) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,6 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
// AGI direction encoding. 0 means "no motion this cycle"; 1..8
|
// AGI direction encoding. 0 means "no motion this cycle"; 1..8
|
||||||
// circle clockwise starting at north.
|
// circle clockwise starting at north.
|
||||||
// 1=N 2=NE 3=E 4=SE
|
// 1=N 2=NE 3=E 4=SE
|
||||||
|
|
|
||||||
|
|
@ -81,9 +81,6 @@ static void opRelLine(AgiPicT *pic, const PicStateT *state, const uint8_t *d
|
||||||
static void plot(AgiPicT *pic, const PicStateT *state, int16_t x, int16_t y);
|
static void plot(AgiPicT *pic, const PicStateT *state, int16_t x, int16_t y);
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// ----- Internal helpers (alphabetical) -----
|
// ----- Internal helpers (alphabetical) -----
|
||||||
|
|
||||||
static void drawCorner(AgiPicT *pic, const PicStateT *state, const uint8_t *data, uint16_t length, uint16_t *pos, bool yFirst) {
|
static void drawCorner(AgiPicT *pic, const PicStateT *state, const uint8_t *data, uint16_t length, uint16_t *pos, bool yFirst) {
|
||||||
|
|
|
||||||
|
|
@ -48,13 +48,6 @@
|
||||||
#define AGI_DIR_EMPTY_VOLUME 0xFF
|
#define AGI_DIR_EMPTY_VOLUME 0xFF
|
||||||
|
|
||||||
|
|
||||||
// IIgs 64 KB code-bank rule: park this TU in its own load segment
|
|
||||||
// so it doesn't pile onto _ROOT (which still holds main() and the
|
|
||||||
// runtime startup). Cross-platform builds ignore this pragma.
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
// ----- Prototypes -----
|
// ----- Prototypes -----
|
||||||
|
|
||||||
static bool buildVolName(char *out, size_t outSize, uint8_t volNum);
|
static bool buildVolName(char *out, size_t outSize, uint8_t volNum);
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,6 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#define GLYPH_W 8u
|
#define GLYPH_W 8u
|
||||||
#define GLYPH_H 8u
|
#define GLYPH_H 8u
|
||||||
#define FIRST_CHAR 0x20u // space
|
#define FIRST_CHAR 0x20u // space
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,6 @@
|
||||||
#define AGI_PRI_GROUND_BAND_PX 12
|
#define AGI_PRI_GROUND_BAND_PX 12
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
// ----- File-local types -----
|
// ----- File-local types -----
|
||||||
|
|
||||||
// Per-draw invariants for one cel blit, grouped so they live in memory
|
// Per-draw invariants for one cel blit, grouped so they live in memory
|
||||||
|
|
@ -284,9 +280,6 @@ uint8_t agiActorPriorityForY(int16_t y) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void agiViewDraw(const AgiViewT *view, uint8_t loopIdx, uint8_t celIdx,
|
void agiViewDraw(const AgiViewT *view, uint8_t loopIdx, uint8_t celIdx,
|
||||||
int16_t x, int16_t y, uint8_t actorPri,
|
int16_t x, int16_t y, uint8_t actorPri,
|
||||||
const uint8_t *picPriority,
|
const uint8_t *picPriority,
|
||||||
|
|
|
||||||
|
|
@ -272,10 +272,6 @@
|
||||||
#define ACTION_UNKNOWN 0xFFu
|
#define ACTION_UNKNOWN 0xFFu
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
// Result of one opcode-group handler in the split agiVmRun dispatch.
|
// Result of one opcode-group handler in the split agiVmRun dispatch.
|
||||||
// VM_DISPATCH_CONTINUE: opcode handled, advance to the next. RETURN:
|
// VM_DISPATCH_CONTINUE: opcode handled, advance to the next. RETURN:
|
||||||
// handled, stop and let agiVmRun return vm->haltReason. UNHANDLED: opcode
|
// handled, stop and let agiVmRun return vm->haltReason. UNHANDLED: opcode
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,6 @@ int main(void) {
|
||||||
uint32_t sfxLen;
|
uint32_t sfxLen;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
config.audioBytes = 64UL * 1024;
|
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,6 @@ int main(void) {
|
||||||
jlSurfaceT *screen;
|
jlSurfaceT *screen;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
config.audioBytes = 64UL * 1024;
|
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ int main(void) {
|
||||||
jlConfigT config;
|
jlConfigT config;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
config.audioBytes = 64UL * 1024;
|
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,6 @@ int main(void) {
|
||||||
jlSurfaceT *screen;
|
jlSurfaceT *screen;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
config.audioBytes = 64UL * 1024;
|
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
|
||||||
|
|
@ -595,7 +595,6 @@ int main(void) {
|
||||||
int16_t cursorRow;
|
int16_t cursorRow;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
config.audioBytes = 64UL * 1024;
|
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,6 @@ int main(void) {
|
||||||
jlSurfaceT *screen;
|
jlSurfaceT *screen;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
config.audioBytes = 64UL * 1024;
|
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,6 @@ int main(void) {
|
||||||
bool saved;
|
bool saved;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
config.audioBytes = 64UL * 1024;
|
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,6 @@ int main(void) {
|
||||||
bool linkOpen;
|
bool linkOpen;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
config.audioBytes = 64UL * 1024;
|
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
|
||||||
|
|
@ -265,7 +265,6 @@ int main(void) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
config.codegenBytes = 0u;
|
config.codegenBytes = 0u;
|
||||||
config.audioBytes = 0u;
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
MB_BASE[MB_PHASE] = PHASE_FAILED;
|
MB_BASE[MB_PHASE] = PHASE_FAILED;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
|
||||||
355
examples/sermouse/sermouse.c
Normal file
355
examples/sermouse/sermouse.c
Normal file
|
|
@ -0,0 +1,355 @@
|
||||||
|
// Serial and mouse together: traffic both ways on the serial port while the
|
||||||
|
// mouse is read and drawn, for a machine whose serial port and mouse share a
|
||||||
|
// chip. The X68000 is one: its Z8530 SCC carries the RS-232C port on channel A
|
||||||
|
// and the mouse on channel B, and the chip has ONE register pointer for both
|
||||||
|
// channels, so a select-then-access register sequence on one side must never
|
||||||
|
// be split by the other. scripts/verify-x68000-serial-mouse.sh runs this under
|
||||||
|
// MAME with a host peer on the line and Lua moving the mouse, and checks that
|
||||||
|
// neither side disturbs the other.
|
||||||
|
//
|
||||||
|
// Serial: the default port at 9600 8N1, both directions at once.
|
||||||
|
// - Transmits an endless stream, byte k = streamByte(k, TX_STREAM_MASK),
|
||||||
|
// STREAM_CHUNK bytes per loop.
|
||||||
|
// - Checks the peer's endless stream in order, byte k = streamByte(k,
|
||||||
|
// RX_STREAM_MASK), counting every byte that is not the one expected.
|
||||||
|
// - Raises DTR, which is already up, DTR_WRITES_PER_LOOP times per loop. A
|
||||||
|
// program raises it once; doing it constantly keeps the port's own
|
||||||
|
// select-then-write register sequence in the path of both channels'
|
||||||
|
// interrupts for the whole run.
|
||||||
|
// SPACE pauses and resumes transmitting. The peer answers byte for byte
|
||||||
|
// (scripts/lib/serialStreamPeer.py), so a pause quiets the line both ways.
|
||||||
|
// ESC quits.
|
||||||
|
//
|
||||||
|
// Mouse: a small pointer follows it.
|
||||||
|
//
|
||||||
|
// The bottom two stage rows are verification scanlines: one nibble per pixel,
|
||||||
|
// high nibble first, behind a sentinel, for a harness that reads the
|
||||||
|
// framebuffer back:
|
||||||
|
// row 198 serial: 3C3C, status, received, bad, sent, short writes, first bad
|
||||||
|
// row 199 mouse: 5A5A, present/left/right/middle, x, y (examples/keys' row)
|
||||||
|
// The serial row is restamped at most every SERIAL_ROW_FRAMES frames, so a
|
||||||
|
// reader that sees the same row on two consecutive frames knows it did not
|
||||||
|
// catch a present half done. The mouse row changes only with the mouse.
|
||||||
|
//
|
||||||
|
// Serial is an OPT-IN add-on: it is reached through <joey/serial.h>, which the
|
||||||
|
// umbrella <joey/joey.h> deliberately does NOT pull in.
|
||||||
|
|
||||||
|
#include <joey/joey.h>
|
||||||
|
#include <joey/serial.h>
|
||||||
|
|
||||||
|
#define LINE_BAUD 9600u
|
||||||
|
#define LINE_DATA_BITS 8u
|
||||||
|
#define LINE_STOP_BITS 1u
|
||||||
|
|
||||||
|
#define STREAM_CHUNK 16u // bytes sent per loop, ~17 ms of line at 9600
|
||||||
|
#define READ_CHUNK 64u
|
||||||
|
#define DTR_WRITES_PER_LOOP 16u
|
||||||
|
#define RX_STREAM_MASK 0x00u // the peer's stream
|
||||||
|
#define TX_STREAM_MASK 0xA5u // ours, so the two can never be mistaken
|
||||||
|
#define BYTE_BITS 8
|
||||||
|
#define NO_BAD_INDEX 0xFFFFFFFFul
|
||||||
|
#define COUNT_SATURATED 0xFFFFu
|
||||||
|
|
||||||
|
#define STATUS_OPEN 0x0001u
|
||||||
|
#define STATUS_PAUSED 0x0002u
|
||||||
|
|
||||||
|
// Verification rows. Every field is a 16-bit word (4 nibbles) or a 32-bit
|
||||||
|
// long (8 nibbles), laid end to end from the sentinel at x = 0.
|
||||||
|
#define NIBBLE_BITS 4
|
||||||
|
#define NIBBLE_MASK 0x0Fu
|
||||||
|
#define WORD_NIBBLES 4
|
||||||
|
#define LONG_NIBBLES 8
|
||||||
|
#define SERIAL_ROW_Y 198
|
||||||
|
#define SERIAL_ROW_SENTINEL 0x3C3Cu
|
||||||
|
#define SERIAL_STATUS_X WORD_NIBBLES
|
||||||
|
#define SERIAL_RECEIVED_X (SERIAL_STATUS_X + WORD_NIBBLES)
|
||||||
|
#define SERIAL_BAD_X (SERIAL_RECEIVED_X + LONG_NIBBLES)
|
||||||
|
#define SERIAL_SENT_X (SERIAL_BAD_X + WORD_NIBBLES)
|
||||||
|
#define SERIAL_SHORT_X (SERIAL_SENT_X + LONG_NIBBLES)
|
||||||
|
#define SERIAL_FIRST_BAD_X (SERIAL_SHORT_X + WORD_NIBBLES)
|
||||||
|
#define SERIAL_ROW_FRAMES 4u
|
||||||
|
#define MOUSE_ROW_Y 199
|
||||||
|
#define MOUSE_ROW_SENTINEL 0x5A5Au
|
||||||
|
#define MOUSE_FLAGS_X WORD_NIBBLES
|
||||||
|
#define MOUSE_XPOS_X (MOUSE_FLAGS_X + WORD_NIBBLES)
|
||||||
|
#define MOUSE_YPOS_X (MOUSE_XPOS_X + WORD_NIBBLES)
|
||||||
|
|
||||||
|
// The mouse flags word, one nibble per predicate, in examples/keys' order.
|
||||||
|
#define MOUSE_FLAG_PRESENT 0x1000u
|
||||||
|
#define MOUSE_FLAG_LEFT 0x0100u
|
||||||
|
#define MOUSE_FLAG_RIGHT 0x0010u
|
||||||
|
#define MOUSE_FLAG_MIDDLE 0x0001u
|
||||||
|
#define MOUSE_FLAGS_NONE 0xFFFFu // never a real flags word: forces the first stamp
|
||||||
|
|
||||||
|
#define POINTER_SIZE 4
|
||||||
|
#define COLOR_BACKGROUND 0
|
||||||
|
#define COLOR_POINTER 15
|
||||||
|
#define PALETTE_RED_SHIFT 8
|
||||||
|
#define PALETTE_GREEN_SHIFT 4
|
||||||
|
|
||||||
|
typedef struct StreamStatsT {
|
||||||
|
uint32_t received; // peer bytes read so far = index of the next one expected
|
||||||
|
uint32_t sent; // our bytes accepted by jlSerialWrite
|
||||||
|
uint32_t firstBad; // index of the first unexpected byte, NO_BAD_INDEX if none
|
||||||
|
uint16_t bad; // unexpected bytes, saturating
|
||||||
|
uint16_t shortWrites; // jlSerialWrite calls that took less than asked, saturating
|
||||||
|
} StreamStatsT;
|
||||||
|
|
||||||
|
typedef struct MouseRowT {
|
||||||
|
int16_t x;
|
||||||
|
int16_t y;
|
||||||
|
uint16_t flags;
|
||||||
|
} MouseRowT;
|
||||||
|
|
||||||
|
typedef struct PointerT {
|
||||||
|
int16_t x;
|
||||||
|
int16_t y;
|
||||||
|
bool drawn;
|
||||||
|
} PointerT;
|
||||||
|
|
||||||
|
static void buildPalette(jlSurfaceT *screen);
|
||||||
|
static void fillPointer(jlSurfaceT *screen, int16_t x, int16_t y, uint8_t color);
|
||||||
|
static uint16_t mouseFlags(void);
|
||||||
|
static void movePointer(jlSurfaceT *screen, PointerT *pointer);
|
||||||
|
static void receiveStream(StreamStatsT *stats);
|
||||||
|
static void sendStream(StreamStatsT *stats);
|
||||||
|
static void stampNibbles(jlSurfaceT *screen, int16_t x, int16_t y, uint32_t value, int16_t nibbles);
|
||||||
|
static void stampSerialRow(jlSurfaceT *screen, const StreamStatsT *stats, uint16_t status);
|
||||||
|
static uint8_t streamByte(uint32_t index, uint8_t mask);
|
||||||
|
static void updateMouseRow(jlSurfaceT *screen, MouseRowT *last);
|
||||||
|
|
||||||
|
|
||||||
|
// A grey ramp, so every index the verification rows stamp is visible.
|
||||||
|
static void buildPalette(jlSurfaceT *screen) {
|
||||||
|
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
for (i = 0u; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
||||||
|
colors[i] = (uint16_t)((i << PALETTE_RED_SHIFT) | (i << PALETTE_GREEN_SHIFT) | i);
|
||||||
|
}
|
||||||
|
jlPaletteSet(screen, 0, colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The pointer block at (x, y), clipped above the verification rows so it can
|
||||||
|
// never draw over them: a harness samples those between frames.
|
||||||
|
static void fillPointer(jlSurfaceT *screen, int16_t x, int16_t y, uint8_t color) {
|
||||||
|
int16_t w;
|
||||||
|
int16_t h;
|
||||||
|
|
||||||
|
w = POINTER_SIZE;
|
||||||
|
h = POINTER_SIZE;
|
||||||
|
if ((int16_t)(x + w) > SURFACE_WIDTH) {
|
||||||
|
w = (int16_t)(SURFACE_WIDTH - x);
|
||||||
|
}
|
||||||
|
if ((int16_t)(y + h) > SERIAL_ROW_Y) {
|
||||||
|
h = (int16_t)(SERIAL_ROW_Y - y);
|
||||||
|
}
|
||||||
|
if (w <= 0 || h <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jlFillRect(screen, x, y, (uint16_t)w, (uint16_t)h, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint16_t mouseFlags(void) {
|
||||||
|
return (uint16_t)((jlMousePresent() ? MOUSE_FLAG_PRESENT : 0u) |
|
||||||
|
(jlMouseDown(MOUSE_BUTTON_LEFT) ? MOUSE_FLAG_LEFT : 0u) |
|
||||||
|
(jlMouseDown(MOUSE_BUTTON_RIGHT) ? MOUSE_FLAG_RIGHT : 0u) |
|
||||||
|
(jlMouseDown(MOUSE_BUTTON_MIDDLE) ? MOUSE_FLAG_MIDDLE : 0u));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Erase the pointer where it was and draw it where the mouse is now.
|
||||||
|
static void movePointer(jlSurfaceT *screen, PointerT *pointer) {
|
||||||
|
int16_t x;
|
||||||
|
int16_t y;
|
||||||
|
|
||||||
|
x = jlMouseX();
|
||||||
|
y = jlMouseY();
|
||||||
|
if (pointer->drawn && x == pointer->x && y == pointer->y) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pointer->drawn) {
|
||||||
|
fillPointer(screen, pointer->x, pointer->y, COLOR_BACKGROUND);
|
||||||
|
}
|
||||||
|
fillPointer(screen, x, y, COLOR_POINTER);
|
||||||
|
pointer->x = x;
|
||||||
|
pointer->y = y;
|
||||||
|
pointer->drawn = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Read everything that has arrived and check it against the peer's stream.
|
||||||
|
static void receiveStream(StreamStatsT *stats) {
|
||||||
|
uint8_t buf[READ_CHUNK];
|
||||||
|
uint16_t got;
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
jlSerialPoll();
|
||||||
|
got = jlSerialRead(buf, READ_CHUNK);
|
||||||
|
while (got > 0u) {
|
||||||
|
for (i = 0u; i < got; i++) {
|
||||||
|
if (buf[i] != streamByte(stats->received, RX_STREAM_MASK)) {
|
||||||
|
if (stats->firstBad == NO_BAD_INDEX) {
|
||||||
|
stats->firstBad = stats->received;
|
||||||
|
}
|
||||||
|
if (stats->bad < COUNT_SATURATED) {
|
||||||
|
stats->bad++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stats->received++;
|
||||||
|
}
|
||||||
|
got = jlSerialRead(buf, READ_CHUNK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Send the next STREAM_CHUNK bytes of our stream. A short write resumes from
|
||||||
|
// the first byte not taken, so the stream stays gapless either way.
|
||||||
|
static void sendStream(StreamStatsT *stats) {
|
||||||
|
uint8_t chunk[STREAM_CHUNK];
|
||||||
|
uint16_t sent;
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
for (i = 0u; i < STREAM_CHUNK; i++) {
|
||||||
|
chunk[i] = streamByte(stats->sent + i, TX_STREAM_MASK);
|
||||||
|
}
|
||||||
|
sent = jlSerialWrite(chunk, STREAM_CHUNK);
|
||||||
|
stats->sent += sent;
|
||||||
|
if (sent < STREAM_CHUNK && stats->shortWrites < COUNT_SATURATED) {
|
||||||
|
stats->shortWrites++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Stamp `value` into `nibbles` consecutive pixels, high nibble first.
|
||||||
|
static void stampNibbles(jlSurfaceT *screen, int16_t x, int16_t y, uint32_t value, int16_t nibbles) {
|
||||||
|
int16_t i;
|
||||||
|
int16_t shift;
|
||||||
|
|
||||||
|
for (i = 0; i < nibbles; i++) {
|
||||||
|
shift = (int16_t)(NIBBLE_BITS * (nibbles - 1 - i));
|
||||||
|
jlDrawPixel(screen, (int16_t)(x + i), y, (uint8_t)((value >> shift) & NIBBLE_MASK));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void stampSerialRow(jlSurfaceT *screen, const StreamStatsT *stats, uint16_t status) {
|
||||||
|
stampNibbles(screen, 0, SERIAL_ROW_Y, SERIAL_ROW_SENTINEL, WORD_NIBBLES);
|
||||||
|
stampNibbles(screen, SERIAL_STATUS_X, SERIAL_ROW_Y, status, WORD_NIBBLES);
|
||||||
|
stampNibbles(screen, SERIAL_RECEIVED_X, SERIAL_ROW_Y, stats->received, LONG_NIBBLES);
|
||||||
|
stampNibbles(screen, SERIAL_BAD_X, SERIAL_ROW_Y, stats->bad, WORD_NIBBLES);
|
||||||
|
stampNibbles(screen, SERIAL_SENT_X, SERIAL_ROW_Y, stats->sent, LONG_NIBBLES);
|
||||||
|
stampNibbles(screen, SERIAL_SHORT_X, SERIAL_ROW_Y, stats->shortWrites, WORD_NIBBLES);
|
||||||
|
stampNibbles(screen, SERIAL_FIRST_BAD_X, SERIAL_ROW_Y, stats->firstBad, LONG_NIBBLES);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Byte `index` of a stream: the index's low byte XOR its next byte, XOR the
|
||||||
|
// stream's mask. A lost or repeated byte throws every byte after it off its
|
||||||
|
// expected value, where a plain counter would miss any gap that is a multiple
|
||||||
|
// of 256. scripts/lib/serialStreamPeer.py uses the same formula and masks.
|
||||||
|
static uint8_t streamByte(uint32_t index, uint8_t mask) {
|
||||||
|
return (uint8_t)(index ^ (index >> BYTE_BITS) ^ mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Restamp the mouse row when the reported state has changed.
|
||||||
|
static void updateMouseRow(jlSurfaceT *screen, MouseRowT *last) {
|
||||||
|
int16_t x;
|
||||||
|
int16_t y;
|
||||||
|
uint16_t flags;
|
||||||
|
|
||||||
|
x = jlMouseX();
|
||||||
|
y = jlMouseY();
|
||||||
|
flags = mouseFlags();
|
||||||
|
if (x == last->x && y == last->y && flags == last->flags) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stampNibbles(screen, 0, MOUSE_ROW_Y, MOUSE_ROW_SENTINEL, WORD_NIBBLES);
|
||||||
|
stampNibbles(screen, MOUSE_FLAGS_X, MOUSE_ROW_Y, flags, WORD_NIBBLES);
|
||||||
|
stampNibbles(screen, MOUSE_XPOS_X, MOUSE_ROW_Y, (uint16_t)x, WORD_NIBBLES);
|
||||||
|
stampNibbles(screen, MOUSE_YPOS_X, MOUSE_ROW_Y, (uint16_t)y, WORD_NIBBLES);
|
||||||
|
last->x = x;
|
||||||
|
last->y = y;
|
||||||
|
last->flags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
jlConfigT config;
|
||||||
|
jlSerialConfigT line;
|
||||||
|
jlSurfaceT *screen;
|
||||||
|
StreamStatsT stats;
|
||||||
|
MouseRowT mouseRow;
|
||||||
|
PointerT pointer;
|
||||||
|
uint16_t status;
|
||||||
|
uint16_t rowFrame;
|
||||||
|
uint16_t i;
|
||||||
|
bool paused;
|
||||||
|
|
||||||
|
config.codegenBytes = 0u;
|
||||||
|
if (!jlInit(&config)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
screen = jlStageGet();
|
||||||
|
buildPalette(screen);
|
||||||
|
jlScbSetRange(screen, 0, SURFACE_HEIGHT - 1, 0);
|
||||||
|
jlSurfaceClear(screen, COLOR_BACKGROUND);
|
||||||
|
|
||||||
|
line.baud = LINE_BAUD;
|
||||||
|
line.dataBits = LINE_DATA_BITS;
|
||||||
|
line.stopBits = LINE_STOP_BITS;
|
||||||
|
line.parity = JL_SERIAL_PARITY_NONE;
|
||||||
|
line.flow = JL_SERIAL_FLOW_NONE;
|
||||||
|
line.unit = 0u;
|
||||||
|
status = jlSerialOpen(JL_SERIAL_DEFAULT, &line) ? STATUS_OPEN : 0u;
|
||||||
|
|
||||||
|
stats.received = 0ul;
|
||||||
|
stats.sent = 0ul;
|
||||||
|
stats.firstBad = NO_BAD_INDEX;
|
||||||
|
stats.bad = 0u;
|
||||||
|
stats.shortWrites = 0u;
|
||||||
|
mouseRow.x = -1;
|
||||||
|
mouseRow.y = -1;
|
||||||
|
mouseRow.flags = MOUSE_FLAGS_NONE;
|
||||||
|
pointer.x = 0;
|
||||||
|
pointer.y = 0;
|
||||||
|
pointer.drawn = false;
|
||||||
|
paused = false;
|
||||||
|
|
||||||
|
jlInputPoll();
|
||||||
|
stampSerialRow(screen, &stats, status);
|
||||||
|
updateMouseRow(screen, &mouseRow);
|
||||||
|
jlStagePresent();
|
||||||
|
rowFrame = jlFrameCount();
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
jlInputPoll();
|
||||||
|
if (jlKeyPressed(KEY_ESCAPE)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (jlKeyPressed(KEY_SPACE)) {
|
||||||
|
paused = !paused;
|
||||||
|
}
|
||||||
|
receiveStream(&stats);
|
||||||
|
if (!paused) {
|
||||||
|
sendStream(&stats);
|
||||||
|
}
|
||||||
|
for (i = 0u; i < DTR_WRITES_PER_LOOP; i++) {
|
||||||
|
jlSerialSetDtr(true);
|
||||||
|
}
|
||||||
|
movePointer(screen, &pointer);
|
||||||
|
updateMouseRow(screen, &mouseRow);
|
||||||
|
if ((uint16_t)(jlFrameCount() - rowFrame) >= SERIAL_ROW_FRAMES) {
|
||||||
|
rowFrame = jlFrameCount();
|
||||||
|
stampSerialRow(screen, &stats, (uint16_t)(status | (paused ? STATUS_PAUSED : 0u)));
|
||||||
|
}
|
||||||
|
jlStagePresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
jlSerialClose();
|
||||||
|
jlShutdown();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# extractFlame.py -- wire the engine-flame sprite cels from the C64 ROM
|
|
||||||
# image (stuff/spacetaxi/raw.bin) into the sprite sheet's flame row.
|
|
||||||
#
|
|
||||||
# The flame is C64 sprite 2, MULTICOLOR (verified: $D01C bit2 set). Its
|
|
||||||
# per-direction cel is chosen from the pointer table at $6DB0 (indexed by
|
|
||||||
# the direction mask $716A). Reading raw.bin at $6DB0 (2-byte load-addr
|
|
||||||
# header -> +2) gives, and the port's kFlameCelByDirMask mirrors exactly:
|
|
||||||
# mask 1 UP -> block $D8 mask 8 RIGHT -> block $D7
|
|
||||||
# mask 2 DOWN -> block $D4 mask 9 UP+RIGHT -> block $D3
|
|
||||||
# mask 4 LEFT -> block $D5 mask 10 DOWN+RIGHT-> block $D6
|
|
||||||
# mask 5 UP+LEFT -> block $D2
|
|
||||||
# mask 6 DOWN+LEFT -> block $D1
|
|
||||||
# flameCels[0..7] load from .spr cels 18..25 (sheet row 2, cols 0..7), so
|
|
||||||
# the cel order below is the kFlameCelByDirMask index order.
|
|
||||||
#
|
|
||||||
# Sprite data lives at block*64 in VIC bank 0 ($3000-$37FF). Each C64
|
|
||||||
# multicolor sprite is 12 MC-pixels x 21 rows (3 bytes/row, 2 bits/pixel);
|
|
||||||
# each MC pixel is 2 hi-res pixels, so it maps 1 MC pixel -> 2 of the
|
|
||||||
# port's 24-wide 4bpp cel pixels. Colors are the live-level flame palette
|
|
||||||
# ($7D05->$D025=$02 red = MC0, $7D06->$D026=$07 yellow = MC1; sprite-2
|
|
||||||
# colour = orange), mapped to sprites.png's indexed palette.
|
|
||||||
#
|
|
||||||
# Patches ONLY the flame row (row 2) of sprites.png -- taxi (row 0),
|
|
||||||
# passenger (row 1), and font.png are left untouched. Re-run the .spr
|
|
||||||
# bake afterwards (make/<port>.mk STAXI_SPR rule).
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
try:
|
|
||||||
from PIL import Image
|
|
||||||
except ImportError:
|
|
||||||
sys.exit("extractFlame.py: needs Pillow (pip install pillow)")
|
|
||||||
|
|
||||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
RAW = os.path.join(HERE, "..", "..", "..", "stuff", "spacetaxi", "raw.bin")
|
|
||||||
PNG = os.path.join(HERE, "sprites", "sprites.png")
|
|
||||||
|
|
||||||
CEL = 24 # cel is 24x24 px
|
|
||||||
LOAD_HDR = 2 # raw.bin 2-byte load-address header
|
|
||||||
|
|
||||||
# flameCels index -> C64 sprite block (from $6DB0 / kFlameCelByDirMask).
|
|
||||||
FLAME_BLOCKS = [0xD8, 0xD4, 0xD5, 0xD2, 0xD1, 0xD7, 0xD3, 0xD6]
|
|
||||||
|
|
||||||
# Multicolor 2-bit pixel -> palette index. The engine renders sprite
|
|
||||||
# cels through kDefaultPalette (stRender.c), whose index IS the C64 VIC-II
|
|
||||||
# colour code -- the sprite's own baked palette is ignored (loadSpriteSheet
|
|
||||||
# passes NULL). So the C64 flame colours map DIRECTLY by code:
|
|
||||||
# 00 transparent(0), 01 MC0=$D025=$02 red(2), 11 MC1=$D026=$07 yellow(7),
|
|
||||||
# 10 sprite-2 colour -> orange(8) (barely used by these cels).
|
|
||||||
# (NOT the sprites.png preview-palette indices, which differ.)
|
|
||||||
MC_TO_INDEX = {0: 0, 1: 2, 2: 8, 3: 7}
|
|
||||||
|
|
||||||
FLAME_ROW_Y = 2 * CEL # sheet row 2 (flame row) top edge
|
|
||||||
|
|
||||||
|
|
||||||
def loadRaw():
|
|
||||||
with open(RAW, "rb") as f:
|
|
||||||
return f.read()
|
|
||||||
|
|
||||||
|
|
||||||
def celFromBlock(raw, block):
|
|
||||||
# Return a 24x24 list-of-rows of palette indices for one flame block.
|
|
||||||
base = LOAD_HDR + block * 64
|
|
||||||
cel = [[0] * CEL for _ in range(CEL)]
|
|
||||||
for row in range(21): # C64 sprite is 21 rows tall
|
|
||||||
for byteIdx in range(3): # 3 bytes/row
|
|
||||||
b = raw[base + row * 3 + byteIdx]
|
|
||||||
for pair in range(4): # 4 MC pixels per byte (MSB first)
|
|
||||||
mc = (b >> ((3 - pair) * 2)) & 3
|
|
||||||
idx = MC_TO_INDEX[mc]
|
|
||||||
px = (byteIdx * 4 + pair) * 2 # MC pixel -> 2 cel pixels
|
|
||||||
cel[row][px] = idx
|
|
||||||
cel[row][px + 1] = idx
|
|
||||||
return cel
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
raw = loadRaw()
|
|
||||||
im = Image.open(PNG)
|
|
||||||
if im.mode != "P":
|
|
||||||
sys.exit(f"extractFlame.py: {PNG} is {im.mode}, expected indexed 'P'")
|
|
||||||
px = im.load()
|
|
||||||
nonEmpty = 0
|
|
||||||
for celIdx, block in enumerate(FLAME_BLOCKS):
|
|
||||||
cel = celFromBlock(raw, block)
|
|
||||||
x0 = celIdx * CEL
|
|
||||||
opaque = 0
|
|
||||||
for y in range(CEL):
|
|
||||||
for x in range(CEL):
|
|
||||||
v = cel[y][x]
|
|
||||||
px[x0 + x, FLAME_ROW_Y + y] = v
|
|
||||||
if v != 0:
|
|
||||||
opaque += 1
|
|
||||||
nonEmpty += 1 if opaque else 0
|
|
||||||
print(f" flameCels[{celIdx}] block ${block:02X}: {opaque} opaque px -> cel {18 + celIdx}")
|
|
||||||
im.save(PNG)
|
|
||||||
print(f"extractFlame: patched {nonEmpty}/8 flame cels into {os.path.relpath(PNG, HERE)}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
BIN
examples/spacetaxi/assets/font.png
(Stored with Git LFS)
BIN
examples/spacetaxi/assets/font.png
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1,301 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# genPlaceholderArt.py -- generate generic geometric placeholder art
|
|
||||||
# for the Space Taxi port. Output is 320x200 indexed PNG, 16-color.
|
|
||||||
# Replace these with hand-authored art any time; the build pipeline
|
|
||||||
# picks up whichever PNG sits in assets/.
|
|
||||||
|
|
||||||
import os
|
|
||||||
from PIL import Image, ImageDraw
|
|
||||||
|
|
||||||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
|
|
||||||
# 16-color palette matching the engine's kDefaultPalette (see stRender.c).
|
|
||||||
# Each entry is (R, G, B) in 0..255. The .jas format will store the
|
|
||||||
# 4-bit-per-channel quantization automatically via joeyasset.
|
|
||||||
PALETTE_RGB = [
|
|
||||||
(0x00, 0x00, 0x00), # 0 black
|
|
||||||
(0x00, 0x00, 0x77), # 1 dark blue
|
|
||||||
(0xCC, 0x00, 0x00), # 2 red
|
|
||||||
(0x00, 0x77, 0x00), # 3 dark green
|
|
||||||
(0x66, 0x44, 0x00), # 4 brown
|
|
||||||
(0xEE, 0xEE, 0x00), # 5 yellow
|
|
||||||
(0x88, 0x88, 0x88), # 6 mid gray
|
|
||||||
(0xBB, 0xBB, 0xBB), # 7 light gray
|
|
||||||
(0x44, 0x66, 0xFF), # 8 sky blue
|
|
||||||
(0x77, 0x88, 0xFF), # 9 light blue
|
|
||||||
(0xFF, 0x88, 0x88), # 10 light red
|
|
||||||
(0x88, 0xFF, 0x44), # 11 light green
|
|
||||||
(0xFF, 0x88, 0x00), # 12 orange
|
|
||||||
(0xFF, 0x00, 0xFF), # 13 magenta
|
|
||||||
(0x00, 0xFF, 0xFF), # 14 cyan
|
|
||||||
(0xFF, 0xFF, 0xFF), # 15 white
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def new_canvas(w=320, h=200):
|
|
||||||
"""Returns a fresh w x h indexed PIL image with the palette installed."""
|
|
||||||
img = Image.new('P', (w, h), 0)
|
|
||||||
flat = []
|
|
||||||
for r, g, b in PALETTE_RGB:
|
|
||||||
flat.extend([r, g, b])
|
|
||||||
flat.extend([0] * (768 - len(flat))) # pad to 256 entries
|
|
||||||
img.putpalette(flat)
|
|
||||||
return img
|
|
||||||
|
|
||||||
|
|
||||||
def write_indexed_png(img, path):
|
|
||||||
"""Save with mode='P' so PNG output is indexed (joeyasset wants this
|
|
||||||
when converted through ppm; ImageMagick's `convert` preserves
|
|
||||||
indexed mode when writing PPM)."""
|
|
||||||
img.save(path, 'PNG', optimize=True)
|
|
||||||
print(f" wrote {path}")
|
|
||||||
|
|
||||||
|
|
||||||
def gen_tilebank():
|
|
||||||
"""tilebank0.png: 320x200, 40x25 cells of 8x8 each (= 1000 tile
|
|
||||||
slots). The engine convention is:
|
|
||||||
index 0 = empty (non-solid)
|
|
||||||
index 1..63 = solid walls
|
|
||||||
index 64..127 = landing-pad surfaces
|
|
||||||
index 128..255 = decorative non-solid
|
|
||||||
We fill a few representative slots; the rest stay black so they
|
|
||||||
show as "empty" if a level references them.
|
|
||||||
"""
|
|
||||||
img = new_canvas()
|
|
||||||
px = img.load()
|
|
||||||
|
|
||||||
def draw_tile(idx, painter):
|
|
||||||
bx = (idx % 40) * 8
|
|
||||||
by = (idx // 40) * 8
|
|
||||||
painter(bx, by)
|
|
||||||
|
|
||||||
def block(color):
|
|
||||||
return lambda x, y: [px.__setitem__((x+dx, y+dy), color)
|
|
||||||
for dx in range(8) for dy in range(8)]
|
|
||||||
|
|
||||||
def bricks(color_a, color_b):
|
|
||||||
def paint(x, y):
|
|
||||||
for dy in range(8):
|
|
||||||
row_offset = 4 if (y // 8 + dy // 4) % 2 == 0 else 0
|
|
||||||
for dx in range(8):
|
|
||||||
is_grout = (dx == (row_offset % 8)) or (dy == 3 or dy == 7)
|
|
||||||
px[x + dx, y + dy] = color_b if is_grout else color_a
|
|
||||||
return paint
|
|
||||||
|
|
||||||
def stripes(color_a, color_b):
|
|
||||||
def paint(x, y):
|
|
||||||
for dy in range(8):
|
|
||||||
c = color_a if (dy + (x // 8)) % 2 == 0 else color_b
|
|
||||||
for dx in range(8):
|
|
||||||
px[x + dx, y + dy] = c
|
|
||||||
return paint
|
|
||||||
|
|
||||||
def landing_pad(color_a, color_b):
|
|
||||||
# diagonal stripes signal a landing-pad surface
|
|
||||||
def paint(x, y):
|
|
||||||
for dy in range(8):
|
|
||||||
for dx in range(8):
|
|
||||||
px[x + dx, y + dy] = color_a if ((dx + dy) // 2) % 2 == 0 else color_b
|
|
||||||
return paint
|
|
||||||
|
|
||||||
# tile 0 -> already black (empty); leave as-is
|
|
||||||
|
|
||||||
# walls 1..7: a variety of brick/stripe patterns
|
|
||||||
draw_tile(1, bricks(6, 0)) # gray brick
|
|
||||||
draw_tile(2, bricks(4, 0)) # brown brick
|
|
||||||
draw_tile(3, block(6)) # solid gray
|
|
||||||
draw_tile(4, block(4)) # solid brown
|
|
||||||
draw_tile(5, stripes(6, 7)) # gray stripes (ceiling)
|
|
||||||
draw_tile(6, stripes(1, 8)) # blue stripes (sky deco)
|
|
||||||
draw_tile(7, block(7)) # solid light gray
|
|
||||||
|
|
||||||
# landing pad tiles 64..67
|
|
||||||
draw_tile(64, landing_pad(5, 12)) # yellow/orange pad
|
|
||||||
draw_tile(65, landing_pad(11, 3)) # green pad
|
|
||||||
draw_tile(66, landing_pad(9, 8)) # blue pad
|
|
||||||
draw_tile(67, landing_pad(15, 7)) # white pad
|
|
||||||
|
|
||||||
write_indexed_png(img, os.path.join(ROOT, 'tiles', 'tilebank0.png'))
|
|
||||||
|
|
||||||
|
|
||||||
def gen_sprites():
|
|
||||||
"""RETIRED 2026-07-20: sprites.png is now fully extracted from the
|
|
||||||
C64 dump by stuff/spacetaxi/extractSprites.py (taxi $C1/$C0/$DC/
|
|
||||||
$DD, passenger $C4-$CB/$D9, flame $6DB0 table, warp $E2-$E8 --
|
|
||||||
216x96, 4 rows). Regenerating placeholder art here would clobber
|
|
||||||
the real sheet, the same trap gen_font has with the hand-edited
|
|
||||||
font.png. Refuses to run; use extractSprites.py instead."""
|
|
||||||
print("gen_sprites: REFUSING -- sprites.png is real C64 art now.")
|
|
||||||
print(" regenerate via: python3 stuff/spacetaxi/extractSprites.py "
|
|
||||||
"stuff/spacetaxi/raw.bin examples/spacetaxi/assets/sprites/sprites.png")
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def gen_sprites_retired():
|
|
||||||
img = new_canvas(9 * 24, 3 * 24) # 216x72: 9 cols x 3 rows of 24x24
|
|
||||||
d = ImageDraw.Draw(img)
|
|
||||||
|
|
||||||
# ---- Taxi cels: simple side-view "pod with thruster" ----
|
|
||||||
# Cel 0 = idle (thruster off)
|
|
||||||
# Cel 1..3 = thrust-on, alternating flame frames
|
|
||||||
for cel in range(4):
|
|
||||||
x = cel * 24
|
|
||||||
# Pod body: rounded rectangle in gray
|
|
||||||
d.rectangle([x+2, 4, x+21, 16], fill=7, outline=15)
|
|
||||||
# Cockpit window in blue
|
|
||||||
d.rectangle([x+13, 6, x+19, 10], fill=8, outline=15)
|
|
||||||
# Landing skid
|
|
||||||
d.line([(x+3, 17), (x+20, 17)], fill=6)
|
|
||||||
d.line([(x+3, 17), (x+3, 19)], fill=6)
|
|
||||||
d.line([(x+20,17), (x+20, 19)], fill=6)
|
|
||||||
# Thruster flame (cels 1..3 only, alternating shape)
|
|
||||||
if cel >= 1:
|
|
||||||
flame_color = [2, 12, 5][(cel - 1) % 3]
|
|
||||||
flame_top = 18
|
|
||||||
flame_bot = 22 if cel != 2 else 23
|
|
||||||
d.polygon([(x+8, flame_top),
|
|
||||||
(x+15, flame_top),
|
|
||||||
(x+12, flame_bot)], fill=flame_color)
|
|
||||||
|
|
||||||
# ---- Taxi shrink cels (transporter warp): the cab receding into the
|
|
||||||
# distance, 5 progressively smaller sizes, centered in the 24x24 cell.
|
|
||||||
# Placed in the free row-0 slots (cels 4..8). No flame -- it is warping
|
|
||||||
# away, not thrusting. The renderer draws one per warp step ($E2..$E9
|
|
||||||
# on the C64 = 8 shrink cels; the port approximates with 5 sizes).
|
|
||||||
for i in range(5):
|
|
||||||
cx = (4 + i) * 24 + 12
|
|
||||||
scale = 1.0 - i * 0.19 # 1.0, 0.81, 0.62, 0.43, 0.24
|
|
||||||
bw = max(3, int(round(19 * scale)))
|
|
||||||
bh = max(2, int(round(12 * scale)))
|
|
||||||
x0 = cx - bw // 2
|
|
||||||
y0 = 12 - bh // 2
|
|
||||||
# Pod body (gray, white outline)
|
|
||||||
d.rectangle([x0, y0, x0 + bw, y0 + bh], fill=7, outline=15)
|
|
||||||
# Cockpit window (blue) while big enough to show
|
|
||||||
if bw >= 8:
|
|
||||||
d.rectangle([x0 + bw - 5, y0 + 1, x0 + bw - 1, y0 + 3], fill=8)
|
|
||||||
|
|
||||||
# ---- Passenger cels: small stick figure, walk cycle ----
|
|
||||||
# Centered in 16x16, walking right.
|
|
||||||
for cel in range(4):
|
|
||||||
x = cel * 16
|
|
||||||
y = 24
|
|
||||||
# Head
|
|
||||||
d.ellipse([x+5, y+1, x+10, y+6], fill=10, outline=15)
|
|
||||||
# Body
|
|
||||||
d.line([(x+7, y+7), (x+7, y+12)], fill=15)
|
|
||||||
# Arms (swing alternately based on cel)
|
|
||||||
if cel == 0 or cel == 2:
|
|
||||||
d.line([(x+4, y+10), (x+10, y+10)], fill=15)
|
|
||||||
else:
|
|
||||||
d.line([(x+3, y+9), (x+11, y+11)], fill=15)
|
|
||||||
# Legs (alternating stride)
|
|
||||||
if cel == 0 or cel == 2:
|
|
||||||
d.line([(x+7, y+12), (x+4, y+15)], fill=15)
|
|
||||||
d.line([(x+7, y+12), (x+10,y+15)], fill=15)
|
|
||||||
else:
|
|
||||||
d.line([(x+7, y+12), (x+5, y+15)], fill=15)
|
|
||||||
d.line([(x+7, y+12), (x+9, y+15)], fill=15)
|
|
||||||
|
|
||||||
write_indexed_png(img, os.path.join(ROOT, 'sprites', 'sprites.png'))
|
|
||||||
|
|
||||||
|
|
||||||
def gen_font():
|
|
||||||
"""font.png: 320x200 (40x25 grid of 8x8 ASCII glyphs).
|
|
||||||
Authored manually for the printable subset (0x20..0x7E).
|
|
||||||
Each glyph cell is at (ascii%40, ascii/40) when ascii<128.
|
|
||||||
Unfilled cells stay black (treated as TILE_NO_GLYPH at runtime).
|
|
||||||
|
|
||||||
WARNING: this generator is UPPERCASE-ONLY (lowercase maps to the
|
|
||||||
uppercase glyphs). The COMMITTED font.png has been hand-extended with
|
|
||||||
real lowercase glyphs, so running this clobbers it -- the HUD reverts
|
|
||||||
to all-caps. Regenerate SPRITES only (gen_sprites) unless you also add
|
|
||||||
the lowercase glyphs here first; then `git checkout` font.png.
|
|
||||||
"""
|
|
||||||
# A compact 5-pixel-wide bitmap font, drawn in 7 rows with 1px
|
|
||||||
# padding on all sides (so 5x7 active inside an 8x8 cell, baseline
|
|
||||||
# at row 6). The font supports digits, uppercase letters, space,
|
|
||||||
# and basic punctuation -- enough for HUD text. Lowercase letters
|
|
||||||
# map to the same glyphs as uppercase (room to add later).
|
|
||||||
GLYPHS = {
|
|
||||||
' ': [],
|
|
||||||
'!': ['..#..', '..#..', '..#..', '..#..', '.....', '..#..', '.....'],
|
|
||||||
'"': ['.#.#.', '.#.#.', '.....', '.....', '.....', '.....', '.....'],
|
|
||||||
'0': ['.###.', '#...#', '#..##', '#.#.#', '##..#', '#...#', '.###.'],
|
|
||||||
'1': ['..#..', '.##..', '..#..', '..#..', '..#..', '..#..', '.###.'],
|
|
||||||
'2': ['.###.', '#...#', '....#', '...#.', '..#..', '.#...', '#####'],
|
|
||||||
'3': ['.###.', '#...#', '....#', '..##.', '....#', '#...#', '.###.'],
|
|
||||||
'4': ['...#.', '..##.', '.#.#.', '#..#.', '#####', '...#.', '...#.'],
|
|
||||||
'5': ['#####', '#....', '####.', '....#', '....#', '#...#', '.###.'],
|
|
||||||
'6': ['.###.', '#....', '#....', '####.', '#...#', '#...#', '.###.'],
|
|
||||||
'7': ['#####', '....#', '...#.', '..#..', '..#..', '..#..', '..#..'],
|
|
||||||
'8': ['.###.', '#...#', '#...#', '.###.', '#...#', '#...#', '.###.'],
|
|
||||||
'9': ['.###.', '#...#', '#...#', '.####', '....#', '....#', '.###.'],
|
|
||||||
':': ['.....', '..#..', '.....', '.....', '.....', '..#..', '.....'],
|
|
||||||
'.': ['.....', '.....', '.....', '.....', '.....', '..#..', '.....'],
|
|
||||||
'-': ['.....', '.....', '.....', '#####', '.....', '.....', '.....'],
|
|
||||||
'/': ['....#', '...#.', '...#.', '..#..', '.#...', '.#...', '#....'],
|
|
||||||
'A': ['.###.', '#...#', '#...#', '#####', '#...#', '#...#', '#...#'],
|
|
||||||
'B': ['####.', '#...#', '#...#', '####.', '#...#', '#...#', '####.'],
|
|
||||||
'C': ['.###.', '#...#', '#....', '#....', '#....', '#...#', '.###.'],
|
|
||||||
'D': ['####.', '#...#', '#...#', '#...#', '#...#', '#...#', '####.'],
|
|
||||||
'E': ['#####', '#....', '#....', '####.', '#....', '#....', '#####'],
|
|
||||||
'F': ['#####', '#....', '#....', '####.', '#....', '#....', '#....'],
|
|
||||||
'G': ['.###.', '#...#', '#....', '#..##', '#...#', '#...#', '.###.'],
|
|
||||||
'H': ['#...#', '#...#', '#...#', '#####', '#...#', '#...#', '#...#'],
|
|
||||||
'I': ['.###.', '..#..', '..#..', '..#..', '..#..', '..#..', '.###.'],
|
|
||||||
'J': ['..###', '...#.', '...#.', '...#.', '...#.', '#..#.', '.##..'],
|
|
||||||
'K': ['#...#', '#..#.', '#.#..', '##...', '#.#..', '#..#.', '#...#'],
|
|
||||||
'L': ['#....', '#....', '#....', '#....', '#....', '#....', '#####'],
|
|
||||||
'M': ['#...#', '##.##', '#.#.#', '#.#.#', '#...#', '#...#', '#...#'],
|
|
||||||
'N': ['#...#', '##..#', '#.#.#', '#.#.#', '#..##', '#...#', '#...#'],
|
|
||||||
'O': ['.###.', '#...#', '#...#', '#...#', '#...#', '#...#', '.###.'],
|
|
||||||
'P': ['####.', '#...#', '#...#', '####.', '#....', '#....', '#....'],
|
|
||||||
'Q': ['.###.', '#...#', '#...#', '#...#', '#.#.#', '#..#.', '.##.#'],
|
|
||||||
'R': ['####.', '#...#', '#...#', '####.', '#.#..', '#..#.', '#...#'],
|
|
||||||
'S': ['.###.', '#...#', '#....', '.###.', '....#', '#...#', '.###.'],
|
|
||||||
'T': ['#####', '..#..', '..#..', '..#..', '..#..', '..#..', '..#..'],
|
|
||||||
'U': ['#...#', '#...#', '#...#', '#...#', '#...#', '#...#', '.###.'],
|
|
||||||
'V': ['#...#', '#...#', '#...#', '#...#', '.#.#.', '.#.#.', '..#..'],
|
|
||||||
'W': ['#...#', '#...#', '#...#', '#.#.#', '#.#.#', '##.##', '#...#'],
|
|
||||||
'X': ['#...#', '#...#', '.#.#.', '..#..', '.#.#.', '#...#', '#...#'],
|
|
||||||
'Y': ['#...#', '#...#', '.#.#.', '..#..', '..#..', '..#..', '..#..'],
|
|
||||||
'Z': ['#####', '....#', '...#.', '..#..', '.#...', '#....', '#####'],
|
|
||||||
}
|
|
||||||
|
|
||||||
img = new_canvas()
|
|
||||||
px = img.load()
|
|
||||||
|
|
||||||
def paint_glyph(ascii_code, glyph_rows):
|
|
||||||
col = ascii_code % 40
|
|
||||||
row = ascii_code // 40
|
|
||||||
bx = col * 8
|
|
||||||
by = row * 8
|
|
||||||
# active 5x7 area: cell-x 1..5, cell-y 0..6 (top row, 1px left pad)
|
|
||||||
for ry, line in enumerate(glyph_rows):
|
|
||||||
for rx, ch in enumerate(line):
|
|
||||||
if ch == '#':
|
|
||||||
px[bx + 1 + rx, by + ry] = 15 # white
|
|
||||||
|
|
||||||
for ascii_code in range(32, 127):
|
|
||||||
c = chr(ascii_code)
|
|
||||||
if c in GLYPHS:
|
|
||||||
paint_glyph(ascii_code, GLYPHS[c])
|
|
||||||
elif 'a' <= c <= 'z':
|
|
||||||
paint_glyph(ascii_code, GLYPHS[c.upper()])
|
|
||||||
|
|
||||||
write_indexed_png(img, os.path.join(ROOT, 'font.png'))
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
os.makedirs(os.path.join(ROOT, 'tiles'), exist_ok=True)
|
|
||||||
os.makedirs(os.path.join(ROOT, 'sprites'), exist_ok=True)
|
|
||||||
print("genPlaceholderArt: emitting 320x200 indexed PNGs...")
|
|
||||||
gen_tilebank()
|
|
||||||
gen_sprites()
|
|
||||||
gen_font()
|
|
||||||
print("done.")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
BIN
examples/spacetaxi/assets/sprites/sprites.png
(Stored with Git LFS)
BIN
examples/spacetaxi/assets/sprites/sprites.png
(Stored with Git LFS)
Binary file not shown.
BIN
examples/spacetaxi/assets/tiles/tbank0.png
(Stored with Git LFS)
BIN
examples/spacetaxi/assets/tiles/tbank0.png
(Stored with Git LFS)
Binary file not shown.
BIN
examples/spacetaxi/assets/tiles/tbank1.png
(Stored with Git LFS)
BIN
examples/spacetaxi/assets/tiles/tbank1.png
(Stored with Git LFS)
Binary file not shown.
BIN
examples/spacetaxi/assets/tiles/tbank2.png
(Stored with Git LFS)
BIN
examples/spacetaxi/assets/tiles/tbank2.png
(Stored with Git LFS)
Binary file not shown.
BIN
examples/spacetaxi/assets/tiles/tilebank0.png
(Stored with Git LFS)
BIN
examples/spacetaxi/assets/tiles/tilebank0.png
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1,496 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# extractFromDump.py -- convert a VICE C64 memory dump into JoeyLib
|
|
||||||
# Space Taxi assets.
|
|
||||||
#
|
|
||||||
# Reads: /tmp/spacetaxi/mem0000-<labelN>.bin (VICE `save` output;
|
|
||||||
# 2-byte LE start-addr header + 64 KB RAM).
|
|
||||||
# Writes: examples/spacetaxi/assets/tiles/tilebank<N>.png
|
|
||||||
# examples/spacetaxi/assets/levels/level<NN>.txt
|
|
||||||
# examples/spacetaxi/assets/sprites/sprites.png (level1 only)
|
|
||||||
# examples/spacetaxi/assets/font.png (level1 only)
|
|
||||||
#
|
|
||||||
# Per-level:
|
|
||||||
# - Custom charset at $2800-$2FFF (2 KB, 256 chars * 8 bytes mono)
|
|
||||||
# becomes a tile bank PNG: 40 cols x 25 rows of 8x8 cells, with
|
|
||||||
# tile index N at cell (N%40, N/40). The PNG uses the EGA-ish
|
|
||||||
# 16-color palette we already install on stage.
|
|
||||||
# - Screen RAM at $0400-$07E7 (1000 bytes = 40x25 char codes) and
|
|
||||||
# color RAM at $D800-$DBE7 (1000 bytes = 40x25 palette nybbles)
|
|
||||||
# become the tilemap+colormap in the level .txt source.
|
|
||||||
#
|
|
||||||
# Sprite data at $3000-$37FF + sprite pointer table at $07F8-$07FF
|
|
||||||
# is decoded once (it's the same across levels for the active set):
|
|
||||||
# - sprite 0 / 7: the taxi cab body & shadow
|
|
||||||
# - sprite 1..6: passenger frames / animation cels
|
|
||||||
# 64 bytes per sprite, 24x21 1-bit, MSB-leftmost. Color comes from
|
|
||||||
# $D027-$D02E (one color per sprite).
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# python3 extractFromDump.py stuff/spacetaxi/mem0000-level1.bin level01 1
|
|
||||||
# python3 extractFromDump.py stuff/spacetaxi/mem0000-level2.bin level02 2
|
|
||||||
# args: <dump-path> <level-name-without-extension> <tilebank-id>
|
|
||||||
#
|
|
||||||
# Dumps live in stuff/spacetaxi/ (committed-ish, repo-local) rather
|
|
||||||
# than /tmp so they survive reboots and stay with the project.
|
|
||||||
|
|
||||||
import os
|
|
||||||
import struct
|
|
||||||
import sys
|
|
||||||
from collections import Counter
|
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
|
|
||||||
# C64 standard color palette (EGA-ish 16-color JoeyLib mapping).
|
|
||||||
# Indices 0..15 match the C64 VIC-II color register order so a value
|
|
||||||
# read from $D8xx or $D02x maps directly to a JoeyLib palette slot.
|
|
||||||
# Colors below approximate Pepto's standard PAL palette in $RGB form.
|
|
||||||
C64_PALETTE_RGB = [
|
|
||||||
(0x00, 0x00, 0x00), # 0 black
|
|
||||||
(0xFF, 0xFF, 0xFF), # 1 white
|
|
||||||
(0x88, 0x39, 0x32), # 2 red
|
|
||||||
(0x67, 0xB6, 0xBD), # 3 cyan
|
|
||||||
(0x8B, 0x3F, 0x96), # 4 purple
|
|
||||||
(0x55, 0xA0, 0x49), # 5 green
|
|
||||||
(0x40, 0x31, 0x8D), # 6 blue
|
|
||||||
(0xBF, 0xCE, 0x72), # 7 yellow
|
|
||||||
(0x8B, 0x54, 0x29), # 8 orange
|
|
||||||
(0x57, 0x42, 0x00), # 9 brown
|
|
||||||
(0xB8, 0x69, 0x62), # 10 light red
|
|
||||||
(0x50, 0x50, 0x50), # 11 dark gray
|
|
||||||
(0x78, 0x78, 0x78), # 12 mid gray
|
|
||||||
(0x94, 0xE0, 0x89), # 13 light green
|
|
||||||
(0x78, 0x69, 0xC4), # 14 light blue
|
|
||||||
(0x9F, 0x9F, 0x9F), # 15 light gray
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
|
|
||||||
|
|
||||||
def load_dump(path):
|
|
||||||
with open(path, "rb") as f:
|
|
||||||
raw = f.read()
|
|
||||||
start = raw[0] | (raw[1] << 8)
|
|
||||||
if start != 0:
|
|
||||||
raise SystemExit(f"{path}: unexpected start ${start:04X}, want $0000")
|
|
||||||
if len(raw) - 2 != 0x10000:
|
|
||||||
raise SystemExit(f"{path}: not a 64K dump (got {len(raw) - 2} bytes)")
|
|
||||||
return raw[2:]
|
|
||||||
|
|
||||||
|
|
||||||
def new_canvas(w, h):
|
|
||||||
img = Image.new("P", (w, h), 0)
|
|
||||||
flat = bytearray()
|
|
||||||
for r, g, b in C64_PALETTE_RGB:
|
|
||||||
flat.extend((r, g, b))
|
|
||||||
flat.extend(b"\x00" * (768 - len(flat)))
|
|
||||||
img.putpalette(bytes(flat))
|
|
||||||
return img
|
|
||||||
|
|
||||||
|
|
||||||
def paint_charset_glyph(px, mem, src_code, bx, by):
|
|
||||||
"""Plot the 8x8 glyph for C64 screen code `src_code` at pixel
|
|
||||||
(bx, by) of the indexed-palette PIL image. Foreground = palette
|
|
||||||
index 1, background untouched (already index 0 from new_canvas).
|
|
||||||
"""
|
|
||||||
src = 0x2800 + src_code * 8
|
|
||||||
for r in range(8):
|
|
||||||
byte = mem[src + r]
|
|
||||||
for c in range(8):
|
|
||||||
if byte & (0x80 >> c):
|
|
||||||
px[bx + c, by + r] = 1
|
|
||||||
|
|
||||||
|
|
||||||
def extract_tilebank(mem, out_path, code_to_slot=None):
|
|
||||||
"""Render the custom charset at $2800-$2FFF as a 320x200 PNG.
|
|
||||||
When `code_to_slot` is None, lays out all 256 chars at their raw
|
|
||||||
C64 positions (40 cols x 25 rows). When `code_to_slot` is given,
|
|
||||||
writes each charset glyph at the slot the level loader expects
|
|
||||||
-- i.e. the bank tile at position N is the glyph for screen-code
|
|
||||||
`code_for_slot[N]`. That keeps level data and tilebank aligned
|
|
||||||
after the level extractor's frequency-sorted code remapping.
|
|
||||||
"""
|
|
||||||
img = new_canvas(320, 200)
|
|
||||||
px = img.load()
|
|
||||||
if code_to_slot is None:
|
|
||||||
for ch in range(256):
|
|
||||||
paint_charset_glyph(px, mem, ch, (ch % 40) * 8, (ch // 40) * 8)
|
|
||||||
else:
|
|
||||||
# Invert: slot -> code. Multiple codes can collide into slot 0
|
|
||||||
# (background); only the first one we see "owns" that slot for
|
|
||||||
# painting purposes -- the rest just fall through to the same
|
|
||||||
# blank tile. Highest priority is the explicit space mapping.
|
|
||||||
slot_to_code = {}
|
|
||||||
for code, slot in code_to_slot.items():
|
|
||||||
slot_to_code.setdefault(slot, code)
|
|
||||||
for slot, code in slot_to_code.items():
|
|
||||||
if slot >= 256:
|
|
||||||
continue
|
|
||||||
paint_charset_glyph(px, mem, code, (slot % 40) * 8, (slot // 40) * 8)
|
|
||||||
img.save(out_path, "PNG", optimize=True)
|
|
||||||
|
|
||||||
|
|
||||||
def build_code_to_slot(mem):
|
|
||||||
"""Build the screen-code -> alphabet-slot map shared between the
|
|
||||||
tilebank emission and the level emission. The slot index is what
|
|
||||||
the runtime sees in level.tilemap[]; the tilebank must mirror this
|
|
||||||
ordering so a level-data slot-N cell renders the charset glyph
|
|
||||||
that was actually used in the original screen RAM at that code.
|
|
||||||
"""
|
|
||||||
screen = mem[0x0400:0x0400 + 1000]
|
|
||||||
codes = sorted(Counter(screen).items(), key=lambda kv: (-kv[1], kv[0]))
|
|
||||||
code_to_slot = {}
|
|
||||||
next_slot = 0
|
|
||||||
for code, _count in codes:
|
|
||||||
if code == 0x20: # space -> always index 0
|
|
||||||
code_to_slot[code] = 0
|
|
||||||
continue
|
|
||||||
if code in code_to_slot:
|
|
||||||
continue
|
|
||||||
if next_slot >= len(ALPHABET):
|
|
||||||
code_to_slot[code] = 0
|
|
||||||
continue
|
|
||||||
# Reserve slot 0 for background (space). The very first
|
|
||||||
# non-space code claims slot 1, etc.
|
|
||||||
if next_slot == 0:
|
|
||||||
next_slot = 1
|
|
||||||
code_to_slot[code] = next_slot
|
|
||||||
next_slot += 1
|
|
||||||
return code_to_slot
|
|
||||||
|
|
||||||
|
|
||||||
def screencode_to_char(code):
|
|
||||||
"""Convert a C64 screen code into a printable tilemap character
|
|
||||||
using the encoding the .txt format already understands:
|
|
||||||
0 -> '.' (treated as 0)
|
|
||||||
1..9 -> '1'..'9' (digits encode the lower 10 indices)
|
|
||||||
but we shift to use up most of the index space below.
|
|
||||||
For arbitrary 0..255 we need a 256-symbol alphabet -- not feasible
|
|
||||||
in a single character per cell. So we encode as TWO chars per cell
|
|
||||||
using base-16 hex would still only cover 0..255. Decision: keep
|
|
||||||
.txt cells one-char and map screen codes via a custom 64-char
|
|
||||||
alphabet plus an @char directive table emitted into the file so
|
|
||||||
the loader knows exactly which screen code each char represents.
|
|
||||||
"""
|
|
||||||
# This function is unused as a one-char encoder; we encode via the
|
|
||||||
# generic alphabet below instead.
|
|
||||||
return code
|
|
||||||
|
|
||||||
|
|
||||||
# Generic 1-char alphabet (62 codes); the level loader's index map
|
|
||||||
# was: '.' or ' '=0, '0'..'9'=0..9, 'A'..'Z'=10..35, 'a'..'z'=36..61.
|
|
||||||
# That's only 62 codes, not 256. The original C64 levels use ~30
|
|
||||||
# distinct screen codes per level so most levels fit. We map the N
|
|
||||||
# most-frequent screen codes to those alphabet slots.
|
|
||||||
ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
||||||
|
|
||||||
|
|
||||||
def extract_level(mem, level_name, tilebank_id, out_path, code_to_alpha):
|
|
||||||
"""Render screen RAM + color RAM as an STL1-format .txt source
|
|
||||||
using the prebuilt screen-code -> alphabet-slot mapping. Caller
|
|
||||||
must pass the same mapping that was used to reorder the tilebank
|
|
||||||
PNG; otherwise the level tilemap and tilebank diverge.
|
|
||||||
"""
|
|
||||||
screen = mem[0x0400:0x0400 + 1000] # 40x25
|
|
||||||
color = mem[0xD800:0xD800 + 1000] # 40x25, low nybble = palette slot
|
|
||||||
codes = sorted(Counter(screen).items(), key=lambda kv: (-kv[1], kv[0]))
|
|
||||||
|
|
||||||
# Full 25-row C64 screen. The title screen uses all 25 rows
|
|
||||||
# (PUBLISHED ... SOFTWARE credit wraps across rows 21, 22, 24
|
|
||||||
# with frame chars interleaved). Gameplay HUD draws over the
|
|
||||||
# bottom 3 rows at runtime.
|
|
||||||
playfield_rows = 25
|
|
||||||
|
|
||||||
lines = []
|
|
||||||
lines.append(f"# Extracted from VICE memory dump. Edit at your own risk;")
|
|
||||||
lines.append(f"# regenerating will overwrite. Keep the @tile mappings in")
|
|
||||||
lines.append(f"# sync with the tilebank PNG (extracted from $2800).")
|
|
||||||
lines.append(f"")
|
|
||||||
lines.append(f"@name {level_name.upper()}")
|
|
||||||
lines.append(f"@tilebank {tilebank_id}")
|
|
||||||
lines.append(f"@music 0")
|
|
||||||
bg = mem[0xD021] & 0x0F
|
|
||||||
border = mem[0xD020] & 0x0F
|
|
||||||
lines.append(f"@bgColor {bg}")
|
|
||||||
lines.append(f"@borderColor {border}")
|
|
||||||
|
|
||||||
# Best-effort taxi spawn: middle-top of playfield. The original
|
|
||||||
# script-driven spawn position would need to be lifted out of
|
|
||||||
# game state at dump time; for now hand-edit if you want it
|
|
||||||
# to match the cracker's preferred spawn.
|
|
||||||
lines.append(f"@taxiSpawn 18 2")
|
|
||||||
lines.append(f"")
|
|
||||||
|
|
||||||
# Pads: scan the playfield for "landable surfaces" -- runs of
|
|
||||||
# >= 3 identical non-background tile cells with two or more
|
|
||||||
# empty rows directly above (so the taxi can approach from the
|
|
||||||
# top). Per-row scanning runs top-to-bottom; for the same
|
|
||||||
# surface tile across rows we only take the topmost contiguous
|
|
||||||
# platform edge.
|
|
||||||
bg_code = codes[0][0] if codes else 0x20
|
|
||||||
def is_bg(code):
|
|
||||||
return code == bg_code or code == 0x20
|
|
||||||
|
|
||||||
pads = []
|
|
||||||
seen_used = [False] * (playfield_rows * 40)
|
|
||||||
for row in range(2, playfield_rows):
|
|
||||||
line = screen[row * 40:(row + 1) * 40]
|
|
||||||
prev = screen[(row - 1) * 40:row * 40]
|
|
||||||
prev2 = screen[(row - 2) * 40:(row - 1) * 40]
|
|
||||||
col = 0
|
|
||||||
while col < 40 and len(pads) < 8:
|
|
||||||
if is_bg(line[col]):
|
|
||||||
col += 1
|
|
||||||
continue
|
|
||||||
j = col
|
|
||||||
while j < 40 and line[j] == line[col] and not seen_used[row * 40 + j]:
|
|
||||||
j += 1
|
|
||||||
width = j - col
|
|
||||||
# Approachable from above? At least 2/3 of cells in the
|
|
||||||
# 2 rows above this stretch must be background.
|
|
||||||
above_clear = 0
|
|
||||||
for k in range(col, j):
|
|
||||||
if is_bg(prev[k]):
|
|
||||||
above_clear += 1
|
|
||||||
if is_bg(prev2[k]):
|
|
||||||
above_clear += 1
|
|
||||||
if width >= 3 and above_clear >= width:
|
|
||||||
pads.append((row, col, width))
|
|
||||||
for k in range(col, j):
|
|
||||||
seen_used[row * 40 + k] = True
|
|
||||||
col = j
|
|
||||||
|
|
||||||
lines.append("# Pads auto-detected from landable surfaces (>= 3 wide,")
|
|
||||||
lines.append("# approachable from above). Verify and adjust as needed.")
|
|
||||||
pad_letter = ord("A")
|
|
||||||
for (row, col, width) in pads:
|
|
||||||
if pad_letter > ord("H"):
|
|
||||||
break
|
|
||||||
lines.append(f"@pad {chr(pad_letter)} {col} {row} {width}")
|
|
||||||
pad_letter += 1
|
|
||||||
if pad_letter == ord("A"):
|
|
||||||
lines.append("# (No landable surfaces auto-detected; emitting placeholders.)")
|
|
||||||
lines.append("@pad A 4 10 4")
|
|
||||||
lines.append("@pad B 32 10 4")
|
|
||||||
pad_letter = ord("C")
|
|
||||||
|
|
||||||
lines.append("")
|
|
||||||
# Fare list: simple round-robin between detected pads.
|
|
||||||
pad_count = pad_letter - ord("A")
|
|
||||||
if pad_count >= 2:
|
|
||||||
for k in range(pad_count):
|
|
||||||
src = chr(ord("A") + k)
|
|
||||||
dst = chr(ord("A") + ((k + 1) % pad_count))
|
|
||||||
lines.append(f"@fare {src} {dst} 45")
|
|
||||||
else:
|
|
||||||
lines.append("@fare A B 45")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Emit the @tile mapping for round-trip clarity (and so the
|
|
||||||
# author can read a level .txt and tell which screen code maps
|
|
||||||
# to which char). Format: @tile <char> <screencode> -- the
|
|
||||||
# mkstlevel parser ignores these today but they're useful
|
|
||||||
# documentation, and a future loader could honor them.
|
|
||||||
lines.append("# Screen-code -> alphabet-char mapping (one per unique code).")
|
|
||||||
inv = {v: k for k, v in code_to_alpha.items()}
|
|
||||||
for idx in sorted(inv):
|
|
||||||
ch = '.' if idx == 0 else ALPHABET[idx]
|
|
||||||
code = inv[idx]
|
|
||||||
lines.append(f"# @tile '{ch}' = $C64_screencode_{code:02X}")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Tilemap (22 rows of 40 chars each).
|
|
||||||
lines.append("@tilemap")
|
|
||||||
for r in range(playfield_rows):
|
|
||||||
row = screen[r * 40:(r + 1) * 40]
|
|
||||||
s = ""
|
|
||||||
for code in row:
|
|
||||||
idx = code_to_alpha.get(code, 0)
|
|
||||||
s += '.' if idx == 0 else ALPHABET[idx]
|
|
||||||
lines.append(s)
|
|
||||||
|
|
||||||
lines.append("")
|
|
||||||
lines.append("@colormap")
|
|
||||||
for r in range(playfield_rows):
|
|
||||||
row = color[r * 40:(r + 1) * 40]
|
|
||||||
s = "".join(
|
|
||||||
('0' if (c & 0x0F) == 0
|
|
||||||
else ALPHABET[(c & 0x0F)]
|
|
||||||
if (c & 0x0F) < len(ALPHABET) else '0')
|
|
||||||
for c in row
|
|
||||||
)
|
|
||||||
lines.append(s)
|
|
||||||
|
|
||||||
with open(out_path, "w") as fp:
|
|
||||||
fp.write("\n".join(lines) + "\n")
|
|
||||||
|
|
||||||
|
|
||||||
def extract_sprites(mem, out_path):
|
|
||||||
"""Render C64 hardware sprites into the JoeyLib-expected sprite
|
|
||||||
sheet layout (320x200, 16-color indexed PNG):
|
|
||||||
y= 0..23 : 1 taxi cel (24x24, x = 0)
|
|
||||||
sprite 0 ptr (in level1 dump: $DC -> $3700)
|
|
||||||
The adjacent slot $DD is the landing-gear-down
|
|
||||||
pose, not a thrust frame -- ignored for now.
|
|
||||||
y= 24..47 : 4 passenger cels (16x16 each, x = 0, 16, 32, 48)
|
|
||||||
Sprite 3 ptr ($07FB) is the passenger data; sprites 3-6 share it.
|
|
||||||
"""
|
|
||||||
img = new_canvas(320, 200)
|
|
||||||
px = img.load()
|
|
||||||
|
|
||||||
# Shared multicolor sprite colors from VIC registers.
|
|
||||||
mcm_color0 = mem[0xD025] & 0x0F # 2-bit value 01
|
|
||||||
mcm_color1 = mem[0xD026] & 0x0F # 2-bit value 11
|
|
||||||
mcm_mask = mem[0xD01C] # bit per sprite: 1 = multicolor
|
|
||||||
|
|
||||||
def paint_sprite_at(data_addr, sprite_color, dst_x, dst_y, dst_w, dst_h, multicolor=False):
|
|
||||||
# Mono C64 sprite is 24x21, 1 bit per pixel, 3 bytes per row.
|
|
||||||
# Multicolor C64 sprite is 12 "double-wide pixels" x 21 rows,
|
|
||||||
# 2 bits per pixel, still 3 bytes per row. Color mapping:
|
|
||||||
# 00 -> transparent
|
|
||||||
# 01 -> mcm_color0 ($D025)
|
|
||||||
# 10 -> sprite_color (sprite's own $D027+sp)
|
|
||||||
# 11 -> mcm_color1 ($D026)
|
|
||||||
if sprite_color == 0:
|
|
||||||
sprite_color = 1
|
|
||||||
sx_max = min(24, dst_w)
|
|
||||||
sy_max = min(21, dst_h)
|
|
||||||
for r in range(sy_max):
|
|
||||||
if multicolor:
|
|
||||||
for byte_idx in range(3):
|
|
||||||
byte = mem[data_addr + r * 3 + byte_idx]
|
|
||||||
# 4 multicolor pixels per byte; each spans 2
|
|
||||||
# hardware pixels wide.
|
|
||||||
for pp in range(4):
|
|
||||||
bits = (byte >> ((3 - pp) * 2)) & 0x03
|
|
||||||
if bits == 0:
|
|
||||||
continue
|
|
||||||
if bits == 1:
|
|
||||||
col = mcm_color0
|
|
||||||
elif bits == 2:
|
|
||||||
col = sprite_color
|
|
||||||
else:
|
|
||||||
col = mcm_color1
|
|
||||||
sx0 = byte_idx * 8 + pp * 2
|
|
||||||
if sx0 + 1 >= sx_max:
|
|
||||||
break
|
|
||||||
px[dst_x + sx0, dst_y + r] = col
|
|
||||||
px[dst_x + sx0 + 1, dst_y + r] = col
|
|
||||||
else:
|
|
||||||
for byte_idx in range(3):
|
|
||||||
byte = mem[data_addr + r * 3 + byte_idx]
|
|
||||||
for bit in range(8):
|
|
||||||
sx = byte_idx * 8 + bit
|
|
||||||
if sx >= sx_max:
|
|
||||||
break
|
|
||||||
if byte & (0x80 >> bit):
|
|
||||||
px[dst_x + sx, dst_y + r] = sprite_color
|
|
||||||
|
|
||||||
def paint_c64_sprite(sp_index, dst_x, dst_y, dst_w, dst_h):
|
|
||||||
ptr = mem[0x07F8 + sp_index]
|
|
||||||
if ptr == 0:
|
|
||||||
return False
|
|
||||||
sprite_color = mem[0xD027 + sp_index] & 0x0F
|
|
||||||
is_multicolor = bool(mcm_mask & (1 << sp_index))
|
|
||||||
paint_sprite_at(ptr * 64, sprite_color, dst_x, dst_y, dst_w, dst_h,
|
|
||||||
multicolor=is_multicolor)
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Single taxi cel from sprite 0's actual pointer.
|
|
||||||
paint_c64_sprite(0, 0, 0, 24, 24)
|
|
||||||
|
|
||||||
# 4 passenger cels = the shared C64 sprite 3 (sprites 3..6 all
|
|
||||||
# point to the same data in the level-1 dump).
|
|
||||||
for cel in range(4):
|
|
||||||
paint_c64_sprite(3, cel * 16, 24, 16, 16)
|
|
||||||
|
|
||||||
img.save(out_path, "PNG", optimize=True)
|
|
||||||
|
|
||||||
|
|
||||||
def extract_font(mem, out_path):
|
|
||||||
"""Render the custom charset (which IS the in-game font in C64
|
|
||||||
text-mode games) as the 320x200 font.png. Same layout as
|
|
||||||
tilebank, but here we want each glyph at cell (ascii%40,
|
|
||||||
ascii/40) for jlDrawText's ASCII map.
|
|
||||||
|
|
||||||
For Space Taxi specifically, the charset doubles as the level
|
|
||||||
tile bank AND any in-screen text Sierra-style. We map the C64
|
|
||||||
screen codes used for ASCII display (codes 1..26 = A..Z,
|
|
||||||
32..63 = punctuation/digits) directly to their ASCII positions
|
|
||||||
in the JoeyLib font sheet.
|
|
||||||
"""
|
|
||||||
img = new_canvas(320, 200)
|
|
||||||
px = img.load()
|
|
||||||
|
|
||||||
def render_at(ascii_code, charset_idx):
|
|
||||||
if ascii_code < 32 or ascii_code >= 128:
|
|
||||||
return
|
|
||||||
src = 0x2800 + (charset_idx & 0xFF) * 8
|
|
||||||
col = ascii_code % 40
|
|
||||||
row = ascii_code // 40
|
|
||||||
bx = col * 8
|
|
||||||
by = row * 8
|
|
||||||
for r in range(8):
|
|
||||||
byte = mem[src + r]
|
|
||||||
for c in range(8):
|
|
||||||
if byte & (0x80 >> c):
|
|
||||||
px[bx + c, by + r] = 1
|
|
||||||
|
|
||||||
# C64 upper-only screen codes: 1..26 = A..Z, 27=[ 28=£ 29=] 30=↑ 31=←
|
|
||||||
# 32 = space, 33..63 = !"# ... ?, etc. We map ASCII directly.
|
|
||||||
for c in range(32, 64):
|
|
||||||
render_at(c, c) # symbols + digits 1:1
|
|
||||||
for c, code in enumerate("ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
|
|
||||||
render_at(ord(code), 1 + c) # screen code 1..26 -> A..Z
|
|
||||||
|
|
||||||
img.save(out_path, "PNG", optimize=True)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) != 4:
|
|
||||||
print("usage: extractFromDump.py <dump-path> <level-name> <tilebank-id>", file=sys.stderr)
|
|
||||||
sys.exit(2)
|
|
||||||
dump_path = sys.argv[1]
|
|
||||||
level_name = sys.argv[2]
|
|
||||||
tilebank_id = int(sys.argv[3])
|
|
||||||
|
|
||||||
mem = load_dump(dump_path)
|
|
||||||
print(f"loaded {dump_path}: 64 KB OK")
|
|
||||||
|
|
||||||
tiles_dir = os.path.join(ROOT, "assets", "tiles")
|
|
||||||
sprites_dir = os.path.join(ROOT, "assets", "sprites")
|
|
||||||
levels_dir = os.path.join(ROOT, "assets", "levels")
|
|
||||||
os.makedirs(tiles_dir, exist_ok=True)
|
|
||||||
os.makedirs(sprites_dir, exist_ok=True)
|
|
||||||
os.makedirs(levels_dir, exist_ok=True)
|
|
||||||
|
|
||||||
# Build the screen-code -> bank-slot mapping once and feed it to
|
|
||||||
# both extractors so the tilemap and tilebank stay aligned.
|
|
||||||
code_to_slot = build_code_to_slot(mem)
|
|
||||||
|
|
||||||
# 8.3 DOS naming -- "tilebank0" is 9 chars and fopen fails under
|
|
||||||
# DOSBox strict 8.3 mode. Keep the short form across the pipeline.
|
|
||||||
tilebank_path = os.path.join(tiles_dir, f"tbank{tilebank_id}.png")
|
|
||||||
extract_tilebank(mem, tilebank_path, code_to_slot)
|
|
||||||
print(f" wrote {tilebank_path}")
|
|
||||||
|
|
||||||
level_path = os.path.join(levels_dir, f"{level_name}.txt")
|
|
||||||
extract_level(mem, level_name, tilebank_id, level_path, code_to_slot)
|
|
||||||
print(f" wrote {level_path}")
|
|
||||||
|
|
||||||
# Sprites + font come from level 1 only (sprite pointer table is
|
|
||||||
# script-driven so it differs by scene; we use the first dump's
|
|
||||||
# set as the canonical sheet, plus its charset as the font).
|
|
||||||
if tilebank_id == 1:
|
|
||||||
sprite_path = os.path.join(sprites_dir, "sprites.png")
|
|
||||||
extract_sprites(mem, sprite_path)
|
|
||||||
print(f" wrote {sprite_path}")
|
|
||||||
|
|
||||||
font_path = os.path.join(ROOT, "assets", "font.png")
|
|
||||||
extract_font(mem, font_path)
|
|
||||||
print(f" wrote {font_path}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
@ -983,7 +983,6 @@ int main(void) {
|
||||||
#else
|
#else
|
||||||
config.codegenBytes = 160UL * 1024;
|
config.codegenBytes = 160UL * 1024;
|
||||||
#endif
|
#endif
|
||||||
config.audioBytes = 32UL * 1024;
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,6 @@ int main(void) {
|
||||||
* the chunky two-shift case asks for. 32 KB fits a 16x16 ball
|
* the chunky two-shift case asks for. 32 KB fits a 16x16 ball
|
||||||
* with all variants. */
|
* with all variants. */
|
||||||
config.codegenBytes = 32UL * 1024;
|
config.codegenBytes = 32UL * 1024;
|
||||||
config.audioBytes = 64UL * 1024;
|
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
|
||||||
318
examples/tilebank/tilebank.c
Normal file
318
examples/tilebank/tilebank.c
Normal file
|
|
@ -0,0 +1,318 @@
|
||||||
|
// Tile-bank check on a real port: loads this port's bake of the host gate's
|
||||||
|
// fixture both ways, checks every pixel, and makes sure every other target's
|
||||||
|
// bake is refused. It is the on-machine half of scripts/check-tilebank.sh:
|
||||||
|
// that host gate proves the shared loader and the generic paste; this proves
|
||||||
|
// that assetbake's encoder for THIS target writes exactly the layout this
|
||||||
|
// port's native tile paste reads, and that this build refuses the others.
|
||||||
|
//
|
||||||
|
// DATA/tilebank/ is staged by tests/host/tileBankStage.py (the make rule in
|
||||||
|
// make/<port>.mk), from tests/host/tileBank.png:
|
||||||
|
// own.tbk this port's bake: the bank that must load
|
||||||
|
// refuse<n>.tbk n = 1, 2, ...: every other target's bake
|
||||||
|
// expect.bin the PNG decoded by PIL, independent of assetbake's
|
||||||
|
// encoders: tileCount LE16, palette[16] LE16 $0RGB, then
|
||||||
|
// tileCount * 64 colour indices (tiles in bank order, each
|
||||||
|
// row-major)
|
||||||
|
//
|
||||||
|
// The checks, all on the stage:
|
||||||
|
// refused each refuse<n>.tbk loads nothing through jlTileBankLoad and
|
||||||
|
// pastes nothing through jlTileBankLoadToSurface
|
||||||
|
// streamed jlTileBankLoadToSurface pastes every tile of own.tbk, the
|
||||||
|
// palette it returns is the PNG's, and every pixel of the stage
|
||||||
|
// is right: blocks below the tile count show the PNG's tiles in
|
||||||
|
// bank order, the rest keep FILL_COLOR
|
||||||
|
// pasted the same pixels again through jlTileBankLoad + jlTilePaste
|
||||||
|
// Both loads draw onto a stage cleared to the host harness's FILL_COLOR with
|
||||||
|
// the default palette and SCBs, exactly the surface whose hash the host
|
||||||
|
// harness prints, so both hashes must equal that one cross-port value.
|
||||||
|
//
|
||||||
|
// Result: one jlLog line, flushed before anything is shown,
|
||||||
|
// TILEBANK: PASS tiles=55 streamed=55 pasted=55 palette=ok streamHash=<hash> pasteHash=<hash> refused=4/4
|
||||||
|
// which scripts/verify-<port>-tilebank.sh reads (and holds the hashes against
|
||||||
|
// the host's), then the tiles in the bank's own colours over a lamp centred on
|
||||||
|
// the stage: LAMP_PASS_COLOR or LAMP_FAIL_COLOR in palette LAMP_PALETTE, with
|
||||||
|
// the rest of its rows left at FILL_COLOR. The MAME gates find the verdict
|
||||||
|
// there before they read the log. Any key exits.
|
||||||
|
//
|
||||||
|
// Portable C only: nothing here knows which port it runs on.
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <joey/joey.h>
|
||||||
|
#include <joey/file.h>
|
||||||
|
|
||||||
|
// ----- Constants -----
|
||||||
|
|
||||||
|
// The staged files (tests/host/tileBankStage.py names them).
|
||||||
|
#define OWN_BANK "tilebank/own.tbk"
|
||||||
|
#define EXPECT_NAME "tilebank/expect.bin"
|
||||||
|
#define REFUSE_BANK_FORMAT "tilebank/refuse%u.tbk"
|
||||||
|
#define BANK_NAME_BYTES 32
|
||||||
|
|
||||||
|
// expect.bin layout (see the header comment).
|
||||||
|
#define LE16_BYTES 2
|
||||||
|
#define BYTE_BITS 8
|
||||||
|
#define PALETTE_COLORS SURFACE_COLORS_PER_PALETTE
|
||||||
|
#define EXPECT_OFF_PALETTE LE16_BYTES
|
||||||
|
#define EXPECT_HEADER_BYTES (EXPECT_OFF_PALETTE + PALETTE_COLORS * LE16_BYTES)
|
||||||
|
#define TILE_PIXELS (TILE_PIXELS_PER_SIDE * TILE_PIXELS_PER_SIDE)
|
||||||
|
|
||||||
|
// The stage is cleared to this before every load. It MUST equal FILL_COLOR in
|
||||||
|
// tests/host/tileBankHost.c: the host's hash is of a surface cleared to it.
|
||||||
|
#define FILL_COLOR 9u
|
||||||
|
|
||||||
|
// No sprites here, so the port's default codegen cache is plenty.
|
||||||
|
#define CODEGEN_DEFAULT 0u
|
||||||
|
|
||||||
|
// The verdict lamp, centred on the stage. Its rows get their own palette so
|
||||||
|
// the lamp colours cannot collide with the bank's sixteen.
|
||||||
|
#define LAMP_W 64
|
||||||
|
#define LAMP_H 64
|
||||||
|
#define LAMP_X ((SURFACE_WIDTH - LAMP_W) / 2)
|
||||||
|
#define LAMP_Y ((SURFACE_HEIGHT - LAMP_H) / 2)
|
||||||
|
#define LAMP_PALETTE 1u
|
||||||
|
#define LAMP_PASS_COLOR 1u
|
||||||
|
#define LAMP_FAIL_COLOR 2u
|
||||||
|
#define LAMP_DARK_RGB 0x0000u
|
||||||
|
#define LAMP_PASS_RGB 0x00F0u
|
||||||
|
#define LAMP_FAIL_RGB 0x0F00u
|
||||||
|
|
||||||
|
// ----- Prototypes -----
|
||||||
|
|
||||||
|
static bool loadExpected(void);
|
||||||
|
static void pasteTiles(uint16_t count);
|
||||||
|
static bool readAll(FILE *fp, uint8_t *dst, uint32_t bytes);
|
||||||
|
static uint16_t readLe16(const uint8_t *bytes);
|
||||||
|
static bool refuseBankFound(char *name, uint16_t n);
|
||||||
|
static uint16_t refuseCheck(uint16_t *banks);
|
||||||
|
static bool runChecks(void);
|
||||||
|
static void showVerdict(bool pass);
|
||||||
|
static bool stageMatches(uint16_t tilesDrawn);
|
||||||
|
|
||||||
|
// ----- State -----
|
||||||
|
|
||||||
|
static uint16_t gBankPalette[PALETTE_COLORS];
|
||||||
|
static uint16_t gExpectCount = 0u;
|
||||||
|
static uint16_t gExpectPalette[PALETTE_COLORS];
|
||||||
|
static uint8_t *gExpectPixels = NULL;
|
||||||
|
static jlSurfaceT *gScreen = NULL;
|
||||||
|
static jlTileT *gTiles = NULL;
|
||||||
|
static bool *gValid = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
// Read expect.bin, and size the tile buffers to its tile count.
|
||||||
|
static bool loadExpected(void) {
|
||||||
|
FILE *fp;
|
||||||
|
uint8_t head[EXPECT_HEADER_BYTES];
|
||||||
|
uint32_t pixelBytes;
|
||||||
|
uint16_t i;
|
||||||
|
bool ok;
|
||||||
|
|
||||||
|
fp = jlDataOpen(EXPECT_NAME, "rb");
|
||||||
|
if (fp == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ok = readAll(fp, head, sizeof(head));
|
||||||
|
if (ok) {
|
||||||
|
gExpectCount = readLe16(head);
|
||||||
|
ok = gExpectCount > 0u;
|
||||||
|
}
|
||||||
|
if (ok) {
|
||||||
|
for (i = 0u; i < PALETTE_COLORS; i++) {
|
||||||
|
gExpectPalette[i] = readLe16(&head[EXPECT_OFF_PALETTE + i * LE16_BYTES]);
|
||||||
|
}
|
||||||
|
pixelBytes = (uint32_t)gExpectCount * TILE_PIXELS;
|
||||||
|
gExpectPixels = (uint8_t *)jlAlloc(pixelBytes);
|
||||||
|
gTiles = (jlTileT *)jlAlloc((uint32_t)gExpectCount * sizeof(jlTileT));
|
||||||
|
gValid = (bool *)jlAlloc((uint32_t)gExpectCount * sizeof(bool));
|
||||||
|
ok = gExpectPixels != NULL && gTiles != NULL && gValid != NULL && readAll(fp, gExpectPixels, pixelBytes);
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Clear the stage, then paste gTiles[0..count) in the row-major block order
|
||||||
|
// jlTileBankLoadToSurface uses.
|
||||||
|
static void pasteTiles(uint16_t count) {
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
jlSurfaceClear(gScreen, FILL_COLOR);
|
||||||
|
for (i = 0u; i < count; i++) {
|
||||||
|
jlTilePaste(gScreen, (uint8_t)(i % TILE_BLOCKS_PER_ROW), (uint8_t)(i / TILE_BLOCKS_PER_ROW), &gTiles[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// fread until done: one fread need not fill a large request (the IIgs libc
|
||||||
|
// stops short), so a short read is only an error when nothing more comes.
|
||||||
|
static bool readAll(FILE *fp, uint8_t *dst, uint32_t bytes) {
|
||||||
|
uint32_t done;
|
||||||
|
size_t got;
|
||||||
|
|
||||||
|
done = 0u;
|
||||||
|
while (done < bytes) {
|
||||||
|
got = fread(dst + done, 1, (size_t)(bytes - done), fp);
|
||||||
|
if (got == 0u) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
done += (uint32_t)got;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint16_t readLe16(const uint8_t *bytes) {
|
||||||
|
return (uint16_t)(bytes[0] | (bytes[1] << BYTE_BITS));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Put refuse bank n's name in name (BANK_NAME_BYTES long); true when that
|
||||||
|
// bank was staged.
|
||||||
|
static bool refuseBankFound(char *name, uint16_t n) {
|
||||||
|
FILE *fp;
|
||||||
|
|
||||||
|
snprintf(name, BANK_NAME_BYTES, REFUSE_BANK_FORMAT, (unsigned)n);
|
||||||
|
fp = jlDataOpen(name, "rb");
|
||||||
|
if (fp == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Every staged refuse bank, both ways: jlTileBankLoad must load nothing and
|
||||||
|
// mark nothing valid, jlTileBankLoadToSurface must paste nothing. Returns how
|
||||||
|
// many were refused both ways; *banks gets how many were staged.
|
||||||
|
static uint16_t refuseCheck(uint16_t *banks) {
|
||||||
|
char name[BANK_NAME_BYTES];
|
||||||
|
uint32_t fillHash;
|
||||||
|
uint16_t refused;
|
||||||
|
uint16_t loaded;
|
||||||
|
uint16_t streamed;
|
||||||
|
|
||||||
|
jlSurfaceClear(gScreen, FILL_COLOR);
|
||||||
|
fillHash = jlSurfaceHash(gScreen);
|
||||||
|
refused = 0u;
|
||||||
|
*banks = 0u;
|
||||||
|
while (refuseBankFound(name, (uint16_t)(*banks + 1u))) {
|
||||||
|
(*banks)++;
|
||||||
|
jlSurfaceClear(gScreen, FILL_COLOR);
|
||||||
|
loaded = jlTileBankLoad(name, gTiles, gExpectCount, gValid, NULL);
|
||||||
|
streamed = jlTileBankLoadToSurface(name, gScreen, NULL);
|
||||||
|
if (loaded == 0u && !gValid[0] && streamed == 0u && jlSurfaceHash(gScreen) == fillHash) {
|
||||||
|
refused++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return refused;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The checks in the header comment, reported as one log line. The paste runs
|
||||||
|
// last so its tiles are what the verdict screen shows.
|
||||||
|
static bool runChecks(void) {
|
||||||
|
uint16_t banks;
|
||||||
|
uint16_t refused;
|
||||||
|
uint16_t streamed;
|
||||||
|
uint16_t pasted;
|
||||||
|
uint32_t streamHash;
|
||||||
|
uint32_t pasteHash;
|
||||||
|
bool paletteOk;
|
||||||
|
bool streamOk;
|
||||||
|
bool pasteOk;
|
||||||
|
bool pass;
|
||||||
|
|
||||||
|
refused = refuseCheck(&banks);
|
||||||
|
|
||||||
|
jlSurfaceClear(gScreen, FILL_COLOR);
|
||||||
|
streamed = jlTileBankLoadToSurface(OWN_BANK, gScreen, gBankPalette);
|
||||||
|
streamOk = streamed == gExpectCount && stageMatches(streamed);
|
||||||
|
streamHash = jlSurfaceHash(gScreen);
|
||||||
|
paletteOk = memcmp(gBankPalette, gExpectPalette, sizeof(gBankPalette)) == 0;
|
||||||
|
|
||||||
|
pasted = jlTileBankLoad(OWN_BANK, gTiles, gExpectCount, gValid, NULL);
|
||||||
|
pasteTiles(pasted);
|
||||||
|
pasteOk = pasted == gExpectCount && stageMatches(pasted);
|
||||||
|
pasteHash = jlSurfaceHash(gScreen);
|
||||||
|
|
||||||
|
pass = streamOk && pasteOk && paletteOk && streamHash == pasteHash && banks > 0u && refused == banks;
|
||||||
|
jlLogF("TILEBANK: %s tiles=%u streamed=%u pasted=%u palette=%s streamHash=%08lX pasteHash=%08lX refused=%u/%u", pass ? "PASS" : "FAIL", (unsigned)gExpectCount, (unsigned)streamed, (unsigned)pasted, paletteOk ? "ok" : "wrong", (unsigned long)streamHash, (unsigned long)pasteHash, (unsigned)refused, (unsigned)banks);
|
||||||
|
return pass;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Show the bank's tiles in its own colours, and the lamp.
|
||||||
|
static void showVerdict(bool pass) {
|
||||||
|
uint16_t colors[PALETTE_COLORS];
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
for (i = 0u; i < PALETTE_COLORS; i++) {
|
||||||
|
colors[i] = LAMP_DARK_RGB;
|
||||||
|
}
|
||||||
|
colors[LAMP_PASS_COLOR] = LAMP_PASS_RGB;
|
||||||
|
colors[LAMP_FAIL_COLOR] = LAMP_FAIL_RGB;
|
||||||
|
jlPaletteSet(gScreen, 0u, gBankPalette);
|
||||||
|
jlPaletteSet(gScreen, LAMP_PALETTE, colors);
|
||||||
|
jlScbSetRange(gScreen, LAMP_Y, LAMP_Y + LAMP_H - 1, LAMP_PALETTE);
|
||||||
|
jlFillRect(gScreen, LAMP_X, LAMP_Y, LAMP_W, LAMP_H, pass ? LAMP_PASS_COLOR : LAMP_FAIL_COLOR);
|
||||||
|
jlStagePresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Every pixel of the stage: blocks below tilesDrawn must show the PNG's tile
|
||||||
|
// of that number, every other block FILL_COLOR. tilesDrawn never exceeds
|
||||||
|
// gExpectCount (the callers check the count first).
|
||||||
|
static bool stageMatches(uint16_t tilesDrawn) {
|
||||||
|
int16_t x;
|
||||||
|
int16_t y;
|
||||||
|
uint16_t block;
|
||||||
|
uint8_t want;
|
||||||
|
|
||||||
|
for (y = 0; y < SURFACE_HEIGHT; y++) {
|
||||||
|
for (x = 0; x < SURFACE_WIDTH; x++) {
|
||||||
|
block = (uint16_t)((y / TILE_PIXELS_PER_SIDE) * TILE_BLOCKS_PER_ROW + x / TILE_PIXELS_PER_SIDE);
|
||||||
|
want = FILL_COLOR;
|
||||||
|
if (block < tilesDrawn) {
|
||||||
|
want = gExpectPixels[(uint32_t)block * TILE_PIXELS + (y % TILE_PIXELS_PER_SIDE) * TILE_PIXELS_PER_SIDE + x % TILE_PIXELS_PER_SIDE];
|
||||||
|
}
|
||||||
|
if (jlSamplePixel(gScreen, x, y) != want) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
jlConfigT config;
|
||||||
|
bool pass;
|
||||||
|
|
||||||
|
config.codegenBytes = CODEGEN_DEFAULT;
|
||||||
|
if (!jlInit(&config)) {
|
||||||
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
gScreen = jlStageGet();
|
||||||
|
|
||||||
|
pass = loadExpected();
|
||||||
|
if (pass) {
|
||||||
|
pass = runChecks();
|
||||||
|
} else {
|
||||||
|
jlLog("TILEBANK: FAIL DATA/" EXPECT_NAME " is missing or short, or memory ran out");
|
||||||
|
}
|
||||||
|
// Flushed BEFORE the verdict shows: a gate that sees the lamp can read
|
||||||
|
// the log at once (on the IIgs the flush is what writes the file).
|
||||||
|
jlLogFlush();
|
||||||
|
showVerdict(pass);
|
||||||
|
|
||||||
|
jlWaitForAnyKey();
|
||||||
|
jlFree(gValid);
|
||||||
|
jlFree(gTiles);
|
||||||
|
jlFree(gExpectPixels);
|
||||||
|
jlShutdown();
|
||||||
|
return pass ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
@ -260,7 +260,7 @@ static void timeOp(const char *name, OpFn op) {
|
||||||
iters = runForFrames(op, UBER_FRAMES, &actualFrames, &millisDelta);
|
iters = runForFrames(op, UBER_FRAMES, &actualFrames, &millisDelta);
|
||||||
|
|
||||||
if (iters == 0UL) {
|
if (iters == 0UL) {
|
||||||
jlLogF("UBER: %s: 0 iters (op too slow?)\n", name);
|
jlLogF("UBER: %s: 0 iters (op too slow?)", name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -275,9 +275,9 @@ static void timeOp(const char *name, OpFn op) {
|
||||||
opsPerSec = (iters * 1000UL) / (unsigned long)millisDelta;
|
opsPerSec = (iters * 1000UL) / (unsigned long)millisDelta;
|
||||||
opsPerSecFr = (iters * (unsigned long)jlFrameHz()) / (unsigned long)actualFrames;
|
opsPerSecFr = (iters * (unsigned long)jlFrameHz()) / (unsigned long)actualFrames;
|
||||||
hash = jlSurfaceHash(gStage);
|
hash = jlSurfaceHash(gStage);
|
||||||
jlLogF("UBER: %s: %lu iters / %u frames = %lu ops/sec | hash=%08lX\n",
|
jlLogF("UBER: %s: %lu iters / %u frames = %lu ops/sec | hash=%08lX",
|
||||||
name, iters, actualFrames, opsPerSec, (unsigned long)hash);
|
name, iters, actualFrames, opsPerSec, (unsigned long)hash);
|
||||||
jlLogF("UBER-CLK: %s: frame=%lu millis=%lu ops/sec (%lu ms)\n",
|
jlLogF("UBER-CLK: %s: frame=%lu millis=%lu ops/sec (%lu ms)",
|
||||||
name, opsPerSecFr, opsPerSec, (unsigned long)millisDelta);
|
name, opsPerSecFr, opsPerSec, (unsigned long)millisDelta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -697,7 +697,7 @@ static void PROBE_ATTR op_probeSpriteBandSum(void) {
|
||||||
jlSpriteDraw(gStage, gSprite, 150, 189);
|
jlSpriteDraw(gStage, gSprite, 150, 189);
|
||||||
jlSpriteDraw(gStage, gSprite, 71, 120);
|
jlSpriteDraw(gStage, gSprite, 71, 120);
|
||||||
sum = probeBandFold();
|
sum = probeBandFold();
|
||||||
jlLogF("UBER-PROBE: spriteBandSum=%lu\n", (unsigned long)sum);
|
jlLogF("UBER-PROBE: spriteBandSum=%lu", (unsigned long)sum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -722,7 +722,7 @@ static void PROBE_ATTR op_probeFillBandSum(void) {
|
||||||
jlDrawRect(gStage, 60, 60, 100, 100, 11);
|
jlDrawRect(gStage, 60, 60, 100, 100, 11);
|
||||||
jlFillCircle(gStage, 200, 120, 30, 12);
|
jlFillCircle(gStage, 200, 120, 30, 12);
|
||||||
sum = probeBandFold();
|
sum = probeBandFold();
|
||||||
jlLogF("UBER-PROBE: fillBandSum=%lu\n", (unsigned long)sum);
|
jlLogF("UBER-PROBE: fillBandSum=%lu", (unsigned long)sum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -872,7 +872,7 @@ static void PROBE_ATTR op_probeShrParity(void) {
|
||||||
stagePx += 160u;
|
stagePx += 160u;
|
||||||
shrPx += 160u;
|
shrPx += 160u;
|
||||||
}
|
}
|
||||||
jlLogF("UBER-PROBE: shrParity %s badRows=%u firstBad=%u\n", (badRows == 0u) ? "OK" : "FAIL", (unsigned int)badRows, (unsigned int)firstBad);
|
jlLogF("UBER-PROBE: shrParity %s badRows=%u firstBad=%u", (badRows == 0u) ? "OK" : "FAIL", (unsigned int)badRows, (unsigned int)firstBad);
|
||||||
|
|
||||||
// Tail phase 1: seed all 16 palettes + the SCB bands, then present.
|
// Tail phase 1: seed all 16 palettes + the SCB bands, then present.
|
||||||
// jlPaletteSet / jlScbSetRange set the dirty flags, so THIS present
|
// jlPaletteSet / jlScbSetRange set the dirty flags, so THIS present
|
||||||
|
|
@ -881,7 +881,7 @@ static void PROBE_ATTR op_probeShrParity(void) {
|
||||||
probeShrTailSeed();
|
probeShrTailSeed();
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
probeShrTailCheck(&badScbUp, &badPalUp, &firstPalUp);
|
probeShrTailCheck(&badScbUp, &badPalUp, &firstPalUp);
|
||||||
jlLogF("UBER-PROBE: shrTailUpload %s badScb=%u badPal=%u firstPal=%u\n", ((badScbUp | badPalUp) == 0u) ? "OK" : "FAIL", (unsigned int)badScbUp, (unsigned int)badPalUp, (unsigned int)firstPalUp);
|
jlLogF("UBER-PROBE: shrTailUpload %s badScb=%u badPal=%u firstPal=%u", ((badScbUp | badPalUp) == 0u) ? "OK" : "FAIL", (unsigned int)badScbUp, (unsigned int)badPalUp, (unsigned int)firstPalUp);
|
||||||
|
|
||||||
// Tail phase 2: pixel-only presents. The dirty flags were cleared by
|
// Tail phase 2: pixel-only presents. The dirty flags were cleared by
|
||||||
// the upload above and nothing below touches a palette or an SCB, so
|
// the upload above and nothing below touches a palette or an SCB, so
|
||||||
|
|
@ -898,7 +898,7 @@ static void PROBE_ATTR op_probeShrParity(void) {
|
||||||
jlFillRect(gStage, 0, 180, 320, 20, 6);
|
jlFillRect(gStage, 0, 180, 320, 20, 6);
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
probeShrTailCheck(&badScbSlam, &badPalSlam, &firstPalSlam);
|
probeShrTailCheck(&badScbSlam, &badPalSlam, &firstPalSlam);
|
||||||
jlLogF("UBER-PROBE: shrTailSlam %s badScb=%u badPal=%u firstPal=%u\n", ((badScbSlam | badPalSlam) == 0u) ? "OK" : "FAIL", (unsigned int)badScbSlam, (unsigned int)badPalSlam, (unsigned int)firstPalSlam);
|
jlLogF("UBER-PROBE: shrTailSlam %s badScb=%u badPal=%u firstPal=%u", ((badScbSlam | badPalSlam) == 0u) ? "OK" : "FAIL", (unsigned int)badScbSlam, (unsigned int)badPalSlam, (unsigned int)firstPalSlam);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -930,7 +930,7 @@ static void PROBE_ATTR op_probeDisplayCheck(void) {
|
||||||
afterSettle = jlpProbeDisplayMismatch(gStage);
|
afterSettle = jlpProbeDisplayMismatch(gStage);
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
afterIdle = jlpProbeDisplayMismatch(gStage);
|
afterIdle = jlpProbeDisplayMismatch(gStage);
|
||||||
jlLogF("UBER-PROBE: displayMismatch afterB=%lu afterSettle=%lu afterIdle=%lu\n", (unsigned long)afterB, (unsigned long)afterSettle, (unsigned long)afterIdle);
|
jlLogF("UBER-PROBE: displayMismatch afterB=%lu afterSettle=%lu afterIdle=%lu", (unsigned long)afterB, (unsigned long)afterSettle, (unsigned long)afterIdle);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -992,7 +992,7 @@ static void PROBE_ATTR op_probePresentPageSum(void) {
|
||||||
fails = (uint8_t)(fails + probePageSumCheck());
|
fails = (uint8_t)(fails + probePageSumCheck());
|
||||||
fails = (uint8_t)(fails + probePageSumCheck());
|
fails = (uint8_t)(fails + probePageSumCheck());
|
||||||
fails = (uint8_t)(fails + probePageSumCheck());
|
fails = (uint8_t)(fails + probePageSumCheck());
|
||||||
jlLogF("UBER-PROBE: presentPageSum %s (fails=%u)\n", fails == 0 ? "PASS" : "FAIL", (unsigned)fails);
|
jlLogF("UBER-PROBE: presentPageSum %s (fails=%u)", fails == 0 ? "PASS" : "FAIL", (unsigned)fails);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -1027,7 +1027,7 @@ static void PROBE_ATTR op_probeVgaCheck(void) {
|
||||||
afterSettle = dosProbeVgaMismatch(gStage);
|
afterSettle = dosProbeVgaMismatch(gStage);
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
afterIdle = dosProbeVgaMismatch(gStage);
|
afterIdle = dosProbeVgaMismatch(gStage);
|
||||||
jlLogF("UBER-PROBE: vgaMismatch afterB=%lu afterSettle=%lu afterIdle=%lu\n", (unsigned long)afterB, (unsigned long)afterSettle, (unsigned long)afterIdle);
|
jlLogF("UBER-PROBE: vgaMismatch afterB=%lu afterSettle=%lu afterIdle=%lu", (unsigned long)afterB, (unsigned long)afterSettle, (unsigned long)afterIdle);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -1077,7 +1077,7 @@ static void PROBE_ATTR probeOp(const char *name, void (*fn)(void)) {
|
||||||
bytes0 = gPresentCopiedBytes;
|
bytes0 = gPresentCopiedBytes;
|
||||||
rows0 = gPresentScannedRows;
|
rows0 = gPresentScannedRows;
|
||||||
timeOp(name, fn);
|
timeOp(name, fn);
|
||||||
jlLogF("UBER-PROBE: %s: copied=%lu scanned=%lu\n", name, (unsigned long)(gPresentCopiedBytes - bytes0), (unsigned long)(gPresentScannedRows - rows0));
|
jlLogF("UBER-PROBE: %s: copied=%lu scanned=%lu", name, (unsigned long)(gPresentCopiedBytes - bytes0), (unsigned long)(gPresentScannedRows - rows0));
|
||||||
// Steady-delta second pass (W2): one more call OUTSIDE the timed
|
// Steady-delta second pass (W2): one more call OUTSIDE the timed
|
||||||
// window. Its single-op delta is the exact steady-state gate value
|
// window. Its single-op delta is the exact steady-state gate value
|
||||||
// -- the timed average carries a first-iteration union carry-in on
|
// -- the timed average carries a first-iteration union carry-in on
|
||||||
|
|
@ -1086,7 +1086,7 @@ static void PROBE_ATTR probeOp(const char *name, void (*fn)(void)) {
|
||||||
bytes0 = gPresentCopiedBytes;
|
bytes0 = gPresentCopiedBytes;
|
||||||
rows0 = gPresentScannedRows;
|
rows0 = gPresentScannedRows;
|
||||||
fn();
|
fn();
|
||||||
jlLogF("UBER-PROBE: %s steady: copied=%lu scanned=%lu\n", name, (unsigned long)(gPresentCopiedBytes - bytes0), (unsigned long)(gPresentScannedRows - rows0));
|
jlLogF("UBER-PROBE: %s steady: copied=%lu scanned=%lu", name, (unsigned long)(gPresentCopiedBytes - bytes0), (unsigned long)(gPresentScannedRows - rows0));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -1160,7 +1160,7 @@ static void PROBE_ATTR op_probeCompactEpoch(void) {
|
||||||
victim = jlSpriteCreate(gBallTiles, BALL_TILES_X, BALL_TILES_Y);
|
victim = jlSpriteCreate(gBallTiles, BALL_TILES_X, BALL_TILES_Y);
|
||||||
survivor = jlSpriteCreate(gBallTiles, BALL_TILES_X, BALL_TILES_Y);
|
survivor = jlSpriteCreate(gBallTiles, BALL_TILES_X, BALL_TILES_Y);
|
||||||
if (victim == NULL || survivor == NULL || !jlSpriteCompile(victim) || !jlSpriteCompile(survivor)) {
|
if (victim == NULL || survivor == NULL || !jlSpriteCompile(victim) || !jlSpriteCompile(survivor)) {
|
||||||
jlLogF("UBER-PROBE: compactEpoch SKIPPED (create/compile failed)\n");
|
jlLogF("UBER-PROBE: compactEpoch SKIPPED (create/compile failed)");
|
||||||
if (victim != NULL) {
|
if (victim != NULL) {
|
||||||
jlSpriteDestroy(victim);
|
jlSpriteDestroy(victim);
|
||||||
}
|
}
|
||||||
|
|
@ -1177,7 +1177,7 @@ static void PROBE_ATTR op_probeCompactEpoch(void) {
|
||||||
jlSpriteCompact();
|
jlSpriteCompact();
|
||||||
jlSpriteDraw(gStage, survivor, 160, 80);
|
jlSpriteDraw(gStage, survivor, 160, 80);
|
||||||
jlSpriteDestroy(survivor);
|
jlSpriteDestroy(survivor);
|
||||||
jlLogF("UBER-PROBE: compactEpoch hash=%08lX bandSum=%lu\n", (unsigned long)jlSurfaceHash(gStage), (unsigned long)probeBandFold());
|
jlLogF("UBER-PROBE: compactEpoch hash=%08lX bandSum=%lu", (unsigned long)jlSurfaceHash(gStage), (unsigned long)probeBandFold());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -1370,7 +1370,7 @@ static void DRAWSHOWCASE_ATTR drawShowcase(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jlLogF("UBER: showcase cells: 0=pixels 1=lineH 2=lineV 3=diag 4=rect 5=fillRect 6=circle 7=fillCircle 8=tiles 9=sprite 10=flood 11=scene\n");
|
jlLogF("UBER: showcase cells: 0=pixels 1=lineH 2=lineV 3=diag 4=rect 5=fillRect 6=circle 7=fillCircle 8=tiles 9=sprite 10=flood 11=scene");
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
|
|
||||||
// Hold the showcase on screen, then auto-advance to the benchmark so
|
// Hold the showcase on screen, then auto-advance to the benchmark so
|
||||||
|
|
@ -1396,12 +1396,12 @@ static void DRAWSHOWCASE_ATTR drawShowcase(void) {
|
||||||
// PRNG golden sequence, arena bookkeeping).
|
// PRNG golden sequence, arena bookkeeping).
|
||||||
|
|
||||||
static void __attribute__((noinline)) chkHash(const char *name) {
|
static void __attribute__((noinline)) chkHash(const char *name) {
|
||||||
jlLogF("UBER-CHK: %s: hash=%08lX\n", name, (unsigned long)jlSurfaceHash(gStage));
|
jlLogF("UBER-CHK: %s: hash=%08lX", name, (unsigned long)jlSurfaceHash(gStage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void __attribute__((noinline)) chkPassFail(const char *name, bool pass) {
|
static void __attribute__((noinline)) chkPassFail(const char *name, bool pass) {
|
||||||
jlLogF("UBER-CHK: %s: %s\n", name, pass ? "PASS" : "FAIL");
|
jlLogF("UBER-CHK: %s: %s", name, pass ? "PASS" : "FAIL");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1647,7 +1647,7 @@ static void __attribute__((noinline)) checkTileMonoFloodExtra(void) {
|
||||||
|
|
||||||
static void __attribute__((noinline)) runCorrectnessChecks(void) {
|
static void __attribute__((noinline)) runCorrectnessChecks(void) {
|
||||||
uberMbPhase(UBER_PHASE_CHECKS);
|
uberMbPhase(UBER_PHASE_CHECKS);
|
||||||
jlLogF("UBER-CHK: ----- begin -----\n");
|
jlLogF("UBER-CHK: ----- begin -----");
|
||||||
jlSurfaceClear(gStage, 0);
|
jlSurfaceClear(gStage, 0);
|
||||||
checkEdgeDraws();
|
checkEdgeDraws();
|
||||||
checkClippedSprites();
|
checkClippedSprites();
|
||||||
|
|
@ -1660,7 +1660,7 @@ static void __attribute__((noinline)) runCorrectnessChecks(void) {
|
||||||
// jlShutdown -> jlInit -> stale-sprite-destroy (#34) needs a teardown
|
// jlShutdown -> jlInit -> stale-sprite-destroy (#34) needs a teardown
|
||||||
// of the whole library mid-run; it gets a dedicated micro-example in
|
// of the whole library mid-run; it gets a dedicated micro-example in
|
||||||
// Phase 1 rather than risking the benchmark's display state here.
|
// Phase 1 rather than risking the benchmark's display state here.
|
||||||
jlLogF("UBER-CHK: ----- end -----\n");
|
jlLogF("UBER-CHK: ----- end -----");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1668,7 +1668,7 @@ static void __attribute__((noinline)) runCorrectnessChecks(void) {
|
||||||
|
|
||||||
static void __attribute__((noinline)) runAllTests(void) {
|
static void __attribute__((noinline)) runAllTests(void) {
|
||||||
uberMbPhase(UBER_PHASE_TIMED_RUN);
|
uberMbPhase(UBER_PHASE_TIMED_RUN);
|
||||||
jlLogF("UBER: ----- begin -----\n");
|
jlLogF("UBER: ----- begin -----");
|
||||||
|
|
||||||
// Surface / palette / SCB.
|
// Surface / palette / SCB.
|
||||||
timeOp("jlSurfaceClear", op_surfaceClear);
|
timeOp("jlSurfaceClear", op_surfaceClear);
|
||||||
|
|
@ -1779,7 +1779,7 @@ static void __attribute__((noinline)) runAllTests(void) {
|
||||||
jlSurfaceDestroy(gCleanSurface);
|
jlSurfaceDestroy(gCleanSurface);
|
||||||
gCleanSurface = NULL;
|
gCleanSurface = NULL;
|
||||||
} else {
|
} else {
|
||||||
jlLogF("UBER: gameFrameClean composite: SKIPPED (surface alloc failed)\n");
|
jlLogF("UBER: gameFrameClean composite: SKIPPED (surface alloc failed)");
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef UBER_PROBE
|
#ifdef UBER_PROBE
|
||||||
|
|
@ -1803,19 +1803,19 @@ static void __attribute__((noinline)) runAllTests(void) {
|
||||||
probeBytes0 = gPresentCopiedBytes;
|
probeBytes0 = gPresentCopiedBytes;
|
||||||
probeRows0 = gPresentScannedRows;
|
probeRows0 = gPresentScannedRows;
|
||||||
timeOp("probe present96", op_probePresent96);
|
timeOp("probe present96", op_probePresent96);
|
||||||
jlLogF("UBER-PROBE: probe present96: copied=%lu scanned=%lu\n", (unsigned long)(gPresentCopiedBytes - probeBytes0), (unsigned long)(gPresentScannedRows - probeRows0));
|
jlLogF("UBER-PROBE: probe present96: copied=%lu scanned=%lu", (unsigned long)(gPresentCopiedBytes - probeBytes0), (unsigned long)(gPresentScannedRows - probeRows0));
|
||||||
probeBytes0 = gPresentCopiedBytes;
|
probeBytes0 = gPresentCopiedBytes;
|
||||||
probeRows0 = gPresentScannedRows;
|
probeRows0 = gPresentScannedRows;
|
||||||
op_probePresent96();
|
op_probePresent96();
|
||||||
jlLogF("UBER-PROBE: probe present96 steady: copied=%lu scanned=%lu\n", (unsigned long)(gPresentCopiedBytes - probeBytes0), (unsigned long)(gPresentScannedRows - probeRows0));
|
jlLogF("UBER-PROBE: probe present96 steady: copied=%lu scanned=%lu", (unsigned long)(gPresentCopiedBytes - probeBytes0), (unsigned long)(gPresentScannedRows - probeRows0));
|
||||||
probeBytes0 = gPresentCopiedBytes;
|
probeBytes0 = gPresentCopiedBytes;
|
||||||
probeRows0 = gPresentScannedRows;
|
probeRows0 = gPresentScannedRows;
|
||||||
timeOp("probe present3win", op_probePresent3win);
|
timeOp("probe present3win", op_probePresent3win);
|
||||||
jlLogF("UBER-PROBE: probe present3win: copied=%lu scanned=%lu\n", (unsigned long)(gPresentCopiedBytes - probeBytes0), (unsigned long)(gPresentScannedRows - probeRows0));
|
jlLogF("UBER-PROBE: probe present3win: copied=%lu scanned=%lu", (unsigned long)(gPresentCopiedBytes - probeBytes0), (unsigned long)(gPresentScannedRows - probeRows0));
|
||||||
probeBytes0 = gPresentCopiedBytes;
|
probeBytes0 = gPresentCopiedBytes;
|
||||||
probeRows0 = gPresentScannedRows;
|
probeRows0 = gPresentScannedRows;
|
||||||
op_probePresent3win();
|
op_probePresent3win();
|
||||||
jlLogF("UBER-PROBE: probe present3win steady: copied=%lu scanned=%lu\n", (unsigned long)(gPresentCopiedBytes - probeBytes0), (unsigned long)(gPresentScannedRows - probeRows0));
|
jlLogF("UBER-PROBE: probe present3win steady: copied=%lu scanned=%lu", (unsigned long)(gPresentCopiedBytes - probeBytes0), (unsigned long)(gPresentScannedRows - probeRows0));
|
||||||
timeOp("probe frameNoPresent", op_probeFrameNoPresent);
|
timeOp("probe frameNoPresent", op_probeFrameNoPresent);
|
||||||
// P7-1 IIgs trusted rows: same geometry (sprite at 40,30 into a
|
// P7-1 IIgs trusted rows: same geometry (sprite at 40,30 into a
|
||||||
// valid backup) as the golden jlSpriteSaveUnder / RestoreUnder /
|
// valid backup) as the golden jlSpriteSaveUnder / RestoreUnder /
|
||||||
|
|
@ -1836,11 +1836,11 @@ static void __attribute__((noinline)) runAllTests(void) {
|
||||||
probeBytes0 = gPresentCopiedBytes;
|
probeBytes0 = gPresentCopiedBytes;
|
||||||
probeRows0 = gPresentScannedRows;
|
probeRows0 = gPresentScannedRows;
|
||||||
timeOp("probe presentIdle", op_probePresentIdle);
|
timeOp("probe presentIdle", op_probePresentIdle);
|
||||||
jlLogF("UBER-PROBE: probe presentIdle: copied=%lu scanned=%lu\n", (unsigned long)(gPresentCopiedBytes - probeBytes0), (unsigned long)(gPresentScannedRows - probeRows0));
|
jlLogF("UBER-PROBE: probe presentIdle: copied=%lu scanned=%lu", (unsigned long)(gPresentCopiedBytes - probeBytes0), (unsigned long)(gPresentScannedRows - probeRows0));
|
||||||
probeBytes0 = gPresentCopiedBytes;
|
probeBytes0 = gPresentCopiedBytes;
|
||||||
probeRows0 = gPresentScannedRows;
|
probeRows0 = gPresentScannedRows;
|
||||||
op_probePresentIdle();
|
op_probePresentIdle();
|
||||||
jlLogF("UBER-PROBE: probe presentIdle steady: copied=%lu scanned=%lu\n", (unsigned long)(gPresentCopiedBytes - probeBytes0), (unsigned long)(gPresentScannedRows - probeRows0));
|
jlLogF("UBER-PROBE: probe presentIdle steady: copied=%lu scanned=%lu", (unsigned long)(gPresentCopiedBytes - probeBytes0), (unsigned long)(gPresentScannedRows - probeRows0));
|
||||||
// Hunt-3 rank 1: gameFrameClean component split on IIgs (the
|
// Hunt-3 rank 1: gameFrameClean component split on IIgs (the
|
||||||
// 16-21 ms unattributed residual). Same recipe as the golden
|
// 16-21 ms unattributed residual). Same recipe as the golden
|
||||||
// row (gCleanSurface was destroyed after it): re-create with
|
// row (gCleanSurface was destroyed after it): re-create with
|
||||||
|
|
@ -1930,7 +1930,7 @@ static void __attribute__((noinline)) runAllTests(void) {
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
jlLogF("UBER: ----- end -----\n");
|
jlLogF("UBER: ----- end -----");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1966,7 +1966,7 @@ static void __attribute__((noinline)) reportElapsed(uint16_t startFrame) {
|
||||||
endFrame = jlFrameCount();
|
endFrame = jlFrameCount();
|
||||||
elapsedFrames = (uint16_t)(endFrame - startFrame);
|
elapsedFrames = (uint16_t)(endFrame - startFrame);
|
||||||
elapsedMs = ((unsigned long)elapsedFrames * 1000UL) / (unsigned long)jlFrameHz();
|
elapsedMs = ((unsigned long)elapsedFrames * 1000UL) / (unsigned long)jlFrameHz();
|
||||||
jlLogF("UBER: total wall time: %lu ms (%u frames @ %u Hz)\n",
|
jlLogF("UBER: total wall time: %lu ms (%u frames @ %u Hz)",
|
||||||
elapsedMs, elapsedFrames, (unsigned)jlFrameHz());
|
elapsedMs, elapsedFrames, (unsigned)jlFrameHz());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1991,7 +1991,7 @@ static bool __attribute__((noinline)) setupSprite(void) {
|
||||||
while (jlFrameCount() == before) {
|
while (jlFrameCount() == before) {
|
||||||
/* wait for next VBL edge */
|
/* wait for next VBL edge */
|
||||||
}
|
}
|
||||||
jlLogF("UBER: jlSpriteCompile: 1 call in <= 1 frame\n");
|
jlLogF("UBER: jlSpriteCompile: 1 call in <= 1 frame");
|
||||||
gBackup.bytes = gBackupBytes;
|
gBackup.bytes = gBackupBytes;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -2008,7 +2008,6 @@ static bool __attribute__((noinline)) initJoeyLib(void) {
|
||||||
* thunk+table DRAWs ~12 KB; plus ~1-2 KB of save/restore classes).
|
* thunk+table DRAWs ~12 KB; plus ~1-2 KB of save/restore classes).
|
||||||
* UL on the multiply because a 16-bit int overflows on 32 * 1024. */
|
* UL on the multiply because a 16-bit int overflows on 32 * 1024. */
|
||||||
config.codegenBytes = 32UL * 1024;
|
config.codegenBytes = 32UL * 1024;
|
||||||
config.audioBytes = 64UL * 1024;
|
|
||||||
return jlInit(&config);
|
return jlInit(&config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2059,9 +2058,9 @@ int main(void) {
|
||||||
// paths on IIgs).
|
// paths on IIgs).
|
||||||
uberMbPhase(UBER_PHASE_AUDIO_INIT);
|
uberMbPhase(UBER_PHASE_AUDIO_INIT);
|
||||||
if (jlAudioInit()) {
|
if (jlAudioInit()) {
|
||||||
jlLogF("UBER: audioInit OK\n");
|
jlLogF("UBER: audioInit OK");
|
||||||
} else {
|
} else {
|
||||||
jlLogF("UBER: audioInit failed (skipping audio)\n");
|
jlLogF("UBER: audioInit failed (skipping audio)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visual showcase: render one of each primitive into its own grid
|
// Visual showcase: render one of each primitive into its own grid
|
||||||
|
|
@ -2081,7 +2080,7 @@ int main(void) {
|
||||||
jlSurfaceClear(gStage, 2);
|
jlSurfaceClear(gStage, 2);
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
|
|
||||||
jlLogF("UBER: press any key to exit\n");
|
jlLogF("UBER: press any key to exit");
|
||||||
// Flush the log to disk BEFORE the blocking key wait, so an automated
|
// Flush the log to disk BEFORE the blocking key wait, so an automated
|
||||||
// (headless) run that kills the process at this prompt still captures
|
// (headless) run that kills the process at this prompt still captures
|
||||||
// the results -- joeyLog otherwise only flushes at the atexit fclose.
|
// the results -- joeyLog otherwise only flushes at the atexit fclose.
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ typedef struct {
|
||||||
// a 64 KB bank boundary), so values above 65536 are clamped to
|
// a 64 KB bank boundary), so values above 65536 are clamped to
|
||||||
// 65536; even a clamped request can fail if no free bank exists.
|
// 65536; even a clamped request can fail if no free bank exists.
|
||||||
uint32_t codegenBytes;
|
uint32_t codegenBytes;
|
||||||
uint32_t audioBytes; // reserved; not yet consulted by any audio engine
|
|
||||||
} jlConfigT;
|
} jlConfigT;
|
||||||
|
|
||||||
// Initialize the library. Returns true on success.
|
// Initialize the library. Returns true on success.
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@
|
||||||
// DOS -- 8250/16550 UART on a COM port; interrupt-driven RX ring.
|
// DOS -- 8250/16550 UART on a COM port; interrupt-driven RX ring.
|
||||||
// Amiga -- serial.device (async exec IO); the OS buffers RX.
|
// Amiga -- serial.device (async exec IO); the OS buffers RX.
|
||||||
// Atari ST -- BIOS Aux (Modem port) via XBIOS Rsconf; TOS buffers RX.
|
// Atari ST -- BIOS Aux (Modem port) via XBIOS Rsconf; TOS buffers RX.
|
||||||
// X68000 -- SCC channel A: IOCS for transmit and line setup (75-9600
|
// X68000 -- SCC channel A: Human68k's RSDRV.SYS for transmit and line
|
||||||
// bps, no RTS/CTS; 75-38400 bps and RTS/CTS with Human68k's
|
// setup (75-38400 bps, RTS/CTS), REQUIRED -- the open fails
|
||||||
// RSDRV.SYS loaded); its own RX interrupt fills the ring.
|
// without it; its own RX interrupt fills the ring.
|
||||||
//
|
//
|
||||||
// Model: non-blocking + buffered. Received bytes accumulate in a background
|
// Model: non-blocking + buffered. Received bytes accumulate in a background
|
||||||
// ring (a hardware IRQ on DOS, the X68000 and the IIgs, the OS on Amiga/ST);
|
// ring (a hardware IRQ on DOS, the X68000 and the IIgs, the OS on Amiga/ST);
|
||||||
|
|
@ -87,7 +87,8 @@ typedef struct {
|
||||||
// Open + configure the port. Only one port is open at a time; a second
|
// Open + configure the port. Only one port is open at a time; a second
|
||||||
// jlSerialOpen without a jlSerialClose fails. Returns false (non-fatal) if the
|
// jlSerialOpen without a jlSerialClose fails. Returns false (non-fatal) if the
|
||||||
// port cannot be opened or the platform has no serial support -- every other
|
// port cannot be opened or the platform has no serial support -- every other
|
||||||
// call then no-ops.
|
// call then no-ops. On the X68000 it also fails when RSDRV.SYS is not loaded,
|
||||||
|
// and jlLastError() says so.
|
||||||
bool jlSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config);
|
bool jlSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config);
|
||||||
|
|
||||||
// Close the open port and release its resources. Safe to call when not open.
|
// Close the open port and release its resources. Safe to call when not open.
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,11 @@
|
||||||
//
|
//
|
||||||
// Performance contract: jlSpriteDraw should be at least as fast as the
|
// Performance contract: jlSpriteDraw should be at least as fast as the
|
||||||
// IIgs reference (the DESIGN.md spec calls for runtime-compiled draw
|
// IIgs reference (the DESIGN.md spec calls for runtime-compiled draw
|
||||||
// code per CPU). Every port has a runtime emitter (DOS x86, Amiga/ST
|
// code per CPU). Every port has a runtime emitter (DOS x86, Amiga/ST/
|
||||||
// 68k planar, IIgs 65816); jlSpriteCompile compiles the shift/op
|
// X68000 68k planar, IIgs 65816); jlSpriteCompile compiles the shift/op
|
||||||
// variants into the codegen arena and the draw/save/restore calls
|
// variants into the codegen arena and the draw/save/restore calls
|
||||||
// dispatch to the compiled routines, falling back to the interpreter
|
// dispatch to the compiled routines, falling back to the interpreter
|
||||||
// for clipped draws or uncompiled variants. On the IIgs the compiled
|
// for clipped draws or uncompiled variants.
|
||||||
// path is temporarily toolchain-gated off (claudeDocs/PERF-AUDIT.md #77).
|
|
||||||
// jlSpritePrewarm compiles ahead of the first draw so the emit cost
|
// jlSpritePrewarm compiles ahead of the first draw so the emit cost
|
||||||
// does not land mid-frame.
|
// does not land mid-frame.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,9 +72,10 @@ void jlTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex);
|
||||||
// tools/assetbake/assetbake.py. The file's per-target byte is checked
|
// tools/assetbake/assetbake.py. The file's per-target byte is checked
|
||||||
// against the build platform and the loader refuses mismatches.
|
// against the build platform and the loader refuses mismatches.
|
||||||
//
|
//
|
||||||
// outValid (optional, NULL OK) is set to true for each loaded slot
|
// outValid (optional, NULL OK) gets one flag per slot in [0, maxTiles):
|
||||||
// so the caller can distinguish loaded tiles from any leftover slots
|
// true for each loaded tile, false for every other slot (all of them
|
||||||
// in a fixed-size jlTileT[] array.
|
// when the file is refused), so the caller can distinguish loaded
|
||||||
|
// tiles from any leftover slots in a fixed-size jlTileT[] array.
|
||||||
//
|
//
|
||||||
// outPalette (optional, NULL OK) receives the 16-entry $0RGB LE16
|
// outPalette (optional, NULL OK) receives the 16-entry $0RGB LE16
|
||||||
// palette if the file embeds one.
|
// palette if the file embeds one.
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
# Host tools every JoeyLib setup needs: a native compiler for the in-repo
|
# Host tools every JoeyLib setup needs: a native compiler for the in-repo
|
||||||
# tools, GNU make, git with LFS (the sample assets live in LFS), the
|
# tools, GNU make, git with LFS (the sample assets live in LFS), the
|
||||||
# archive tools the other components unpack with, and Python 3 with
|
# archive tools the other components unpack with, Python 3 with Pillow
|
||||||
# Pillow for the asset converters.
|
# for the asset converters, and socat for joey-run's serial bridging.
|
||||||
|
|
||||||
compDescription="host tools: make, host compiler, git-lfs, python3 + Pillow, archivers"
|
compDescription="host tools: make, host compiler, git-lfs, python3 + Pillow, archivers, socat"
|
||||||
# Not in any port group: every other component requires it.
|
# Not in any port group: every other component requires it.
|
||||||
compGroups="host"
|
compGroups="host"
|
||||||
compRequires=""
|
compRequires=""
|
||||||
compHostDeps="build-tools make git git-lfs curl tar xz bzip2 unzip lhasa python3 python3-pil procps diffutils"
|
compHostDeps="build-tools make git git-lfs curl tar xz bzip2 unzip lhasa python3 python3-pil procps diffutils socat"
|
||||||
compPin="1"
|
compPin="1"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ python3 python3 python3
|
||||||
python3-pil python3-pil python3-pillow
|
python3-pil python3-pil python3-pillow
|
||||||
php-cli php-cli php-cli
|
php-cli php-cli php-cli
|
||||||
patch patch patch
|
patch patch patch
|
||||||
|
# joey-run --serial bridges some emulators' serial ports to TCP with socat.
|
||||||
|
socat socat socat
|
||||||
|
|
||||||
# ---- runtime libraries the prebuilt toolchains link against
|
# ---- runtime libraries the prebuilt toolchains link against
|
||||||
libfl libfl2 libfl2
|
libfl libfl2 libfl2
|
||||||
|
|
|
||||||
200
joey-run
200
joey-run
|
|
@ -27,11 +27,32 @@
|
||||||
# --machine rom01 IIgs: the ROM 01 machine instead of ROM 03
|
# --machine rom01 IIgs: the ROM 01 machine instead of ROM 03
|
||||||
# --ram SIZE IIgs: memory size for MAME (default 4M; AGI wants 8M)
|
# --ram SIZE IIgs: memory size for MAME (default 4M; AGI wants 8M)
|
||||||
# --debug IIgs and X68000: start MAME's debugger
|
# --debug IIgs and X68000: start MAME's debugger
|
||||||
|
# --serial HOST:PORT connect the machine's serial port to the TCP server
|
||||||
|
# at HOST:PORT: a BBS, a program of your own, another
|
||||||
|
# run's --serial-listen, or `nc -l PORT`
|
||||||
|
# --serial-listen PORT
|
||||||
|
# the serial port waits for a TCP connection on PORT
|
||||||
|
# (on every network interface) instead
|
||||||
|
# --serial-baud N IIgs and X68000: the rate MAME's end of the wire runs
|
||||||
|
# at, which must match the program's (default 9600,
|
||||||
|
# always 8N1); the other emulators pass bytes at any rate
|
||||||
# -h, --help this text
|
# -h, --help this text
|
||||||
#
|
#
|
||||||
# Arguments after -- are passed to the program (DOS only: the other
|
# Arguments after -- are passed to the program (DOS only: the other
|
||||||
# machines' autostart cannot pass arguments).
|
# machines' autostart cannot pass arguments).
|
||||||
#
|
#
|
||||||
|
# The serial port is the machine's usual one: the IIgs modem port (the
|
||||||
|
# printer port and a Super Serial Card are not connected), the X68000's
|
||||||
|
# RS-232C port, the ST's Modem port, the Amiga's serial port (serial.device
|
||||||
|
# unit 0) and DOS's COM1. The link is raw TCP, without telnet negotiation.
|
||||||
|
# socat relays it where the emulator cannot make it itself: the ST, the
|
||||||
|
# Amiga with --serial, and the IIgs and X68000 with --serial-listen. When
|
||||||
|
# --serial's server is not there, joey-run stops, except on the IIgs and
|
||||||
|
# X68000, where MAME prints "Unable to load image" and runs with the port
|
||||||
|
# unplugged. Listening, what the program sends before the connection
|
||||||
|
# arrives waits for it, except on DOS, which drops it; the Amiga holds the
|
||||||
|
# whole machine until the connection arrives.
|
||||||
|
#
|
||||||
# The emulators and firmware come from ./install.sh
|
# The emulators and firmware come from ./install.sh
|
||||||
# (install emulator:PORT). Interactive runs use the program's own folder
|
# (install emulator:PORT). Interactive runs use the program's own folder
|
||||||
# or disk image, so saves and logs persist; --headless and --data runs
|
# or disk image, so saves and logs persist; --headless and --data runs
|
||||||
|
|
@ -56,6 +77,20 @@ progArgs=()
|
||||||
emuPid=""
|
emuPid=""
|
||||||
xvfbPid=""
|
xvfbPid=""
|
||||||
work=""
|
work=""
|
||||||
|
serialMode="" # connect (--serial), listen (--serial-listen) or off
|
||||||
|
serialHost=""
|
||||||
|
serialPort=""
|
||||||
|
serialBaud=9600 # MAME's null modem's own default rate
|
||||||
|
serialArgs=() # MAME's options for the link (mameSerial)
|
||||||
|
serialPty="" # the pty socat hands Hatari and FS-UAE
|
||||||
|
relayPid=""
|
||||||
|
tcpPortMax=65535
|
||||||
|
# How long socat keeps trying to reach --serial's server before the run
|
||||||
|
# stops; an unreachable host otherwise hangs for the kernel's SYN timeout.
|
||||||
|
serialConnectSeconds=10
|
||||||
|
# DOSBox logs whether COM1 reached --serial's server as it starts.
|
||||||
|
dosSerialLogSeconds=5
|
||||||
|
pollSeconds=0.1
|
||||||
|
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
|
|
@ -71,6 +106,7 @@ fail() {
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
joeyStop "$emuPid"
|
joeyStop "$emuPid"
|
||||||
|
joeyStop "$relayPid"
|
||||||
if [ -n "$xvfbPid" ]; then
|
if [ -n "$xvfbPid" ]; then
|
||||||
kill "$xvfbPid" 2>/dev/null || true
|
kill "$xvfbPid" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
@ -234,6 +270,107 @@ runMame() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ---- the serial link ----------------------------------------------------------
|
||||||
|
|
||||||
|
# portFree PORT -- nothing on this machine listens on TCP PORT yet. Checked
|
||||||
|
# up front because an emulator that cannot take the port only says so in a
|
||||||
|
# log nobody reads, and runs with the serial port unplugged.
|
||||||
|
portFree() {
|
||||||
|
python3 - "$1" <<'PY'
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
|
||||||
|
probe = socket.socket()
|
||||||
|
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
try:
|
||||||
|
probe.bind(("", int(sys.argv[1])))
|
||||||
|
except OSError:
|
||||||
|
sys.exit(1)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# serialNote NAME -- tell the user where the serial port (NAME) is wired.
|
||||||
|
serialNote() {
|
||||||
|
case "$serialMode" in
|
||||||
|
connect) echo "joey-run: $1 connects to $serialHost:$serialPort." ;;
|
||||||
|
listen) echo "joey-run: $1 waits for a connection on TCP port $serialPort." ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# relaySerial NEAR PATH -- run socat between the emulator's end of the link
|
||||||
|
# (NEAR, a pty or unix socket that appears at PATH) and the network, and
|
||||||
|
# wait for PATH. socat opens its first address before its second, so the
|
||||||
|
# order matters. Connecting, the TCP connection goes first: an unreachable
|
||||||
|
# server stops the run before the machine boots. Listening, NEAR goes
|
||||||
|
# first: the emulator can open it at once, and what the program sends
|
||||||
|
# waits in the kernel until a client connects.
|
||||||
|
relaySerial() {
|
||||||
|
local near="$1"
|
||||||
|
local path="$2"
|
||||||
|
local deadline
|
||||||
|
command -v socat >/dev/null 2>&1 ||
|
||||||
|
fail "the serial link on this machine goes through socat, which is not installed (package socat: apt install socat, dnf install socat)"
|
||||||
|
if [ "$serialMode" = "connect" ]; then
|
||||||
|
(cd "$work" && exec socat "tcp:$serialHost:$serialPort,connect-timeout=$serialConnectSeconds" "$near") 2>"$work/socat.log" &
|
||||||
|
else
|
||||||
|
(cd "$work" && exec socat "$near" "tcp-listen:$serialPort,reuseaddr") 2>"$work/socat.log" &
|
||||||
|
fi
|
||||||
|
relayPid=$!
|
||||||
|
deadline=$((SECONDS + serialConnectSeconds + 1))
|
||||||
|
while [ ! -e "$path" ]; do
|
||||||
|
if ! joeyAlive "$relayPid" || [ "$SECONDS" -ge "$deadline" ]; then
|
||||||
|
cat "$work/socat.log" >&2
|
||||||
|
if [ "$serialMode" = "connect" ]; then
|
||||||
|
fail "cannot connect to $serialHost:$serialPort"
|
||||||
|
fi
|
||||||
|
fail "socat cannot listen on TCP port $serialPort"
|
||||||
|
fi
|
||||||
|
sleep "$pollSeconds"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# mameSerial DRIVER SLOT -- set serialArgs to the MAME options that plug a
|
||||||
|
# null modem into SLOT and wire it up. MAME's bitbanger connects out by
|
||||||
|
# itself; to listen, socat takes the TCP port and MAME connects to socat
|
||||||
|
# through a unix socket. The null modem clocks the wire itself, at
|
||||||
|
# --serial-baud, which cfg/DRIVER.cfg sets.
|
||||||
|
mameSerial() {
|
||||||
|
local driver="$1"
|
||||||
|
local slot="$2"
|
||||||
|
local bitb="socket.$serialHost:$serialPort"
|
||||||
|
[ -n "$serialMode" ] || return 0
|
||||||
|
mkdir -p "$work/cfg"
|
||||||
|
python3 "$repo/scripts/lib/mameNullModemCfg.py" "$driver" ":$slot:null_modem" "$serialBaud" \
|
||||||
|
> "$work/cfg/$driver.cfg" || fail "--serial-baud $serialBaud is not a rate MAME's null modem has"
|
||||||
|
if [ "$serialMode" = "listen" ]; then
|
||||||
|
relaySerial "unix-listen:serial.sock" "$work/serial.sock"
|
||||||
|
bitb="domain.serial.sock"
|
||||||
|
fi
|
||||||
|
serialArgs=(-"$slot" null_modem -bitb "$bitb")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# dosSerialCheck -- DOSBox tries --serial's server as it starts and, when
|
||||||
|
# that fails, only says so in its log and runs with COM1 unplugged; stop
|
||||||
|
# with the reason instead.
|
||||||
|
dosSerialCheck() {
|
||||||
|
local deadline=$((SECONDS + dosSerialLogSeconds))
|
||||||
|
local line
|
||||||
|
[ "$serialMode" = "connect" ] || return 0
|
||||||
|
while [ "$SECONDS" -lt "$deadline" ]; do
|
||||||
|
line="$(grep -m 1 'SERIAL: Port 1 ' "$work/dosbox.log" 2>/dev/null)" || true
|
||||||
|
case "$line" in
|
||||||
|
*"connection failed"*) fail "DOSBox cannot connect COM1 to $serialHost:$serialPort" ;;
|
||||||
|
?*) return 0 ;;
|
||||||
|
esac
|
||||||
|
sleep "$pollSeconds"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# ---- one function per machine -------------------------------------------------
|
# ---- one function per machine -------------------------------------------------
|
||||||
|
|
||||||
runIigs() {
|
runIigs() {
|
||||||
|
|
@ -287,8 +424,10 @@ runIigs() {
|
||||||
media=(-flop3 "$work/boot.po" -flop4 "$work/data.2mg")
|
media=(-flop3 "$work/boot.po" -flop4 "$work/data.2mg")
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
mameSerial "$machine" modem
|
||||||
echo "joey-run: booting the IIgs ($machine); the program comes up in about a minute. Close MAME to stop."
|
echo "joey-run: booting the IIgs ($machine); the program comes up in about a minute. Close MAME to stop."
|
||||||
runMame "$machine" -ramsize "$ramSize" -rompath "$IIGS_ROMPATH" "${media[@]}"
|
serialNote "the modem port"
|
||||||
|
runMame "$machine" -ramsize "$ramSize" -rompath "$IIGS_ROMPATH" "${media[@]}" "${serialArgs[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -327,7 +466,24 @@ runAmiga() {
|
||||||
args+=(--joystick_port_0=none --joystick_port_1=none)
|
args+=(--joystick_port_0=none --joystick_port_1=none)
|
||||||
export ALSOFT_DRIVERS=null
|
export ALSOFT_DRIVERS=null
|
||||||
fi
|
fi
|
||||||
|
case "$serialMode" in
|
||||||
|
connect)
|
||||||
|
# FS-UAE's tcp:// serial port only ever listens, so socat makes
|
||||||
|
# the connection and hands FS-UAE a pty.
|
||||||
|
relaySerial "pty,rawer,link=$serialPty" "$serialPty"
|
||||||
|
args+=(--amiga_enable_serial_port=1 --serial_port="$serialPty")
|
||||||
|
;;
|
||||||
|
listen)
|
||||||
|
# /wait holds the machine until the client connects, so nothing
|
||||||
|
# the program sends goes to nobody.
|
||||||
|
args+=(--amiga_enable_serial_port=1 --serial_port="tcp://0.0.0.0:$serialPort/wait")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
echo "joey-run: booting the Amiga; $name starts from the JOEYLIB hard drive. Close FS-UAE to stop."
|
echo "joey-run: booting the Amiga; $name starts from the JOEYLIB hard drive. Close FS-UAE to stop."
|
||||||
|
serialNote "the serial port"
|
||||||
|
if [ "$serialMode" = "listen" ]; then
|
||||||
|
echo "joey-run: the Amiga does not start until that connection arrives."
|
||||||
|
fi
|
||||||
"$FSUAE" "${args[@]}" >"$work/fs-uae.log" 2>&1 &
|
"$FSUAE" "${args[@]}" >"$work/fs-uae.log" 2>&1 &
|
||||||
emuPid=$!
|
emuPid=$!
|
||||||
waitOrStop
|
waitOrStop
|
||||||
|
|
@ -351,7 +507,14 @@ runAtarist() {
|
||||||
startDisplay
|
startDisplay
|
||||||
args+=(--sound off)
|
args+=(--sound off)
|
||||||
fi
|
fi
|
||||||
|
if [ -n "$serialMode" ]; then
|
||||||
|
# Hatari's serial port can only be a file or a tty, so socat
|
||||||
|
# stands between a pty and the network.
|
||||||
|
relaySerial "pty,rawer,link=$serialPty" "$serialPty"
|
||||||
|
args+=(--rs232-in "$serialPty" --rs232-out "$serialPty")
|
||||||
|
fi
|
||||||
echo "joey-run: booting the Atari ST (EmuTOS); $(basename "$program") starts from drive C. Close Hatari to stop."
|
echo "joey-run: booting the Atari ST (EmuTOS); $(basename "$program") starts from drive C. Close Hatari to stop."
|
||||||
|
serialNote "the Modem port"
|
||||||
"$HATARI" "${args[@]}" >"$work/hatari.log" 2>&1 &
|
"$HATARI" "${args[@]}" >"$work/hatari.log" 2>&1 &
|
||||||
emuPid=$!
|
emuPid=$!
|
||||||
waitOrStop
|
waitOrStop
|
||||||
|
|
@ -374,6 +537,12 @@ runDos() {
|
||||||
fi
|
fi
|
||||||
args=("${JOEY_DOSBOX_ISOLATE[@]}" --conf "$repo/scripts/dosbox-386sx16.conf"
|
args=("${JOEY_DOSBOX_ISOLATE[@]}" --conf "$repo/scripts/dosbox-386sx16.conf"
|
||||||
--set "mouse_capture=seamless" -c "MOUNT C \"$dir\"" -c "C:" -c "$command")
|
--set "mouse_capture=seamless" -c "MOUNT C \"$dir\"" -c "C:" -c "$command")
|
||||||
|
# transparent:1 -- data bytes only: DOSBox's in-band escapes for the
|
||||||
|
# control lines would reach a plain TCP peer as data.
|
||||||
|
case "$serialMode" in
|
||||||
|
connect) args+=(--set "serial1=nullmodem server:$serialHost port:$serialPort transparent:1") ;;
|
||||||
|
listen) args+=(--set "serial1=nullmodem port:$serialPort transparent:1") ;;
|
||||||
|
esac
|
||||||
if [ "$headless" = "1" ]; then
|
if [ "$headless" = "1" ]; then
|
||||||
startDisplay
|
startDisplay
|
||||||
else
|
else
|
||||||
|
|
@ -382,8 +551,10 @@ runDos() {
|
||||||
fi
|
fi
|
||||||
args+=(--exit)
|
args+=(--exit)
|
||||||
echo "joey-run: booting DOS (386SX-16); $command runs from drive C. Close DOSBox to stop."
|
echo "joey-run: booting DOS (386SX-16); $command runs from drive C. Close DOSBox to stop."
|
||||||
|
serialNote "COM1"
|
||||||
XDG_CONFIG_HOME="$work/xdg" "$DOSBOX" "${args[@]}" >"$work/dosbox.log" 2>&1 &
|
XDG_CONFIG_HOME="$work/xdg" "$DOSBOX" "${args[@]}" >"$work/dosbox.log" 2>&1 &
|
||||||
emuPid=$!
|
emuPid=$!
|
||||||
|
dosSerialCheck
|
||||||
waitOrStop
|
waitOrStop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -414,8 +585,10 @@ runX68000() {
|
||||||
if [ -d "$stage/DATA" ]; then
|
if [ -d "$stage/DATA" ]; then
|
||||||
x68kAddTree "$work/data.xdf" "$stage/DATA" DATA || fail "the DATA files do not fit on the 1232 KB data disk"
|
x68kAddTree "$work/data.xdf" "$stage/DATA" DATA || fail "the DATA files do not fit on the 1232 KB data disk"
|
||||||
fi
|
fi
|
||||||
|
mameSerial x68000 rs232c
|
||||||
echo "joey-run: booting the X68000 (Human68k 3.02); $name runs from drive B. Close MAME to stop."
|
echo "joey-run: booting the X68000 (Human68k 3.02); $name runs from drive B. Close MAME to stop."
|
||||||
runMame x68000 -bios ipl10 -rompath "$X68K_DIR/roms" -flop1 "$work/boot.xdf" -flop2 "$work/data.xdf"
|
serialNote "the RS-232C port"
|
||||||
|
runMame x68000 -bios ipl10 -rompath "$X68K_DIR/roms" -flop1 "$work/boot.xdf" -flop2 "$work/data.xdf" "${serialArgs[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -438,6 +611,22 @@ while [ $# -gt 0 ]; do
|
||||||
;;
|
;;
|
||||||
--ram) ramSize="${2:?--ram needs a size such as 4M}"; shift ;;
|
--ram) ramSize="${2:?--ram needs a size such as 4M}"; shift ;;
|
||||||
--debug) debug=1 ;;
|
--debug) debug=1 ;;
|
||||||
|
--serial)
|
||||||
|
[ -z "$serialMode" ] || fail "one --serial or --serial-listen per run"
|
||||||
|
[[ "${2:-}" =~ ^([^:]+):([0-9]+)$ ]] || fail "--serial takes HOST:PORT (a host name or IPv4 address)"
|
||||||
|
serialMode=connect
|
||||||
|
serialHost="${BASH_REMATCH[1]}"
|
||||||
|
serialPort="${BASH_REMATCH[2]}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--serial-listen)
|
||||||
|
[ -z "$serialMode" ] || fail "one --serial or --serial-listen per run"
|
||||||
|
[[ "${2:-}" =~ ^[0-9]+$ ]] || fail "--serial-listen takes a port number"
|
||||||
|
serialMode=listen
|
||||||
|
serialPort="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--serial-baud) serialBaud="${2:?--serial-baud needs a rate such as 2400}"; shift ;;
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
--) shift; progArgs=("$@"); break ;;
|
--) shift; progArgs=("$@"); break ;;
|
||||||
-*) fail "unknown option $1 (see: joey-run --help)" ;;
|
-*) fail "unknown option $1 (see: joey-run --help)" ;;
|
||||||
|
|
@ -462,6 +651,12 @@ if [ -n "$dataDir" ]; then
|
||||||
[ -d "$dataDir" ] || fail "--data: no such folder $dataDir"
|
[ -d "$dataDir" ] || fail "--data: no such folder $dataDir"
|
||||||
dataDir="$(cd "$dataDir" && pwd)"
|
dataDir="$(cd "$dataDir" && pwd)"
|
||||||
fi
|
fi
|
||||||
|
if [ -n "$serialMode" ]; then
|
||||||
|
[ "$serialPort" -ge 1 ] && [ "$serialPort" -le "$tcpPortMax" ] || fail "TCP port $serialPort is not 1 to $tcpPortMax"
|
||||||
|
fi
|
||||||
|
if [ "$serialMode" = "listen" ]; then
|
||||||
|
portFree "$serialPort" || fail "TCP port $serialPort is already in use"
|
||||||
|
fi
|
||||||
|
|
||||||
# Resolve PROGRAM to a file and its machine.
|
# Resolve PROGRAM to a file and its machine.
|
||||||
if [ -d "$program" ]; then
|
if [ -d "$program" ]; then
|
||||||
|
|
@ -482,6 +677,7 @@ if [ ${#progArgs[@]} -gt 0 ] && [ "$detected" != "dos" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
work="$(mktemp -d -t joey-run.XXXXXX)"
|
work="$(mktemp -d -t joey-run.XXXXXX)"
|
||||||
|
serialPty="$work/serial.pty"
|
||||||
trap cleanup EXIT INT TERM
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
case "$detected" in
|
case "$detected" in
|
||||||
|
|
|
||||||
121
make/amiga.mk
121
make/amiga.mk
|
|
@ -17,7 +17,7 @@ BINDIR := $(BUILD)/bin
|
||||||
# independently. -I on $(SRC_DIR)/amiga lets ptplayer.h resolve
|
# independently. -I on $(SRC_DIR)/amiga lets ptplayer.h resolve
|
||||||
# <SDI_compiler.h> from the machine-local shim alongside our code.
|
# <SDI_compiler.h> from the machine-local shim alongside our code.
|
||||||
PTPLAYER_DIR := $(AMIGA_PTPLAYER)
|
PTPLAYER_DIR := $(AMIGA_PTPLAYER)
|
||||||
CFLAGS := $(COMMON_CFLAGS) -m68000 -fomit-frame-pointer -noixemul -D__OSCOMPAT -I$(SRC_DIR)/amiga -I$(SRC_68K) -I$(PTPLAYER_DIR) -MMD -MP $(CFLAGS_EXTRA)
|
CFLAGS := $(COMMON_CFLAGS) -m68000 -fomit-frame-pointer -noixemul -D__OSCOMPAT -I$(SRC_DIR)/amiga -I$(SRC_68K) -I$(PTPLAYER_DIR) -MMD -MP
|
||||||
# OSCOMPAT=1 selects PTPlayer's audio.device-friendly variant (uses
|
# OSCOMPAT=1 selects PTPlayer's audio.device-friendly variant (uses
|
||||||
# CIA-B + audio.device interrupts via the OS rather than taking over
|
# CIA-B + audio.device interrupts via the OS rather than taking over
|
||||||
# Paula directly), matching the way our HAL cooperates with Intuition.
|
# Paula directly), matching the way our HAL cooperates with Intuition.
|
||||||
|
|
@ -64,43 +64,43 @@ LIB_OBJS := \
|
||||||
|
|
||||||
LIB := $(LIBDIR)/libjoey.a
|
LIB := $(LIBDIR)/libjoey.a
|
||||||
|
|
||||||
HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
||||||
HELLO_BIN := $(BINDIR)/Hello
|
HELLO_BIN := $(BINDIR)/Hello
|
||||||
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||||
PATTERN_BIN := $(BINDIR)/Pattern
|
PATTERN_BIN := $(BINDIR)/Pattern
|
||||||
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
||||||
DRAW_BIN := $(BINDIR)/Draw
|
DRAW_BIN := $(BINDIR)/Draw
|
||||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||||
KEYS_BIN := $(BINDIR)/Keys
|
KEYS_BIN := $(BINDIR)/Keys
|
||||||
SERIAL_SRC := $(EXAMPLES)/serial/serial.c
|
SERIAL_SRC := $(EXAMPLES)/serial/serial.c
|
||||||
SERIAL_BIN := $(BINDIR)/Serial
|
SERIAL_BIN := $(BINDIR)/Serial
|
||||||
SERTEST_SRC := $(EXAMPLES)/sertest/sertest.c
|
SERTEST_SRC := $(EXAMPLES)/sertest/sertest.c
|
||||||
SERTEST_BIN := $(BINDIR)/SerTest
|
SERTEST_BIN := $(BINDIR)/SerTest
|
||||||
SAVE_SRC := $(EXAMPLES)/save/save.c
|
SAVE_SRC := $(EXAMPLES)/save/save.c
|
||||||
SAVE_BIN := $(BINDIR)/Save
|
SAVE_BIN := $(BINDIR)/Save
|
||||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
TILEBANK_SRC := $(EXAMPLES)/tilebank/tilebank.c
|
||||||
JOY_BIN := $(BINDIR)/Joy
|
TILEBANK_BIN := $(BINDIR)/TileBank
|
||||||
SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c
|
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||||
SPRITE_BIN := $(BINDIR)/Sprite
|
JOY_BIN := $(BINDIR)/Joy
|
||||||
AUDIO_SRC := $(EXAMPLES)/audio/audio.c
|
SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c
|
||||||
AUDIO_BIN := $(BINDIR)/Audio
|
SPRITE_BIN := $(BINDIR)/Sprite
|
||||||
UBER_SRC := $(EXAMPLES)/uber/uber.c
|
AUDIO_SRC := $(EXAMPLES)/audio/audio.c
|
||||||
UBER_BIN := $(BINDIR)/Uber
|
AUDIO_BIN := $(BINDIR)/Audio
|
||||||
ADV_SRC := $(EXAMPLES)/adventure/adventure.c
|
UBER_SRC := $(EXAMPLES)/uber/uber.c
|
||||||
ADV_BIN := $(BINDIR)/Adv
|
UBER_BIN := $(BINDIR)/Uber
|
||||||
ADV2_SRC := $(EXAMPLES)/adventure2/adventure2.c
|
ADV_SRC := $(EXAMPLES)/adventure/adventure.c
|
||||||
ADV2_BIN := $(BINDIR)/Adv2
|
ADV_BIN := $(BINDIR)/Adv
|
||||||
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
|
ADV2_SRC := $(EXAMPLES)/adventure2/adventure2.c
|
||||||
AGI_BIN := $(BINDIR)/Agi
|
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/stCels.c $(EXAMPLES)/spacetaxi/stAudio.c $(EXAMPLES)/spacetaxi/stLevelCels.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 $(EXAMPLES)/spacetaxi/stLevelCels.c
|
||||||
STAXI_INSTALL_DIR := $(BINDIR)/Taxi
|
STAXI_INSTALL_DIR := $(BINDIR)/Taxi
|
||||||
STAXI_BIN := $(STAXI_INSTALL_DIR)/Taxi
|
STAXI_BIN := $(STAXI_INSTALL_DIR)/Taxi
|
||||||
|
|
||||||
# Space Taxi asset staging: same pattern as the DOS port -- sources
|
# Space Taxi asset staging: same pattern as the DOS port -- generated
|
||||||
# under examples/spacetaxi/assets, generated artifacts under
|
# artifacts under examples/spacetaxi/generated (nukeable), the runtime
|
||||||
# examples/spacetaxi/generated (nukeable), runtime tree under
|
# tree in DATA/ beside the binary. Mirrors the dos.mk wiring exactly.
|
||||||
# bin/assets/. Mirrors the dos.mk wiring exactly.
|
|
||||||
STAXI_SRC_DIR := $(EXAMPLES)/spacetaxi/assets
|
|
||||||
STAXI_GEN_DIR := $(EXAMPLES)/spacetaxi/generated/amiga
|
STAXI_GEN_DIR := $(EXAMPLES)/spacetaxi/generated/amiga
|
||||||
STAXI_RUN_DIR := $(STAXI_INSTALL_DIR)/DATA
|
STAXI_RUN_DIR := $(STAXI_INSTALL_DIR)/DATA
|
||||||
# Level data is extracted from the C64 ROM (see dos.mk for the long
|
# Level data is extracted from the C64 ROM (see dos.mk for the long
|
||||||
|
|
@ -109,22 +109,10 @@ STAXI_RUN_DIR := $(STAXI_INSTALL_DIR)/DATA
|
||||||
STAXI_ROM_LEVEL_GEN := $(foreach n,$(STAXI_LEVEL_NUMS),$(STAXI_GEN_DIR)/levels/level$(n).dat) \
|
STAXI_ROM_LEVEL_GEN := $(foreach n,$(STAXI_LEVEL_NUMS),$(STAXI_GEN_DIR)/levels/level$(n).dat) \
|
||||||
$(STAXI_GEN_DIR)/levels/title.dat
|
$(STAXI_GEN_DIR)/levels/title.dat
|
||||||
STAXI_LEVEL_GEN := $(STAXI_ROM_LEVEL_GEN)
|
STAXI_LEVEL_GEN := $(STAXI_ROM_LEVEL_GEN)
|
||||||
# Tile-bank PNGs (font.png and any tiles/*.png) bake to .tbk with
|
|
||||||
# Amiga plane-major bytes. Sprite-sheet PNGs bake to cross-target
|
|
||||||
# chunky 4bpp .spr (the Phase 11 walker reads chunky). The runtime
|
|
||||||
# loader (jlTileBankLoad / jlSpriteBankLoad) refuses .tbk files
|
|
||||||
# baked for the wrong target byte, so a stale build won't silently
|
|
||||||
# render garbage.
|
|
||||||
STAXI_TILE_PNGS := $(STAXI_SRC_DIR)/font.png $(wildcard $(STAXI_SRC_DIR)/tiles/*.png)
|
|
||||||
STAXI_SPRITE_PNGS := $(wildcard $(STAXI_SRC_DIR)/sprites/*.png)
|
|
||||||
STAXI_TBK_GEN := $(patsubst $(STAXI_SRC_DIR)/%.png,$(STAXI_GEN_DIR)/%.tbk,$(STAXI_TILE_PNGS))
|
|
||||||
STAXI_SPR_GEN := $(patsubst $(STAXI_SRC_DIR)/%.png,$(STAXI_GEN_DIR)/%.spr,$(STAXI_SPRITE_PNGS))
|
|
||||||
STAXI_LEVEL_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_LEVEL_GEN))
|
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)
|
||||||
# Pre-compiled sprite banks (.spc): baked from the .spr by the host spritebake
|
# 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
|
# tool for THIS target and staged in DATA/sprites, so the runtime loads
|
||||||
# compiled routines with no startup JIT (jlSpriteBankLoadPrecompiled).
|
# compiled routines with no startup JIT (jlSpriteBankLoadPrecompiled).
|
||||||
SPRITEBAKE_BIN := $(REPO_DIR)/build/tools/spritebake-amiga
|
SPRITEBAKE_BIN := $(REPO_DIR)/build/tools/spritebake-amiga
|
||||||
# The C64-exact cel bank, generated by staxibake (from the shared cel table
|
# The C64-exact cel bank, generated by staxibake (from the shared cel table
|
||||||
|
|
@ -160,19 +148,18 @@ STAXICELS_SPC_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXICEL
|
||||||
# not reach the game (found 2026-09-23 when a new cel made the runtime
|
# not reach the game (found 2026-09-23 when a new cel made the runtime
|
||||||
# reject the bank as too short).
|
# reject the bank as too short).
|
||||||
STAXI_ASSET_DSTS += $(STAXICELS_SPC_RUN)
|
STAXI_ASSET_DSTS += $(STAXICELS_SPC_RUN)
|
||||||
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
|
|
||||||
ASSETBAKE_TARGET := amiga
|
|
||||||
|
|
||||||
# Game data lives under bin/DATA/, ready to be copied into the
|
# Game data lives under bin/DATA/, ready to be copied into the
|
||||||
# scratch JOEYLIB hard-drive dir staged by joey-run.
|
# scratch JOEYLIB hard-drive dir staged by joey-run.
|
||||||
# audio.c fopens "DATA/test.mod" etc. relative to the boot volume.
|
# audio.c fopens "DATA/test.mod" etc. relative to the boot volume.
|
||||||
DATA_DIR := $(BINDIR)/DATA
|
DATA_DIR := $(BINDIR)/DATA
|
||||||
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
||||||
|
# TileBank's DATA/tilebank: this port's bake of the tile-bank fixture, the
|
||||||
|
# other targets' bakes it must refuse, and the expected pixels (common.mk).
|
||||||
|
TILEBANK_DATA := $(DATA_DIR)/tilebank/own.tbk
|
||||||
|
|
||||||
.PHONY: all amiga clean-amiga clean
|
.PHONY: all amiga clean-amiga clean
|
||||||
all amiga: $(LIB) $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(SERIAL_BIN) $(SERTEST_BIN) $(SAVE_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(UBER_BIN) $(ADV_BIN) $(ADV2_BIN) $(AGI_BIN) $(STAXI_BIN) $(DATA_FILES) $(call staxiData,$(STAXI_ASSET_DSTS))
|
all amiga: $(LIB) $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(SERIAL_BIN) $(SERTEST_BIN) $(SAVE_BIN) $(TILEBANK_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(UBER_BIN) $(ADV_BIN) $(ADV2_BIN) $(AGI_BIN) $(STAXI_BIN) $(DATA_FILES) $(TILEBANK_DATA) $(call staxiData,$(STAXI_ASSET_DSTS))
|
||||||
|
|
||||||
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
|
|
@ -244,6 +231,10 @@ $(SAVE_BIN): $(SAVE_SRC) $(LIB)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(AMIGA_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
$(AMIGA_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
|
$(TILEBANK_BIN): $(TILEBANK_SRC) $(LIB)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(AMIGA_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
$(JOY_BIN): $(JOY_SRC) $(LIB)
|
$(JOY_BIN): $(JOY_SRC) $(LIB)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(AMIGA_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
$(AMIGA_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||||
|
|
@ -278,20 +269,12 @@ $(STAXI_BIN): $(STAXI_SRCS) $(LIB)
|
||||||
|
|
||||||
# Space Taxi asset pipeline. .SECONDARY keeps the generated/ artifacts
|
# Space Taxi asset pipeline. .SECONDARY keeps the generated/ artifacts
|
||||||
# from being auto-deleted after staging.
|
# from being auto-deleted after staging.
|
||||||
.SECONDARY: $(STAXI_LEVEL_GEN) $(STAXI_TBK_GEN) $(STAXI_SPR_GEN) $(STAXI_SPC_GEN) $(STAXICELS_SPC_GEN) $(STAXILEVEL_SPR_GEN) $(STAXILEVEL_SPC_GEN)
|
.SECONDARY: $(STAXI_LEVEL_GEN) $(STAXICELS_SPC_GEN) $(STAXILEVEL_SPR_GEN) $(STAXILEVEL_SPC_GEN)
|
||||||
|
|
||||||
$(STAXI_ROM_LEVEL_GEN) &: $(STAXI_RAW_DUMP) $(ROMTOLEVEL_BIN)
|
$(STAXI_ROM_LEVEL_GEN) &: $(STAXI_RAW_DUMP) $(ROMTOLEVEL_BIN)
|
||||||
@mkdir -p $(STAXI_GEN_DIR)/levels
|
@mkdir -p $(STAXI_GEN_DIR)/levels
|
||||||
python3 $(ROMTOLEVEL_BIN) $(STAXI_RAW_DUMP) $(STAXI_GEN_DIR)/levels
|
python3 $(ROMTOLEVEL_BIN) $(STAXI_RAW_DUMP) $(STAXI_GEN_DIR)/levels
|
||||||
|
|
||||||
$(STAXI_GEN_DIR)/%.tbk: $(STAXI_SRC_DIR)/%.png $(ASSETBAKE_BIN)
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
python3 $(ASSETBAKE_BIN) --type tile --target $(ASSETBAKE_TARGET) $< $@
|
|
||||||
|
|
||||||
$(STAXI_GEN_DIR)/%.spr: $(STAXI_SRC_DIR)/%.png $(ASSETBAKE_BIN)
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
python3 $(ASSETBAKE_BIN) --type sprite --cell 3x3 $< $@
|
|
||||||
|
|
||||||
$(STAXI_GEN_DIR)/%.spc: $(STAXI_GEN_DIR)/%.spr $(SPRITEBAKE_BIN)
|
$(STAXI_GEN_DIR)/%.spc: $(STAXI_GEN_DIR)/%.spr $(SPRITEBAKE_BIN)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(SPRITEBAKE_BIN) $< $@
|
$(SPRITEBAKE_BIN) $< $@
|
||||||
|
|
@ -302,8 +285,7 @@ $(SPRITEBAKE_BIN):
|
||||||
$(STAXIBAKE_BIN):
|
$(STAXIBAKE_BIN):
|
||||||
$(MAKE) -f $(REPO_DIR)/make/tools.mk staxibake
|
$(MAKE) -f $(REPO_DIR)/make/tools.mk staxibake
|
||||||
|
|
||||||
# Explicit rule (overrides the %.spr-from-%.png pattern): the cel bank is
|
# The cel bank is generated from the game's cel table (stCels.c).
|
||||||
# generated from the game's cel table, not a PNG.
|
|
||||||
$(STAXICELS_SPR_GEN): $(STAXIBAKE_BIN) $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stC64Data.c
|
$(STAXICELS_SPR_GEN): $(STAXIBAKE_BIN) $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stC64Data.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(STAXIBAKE_BIN) $@
|
$(STAXIBAKE_BIN) $@
|
||||||
|
|
@ -312,14 +294,6 @@ $(STAXI_RUN_DIR)/levels/%.dat: $(STAXI_GEN_DIR)/levels/%.dat
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
cp $< $@
|
cp $< $@
|
||||||
|
|
||||||
$(STAXI_RUN_DIR)/%.tbk: $(STAXI_GEN_DIR)/%.tbk
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
cp $< $@
|
|
||||||
|
|
||||||
$(STAXI_RUN_DIR)/%.spr: $(STAXI_GEN_DIR)/%.spr
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
cp $< $@
|
|
||||||
|
|
||||||
$(STAXI_RUN_DIR)/%.spc: $(STAXI_GEN_DIR)/%.spc
|
$(STAXI_RUN_DIR)/%.spc: $(STAXI_GEN_DIR)/%.spc
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
cp $< $@
|
cp $< $@
|
||||||
|
|
@ -332,6 +306,9 @@ $(DATA_DIR)/test.sfx: $(REPO_DIR)/assets/test.sfx
|
||||||
@mkdir -p $(DATA_DIR)
|
@mkdir -p $(DATA_DIR)
|
||||||
cp $< $@
|
cp $< $@
|
||||||
|
|
||||||
|
$(TILEBANK_DATA): $(TILEBANK_BAKE_DEPS)
|
||||||
|
python3 $(TILEBANK_STAGE) $(PLATFORM) $(TILEBANK_PNG) $(dir $@)
|
||||||
|
|
||||||
clean-amiga:
|
clean-amiga:
|
||||||
rm -rf $(BUILD)
|
rm -rf $(BUILD)
|
||||||
|
|
||||||
|
|
@ -346,7 +323,7 @@ clean: clean-amiga
|
||||||
# different TUs see different struct layouts.
|
# different TUs see different struct layouts.
|
||||||
-include $(LIB_OBJS:.o=.d)
|
-include $(LIB_OBJS:.o=.d)
|
||||||
|
|
||||||
# Shorter stem than the %.spr-from-%.png pattern, so it wins for level banks.
|
# The per-level cel banks, baked from each level's file.
|
||||||
$(STAXI_GEN_DIR)/sprites/level%.spr: $(STAXI_GEN_DIR)/levels/level%.dat $(STAXIBAKE_BIN) $(EXAMPLES)/spacetaxi/stLevelCels.c
|
$(STAXI_GEN_DIR)/sprites/level%.spr: $(STAXI_GEN_DIR)/levels/level%.dat $(STAXIBAKE_BIN) $(EXAMPLES)/spacetaxi/stLevelCels.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(STAXIBAKE_BIN) --level $< $@
|
$(STAXIBAKE_BIN) --level $< $@
|
||||||
|
|
|
||||||
111
make/atarist.mk
111
make/atarist.mk
|
|
@ -11,9 +11,8 @@ BUILD := $(REPO_DIR)/build/$(PLATFORM)
|
||||||
LIBDIR := $(BUILD)/lib
|
LIBDIR := $(BUILD)/lib
|
||||||
BINDIR := $(BUILD)/bin
|
BINDIR := $(BUILD)/bin
|
||||||
|
|
||||||
# CFLAGS_EXTRA: command-line A/B hook (e.g. make CFLAGS_EXTRA=-DX)
|
|
||||||
# so flag experiments never edit a tracked file (perf-hunt-2 rank 17).
|
# so flag experiments never edit a tracked file (perf-hunt-2 rank 17).
|
||||||
CFLAGS := $(COMMON_CFLAGS) -m68000 -fomit-frame-pointer -I$(LIBXMP_DIR)/include -I$(ST_SHIM) -I$(SRC_DIR)/atarist -I$(SRC_68K) -MMD -MP $(CFLAGS_EXTRA)
|
CFLAGS := $(COMMON_CFLAGS) -m68000 -fomit-frame-pointer -I$(LIBXMP_DIR)/include -I$(ST_SHIM) -I$(SRC_DIR)/atarist -I$(SRC_68K) -MMD -MP
|
||||||
LDFLAGS :=
|
LDFLAGS :=
|
||||||
|
|
||||||
# libxmp-lite shared with the DOS port. Built as a static archive that
|
# libxmp-lite shared with the DOS port. Built as a static archive that
|
||||||
|
|
@ -51,40 +50,41 @@ LIB_OBJS := \
|
||||||
|
|
||||||
LIB := $(LIBDIR)/libjoey.a
|
LIB := $(LIBDIR)/libjoey.a
|
||||||
|
|
||||||
HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
||||||
HELLO_BIN := $(BINDIR)/HELLO.PRG
|
HELLO_BIN := $(BINDIR)/HELLO.PRG
|
||||||
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||||
PATTERN_BIN := $(BINDIR)/PATTERN.PRG
|
PATTERN_BIN := $(BINDIR)/PATTERN.PRG
|
||||||
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
||||||
DRAW_BIN := $(BINDIR)/DRAW.PRG
|
DRAW_BIN := $(BINDIR)/DRAW.PRG
|
||||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||||
KEYS_BIN := $(BINDIR)/KEYS.PRG
|
KEYS_BIN := $(BINDIR)/KEYS.PRG
|
||||||
SERIAL_SRC := $(EXAMPLES)/serial/serial.c
|
SERIAL_SRC := $(EXAMPLES)/serial/serial.c
|
||||||
SERIAL_BIN := $(BINDIR)/SERIAL.PRG
|
SERIAL_BIN := $(BINDIR)/SERIAL.PRG
|
||||||
SERTEST_SRC := $(EXAMPLES)/sertest/sertest.c
|
SERTEST_SRC := $(EXAMPLES)/sertest/sertest.c
|
||||||
SERTEST_BIN := $(BINDIR)/SERTEST.PRG
|
SERTEST_BIN := $(BINDIR)/SERTEST.PRG
|
||||||
SAVE_SRC := $(EXAMPLES)/save/save.c
|
SAVE_SRC := $(EXAMPLES)/save/save.c
|
||||||
SAVE_BIN := $(BINDIR)/SAVE.PRG
|
SAVE_BIN := $(BINDIR)/SAVE.PRG
|
||||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
TILEBANK_SRC := $(EXAMPLES)/tilebank/tilebank.c
|
||||||
JOY_BIN := $(BINDIR)/JOY.PRG
|
TILEBANK_BIN := $(BINDIR)/TILEBANK.PRG
|
||||||
SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c
|
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||||
SPRITE_BIN := $(BINDIR)/SPRITE.PRG
|
JOY_BIN := $(BINDIR)/JOY.PRG
|
||||||
AUDIO_SRC := $(EXAMPLES)/audio/audio.c
|
SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c
|
||||||
AUDIO_BIN := $(BINDIR)/AUDIO.PRG
|
SPRITE_BIN := $(BINDIR)/SPRITE.PRG
|
||||||
UBER_SRC := $(EXAMPLES)/uber/uber.c
|
AUDIO_SRC := $(EXAMPLES)/audio/audio.c
|
||||||
UBER_BIN := $(BINDIR)/UBER.PRG
|
AUDIO_BIN := $(BINDIR)/AUDIO.PRG
|
||||||
ADV_SRC := $(EXAMPLES)/adventure/adventure.c
|
UBER_SRC := $(EXAMPLES)/uber/uber.c
|
||||||
ADV_BIN := $(BINDIR)/ADV.PRG
|
UBER_BIN := $(BINDIR)/UBER.PRG
|
||||||
ADV2_SRC := $(EXAMPLES)/adventure2/adventure2.c
|
ADV_SRC := $(EXAMPLES)/adventure/adventure.c
|
||||||
ADV2_BIN := $(BINDIR)/ADV2.PRG
|
ADV_BIN := $(BINDIR)/ADV.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
|
ADV2_SRC := $(EXAMPLES)/adventure2/adventure2.c
|
||||||
AGI_BIN := $(BINDIR)/AGI.PRG
|
ADV2_BIN := $(BINDIR)/ADV2.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/stCels.c $(EXAMPLES)/spacetaxi/stAudio.c $(EXAMPLES)/spacetaxi/stLevelCels.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
|
||||||
|
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/stCels.c $(EXAMPLES)/spacetaxi/stAudio.c $(EXAMPLES)/spacetaxi/stLevelCels.c
|
||||||
STAXI_INSTALL_DIR := $(BINDIR)/STAXI
|
STAXI_INSTALL_DIR := $(BINDIR)/STAXI
|
||||||
STAXI_BIN := $(STAXI_INSTALL_DIR)/STAXI.PRG
|
STAXI_BIN := $(STAXI_INSTALL_DIR)/STAXI.PRG
|
||||||
|
|
||||||
# Space Taxi asset pipeline (mirrors dos.mk / amiga.mk).
|
# Space Taxi asset pipeline (mirrors dos.mk / amiga.mk).
|
||||||
STAXI_SRC_DIR := $(EXAMPLES)/spacetaxi/assets
|
|
||||||
STAXI_GEN_DIR := $(EXAMPLES)/spacetaxi/generated/atarist
|
STAXI_GEN_DIR := $(EXAMPLES)/spacetaxi/generated/atarist
|
||||||
STAXI_RUN_DIR := $(STAXI_INSTALL_DIR)/DATA
|
STAXI_RUN_DIR := $(STAXI_INSTALL_DIR)/DATA
|
||||||
# Level data is extracted from the C64 ROM (see dos.mk for the long
|
# Level data is extracted from the C64 ROM (see dos.mk for the long
|
||||||
|
|
@ -93,16 +93,10 @@ STAXI_RUN_DIR := $(STAXI_INSTALL_DIR)/DATA
|
||||||
STAXI_ROM_LEVEL_GEN := $(foreach n,$(STAXI_LEVEL_NUMS),$(STAXI_GEN_DIR)/levels/level$(n).dat) \
|
STAXI_ROM_LEVEL_GEN := $(foreach n,$(STAXI_LEVEL_NUMS),$(STAXI_GEN_DIR)/levels/level$(n).dat) \
|
||||||
$(STAXI_GEN_DIR)/levels/title.dat
|
$(STAXI_GEN_DIR)/levels/title.dat
|
||||||
STAXI_LEVEL_GEN := $(STAXI_ROM_LEVEL_GEN)
|
STAXI_LEVEL_GEN := $(STAXI_ROM_LEVEL_GEN)
|
||||||
STAXI_TILE_PNGS := $(STAXI_SRC_DIR)/font.png $(wildcard $(STAXI_SRC_DIR)/tiles/*.png)
|
|
||||||
STAXI_SPRITE_PNGS := $(wildcard $(STAXI_SRC_DIR)/sprites/*.png)
|
|
||||||
STAXI_TBK_GEN := $(patsubst $(STAXI_SRC_DIR)/%.png,$(STAXI_GEN_DIR)/%.tbk,$(STAXI_TILE_PNGS))
|
|
||||||
STAXI_SPR_GEN := $(patsubst $(STAXI_SRC_DIR)/%.png,$(STAXI_GEN_DIR)/%.spr,$(STAXI_SPRITE_PNGS))
|
|
||||||
STAXI_LEVEL_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_LEVEL_GEN))
|
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)
|
||||||
# Pre-compiled sprite banks (.spc): baked from the .spr by the host spritebake
|
# 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
|
# tool for THIS target and staged in DATA/sprites, so the runtime loads
|
||||||
# compiled routines with no startup JIT (jlSpriteBankLoadPrecompiled). ST is
|
# 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
|
# the one port whose .spc carries a reloc list (the shifted-DRAW thunk's jmp to
|
||||||
# stSpriteDrawShiftWalker), re-stamped by the loader.
|
# stSpriteDrawShiftWalker), re-stamped by the loader.
|
||||||
|
|
@ -140,19 +134,18 @@ STAXICELS_SPC_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXICEL
|
||||||
# not reach the game (found 2026-09-23 when a new cel made the runtime
|
# not reach the game (found 2026-09-23 when a new cel made the runtime
|
||||||
# reject the bank as too short).
|
# reject the bank as too short).
|
||||||
STAXI_ASSET_DSTS += $(STAXICELS_SPC_RUN)
|
STAXI_ASSET_DSTS += $(STAXICELS_SPC_RUN)
|
||||||
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
|
|
||||||
ASSETBAKE_TARGET := atarist
|
|
||||||
|
|
||||||
# Game data lives under bin/DATA/, alongside the binaries Hatari picks
|
# Game data lives under bin/DATA/, alongside the binaries Hatari picks
|
||||||
# up when bin/ is mounted as the GEMDOS C: drive. audio.c fopens
|
# up when bin/ is mounted as the GEMDOS C: drive. audio.c fopens
|
||||||
# "DATA/test.mod" etc.
|
# "DATA/test.mod" etc.
|
||||||
DATA_DIR := $(BINDIR)/DATA
|
DATA_DIR := $(BINDIR)/DATA
|
||||||
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
||||||
|
# TILEBANK's DATA/tilebank: this port's bake of the tile-bank fixture, the
|
||||||
|
# other targets' bakes it must refuse, and the expected pixels (common.mk).
|
||||||
|
TILEBANK_DATA := $(DATA_DIR)/tilebank/own.tbk
|
||||||
|
|
||||||
.PHONY: all atarist clean-atarist clean
|
.PHONY: all atarist clean-atarist clean
|
||||||
all atarist: $(LIB) $(LIBXMP_AR) $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(SERIAL_BIN) $(SERTEST_BIN) $(SAVE_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(UBER_BIN) $(ADV_BIN) $(ADV2_BIN) $(AGI_BIN) $(STAXI_BIN) $(DATA_FILES) $(call staxiData,$(STAXI_ASSET_DSTS))
|
all atarist: $(LIB) $(LIBXMP_AR) $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(SERIAL_BIN) $(SERTEST_BIN) $(SAVE_BIN) $(TILEBANK_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(UBER_BIN) $(ADV_BIN) $(ADV2_BIN) $(AGI_BIN) $(STAXI_BIN) $(DATA_FILES) $(TILEBANK_DATA) $(call staxiData,$(STAXI_ASSET_DSTS))
|
||||||
|
|
||||||
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
|
|
@ -231,6 +224,10 @@ $(SAVE_BIN): $(SAVE_SRC) $(LIB) $(LIBXMP_AR)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(ST_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(LDFLAGS)
|
$(ST_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
|
$(TILEBANK_BIN): $(TILEBANK_SRC) $(LIB) $(LIBXMP_AR)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(ST_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
$(JOY_BIN): $(JOY_SRC) $(LIB) $(LIBXMP_AR)
|
$(JOY_BIN): $(JOY_SRC) $(LIB) $(LIBXMP_AR)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(ST_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(LDFLAGS)
|
$(ST_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(LDFLAGS)
|
||||||
|
|
@ -264,20 +261,12 @@ $(STAXI_BIN): $(STAXI_SRCS) $(LIB) $(LIBXMP_AR)
|
||||||
$(ST_CC) $(CFLAGS) $(STAXI_SRCS) $(LIB) $(LIBXMP_AR) -o $@ $(LDFLAGS)
|
$(ST_CC) $(CFLAGS) $(STAXI_SRCS) $(LIB) $(LIBXMP_AR) -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
# Space Taxi asset pipeline (mirrors dos.mk + amiga.mk).
|
# Space Taxi asset pipeline (mirrors dos.mk + amiga.mk).
|
||||||
.SECONDARY: $(STAXI_LEVEL_GEN) $(STAXI_TBK_GEN) $(STAXI_SPR_GEN) $(STAXI_SPC_GEN) $(STAXICELS_SPC_GEN) $(STAXILEVEL_SPR_GEN) $(STAXILEVEL_SPC_GEN)
|
.SECONDARY: $(STAXI_LEVEL_GEN) $(STAXICELS_SPC_GEN) $(STAXILEVEL_SPR_GEN) $(STAXILEVEL_SPC_GEN)
|
||||||
|
|
||||||
$(STAXI_ROM_LEVEL_GEN) &: $(STAXI_RAW_DUMP) $(ROMTOLEVEL_BIN)
|
$(STAXI_ROM_LEVEL_GEN) &: $(STAXI_RAW_DUMP) $(ROMTOLEVEL_BIN)
|
||||||
@mkdir -p $(STAXI_GEN_DIR)/levels
|
@mkdir -p $(STAXI_GEN_DIR)/levels
|
||||||
python3 $(ROMTOLEVEL_BIN) $(STAXI_RAW_DUMP) $(STAXI_GEN_DIR)/levels
|
python3 $(ROMTOLEVEL_BIN) $(STAXI_RAW_DUMP) $(STAXI_GEN_DIR)/levels
|
||||||
|
|
||||||
$(STAXI_GEN_DIR)/%.tbk: $(STAXI_SRC_DIR)/%.png $(ASSETBAKE_BIN)
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
python3 $(ASSETBAKE_BIN) --type tile --target $(ASSETBAKE_TARGET) $< $@
|
|
||||||
|
|
||||||
$(STAXI_GEN_DIR)/%.spr: $(STAXI_SRC_DIR)/%.png $(ASSETBAKE_BIN)
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
python3 $(ASSETBAKE_BIN) --type sprite --cell 3x3 $< $@
|
|
||||||
|
|
||||||
$(STAXI_GEN_DIR)/%.spc: $(STAXI_GEN_DIR)/%.spr $(SPRITEBAKE_BIN)
|
$(STAXI_GEN_DIR)/%.spc: $(STAXI_GEN_DIR)/%.spr $(SPRITEBAKE_BIN)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(SPRITEBAKE_BIN) $< $@
|
$(SPRITEBAKE_BIN) $< $@
|
||||||
|
|
@ -288,8 +277,7 @@ $(SPRITEBAKE_BIN):
|
||||||
$(STAXIBAKE_BIN):
|
$(STAXIBAKE_BIN):
|
||||||
$(MAKE) -f $(REPO_DIR)/make/tools.mk staxibake
|
$(MAKE) -f $(REPO_DIR)/make/tools.mk staxibake
|
||||||
|
|
||||||
# Explicit rule (overrides the %.spr-from-%.png pattern): the cel bank is
|
# The cel bank is generated from the game's cel table (stCels.c).
|
||||||
# generated from the game's cel table, not a PNG.
|
|
||||||
$(STAXICELS_SPR_GEN): $(STAXIBAKE_BIN) $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stC64Data.c
|
$(STAXICELS_SPR_GEN): $(STAXIBAKE_BIN) $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stC64Data.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(STAXIBAKE_BIN) $@
|
$(STAXIBAKE_BIN) $@
|
||||||
|
|
@ -298,14 +286,6 @@ $(STAXI_RUN_DIR)/levels/%.dat: $(STAXI_GEN_DIR)/levels/%.dat
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
cp $< $@
|
cp $< $@
|
||||||
|
|
||||||
$(STAXI_RUN_DIR)/%.tbk: $(STAXI_GEN_DIR)/%.tbk
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
cp $< $@
|
|
||||||
|
|
||||||
$(STAXI_RUN_DIR)/%.spr: $(STAXI_GEN_DIR)/%.spr
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
cp $< $@
|
|
||||||
|
|
||||||
$(STAXI_RUN_DIR)/%.spc: $(STAXI_GEN_DIR)/%.spc
|
$(STAXI_RUN_DIR)/%.spc: $(STAXI_GEN_DIR)/%.spc
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
cp $< $@
|
cp $< $@
|
||||||
|
|
@ -318,6 +298,9 @@ $(DATA_DIR)/test.sfx: $(REPO_DIR)/assets/test.sfx
|
||||||
@mkdir -p $(DATA_DIR)
|
@mkdir -p $(DATA_DIR)
|
||||||
cp $< $@
|
cp $< $@
|
||||||
|
|
||||||
|
$(TILEBANK_DATA): $(TILEBANK_BAKE_DEPS)
|
||||||
|
python3 $(TILEBANK_STAGE) $(PLATFORM) $(TILEBANK_PNG) $(dir $@)
|
||||||
|
|
||||||
clean-atarist:
|
clean-atarist:
|
||||||
rm -rf $(BUILD)
|
rm -rf $(BUILD)
|
||||||
|
|
||||||
|
|
@ -332,7 +315,7 @@ clean: clean-atarist
|
||||||
# different TUs see different struct layouts.
|
# different TUs see different struct layouts.
|
||||||
-include $(LIB_OBJS:.o=.d)
|
-include $(LIB_OBJS:.o=.d)
|
||||||
|
|
||||||
# Shorter stem than the %.spr-from-%.png pattern, so it wins for level banks.
|
# The per-level cel banks, baked from each level's file.
|
||||||
$(STAXI_GEN_DIR)/sprites/level%.spr: $(STAXI_GEN_DIR)/levels/level%.dat $(STAXIBAKE_BIN) $(EXAMPLES)/spacetaxi/stLevelCels.c
|
$(STAXI_GEN_DIR)/sprites/level%.spr: $(STAXI_GEN_DIR)/levels/level%.dat $(STAXIBAKE_BIN) $(EXAMPLES)/spacetaxi/stLevelCels.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(STAXIBAKE_BIN) --level $< $@
|
$(STAXIBAKE_BIN) --level $< $@
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,14 @@ SRC_CG := $(REPO_DIR)/src/codegen
|
||||||
SRC_68K := $(REPO_DIR)/src/m68k
|
SRC_68K := $(REPO_DIR)/src/m68k
|
||||||
EXAMPLES := $(REPO_DIR)/examples
|
EXAMPLES := $(REPO_DIR)/examples
|
||||||
|
|
||||||
|
# The tile-bank example (examples/tilebank) loads the host gate's fixture, so
|
||||||
|
# the host (scripts/check-tilebank.sh) and every port share one expected hash.
|
||||||
|
# Each port stages its DATA/tilebank with TILEBANK_STAGE: its own bake, every
|
||||||
|
# other target's bake (to be refused) and the expected pixels.
|
||||||
|
TILEBANK_PNG := $(REPO_DIR)/tests/host/tileBank.png
|
||||||
|
TILEBANK_STAGE := $(REPO_DIR)/tests/host/tileBankStage.py
|
||||||
|
TILEBANK_BAKE_DEPS := $(TILEBANK_PNG) $(TILEBANK_STAGE) $(REPO_DIR)/tools/assetbake/assetbake.py
|
||||||
|
|
||||||
# Portable C sources for libjoey -- present on every target.
|
# Portable C sources for libjoey -- present on every target.
|
||||||
CORE_C_SRCS := $(wildcard $(SRC_CORE)/*.c)
|
CORE_C_SRCS := $(wildcard $(SRC_CORE)/*.c)
|
||||||
|
|
||||||
|
|
@ -46,5 +54,7 @@ CORE_C_SRCS := $(wildcard $(SRC_CORE)/*.c)
|
||||||
GENERIC_C_SRCS := $(filter-out %/spriteEmitStub.c, $(wildcard $(SRC_DIR)/generic/*.c))
|
GENERIC_C_SRCS := $(filter-out %/spriteEmitStub.c, $(wildcard $(SRC_DIR)/generic/*.c))
|
||||||
|
|
||||||
# Common include flags. Per-port code can include port.h / surfaceInternal.h
|
# Common include flags. Per-port code can include port.h / surfaceInternal.h
|
||||||
# directly because SRC_CORE is in the include path.
|
# directly because SRC_CORE is in the include path. EXTRA_CFLAGS is the one
|
||||||
|
# command-line hook for extra flags on every port (make EXTRA_CFLAGS=-DX);
|
||||||
|
# make/iigs.mk, which does not use COMMON_CFLAGS, honours it too.
|
||||||
COMMON_CFLAGS := -I$(INCLUDE_DIR) -I$(SRC_CORE) -Wall -Wextra -Werror -O2 $(EXTRA_CFLAGS)
|
COMMON_CFLAGS := -I$(INCLUDE_DIR) -I$(SRC_CORE) -Wall -Wextra -Werror -O2 $(EXTRA_CFLAGS)
|
||||||
|
|
|
||||||
129
make/dos.mk
129
make/dos.mk
|
|
@ -15,9 +15,8 @@ BINDIR := $(BUILD)/bin
|
||||||
# -fomit-frame-pointer: flag parity with the ST/Amiga CFLAGS (both had
|
# -fomit-frame-pointer: flag parity with the ST/Amiga CFLAGS (both had
|
||||||
# it; DOS never did). Frees EBP for allocation in the hot loops under
|
# it; DOS never did). Frees EBP for allocation in the hot loops under
|
||||||
# DOSBox's per-instruction-charged core (perf-hunt-2 rank 2).
|
# DOSBox's per-instruction-charged core (perf-hunt-2 rank 2).
|
||||||
# CFLAGS_EXTRA: command-line A/B hook (e.g. make CFLAGS_EXTRA=-DX)
|
|
||||||
# so flag experiments never edit a tracked file (perf-hunt-2 rank 17).
|
# so flag experiments never edit a tracked file (perf-hunt-2 rank 17).
|
||||||
CFLAGS := $(COMMON_CFLAGS) -march=i386 -m32 -fomit-frame-pointer -I$(SRC_DIR)/dos -I$(LIBXMP_DIR)/include -MMD -MP $(CFLAGS_EXTRA)
|
CFLAGS := $(COMMON_CFLAGS) -march=i386 -m32 -fomit-frame-pointer -I$(SRC_DIR)/dos -I$(LIBXMP_DIR)/include -MMD -MP
|
||||||
ASFLAGS := -f coff
|
ASFLAGS := -f coff
|
||||||
LDFLAGS :=
|
LDFLAGS :=
|
||||||
|
|
||||||
|
|
@ -47,34 +46,36 @@ LIB_OBJS := \
|
||||||
|
|
||||||
LIB := $(LIBDIR)/libjoey.a
|
LIB := $(LIBDIR)/libjoey.a
|
||||||
|
|
||||||
HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
||||||
HELLO_BIN := $(BINDIR)/HELLO.EXE
|
HELLO_BIN := $(BINDIR)/HELLO.EXE
|
||||||
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||||
PATTERN_BIN := $(BINDIR)/PATTERN.EXE
|
PATTERN_BIN := $(BINDIR)/PATTERN.EXE
|
||||||
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
||||||
DRAW_BIN := $(BINDIR)/DRAW.EXE
|
DRAW_BIN := $(BINDIR)/DRAW.EXE
|
||||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||||
KEYS_BIN := $(BINDIR)/KEYS.EXE
|
KEYS_BIN := $(BINDIR)/KEYS.EXE
|
||||||
SERIAL_SRC := $(EXAMPLES)/serial/serial.c
|
SERIAL_SRC := $(EXAMPLES)/serial/serial.c
|
||||||
SERIAL_BIN := $(BINDIR)/SERIAL.EXE
|
SERIAL_BIN := $(BINDIR)/SERIAL.EXE
|
||||||
SERTEST_SRC := $(EXAMPLES)/sertest/sertest.c
|
SERTEST_SRC := $(EXAMPLES)/sertest/sertest.c
|
||||||
SERTEST_BIN := $(BINDIR)/SERTEST.EXE
|
SERTEST_BIN := $(BINDIR)/SERTEST.EXE
|
||||||
SAVE_SRC := $(EXAMPLES)/save/save.c
|
SAVE_SRC := $(EXAMPLES)/save/save.c
|
||||||
SAVE_BIN := $(BINDIR)/SAVE.EXE
|
SAVE_BIN := $(BINDIR)/SAVE.EXE
|
||||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
TILEBANK_SRC := $(EXAMPLES)/tilebank/tilebank.c
|
||||||
JOY_BIN := $(BINDIR)/JOY.EXE
|
TILEBANK_BIN := $(BINDIR)/TILEBANK.EXE
|
||||||
SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c
|
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||||
SPRITE_BIN := $(BINDIR)/SPRITE.EXE
|
JOY_BIN := $(BINDIR)/JOY.EXE
|
||||||
AUDIO_SRC := $(EXAMPLES)/audio/audio.c
|
SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c
|
||||||
AUDIO_BIN := $(BINDIR)/AUDIO.EXE
|
SPRITE_BIN := $(BINDIR)/SPRITE.EXE
|
||||||
UBER_SRC := $(EXAMPLES)/uber/uber.c
|
AUDIO_SRC := $(EXAMPLES)/audio/audio.c
|
||||||
UBER_BIN := $(BINDIR)/UBER.EXE
|
AUDIO_BIN := $(BINDIR)/AUDIO.EXE
|
||||||
ADV_SRC := $(EXAMPLES)/adventure/adventure.c
|
UBER_SRC := $(EXAMPLES)/uber/uber.c
|
||||||
ADV_BIN := $(BINDIR)/ADV.EXE
|
UBER_BIN := $(BINDIR)/UBER.EXE
|
||||||
ADV2_SRC := $(EXAMPLES)/adventure2/adventure2.c
|
ADV_SRC := $(EXAMPLES)/adventure/adventure.c
|
||||||
ADV2_BIN := $(BINDIR)/ADV2.EXE
|
ADV_BIN := $(BINDIR)/ADV.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
|
ADV2_SRC := $(EXAMPLES)/adventure2/adventure2.c
|
||||||
AGI_BIN := $(BINDIR)/AGI.EXE
|
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/stCels.c $(EXAMPLES)/spacetaxi/stAudio.c $(EXAMPLES)/spacetaxi/stLevelCels.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 $(EXAMPLES)/spacetaxi/stLevelCels.c
|
||||||
# JoeyLib install convention: each app lives in its own bin subdir
|
# JoeyLib install convention: each app lives in its own bin subdir
|
||||||
# with a DATA/ folder alongside the binary for runtime assets.
|
# with a DATA/ folder alongside the binary for runtime assets.
|
||||||
|
|
@ -85,15 +86,17 @@ STAXI_BIN := $(STAXI_INSTALL_DIR)/STAXI.EXE
|
||||||
# up when bin/ is mounted as C:. audio.c fopens "DATA/test.mod" etc.
|
# up when bin/ is mounted as C:. audio.c fopens "DATA/test.mod" etc.
|
||||||
DATA_DIR := $(BINDIR)/DATA
|
DATA_DIR := $(BINDIR)/DATA
|
||||||
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
||||||
|
# TILEBANK's DATA/tilebank: this port's bake of the tile-bank fixture, the
|
||||||
|
# other targets' bakes it must refuse, and the expected pixels (common.mk).
|
||||||
|
TILEBANK_DATA := $(DATA_DIR)/tilebank/own.tbk
|
||||||
|
|
||||||
# Space Taxi asset pipeline.
|
# Space Taxi asset pipeline.
|
||||||
#
|
#
|
||||||
# Sources live in examples/spacetaxi/assets/ (txt, png, etc. -- the
|
# Build artifacts go to examples/spacetaxi/generated/<port> -- safe to
|
||||||
# stuff the user hand-authors). Build artifacts go to
|
# nuke; recreated by this makefile. What the game loads is then copied
|
||||||
# examples/spacetaxi/generated/ (dat, jas) -- safe to nuke; recreated
|
# into the DATA tree beside its binary: the levels, the cel bank, the
|
||||||
# by this makefile. Runtime files are then copied from generated/
|
# per-level cel banks and the speech. Tiles and the font come from the
|
||||||
# into the per-target bin/assets/ tree.
|
# C64 data compiled into the binary (stC64Data.c).
|
||||||
STAXI_SRC_DIR := $(EXAMPLES)/spacetaxi/assets
|
|
||||||
STAXI_GEN_DIR := $(EXAMPLES)/spacetaxi/generated/dos
|
STAXI_GEN_DIR := $(EXAMPLES)/spacetaxi/generated/dos
|
||||||
STAXI_RUN_DIR := $(STAXI_INSTALL_DIR)/DATA
|
STAXI_RUN_DIR := $(STAXI_INSTALL_DIR)/DATA
|
||||||
|
|
||||||
|
|
@ -103,29 +106,18 @@ STAXI_RUN_DIR := $(STAXI_INSTALL_DIR)/DATA
|
||||||
# decompressor through a Python 6502 emulator and writes STL2 .dat
|
# decompressor through a Python 6502 emulator and writes STL2 .dat
|
||||||
# files directly. Input is the raw RAM dump. mkstlevel + title.txt
|
# files directly. Input is the raw RAM dump. mkstlevel + title.txt
|
||||||
# is no longer used: the C64 title encodes its screen with the game's
|
# is no longer used: the C64 title encodes its screen with the game's
|
||||||
# custom charset (extracted into tbank0.png), so a hand-authored tile
|
# custom charset (C64 data in stC64Data.c), so a hand-authored tile
|
||||||
# layout with a different glyph mapping renders as garbage.
|
# layout with a different glyph mapping renders as garbage.
|
||||||
STAXI_ROM_LEVEL_GEN := $(foreach n,$(STAXI_LEVEL_NUMS),$(STAXI_GEN_DIR)/levels/level$(n).dat) \
|
STAXI_ROM_LEVEL_GEN := $(foreach n,$(STAXI_LEVEL_NUMS),$(STAXI_GEN_DIR)/levels/level$(n).dat) \
|
||||||
$(STAXI_GEN_DIR)/levels/title.dat
|
$(STAXI_GEN_DIR)/levels/title.dat
|
||||||
STAXI_LEVEL_GEN := $(STAXI_ROM_LEVEL_GEN)
|
STAXI_LEVEL_GEN := $(STAXI_ROM_LEVEL_GEN)
|
||||||
|
|
||||||
# Tile-bank PNGs (font.png and any tiles/*.png) bake to .tbk with
|
|
||||||
# DOS / IIgs chunky 4bpp bytes. Sprite-sheet PNGs bake to cross-
|
|
||||||
# target chunky 4bpp .spr blobs.
|
|
||||||
STAXI_TILE_PNGS := $(STAXI_SRC_DIR)/font.png $(wildcard $(STAXI_SRC_DIR)/tiles/*.png)
|
|
||||||
STAXI_SPRITE_PNGS := $(wildcard $(STAXI_SRC_DIR)/sprites/*.png)
|
|
||||||
STAXI_TBK_GEN := $(patsubst $(STAXI_SRC_DIR)/%.png,$(STAXI_GEN_DIR)/%.tbk,$(STAXI_TILE_PNGS))
|
|
||||||
STAXI_SPR_GEN := $(patsubst $(STAXI_SRC_DIR)/%.png,$(STAXI_GEN_DIR)/%.spr,$(STAXI_SPRITE_PNGS))
|
|
||||||
ASSETBAKE_BIN := $(REPO_DIR)/tools/assetbake/assetbake.py
|
|
||||||
ASSETBAKE_TARGET := dos
|
|
||||||
|
|
||||||
# Final runtime tree (mirrors generated/ into bin/assets/).
|
# The runtime tree beside the binary.
|
||||||
STAXI_LEVEL_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_LEVEL_GEN))
|
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)
|
||||||
# Pre-compiled sprite banks (.spc): baked from the .spr by the host spritebake
|
# 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
|
# tool for THIS target and staged in DATA/sprites, so the runtime loads
|
||||||
# compiled routines with no startup JIT (jlSpriteBankLoadPrecompiled).
|
# compiled routines with no startup JIT (jlSpriteBankLoadPrecompiled).
|
||||||
SPRITEBAKE_BIN := $(REPO_DIR)/build/tools/spritebake-dos
|
SPRITEBAKE_BIN := $(REPO_DIR)/build/tools/spritebake-dos
|
||||||
# The C64-exact cel bank, generated by staxibake (from the shared cel table
|
# The C64-exact cel bank, generated by staxibake (from the shared cel table
|
||||||
|
|
@ -161,13 +153,11 @@ STAXICELS_SPC_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXICEL
|
||||||
# not reach the game (found 2026-09-23 when a new cel made the runtime
|
# not reach the game (found 2026-09-23 when a new cel made the runtime
|
||||||
# reject the bank as too short).
|
# reject the bank as too short).
|
||||||
STAXI_ASSET_DSTS += $(STAXICELS_SPC_RUN)
|
STAXI_ASSET_DSTS += $(STAXICELS_SPC_RUN)
|
||||||
STAXI_SPC_GEN := $(STAXI_SPR_GEN:.spr=.spc)
|
|
||||||
STAXI_SPC_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_SPC_GEN))
|
|
||||||
|
|
||||||
MKSTLEVEL_BIN := $(REPO_DIR)/build/tools/mkstlevel
|
MKSTLEVEL_BIN := $(REPO_DIR)/build/tools/mkstlevel
|
||||||
|
|
||||||
.PHONY: all dos clean-dos clean
|
.PHONY: all dos clean-dos clean
|
||||||
all dos: $(LIB) $(LIBXMP_AR) $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(SERIAL_BIN) $(SERTEST_BIN) $(SAVE_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(UBER_BIN) $(ADV_BIN) $(ADV2_BIN) $(AGI_BIN) $(STAXI_BIN) $(DATA_FILES) $(call staxiData,$(STAXI_ASSET_DSTS))
|
all dos: $(LIB) $(LIBXMP_AR) $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(SERIAL_BIN) $(SERTEST_BIN) $(SAVE_BIN) $(TILEBANK_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(UBER_BIN) $(ADV_BIN) $(ADV2_BIN) $(AGI_BIN) $(STAXI_BIN) $(DATA_FILES) $(TILEBANK_DATA) $(call staxiData,$(STAXI_ASSET_DSTS))
|
||||||
|
|
||||||
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
|
|
@ -236,6 +226,11 @@ $(SAVE_BIN): $(SAVE_SRC) $(LIB) $(LIBXMP_AR)
|
||||||
$(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@
|
$(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@
|
||||||
$(DOS_EMBED_DPMI) $@
|
$(DOS_EMBED_DPMI) $@
|
||||||
|
|
||||||
|
$(TILEBANK_BIN): $(TILEBANK_SRC) $(LIB) $(LIBXMP_AR)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@
|
||||||
|
$(DOS_EMBED_DPMI) $@
|
||||||
|
|
||||||
$(JOY_BIN): $(JOY_SRC) $(LIB) $(LIBXMP_AR)
|
$(JOY_BIN): $(JOY_SRC) $(LIB) $(LIBXMP_AR)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@
|
$(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@
|
||||||
|
|
@ -280,7 +275,7 @@ $(STAXI_BIN): $(STAXI_SRCS) $(LIB) $(LIBXMP_AR)
|
||||||
# Mark these .SECONDARY so make doesn't auto-delete them after the
|
# Mark these .SECONDARY so make doesn't auto-delete them after the
|
||||||
# stage-B copy into bin/ -- they're the canonical distribution
|
# stage-B copy into bin/ -- they're the canonical distribution
|
||||||
# artifacts.
|
# artifacts.
|
||||||
.SECONDARY: $(STAXI_LEVEL_GEN) $(STAXI_TBK_GEN) $(STAXI_SPR_GEN) $(STAXI_SPC_GEN) $(STAXICELS_SPC_GEN) $(STAXILEVEL_SPR_GEN) $(STAXILEVEL_SPC_GEN)
|
.SECONDARY: $(STAXI_LEVEL_GEN) $(STAXICELS_SPC_GEN) $(STAXILEVEL_SPR_GEN) $(STAXILEVEL_SPC_GEN)
|
||||||
|
|
||||||
# Bulk-extract all 24 canonical levels plus the title from the C64
|
# Bulk-extract all 24 canonical levels plus the title from the C64
|
||||||
# ROM in one invocation. Grouped target (&:) means make treats the
|
# ROM in one invocation. Grouped target (&:) means make treats the
|
||||||
|
|
@ -290,14 +285,6 @@ $(STAXI_ROM_LEVEL_GEN) &: $(STAXI_RAW_DUMP) $(ROMTOLEVEL_BIN)
|
||||||
@mkdir -p $(STAXI_GEN_DIR)/levels
|
@mkdir -p $(STAXI_GEN_DIR)/levels
|
||||||
python3 $(ROMTOLEVEL_BIN) $(STAXI_RAW_DUMP) $(STAXI_GEN_DIR)/levels
|
python3 $(ROMTOLEVEL_BIN) $(STAXI_RAW_DUMP) $(STAXI_GEN_DIR)/levels
|
||||||
|
|
||||||
$(STAXI_GEN_DIR)/%.tbk: $(STAXI_SRC_DIR)/%.png $(ASSETBAKE_BIN)
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
python3 $(ASSETBAKE_BIN) --type tile --target $(ASSETBAKE_TARGET) $< $@
|
|
||||||
|
|
||||||
$(STAXI_GEN_DIR)/%.spr: $(STAXI_SRC_DIR)/%.png $(ASSETBAKE_BIN)
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
python3 $(ASSETBAKE_BIN) --type sprite --cell 3x3 $< $@
|
|
||||||
|
|
||||||
$(STAXI_GEN_DIR)/%.spc: $(STAXI_GEN_DIR)/%.spr $(SPRITEBAKE_BIN)
|
$(STAXI_GEN_DIR)/%.spc: $(STAXI_GEN_DIR)/%.spr $(SPRITEBAKE_BIN)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(SPRITEBAKE_BIN) $< $@
|
$(SPRITEBAKE_BIN) $< $@
|
||||||
|
|
@ -308,25 +295,16 @@ $(SPRITEBAKE_BIN):
|
||||||
$(STAXIBAKE_BIN):
|
$(STAXIBAKE_BIN):
|
||||||
$(MAKE) -f $(REPO_DIR)/make/tools.mk staxibake
|
$(MAKE) -f $(REPO_DIR)/make/tools.mk staxibake
|
||||||
|
|
||||||
# Explicit rule (overrides the %.spr-from-%.png pattern): the cel bank is
|
# The cel bank is generated from the game's cel table (stCels.c).
|
||||||
# generated from the game's cel table, not a PNG.
|
|
||||||
$(STAXICELS_SPR_GEN): $(STAXIBAKE_BIN) $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stC64Data.c
|
$(STAXICELS_SPR_GEN): $(STAXIBAKE_BIN) $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stC64Data.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(STAXIBAKE_BIN) $@
|
$(STAXIBAKE_BIN) $@
|
||||||
|
|
||||||
# Stage B: copy generated/ into the per-target bin/assets/ runtime tree
|
# Stage B: copy generated/ into the runtime tree beside the binary
|
||||||
$(STAXI_RUN_DIR)/levels/%.dat: $(STAXI_GEN_DIR)/levels/%.dat
|
$(STAXI_RUN_DIR)/levels/%.dat: $(STAXI_GEN_DIR)/levels/%.dat
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
cp $< $@
|
cp $< $@
|
||||||
|
|
||||||
$(STAXI_RUN_DIR)/%.tbk: $(STAXI_GEN_DIR)/%.tbk
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
cp $< $@
|
|
||||||
|
|
||||||
$(STAXI_RUN_DIR)/%.spr: $(STAXI_GEN_DIR)/%.spr
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
cp $< $@
|
|
||||||
|
|
||||||
$(STAXI_RUN_DIR)/%.spc: $(STAXI_GEN_DIR)/%.spc
|
$(STAXI_RUN_DIR)/%.spc: $(STAXI_GEN_DIR)/%.spc
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
cp $< $@
|
cp $< $@
|
||||||
|
|
@ -342,6 +320,9 @@ $(DATA_DIR)/test.sfx: $(REPO_DIR)/assets/test.sfx
|
||||||
@mkdir -p $(DATA_DIR)
|
@mkdir -p $(DATA_DIR)
|
||||||
cp $< $@
|
cp $< $@
|
||||||
|
|
||||||
|
$(TILEBANK_DATA): $(TILEBANK_BAKE_DEPS)
|
||||||
|
python3 $(TILEBANK_STAGE) $(PLATFORM) $(TILEBANK_PNG) $(dir $@)
|
||||||
|
|
||||||
clean-dos:
|
clean-dos:
|
||||||
rm -rf $(BUILD)
|
rm -rf $(BUILD)
|
||||||
|
|
||||||
|
|
@ -363,7 +344,7 @@ clean-spacetaxi:
|
||||||
# different TUs see different struct layouts.
|
# different TUs see different struct layouts.
|
||||||
-include $(LIB_OBJS:.o=.d)
|
-include $(LIB_OBJS:.o=.d)
|
||||||
|
|
||||||
# Shorter stem than the %.spr-from-%.png pattern, so it wins for level banks.
|
# The per-level cel banks, baked from each level's file.
|
||||||
$(STAXI_GEN_DIR)/sprites/level%.spr: $(STAXI_GEN_DIR)/levels/level%.dat $(STAXIBAKE_BIN) $(EXAMPLES)/spacetaxi/stLevelCels.c
|
$(STAXI_GEN_DIR)/sprites/level%.spr: $(STAXI_GEN_DIR)/levels/level%.dat $(STAXIBAKE_BIN) $(EXAMPLES)/spacetaxi/stLevelCels.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(STAXIBAKE_BIN) --level $< $@
|
$(STAXIBAKE_BIN) --level $< $@
|
||||||
|
|
|
||||||
58
make/iigs.mk
58
make/iigs.mk
|
|
@ -56,22 +56,28 @@ LIB_SRCS := $(LIB_C_SRCS) $(PORT_ASM_SRCS)
|
||||||
IIGS_LIB_HDRS := $(wildcard $(INCLUDE_DIR)/joey/*.h) $(wildcard $(SRC_CORE)/*.h) $(wildcard $(REPO_DIR)/src/codegen/*.h) $(wildcard $(SRC_DIR)/iigs/*.h) $(wildcard $(REPO_DIR)/src/m68k/*.h)
|
IIGS_LIB_HDRS := $(wildcard $(INCLUDE_DIR)/joey/*.h) $(wildcard $(SRC_CORE)/*.h) $(wildcard $(REPO_DIR)/src/codegen/*.h) $(wildcard $(SRC_DIR)/iigs/*.h) $(wildcard $(REPO_DIR)/src/m68k/*.h)
|
||||||
|
|
||||||
# Example sources (single-TU unless noted).
|
# Example sources (single-TU unless noted).
|
||||||
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||||
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
||||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||||
SERIAL_SRC := $(EXAMPLES)/serial/serial.c
|
SERIAL_SRC := $(EXAMPLES)/serial/serial.c
|
||||||
SERTEST_SRC := $(EXAMPLES)/sertest/sertest.c
|
SERTEST_SRC := $(EXAMPLES)/sertest/sertest.c
|
||||||
SCCPROBE_SRC := $(EXAMPLES)/sccprobe/sccprobe.c
|
SCCPROBE_SRC := $(EXAMPLES)/sccprobe/sccprobe.c
|
||||||
SERLOSS_SRC := $(EXAMPLES)/serloss/serloss.c
|
SERLOSS_SRC := $(EXAMPLES)/serloss/serloss.c
|
||||||
SAVE_SRC := $(EXAMPLES)/save/save.c
|
SAVE_SRC := $(EXAMPLES)/save/save.c
|
||||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
TILEBANK_SRC := $(EXAMPLES)/tilebank/tilebank.c
|
||||||
SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c
|
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||||
UBER_SRC := $(EXAMPLES)/uber/uber.c
|
SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c
|
||||||
ADV_SRC := $(EXAMPLES)/adventure/adventure.c
|
UBER_SRC := $(EXAMPLES)/uber/uber.c
|
||||||
ADV2_SRC := $(EXAMPLES)/adventure2/adventure2.c
|
ADV_SRC := $(EXAMPLES)/adventure/adventure.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
|
ADV2_SRC := $(EXAMPLES)/adventure2/adventure2.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 $(EXAMPLES)/spacetaxi/stLevelCels.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
|
||||||
AUDIO_SRC := $(EXAMPLES)/audio/audio.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 $(EXAMPLES)/spacetaxi/stLevelCels.c
|
||||||
|
AUDIO_SRC := $(EXAMPLES)/audio/audio.c
|
||||||
|
|
||||||
|
# TILEBANK's DATA/tilebank: this port's bake of the tile-bank fixture, the
|
||||||
|
# other targets' bakes it must refuse, and the expected pixels (common.mk).
|
||||||
|
# make-iigs-disk.sh packs it as /JOEYLIB/DATA/TILEBANK beside the program.
|
||||||
|
TILEBANK_DATA := $(BINDIR)/DATA/tilebank/own.tbk
|
||||||
|
|
||||||
# NinjaTrackerPlus replayer: Merlin32-assemble ninjatrackerplus.s to a 34KB
|
# NinjaTrackerPlus replayer: Merlin32-assemble ninjatrackerplus.s to a 34KB
|
||||||
# binary that ships next to the program; jlpAudioInit loads "ntpplayer" from
|
# binary that ships next to the program; jlpAudioInit loads "ntpplayer" from
|
||||||
|
|
@ -82,7 +88,7 @@ NTP_PATCH := $(REPO_DIR)/make/support/iigs/ntpIrqWindow.py
|
||||||
NTP_BIN := $(BUILD)/audio/ntpplayer.bin
|
NTP_BIN := $(BUILD)/audio/ntpplayer.bin
|
||||||
IIGS_MERLIN := $(IIGS_AS)
|
IIGS_MERLIN := $(IIGS_AS)
|
||||||
|
|
||||||
.PHONY: all iigs iigs-lib iigs-clang-smoke iigs-examples iigs-disk iigs-disk-all iigs-game-disk iigs-game-disks iigs-game-disk-agi iigs-verify-game-disk iigs-verify iigs-verify-all iigs-verify-save iigs-verify-serial iigs-verify-serial-loss iigs-verify-shrtail clean-iigs clean
|
.PHONY: all iigs iigs-lib iigs-clang-smoke iigs-examples iigs-disk iigs-disk-all iigs-game-disk iigs-game-disks iigs-game-disk-agi iigs-verify-game-disk iigs-verify iigs-verify-all iigs-verify-save iigs-verify-serial iigs-verify-serial-loss iigs-verify-shrtail iigs-verify-tilebank clean-iigs clean
|
||||||
|
|
||||||
# Default: build (compile-check) the library. The MAME smoke test is
|
# Default: build (compile-check) the library. The MAME smoke test is
|
||||||
# `iigs-clang-smoke`; disks and examples are `iigs-disk`.
|
# `iigs-clang-smoke`; disks and examples are `iigs-disk`.
|
||||||
|
|
@ -94,10 +100,11 @@ all iigs: iigs-lib
|
||||||
# fix), and AUDIO's 34KB-rodata blocker was resolved by loading the
|
# fix), and AUDIO's 34KB-rodata blocker was resolved by loading the
|
||||||
# NTP replayer from disk (see the $(BINDIR)/AUDIO rule). Leaving them
|
# NTP replayer from disk (see the $(BINDIR)/AUDIO rule). Leaving them
|
||||||
# out made `make iigs` silently test 8-day-old STAXI binaries against
|
# out made `make iigs` silently test 8-day-old STAXI binaries against
|
||||||
# new assets (the 2026-07-20 gear-out/no-flame reports).
|
# new assets (the 2026-07-20 gear-out/no-flame reports). TILEBANK's staged
|
||||||
|
# DATA rides along, so every disk that packs TILEBANK finds it.
|
||||||
iigs-examples: $(BINDIR)/PATTERN $(BINDIR)/DRAW $(BINDIR)/KEYS $(BINDIR)/SERIAL $(BINDIR)/SERTEST $(BINDIR)/SAVE $(BINDIR)/JOY \
|
iigs-examples: $(BINDIR)/PATTERN $(BINDIR)/DRAW $(BINDIR)/KEYS $(BINDIR)/SERIAL $(BINDIR)/SERTEST $(BINDIR)/SAVE $(BINDIR)/JOY \
|
||||||
$(BINDIR)/SPRITE $(BINDIR)/UBER $(BINDIR)/ADV $(BINDIR)/ADV2 \
|
$(BINDIR)/SPRITE $(BINDIR)/UBER $(BINDIR)/ADV $(BINDIR)/ADV2 \
|
||||||
$(BINDIR)/AGI $(BINDIR)/STAXI $(BINDIR)/AUDIO
|
$(BINDIR)/AGI $(BINDIR)/STAXI $(BINDIR)/AUDIO $(BINDIR)/TILEBANK $(TILEBANK_DATA)
|
||||||
|
|
||||||
# Build the JOEYLIB data disk (build/iigs/bin/joey.2mg) that the gates, and
|
# Build the JOEYLIB data disk (build/iigs/bin/joey.2mg) that the gates, and
|
||||||
# `joey-run build/iigs/bin/joey.2mg`, mount beside GS/OS. Packs the launchable examples as ProDOS S16 apps
|
# `joey-run build/iigs/bin/joey.2mg`, mount beside GS/OS. Packs the launchable examples as ProDOS S16 apps
|
||||||
|
|
@ -210,7 +217,7 @@ iigs-verify-game-disk: iigs-game-disk
|
||||||
iigs-disk-all: iigs-examples $(NTP_BIN) $(STAXI_IIGS_SPC) $(call staxiData,$(STAXI_IIGS_LEVELS) $(STAXILEVEL_IIGS_SPC)) $(STAXI_IIGS_SPEECH)
|
iigs-disk-all: iigs-examples $(NTP_BIN) $(STAXI_IIGS_SPC) $(call staxiData,$(STAXI_IIGS_LEVELS) $(STAXILEVEL_IIGS_SPC)) $(STAXI_IIGS_SPEECH)
|
||||||
BINDIR="$(BINDIR)" NTP_BIN="$(NTP_BIN)" \
|
BINDIR="$(BINDIR)" NTP_BIN="$(NTP_BIN)" \
|
||||||
JOEY_DISK_SIZE="16MB" \
|
JOEY_DISK_SIZE="16MB" \
|
||||||
JOEY_DISK_EXAMPLES="DRAW PATTERN KEYS JOY SPRITE SERIAL SERTEST SAVE AUDIO UBER ADV2 ADV AGI STAXI" \
|
JOEY_DISK_EXAMPLES="DRAW PATTERN KEYS JOY SPRITE SERIAL SERTEST SAVE TILEBANK AUDIO UBER ADV2 ADV AGI STAXI" \
|
||||||
$(REPO_DIR)/scripts/make-iigs-disk.sh $(BINDIR)/joey-all.2mg
|
$(REPO_DIR)/scripts/make-iigs-disk.sh $(BINDIR)/joey-all.2mg
|
||||||
|
|
||||||
# Headless visual-verification gate: boot GS/OS under MAME, launch the DRAW
|
# Headless visual-verification gate: boot GS/OS under MAME, launch the DRAW
|
||||||
|
|
@ -270,6 +277,12 @@ iigs-verify-shrtail: $(LIB) $(NTP_BIN)
|
||||||
{ echo "iigs-verify-shrtail: FAIL - \$$E1 SCB/palette block mismatched the stage" >&2; exit 1; } || true
|
{ echo "iigs-verify-shrtail: FAIL - \$$E1 SCB/palette block mismatched the stage" >&2; exit 1; } || true
|
||||||
@echo "iigs-verify-shrtail: PASS"
|
@echo "iigs-verify-shrtail: PASS"
|
||||||
|
|
||||||
|
# Tile-bank gate: TILEBANK loads this port's own .tbk bake both ways off its
|
||||||
|
# own one-example disk; both must hash like the host gate's, and every other
|
||||||
|
# target's bake must be refused. NTP_BIN keeps the disk's audio init quiet.
|
||||||
|
iigs-verify-tilebank: $(BINDIR)/TILEBANK $(TILEBANK_DATA) $(NTP_BIN)
|
||||||
|
$(REPO_DIR)/scripts/verify-iigs-tilebank.sh
|
||||||
|
|
||||||
# Runtime gate for the save HAL's GS/OS calls (Create + GetDevNumber/DInfo/
|
# Runtime gate for the save HAL's GS/OS calls (Create + GetDevNumber/DInfo/
|
||||||
# Volume): boots SAVE under MAME and checks the save-OK lamp + disk-free bar.
|
# Volume): boots SAVE under MAME and checks the save-OK lamp + disk-free bar.
|
||||||
iigs-verify-save: $(BINDIR)/SAVE $(BINDIR)/DRAW
|
iigs-verify-save: $(BINDIR)/SAVE $(BINDIR)/DRAW
|
||||||
|
|
@ -360,6 +373,13 @@ $(BINDIR)/SAVE: $(SAVE_SRC) $(LIB) $(IIGS_CLANG_BUILD)
|
||||||
@mkdir -p $(dir $@) $(DEP_DIR)
|
@mkdir -p $(dir $@) $(DEP_DIR)
|
||||||
$(IIGS_CLANG_BUILD) -M $(DEP_DIR)/SAVE.d $(INCLUDES) -o $@ $(SAVE_SRC) $(LIB)
|
$(IIGS_CLANG_BUILD) -M $(DEP_DIR)/SAVE.d $(INCLUDES) -o $@ $(SAVE_SRC) $(LIB)
|
||||||
|
|
||||||
|
$(BINDIR)/TILEBANK: $(TILEBANK_SRC) $(LIB) $(IIGS_CLANG_BUILD)
|
||||||
|
@mkdir -p $(dir $@) $(DEP_DIR)
|
||||||
|
$(IIGS_CLANG_BUILD) -M $(DEP_DIR)/TILEBANK.d $(INCLUDES) -o $@ $(TILEBANK_SRC) $(LIB)
|
||||||
|
|
||||||
|
$(TILEBANK_DATA): $(TILEBANK_BAKE_DEPS)
|
||||||
|
python3 $(TILEBANK_STAGE) $(PLATFORM) $(TILEBANK_PNG) $(dir $@)
|
||||||
|
|
||||||
$(BINDIR)/JOY: $(JOY_SRC) $(LIB) $(IIGS_CLANG_BUILD)
|
$(BINDIR)/JOY: $(JOY_SRC) $(LIB) $(IIGS_CLANG_BUILD)
|
||||||
@mkdir -p $(dir $@) $(DEP_DIR)
|
@mkdir -p $(dir $@) $(DEP_DIR)
|
||||||
$(IIGS_CLANG_BUILD) -M $(DEP_DIR)/JOY.d $(INCLUDES) -o $@ $(JOY_SRC) $(LIB)
|
$(IIGS_CLANG_BUILD) -M $(DEP_DIR)/JOY.d $(INCLUDES) -o $@ $(JOY_SRC) $(LIB)
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ export PATH := $(X68K_ROOT)/bin:$(PATH)
|
||||||
CFLAGS := $(COMMON_CFLAGS) -m68000 -fomit-frame-pointer \
|
CFLAGS := $(COMMON_CFLAGS) -m68000 -fomit-frame-pointer \
|
||||||
-DJOEYLIB_PLATFORM_X68000 \
|
-DJOEYLIB_PLATFORM_X68000 \
|
||||||
-I$(LIBXMP_DIR)/include \
|
-I$(LIBXMP_DIR)/include \
|
||||||
-I$(SRC_DIR)/x68000 -I$(SRC_68K) -I$(REPO_DIR)/src/codegen -MMD -MP $(CFLAGS_EXTRA)
|
-I$(SRC_DIR)/x68000 -I$(SRC_68K) -I$(REPO_DIR)/src/codegen -MMD -MP
|
||||||
LDFLAGS := -lm
|
LDFLAGS := -lm
|
||||||
|
|
||||||
PORT_C_SRCS := $(wildcard $(SRC_DIR)/x68000/*.c)
|
PORT_C_SRCS := $(wildcard $(SRC_DIR)/x68000/*.c)
|
||||||
|
|
@ -93,26 +93,35 @@ LIB := $(LIBDIR)/libjoey.a
|
||||||
# KEYS the input and mouse vehicle: key predicates, the typed line
|
# KEYS the input and mouse vehicle: key predicates, the typed line
|
||||||
# (jlInputGetChar) and its verification row.
|
# (jlInputGetChar) and its verification row.
|
||||||
# SERIAL the RS-232C gate's echo program.
|
# SERIAL the RS-232C gate's echo program.
|
||||||
HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
# SERMOUSE the serial + mouse gate's program: streams both ways on the
|
||||||
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
# RS-232C port (SCC channel A) while the mouse (channel B) moves.
|
||||||
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
# TILEBANK the tile-bank gate's program: this port's .tbk bake loaded both
|
||||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
# ways and pixel-checked, every other target's bake refused.
|
||||||
SERIAL_SRC := $(EXAMPLES)/serial/serial.c
|
HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
||||||
SERTEST_SRC := $(EXAMPLES)/sertest/sertest.c
|
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||||
SAVE_SRC := $(EXAMPLES)/save/save.c
|
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
||||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||||
SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c
|
SERIAL_SRC := $(EXAMPLES)/serial/serial.c
|
||||||
AUDIO_SRC := $(EXAMPLES)/audio/audio.c
|
SERTEST_SRC := $(EXAMPLES)/sertest/sertest.c
|
||||||
UBER_SRC := $(EXAMPLES)/uber/uber.c
|
SERMOUSE_SRC := $(EXAMPLES)/sermouse/sermouse.c
|
||||||
ADV_SRC := $(EXAMPLES)/adventure/adventure.c
|
SAVE_SRC := $(EXAMPLES)/save/save.c
|
||||||
ADV2_SRC := $(EXAMPLES)/adventure2/adventure2.c
|
TILEBANK_SRC := $(EXAMPLES)/tilebank/tilebank.c
|
||||||
AGI_SRC := $(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
|
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||||
EXAMPLES_X := HELLO PATTERN DRAW KEYS SERIAL SERTEST SAVE JOY SPRITE AUDIO UBER ADV ADV2 AGI
|
SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c
|
||||||
|
AUDIO_SRC := $(EXAMPLES)/audio/audio.c
|
||||||
|
UBER_SRC := $(EXAMPLES)/uber/uber.c
|
||||||
|
ADV_SRC := $(EXAMPLES)/adventure/adventure.c
|
||||||
|
ADV2_SRC := $(EXAMPLES)/adventure2/adventure2.c
|
||||||
|
AGI_SRC := $(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
|
||||||
|
EXAMPLES_X := HELLO PATTERN DRAW KEYS SERIAL SERTEST SERMOUSE SAVE TILEBANK JOY SPRITE AUDIO UBER ADV ADV2 AGI
|
||||||
EXAMPLE_BINS := $(foreach e,$(EXAMPLES_X),$(BINDIR)/$(e).X)
|
EXAMPLE_BINS := $(foreach e,$(EXAMPLES_X),$(BINDIR)/$(e).X)
|
||||||
|
|
||||||
# AUDIO loads DATA/test.mod and DATA/test.sfx from beside the program.
|
# AUDIO loads DATA/test.mod and DATA/test.sfx from beside the program.
|
||||||
DATA_DIR := $(BINDIR)/DATA
|
DATA_DIR := $(BINDIR)/DATA
|
||||||
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
||||||
|
# TILEBANK's DATA/tilebank: this port's bake of the tile-bank fixture, the
|
||||||
|
# other targets' bakes it must refuse, and the expected pixels (common.mk).
|
||||||
|
TILEBANK_DATA := $(DATA_DIR)/tilebank/own.tbk
|
||||||
|
|
||||||
# Space Taxi ships in its own folder with its DATA, as on the other ports,
|
# Space Taxi ships in its own folder with its DATA, as on the other ports,
|
||||||
# so its megabyte of data never rides onto every other program's floppy.
|
# so its megabyte of data never rides onto every other program's floppy.
|
||||||
|
|
@ -120,13 +129,13 @@ STAXI_DIR := $(BINDIR)/STAXI
|
||||||
STAXI_BIN := $(STAXI_DIR)/STAXI.X
|
STAXI_BIN := $(STAXI_DIR)/STAXI.X
|
||||||
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 $(EXAMPLES)/spacetaxi/stLevelCels.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 $(EXAMPLES)/spacetaxi/stLevelCels.c
|
||||||
|
|
||||||
.PHONY: all x68000 x68000-lib x68000-examples x68000-verify-serial x68000-verify-mouse x68000-verify-golden clean-x68000 clean
|
.PHONY: all x68000 x68000-lib x68000-examples x68000-verify-serial x68000-verify-mouse x68000-verify-serial-mouse x68000-verify-tilebank x68000-verify-golden clean-x68000 clean
|
||||||
|
|
||||||
all x68000: x68000-lib x68000-examples
|
all x68000: x68000-lib x68000-examples
|
||||||
|
|
||||||
x68000-lib: $(LIB) $(LIBXMP_AR)
|
x68000-lib: $(LIB) $(LIBXMP_AR)
|
||||||
|
|
||||||
x68000-examples: $(EXAMPLE_BINS) $(STAXI_BIN) $(DATA_FILES)
|
x68000-examples: $(EXAMPLE_BINS) $(STAXI_BIN) $(DATA_FILES) $(TILEBANK_DATA)
|
||||||
|
|
||||||
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
|
|
@ -171,6 +180,9 @@ $(DATA_DIR)/%: $(REPO_DIR)/assets/%
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
cp $< $@
|
cp $< $@
|
||||||
|
|
||||||
|
$(TILEBANK_DATA): $(TILEBANK_BAKE_DEPS)
|
||||||
|
python3 $(TILEBANK_STAGE) $(PLATFORM) $(TILEBANK_PNG) $(dir $@)
|
||||||
|
|
||||||
# Space Taxi asset variables (the rules follow the binary below); make
|
# Space Taxi asset variables (the rules follow the binary below); make
|
||||||
# expands prerequisites when it reads the rule, so these come first.
|
# expands prerequisites when it reads the rule, so these come first.
|
||||||
STAXI_GEN_DIR := $(EXAMPLES)/spacetaxi/generated/x68000
|
STAXI_GEN_DIR := $(EXAMPLES)/spacetaxi/generated/x68000
|
||||||
|
|
@ -273,6 +285,16 @@ x68000-verify-serial: $(BINDIR)/SERIAL.X
|
||||||
x68000-verify-mouse: $(BINDIR)/KEYS.X
|
x68000-verify-mouse: $(BINDIR)/KEYS.X
|
||||||
$(REPO_DIR)/scripts/verify-x68000-mouse.sh
|
$(REPO_DIR)/scripts/verify-x68000-mouse.sh
|
||||||
|
|
||||||
|
# Serial + mouse together: both share the SCC, so stream both ways on the
|
||||||
|
# RS-232C port while the mouse moves, and check neither disturbs the other.
|
||||||
|
x68000-verify-serial-mouse: $(BINDIR)/SERMOUSE.X
|
||||||
|
$(REPO_DIR)/scripts/verify-x68000-serial-mouse.sh
|
||||||
|
|
||||||
|
# Tile-bank gate: this port's own .tbk bake loaded both ways must hash like
|
||||||
|
# the host gate's, and every other target's bake must be refused (~1 min).
|
||||||
|
x68000-verify-tilebank: $(BINDIR)/TILEBANK.X $(TILEBANK_DATA)
|
||||||
|
$(REPO_DIR)/scripts/verify-x68000-tilebank.sh
|
||||||
|
|
||||||
# Golden-hash gate against the Apple IIgs reference (~70 min: UBER on the
|
# Golden-hash gate against the Apple IIgs reference (~70 min: UBER on the
|
||||||
# generic renderer is slow and must reach jlLogFlush before hashes exist).
|
# generic renderer is slow and must reach jlLogFlush before hashes exist).
|
||||||
x68000-verify-golden: $(LIB) $(LIBXMP_AR)
|
x68000-verify-golden: $(LIB) $(LIBXMP_AR)
|
||||||
|
|
|
||||||
45
scripts/check-tilebank.sh
Executable file
45
scripts/check-tilebank.sh
Executable file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# check-tilebank.sh - Host-cc gate for the baked tile-bank loaders
|
||||||
|
# (jlTileBankLoad / jlTileBankLoadToSurface in src/core/assetLoad.c), end to
|
||||||
|
# end: assetbake.py bakes tests/host/tileBank.png, the real loader reads it,
|
||||||
|
# the real generic tile paste draws it, and tests/host/tileBankHost.c checks
|
||||||
|
# every pixel against the PNG. Also checks the refusals (a bank baked for any
|
||||||
|
# other target, bad magic, short header, missing file), a truncated bank,
|
||||||
|
# maxTiles below the bank size, a bank without a palette, and a bank bigger
|
||||||
|
# than a surface.
|
||||||
|
#
|
||||||
|
# Built as a whole BLANK-port program (the check-blank.sh source set plus the
|
||||||
|
# harness). BLANK draws chunky 4bpp like DOS, and assetLoad.c expects the DOS
|
||||||
|
# target byte there, so the DOS bake is the bank that must load. Every other
|
||||||
|
# target assetbake knows is baked too and must be refused.
|
||||||
|
#
|
||||||
|
# tests/host/tileBankStage.py stages the banks and the expected pixels and
|
||||||
|
# palette (decoded from the PNG with PIL, independent of assetbake's
|
||||||
|
# encoders), exactly as make/<port>.mk stages them for examples/tilebank on
|
||||||
|
# the real ports. The harness prints the surface hash of its to-surface load;
|
||||||
|
# scripts/verify-<port>-tilebank.sh run this script and require each port's
|
||||||
|
# own bake to hash the same. Exits nonzero on any failed check.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
cc=${CC:-cc}
|
||||||
|
work=$(mktemp -d -t joeylib-tilebank.XXXXXX)
|
||||||
|
trap 'rm -rf "$work"' EXIT
|
||||||
|
png="$repo/tests/host/tileBank.png"
|
||||||
|
hostTarget=dos
|
||||||
|
|
||||||
|
cd "$repo"
|
||||||
|
"$cc" -DJOEYLIB_PLATFORM_BLANK \
|
||||||
|
-Iinclude -Iinclude/joey -Isrc/core -Isrc/codegen -Wall \
|
||||||
|
src/core/*.c src/generic/*.c src/blank/blank.c tests/host/tileBankHost.c \
|
||||||
|
-o "$work/tileBankHost"
|
||||||
|
|
||||||
|
python3 "$repo/tests/host/tileBankStage.py" "$hostTarget" "$png" "$work/DATA"
|
||||||
|
refuse=()
|
||||||
|
for bank in "$work"/DATA/refuse*.tbk; do
|
||||||
|
refuse+=("$(basename "$bank")")
|
||||||
|
done
|
||||||
|
|
||||||
|
# Run from the temp dir: the harness reads and writes DATA/ there.
|
||||||
|
cd "$work"
|
||||||
|
"$work/tileBankHost" "${refuse[@]}"
|
||||||
|
|
@ -76,6 +76,13 @@ joeyExampleProgram() {
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
# A mixed-case Amiga name (SerTest) is not derivable from the example
|
||||||
|
# name, so take the file in any case.
|
||||||
|
candidate="$(find "$bin" -maxdepth 1 -type f -iname "$up$ext" -print -quit 2>/dev/null)"
|
||||||
|
if [ -n "$candidate" ]; then
|
||||||
|
printf '%s\n' "$candidate"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
printf '%s\n' "$bin/$up$ext"
|
printf '%s\n' "$bin/$up$ext"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
66
scripts/lib/mameNullModemCfg.py
Normal file
66
scripts/lib/mameNullModemCfg.py
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""mameNullModemCfg.py - a MAME cfg file that sets a null_modem's line.
|
||||||
|
|
||||||
|
MAME's null_modem clocks the wire itself, at 9600 8 N 1 unless its
|
||||||
|
configuration says otherwise, so a guest whose UART runs at any other rate
|
||||||
|
sees garbage. The line is a CONFIG input port on the device, which MAME
|
||||||
|
reads back from cfg/SYSTEM.cfg in its -cfg_directory.
|
||||||
|
|
||||||
|
mameNullModemCfg.py SYSTEM TAG BAUD [DATABITS PARITY STOPBITS]
|
||||||
|
|
||||||
|
SYSTEM is the driver (x68000, apple2gs, apple2gsr1), TAG the null_modem's
|
||||||
|
device tag (":rs232c:null_modem", ":modem:null_modem"). The line may also
|
||||||
|
come as one quoted argument ("4800 7 E 1"). DATABITS PARITY STOPBITS
|
||||||
|
default to 8 N 1. Prints the cfg; exit 2 for a line MAME cannot set.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# MAME's RS232_* enum values (src/devices/bus/rs232/rs232.h).
|
||||||
|
BAUD = {"50": 14, "75": 15, "110": 0, "150": 1, "200": 17, "300": 2, "600": 3,
|
||||||
|
"1200": 4, "1800": 18, "2000": 19, "2400": 5, "3600": 20, "4800": 6,
|
||||||
|
"7200": 21, "9600": 7, "14400": 8, "19200": 9, "28800": 10,
|
||||||
|
"38400": 11, "57600": 12, "115200": 13}
|
||||||
|
DATA = {"5": 0, "6": 1, "7": 2, "8": 3}
|
||||||
|
PARITY = {"N": 0, "O": 1, "E": 2}
|
||||||
|
STOP = {"1": 1, "2": 3}
|
||||||
|
DEFAULT_FORMAT = ["8", "N", "1"]
|
||||||
|
# The null_modem's own defaults (src/devices/bus/rs232/null_modem.cpp).
|
||||||
|
BAUD_DEFAULT = 7
|
||||||
|
DATA_DEFAULT = 3
|
||||||
|
PARITY_DEFAULT = 0
|
||||||
|
STOP_DEFAULT = 1
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
if len(argv) < 4:
|
||||||
|
sys.stderr.write(__doc__)
|
||||||
|
return 2
|
||||||
|
system = argv[1]
|
||||||
|
tag = argv[2]
|
||||||
|
line = " ".join(argv[3:]).split()
|
||||||
|
if len(line) == 1:
|
||||||
|
line += DEFAULT_FORMAT
|
||||||
|
if len(line) != 4:
|
||||||
|
sys.stderr.write("mameNullModemCfg.py: the line is BAUD [DATABITS PARITY STOPBITS]\n")
|
||||||
|
return 2
|
||||||
|
baud, data, parity, stop = line
|
||||||
|
parity = parity.upper()
|
||||||
|
for value, table, what in ((baud, BAUD, "baud rate"), (data, DATA, "data bits"),
|
||||||
|
(parity, PARITY, "parity"), (stop, STOP, "stop bits")):
|
||||||
|
if value not in table:
|
||||||
|
sys.stderr.write("mameNullModemCfg.py: MAME's null_modem has no %s %s (it takes %s)\n"
|
||||||
|
% (what, value, " ".join(table)))
|
||||||
|
return 2
|
||||||
|
ports = [("RS232_TXBAUD", BAUD_DEFAULT, BAUD[baud]), ("RS232_RXBAUD", BAUD_DEFAULT, BAUD[baud]),
|
||||||
|
("RS232_DATABITS", DATA_DEFAULT, DATA[data]), ("RS232_PARITY", PARITY_DEFAULT, PARITY[parity]),
|
||||||
|
("RS232_STOPBITS", STOP_DEFAULT, STOP[stop])]
|
||||||
|
print('<?xml version="1.0"?>\n<mameconfig version="10">\n <system name="%s">\n <input>' % system)
|
||||||
|
for name, default, value in ports:
|
||||||
|
print(f' <port tag="{tag}:{name}" type="CONFIG" mask="255" defvalue="{default}" value="{value}" />')
|
||||||
|
print(' </input>\n </system>\n</mameconfig>')
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main(sys.argv))
|
||||||
122
scripts/lib/serialStreamPeer.py
Executable file
122
scripts/lib/serialStreamPeer.py
Executable file
|
|
@ -0,0 +1,122 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""serialStreamPeer.py - host end of a both-directions serial stream gate.
|
||||||
|
|
||||||
|
The guest (examples/sermouse) transmits an endless stream and checks an
|
||||||
|
endless stream from us, both byte-exact:
|
||||||
|
|
||||||
|
byte k = (k ^ (k >> 8) ^ mask) & 0xFF
|
||||||
|
|
||||||
|
with mask GUEST_MASK for the guest's stream and PEER_MASK for ours (the same
|
||||||
|
formula and masks as streamByte() in examples/sermouse/sermouse.c). A lost or
|
||||||
|
repeated byte throws every byte after it off its expected value.
|
||||||
|
|
||||||
|
This peer listens (the emulator's null_modem connects out), checks the guest's
|
||||||
|
stream in order, and answers it: our stream is paced byte for byte on the
|
||||||
|
guest's, never more than WINDOW bytes ahead of what has come back. So when the
|
||||||
|
guest pauses its stream the line goes quiet both ways, and once the guest has
|
||||||
|
been silent for QUIET_SECONDS the counts are recorded as a quiet snapshot
|
||||||
|
"sent:received" -- at that moment every byte either side sent has arrived, so a
|
||||||
|
harness that reads the guest's own counts while it is paused can require them
|
||||||
|
to equal one of these exactly.
|
||||||
|
|
||||||
|
serialStreamPeer.py PORT WALL MIN_BYTES
|
||||||
|
|
||||||
|
Runs until the emulator closes the link or WALL seconds pass, then prints one
|
||||||
|
line, STREAM PASS or STREAM FAIL, with the counts and the quiet snapshots. PASS
|
||||||
|
means the guest's stream arrived with no bad byte and at least MIN_BYTES long.
|
||||||
|
Exit 0 on pass, 1 on fail.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import select
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
from serialLoopPeer import acceptOne
|
||||||
|
|
||||||
|
GUEST_MASK = 0xA5
|
||||||
|
PEER_MASK = 0x00
|
||||||
|
WINDOW = 256 # our bytes allowed ahead of the guest's
|
||||||
|
QUIET_SECONDS = 0.25 # guest silence that counts as a pause
|
||||||
|
POLL_SECONDS = 0.05
|
||||||
|
READ_SIZE = 4096
|
||||||
|
BYTE_MASK = 0xFF
|
||||||
|
BYTE_BITS = 8
|
||||||
|
NO_BAD_INDEX = -1
|
||||||
|
|
||||||
|
|
||||||
|
def streamByte(index, mask):
|
||||||
|
return (index ^ (index >> BYTE_BITS) ^ mask) & BYTE_MASK
|
||||||
|
|
||||||
|
|
||||||
|
def run(conn, wall, start):
|
||||||
|
received = 0
|
||||||
|
sent = 0
|
||||||
|
bad = 0
|
||||||
|
firstBad = NO_BAD_INDEX
|
||||||
|
lastRx = 0.0
|
||||||
|
quietArmed = False
|
||||||
|
snapshots = []
|
||||||
|
closed = False
|
||||||
|
|
||||||
|
conn.setblocking(False)
|
||||||
|
while time.time() - start < wall:
|
||||||
|
# Our stream starts once the guest's has: it is open and listening.
|
||||||
|
want = received + WINDOW - sent if received > 0 else 0
|
||||||
|
writers = [conn] if want > 0 else []
|
||||||
|
readable, writable, _ = select.select([conn], writers, [], POLL_SECONDS)
|
||||||
|
now = time.time()
|
||||||
|
if readable:
|
||||||
|
try:
|
||||||
|
data = conn.recv(READ_SIZE)
|
||||||
|
except OSError:
|
||||||
|
data = b""
|
||||||
|
if not data:
|
||||||
|
closed = True
|
||||||
|
break
|
||||||
|
for value in data:
|
||||||
|
if value != streamByte(received, GUEST_MASK):
|
||||||
|
if firstBad == NO_BAD_INDEX:
|
||||||
|
firstBad = received
|
||||||
|
bad += 1
|
||||||
|
received += 1
|
||||||
|
lastRx = now
|
||||||
|
quietArmed = True
|
||||||
|
if writable and want > 0:
|
||||||
|
chunk = bytes(streamByte(sent + i, PEER_MASK) for i in range(want))
|
||||||
|
try:
|
||||||
|
sent += conn.send(chunk)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
if quietArmed and now - lastRx >= QUIET_SECONDS:
|
||||||
|
snapshots.append("%d:%d" % (sent, received))
|
||||||
|
quietArmed = False
|
||||||
|
return received, sent, bad, firstBad, snapshots, closed
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
if len(argv) != 4:
|
||||||
|
sys.stderr.write(__doc__)
|
||||||
|
return 2
|
||||||
|
port = int(argv[1])
|
||||||
|
wall = float(argv[2])
|
||||||
|
minBytes = int(argv[3])
|
||||||
|
start = time.time()
|
||||||
|
|
||||||
|
print("STREAM listening on 127.0.0.1:%d" % port, flush=True)
|
||||||
|
conn = acceptOne(port, wall)
|
||||||
|
if conn is None:
|
||||||
|
print("STREAM FAIL: the emulator never connected")
|
||||||
|
return 1
|
||||||
|
received, sent, bad, firstBad, snapshots, closed = run(conn, wall, start)
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
ok = bad == 0 and received >= minBytes
|
||||||
|
print("STREAM %s received=%d sent=%d bad=%d firstBad=%d quiet=%s end=%s" %
|
||||||
|
("PASS" if ok else "FAIL", received, sent, bad, firstBad,
|
||||||
|
",".join(snapshots) if snapshots else "none",
|
||||||
|
"closed" if closed else "wall"))
|
||||||
|
return 0 if ok else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main(sys.argv))
|
||||||
144
scripts/lib/tileBankGate.sh
Normal file
144
scripts/lib/tileBankGate.sh
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
# Shared by the tile-bank gates, scripts/verify-<port>-tilebank.sh. Each runs
|
||||||
|
# examples/tilebank on its port with the DATA/tilebank make/<port>.mk staged
|
||||||
|
# (tests/host/tileBankStage.py: the port's own .tbk bake of
|
||||||
|
# tests/host/tileBank.png, every other target's bake, the expected pixels),
|
||||||
|
# gets the program's TILEBANK log line off the machine, and judges it here
|
||||||
|
# against the host gate: scripts/check-tilebank.sh is run for its surface
|
||||||
|
# hash, so the expected value has exactly one source.
|
||||||
|
# Source after joeyEnv.sh.
|
||||||
|
|
||||||
|
TILEBANK_GATE="$(basename "$0" .sh)"
|
||||||
|
|
||||||
|
|
||||||
|
# tileBankAwaitLine DIR SECONDS PID -- wait for the program's log (joeylog.txt
|
||||||
|
# in any case, as DOS and GEMDOS name it) in DIR to hold a TILEBANK line, for
|
||||||
|
# up to SECONDS or until PID exits; print the log's path if it does.
|
||||||
|
tileBankAwaitLine() {
|
||||||
|
local dir="$1"
|
||||||
|
local seconds="$2"
|
||||||
|
local pid="$3"
|
||||||
|
local log
|
||||||
|
local waited
|
||||||
|
|
||||||
|
for ((waited = 0; waited < seconds; waited++)); do
|
||||||
|
log="$(find "$dir" -maxdepth 1 -iname joeylog.txt -print -quit)"
|
||||||
|
if [ -n "$log" ] && grep -q '^TILEBANK: ' "$log"; then
|
||||||
|
printf '%s\n' "$log"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
joeyAlive "$pid" || return 1
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# tileBankChild PID -- print the child of PID: the emulator a `timeout -s KILL`
|
||||||
|
# wrapper started. Stop the child first; stopping only the wrapper can orphan
|
||||||
|
# an emulator that ignores SIGTERM (Hatari does).
|
||||||
|
tileBankChild() {
|
||||||
|
local tries
|
||||||
|
local child
|
||||||
|
|
||||||
|
for ((tries = 0; tries < 20; tries++)); do
|
||||||
|
child="$(pgrep -P "$1" | head -1)"
|
||||||
|
if [ -n "$child" ]; then
|
||||||
|
printf '%s\n' "$child"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# tileBankData BINDIR -- print the DATA/tilebank to stage: the one
|
||||||
|
# make/<port>.mk staged beside the program, or TILEBANK_DATA_DIR when set (a
|
||||||
|
# negative control stages a deliberately broken bake that way).
|
||||||
|
tileBankData() {
|
||||||
|
printf '%s\n' "${TILEBANK_DATA_DIR:-$1/DATA/tilebank}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# tileBankJudge LOG -- judge examples/tilebank's TILEBANK line in LOG: the
|
||||||
|
# program's own verdict (both loads pixel-exact, the palette, every staged
|
||||||
|
# foreign bank refused both ways), both surface hashes equal to the host's,
|
||||||
|
# and every other target assetbake knows refused. Prints the line and one
|
||||||
|
# PASS or FAIL line; returns 0 on PASS.
|
||||||
|
tileBankJudge() {
|
||||||
|
local line
|
||||||
|
local verdict
|
||||||
|
local streamHash
|
||||||
|
local pasteHash
|
||||||
|
local refused
|
||||||
|
local wantRefused="$TILEBANK_OTHERS/$TILEBANK_OTHERS"
|
||||||
|
local fails=()
|
||||||
|
local why
|
||||||
|
|
||||||
|
line="$(tr -d '\r' < "$1" | grep -E '^TILEBANK: ' | tail -1)"
|
||||||
|
if [ -z "$line" ]; then
|
||||||
|
echo "$TILEBANK_GATE: FAIL - no TILEBANK line in the program's log" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "$line"
|
||||||
|
verdict="$(sed -n 's/^TILEBANK: \([A-Z]*\).*/\1/p' <<< "$line")"
|
||||||
|
streamHash="$(sed -n 's/.* streamHash=\([0-9A-F]*\).*/\1/p' <<< "$line")"
|
||||||
|
pasteHash="$(sed -n 's/.* pasteHash=\([0-9A-F]*\).*/\1/p' <<< "$line")"
|
||||||
|
refused="$(sed -n 's/.* refused=\([0-9]*\/[0-9]*\).*/\1/p' <<< "$line")"
|
||||||
|
[ "$verdict" = PASS ] || fails+=("the program's own checks failed")
|
||||||
|
[ "$streamHash" = "$TILEBANK_HASH" ] || fails+=("streamHash ${streamHash:-missing}, host $TILEBANK_HASH")
|
||||||
|
[ "$pasteHash" = "$TILEBANK_HASH" ] || fails+=("pasteHash ${pasteHash:-missing}, host $TILEBANK_HASH")
|
||||||
|
[ "$refused" = "$wantRefused" ] || fails+=("refused ${refused:-missing}, want $wantRefused")
|
||||||
|
if [ "${#fails[@]}" -gt 0 ]; then
|
||||||
|
why="$(printf '%s; ' "${fails[@]}")"
|
||||||
|
echo "$TILEBANK_GATE: FAIL - ${why%; }" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "$TILEBANK_GATE: PASS (own bake pixel-exact both ways, hash $TILEBANK_HASH = host; $wantRefused other targets refused)"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# tileBankNeedBuild PROGRAM DATADIR BUILD-HINT -- exit 2 unless the port's
|
||||||
|
# TILEBANK program and its DATA/tilebank are there.
|
||||||
|
tileBankNeedBuild() {
|
||||||
|
local what
|
||||||
|
|
||||||
|
for what in "$1" "$2/own.tbk" "$2/expect.bin"; do
|
||||||
|
if [ ! -e "$what" ]; then
|
||||||
|
echo "$TILEBANK_GATE: $what is missing; run: $3" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# tileBankReference -- run the host gate, scripts/check-tilebank.sh, which must
|
||||||
|
# pass (its pixel checks are what make its hash worth matching), and set
|
||||||
|
# TILEBANK_HASH the surface hash of its to-surface load of its own bake
|
||||||
|
# TILEBANK_OTHERS how many targets besides any one port assetbake knows
|
||||||
|
# Exits 2 without a host C compiler, 1 if the host gate fails.
|
||||||
|
tileBankReference() {
|
||||||
|
local out
|
||||||
|
|
||||||
|
if ! command -v "${CC:-cc}" > /dev/null; then
|
||||||
|
echo "$TILEBANK_GATE: no host C compiler (${CC:-cc}) for the reference run of scripts/check-tilebank.sh" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if ! out="$("$JOEY_REPO/scripts/check-tilebank.sh" 2>&1)"; then
|
||||||
|
echo "$out" | tail -15 >&2
|
||||||
|
echo "$TILEBANK_GATE: FAIL - the host reference, scripts/check-tilebank.sh, failed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
TILEBANK_HASH="$(sed -n 's/.*to-surface hash \([0-9A-F]\{8\}\).*/\1/p' <<< "$out" | head -1)"
|
||||||
|
if [ -z "$TILEBANK_HASH" ]; then
|
||||||
|
echo "$TILEBANK_GATE: FAIL - scripts/check-tilebank.sh printed no to-surface hash" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
TILEBANK_OTHERS="$(python3 - "$JOEY_REPO/tools/assetbake" <<'PY'
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, sys.argv[1])
|
||||||
|
from assetbake import TARGET_NAMES
|
||||||
|
print(len(TARGET_NAMES) - 1)
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
}
|
||||||
|
|
@ -32,7 +32,7 @@ x68kAddTree() {
|
||||||
# (~900 KB, in subdirectories only deltree can free): nothing a JoeyLib
|
# (~900 KB, in subdirectories only deltree can free): nothing a JoeyLib
|
||||||
# program needs -- except \SYS\RSDRV.SYS, Human68k's RS-232C driver, which
|
# program needs -- except \SYS\RSDRV.SYS, Human68k's RS-232C driver, which
|
||||||
# comes back: the stock CONFIG.SYS loads it (DEVICE = \SYS\RSDRV.SYS), and
|
# comes back: the stock CONFIG.SYS loads it (DEVICE = \SYS\RSDRV.SYS), and
|
||||||
# with it the serial add-on runs above 9600 bps and does RTS/CTS.
|
# the serial add-on requires it (src/x68000/serial.c).
|
||||||
x68kBootDisk() {
|
x68kBootDisk() {
|
||||||
local out="$1"
|
local out="$1"
|
||||||
local name
|
local name
|
||||||
|
|
@ -44,10 +44,11 @@ x68kBootDisk() {
|
||||||
for name in SYS HIS BIN BASIC2 ASK ETC; do
|
for name in SYS HIS BIN BASIC2 ASK ETC; do
|
||||||
x68kXdf deltree "$out" "$name" >/dev/null 2>&1 || true
|
x68kXdf deltree "$out" "$name" >/dev/null 2>&1 || true
|
||||||
done
|
done
|
||||||
# Optional: without it the serial add-on keeps the ROM's limits.
|
if ! x68kXdf extract "$X68K_SYSTEM_DISK" SYS/RSDRV.SYS "$out.rsdrv" >/dev/null 2>&1; then
|
||||||
if x68kXdf extract "$X68K_SYSTEM_DISK" SYS/RSDRV.SYS "$out.rsdrv" >/dev/null 2>&1; then
|
echo "x68kBootDisk: $X68K_SYSTEM_DISK has no SYS/RSDRV.SYS, which the serial add-on requires" >&2
|
||||||
x68kXdf add "$out" "$out.rsdrv" SYS/RSDRV.SYS >/dev/null || return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
x68kXdf add "$out" "$out.rsdrv" SYS/RSDRV.SYS >/dev/null || return 1
|
||||||
rm -f "$out.rsdrv"
|
rm -f "$out.rsdrv"
|
||||||
{ printf '%s\r\n' "$@"; printf '\x1a'; } > "$out.autoexec"
|
{ printf '%s\r\n' "$@"; printf '\x1a'; } > "$out.autoexec"
|
||||||
x68kXdf add "$out" "$out.autoexec" AUTOEXEC.BAT >/dev/null || return 1
|
x68kXdf add "$out" "$out.autoexec" AUTOEXEC.BAT >/dev/null || return 1
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,17 @@ freeBlocks() {
|
||||||
| grep -oE 'Free : [0-9]+' | grep -oE '[0-9]+' | head -1
|
| grep -oE 'Free : [0-9]+' | grep -oE '[0-9]+' | head -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# addDataFile FILE FOLDER -- add FILE to FOLDER on the volume as a plain ProDOS
|
||||||
|
# BIN ($06) file under its upper-cased name. GS/OS ProDOS lookups are
|
||||||
|
# case-insensitive, so a program's lowercase fopen paths still match.
|
||||||
|
addDataFile() {
|
||||||
|
local base
|
||||||
|
base=$(basename "$1")
|
||||||
|
cp "$1" "$work/${base^^}#060000"
|
||||||
|
"$CADIUS" ADDFILE "$OUT" "$2" "$work/${base^^}#060000" >/dev/null
|
||||||
|
rm -f "$work/${base^^}#060000"
|
||||||
|
}
|
||||||
|
|
||||||
added=()
|
added=()
|
||||||
skipped=()
|
skipped=()
|
||||||
for name in "${order[@]}"; do
|
for name in "${order[@]}"; do
|
||||||
|
|
@ -112,49 +123,43 @@ make-iigs-disk.sh: WARNING - NTPPLAYER is NOT on $OUT.
|
||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Space Taxi runtime data: the game fopen()s a DATA/ tree (levels/*.dat,
|
# Space Taxi runtime data: the game fopen()s a DATA/ tree -- levels/*.dat,
|
||||||
# tiles/tbank*.tbk, sprites/sprites.spr, font.tbk -- see stRender.c /
|
# sprites/staxicels.spc, the optional sprites/levelNN.spc and speech.bin
|
||||||
# stLevel.c). Packed only when STAXI made it onto the disk, from the IIgs
|
# (stLevel.c, stRender.c, stAudio.c); tiles and the font are C64 data
|
||||||
# asset bake at examples/spacetaxi/generated/iigs (levels and .spr are
|
# compiled into the binary. Packed only when STAXI made it onto the disk,
|
||||||
# cross-target; .tbk banks are IIgs-baked -- assetLoad.c validates the
|
# from the IIgs bake at examples/spacetaxi/generated/iigs, and ONLY those
|
||||||
# header target byte). GS/OS ProDOS lookups are case-insensitive, so the
|
# files: the bake directory also holds intermediates (the .spr the .spc
|
||||||
# uppercase ProDOS names match the game's lowercase fopen paths.
|
# compile from) and, in an old tree, art the game stopped loading.
|
||||||
staxiData="$repo/examples/spacetaxi/generated/iigs"
|
staxiData="$repo/examples/spacetaxi/generated/iigs"
|
||||||
if [[ " ${added[*]:-} " == *" STAXI "* ]]; then
|
if [[ " ${added[*]:-} " == *" STAXI "* ]]; then
|
||||||
if [ -d "$staxiData" ]; then
|
if [ -d "$staxiData" ]; then
|
||||||
"$CADIUS" CREATEFOLDER "$OUT" "/$VOL/DATA" >/dev/null
|
"$CADIUS" CREATEFOLDER "$OUT" "/$VOL/DATA" >/dev/null
|
||||||
"$CADIUS" CREATEFOLDER "$OUT" "/$VOL/DATA/LEVELS" >/dev/null
|
"$CADIUS" CREATEFOLDER "$OUT" "/$VOL/DATA/LEVELS" >/dev/null
|
||||||
"$CADIUS" CREATEFOLDER "$OUT" "/$VOL/DATA/TILES" >/dev/null
|
|
||||||
"$CADIUS" CREATEFOLDER "$OUT" "/$VOL/DATA/SPRITES" >/dev/null
|
"$CADIUS" CREATEFOLDER "$OUT" "/$VOL/DATA/SPRITES" >/dev/null
|
||||||
dataAdded=0
|
dataAdded=0
|
||||||
while IFS= read -r f; do
|
while IFS= read -r f; do
|
||||||
rel=${f#"$staxiData/"}
|
rel=${f#"$staxiData/"}
|
||||||
dir=$(dirname "$rel")
|
dir=$(dirname "$rel")
|
||||||
base=$(basename "$rel")
|
|
||||||
dst="/$VOL/DATA"
|
dst="/$VOL/DATA"
|
||||||
if [ "$dir" != "." ]; then
|
if [ "$dir" != "." ]; then
|
||||||
dst="/$VOL/DATA/${dir^^}"
|
dst="/$VOL/DATA/${dir^^}"
|
||||||
fi
|
fi
|
||||||
cp "$f" "$work/${base^^}#060000"
|
addDataFile "$f" "$dst"
|
||||||
"$CADIUS" ADDFILE "$OUT" "$dst" "$work/${base^^}#060000" >/dev/null
|
|
||||||
rm -f "$work/${base^^}#060000"
|
|
||||||
dataAdded=$((dataAdded + 1))
|
dataAdded=$((dataAdded + 1))
|
||||||
done < <(find "$staxiData" -type f -not -path '*/sprites/level*.spc' | sort)
|
done < <(find "$staxiData/levels" -type f -name '*.dat' | sort
|
||||||
|
printf '%s\n' "$staxiData/sprites/staxicels.spc" "$staxiData/speech.bin")
|
||||||
# The per-level hook-sprite banks are optional (the game JIT-compiles
|
# The per-level hook-sprite banks are optional (the game JIT-compiles
|
||||||
# a level's cels at entry when its bank is missing) and large, so
|
# a level's cels at entry when its bank is missing) and large, so
|
||||||
# they go on last and only while the floppy has room for them.
|
# they go on last and only while the floppy has room for them.
|
||||||
levelSkipped=0
|
levelSkipped=0
|
||||||
while IFS= read -r f; do
|
while IFS= read -r f; do
|
||||||
base=$(basename "$f")
|
|
||||||
sz=$(stat -c%s "$f")
|
sz=$(stat -c%s "$f")
|
||||||
need=$(( (sz + 511) / 512 + (sz + 131071) / 131072 + 2 ))
|
need=$(( (sz + 511) / 512 + (sz + 131071) / 131072 + 2 ))
|
||||||
if [ "$need" -gt "$(freeBlocks)" ]; then
|
if [ "$need" -gt "$(freeBlocks)" ]; then
|
||||||
levelSkipped=$((levelSkipped + 1))
|
levelSkipped=$((levelSkipped + 1))
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
cp "$f" "$work/${base^^}#060000"
|
addDataFile "$f" "/$VOL/DATA/SPRITES"
|
||||||
"$CADIUS" ADDFILE "$OUT" "/$VOL/DATA/SPRITES" "$work/${base^^}#060000" >/dev/null
|
|
||||||
rm -f "$work/${base^^}#060000"
|
|
||||||
dataAdded=$((dataAdded + 1))
|
dataAdded=$((dataAdded + 1))
|
||||||
done < <(find "$staxiData" -type f -path '*/sprites/level*.spc' | sort)
|
done < <(find "$staxiData" -type f -path '*/sprites/level*.spc' | sort)
|
||||||
added+=("DATA[$dataAdded files]")
|
added+=("DATA[$dataAdded files]")
|
||||||
|
|
@ -166,6 +171,26 @@ if [[ " ${added[*]:-} " == *" STAXI "* ]]; then
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# The tile-bank example's DATA/tilebank, staged by make/iigs.mk from the host
|
||||||
|
# gate's fixture (tests/host/tileBankStage.py): its own bake, every other
|
||||||
|
# target's bake (each must be refused) and the expected pixels. Packed only
|
||||||
|
# when TILEBANK made it onto the disk. CREATEFOLDER makes the missing parent
|
||||||
|
# and leaves a DATA folder Space Taxi's data already made alone.
|
||||||
|
tileBankData="$BINDIR/DATA/tilebank"
|
||||||
|
if [[ " ${added[*]:-} " == *" TILEBANK "* ]]; then
|
||||||
|
if [ -d "$tileBankData" ]; then
|
||||||
|
"$CADIUS" CREATEFOLDER "$OUT" "/$VOL/DATA/TILEBANK" >/dev/null
|
||||||
|
tileBankAdded=0
|
||||||
|
for f in "$tileBankData"/*; do
|
||||||
|
addDataFile "$f" "/$VOL/DATA/TILEBANK"
|
||||||
|
tileBankAdded=$((tileBankAdded + 1))
|
||||||
|
done
|
||||||
|
added+=("DATA/TILEBANK[$tileBankAdded files]")
|
||||||
|
else
|
||||||
|
echo " WARNING: TILEBANK packed without its DATA/tilebank ($tileBankData missing)" >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
echo "iigs-disk: $OUT (volume /$VOL)"
|
echo "iigs-disk: $OUT (volume /$VOL)"
|
||||||
echo " added: ${added[*]:-none}"
|
echo " added: ${added[*]:-none}"
|
||||||
if [ ${#skipped[@]} -gt 0 ]; then
|
if [ ${#skipped[@]} -gt 0 ]; then
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,68 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# verify-all.sh - Run one kind of gate on every port and summarise.
|
# verify-all.sh - Run JoeyLib's gates, one kind or all of them, and summarise.
|
||||||
#
|
#
|
||||||
# Driven by `make verify-audio` and `make verify-serial`.
|
# Driven by `make verify` (all), `make verify-audio` and `make verify-serial`.
|
||||||
#
|
#
|
||||||
# audio: five gates, two kinds:
|
# Kinds, in the order `all` runs them:
|
||||||
# iigs DOC wavetable + sound IRQ vector (register tap)
|
# host the shared layer on the host, no emulator: the new-port
|
||||||
# x68000 YM2151 noise + PPI port C + ADPCM rate (register tap)
|
# template (blank), input, millis, save, serial, sfxmix, tilebank
|
||||||
# atarist YM2149 write trace, with the pc (register tap)
|
# golden UBER on each port, its 51 per-op surface hashes against
|
||||||
# amiga recorded sound, four loudness phases (Paula has no tap)
|
# tests/goldens/uber (x68000 against the IIgs's, its own gate)
|
||||||
# dos recorded sound, four loudness phases (SB has no tap)
|
# visual iigs: every launchable example on joey.2mg boots and draws
|
||||||
|
# tilebank each port: examples/tilebank loads the port's own .tbk bake
|
||||||
|
# of the host fixture both ways, pixel-exact and with the host
|
||||||
|
# gate's surface hash, and refuses every other target's bake (the
|
||||||
|
# host half is check-tilebank.sh under `host`)
|
||||||
|
# input each port: a typed string reaches jlInputGetChar byte-exact
|
||||||
|
# audio five gates, two kinds:
|
||||||
|
# iigs DOC wavetable + sound IRQ vector (register tap)
|
||||||
|
# x68000 YM2151 noise + PPI port C + ADPCM rate (register tap)
|
||||||
|
# atarist YM2149 write trace, with the pc (register tap)
|
||||||
|
# amiga recorded sound, four loudness phases (Paula has no tap)
|
||||||
|
# dos recorded sound, four loudness phases (SB has no tap)
|
||||||
|
# serial the host check of the shared layer, then each port's loopback
|
||||||
|
# (banner out, probe echoed back):
|
||||||
|
# host scripts/check-serial.sh (no emulator)
|
||||||
|
# iigs SCC modem port -> MAME null_modem
|
||||||
|
# x68000 SCC channel A -> MAME null_modem
|
||||||
|
# atarist Modem port -> Hatari pty
|
||||||
|
# amiga serial.device -> FS-UAE TCP
|
||||||
|
# dos COM1 -> DOSBox nullmodem
|
||||||
|
# mouse x68000: deltas, clamps and buttons through the IOCS mouse
|
||||||
|
# (and with serial streaming both ways on the same SCC)
|
||||||
|
# save iigs: a SAVES/ blob written and read back through GS/OS
|
||||||
|
# loss iigs: 4096 bytes through full-screen presents, lossless on the
|
||||||
|
# modem port at 9600 and a Super Serial Card at 2400
|
||||||
|
# disk iigs: the one-disk STAXI (ROM 3 and ROM 01) and AGI images boot
|
||||||
|
# and draw
|
||||||
#
|
#
|
||||||
# serial: the host check of the shared layer, then each port's loopback
|
# `all` first BUILDS every selected port (make <port>, and iigs-disk plus the
|
||||||
# (banner out, probe echoed back):
|
# loss probes and game disks for the IIgs), stopping on a build failure, then
|
||||||
# host scripts/check-serial.sh (no emulator)
|
# runs every kind. A gate for a port that was not selected is left out.
|
||||||
# iigs SCC modem port -> MAME null_modem
|
|
||||||
# x68000 SCC channel A -> MAME null_modem
|
|
||||||
# atarist Modem port -> Hatari pty
|
|
||||||
# amiga serial.device -> FS-UAE TCP
|
|
||||||
# dos COM1 -> DOSBox nullmodem
|
|
||||||
#
|
#
|
||||||
# SEQUENTIAL, deliberately. The two recording audio gates drive the emulator on
|
# SEQUENTIAL, deliberately. The two recording audio gates drive the emulator on
|
||||||
# wall-clock sleeps and grade fixed time windows of the capture, so a
|
# wall-clock sleeps and grade fixed time windows of the capture, so a
|
||||||
# second emulator competing for CPU can slide a phase boundary and fail a
|
# second emulator competing for CPU can slide a phase boundary and fail a
|
||||||
# gate that is actually fine. Running five emulators at once also makes
|
# gate that is actually fine. Running several emulators at once also makes
|
||||||
# the timing-sensitive register gates noisier. The whole set is ~2.5
|
# the timing-sensitive register gates noisier.
|
||||||
# minutes; parallelism is not worth the flakiness.
|
|
||||||
#
|
#
|
||||||
# Exit codes, distinguishing "it works" from "it did not run":
|
# Exit codes, distinguishing "it works" from "it did not run":
|
||||||
# 0 every selected gate PASSED
|
# 0 every selected gate PASSED
|
||||||
# 1 at least one gate FAILED (a real defect)
|
# 1 at least one gate FAILED (a real defect), or an `all` build failed
|
||||||
# 3 no failures, but at least one gate SKIPPED for a missing
|
# 3 no failures, but at least one gate SKIPPED for a missing
|
||||||
# prerequisite -- an unbuilt port or absent emulator. Non-zero on
|
# prerequisite -- an unbuilt port or absent emulator. Non-zero on
|
||||||
# purpose: a run that quietly tested nothing must not look green.
|
# purpose: a run that quietly tested nothing must not look green.
|
||||||
|
# A gate reached through make cannot tell a skip from a failure (make
|
||||||
|
# exits 2 for both), so those count as failures; `all` builds their
|
||||||
|
# prerequisites first, so a missing one is a real problem anyway.
|
||||||
#
|
#
|
||||||
# Each gate already reports its own evidence line; this only adds the
|
# Each gate already reports its own evidence line; this only adds the
|
||||||
# roll-up. Gate stdout/stderr is echoed as it happens, so a failure shows
|
# roll-up. Gate stdout/stderr is echoed as it happens, so a failure shows
|
||||||
# its diagnosis here and not just a verdict.
|
# its diagnosis here and not just a verdict.
|
||||||
#
|
#
|
||||||
# Usage: scripts/verify-all.sh audio|serial [port ...]
|
# Usage: scripts/verify-all.sh KIND|all [port ...]
|
||||||
# with no ports, all of that kind's. Otherwise any of:
|
# with no ports, every port the kind has. Ports: host iigs
|
||||||
# iigs x68000 atarist amiga dos (and host, for serial)
|
# x68000 atarist amiga dos
|
||||||
# Env: VERIFY_ALL_LOG also append the full transcript to this file
|
# Env: VERIFY_ALL_LOG also append the full transcript to this file
|
||||||
# Requires: whatever each selected gate requires; missing ones SKIP.
|
# Requires: whatever each selected gate requires; missing ones SKIP.
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
@ -49,77 +72,158 @@ kind=${1:-}
|
||||||
shift || true
|
shift || true
|
||||||
label="verify-all $kind"
|
label="verify-all $kind"
|
||||||
|
|
||||||
case $kind in
|
KINDS="host golden visual tilebank input audio serial mouse save loss disk"
|
||||||
audio) ALL_PORTS="iigs x68000 atarist amiga dos" ;;
|
ALL_PORTS="host iigs x68000 atarist amiga dos"
|
||||||
serial) ALL_PORTS="host iigs x68000 atarist amiga dos" ;;
|
# UBER on the Amiga needs longer than bench-amiga.sh's interactive default.
|
||||||
*) echo "usage: scripts/verify-all.sh audio|serial [port ...]" >&2; exit 2 ;;
|
AMIGA_UBER_SECONDS=400
|
||||||
esac
|
|
||||||
ports=${*:-$ALL_PORTS}
|
|
||||||
|
|
||||||
|
# kind port name command...: every gate, in run order within its kind.
|
||||||
|
GATES=$(cat <<EOF
|
||||||
|
host host blank scripts/check-blank.sh
|
||||||
|
host host input scripts/check-input.sh
|
||||||
|
host host millis scripts/check-millis.sh
|
||||||
|
host host save scripts/check-save.sh
|
||||||
|
host host serial scripts/check-serial.sh
|
||||||
|
host host sfxmix scripts/check-sfxmix.sh
|
||||||
|
host host tilebank scripts/check-tilebank.sh
|
||||||
|
golden iigs uber scripts/bench-iigs.sh && tools/diff-uber-hashes tests/goldens/uber/iigs.txt build/iigs/bin/joeylog.txt
|
||||||
|
golden x68000 uber scripts/verify-x68000-golden.sh
|
||||||
|
golden atarist uber scripts/bench-atarist.sh && tools/diff-uber-hashes tests/goldens/uber/atarist.txt build/atarist/bin/joeylog.txt
|
||||||
|
golden amiga uber TIMEOUT=$AMIGA_UBER_SECONDS scripts/bench-amiga.sh && tools/diff-uber-hashes tests/goldens/uber/amiga.txt build/amiga/bin/joeylog.txt
|
||||||
|
golden dos uber scripts/bench-dos.sh && tools/diff-uber-hashes tests/goldens/uber/dos.txt build/dos/bin/JOEYLOG.TXT
|
||||||
|
visual iigs examples scripts/verify-iigs-all.sh
|
||||||
|
tilebank iigs tilebank make -s -f make/iigs.mk iigs-verify-tilebank || exit 1
|
||||||
|
tilebank x68000 tilebank scripts/verify-x68000-tilebank.sh
|
||||||
|
tilebank atarist tilebank scripts/verify-atarist-tilebank.sh
|
||||||
|
tilebank amiga tilebank scripts/verify-amiga-tilebank.sh
|
||||||
|
tilebank dos tilebank scripts/verify-dos-tilebank.sh
|
||||||
|
input iigs input scripts/verify-iigs-input.sh
|
||||||
|
input x68000 input scripts/verify-x68000-input.sh
|
||||||
|
input atarist input scripts/verify-atarist-input.sh
|
||||||
|
input amiga input scripts/verify-amiga-input.sh
|
||||||
|
input dos input scripts/verify-dos-input.sh
|
||||||
|
audio iigs audio scripts/verify-iigs-audio.sh staxi
|
||||||
|
audio x68000 audio scripts/verify-x68000-audio.sh
|
||||||
|
audio atarist audio scripts/verify-atarist-audio.sh
|
||||||
|
audio amiga audio scripts/verify-amiga-audio.sh
|
||||||
|
audio dos audio scripts/verify-dos-audio.sh
|
||||||
|
serial host serial scripts/check-serial.sh
|
||||||
|
serial iigs serial scripts/verify-iigs-serial.sh
|
||||||
|
serial x68000 serial scripts/verify-x68000-serial.sh
|
||||||
|
serial atarist serial scripts/verify-atarist-serial.sh
|
||||||
|
serial amiga serial scripts/verify-amiga-serial.sh
|
||||||
|
serial dos serial scripts/verify-dos-serial.sh
|
||||||
|
mouse x68000 mouse scripts/verify-x68000-mouse.sh
|
||||||
|
mouse x68000 sermouse scripts/verify-x68000-serial-mouse.sh
|
||||||
|
save iigs save make -s -f make/iigs.mk iigs-verify-save || exit 1
|
||||||
|
loss iigs loss make -s -f make/iigs.mk iigs-verify-serial-loss || exit 1
|
||||||
|
disk iigs staxi make -s -f make/iigs.mk iigs-verify-game-disk GAME=STAXI || exit 1
|
||||||
|
disk iigs staxi01 GAME_VERIFY_MACHINE=apple2gsr1 scripts/verify-iigs-game-disk.sh build/iigs/bin/STAXI.2mg
|
||||||
|
disk iigs agi make -s -f make/iigs.mk iigs-game-disk-agi || exit 1; scripts/verify-iigs-game-disk.sh build/iigs/bin/AGI.2mg 2 8M
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
case " all $KINDS " in
|
||||||
|
*" $kind "*) ;;
|
||||||
|
*) echo "usage: scripts/verify-all.sh all|$(echo "$KINDS" | tr ' ' '|') [port ...]" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
if [ "$kind" = all ]; then
|
||||||
|
kinds=$KINDS
|
||||||
|
kindPorts=$ALL_PORTS
|
||||||
|
else
|
||||||
|
kinds=$kind
|
||||||
|
kindPorts=$(echo "$GATES" | awk -v k="$kind" '$1 == k { print $2 }' | awk '!seen[$0]++' | tr '\n' ' ')
|
||||||
|
fi
|
||||||
|
ports=${*:-$kindPorts}
|
||||||
for p in $ports; do
|
for p in $ports; do
|
||||||
case " $ALL_PORTS " in
|
case " $kindPorts " in
|
||||||
*" $p "*) ;;
|
*" $p "*) ;;
|
||||||
*) echo "$label: unknown port '$p' (choose from: $ALL_PORTS)" >&2; exit 2 ;;
|
*) echo "$label: unknown port '$p' (choose from: $kindPorts)" >&2; exit 2 ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
gateFor() { # $1 = port -> script + args
|
# Echo a command's output as it happens, and into VERIFY_ALL_LOG if set;
|
||||||
case $kind:$1 in
|
# returns the command's exit code. stdin is /dev/null so no gate can read
|
||||||
audio:iigs) echo "scripts/verify-iigs-audio.sh staxi" ;;
|
# the rest of the gate list.
|
||||||
audio:*) echo "scripts/verify-$1-audio.sh" ;;
|
runLogged() {
|
||||||
serial:host) echo "scripts/check-serial.sh" ;;
|
if [ -n "${VERIFY_ALL_LOG:-}" ]; then
|
||||||
serial:*) echo "scripts/verify-$1-serial.sh" ;;
|
( cd "$repo" && bash -c "$1" ) < /dev/null 2>&1 | tee -a "$VERIFY_ALL_LOG"
|
||||||
esac
|
return "${PIPESTATUS[0]}"
|
||||||
|
fi
|
||||||
|
( cd "$repo" && bash -c "$1" ) < /dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
passed=""
|
# The build step of `all`: each selected port, then the IIgs extras its
|
||||||
failed=""
|
# gates need (make iigs builds no examples; iigs-disk does).
|
||||||
skipped=""
|
buildPorts() {
|
||||||
declare -A elapsed
|
local port
|
||||||
|
local cmd
|
||||||
|
|
||||||
for port in $ports; do
|
for port in $ports; do
|
||||||
gate=$(gateFor "$port")
|
case $port in
|
||||||
echo
|
host) continue ;;
|
||||||
echo "=== $label: $port ($gate) ==="
|
iigs) cmd="make -f make/iigs.mk iigs-disk $repo/build/iigs/bin/SCCPROBE $repo/build/iigs/bin/SERLOSS $repo/build/iigs/bin/SERLOSSS" ;;
|
||||||
start=$SECONDS
|
*) cmd="make $port" ;;
|
||||||
if [ -n "${VERIFY_ALL_LOG:-}" ]; then
|
esac
|
||||||
( cd "$repo" && $gate ) 2>&1 | tee -a "$VERIFY_ALL_LOG"
|
echo
|
||||||
rc=${PIPESTATUS[0]}
|
echo "=== $label: build $port ($cmd) ==="
|
||||||
else
|
if ! runLogged "$cmd"; then
|
||||||
( cd "$repo" && $gate )
|
echo "$label: FAIL - building $port failed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$kind" = all ]; then
|
||||||
|
buildPorts
|
||||||
|
fi
|
||||||
|
|
||||||
|
gateNames=()
|
||||||
|
gateVerdicts=()
|
||||||
|
gateSeconds=()
|
||||||
|
passed=0
|
||||||
|
failed=0
|
||||||
|
skipped=0
|
||||||
|
|
||||||
|
for k in $kinds; do
|
||||||
|
while read -r gKind gPort gName gCmd; do
|
||||||
|
[ "$gKind" = "$k" ] || continue
|
||||||
|
case " $ports " in *" $gPort "*) ;; *) continue ;; esac
|
||||||
|
echo
|
||||||
|
echo "=== $label: $gKind $gPort $gName ($gCmd) ==="
|
||||||
|
start=$SECONDS
|
||||||
|
runLogged "$gCmd"
|
||||||
rc=$?
|
rc=$?
|
||||||
fi
|
case $rc in
|
||||||
elapsed[$port]=$((SECONDS - start))
|
0) verdict=PASS; passed=$((passed + 1)) ;;
|
||||||
case $rc in
|
2) verdict="SKIP (prerequisite missing)"; skipped=$((skipped + 1)) ;;
|
||||||
0) passed="$passed $port" ;;
|
*) verdict=FAIL; failed=$((failed + 1)) ;;
|
||||||
2) skipped="$skipped $port" ;;
|
esac
|
||||||
*) failed="$failed $port" ;;
|
gateNames+=("$gKind $gPort $gName")
|
||||||
esac
|
gateVerdicts+=("$verdict")
|
||||||
|
gateSeconds+=("$((SECONDS - start))")
|
||||||
|
done <<< "$GATES"
|
||||||
done
|
done
|
||||||
|
|
||||||
count() { set -- $1; echo $#; }
|
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "================ $label summary ================"
|
echo "================ $label summary ================"
|
||||||
for port in $ports; do
|
for i in "${!gateNames[@]}"; do
|
||||||
case " $passed " in *" $port "*) verdict=PASS ;; *)
|
printf " %-26s %-28s %4ds\n" "${gateNames[$i]}" "${gateVerdicts[$i]}" "${gateSeconds[$i]}"
|
||||||
case " $skipped " in *" $port "*) verdict="SKIP (prerequisite missing)" ;; *)
|
|
||||||
verdict=FAIL ;;
|
|
||||||
esac ;;
|
|
||||||
esac
|
|
||||||
printf " %-8s %-28s %3ds\n" "$port" "$verdict" "${elapsed[$port]}"
|
|
||||||
done
|
done
|
||||||
echo " ----"
|
echo " ----"
|
||||||
printf " %d passed, %d failed, %d skipped\n" \
|
printf " %d passed, %d failed, %d skipped\n" "$passed" "$failed" "$skipped"
|
||||||
"$(count "$passed")" "$(count "$failed")" "$(count "$skipped")"
|
|
||||||
|
|
||||||
if [ -n "$failed" ]; then
|
if [ "$failed" -gt 0 ]; then
|
||||||
echo "$label: FAIL -$failed" >&2
|
echo "$label: FAIL" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [ -n "$skipped" ]; then
|
if [ "$skipped" -gt 0 ]; then
|
||||||
echo "$label: INCOMPLETE - skipped$skipped (build those ports, then re-run)" >&2
|
echo "$label: INCOMPLETE - some gates skipped (build those ports, then re-run)" >&2
|
||||||
exit 3
|
exit 3
|
||||||
fi
|
fi
|
||||||
echo "$label: PASS (all $(count "$passed") $kind gates)"
|
if [ "$passed" -eq 0 ]; then
|
||||||
|
echo "$label: nothing selected" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
echo "$label: PASS (all $passed gates)"
|
||||||
exit 0
|
exit 0
|
||||||
|
|
|
||||||
81
scripts/verify-amiga-tilebank.sh
Executable file
81
scripts/verify-amiga-tilebank.sh
Executable file
|
|
@ -0,0 +1,81 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-amiga-tilebank.sh - Tile-bank gate for the Amiga: proves that
|
||||||
|
# assetbake's amiga encoder (plane-major: 8 rows of plane 0, then plane 1, ...)
|
||||||
|
# writes exactly the layout the Amiga's native tile paste
|
||||||
|
# (src/m68k/planarTile.h via src/amiga/amigaTile.h) reads, and that the Amiga
|
||||||
|
# build refuses a bank baked for any other target.
|
||||||
|
#
|
||||||
|
# Boots an A500 (real Kickstart) in FS-UAE under Xvfb from a scratch directory
|
||||||
|
# hard drive whose startup-sequence runs TileBank (examples/tilebank), with
|
||||||
|
# the DATA/tilebank make/amiga.mk staged from the host gate's fixture beside
|
||||||
|
# it. The program loads its own bake through jlTileBankLoadToSurface and
|
||||||
|
# through jlTileBankLoad + jlTilePaste, checks every pixel of both against the
|
||||||
|
# PNG, hashes both, and tries every other target's bake; its TILEBANK line in
|
||||||
|
# joeylog.txt must say PASS with both hashes equal to the host gate's
|
||||||
|
# (scripts/check-tilebank.sh, run here for the value) and every other target
|
||||||
|
# refused. FS-UAE is stopped as soon as the line appears.
|
||||||
|
#
|
||||||
|
# Usage: scripts/verify-amiga-tilebank.sh
|
||||||
|
# Env: TILEBANK_DATA_DIR stage this DATA/tilebank instead (negative controls)
|
||||||
|
# Requires: fs-uae, the Kickstart, Xvfb, a host cc; build/amiga/bin/TileBank
|
||||||
|
# and its DATA/tilebank (make amiga).
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
source "$repo/scripts/lib/tileBankGate.sh"
|
||||||
|
joeyNeed FSUAE fsuae
|
||||||
|
joeyNeed KICKSTART_ROM kickstart
|
||||||
|
|
||||||
|
binDir=$repo/build/amiga/bin
|
||||||
|
dataDir=$(tileBankData "$binDir")
|
||||||
|
wall=180 # hard cap on FS-UAE, whatever happens to this script
|
||||||
|
lineSeconds=150 # a real-time boot plus the checks
|
||||||
|
|
||||||
|
tileBankNeedBuild "$binDir/TileBank" "$dataDir" "make amiga"
|
||||||
|
tileBankReference
|
||||||
|
|
||||||
|
work=$(mktemp -d -t joeylib-amiga-tilebank.XXXXXX)
|
||||||
|
guard=""
|
||||||
|
fsuae=""
|
||||||
|
xvfb=""
|
||||||
|
# 2>/dev/null: bash reports the SIGKILLed timeout wrapper as "Killed".
|
||||||
|
cleanup() {
|
||||||
|
joeyStop "$fsuae" "$guard" "$xvfb" 2> /dev/null
|
||||||
|
rm -rf "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
mkdir -p "$work/hd/s" "$work/hd/DATA"
|
||||||
|
cp "$binDir/TileBank" "$work/hd/"
|
||||||
|
cp -r "$dataDir" "$work/hd/DATA/tilebank"
|
||||||
|
echo ":TileBank" > "$work/hd/s/startup-sequence"
|
||||||
|
|
||||||
|
display=":$((RANDOM % 400 + 1300))"
|
||||||
|
Xvfb "$display" -screen 0 800x600x24 -nolisten tcp > /dev/null 2>&1 &
|
||||||
|
xvfb=$!
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# 2 MB of fast memory: a bare 512 KB A500 fails jlInit.
|
||||||
|
DISPLAY="$display" timeout -s KILL "$wall" "$FSUAE" \
|
||||||
|
--amiga_model=A500 \
|
||||||
|
--fast_memory=2048 \
|
||||||
|
--kickstart_file="$KICKSTART_ROM" \
|
||||||
|
--hard_drive_0="$work/hd" \
|
||||||
|
--hard_drive_0_label=JOEYLIB \
|
||||||
|
--automatic_input_grab=0 \
|
||||||
|
--fullscreen=0 \
|
||||||
|
--fade_out_duration=0 > "$work/fsuae.log" 2>&1 &
|
||||||
|
guard=$!
|
||||||
|
fsuae=$(tileBankChild "$guard")
|
||||||
|
|
||||||
|
log=$(tileBankAwaitLine "$work/hd" "$lineSeconds" "$guard")
|
||||||
|
joeyStop "$fsuae" "$guard" 2> /dev/null
|
||||||
|
fsuae=""
|
||||||
|
guard=""
|
||||||
|
if [ -z "$log" ]; then
|
||||||
|
echo "verify-amiga-tilebank: FAIL - no TILEBANK line within ${lineSeconds}s" >&2
|
||||||
|
tail -5 "$work/fsuae.log" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
tileBankJudge "$log"
|
||||||
70
scripts/verify-atarist-tilebank.sh
Executable file
70
scripts/verify-atarist-tilebank.sh
Executable file
|
|
@ -0,0 +1,70 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-atarist-tilebank.sh - Tile-bank gate for the Atari ST: proves that
|
||||||
|
# assetbake's atarist encoder (interleaved planes, 4 plane bytes per row)
|
||||||
|
# writes exactly the layout the ST's native tile paste (src/atarist/stTile.h)
|
||||||
|
# reads, and that the ST build refuses a bank baked for any other target.
|
||||||
|
#
|
||||||
|
# Runs TILEBANK.PRG (examples/tilebank) under Hatari from a scratch GEMDOS
|
||||||
|
# drive holding it and the DATA/tilebank make/atarist.mk staged from the host
|
||||||
|
# gate's fixture. The program loads its own bake through
|
||||||
|
# jlTileBankLoadToSurface and through jlTileBankLoad + jlTilePaste, checks
|
||||||
|
# every pixel of both against the PNG, hashes both, and tries every other
|
||||||
|
# target's bake; its TILEBANK log line must say PASS with both hashes equal
|
||||||
|
# to the host gate's (scripts/check-tilebank.sh, run here for the value) and
|
||||||
|
# every other target refused. Hatari is stopped as soon as the line appears.
|
||||||
|
#
|
||||||
|
# Usage: scripts/verify-atarist-tilebank.sh
|
||||||
|
# Env: TILEBANK_DATA_DIR stage this DATA/tilebank instead (negative controls)
|
||||||
|
# Requires: hatari, EmuTOS, a host cc; build/atarist/bin/TILEBANK.PRG and its
|
||||||
|
# DATA/tilebank (make atarist).
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
source "$repo/scripts/lib/tileBankGate.sh"
|
||||||
|
joeyNeed HATARI hatari
|
||||||
|
joeyNeed EMUTOS_IMG emutos
|
||||||
|
|
||||||
|
binDir=$repo/build/atarist/bin
|
||||||
|
dataDir=$(tileBankData "$binDir")
|
||||||
|
wall=150 # hard cap on Hatari, whatever happens to this script
|
||||||
|
lineSeconds=120 # boot plus the checks, fast-forwarded, take ~10 s
|
||||||
|
|
||||||
|
tileBankNeedBuild "$binDir/TILEBANK.PRG" "$dataDir" "make atarist"
|
||||||
|
tileBankReference
|
||||||
|
|
||||||
|
work=$(mktemp -d -t joeylib-st-tilebank.XXXXXX)
|
||||||
|
guard=""
|
||||||
|
hatari=""
|
||||||
|
# 2>/dev/null: bash reports the SIGKILLed timeout wrapper as "Killed".
|
||||||
|
cleanup() {
|
||||||
|
joeyStop "$hatari" "$guard" 2> /dev/null
|
||||||
|
rm -rf "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
mkdir -p "$work/c/DATA"
|
||||||
|
cp "$binDir/TILEBANK.PRG" "$work/c/"
|
||||||
|
cp -r "$dataDir" "$work/c/DATA/tilebank"
|
||||||
|
|
||||||
|
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy timeout -s KILL "$wall" "$HATARI" \
|
||||||
|
--tos "$EMUTOS_IMG" \
|
||||||
|
--harddrive "$work/c" \
|
||||||
|
--gemdos-drive C \
|
||||||
|
--drive-a off --drive-b off \
|
||||||
|
--sound off \
|
||||||
|
--fast-forward on \
|
||||||
|
--auto 'C:\TILEBANK.PRG' > "$work/hatari.log" 2>&1 &
|
||||||
|
guard=$!
|
||||||
|
hatari=$(tileBankChild "$guard")
|
||||||
|
|
||||||
|
log=$(tileBankAwaitLine "$work/c" "$lineSeconds" "$guard")
|
||||||
|
joeyStop "$hatari" "$guard" 2> /dev/null
|
||||||
|
hatari=""
|
||||||
|
guard=""
|
||||||
|
if [ -z "$log" ]; then
|
||||||
|
echo "verify-atarist-tilebank: FAIL - no TILEBANK line within ${lineSeconds}s" >&2
|
||||||
|
tail -5 "$work/hatari.log" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
tileBankJudge "$log"
|
||||||
75
scripts/verify-dos-tilebank.sh
Executable file
75
scripts/verify-dos-tilebank.sh
Executable file
|
|
@ -0,0 +1,75 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-dos-tilebank.sh - Tile-bank gate for DOS: proves that assetbake's
|
||||||
|
# dos encoder (chunky 4bpp, high nibble = left pixel) writes exactly the
|
||||||
|
# layout the DOS tile paste reads, and that the DOS build refuses a bank baked
|
||||||
|
# for any other target.
|
||||||
|
#
|
||||||
|
# Runs TILEBANK.EXE (examples/tilebank) in DOSBox Staging on the 386SX-16
|
||||||
|
# reference config, from a scratch C: holding it and the DATA/tilebank
|
||||||
|
# make/dos.mk staged from the host gate's fixture. The program loads its own
|
||||||
|
# bake through jlTileBankLoadToSurface and through jlTileBankLoad +
|
||||||
|
# jlTilePaste, checks every pixel of both against the PNG, hashes both, and
|
||||||
|
# tries every other target's bake; its TILEBANK line in JOEYLOG.TXT must say
|
||||||
|
# PASS with both hashes equal to the host gate's (scripts/check-tilebank.sh,
|
||||||
|
# run here for the value) and every other target refused. DOSBox is stopped
|
||||||
|
# as soon as the line appears.
|
||||||
|
#
|
||||||
|
# Usage: scripts/verify-dos-tilebank.sh
|
||||||
|
# Env: TILEBANK_DATA_DIR stage this DATA/tilebank instead (negative controls)
|
||||||
|
# Requires: DOSBox Staging, Xvfb, a host cc; build/dos/bin/TILEBANK.EXE and
|
||||||
|
# its DATA/tilebank (make dos).
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
source "$repo/scripts/lib/tileBankGate.sh"
|
||||||
|
joeyNeed DOSBOX dosbox
|
||||||
|
|
||||||
|
binDir=$repo/build/dos/bin
|
||||||
|
dataDir=$(tileBankData "$binDir")
|
||||||
|
conf=$repo/scripts/dosbox-386sx16.conf
|
||||||
|
wall=150 # hard cap on DOSBox, whatever happens to this script
|
||||||
|
lineSeconds=120 # DOS boot plus the checks at 386SX-16 speed
|
||||||
|
|
||||||
|
tileBankNeedBuild "$binDir/TILEBANK.EXE" "$dataDir" "make dos"
|
||||||
|
tileBankReference
|
||||||
|
|
||||||
|
work=$(mktemp -d -t joeylib-dos-tilebank.XXXXXX)
|
||||||
|
guard=""
|
||||||
|
dosbox=""
|
||||||
|
xvfb=""
|
||||||
|
# 2>/dev/null: bash reports the SIGKILLed timeout wrapper as "Killed".
|
||||||
|
cleanup() {
|
||||||
|
joeyStop "$dosbox" "$guard" "$xvfb" 2> /dev/null
|
||||||
|
rm -rf "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
mkdir -p "$work/c/DATA" "$work/xdg"
|
||||||
|
cp "$binDir/TILEBANK.EXE" "$work/c/"
|
||||||
|
cp -r "$dataDir" "$work/c/DATA/tilebank"
|
||||||
|
|
||||||
|
display=":$((RANDOM % 400 + 1300))"
|
||||||
|
Xvfb "$display" -screen 0 800x600x24 -nolisten tcp > /dev/null 2>&1 &
|
||||||
|
xvfb=$!
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# The mount is an explicit -c: DOSBox runs every -c line before it would
|
||||||
|
# auto-mount a trailing directory (see bench-dos.sh).
|
||||||
|
XDG_CONFIG_HOME="$work/xdg" DISPLAY="$display" SDL_AUDIODRIVER=dummy \
|
||||||
|
timeout -s KILL "$wall" "$DOSBOX" "${JOEY_DOSBOX_ISOLATE[@]}" \
|
||||||
|
--conf "$conf" \
|
||||||
|
-c "MOUNT C \"$work/c\"" -c "C:" -c "TILEBANK.EXE" > "$work/dosbox.log" 2>&1 &
|
||||||
|
guard=$!
|
||||||
|
dosbox=$(tileBankChild "$guard")
|
||||||
|
|
||||||
|
log=$(tileBankAwaitLine "$work/c" "$lineSeconds" "$guard")
|
||||||
|
joeyStop "$dosbox" "$guard" 2> /dev/null
|
||||||
|
dosbox=""
|
||||||
|
guard=""
|
||||||
|
if [ -z "$log" ]; then
|
||||||
|
echo "verify-dos-tilebank: FAIL - no TILEBANK line within ${lineSeconds}s" >&2
|
||||||
|
tail -5 "$work/dosbox.log" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
tileBankJudge "$log"
|
||||||
196
scripts/verify-iigs-tilebank.sh
Executable file
196
scripts/verify-iigs-tilebank.sh
Executable file
|
|
@ -0,0 +1,196 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-iigs-tilebank.sh - Tile-bank gate for the Apple IIgs: proves that
|
||||||
|
# assetbake's iigs encoder (chunky 4bpp, high nibble = left pixel) writes
|
||||||
|
# exactly the layout the IIgs tile paste reads, and that the IIgs build
|
||||||
|
# refuses a bank baked for any other target.
|
||||||
|
#
|
||||||
|
# Builds a one-example JOEYLIB disk (TILEBANK, examples/tilebank, with the
|
||||||
|
# DATA/tilebank make/iigs.mk staged from the host gate's fixture), boots GS/OS
|
||||||
|
# under headless MAME and launches TILEBANK from the Finder (verify-iigs-
|
||||||
|
# save.sh's keystroke timeline). The program loads its own bake through
|
||||||
|
# jlTileBankLoadToSurface and through jlTileBankLoad + jlTilePaste, checks
|
||||||
|
# every pixel of both against the PNG, hashes both, tries every other
|
||||||
|
# target's bake, writes one TILEBANK line to JOEYLOG.TXT (jlLogFlush: one
|
||||||
|
# open/write/close) and only then shows its verdict lamp. The Lua below waits
|
||||||
|
# for the lamp in the Super Hi-Res framebuffer and stops MAME; the line,
|
||||||
|
# extracted from the work disk with cadius, must say PASS with both hashes
|
||||||
|
# equal to the host gate's (scripts/check-tilebank.sh, run here for the
|
||||||
|
# value) and every other target refused, and the lamp must be the PASS
|
||||||
|
# colour.
|
||||||
|
#
|
||||||
|
# SHR geometry: 160 bytes per row at $E12000, high nibble = left pixel; the
|
||||||
|
# stage is the whole 320x200 screen. The lamp is examples/tilebank's: 64x64
|
||||||
|
# at (128, 68), colour 1 for PASS and 2 for FAIL, with FILL_COLOR (9) either
|
||||||
|
# side of it on its rows.
|
||||||
|
#
|
||||||
|
# Usage: scripts/verify-iigs-tilebank.sh
|
||||||
|
# (make -f make/iigs.mk iigs-verify-tilebank builds what it needs first)
|
||||||
|
# Env: TILEBANK_DATA_DIR stage this DATA/tilebank instead (negative controls)
|
||||||
|
# Requires: ./install.sh install iigs; MAME with the apple2gs ROM and the GS/OS
|
||||||
|
# system disk; a host cc; build/iigs/bin/TILEBANK and its
|
||||||
|
# DATA/tilebank.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
source "$repo/scripts/lib/tileBankGate.sh"
|
||||||
|
joeyNeed LLVM816_ROOT toolchain:iigs
|
||||||
|
joeyNeed CADIUS cadius
|
||||||
|
joeyNeed MAME mame
|
||||||
|
joeyNeed IIGS_ROMPATH iigsFirmware
|
||||||
|
joeyNeed GSOS_SYSTEM_DISK iigsFirmware
|
||||||
|
|
||||||
|
binDir=$repo/build/iigs/bin
|
||||||
|
dataDir=$(tileBankData "$binDir")
|
||||||
|
wall=900 # hard cap on MAME, whatever happens to this script
|
||||||
|
frames=24000 # emulated frames before the Lua gives up on the lamp
|
||||||
|
|
||||||
|
tileBankNeedBuild "$binDir/TILEBANK" "$dataDir" "make -f make/iigs.mk iigs-verify-tilebank"
|
||||||
|
tileBankReference
|
||||||
|
|
||||||
|
work=$(mktemp -d -t joeylib-iigs-tilebank.XXXXXX)
|
||||||
|
trap 'rm -rf "$work"' EXIT
|
||||||
|
|
||||||
|
# make-iigs-disk.sh packs BINDIR's TILEBANK and BINDIR/DATA/tilebank, so a
|
||||||
|
# scratch BINDIR carries whichever DATA/tilebank this run stages.
|
||||||
|
mkdir -p "$work/bin/DATA" "$work/log"
|
||||||
|
cp "$binDir/TILEBANK" "$work/bin/"
|
||||||
|
cp -r "$dataDir" "$work/bin/DATA/tilebank"
|
||||||
|
BINDIR="$work/bin" JOEY_DISK_EXAMPLES="TILEBANK" "$repo/scripts/make-iigs-disk.sh" "$work/joey.2mg" > /dev/null || exit 1
|
||||||
|
if ! "$CADIUS" CATALOG "$work/joey.2mg" 2> /dev/null | grep -qE '^ +OWN\.TBK +BIN'; then
|
||||||
|
echo "verify-iigs-tilebank: FAIL - the disk has no DATA/TILEBANK/OWN.TBK" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cp "$GSOS_SYSTEM_DISK" "$work/boot.po"
|
||||||
|
|
||||||
|
cat > "$work/tilebank.lua" <<LUA
|
||||||
|
local mem = manager.machine.devices[":maincpu"].spaces["program"]
|
||||||
|
local nat = manager.machine.natkeyboard
|
||||||
|
|
||||||
|
local FRAMES = $frames
|
||||||
|
local FIRST_LOOK = 3800 -- the Finder launch below is done by then
|
||||||
|
local SAMPLE_EVERY = 10 -- frames between lamp looks
|
||||||
|
local LAMP_STABLE = 3 -- looks in a row that must agree
|
||||||
|
local EXIT_SETTLE = 120 -- let the program settle before MAME writes the disk back
|
||||||
|
|
||||||
|
local LAMP_X, LAMP_Y, LAMP_W, LAMP_H = 128, 68, 64, 64
|
||||||
|
local PASS_COLOR, FAIL_COLOR, FILL_COLOR = 1, 2, 9
|
||||||
|
local MARGIN = 4 -- inside probes keep off the lamp's edge
|
||||||
|
local BESIDE = 8 -- outside probes sit this far off it
|
||||||
|
|
||||||
|
local frame = 0
|
||||||
|
local idx = 1
|
||||||
|
local seen = 0
|
||||||
|
local lamp = 0
|
||||||
|
local exitAt = 0
|
||||||
|
local done = false
|
||||||
|
|
||||||
|
local function field(port, name)
|
||||||
|
local p = manager.machine.ioport.ports[port]
|
||||||
|
if p == nil then return nil end
|
||||||
|
return p.fields[name]
|
||||||
|
end
|
||||||
|
local keyCmd = field(":macadb:KEY3", "Command / Open Apple")
|
||||||
|
local function press(f) if f then f:set_value(1) end end
|
||||||
|
local function release(f) if f then f:set_value(0) end end
|
||||||
|
|
||||||
|
local function stagePixel(x, y)
|
||||||
|
local b = mem:read_u8(0xE12000 + y * 160 + (x >> 1))
|
||||||
|
if (x & 1) == 0 then return (b >> 4) & 0x0F else return b & 0x0F end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The lamp colour when the whole lamp shows one verdict colour with
|
||||||
|
-- FILL_COLOR on its rows either side, else 0.
|
||||||
|
local function lampColor()
|
||||||
|
local left = LAMP_X + MARGIN
|
||||||
|
local right = LAMP_X + LAMP_W - 1 - MARGIN
|
||||||
|
local top = LAMP_Y + MARGIN
|
||||||
|
local bottom = LAMP_Y + LAMP_H - 1 - MARGIN
|
||||||
|
local midY = LAMP_Y + LAMP_H // 2
|
||||||
|
local c = stagePixel(left, top)
|
||||||
|
if c ~= PASS_COLOR and c ~= FAIL_COLOR then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
for _, p in ipairs({ {right, top}, {left, bottom}, {right, bottom}, {LAMP_X + LAMP_W // 2, midY} }) do
|
||||||
|
if stagePixel(p[1], p[2]) ~= c then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if stagePixel(LAMP_X - BESIDE, midY) ~= FILL_COLOR or stagePixel(LAMP_X + LAMP_W - 1 + BESIDE, midY) ~= FILL_COLOR then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
return c
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Select the JOEYLIB volume, open it, select TILEBANK, open it.
|
||||||
|
local steps = {
|
||||||
|
{3000, function() nat:post("J") end},
|
||||||
|
{3120, function() press(keyCmd) end},
|
||||||
|
{3126, function() nat:post("o") end},
|
||||||
|
{3180, function() release(keyCmd) end},
|
||||||
|
{3540, function() nat:post("TILEBANK") end},
|
||||||
|
{3660, function() press(keyCmd) end},
|
||||||
|
{3666, function() nat:post("o") end},
|
||||||
|
{3720, function() release(keyCmd) end},
|
||||||
|
}
|
||||||
|
|
||||||
|
emu.register_frame_done(function()
|
||||||
|
frame = frame + 1
|
||||||
|
if done then return end
|
||||||
|
while idx <= #steps and frame >= steps[idx][1] do
|
||||||
|
steps[idx][2]()
|
||||||
|
idx = idx + 1
|
||||||
|
end
|
||||||
|
if exitAt == 0 then
|
||||||
|
if frame >= FIRST_LOOK and frame % SAMPLE_EVERY == 0 then
|
||||||
|
local c = lampColor()
|
||||||
|
if c ~= 0 and c == lamp then
|
||||||
|
seen = seen + 1
|
||||||
|
else
|
||||||
|
seen = (c ~= 0) and 1 or 0
|
||||||
|
end
|
||||||
|
lamp = c
|
||||||
|
if seen >= LAMP_STABLE then
|
||||||
|
io.write(string.format("TILEBANK-IIGS lamp=%s f%d\n", (lamp == PASS_COLOR) and "PASS" or "FAIL", frame))
|
||||||
|
io.flush()
|
||||||
|
exitAt = frame + EXIT_SETTLE
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif frame >= exitAt then
|
||||||
|
done = true
|
||||||
|
manager.machine:exit()
|
||||||
|
end
|
||||||
|
if frame > FRAMES then
|
||||||
|
done = true
|
||||||
|
io.write("TILEBANK-IIGS no-lamp f"..frame.."\n")
|
||||||
|
io.flush()
|
||||||
|
manager.machine:exit()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
LUA
|
||||||
|
|
||||||
|
# From the work dir, so MAME's cfg/ and nvram/ never persist between runs.
|
||||||
|
out=$(cd "$work" && QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
|
||||||
|
timeout -s KILL "$wall" "$MAME" apple2gs \
|
||||||
|
-rompath "$IIGS_ROMPATH" \
|
||||||
|
-flop3 "$work/boot.po" -flop4 "$work/joey.2mg" \
|
||||||
|
-video none -sound none -nothrottle \
|
||||||
|
-autoboot_script "$work/tilebank.lua" < /dev/null 2>&1 | grep '^TILEBANK-IIGS')
|
||||||
|
echo "${out:-TILEBANK-IIGS: no report from the Lua (MAME died, or hit the ${wall}s cap)}"
|
||||||
|
|
||||||
|
lampOk=1
|
||||||
|
if ! grep -q 'lamp=PASS' <<< "$out"; then
|
||||||
|
lampOk=0
|
||||||
|
fi
|
||||||
|
# cadius names the extracted file with its ProDOS type appended.
|
||||||
|
"$CADIUS" EXTRACTFILE "$work/joey.2mg" "/JOEYLIB/JOEYLOG.TXT" "$work/log/" > /dev/null 2>&1
|
||||||
|
log=$(find "$work/log" -maxdepth 1 -iname 'joeylog.txt*' -print -quit)
|
||||||
|
if [ -z "$log" ]; then
|
||||||
|
echo "verify-iigs-tilebank: FAIL - no JOEYLOG.TXT on the work disk (the program never flushed its log)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
tileBankJudge "$log" || exit 1
|
||||||
|
if [ "$lampOk" -eq 0 ]; then
|
||||||
|
echo "verify-iigs-tilebank: FAIL - the on-screen lamp did not show PASS" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
541
scripts/verify-x68000-serial-mouse.sh
Executable file
541
scripts/verify-x68000-serial-mouse.sh
Executable file
|
|
@ -0,0 +1,541 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-x68000-serial-mouse.sh - the X68000's RS-232C port and mouse at once.
|
||||||
|
#
|
||||||
|
# Both live on the one Z8530 SCC: RS-232C on channel A, where JoeyLib's serial
|
||||||
|
# add-on owns the receive interrupt (src/x68000/x68kSccRxIsr.s), and the mouse
|
||||||
|
# on channel B, where the IOCS ROM owns the receive interrupt and solicits a
|
||||||
|
# packet every other Timer C tick. The chip has ONE register pointer for both
|
||||||
|
# channels, so a select-then-access register sequence split by the other
|
||||||
|
# side's interrupt goes to the wrong register, and the interrupt-under-service
|
||||||
|
# reset it swallows wedges the chip's interrupt chain. The serial and mouse
|
||||||
|
# gates each run one side alone; this one runs both together, hard, for long
|
||||||
|
# enough to matter.
|
||||||
|
#
|
||||||
|
# Boots SERMOUSE.X (examples/sermouse) under the patched MAME with SCC channel
|
||||||
|
# A on a null_modem socket and every SCC channel at the real chip's 3-byte
|
||||||
|
# receive FIFO (JOEY_Z80SCC_RX_FIFO=3), so an interrupt held off too long loses
|
||||||
|
# bytes here as it would on the machine. Then, all at once:
|
||||||
|
# serial the guest streams byte k = streamByte(k, 0xA5) and checks the
|
||||||
|
# peer's stream, byte k = streamByte(k, 0x00), in order; the host
|
||||||
|
# peer (scripts/lib/serialStreamPeer.py) checks the guest's and
|
||||||
|
# answers byte for byte. The guest also raises DTR 16 times a loop,
|
||||||
|
# the add-on's own select-then-write of channel A's WR5, so that
|
||||||
|
# sequence runs under both channels' interrupts all the time.
|
||||||
|
# mouse the Lua visits the four stage corners, pushing both axes into the
|
||||||
|
# corner EVERY frame -- a packet on channel B at every solicitation,
|
||||||
|
# while the pointer stays pinned where the clamp puts it -- checks
|
||||||
|
# the position there, then presses a button and checks it, then
|
||||||
|
# releases it (checked at the next corner). Clamp edges only: MAME's
|
||||||
|
# relative-axis injection is timing-sensitive, so interior positions
|
||||||
|
# are not testable (see verify-x68000-mouse.sh). Two left clicks a
|
||||||
|
# pass also prove the ROM's software keyboard stays off (hal.c
|
||||||
|
# X68K_SKEY_OFF): the second would bring it up and eat the next
|
||||||
|
# right click.
|
||||||
|
# Timeline: PASSES corner passes with the traffic flowing, every check also
|
||||||
|
# requiring both stream counts to have moved and no bad byte; SPACE pauses the
|
||||||
|
# guest's stream, the peer follows, and once quiet the guest's counts must
|
||||||
|
# hold still and equal a quiet snapshot of the peer's EXACTLY -- every byte
|
||||||
|
# sent either way arrived, in order; one more pass in the quiet; SPACE resumes
|
||||||
|
# and both counts must move again (the receive interrupt still fires at the
|
||||||
|
# end, the program still runs).
|
||||||
|
#
|
||||||
|
# MAME 0.264 needs one correction here, made in the Lua and not in MAME. Its
|
||||||
|
# Z8530 (src/devices/machine/z80scc.cpp) answers an interrupt acknowledge
|
||||||
|
# wrongly when BOTH channels have one pending: z80daisy_irq_ack puts the
|
||||||
|
# highest-priority source in service but returns m_chanB->m_rr2, the vector of
|
||||||
|
# whichever source was triggered LAST. The CPU then runs the other channel's
|
||||||
|
# handler, whose interrupt-under-service reset clears the source the chip
|
||||||
|
# acknowledged, and that channel's byte is left in its FIFO with nothing
|
||||||
|
# pending: the channel runs a byte behind until its FIFO overruns (serial
|
||||||
|
# loses bytes) or a stale byte starts a mouse packet (pointer and buttons go
|
||||||
|
# wrong). Measured before the correction: both, within seconds. Silicon
|
||||||
|
# returns the vector of the source it acknowledges, so a tap on the CPU's
|
||||||
|
# level-5 acknowledge rewrites the vector to that source's, and the verdict
|
||||||
|
# line reports how many acknowledges it corrected (iackFixed). Only this gate
|
||||||
|
# keeps both channels busy at once, so no other gate needs it.
|
||||||
|
#
|
||||||
|
# The guest reports through two verification rows at the bottom of the stage
|
||||||
|
# (one nibble per pixel, high nibble first), read from the text plane:
|
||||||
|
# row 198 3C3C, status, received(8), bad, sent(8), short writes, first bad(8)
|
||||||
|
# row 199 5A5A, present/left/right/middle, x, y (examples/keys' mouse row)
|
||||||
|
# The serial row changes at most every 4 frames, so a read counts only when
|
||||||
|
# two consecutive frames agree (a present caught half done cannot).
|
||||||
|
#
|
||||||
|
# PASS: every Lua check right, the peer's stream PASS, at least MIN_BYTES each
|
||||||
|
# way, and the guest's quiet counts among the peer's quiet snapshots.
|
||||||
|
#
|
||||||
|
# Requires: ./install.sh install x68000 -- the patched MAME
|
||||||
|
# (installer/patches/mame: 0002 for the FIFO depth, 0004 for the
|
||||||
|
# RS-232C slot and the mouse) and the Human68k system disk.
|
||||||
|
#
|
||||||
|
# bash scripts/verify-x68000-serial-mouse.sh
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
joeyNeed MAME mame
|
||||||
|
joeyNeed X68K_DIR x68kFirmware
|
||||||
|
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
||||||
|
source "$repo/scripts/lib/x68kDisk.sh"
|
||||||
|
PORT="${X68K_SERMOUSE_PORT:-6801}"
|
||||||
|
WALL="${X68K_SERMOUSE_WALL:-600}"
|
||||||
|
PASSES="${X68K_SERMOUSE_PASSES:-3}"
|
||||||
|
MIN_BYTES="${X68K_SERMOUSE_MIN_BYTES:-16384}"
|
||||||
|
PROG="$repo/build/x68000/bin/SERMOUSE.X"
|
||||||
|
FIFO_DEPTH=3
|
||||||
|
# The peer outlives MAME by this much, so it always sees the link close, and
|
||||||
|
# is given this long to report once MAME is gone.
|
||||||
|
PEER_MARGIN=30
|
||||||
|
PEER_GRACE=10
|
||||||
|
|
||||||
|
[ -f "$X68K_SYSTEM_DISK" ] || { echo "verify-x68000-serial-mouse: missing $X68K_SYSTEM_DISK" >&2; exit 2; }
|
||||||
|
[ -f "$PROG" ] || { echo "verify-x68000-serial-mouse: build SERMOUSE.X first (make -f make/x68000.mk)" >&2; exit 2; }
|
||||||
|
|
||||||
|
work=$(mktemp -d -t joey-x68sermouse.XXXXXX)
|
||||||
|
peer=""
|
||||||
|
mame=""
|
||||||
|
mameChild=""
|
||||||
|
cleanup() {
|
||||||
|
joeyStop "$mameChild" "$mame" "$peer"
|
||||||
|
rm -rf "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
x68kBootDisk "$work/boot.xdf" 'A:\SERMOUSE.X' || exit 1
|
||||||
|
x68kXdf add "$work/boot.xdf" "$PROG" SERMOUSE.X >/dev/null || exit 1
|
||||||
|
|
||||||
|
{
|
||||||
|
printf 'local PASSES = %d\n' "$PASSES"
|
||||||
|
printf 'local MIN_BYTES = %d\n' "$MIN_BYTES"
|
||||||
|
cat <<'LUA'
|
||||||
|
local cpu = manager.machine.devices[":maincpu"]
|
||||||
|
local mem = cpu.spaces["program"]
|
||||||
|
local nat = manager.machine.natkeyboard
|
||||||
|
local scc = manager.machine.devices[":scc"]
|
||||||
|
local sccA = manager.machine.devices[":scc:cha"]
|
||||||
|
local frame = 0
|
||||||
|
local t0 = 0
|
||||||
|
local biased = false
|
||||||
|
local done = false
|
||||||
|
local checks = 0
|
||||||
|
local fails = {}
|
||||||
|
local steps = {}
|
||||||
|
local pending = nil
|
||||||
|
local lastRaw = nil
|
||||||
|
local prev = nil
|
||||||
|
local quiet = nil
|
||||||
|
local pushX = 0
|
||||||
|
local pushY = 0
|
||||||
|
local axisValX = 0
|
||||||
|
local axisValY = 0
|
||||||
|
|
||||||
|
-- Stage geometry on the text plane (src/x68000/hal.c X68K_PRESENT_TEXT): four
|
||||||
|
-- bit planes 128 KB apart at 0xE00000, 128 bytes per row, leftmost pixel in
|
||||||
|
-- bit 7, the stage centred at (96, 156).
|
||||||
|
local TEXT_BASE = 0xE00000
|
||||||
|
local PLANES = 4
|
||||||
|
local PLANE_BYTES = 0x20000
|
||||||
|
local ROW_BYTES = 128
|
||||||
|
local ORIGIN_X = 96
|
||||||
|
local ORIGIN_Y = 156
|
||||||
|
-- The verification rows (examples/sermouse/sermouse.c): 16-bit words and
|
||||||
|
-- 32-bit longs, one nibble per pixel, laid end to end from the sentinel.
|
||||||
|
local WORD = 4
|
||||||
|
local LONG = 8
|
||||||
|
local SERIAL_ROW = 198
|
||||||
|
local SERIAL_SENTINEL = 0x3C3C
|
||||||
|
local SERIAL_NIBBLES = 4 * WORD + 3 * LONG
|
||||||
|
local MOUSE_ROW = 199
|
||||||
|
local MOUSE_SENTINEL = 0x5A5A
|
||||||
|
local MOUSE_NIBBLES = 4 * WORD
|
||||||
|
local STAGE_RIGHT = 319
|
||||||
|
local STAGE_BOTTOM = 199
|
||||||
|
local STATUS_OPEN = 0x0001
|
||||||
|
local STATUS_PAUSED = 0x0002
|
||||||
|
local NO_BAD_INDEX = 0xFFFFFFFF
|
||||||
|
|
||||||
|
-- Mouse drive: the x68k mouse's X/Y are 12-bit absolute counters it diffs
|
||||||
|
-- into delta packets; biased mid-range so a pass (net zero per axis, at most
|
||||||
|
-- two phases one way) never wraps them.
|
||||||
|
local AXIS_BIAS = 2048
|
||||||
|
local PUSH_STEP = 6 -- counter units per frame, per axis
|
||||||
|
local PUSH_FRAMES = 90 -- 540 units per phase: over the 319-pixel stage with room to spare
|
||||||
|
local BUTTON_FRAMES = 40
|
||||||
|
local CORNER_FRAMES = PUSH_FRAMES + BUTTON_FRAMES
|
||||||
|
local PASS_FRAMES = 4 * CORNER_FRAMES
|
||||||
|
local QUIET_SETTLE = 120 -- frames after SPACE before the line is read as quiet
|
||||||
|
local QUIET_HOLD = 60 -- and again this much later: it must not move
|
||||||
|
local RESUME_FRAMES = 120
|
||||||
|
local BOOT_BIAS_FRAME = 300
|
||||||
|
local SENTINEL_FIRST = 600
|
||||||
|
local SENTINEL_EVERY = 50
|
||||||
|
local SETTLE_FRAMES = 60
|
||||||
|
local STEADY_LIMIT = 60 -- frames a serial read may take to see two agreeing frames
|
||||||
|
local BOOT_LIMIT = 30000
|
||||||
|
|
||||||
|
-- The acknowledge-vector correction (see the script header). MAME keeps the
|
||||||
|
-- chip's six interrupt sources in m_int_state, highest priority first,
|
||||||
|
-- channel A's three then channel B's, each INT (pending) and/or IEO (in
|
||||||
|
-- service); m_int_source holds each one's type, which with the channel is the
|
||||||
|
-- 3-bit status the vector carries in bits 3-1 (WR9 status-low, as the X68000
|
||||||
|
-- runs it). The 68000 fetches the level-5 vector at 0xFFFFFA in cpu space.
|
||||||
|
local IACK5 = 0xFFFFFA
|
||||||
|
local SCC_SOURCES = 6
|
||||||
|
local SCC_A_SOURCES = 3
|
||||||
|
local DAISY_IEO = 0x02
|
||||||
|
local WR9_VIS = 0x01
|
||||||
|
local WR9_NV_SHSL = 0x12 -- no vector / status high: vectors this does not model
|
||||||
|
local STATUS_CHANNEL_A = 0x04
|
||||||
|
local STATUS_TYPE_MASK = 0x03
|
||||||
|
local VECTOR_STATUS_KEEP = 0xF1
|
||||||
|
local VECTOR_BYTE = 0xFF
|
||||||
|
local intState = emu.item(scc.items["0/m_int_state"])
|
||||||
|
local intSource = emu.item(scc.items["0/m_int_source"])
|
||||||
|
local wr9 = emu.item(scc.items["0/m_wr9"])
|
||||||
|
local wr2 = emu.item(sccA.items["0/m_wr2"])
|
||||||
|
local iackFixed = 0
|
||||||
|
|
||||||
|
local function textPix(px, y)
|
||||||
|
local sx = ORIGIN_X + px
|
||||||
|
local addr = TEXT_BASE + (ORIGIN_Y + y) * ROW_BYTES + (sx >> 3)
|
||||||
|
local bit = 7 - (sx & 7)
|
||||||
|
local v = 0
|
||||||
|
for p = 0, PLANES - 1 do
|
||||||
|
v = v | (((mem:read_u8(addr + p * PLANE_BYTES) >> bit) & 1) << p)
|
||||||
|
end
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
|
||||||
|
local function rowNibbles(y, count)
|
||||||
|
local t = {}
|
||||||
|
for i = 0, count - 1 do
|
||||||
|
t[#t + 1] = textPix(i, y)
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
-- A reader over a row's nibbles: each call takes the next field, high nibble
|
||||||
|
-- first.
|
||||||
|
local function fields(t)
|
||||||
|
local pos = 0
|
||||||
|
return function(count)
|
||||||
|
local v = 0
|
||||||
|
for i = pos + 1, pos + count do
|
||||||
|
v = (v << 4) | t[i]
|
||||||
|
end
|
||||||
|
pos = pos + count
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parseSerial(t)
|
||||||
|
local take = fields(t)
|
||||||
|
local s = {}
|
||||||
|
s.sentinel = take(WORD) == SERIAL_SENTINEL
|
||||||
|
s.status = take(WORD)
|
||||||
|
s.rx = take(LONG)
|
||||||
|
s.bad = take(WORD)
|
||||||
|
s.tx = take(LONG)
|
||||||
|
s.short = take(WORD)
|
||||||
|
s.firstBad = take(LONG)
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The flags word is one nibble per predicate: present, left, right, middle.
|
||||||
|
local function readMouse()
|
||||||
|
local take = fields(rowNibbles(MOUSE_ROW, MOUSE_NIBBLES))
|
||||||
|
local m = {}
|
||||||
|
m.sentinel = take(WORD) == MOUSE_SENTINEL
|
||||||
|
m.present = take(1)
|
||||||
|
m.left = take(1)
|
||||||
|
m.right = take(1)
|
||||||
|
m.middle = take(1)
|
||||||
|
m.x = take(WORD)
|
||||||
|
m.y = take(WORD)
|
||||||
|
return m
|
||||||
|
end
|
||||||
|
|
||||||
|
local ports = manager.machine.ioport.ports
|
||||||
|
local axisX, axisY, btnLeft, btnRight
|
||||||
|
local portX = ports[":mouse_port:x68k:X"]
|
||||||
|
local portY = ports[":mouse_port:x68k:Y"]
|
||||||
|
local portB = ports[":mouse_port:x68k:BTN"]
|
||||||
|
if portX then for _, f in pairs(portX.fields) do axisX = f end end
|
||||||
|
if portY then for _, f in pairs(portY.fields) do axisY = f end end
|
||||||
|
if portB then
|
||||||
|
for _, f in pairs(portB.fields) do
|
||||||
|
if f.mask == 2 then btnLeft = f end
|
||||||
|
if f.mask == 1 then btnRight = f end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- After MAME has acknowledged: the source now in service is the one the
|
||||||
|
-- vector must name. A global, because a collected tap handle removes the tap.
|
||||||
|
iackTap = cpu.spaces["cpu_space"]:install_read_tap(IACK5, IACK5 + 1, "iackVector", function(offset, data, mask)
|
||||||
|
local w = wr9:read(0)
|
||||||
|
if (w & WR9_VIS) == 0 or (w & WR9_NV_SHSL) ~= 0 then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
for i = 0, SCC_SOURCES - 1 do
|
||||||
|
if (intState:read(i) & DAISY_IEO) ~= 0 then
|
||||||
|
local status = (intSource:read(i) & STATUS_TYPE_MASK) | (i < SCC_A_SOURCES and STATUS_CHANNEL_A or 0)
|
||||||
|
local vector = (wr2:read(0) & VECTOR_STATUS_KEEP) | (status << 1)
|
||||||
|
if vector == (data & VECTOR_BYTE) then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
iackFixed = iackFixed + 1
|
||||||
|
return (data & ~VECTOR_BYTE) | vector
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function finish(verdict)
|
||||||
|
done = true
|
||||||
|
local s = prev or {}
|
||||||
|
io.write(string.format("VERIFY-X68K-SERMOUSE %s checks=%d fails=%d rx=%d tx=%d quiet=%s iackFixed=%d%s\n",
|
||||||
|
verdict, checks, #fails, s.rx or 0, s.tx or 0,
|
||||||
|
quiet and string.format("%d:%d", quiet.rx, quiet.tx) or "none", iackFixed,
|
||||||
|
#fails > 0 and (" detail=" .. table.concat(fails, ",")) or ""))
|
||||||
|
io.flush()
|
||||||
|
manager.machine:exit()
|
||||||
|
end
|
||||||
|
|
||||||
|
if not (axisX and axisY and btnLeft and btnRight) then
|
||||||
|
done = true
|
||||||
|
io.write("VERIFY-X68K-SERMOUSE error: mouse ioports not found\n")
|
||||||
|
io.flush()
|
||||||
|
manager.machine:exit()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- One check; the first failure is the verdict, so the run stops there.
|
||||||
|
local function expect(name, ok, detail)
|
||||||
|
if done then return end
|
||||||
|
checks = checks + 1
|
||||||
|
if not ok then
|
||||||
|
fails[#fails + 1] = name .. (detail and ("(" .. detail .. ")") or "")
|
||||||
|
finish("FAIL")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function expectMouse(step, x, y, l, r)
|
||||||
|
local m = readMouse()
|
||||||
|
expect(step .. ":sentinel", m.sentinel)
|
||||||
|
expect(step .. ":present", m.present == 1, tostring(m.present))
|
||||||
|
expect(step .. ":xy", m.x == x and m.y == y, string.format("%d,%d want %d,%d", m.x, m.y, x, y))
|
||||||
|
expect(step .. ":buttons", m.left == l and m.right == r and m.middle == 0,
|
||||||
|
string.format("%d%d%d want %d%d0", m.left, m.right, m.middle, l, r))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- A steady serial read: fn runs with the row once two consecutive frames
|
||||||
|
-- agree on it.
|
||||||
|
local function whenSteady(name, fn)
|
||||||
|
pending = { name = name, fn = fn, since = frame }
|
||||||
|
lastRaw = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function serialHealthy(step, s)
|
||||||
|
expect(step .. ":sentinel", s.sentinel)
|
||||||
|
expect(step .. ":open", (s.status & STATUS_OPEN) ~= 0, string.format("status %04X", s.status))
|
||||||
|
expect(step .. ":bad", s.bad == 0 and s.firstBad == NO_BAD_INDEX, string.format("%d first %d", s.bad, s.firstBad))
|
||||||
|
expect(step .. ":short", s.short == 0, tostring(s.short))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Traffic flowing: both counts moved since the last read.
|
||||||
|
local function expectMoving(step)
|
||||||
|
whenSteady(step, function(s)
|
||||||
|
serialHealthy(step, s)
|
||||||
|
expect(step .. ":paused", (s.status & STATUS_PAUSED) == 0)
|
||||||
|
if prev then
|
||||||
|
expect(step .. ":rxMoved", s.rx > prev.rx, string.format("%d -> %d", prev.rx, s.rx))
|
||||||
|
expect(step .. ":txMoved", s.tx > prev.tx, string.format("%d -> %d", prev.tx, s.tx))
|
||||||
|
end
|
||||||
|
prev = s
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Line quiet: both counts exactly where the pause left them.
|
||||||
|
local function expectFrozen(step)
|
||||||
|
whenSteady(step, function(s)
|
||||||
|
serialHealthy(step, s)
|
||||||
|
expect(step .. ":paused", (s.status & STATUS_PAUSED) ~= 0)
|
||||||
|
if quiet == nil then
|
||||||
|
quiet = s
|
||||||
|
expect(step .. ":rxMin", s.rx >= MIN_BYTES, tostring(s.rx))
|
||||||
|
expect(step .. ":txMin", s.tx >= MIN_BYTES, tostring(s.tx))
|
||||||
|
else
|
||||||
|
expect(step .. ":rxFrozen", s.rx == quiet.rx, string.format("%d -> %d", quiet.rx, s.rx))
|
||||||
|
expect(step .. ":txFrozen", s.tx == quiet.tx, string.format("%d -> %d", quiet.tx, s.tx))
|
||||||
|
end
|
||||||
|
prev = s
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function at(t, fn)
|
||||||
|
steps[#steps + 1] = { at = t, run = fn }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Corners in visiting order: where the clamp pins the pointer, the push
|
||||||
|
-- direction that pins it there, and the button pressed once it is.
|
||||||
|
local corners = {
|
||||||
|
{ name = "topLeft", x = 0, y = 0, dx = -1, dy = -1, button = "left" },
|
||||||
|
{ name = "topRight", x = STAGE_RIGHT, y = 0, dx = 1, dy = -1, button = "right" },
|
||||||
|
{ name = "bottomRight", x = STAGE_RIGHT, y = STAGE_BOTTOM, dx = 1, dy = 1, button = "left" },
|
||||||
|
{ name = "bottomLeft", x = 0, y = STAGE_BOTTOM, dx = -1, dy = 1, button = "right" },
|
||||||
|
}
|
||||||
|
|
||||||
|
-- One pass over the corners from frame `base`; `serialCheck` runs beside
|
||||||
|
-- each position check.
|
||||||
|
local function pass(base, label, serialCheck)
|
||||||
|
for i, c in ipairs(corners) do
|
||||||
|
local t = base + (i - 1) * CORNER_FRAMES
|
||||||
|
local step = label .. ":" .. c.name
|
||||||
|
local button = c.button == "left" and btnLeft or btnRight
|
||||||
|
local l = c.button == "left" and 1 or 0
|
||||||
|
at(t, function() pushX = c.dx; pushY = c.dy end)
|
||||||
|
at(t + PUSH_FRAMES, function()
|
||||||
|
expectMouse(step, c.x, c.y, 0, 0)
|
||||||
|
serialCheck(step)
|
||||||
|
button:set_value(1)
|
||||||
|
end)
|
||||||
|
at(t + CORNER_FRAMES - 1, function()
|
||||||
|
expectMouse(step .. "Button", c.x, c.y, l, 1 - l)
|
||||||
|
button:clear_value()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local t = 0
|
||||||
|
for p = 1, PASSES do
|
||||||
|
pass(t, "busy" .. p, expectMoving)
|
||||||
|
t = t + PASS_FRAMES
|
||||||
|
end
|
||||||
|
at(t, function() nat:post(" ") end)
|
||||||
|
at(t + QUIET_SETTLE, function() expectFrozen("pause") end)
|
||||||
|
at(t + QUIET_SETTLE + QUIET_HOLD, function() expectFrozen("pauseHold") end)
|
||||||
|
t = t + QUIET_SETTLE + QUIET_HOLD + 1
|
||||||
|
pass(t, "quiet", expectFrozen)
|
||||||
|
t = t + PASS_FRAMES
|
||||||
|
at(t, function() nat:post(" ") end)
|
||||||
|
at(t + RESUME_FRAMES, function()
|
||||||
|
prev = quiet
|
||||||
|
expectMoving("resumed")
|
||||||
|
end)
|
||||||
|
local finalFrame = t + RESUME_FRAMES
|
||||||
|
|
||||||
|
emu.register_frame_done(function()
|
||||||
|
frame = frame + 1
|
||||||
|
if done then return end
|
||||||
|
if not biased and frame == BOOT_BIAS_FRAME then
|
||||||
|
biased = true
|
||||||
|
axisValX = AXIS_BIAS
|
||||||
|
axisValY = AXIS_BIAS
|
||||||
|
axisX:set_value(axisValX)
|
||||||
|
axisY:set_value(axisValY)
|
||||||
|
end
|
||||||
|
if t0 == 0 then
|
||||||
|
if frame > SENTINEL_FIRST and frame % SENTINEL_EVERY == 0 then
|
||||||
|
local s = parseSerial(rowNibbles(SERIAL_ROW, SERIAL_NIBBLES))
|
||||||
|
if s.sentinel and readMouse().sentinel then
|
||||||
|
t0 = frame + SETTLE_FRAMES
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if frame > BOOT_LIMIT then
|
||||||
|
done = true
|
||||||
|
io.write("VERIFY-X68K-SERMOUSE timeout: SERMOUSE never came up\n")
|
||||||
|
io.flush()
|
||||||
|
manager.machine:exit()
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if pushX ~= 0 or pushY ~= 0 then
|
||||||
|
axisValX = axisValX + pushX * PUSH_STEP
|
||||||
|
axisValY = axisValY + pushY * PUSH_STEP
|
||||||
|
axisX:set_value(axisValX)
|
||||||
|
axisY:set_value(axisValY)
|
||||||
|
end
|
||||||
|
for _, s in ipairs(steps) do
|
||||||
|
if frame == t0 + s.at and not done then s.run() end
|
||||||
|
end
|
||||||
|
if pending and not done then
|
||||||
|
local raw = table.concat(rowNibbles(SERIAL_ROW, SERIAL_NIBBLES), ",")
|
||||||
|
if raw == lastRaw then
|
||||||
|
local p = pending
|
||||||
|
pending = nil
|
||||||
|
p.fn(parseSerial(rowNibbles(SERIAL_ROW, SERIAL_NIBBLES)))
|
||||||
|
elseif frame - pending.since > STEADY_LIMIT then
|
||||||
|
expect(pending.name .. ":steady", false)
|
||||||
|
else
|
||||||
|
lastRaw = raw
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not done and not pending and frame > t0 + finalFrame then
|
||||||
|
finish(#fails == 0 and "PASS" or "FAIL")
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
LUA
|
||||||
|
} > "$work/sermouse.lua"
|
||||||
|
|
||||||
|
python3 "$repo/scripts/lib/serialStreamPeer.py" "$PORT" "$((WALL + PEER_MARGIN))" "$MIN_BYTES" \
|
||||||
|
> "$work/peer.out" 2>&1 &
|
||||||
|
peer=$!
|
||||||
|
# The peer must be listening before MAME connects out to it.
|
||||||
|
for _ in $(seq 1 50); do
|
||||||
|
grep -q '^STREAM listening' "$work/peer.out" 2>/dev/null && break
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
# From the work dir, so MAME's cfg/ and nvram/ land there. Under timeout so a
|
||||||
|
# hung run cannot outlive WALL whatever happens to this script; mameChild is
|
||||||
|
# MAME itself, which cleanup stops first (stopping only the timeout process
|
||||||
|
# could leave MAME running).
|
||||||
|
cd "$work" || exit 1
|
||||||
|
JOEY_Z80SCC_RX_FIFO=$FIFO_DEPTH timeout -s KILL "$WALL" "$MAME" x68000 -bios ipl10 \
|
||||||
|
-rompath "$X68K_DIR/roms" -flop1 "$work/boot.xdf" \
|
||||||
|
-rs232c null_modem -bitb "socket.127.0.0.1:$PORT" \
|
||||||
|
-video none -sound none -nothrottle \
|
||||||
|
-autoboot_script "$work/sermouse.lua" </dev/null > "$work/mame.out" 2>&1 &
|
||||||
|
mame=$!
|
||||||
|
sleep 1
|
||||||
|
mameChild=$(pgrep -P "$mame" | head -1)
|
||||||
|
|
||||||
|
# The Lua ends the run: after its last check, or at its first failure.
|
||||||
|
wait "$mame"
|
||||||
|
mame=""
|
||||||
|
mameChild=""
|
||||||
|
for _ in $(seq 1 "$PEER_GRACE"); do
|
||||||
|
joeyAlive "$peer" || break
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
joeyStop "$peer"
|
||||||
|
peer=""
|
||||||
|
|
||||||
|
lua=$(grep -E '^VERIFY-X68K-SERMOUSE ' "$work/mame.out" | tail -1)
|
||||||
|
stream=$(grep -E '^STREAM (PASS|FAIL)' "$work/peer.out" | tail -1)
|
||||||
|
echo "${lua:-VERIFY-X68K-SERMOUSE: no verdict from the Lua}"
|
||||||
|
echo "${stream:-STREAM: no verdict from the peer}"
|
||||||
|
|
||||||
|
if ! grep -q "RX FIFO depth $FIFO_DEPTH" "$work/mame.out"; then
|
||||||
|
echo "verify-x68000-serial-mouse: SKIP - this MAME does not take JOEY_Z80SCC_RX_FIFO (reinstall: ./install.sh install mame)" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
fail=0
|
||||||
|
if ! echo "$lua" | grep -q '^VERIFY-X68K-SERMOUSE PASS .* fails=0 '; then
|
||||||
|
echo "verify-x68000-serial-mouse: FAIL - guest or mouse check (Lua line above)" >&2
|
||||||
|
tail -5 "$work/mame.out" >&2
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
if ! echo "$stream" | grep -q '^STREAM PASS '; then
|
||||||
|
echo "verify-x68000-serial-mouse: FAIL - the guest's stream to the host (peer line above)" >&2
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
guestQuiet=$(echo "$lua" | sed -n 's/.* quiet=\([0-9]*:[0-9]*\).*/\1/p')
|
||||||
|
peerQuiet=$(echo "$stream" | sed -n 's/.* quiet=\([0-9:,]*\) .*/\1/p')
|
||||||
|
if [ -z "$guestQuiet" ] || [[ ",$peerQuiet," != *",$guestQuiet,"* ]]; then
|
||||||
|
echo "verify-x68000-serial-mouse: FAIL - quiet counts differ: guest received:sent $guestQuiet, peer sent:received $peerQuiet" >&2
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
if [ "$fail" -ne 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "verify-x68000-serial-mouse: PASS (both streams byte-exact and complete at the pause, mouse corners and buttons right throughout, receive interrupt alive at the end)"
|
||||||
|
|
@ -61,25 +61,9 @@ sleep 2
|
||||||
# cwd where the next run would read them back.
|
# cwd where the next run would read them back.
|
||||||
cd "$work" || exit 1
|
cd "$work" || exit 1
|
||||||
if [ -n "$LINE" ]; then
|
if [ -n "$LINE" ]; then
|
||||||
python3 - "$LINE" > "$work/x68000.cfg" <<'PY' || exit 2
|
|
||||||
import sys
|
|
||||||
# MAME's RS232_* enum values (src/devices/bus/rs232/rs232.h).
|
|
||||||
BAUD = {"300": 2, "600": 3, "1200": 4, "2400": 5, "4800": 6, "9600": 7, "19200": 9, "38400": 11}
|
|
||||||
DATA = {"5": 0, "6": 1, "7": 2, "8": 3}
|
|
||||||
PARITY = {"N": 0, "O": 1, "E": 2}
|
|
||||||
STOP = {"1": 1, "2": 3}
|
|
||||||
baud, data, parity, stop = sys.argv[1].split()
|
|
||||||
tag = ":rs232c:null_modem:"
|
|
||||||
ports = [("RS232_TXBAUD", 7, BAUD[baud]), ("RS232_RXBAUD", 7, BAUD[baud]),
|
|
||||||
("RS232_DATABITS", 3, DATA[data]), ("RS232_PARITY", 0, PARITY[parity.upper()]),
|
|
||||||
("RS232_STOPBITS", 1, STOP[stop])]
|
|
||||||
print('<?xml version="1.0"?>\n<mameconfig version="10">\n <system name="x68000">\n <input>')
|
|
||||||
for name, default, value in ports:
|
|
||||||
print(f' <port tag="{tag}{name}" type="CONFIG" mask="255" defvalue="{default}" value="{value}" />')
|
|
||||||
print(' </input>\n </system>\n</mameconfig>')
|
|
||||||
PY
|
|
||||||
mkdir -p "$work/cfg"
|
mkdir -p "$work/cfg"
|
||||||
mv "$work/x68000.cfg" "$work/cfg/x68000.cfg"
|
python3 "$repo/scripts/lib/mameNullModemCfg.py" x68000 :rs232c:null_modem "$LINE" \
|
||||||
|
> "$work/cfg/x68000.cfg" || exit 2
|
||||||
fi
|
fi
|
||||||
# MAME runs until the peer has its verdict and is then stopped by PID: waiting
|
# MAME runs until the peer has its verdict and is then stopped by PID: waiting
|
||||||
# out a whole run once the probe has echoed cost minutes per gate. The peer's
|
# out a whole run once the probe has echoed cost minutes per gate. The peer's
|
||||||
|
|
|
||||||
170
scripts/verify-x68000-tilebank.sh
Executable file
170
scripts/verify-x68000-tilebank.sh
Executable file
|
|
@ -0,0 +1,170 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-x68000-tilebank.sh - Tile-bank gate for the X68000: proves that
|
||||||
|
# assetbake's x68000 encoder (plane-major, like the Amiga's, under its own
|
||||||
|
# target byte) writes exactly the layout the X68000's native tile paste
|
||||||
|
# (src/m68k/planarTile.h via src/x68000/x68kTile.h) reads, and that the
|
||||||
|
# X68000 build refuses a bank baked for any other target.
|
||||||
|
#
|
||||||
|
# Boots Human68k headless under MAME with TILEBANK.X (examples/tilebank) and
|
||||||
|
# the DATA/tilebank make/x68000.mk staged from the host gate's fixture on a
|
||||||
|
# data disk in drive B. The program loads its own bake through
|
||||||
|
# jlTileBankLoadToSurface and through jlTileBankLoad + jlTilePaste, checks
|
||||||
|
# every pixel of both against the PNG, hashes both, tries every other
|
||||||
|
# target's bake, logs one TILEBANK line and shows its verdict lamp. The Lua
|
||||||
|
# below waits for the lamp in text VRAM, then presses a key so the program
|
||||||
|
# exits -- Human68k only finalises joeylog.txt's directory entry when the
|
||||||
|
# program's stdio closes it -- and stops MAME. The line, extracted from the
|
||||||
|
# data disk, must say PASS with both hashes equal to the host gate's
|
||||||
|
# (scripts/check-tilebank.sh, run here for the value) and every other target
|
||||||
|
# refused, and the lamp must be the PASS colour.
|
||||||
|
#
|
||||||
|
# Text-plane geometry (see verify-x68000-input.sh): four 1bpp planes 0x20000
|
||||||
|
# apart at 0xE00000, 128 bytes per row, leftmost pixel in bit 7, the 320x200
|
||||||
|
# stage centred at (96, 156). The lamp is examples/tilebank's: 64x64 at stage
|
||||||
|
# (128, 68), colour 1 for PASS and 2 for FAIL, with FILL_COLOR (9) either side
|
||||||
|
# of it on its rows.
|
||||||
|
#
|
||||||
|
# Usage: scripts/verify-x68000-tilebank.sh
|
||||||
|
# Env: TILEBANK_DATA_DIR stage this DATA/tilebank instead (negative controls)
|
||||||
|
# Requires: MAME, the X68000 firmware and CG ROM, a host cc;
|
||||||
|
# build/x68000/bin/TILEBANK.X and its DATA/tilebank (make x68000).
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
source "$repo/scripts/lib/tileBankGate.sh"
|
||||||
|
joeyNeed MAME mame
|
||||||
|
joeyNeed X68K_DIR x68kFirmware
|
||||||
|
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
||||||
|
source "$repo/scripts/lib/x68kDisk.sh"
|
||||||
|
joeyNeedFile "$X68K_SYSTEM_DISK" x68kFirmware
|
||||||
|
|
||||||
|
binDir=$repo/build/x68000/bin
|
||||||
|
dataDir=$(tileBankData "$binDir")
|
||||||
|
wall=600 # hard cap on MAME, whatever happens to this script
|
||||||
|
frames=20000 # emulated frames before the Lua gives up on the lamp
|
||||||
|
|
||||||
|
tileBankNeedBuild "$binDir/TILEBANK.X" "$dataDir" "make x68000"
|
||||||
|
tileBankReference
|
||||||
|
|
||||||
|
work=$(mktemp -d -t joeylib-x68-tilebank.XXXXXX)
|
||||||
|
trap 'rm -rf "$work"' EXIT
|
||||||
|
|
||||||
|
# The boot disk carries only Human68k and AUTOEXEC; the program, its DATA and
|
||||||
|
# its log live on the data disk in B:, which AUTOEXEC makes current.
|
||||||
|
x68kBootDisk "$work/boot.xdf" 'B:' 'B:\TILEBANK.X' || exit 1
|
||||||
|
x68kDataDisk "$work/data.xdf" || exit 1
|
||||||
|
x68kXdf add "$work/data.xdf" "$binDir/TILEBANK.X" TILEBANK.X > /dev/null || exit 1
|
||||||
|
x68kAddTree "$work/data.xdf" "$dataDir" DATA/TILEBANK || exit 1
|
||||||
|
|
||||||
|
cat > "$work/tilebank.lua" <<LUA
|
||||||
|
local mem = manager.machine.devices[":maincpu"].spaces["program"]
|
||||||
|
local nat = manager.machine.natkeyboard
|
||||||
|
|
||||||
|
local FRAMES = $frames
|
||||||
|
local SAMPLE_EVERY = 10 -- frames between lamp looks
|
||||||
|
local LAMP_STABLE = 3 -- looks in a row that must agree
|
||||||
|
local KEY_EVERY = 60 -- frames between key presses once the lamp shows
|
||||||
|
local EXIT_SETTLE = 900 -- keypress -> jlShutdown -> stdio closes the log
|
||||||
|
|
||||||
|
local LAMP_X, LAMP_Y, LAMP_W, LAMP_H = 128, 68, 64, 64
|
||||||
|
local PASS_COLOR, FAIL_COLOR, FILL_COLOR = 1, 2, 9
|
||||||
|
local MARGIN = 4 -- inside probes keep off the lamp's edge
|
||||||
|
local BESIDE = 8 -- outside probes sit this far off it
|
||||||
|
|
||||||
|
local frame = 0
|
||||||
|
local seen = 0
|
||||||
|
local lamp = 0
|
||||||
|
local exitAt = 0
|
||||||
|
local done = false
|
||||||
|
|
||||||
|
local function stagePixel(px, py)
|
||||||
|
local x = 96 + px
|
||||||
|
local byteOff = (156 + py) * 128 + (x >> 3)
|
||||||
|
local bit = 0x80 >> (x & 7)
|
||||||
|
local v = 0
|
||||||
|
for plane = 0, 3 do
|
||||||
|
if (mem:read_u8(0xE00000 + plane * 0x20000 + byteOff) & bit) ~= 0 then
|
||||||
|
v = v | (1 << plane)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The lamp colour when the whole lamp shows one verdict colour with
|
||||||
|
-- FILL_COLOR on its rows either side, else 0.
|
||||||
|
local function lampColor()
|
||||||
|
local left = LAMP_X + MARGIN
|
||||||
|
local right = LAMP_X + LAMP_W - 1 - MARGIN
|
||||||
|
local top = LAMP_Y + MARGIN
|
||||||
|
local bottom = LAMP_Y + LAMP_H - 1 - MARGIN
|
||||||
|
local midY = LAMP_Y + LAMP_H // 2
|
||||||
|
local c = stagePixel(left, top)
|
||||||
|
if c ~= PASS_COLOR and c ~= FAIL_COLOR then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
for _, p in ipairs({ {right, top}, {left, bottom}, {right, bottom}, {LAMP_X + LAMP_W // 2, midY} }) do
|
||||||
|
if stagePixel(p[1], p[2]) ~= c then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if stagePixel(LAMP_X - BESIDE, midY) ~= FILL_COLOR or stagePixel(LAMP_X + LAMP_W - 1 + BESIDE, midY) ~= FILL_COLOR then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
return c
|
||||||
|
end
|
||||||
|
|
||||||
|
emu.register_frame_done(function()
|
||||||
|
frame = frame + 1
|
||||||
|
if done then return end
|
||||||
|
if exitAt == 0 then
|
||||||
|
if frame % SAMPLE_EVERY == 0 then
|
||||||
|
local c = lampColor()
|
||||||
|
if c ~= 0 and c == lamp then
|
||||||
|
seen = seen + 1
|
||||||
|
else
|
||||||
|
seen = (c ~= 0) and 1 or 0
|
||||||
|
end
|
||||||
|
lamp = c
|
||||||
|
if seen >= LAMP_STABLE then
|
||||||
|
io.write(string.format("TILEBANK-X68K lamp=%s f%d\n", (lamp == PASS_COLOR) and "PASS" or "FAIL", frame))
|
||||||
|
io.flush()
|
||||||
|
exitAt = frame + EXIT_SETTLE
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if frame % KEY_EVERY == 0 then nat:post(" ") end
|
||||||
|
if frame >= exitAt then
|
||||||
|
done = true
|
||||||
|
manager.machine:exit()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if frame > FRAMES then
|
||||||
|
done = true
|
||||||
|
io.write("TILEBANK-X68K no-lamp f"..frame.."\n")
|
||||||
|
io.flush()
|
||||||
|
manager.machine:exit()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
LUA
|
||||||
|
|
||||||
|
# From the work dir, so MAME's cfg/ and nvram/ never persist between runs.
|
||||||
|
out=$(cd "$work" && timeout -s KILL "$wall" "$MAME" x68000 -bios ipl10 \
|
||||||
|
-rompath "$X68K_DIR/roms" -flop1 "$work/boot.xdf" -flop2 "$work/data.xdf" \
|
||||||
|
-video none -sound none -nothrottle \
|
||||||
|
-autoboot_script "$work/tilebank.lua" < /dev/null 2>&1 | grep '^TILEBANK-X68K')
|
||||||
|
echo "${out:-TILEBANK-X68K: no report from the Lua (MAME died, or hit the ${wall}s cap)}"
|
||||||
|
|
||||||
|
lampOk=1
|
||||||
|
if ! grep -q 'lamp=PASS' <<< "$out"; then
|
||||||
|
lampOk=0
|
||||||
|
fi
|
||||||
|
if ! x68kXdf extract "$work/data.xdf" joeylog.txt "$work/joeylog.txt" > /dev/null 2>&1; then
|
||||||
|
echo "verify-x68000-tilebank: FAIL - no joeylog.txt on the data disk (the program never logged, or never exited)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
tileBankJudge "$work/joeylog.txt" || exit 1
|
||||||
|
if [ "$lampOk" -eq 0 ]; then
|
||||||
|
echo "verify-x68000-tilebank: FAIL - the on-screen lamp did not show PASS" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
@ -1,372 +1,16 @@
|
||||||
// Amiga tile ops as always-inline functions (NATIVE-PERF Phase 2
|
// Amiga tile ops: the shared planar bodies in src/m68k/planarTile.h, bound to
|
||||||
// R4a). Bodies moved verbatim from src/amiga/hal.c so the public
|
// the Amiga's planar surface data. The X68000 uses the same bodies
|
||||||
// wrappers in src/core/tile.c inline them, deleting the second full
|
// (src/x68000/x68kTile.h). Do not include this header directly; it comes with
|
||||||
// cdecl call layer (~130-190 cycles/op on the 68000). The jlpTile*
|
// port.h on the Amiga.
|
||||||
// dispatch names are macro-aliased to these in port.h, which also
|
|
||||||
// #includes this header at its tail -- AFTER the jlpGenericTile*
|
|
||||||
// prototypes the portData == NULL fallbacks call. Do not include this
|
|
||||||
// header directly; it comes with port.h on the Amiga.
|
|
||||||
#ifndef JOEY_AMIGA_TILE_H
|
#ifndef JOEY_AMIGA_TILE_H
|
||||||
#define JOEY_AMIGA_TILE_H
|
#define JOEY_AMIGA_TILE_H
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include "joey/tile.h"
|
|
||||||
#include "amigaPlanar.h"
|
#include "amigaPlanar.h"
|
||||||
|
|
||||||
// 8-row unrolled tile access (NATIVE-PERF Phase 2 R8): row offsets
|
#define PLANAR_TILE_DATA_T AmigaPlanarT
|
||||||
// are compile-time constants row * 40, so each row is one d16(An)
|
#define PLANAR_TILE_BYTES_PER_ROW AMIGA_BYTES_PER_ROW
|
||||||
// access with no pointer stepping and no loop control. gcc-amigaos
|
#define PLANAR_TILE_PLANES AMIGA_BITPLANES
|
||||||
// -O2 keeps the equivalent row loops rolled as dbf (objdump-verified,
|
|
||||||
// ~18 cycles/row of pure loop overhead). Only the row dimension
|
|
||||||
// unrolls -- the 4-plane loop keeps real per-plane setup and stays
|
|
||||||
// register-light per the m68k-gcc-unroll-backfire lesson.
|
|
||||||
#define AMIGA_TILE_ROW_OFF(_r) ((uint16_t)((_r) * AMIGA_BYTES_PER_ROW))
|
|
||||||
|
|
||||||
// Phase 5 planar dual-write for tile ops, fully planar after Phase 9
|
#include "planarTile.h"
|
||||||
// dropped the chunky shadow. All tiles are 8-pixel aligned (8x8 blocks
|
|
||||||
// at multiples of 8), so plane writes are byte-aligned -- one plane
|
|
||||||
// byte per row, 8 rows per tile, no edge masks. Stride between rows
|
|
||||||
// in a plane is AMIGA_BYTES_PER_ROW (40).
|
|
||||||
|
|
||||||
static inline __attribute__((always_inline)) void amigaTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex) {
|
|
||||||
AmigaPlanarT *pd;
|
|
||||||
uint16_t plane;
|
|
||||||
uint8_t fillByte;
|
|
||||||
uint8_t *p;
|
|
||||||
|
|
||||||
pd = (AmigaPlanarT *)s->portData;
|
|
||||||
if (pd == NULL) {
|
|
||||||
jlpGenericTileFill(s, bx, by, colorIndex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (plane = 0; plane < AMIGA_BITPLANES; plane++) {
|
|
||||||
fillByte = ((colorIndex >> plane) & 1u) ? 0xFFu : 0x00u;
|
|
||||||
p = pd->planes[plane] + (uint16_t)by * 8u * AMIGA_BYTES_PER_ROW + bx;
|
|
||||||
p[AMIGA_TILE_ROW_OFF(0)] = fillByte;
|
|
||||||
p[AMIGA_TILE_ROW_OFF(1)] = fillByte;
|
|
||||||
p[AMIGA_TILE_ROW_OFF(2)] = fillByte;
|
|
||||||
p[AMIGA_TILE_ROW_OFF(3)] = fillByte;
|
|
||||||
p[AMIGA_TILE_ROW_OFF(4)] = fillByte;
|
|
||||||
p[AMIGA_TILE_ROW_OFF(5)] = fillByte;
|
|
||||||
p[AMIGA_TILE_ROW_OFF(6)] = fillByte;
|
|
||||||
p[AMIGA_TILE_ROW_OFF(7)] = fillByte;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline __attribute__((always_inline)) void amigaTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy) {
|
|
||||||
AmigaPlanarT *dstPd;
|
|
||||||
AmigaPlanarT *srcPd;
|
|
||||||
uint16_t plane;
|
|
||||||
uint8_t *dp;
|
|
||||||
const uint8_t *sp;
|
|
||||||
|
|
||||||
dstPd = (AmigaPlanarT *)dst->portData;
|
|
||||||
srcPd = (AmigaPlanarT *)src->portData;
|
|
||||||
if (dstPd == NULL || srcPd == NULL) {
|
|
||||||
jlpGenericTileCopy(dst, dstBx, dstBy, src, srcBx, srcBy);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (plane = 0; plane < AMIGA_BITPLANES; plane++) {
|
|
||||||
dp = dstPd->planes[plane] + (uint16_t)dstBy * 8u * AMIGA_BYTES_PER_ROW + dstBx;
|
|
||||||
sp = srcPd->planes[plane] + (uint16_t)srcBy * 8u * AMIGA_BYTES_PER_ROW + srcBx;
|
|
||||||
dp[AMIGA_TILE_ROW_OFF(0)] = sp[AMIGA_TILE_ROW_OFF(0)];
|
|
||||||
dp[AMIGA_TILE_ROW_OFF(1)] = sp[AMIGA_TILE_ROW_OFF(1)];
|
|
||||||
dp[AMIGA_TILE_ROW_OFF(2)] = sp[AMIGA_TILE_ROW_OFF(2)];
|
|
||||||
dp[AMIGA_TILE_ROW_OFF(3)] = sp[AMIGA_TILE_ROW_OFF(3)];
|
|
||||||
dp[AMIGA_TILE_ROW_OFF(4)] = sp[AMIGA_TILE_ROW_OFF(4)];
|
|
||||||
dp[AMIGA_TILE_ROW_OFF(5)] = sp[AMIGA_TILE_ROW_OFF(5)];
|
|
||||||
dp[AMIGA_TILE_ROW_OFF(6)] = sp[AMIGA_TILE_ROW_OFF(6)];
|
|
||||||
dp[AMIGA_TILE_ROW_OFF(7)] = sp[AMIGA_TILE_ROW_OFF(7)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Pure-planar masked copy. For each row of the 8x8 tile, read 4 src
|
|
||||||
// plane bytes; compute a per-pixel "non-transparent" mask via XOR
|
|
||||||
// against the transparent index's per-plane bit pattern (a pixel
|
|
||||||
// matches transparent IFF all 4 plane bits match transparent's 4
|
|
||||||
// bits = OR of 4 XOR'd bytes is 0 in that bit). Then for each plane,
|
|
||||||
// dst = (dst & ~mask) | (src & mask) -- copy src bits at mask-set
|
|
||||||
// positions, preserve dst bits elsewhere.
|
|
||||||
static inline __attribute__((always_inline)) void amigaTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparent) {
|
|
||||||
AmigaPlanarT *dstPd;
|
|
||||||
AmigaPlanarT *srcPd;
|
|
||||||
uint8_t transBitByte[AMIGA_BITPLANES];
|
|
||||||
uint16_t i;
|
|
||||||
uint8_t row;
|
|
||||||
uint16_t srcByteOff;
|
|
||||||
uint16_t dstByteOff;
|
|
||||||
uint8_t srcPlaneBytes[AMIGA_BITPLANES];
|
|
||||||
uint8_t maskByte;
|
|
||||||
|
|
||||||
dstPd = (AmigaPlanarT *)dst->portData;
|
|
||||||
srcPd = (AmigaPlanarT *)src->portData;
|
|
||||||
if (dstPd == NULL || srcPd == NULL) {
|
|
||||||
jlpGenericTileCopyMasked(dst, dstBx, dstBy, src, srcBx, srcBy, transparent);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
transparent = (uint8_t)(transparent & 0x0Fu);
|
|
||||||
/* Per-plane "all bits set if transparent's bit at this plane is 1
|
|
||||||
* else all 0" -- so XOR gives bit set where pixel differs from
|
|
||||||
* transparent in that plane. */
|
|
||||||
for (i = 0; i < AMIGA_BITPLANES; i++) {
|
|
||||||
transBitByte[i] = ((transparent >> i) & 1u) ? 0xFFu : 0x00u;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (row = 0; row < 8u; row++) {
|
|
||||||
srcByteOff = (uint16_t)((uint16_t)srcBy * 8u + row) * AMIGA_BYTES_PER_ROW + srcBx;
|
|
||||||
dstByteOff = (uint16_t)((uint16_t)dstBy * 8u + row) * AMIGA_BYTES_PER_ROW + dstBx;
|
|
||||||
srcPlaneBytes[0] = srcPd->planes[0][srcByteOff];
|
|
||||||
srcPlaneBytes[1] = srcPd->planes[1][srcByteOff];
|
|
||||||
srcPlaneBytes[2] = srcPd->planes[2][srcByteOff];
|
|
||||||
srcPlaneBytes[3] = srcPd->planes[3][srcByteOff];
|
|
||||||
/* maskByte: bit set where pixel differs from transparent in
|
|
||||||
* ANY plane -- i.e., where the pixel is non-transparent. */
|
|
||||||
maskByte = (uint8_t)((srcPlaneBytes[0] ^ transBitByte[0])
|
|
||||||
| (srcPlaneBytes[1] ^ transBitByte[1])
|
|
||||||
| (srcPlaneBytes[2] ^ transBitByte[2])
|
|
||||||
| (srcPlaneBytes[3] ^ transBitByte[3]));
|
|
||||||
for (i = 0; i < AMIGA_BITPLANES; i++) {
|
|
||||||
uint8_t existing = dstPd->planes[i][dstByteOff];
|
|
||||||
dstPd->planes[i][dstByteOff] = (uint8_t)((existing & (uint8_t)~maskByte)
|
|
||||||
| (srcPlaneBytes[i] & maskByte));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Phase 9 plane-to-chunky derivation for jlTileSnap. Reads 8 plane
|
|
||||||
// bytes (1 byte per row x 8 rows) from each of 4 planes for the
|
|
||||||
// 8-pixel-aligned tile column at bx, then assembles 32 chunky bytes
|
|
||||||
// (4 per row x 8 rows, packed 2 px/byte high-then-low nibble) into
|
|
||||||
// On Amiga, jlTileT.pixels is opaque port-specific storage (cross-
|
|
||||||
// platform tile.c never reads it directly when s->pixels is NULL).
|
|
||||||
// We use those 32 bytes as 4 planes x 8 rows, plane-major:
|
|
||||||
// bytes [0..7] = plane 0, rows 0..7
|
|
||||||
// bytes [8..15] = plane 1, rows 0..7
|
|
||||||
// bytes [16..23] = plane 2, rows 0..7
|
|
||||||
// bytes [24..31] = plane 3, rows 0..7
|
|
||||||
// snap/paste then become 32 plain byte loads + stores -- no chunky
|
|
||||||
// <-> planar conversion at all. The previous c2p-based path paid
|
|
||||||
// 4 KB LUT lookups + bit shuffling per pixel; this is ~50x cheaper.
|
|
||||||
#define AMIGA_TILE_PLANE_STRIDE 8
|
|
||||||
static inline __attribute__((always_inline)) void amigaTileSnap(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *chunkyOut) {
|
|
||||||
AmigaPlanarT *pd;
|
|
||||||
uint16_t rowBase;
|
|
||||||
uint8_t plane;
|
|
||||||
|
|
||||||
pd = (AmigaPlanarT *)src->portData;
|
|
||||||
if (pd == NULL) {
|
|
||||||
jlpGenericTileSnap(src, bx, by, chunkyOut);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rowBase = (uint16_t)((uint16_t)by * 8u) * AMIGA_BYTES_PER_ROW + bx;
|
|
||||||
for (plane = 0; plane < AMIGA_BITPLANES; plane++) {
|
|
||||||
const uint8_t *p = pd->planes[plane] + rowBase;
|
|
||||||
uint8_t *q = chunkyOut + plane * AMIGA_TILE_PLANE_STRIDE;
|
|
||||||
q[0] = p[AMIGA_TILE_ROW_OFF(0)];
|
|
||||||
q[1] = p[AMIGA_TILE_ROW_OFF(1)];
|
|
||||||
q[2] = p[AMIGA_TILE_ROW_OFF(2)];
|
|
||||||
q[3] = p[AMIGA_TILE_ROW_OFF(3)];
|
|
||||||
q[4] = p[AMIGA_TILE_ROW_OFF(4)];
|
|
||||||
q[5] = p[AMIGA_TILE_ROW_OFF(5)];
|
|
||||||
q[6] = p[AMIGA_TILE_ROW_OFF(6)];
|
|
||||||
q[7] = p[AMIGA_TILE_ROW_OFF(7)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline __attribute__((always_inline)) void amigaTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *chunkyTile) {
|
|
||||||
AmigaPlanarT *pd;
|
|
||||||
uint8_t plane;
|
|
||||||
uint16_t rowBase;
|
|
||||||
|
|
||||||
pd = (AmigaPlanarT *)dst->portData;
|
|
||||||
if (pd == NULL) {
|
|
||||||
jlpGenericTilePaste(dst, bx, by, chunkyTile);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* jlTileT.pixels[] holds plane-major planar bytes (see
|
|
||||||
* jlpTileSnap header for layout). Paste = 32 byte stores
|
|
||||||
* with no chunky -> planar conversion. */
|
|
||||||
rowBase = (uint16_t)((uint16_t)by * 8u) * AMIGA_BYTES_PER_ROW + bx;
|
|
||||||
for (plane = 0; plane < AMIGA_BITPLANES; plane++) {
|
|
||||||
const uint8_t *q = chunkyTile + plane * AMIGA_TILE_PLANE_STRIDE;
|
|
||||||
uint8_t *p = pd->planes[plane] + rowBase;
|
|
||||||
p[AMIGA_TILE_ROW_OFF(0)] = q[0];
|
|
||||||
p[AMIGA_TILE_ROW_OFF(1)] = q[1];
|
|
||||||
p[AMIGA_TILE_ROW_OFF(2)] = q[2];
|
|
||||||
p[AMIGA_TILE_ROW_OFF(3)] = q[3];
|
|
||||||
p[AMIGA_TILE_ROW_OFF(4)] = q[4];
|
|
||||||
p[AMIGA_TILE_ROW_OFF(5)] = q[5];
|
|
||||||
p[AMIGA_TILE_ROW_OFF(6)] = q[6];
|
|
||||||
p[AMIGA_TILE_ROW_OFF(7)] = q[7];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Whole-map paste engine (NATIVE-PERF W3 item 9). jlTileMapPaste's
|
|
||||||
// shared whole-map arm calls this ONCE per map: the portData load +
|
|
||||||
// NULL test, the by*320 row-base derive, and the four plane base
|
|
||||||
// pointers are hoisted out of the per-cell loop -- the per-cell
|
|
||||||
// amigaTilePaste route reloads portData, rebuilds the cell offset,
|
|
||||||
// and re-reads pd->planes[0..3] from the struct every cell (~172 of
|
|
||||||
// its ~776 cyc/cell, objdump-measured). The four dst pointers stay
|
|
||||||
// resident in a0-a3 (one byte per column, one row stride per tile
|
|
||||||
// row), so each cell is 32 move.b off compile-time displacements
|
|
||||||
// with zero per-plane setup. Plane order, row order, and written
|
|
||||||
// values are identical to per-cell amigaTilePaste -- pixel output is
|
|
||||||
// byte-identical. movep is unusable here (plane bytes sit 8000 bytes
|
|
||||||
// apart across separate AllocMem planes, vs the ST's interleaved +2
|
|
||||||
// layout), so C is the right weapon on this port.
|
|
||||||
#define AMIGA_TILE_MAP_ROW_STRIDE ((uint16_t)(TILE_PIXELS_PER_SIDE * AMIGA_BYTES_PER_ROW))
|
|
||||||
|
|
||||||
#define AMIGA_MAP_CELL_PLANE(_dp, _plane) \
|
|
||||||
do { \
|
|
||||||
(_dp)[AMIGA_TILE_ROW_OFF(0)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 0]; \
|
|
||||||
(_dp)[AMIGA_TILE_ROW_OFF(1)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 1]; \
|
|
||||||
(_dp)[AMIGA_TILE_ROW_OFF(2)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 2]; \
|
|
||||||
(_dp)[AMIGA_TILE_ROW_OFF(3)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 3]; \
|
|
||||||
(_dp)[AMIGA_TILE_ROW_OFF(4)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 4]; \
|
|
||||||
(_dp)[AMIGA_TILE_ROW_OFF(5)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 5]; \
|
|
||||||
(_dp)[AMIGA_TILE_ROW_OFF(6)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 6]; \
|
|
||||||
(_dp)[AMIGA_TILE_ROW_OFF(7)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 7]; \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
static inline __attribute__((always_inline)) void amigaTileMapPaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, uint8_t wTiles, uint8_t hTiles, const jlTileT *tiles, const uint8_t *map) {
|
|
||||||
AmigaPlanarT *pd;
|
|
||||||
const uint8_t *tileBytes;
|
|
||||||
const uint8_t *src;
|
|
||||||
uint8_t *p0;
|
|
||||||
uint8_t *p1;
|
|
||||||
uint8_t *p2;
|
|
||||||
uint8_t *p3;
|
|
||||||
uint16_t rowBase;
|
|
||||||
uint16_t rewind;
|
|
||||||
uint8_t idx;
|
|
||||||
uint8_t ty;
|
|
||||||
uint8_t tx;
|
|
||||||
|
|
||||||
tileBytes = (const uint8_t *)tiles;
|
|
||||||
pd = (AmigaPlanarT *)dst->portData;
|
|
||||||
if (pd == NULL) {
|
|
||||||
for (ty = 0; ty < hTiles; ty++) {
|
|
||||||
for (tx = 0; tx < wTiles; tx++) {
|
|
||||||
idx = map[tx];
|
|
||||||
if (idx != TILE_MAP_SKIP) {
|
|
||||||
jlpGenericTilePaste(dst, (uint8_t)(bx + tx), (uint8_t)(by + ty), tileBytes + ((uint16_t)idx << 5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
map += wTiles;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rowBase = (uint16_t)((uint16_t)by * AMIGA_TILE_MAP_ROW_STRIDE + bx);
|
|
||||||
rewind = (uint16_t)(AMIGA_TILE_MAP_ROW_STRIDE - wTiles);
|
|
||||||
p0 = pd->planes[0] + rowBase;
|
|
||||||
p1 = pd->planes[1] + rowBase;
|
|
||||||
p2 = pd->planes[2] + rowBase;
|
|
||||||
p3 = pd->planes[3] + rowBase;
|
|
||||||
for (ty = hTiles; ty != 0u; ty--) {
|
|
||||||
for (tx = wTiles; tx != 0u; tx--) {
|
|
||||||
idx = *map++;
|
|
||||||
if (idx != TILE_MAP_SKIP) {
|
|
||||||
src = tileBytes + ((uint16_t)idx << 5);
|
|
||||||
AMIGA_MAP_CELL_PLANE(p0, 0);
|
|
||||||
AMIGA_MAP_CELL_PLANE(p1, 1);
|
|
||||||
AMIGA_MAP_CELL_PLANE(p2, 2);
|
|
||||||
AMIGA_MAP_CELL_PLANE(p3, 3);
|
|
||||||
}
|
|
||||||
p0++;
|
|
||||||
p1++;
|
|
||||||
p2++;
|
|
||||||
p3++;
|
|
||||||
}
|
|
||||||
p0 += rewind;
|
|
||||||
p1 += rewind;
|
|
||||||
p2 += rewind;
|
|
||||||
p3 += rewind;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#undef AMIGA_MAP_CELL_PLANE
|
|
||||||
#undef AMIGA_TILE_MAP_ROW_STRIDE
|
|
||||||
|
|
||||||
|
|
||||||
// Planar monochrome paste. monoTile follows the cross-port mono
|
|
||||||
// contract (include/joey/tile.h + jlpGenericTilePasteMono): 32 chunky
|
|
||||||
// nibble-pair bytes, row-major, TILE_BYTES_PER_ROW bytes per row, two
|
|
||||||
// pixels per byte. A pixel renders fgColor when its source nibble is
|
|
||||||
// nonzero, bgColor when zero; the HIGH nibble is the LEFT pixel. Each
|
|
||||||
// row's 4 source bytes fold into an 8-bit shape mask (bit 7 =
|
|
||||||
// leftmost pixel, matching the planar bit order), then each plane k
|
|
||||||
// writes
|
|
||||||
// outPlaneK = (shape & maskFgK) | (~shape & maskBgK)
|
|
||||||
// where maskXK = $FF if (X & (1 << k)) else $00.
|
|
||||||
// Eight 1bpp shape rows into the bitplanes: set bits take the
|
|
||||||
// foreground's plane bit, clear bits the background's. Shared by the
|
|
||||||
// mono paste (which folds a chunky silhouette into these rows first)
|
|
||||||
// and the glyph paste (whose input already IS these rows).
|
|
||||||
static inline __attribute__((always_inline)) void amigaTilePasteShape(AmigaPlanarT *pd, uint8_t bx, uint8_t by, const uint8_t *shape, uint8_t fgColor, uint8_t bgColor) {
|
|
||||||
uint16_t rowBase = (uint16_t)((uint16_t)by * 8u) * AMIGA_BYTES_PER_ROW + bx;
|
|
||||||
uint8_t plane;
|
|
||||||
uint8_t row;
|
|
||||||
|
|
||||||
for (plane = 0; plane < AMIGA_BITPLANES; plane++) {
|
|
||||||
uint8_t maskFg = (uint8_t)((fgColor & (1u << plane)) ? 0xFFu : 0x00u);
|
|
||||||
uint8_t maskBg = (uint8_t)((bgColor & (1u << plane)) ? 0xFFu : 0x00u);
|
|
||||||
uint8_t *p = pd->planes[plane] + rowBase;
|
|
||||||
for (row = 0; row < 8u; row++) {
|
|
||||||
uint8_t s = shape[row];
|
|
||||||
p[row * AMIGA_BYTES_PER_ROW] = (uint8_t)((uint8_t)(s & maskFg) | (uint8_t)((uint8_t)(~s) & maskBg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline __attribute__((always_inline)) void amigaTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *monoTile, uint8_t fgColor, uint8_t bgColor) {
|
|
||||||
AmigaPlanarT *pd;
|
|
||||||
uint8_t shape[TILE_PIXELS_PER_SIDE];
|
|
||||||
uint8_t row;
|
|
||||||
uint8_t col;
|
|
||||||
|
|
||||||
pd = (AmigaPlanarT *)dst->portData;
|
|
||||||
if (pd == NULL) {
|
|
||||||
jlpGenericTilePasteMono(dst, bx, by, monoTile, fgColor, bgColor);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Fold each chunky nibble-pair row into one 1bpp shape byte.
|
|
||||||
for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
|
|
||||||
uint8_t bits = 0u;
|
|
||||||
for (col = 0; col < TILE_BYTES_PER_ROW; col++) {
|
|
||||||
uint8_t srcByte = *monoTile++;
|
|
||||||
bits = (uint8_t)(bits << 2);
|
|
||||||
if (srcByte & 0xF0u) {
|
|
||||||
bits = (uint8_t)(bits | 0x02u);
|
|
||||||
}
|
|
||||||
if (srcByte & 0x0Fu) {
|
|
||||||
bits = (uint8_t)(bits | 0x01u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
shape[row] = bits;
|
|
||||||
}
|
|
||||||
amigaTilePasteShape(pd, bx, by, shape, fgColor, bgColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 1bpp glyph rows straight to the planes: a glyph byte IS the row shape
|
|
||||||
// the mono paste has to fold out of a chunky silhouette, so this skips
|
|
||||||
// the fold entirely (and cannot be handed the wrong tile format).
|
|
||||||
static inline __attribute__((always_inline)) void amigaTilePasteGlyph(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *bits, uint8_t fgColor, uint8_t bgColor) {
|
|
||||||
AmigaPlanarT *pd = (AmigaPlanarT *)dst->portData;
|
|
||||||
|
|
||||||
if (pd == NULL) {
|
|
||||||
jlpGenericTilePasteGlyph(dst, bx, by, bits, fgColor, bgColor);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
amigaTilePasteShape(pd, bx, by, bits, fgColor, bgColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ typedef char AssetTileSizeCheckT[(sizeof(jlTileT) == TILE_BYTES) ? 1 : -1];
|
||||||
#elif defined(JOEYLIB_PLATFORM_BLANK)
|
#elif defined(JOEYLIB_PLATFORM_BLANK)
|
||||||
#define EXPECTED_TILE_TARGET 3u // chunky 4bpp, same as DOS (uses the generic tile ops)
|
#define EXPECTED_TILE_TARGET 3u // chunky 4bpp, same as DOS (uses the generic tile ops)
|
||||||
#elif defined(JOEYLIB_PLATFORM_X68000)
|
#elif defined(JOEYLIB_PLATFORM_X68000)
|
||||||
// Plane-major like the Amiga (src/x68000/x68kTile.h reads jlTileT.pixels as
|
// Plane-major like the Amiga (src/m68k/planarTile.h reads jlTileT.pixels as
|
||||||
// 4 planes x 8 rows), baked by assetbake --target x68000.
|
// 4 planes x 8 rows), baked by assetbake --target x68000.
|
||||||
#define EXPECTED_TILE_TARGET 5u
|
#define EXPECTED_TILE_TARGET 5u
|
||||||
#else
|
#else
|
||||||
|
|
@ -578,26 +578,29 @@ uint16_t jlTileBankLoad(const char *path, jlTileT *outTiles, uint16_t maxTiles,
|
||||||
uint16_t toLoad;
|
uint16_t toLoad;
|
||||||
size_t got;
|
size_t got;
|
||||||
|
|
||||||
if (path == NULL || outTiles == NULL || maxTiles == 0u) {
|
if (outTiles == NULL || maxTiles == 0u) {
|
||||||
return 0u;
|
return 0u;
|
||||||
}
|
}
|
||||||
// The target byte for .tbk must match the build platform. .spr
|
// Define the whole outValid array up front -- BEFORE the file is
|
||||||
// files carry target=0; tiles carry the per-platform value. A
|
// opened -- so leftover slots read false exactly as the tile.h
|
||||||
// mismatch is a build-system bug -- the per-target make rule
|
// contract promises, regardless of caller pre-zeroing. Clearing it
|
||||||
// should have produced a .tbk with the matching target byte.
|
// only after the header validated left a refused, missing or
|
||||||
if (!openReadValidateHeader(path, "JTB1", EXPECTED_TILE_TARGET, header, outPalette, &fp)) {
|
// short-header load reporting whatever an earlier load into the
|
||||||
return 0u;
|
// same array had marked valid (caught by scripts/check-tilebank.sh).
|
||||||
}
|
|
||||||
tileCount = (uint16_t)(header[6] | (header[7] << 8));
|
|
||||||
|
|
||||||
// Define the whole outValid array up front so leftover slots read
|
|
||||||
// false exactly as the tile.h contract promises, regardless of
|
|
||||||
// caller pre-zeroing.
|
|
||||||
if (outValid != NULL) {
|
if (outValid != NULL) {
|
||||||
for (i = 0; i < maxTiles; i++) {
|
for (i = 0; i < maxTiles; i++) {
|
||||||
outValid[i] = false;
|
outValid[i] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// The target byte for .tbk must match the build platform. .spr
|
||||||
|
// files carry target=0; tiles carry the per-platform value. A
|
||||||
|
// mismatch is a build-system bug -- the per-target make rule
|
||||||
|
// should have produced a .tbk with the matching target byte. A
|
||||||
|
// NULL path needs no guard here: jlDataOpen refuses it.
|
||||||
|
if (!openReadValidateHeader(path, "JTB1", EXPECTED_TILE_TARGET, header, outPalette, &fp)) {
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
tileCount = (uint16_t)(header[6] | (header[7] << 8));
|
||||||
|
|
||||||
toLoad = (tileCount < maxTiles) ? tileCount : maxTiles;
|
toLoad = (tileCount < maxTiles) ? tileCount : maxTiles;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,8 +96,7 @@ void jlLogF(const char *fmt, ...) {
|
||||||
(void)vsnprintf(line, sizeof(line), fmt, args);
|
(void)vsnprintf(line, sizeof(line), fmt, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
ringAppend(line);
|
ringAppend(line);
|
||||||
// Callers' formats mostly end in \n already; jlLog adds one for
|
ringAppend("\n"); // one call is one line, as with jlLog, on every port
|
||||||
// plain strings, so nothing is appended here.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -383,8 +383,9 @@ void iigsIrqLatencyTighten(void);
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// 68k tile ops as always-inline headers (NATIVE-PERF Phase 2 R4a).
|
// 68k tile ops as always-inline headers (NATIVE-PERF Phase 2 R4a).
|
||||||
//
|
//
|
||||||
// The ST/Amiga tile overrides live in stTile.h / amigaTile.h as
|
// The ST tile overrides live in stTile.h as static inline functions named
|
||||||
// static inline functions named stTile* / amigaTile*. Aliasing the
|
// stTile*; the Amiga and X68000 share planarTile* (src/m68k/planarTile.h,
|
||||||
|
// through the amigaTile.h / x68kTile.h shims). Aliasing the
|
||||||
// jlpTile* dispatch names to them HERE (before the dispatch tail)
|
// jlpTile* dispatch names to them HERE (before the dispatch tail)
|
||||||
// makes the tail's #if !defined(jlpTile*) guards skip their extern
|
// makes the tail's #if !defined(jlpTile*) guards skip their extern
|
||||||
// prototypes -- same mechanism as the IIgs macro block above -- and
|
// prototypes -- same mechanism as the IIgs macro block above -- and
|
||||||
|
|
@ -407,24 +408,16 @@ void iigsIrqLatencyTighten(void);
|
||||||
// union-mark stanza lives exactly twice in tile.c (shared arm +
|
// union-mark stanza lives exactly twice in tile.c (shared arm +
|
||||||
// generic) no matter how many ports bring engines.
|
// generic) no matter how many ports bring engines.
|
||||||
#define jlpTileMapPaste stTileMapPaste
|
#define jlpTileMapPaste stTileMapPaste
|
||||||
#elif defined(JOEYLIB_PLATFORM_AMIGA)
|
#elif defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_X68000)
|
||||||
#define jlpTileFill amigaTileFill
|
// One set of planar bodies for both (src/m68k/planarTile.h).
|
||||||
#define jlpTileCopy amigaTileCopy
|
#define jlpTileFill planarTileFill
|
||||||
#define jlpTileCopyMasked amigaTileCopyMasked
|
#define jlpTileCopy planarTileCopy
|
||||||
#define jlpTilePaste amigaTilePaste
|
#define jlpTileCopyMasked planarTileCopyMasked
|
||||||
#define jlpTilePasteMono amigaTilePasteMono
|
#define jlpTilePaste planarTilePaste
|
||||||
#define jlpTilePasteGlyph amigaTilePasteGlyph
|
#define jlpTilePasteMono planarTilePasteMono
|
||||||
#define jlpTileSnap amigaTileSnap
|
#define jlpTilePasteGlyph planarTilePasteGlyph
|
||||||
#define jlpTileMapPaste amigaTileMapPaste
|
#define jlpTileSnap planarTileSnap
|
||||||
#elif defined(JOEYLIB_PLATFORM_X68000)
|
#define jlpTileMapPaste planarTileMapPaste
|
||||||
#define jlpTileFill x68kTileFill
|
|
||||||
#define jlpTileCopy x68kTileCopy
|
|
||||||
#define jlpTileCopyMasked x68kTileCopyMasked
|
|
||||||
#define jlpTilePaste x68kTilePaste
|
|
||||||
#define jlpTilePasteMono x68kTilePasteMono
|
|
||||||
#define jlpTilePasteGlyph x68kTilePasteGlyph
|
|
||||||
#define jlpTileSnap x68kTileSnap
|
|
||||||
#define jlpTileMapPaste x68kTileMapPaste
|
|
||||||
#elif defined(JOEYLIB_PLATFORM_DOS)
|
#elif defined(JOEYLIB_PLATFORM_DOS)
|
||||||
// DOS overrides only the whole-map walker (dosTile.h, W3); the
|
// DOS overrides only the whole-map walker (dosTile.h, W3); the
|
||||||
// single-tile ops stay on the chunky generics.
|
// single-tile ops stay on the chunky generics.
|
||||||
|
|
|
||||||
377
src/m68k/planarTile.h
Normal file
377
src/m68k/planarTile.h
Normal file
|
|
@ -0,0 +1,377 @@
|
||||||
|
// Planar tile ops shared by the Amiga and the X68000, as always-inline
|
||||||
|
// functions (NATIVE-PERF Phase 2 R4a). Both ports keep their surfaces as four
|
||||||
|
// separate bitplanes of 40 bytes a row with the same portData layout
|
||||||
|
// (AmigaPlanarT / X68kPlanarT, field for field), so an 8x8 tile is 8 rows x 4
|
||||||
|
// planes x 1 byte on both and one body serves them. The public wrappers in
|
||||||
|
// src/core/tile.c inline these, deleting the second full cdecl call layer
|
||||||
|
// (~130-190 cycles/op on the 68000). The jlpTile* dispatch names are
|
||||||
|
// macro-aliased to these in port.h, which also #includes the port's shim
|
||||||
|
// (amigaTile.h / x68kTile.h) at its tail -- AFTER the jlpGenericTile*
|
||||||
|
// prototypes the portData == NULL fallbacks call. Do not include this header
|
||||||
|
// directly: the shim defines, before including it,
|
||||||
|
// PLANAR_TILE_DATA_T the port's planar portData type
|
||||||
|
// PLANAR_TILE_BYTES_PER_ROW bytes per plane row (40)
|
||||||
|
// PLANAR_TILE_PLANES bitplanes (4)
|
||||||
|
#ifndef JOEY_PLANAR_TILE_H
|
||||||
|
#define JOEY_PLANAR_TILE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "joey/tile.h"
|
||||||
|
|
||||||
|
// 8-row unrolled tile access (NATIVE-PERF Phase 2 R8): row offsets
|
||||||
|
// are compile-time constants row * 40, so each row is one d16(An)
|
||||||
|
// access with no pointer stepping and no loop control. gcc
|
||||||
|
// -O2 keeps the equivalent row loops rolled as dbf (objdump-verified,
|
||||||
|
// ~18 cycles/row of pure loop overhead). Only the row dimension
|
||||||
|
// unrolls -- the 4-plane loop keeps real per-plane setup and stays
|
||||||
|
// register-light per the m68k-gcc-unroll-backfire lesson.
|
||||||
|
#define PLANAR_TILE_ROW_OFF(_r) ((uint16_t)((_r) * PLANAR_TILE_BYTES_PER_ROW))
|
||||||
|
|
||||||
|
// Phase 5 planar dual-write for tile ops, fully planar after Phase 9
|
||||||
|
// dropped the chunky shadow. All tiles are 8-pixel aligned (8x8 blocks
|
||||||
|
// at multiples of 8), so plane writes are byte-aligned -- one plane
|
||||||
|
// byte per row, 8 rows per tile, no edge masks. Stride between rows
|
||||||
|
// in a plane is PLANAR_TILE_BYTES_PER_ROW (40).
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline)) void planarTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex) {
|
||||||
|
PLANAR_TILE_DATA_T *pd;
|
||||||
|
uint16_t plane;
|
||||||
|
uint8_t fillByte;
|
||||||
|
uint8_t *p;
|
||||||
|
|
||||||
|
pd = (PLANAR_TILE_DATA_T *)s->portData;
|
||||||
|
if (pd == NULL) {
|
||||||
|
jlpGenericTileFill(s, bx, by, colorIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (plane = 0; plane < PLANAR_TILE_PLANES; plane++) {
|
||||||
|
fillByte = ((colorIndex >> plane) & 1u) ? 0xFFu : 0x00u;
|
||||||
|
p = pd->planes[plane] + (uint16_t)by * 8u * PLANAR_TILE_BYTES_PER_ROW + bx;
|
||||||
|
p[PLANAR_TILE_ROW_OFF(0)] = fillByte;
|
||||||
|
p[PLANAR_TILE_ROW_OFF(1)] = fillByte;
|
||||||
|
p[PLANAR_TILE_ROW_OFF(2)] = fillByte;
|
||||||
|
p[PLANAR_TILE_ROW_OFF(3)] = fillByte;
|
||||||
|
p[PLANAR_TILE_ROW_OFF(4)] = fillByte;
|
||||||
|
p[PLANAR_TILE_ROW_OFF(5)] = fillByte;
|
||||||
|
p[PLANAR_TILE_ROW_OFF(6)] = fillByte;
|
||||||
|
p[PLANAR_TILE_ROW_OFF(7)] = fillByte;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline)) void planarTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy) {
|
||||||
|
PLANAR_TILE_DATA_T *dstPd;
|
||||||
|
PLANAR_TILE_DATA_T *srcPd;
|
||||||
|
uint16_t plane;
|
||||||
|
uint8_t *dp;
|
||||||
|
const uint8_t *sp;
|
||||||
|
|
||||||
|
dstPd = (PLANAR_TILE_DATA_T *)dst->portData;
|
||||||
|
srcPd = (PLANAR_TILE_DATA_T *)src->portData;
|
||||||
|
if (dstPd == NULL || srcPd == NULL) {
|
||||||
|
jlpGenericTileCopy(dst, dstBx, dstBy, src, srcBx, srcBy);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (plane = 0; plane < PLANAR_TILE_PLANES; plane++) {
|
||||||
|
dp = dstPd->planes[plane] + (uint16_t)dstBy * 8u * PLANAR_TILE_BYTES_PER_ROW + dstBx;
|
||||||
|
sp = srcPd->planes[plane] + (uint16_t)srcBy * 8u * PLANAR_TILE_BYTES_PER_ROW + srcBx;
|
||||||
|
dp[PLANAR_TILE_ROW_OFF(0)] = sp[PLANAR_TILE_ROW_OFF(0)];
|
||||||
|
dp[PLANAR_TILE_ROW_OFF(1)] = sp[PLANAR_TILE_ROW_OFF(1)];
|
||||||
|
dp[PLANAR_TILE_ROW_OFF(2)] = sp[PLANAR_TILE_ROW_OFF(2)];
|
||||||
|
dp[PLANAR_TILE_ROW_OFF(3)] = sp[PLANAR_TILE_ROW_OFF(3)];
|
||||||
|
dp[PLANAR_TILE_ROW_OFF(4)] = sp[PLANAR_TILE_ROW_OFF(4)];
|
||||||
|
dp[PLANAR_TILE_ROW_OFF(5)] = sp[PLANAR_TILE_ROW_OFF(5)];
|
||||||
|
dp[PLANAR_TILE_ROW_OFF(6)] = sp[PLANAR_TILE_ROW_OFF(6)];
|
||||||
|
dp[PLANAR_TILE_ROW_OFF(7)] = sp[PLANAR_TILE_ROW_OFF(7)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Pure-planar masked copy. For each row of the 8x8 tile, read 4 src
|
||||||
|
// plane bytes; compute a per-pixel "non-transparent" mask via XOR
|
||||||
|
// against the transparent index's per-plane bit pattern (a pixel
|
||||||
|
// matches transparent IFF all 4 plane bits match transparent's 4
|
||||||
|
// bits = OR of 4 XOR'd bytes is 0 in that bit). Then for each plane,
|
||||||
|
// dst = (dst & ~mask) | (src & mask) -- copy src bits at mask-set
|
||||||
|
// positions, preserve dst bits elsewhere.
|
||||||
|
static inline __attribute__((always_inline)) void planarTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparent) {
|
||||||
|
PLANAR_TILE_DATA_T *dstPd;
|
||||||
|
PLANAR_TILE_DATA_T *srcPd;
|
||||||
|
uint8_t transBitByte[PLANAR_TILE_PLANES];
|
||||||
|
uint16_t i;
|
||||||
|
uint8_t row;
|
||||||
|
uint16_t srcByteOff;
|
||||||
|
uint16_t dstByteOff;
|
||||||
|
uint8_t srcPlaneBytes[PLANAR_TILE_PLANES];
|
||||||
|
uint8_t maskByte;
|
||||||
|
|
||||||
|
dstPd = (PLANAR_TILE_DATA_T *)dst->portData;
|
||||||
|
srcPd = (PLANAR_TILE_DATA_T *)src->portData;
|
||||||
|
if (dstPd == NULL || srcPd == NULL) {
|
||||||
|
jlpGenericTileCopyMasked(dst, dstBx, dstBy, src, srcBx, srcBy, transparent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
transparent = (uint8_t)(transparent & 0x0Fu);
|
||||||
|
/* Per-plane "all bits set if transparent's bit at this plane is 1
|
||||||
|
* else all 0" -- so XOR gives bit set where pixel differs from
|
||||||
|
* transparent in that plane. */
|
||||||
|
for (i = 0; i < PLANAR_TILE_PLANES; i++) {
|
||||||
|
transBitByte[i] = ((transparent >> i) & 1u) ? 0xFFu : 0x00u;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (row = 0; row < 8u; row++) {
|
||||||
|
srcByteOff = (uint16_t)((uint16_t)srcBy * 8u + row) * PLANAR_TILE_BYTES_PER_ROW + srcBx;
|
||||||
|
dstByteOff = (uint16_t)((uint16_t)dstBy * 8u + row) * PLANAR_TILE_BYTES_PER_ROW + dstBx;
|
||||||
|
srcPlaneBytes[0] = srcPd->planes[0][srcByteOff];
|
||||||
|
srcPlaneBytes[1] = srcPd->planes[1][srcByteOff];
|
||||||
|
srcPlaneBytes[2] = srcPd->planes[2][srcByteOff];
|
||||||
|
srcPlaneBytes[3] = srcPd->planes[3][srcByteOff];
|
||||||
|
/* maskByte: bit set where pixel differs from transparent in
|
||||||
|
* ANY plane -- i.e., where the pixel is non-transparent. */
|
||||||
|
maskByte = (uint8_t)((srcPlaneBytes[0] ^ transBitByte[0])
|
||||||
|
| (srcPlaneBytes[1] ^ transBitByte[1])
|
||||||
|
| (srcPlaneBytes[2] ^ transBitByte[2])
|
||||||
|
| (srcPlaneBytes[3] ^ transBitByte[3]));
|
||||||
|
for (i = 0; i < PLANAR_TILE_PLANES; i++) {
|
||||||
|
uint8_t existing = dstPd->planes[i][dstByteOff];
|
||||||
|
dstPd->planes[i][dstByteOff] = (uint8_t)((existing & (uint8_t)~maskByte)
|
||||||
|
| (srcPlaneBytes[i] & maskByte));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Phase 9 plane-to-chunky derivation for jlTileSnap. Reads 8 plane
|
||||||
|
// bytes (1 byte per row x 8 rows) from each of 4 planes for the
|
||||||
|
// 8-pixel-aligned tile column at bx, then assembles 32 chunky bytes
|
||||||
|
// (4 per row x 8 rows, packed 2 px/byte high-then-low nibble) into
|
||||||
|
// On these ports, jlTileT.pixels is opaque port-specific storage (cross-
|
||||||
|
// platform tile.c never reads it directly when s->pixels is NULL).
|
||||||
|
// We use those 32 bytes as 4 planes x 8 rows, plane-major:
|
||||||
|
// bytes [0..7] = plane 0, rows 0..7
|
||||||
|
// bytes [8..15] = plane 1, rows 0..7
|
||||||
|
// bytes [16..23] = plane 2, rows 0..7
|
||||||
|
// bytes [24..31] = plane 3, rows 0..7
|
||||||
|
// snap/paste then become 32 plain byte loads + stores -- no chunky
|
||||||
|
// <-> planar conversion at all. The previous c2p-based path paid
|
||||||
|
// 4 KB LUT lookups + bit shuffling per pixel; this is ~50x cheaper.
|
||||||
|
#define PLANAR_TILE_PLANE_STRIDE 8
|
||||||
|
static inline __attribute__((always_inline)) void planarTileSnap(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *chunkyOut) {
|
||||||
|
PLANAR_TILE_DATA_T *pd;
|
||||||
|
uint16_t rowBase;
|
||||||
|
uint8_t plane;
|
||||||
|
|
||||||
|
pd = (PLANAR_TILE_DATA_T *)src->portData;
|
||||||
|
if (pd == NULL) {
|
||||||
|
jlpGenericTileSnap(src, bx, by, chunkyOut);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rowBase = (uint16_t)((uint16_t)by * 8u) * PLANAR_TILE_BYTES_PER_ROW + bx;
|
||||||
|
for (plane = 0; plane < PLANAR_TILE_PLANES; plane++) {
|
||||||
|
const uint8_t *p = pd->planes[plane] + rowBase;
|
||||||
|
uint8_t *q = chunkyOut + plane * PLANAR_TILE_PLANE_STRIDE;
|
||||||
|
q[0] = p[PLANAR_TILE_ROW_OFF(0)];
|
||||||
|
q[1] = p[PLANAR_TILE_ROW_OFF(1)];
|
||||||
|
q[2] = p[PLANAR_TILE_ROW_OFF(2)];
|
||||||
|
q[3] = p[PLANAR_TILE_ROW_OFF(3)];
|
||||||
|
q[4] = p[PLANAR_TILE_ROW_OFF(4)];
|
||||||
|
q[5] = p[PLANAR_TILE_ROW_OFF(5)];
|
||||||
|
q[6] = p[PLANAR_TILE_ROW_OFF(6)];
|
||||||
|
q[7] = p[PLANAR_TILE_ROW_OFF(7)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline)) void planarTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *chunkyTile) {
|
||||||
|
PLANAR_TILE_DATA_T *pd;
|
||||||
|
uint8_t plane;
|
||||||
|
uint16_t rowBase;
|
||||||
|
|
||||||
|
pd = (PLANAR_TILE_DATA_T *)dst->portData;
|
||||||
|
if (pd == NULL) {
|
||||||
|
jlpGenericTilePaste(dst, bx, by, chunkyTile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* jlTileT.pixels[] holds plane-major planar bytes (see
|
||||||
|
* jlpTileSnap header for layout). Paste = 32 byte stores
|
||||||
|
* with no chunky -> planar conversion. */
|
||||||
|
rowBase = (uint16_t)((uint16_t)by * 8u) * PLANAR_TILE_BYTES_PER_ROW + bx;
|
||||||
|
for (plane = 0; plane < PLANAR_TILE_PLANES; plane++) {
|
||||||
|
const uint8_t *q = chunkyTile + plane * PLANAR_TILE_PLANE_STRIDE;
|
||||||
|
uint8_t *p = pd->planes[plane] + rowBase;
|
||||||
|
p[PLANAR_TILE_ROW_OFF(0)] = q[0];
|
||||||
|
p[PLANAR_TILE_ROW_OFF(1)] = q[1];
|
||||||
|
p[PLANAR_TILE_ROW_OFF(2)] = q[2];
|
||||||
|
p[PLANAR_TILE_ROW_OFF(3)] = q[3];
|
||||||
|
p[PLANAR_TILE_ROW_OFF(4)] = q[4];
|
||||||
|
p[PLANAR_TILE_ROW_OFF(5)] = q[5];
|
||||||
|
p[PLANAR_TILE_ROW_OFF(6)] = q[6];
|
||||||
|
p[PLANAR_TILE_ROW_OFF(7)] = q[7];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Whole-map paste engine (NATIVE-PERF W3 item 9). jlTileMapPaste's
|
||||||
|
// shared whole-map arm calls this ONCE per map: the portData load +
|
||||||
|
// NULL test, the by*320 row-base derive, and the four plane base
|
||||||
|
// pointers are hoisted out of the per-cell loop -- the per-cell
|
||||||
|
// planarTilePaste route reloads portData, rebuilds the cell offset,
|
||||||
|
// and re-reads pd->planes[0..3] from the struct every cell (~172 of
|
||||||
|
// its ~776 cyc/cell, objdump-measured). The four dst pointers stay
|
||||||
|
// resident in a0-a3 (one byte per column, one row stride per tile
|
||||||
|
// row), so each cell is 32 move.b off compile-time displacements
|
||||||
|
// with zero per-plane setup. Plane order, row order, and written
|
||||||
|
// values are identical to per-cell planarTilePaste -- pixel output is
|
||||||
|
// byte-identical. movep is unusable here (plane bytes sit 8000 bytes
|
||||||
|
// apart across separate AllocMem planes, vs the ST's interleaved +2
|
||||||
|
// layout), so C is the right weapon on this port.
|
||||||
|
#define PLANAR_TILE_MAP_ROW_STRIDE ((uint16_t)(TILE_PIXELS_PER_SIDE * PLANAR_TILE_BYTES_PER_ROW))
|
||||||
|
|
||||||
|
#define PLANAR_MAP_CELL_PLANE(_dp, _plane) \
|
||||||
|
do { \
|
||||||
|
(_dp)[PLANAR_TILE_ROW_OFF(0)] = src[(_plane) * PLANAR_TILE_PLANE_STRIDE + 0]; \
|
||||||
|
(_dp)[PLANAR_TILE_ROW_OFF(1)] = src[(_plane) * PLANAR_TILE_PLANE_STRIDE + 1]; \
|
||||||
|
(_dp)[PLANAR_TILE_ROW_OFF(2)] = src[(_plane) * PLANAR_TILE_PLANE_STRIDE + 2]; \
|
||||||
|
(_dp)[PLANAR_TILE_ROW_OFF(3)] = src[(_plane) * PLANAR_TILE_PLANE_STRIDE + 3]; \
|
||||||
|
(_dp)[PLANAR_TILE_ROW_OFF(4)] = src[(_plane) * PLANAR_TILE_PLANE_STRIDE + 4]; \
|
||||||
|
(_dp)[PLANAR_TILE_ROW_OFF(5)] = src[(_plane) * PLANAR_TILE_PLANE_STRIDE + 5]; \
|
||||||
|
(_dp)[PLANAR_TILE_ROW_OFF(6)] = src[(_plane) * PLANAR_TILE_PLANE_STRIDE + 6]; \
|
||||||
|
(_dp)[PLANAR_TILE_ROW_OFF(7)] = src[(_plane) * PLANAR_TILE_PLANE_STRIDE + 7]; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline)) void planarTileMapPaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, uint8_t wTiles, uint8_t hTiles, const jlTileT *tiles, const uint8_t *map) {
|
||||||
|
PLANAR_TILE_DATA_T *pd;
|
||||||
|
const uint8_t *tileBytes;
|
||||||
|
const uint8_t *src;
|
||||||
|
uint8_t *p0;
|
||||||
|
uint8_t *p1;
|
||||||
|
uint8_t *p2;
|
||||||
|
uint8_t *p3;
|
||||||
|
uint16_t rowBase;
|
||||||
|
uint16_t rewind;
|
||||||
|
uint8_t idx;
|
||||||
|
uint8_t ty;
|
||||||
|
uint8_t tx;
|
||||||
|
|
||||||
|
tileBytes = (const uint8_t *)tiles;
|
||||||
|
pd = (PLANAR_TILE_DATA_T *)dst->portData;
|
||||||
|
if (pd == NULL) {
|
||||||
|
for (ty = 0; ty < hTiles; ty++) {
|
||||||
|
for (tx = 0; tx < wTiles; tx++) {
|
||||||
|
idx = map[tx];
|
||||||
|
if (idx != TILE_MAP_SKIP) {
|
||||||
|
jlpGenericTilePaste(dst, (uint8_t)(bx + tx), (uint8_t)(by + ty), tileBytes + ((uint16_t)idx << 5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
map += wTiles;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rowBase = (uint16_t)((uint16_t)by * PLANAR_TILE_MAP_ROW_STRIDE + bx);
|
||||||
|
rewind = (uint16_t)(PLANAR_TILE_MAP_ROW_STRIDE - wTiles);
|
||||||
|
p0 = pd->planes[0] + rowBase;
|
||||||
|
p1 = pd->planes[1] + rowBase;
|
||||||
|
p2 = pd->planes[2] + rowBase;
|
||||||
|
p3 = pd->planes[3] + rowBase;
|
||||||
|
for (ty = hTiles; ty != 0u; ty--) {
|
||||||
|
for (tx = wTiles; tx != 0u; tx--) {
|
||||||
|
idx = *map++;
|
||||||
|
if (idx != TILE_MAP_SKIP) {
|
||||||
|
src = tileBytes + ((uint16_t)idx << 5);
|
||||||
|
PLANAR_MAP_CELL_PLANE(p0, 0);
|
||||||
|
PLANAR_MAP_CELL_PLANE(p1, 1);
|
||||||
|
PLANAR_MAP_CELL_PLANE(p2, 2);
|
||||||
|
PLANAR_MAP_CELL_PLANE(p3, 3);
|
||||||
|
}
|
||||||
|
p0++;
|
||||||
|
p1++;
|
||||||
|
p2++;
|
||||||
|
p3++;
|
||||||
|
}
|
||||||
|
p0 += rewind;
|
||||||
|
p1 += rewind;
|
||||||
|
p2 += rewind;
|
||||||
|
p3 += rewind;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef PLANAR_MAP_CELL_PLANE
|
||||||
|
#undef PLANAR_TILE_MAP_ROW_STRIDE
|
||||||
|
|
||||||
|
|
||||||
|
// Planar monochrome paste. monoTile follows the cross-port mono
|
||||||
|
// contract (include/joey/tile.h + jlpGenericTilePasteMono): 32 chunky
|
||||||
|
// nibble-pair bytes, row-major, TILE_BYTES_PER_ROW bytes per row, two
|
||||||
|
// pixels per byte. A pixel renders fgColor when its source nibble is
|
||||||
|
// nonzero, bgColor when zero; the HIGH nibble is the LEFT pixel. Each
|
||||||
|
// row's 4 source bytes fold into an 8-bit shape mask (bit 7 =
|
||||||
|
// leftmost pixel, matching the planar bit order), then each plane k
|
||||||
|
// writes
|
||||||
|
// outPlaneK = (shape & maskFgK) | (~shape & maskBgK)
|
||||||
|
// where maskXK = $FF if (X & (1 << k)) else $00.
|
||||||
|
// Eight 1bpp shape rows into the bitplanes: set bits take the
|
||||||
|
// foreground's plane bit, clear bits the background's. Shared by the
|
||||||
|
// mono paste (which folds a chunky silhouette into these rows first)
|
||||||
|
// and the glyph paste (whose input already IS these rows).
|
||||||
|
static inline __attribute__((always_inline)) void planarTilePasteShape(PLANAR_TILE_DATA_T *pd, uint8_t bx, uint8_t by, const uint8_t *shape, uint8_t fgColor, uint8_t bgColor) {
|
||||||
|
uint16_t rowBase = (uint16_t)((uint16_t)by * 8u) * PLANAR_TILE_BYTES_PER_ROW + bx;
|
||||||
|
uint8_t plane;
|
||||||
|
uint8_t row;
|
||||||
|
|
||||||
|
for (plane = 0; plane < PLANAR_TILE_PLANES; plane++) {
|
||||||
|
uint8_t maskFg = (uint8_t)((fgColor & (1u << plane)) ? 0xFFu : 0x00u);
|
||||||
|
uint8_t maskBg = (uint8_t)((bgColor & (1u << plane)) ? 0xFFu : 0x00u);
|
||||||
|
uint8_t *p = pd->planes[plane] + rowBase;
|
||||||
|
for (row = 0; row < 8u; row++) {
|
||||||
|
uint8_t s = shape[row];
|
||||||
|
p[row * PLANAR_TILE_BYTES_PER_ROW] = (uint8_t)((uint8_t)(s & maskFg) | (uint8_t)((uint8_t)(~s) & maskBg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline)) void planarTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *monoTile, uint8_t fgColor, uint8_t bgColor) {
|
||||||
|
PLANAR_TILE_DATA_T *pd;
|
||||||
|
uint8_t shape[TILE_PIXELS_PER_SIDE];
|
||||||
|
uint8_t row;
|
||||||
|
uint8_t col;
|
||||||
|
|
||||||
|
pd = (PLANAR_TILE_DATA_T *)dst->portData;
|
||||||
|
if (pd == NULL) {
|
||||||
|
jlpGenericTilePasteMono(dst, bx, by, monoTile, fgColor, bgColor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Fold each chunky nibble-pair row into one 1bpp shape byte.
|
||||||
|
for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
|
||||||
|
uint8_t bits = 0u;
|
||||||
|
for (col = 0; col < TILE_BYTES_PER_ROW; col++) {
|
||||||
|
uint8_t srcByte = *monoTile++;
|
||||||
|
bits = (uint8_t)(bits << 2);
|
||||||
|
if (srcByte & 0xF0u) {
|
||||||
|
bits = (uint8_t)(bits | 0x02u);
|
||||||
|
}
|
||||||
|
if (srcByte & 0x0Fu) {
|
||||||
|
bits = (uint8_t)(bits | 0x01u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shape[row] = bits;
|
||||||
|
}
|
||||||
|
planarTilePasteShape(pd, bx, by, shape, fgColor, bgColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 1bpp glyph rows straight to the planes: a glyph byte IS the row shape
|
||||||
|
// the mono paste has to fold out of a chunky silhouette, so this skips
|
||||||
|
// the fold entirely (and cannot be handed the wrong tile format).
|
||||||
|
static inline __attribute__((always_inline)) void planarTilePasteGlyph(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *bits, uint8_t fgColor, uint8_t bgColor) {
|
||||||
|
PLANAR_TILE_DATA_T *pd = (PLANAR_TILE_DATA_T *)dst->portData;
|
||||||
|
|
||||||
|
if (pd == NULL) {
|
||||||
|
jlpGenericTilePasteGlyph(dst, bx, by, bits, fgColor, bgColor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
planarTilePasteShape(pd, bx, by, bits, fgColor, bgColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -324,7 +324,7 @@ void jlpAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
||||||
XMP_FORMAT_MONO | XMP_FORMAT_8BIT | XMP_FORMAT_UNSIGNED);
|
XMP_FORMAT_MONO | XMP_FORMAT_8BIT | XMP_FORMAT_UNSIGNED);
|
||||||
if (gX68kModStartRc != 0) {
|
if (gX68kModStartRc != 0) {
|
||||||
// Do not fail silently -- that is exactly how the rate bug survived.
|
// Do not fail silently -- that is exactly how the rate bug survived.
|
||||||
jlLogF("x68k: xmp_start_player failed rc=%d rate=%d\n",
|
jlLogF("x68k: xmp_start_player failed rc=%d rate=%d",
|
||||||
(int)gX68kModStartRc, (int)MSM6258_RATE);
|
(int)gX68kModStartRc, (int)MSM6258_RATE);
|
||||||
xmp_release_module(gXmpCtx);
|
xmp_release_module(gXmpCtx);
|
||||||
gXmpLoaded = false;
|
gXmpLoaded = false;
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,17 @@
|
||||||
// queue -- purely defensive, so a wedged _B_KEYSNS can't spin forever.
|
// queue -- purely defensive, so a wedged _B_KEYSNS can't spin forever.
|
||||||
#define X68K_KEY_DRAIN_GUARD 32u
|
#define X68K_KEY_DRAIN_GUARD 32u
|
||||||
|
|
||||||
|
// _iocs_skey_mod modes (IOCS $7D). The IPL ROM powers up in AUTO -- its boot
|
||||||
|
// copies $FF into the mode byte -- and in AUTO every press of the LEFT mouse
|
||||||
|
// button steps the ROM's own display: its mouse pointer on, then its software
|
||||||
|
// keyboard, then both off (IPL 1.0, from $FF467E). Both draw into the text
|
||||||
|
// plane, which is this port's display, and while that keyboard is up a RIGHT
|
||||||
|
// press over it types a key and never reaches _MS_GETDT (found by
|
||||||
|
// verify-x68000-serial-mouse.sh: two left clicks, and the next right click
|
||||||
|
// went missing). OFF for as long as a program runs; AUTO is handed back.
|
||||||
|
#define X68K_SKEY_OFF 0
|
||||||
|
#define X68K_SKEY_AUTO (-1)
|
||||||
|
|
||||||
// MC68901 MFP general-purpose I/O. Bit 4 is the CRTC's V-DISP line.
|
// MC68901 MFP general-purpose I/O. Bit 4 is the CRTC's V-DISP line.
|
||||||
#define X68K_MFP_GPIP ((volatile uint8_t *)0xE88001L)
|
#define X68K_MFP_GPIP ((volatile uint8_t *)0xE88001L)
|
||||||
#define X68K_GPIP_VDISP 0x10u
|
#define X68K_GPIP_VDISP 0x10u
|
||||||
|
|
@ -294,7 +305,11 @@ void jlpShutdown(void) {
|
||||||
// next Human68k program's pointer to a corner of the screen. _MS_INIT
|
// next Human68k program's pointer to a corner of the screen. _MS_INIT
|
||||||
// re-establishes IOCS's own defaults, and it runs AFTER the crtmod restore
|
// re-establishes IOCS's own defaults, and it runs AFTER the crtmod restore
|
||||||
// above so those defaults match the screen the user is going back to.
|
// above so those defaults match the screen the user is going back to.
|
||||||
|
// The software keyboard's AUTO mode goes back too (see X68K_SKEY_OFF): IOCS
|
||||||
|
// has no call that reports the mode, so the ROM's power-on default is
|
||||||
|
// what is restored.
|
||||||
_iocs_ms_init();
|
_iocs_ms_init();
|
||||||
|
(void)_iocs_skey_mod(X68K_SKEY_AUTO, 0, 0);
|
||||||
gModeSet = false;
|
gModeSet = false;
|
||||||
if (gPrevSsp >= 0) {
|
if (gPrevSsp >= 0) {
|
||||||
(void)_dos_super(gPrevSsp); // back to user mode for Human68k
|
(void)_dos_super(gPrevSsp); // back to user mode for Human68k
|
||||||
|
|
@ -919,9 +934,13 @@ void jlpInputInit(void) {
|
||||||
// verify-x68000-mouse.sh), so re-reading it between packets
|
// verify-x68000-mouse.sh), so re-reading it between packets
|
||||||
// re-counts stale motion. The tracker integrates each packet
|
// re-counts stale motion. The tracker integrates each packet
|
||||||
// exactly once at receive time and clamps to the _MS_LIMIT box,
|
// exactly once at receive time and clamps to the _MS_LIMIT box,
|
||||||
// which is set to stage coordinates here -- no cursor is ever
|
// which is set to stage coordinates here -- the tracker just does
|
||||||
// shown (_MS_CURON is never called), the tracker just does the
|
// the bookkeeping. No IOCS pointer is ever shown: the ROM's
|
||||||
// bookkeeping.
|
// software keyboard goes to OFF first (X68K_SKEY_OFF), so no left
|
||||||
|
// click can bring its pointer or keyboard up, and _MS_INIT takes
|
||||||
|
// down a pointer already up (switching the keyboard off can leave
|
||||||
|
// one).
|
||||||
|
(void)_iocs_skey_mod(X68K_SKEY_OFF, 0, 0);
|
||||||
_iocs_ms_init();
|
_iocs_ms_init();
|
||||||
_iocs_ms_limit(0, 0, SURFACE_WIDTH - 1, SURFACE_HEIGHT - 1);
|
_iocs_ms_limit(0, 0, SURFACE_WIDTH - 1, SURFACE_HEIGHT - 1);
|
||||||
_iocs_ms_curst(SURFACE_WIDTH / 2, SURFACE_HEIGHT / 2);
|
_iocs_ms_curst(SURFACE_WIDTH / 2, SURFACE_HEIGHT / 2);
|
||||||
|
|
|
||||||
|
|
@ -23,30 +23,31 @@
|
||||||
|
|
||||||
// ----- Line configuration --------------------------------------------------
|
// ----- Line configuration --------------------------------------------------
|
||||||
//
|
//
|
||||||
// _iocs_set232c's mode word, from the IOCS call reference (reproduced on Data
|
// RSDRV.SYS, Human68k's RS-232C driver, is REQUIRED: jlpSerialOpen fails
|
||||||
// Crystal's X68k/IOCS page):
|
// without it. It is on every Human68k system disk, whose stock CONFIG.SYS
|
||||||
|
// loads it (DEVICE = \SYS\RSDRV.SYS), and JoeyLib's X68000 boot disks keep it
|
||||||
|
// (scripts/lib/x68kDisk.sh). The ROM's own RS-232C routines (IPL-ROM 1.0)
|
||||||
|
// stop at 9600, have no RTS/CTS, and write the SCC's register pairs with
|
||||||
|
// interrupts on, so a mouse packet arriving during an open or close could
|
||||||
|
// split one; RSDRV's mask themselves. One implementation also means one set
|
||||||
|
// of limits to document and one configuration to test.
|
||||||
|
//
|
||||||
|
// _iocs_set232c's mode word as RSDRV 1.13 reads it (from its code; the ROM's
|
||||||
|
// is the same below bit 7 and indexes 0..7):
|
||||||
// bits 15-14 stop bits 1 = 1, 2 = 1.5, 0 or 3 = 2
|
// bits 15-14 stop bits 1 = 1, 2 = 1.5, 0 or 3 = 2
|
||||||
// bits 13-12 parity 1 = odd, 3 = even, 0 or 2 = none
|
// bits 13-12 parity 1 = odd, 3 = even, 0 or 2 = none
|
||||||
// bits 11-10 data bits 0 = 5 or fewer, 1 = 6, 2 = 7, 3 = 8
|
// bits 11-10 data bits 0 = 5 or fewer, 1 = 6, 2 = 7, 3 = 8
|
||||||
// bit 9 XON/XOFF
|
// bit 9 XON/XOFF
|
||||||
// bit 8 SI/SO (does not work in ROM IOCS; never set)
|
// bit 8 SI/SO (never set)
|
||||||
// bit 7 RTS/CTS (RSDRV.SYS only, below)
|
// bit 7 RTS/CTS, which overrides XON/XOFF
|
||||||
// bits 6-0 rate index 0..7 = 75 150 300 600 1200 2400 4800 9600
|
// bits 6-0 rate index 0..9 = 75 150 300 600 1200 2400 4800 9600 19200
|
||||||
// It returns the mode it replaced, which jlpSerialClose puts back.
|
// 38400 (time constant 2, really 39062 bps, 1.7% fast)
|
||||||
//
|
// It returns the mode it replaced, which jlpSerialClose puts back. With
|
||||||
// Two implementations answer it, and they differ above 9600 (both read from
|
// RTS/CTS, RSDRV's _OSNS232C reports "not ready" while CTS is off, and its
|
||||||
// their code). The ROM's (IPL-ROM 1.0) takes indexes 0..7 and turns anything
|
// receive side drops RTS as its buffer fills. With this HAL's receive
|
||||||
// else -- bit 7 included -- into 1200. Human68k's RS-232C driver RSDRV.SYS
|
// interrupt installed (below) RSDRV's receive side never runs, so RTS stays
|
||||||
// (1.13 on the Human68k 3.02 system disk, whose CONFIG.SYS loads it) replaces
|
// up and the 4 KB soft ring is the receive buffer; transmit still waits for
|
||||||
// the RS-232C IOCS calls, takes indexes 0..9 -- 8 = 19200, 9 = 38400 (time
|
// CTS.
|
||||||
// constant 2, really 39062 bps, 1.7% fast) -- and reads bit 7 as RTS/CTS,
|
|
||||||
// which overrides XON/XOFF: its _OSNS232C reports "not ready" while CTS is
|
|
||||||
// off, and its receive side drops RTS as its buffer fills. So the rates above
|
|
||||||
// 9600 and RTS/CTS are used only when RSDRV is detected (x68kRsdrvPresent);
|
|
||||||
// without it a faster request clamps to 9600 and RTS/CTS falls back to no
|
|
||||||
// handshaking. With this HAL's receive interrupt installed (below) RSDRV's
|
|
||||||
// receive side never runs, so RTS stays up and the 4 KB soft ring is the
|
|
||||||
// receive buffer; transmit still waits for CTS.
|
|
||||||
#define X68K_MODE_STOP_1 0x4000u
|
#define X68K_MODE_STOP_1 0x4000u
|
||||||
#define X68K_MODE_STOP_2 0xC000u
|
#define X68K_MODE_STOP_2 0xC000u
|
||||||
#define X68K_MODE_PARITY_ODD 0x1000u
|
#define X68K_MODE_PARITY_ODD 0x1000u
|
||||||
|
|
@ -57,10 +58,9 @@
|
||||||
#define X68K_MODE_RTSCTS 0x0080u
|
#define X68K_MODE_RTSCTS 0x0080u
|
||||||
#define X68K_DATA_BITS_MIN 5u
|
#define X68K_DATA_BITS_MIN 5u
|
||||||
#define X68K_DATA_BITS_MAX 8u
|
#define X68K_DATA_BITS_MAX 8u
|
||||||
#define X68K_RATE_COUNT_ROM 8u
|
#define X68K_RATE_COUNT 10u
|
||||||
#define X68K_RATE_COUNT_RSDRV 10u
|
|
||||||
|
|
||||||
static const uint16_t kX68kRates[X68K_RATE_COUNT_RSDRV] = { 75u, 150u, 300u, 600u, 1200u, 2400u, 4800u, 9600u, 19200u, 38400u };
|
static const uint16_t kX68kRates[X68K_RATE_COUNT] = { 75u, 150u, 300u, 600u, 1200u, 2400u, 4800u, 9600u, 19200u, 38400u };
|
||||||
|
|
||||||
// RSDRV.SYS detection. The driver adds an extended RS-232C IOCS call, $F1
|
// RSDRV.SYS detection. The driver adds an extended RS-232C IOCS call, $F1
|
||||||
// (d2 = function | port << 8), whose function $37 answers $0201 and touches
|
// (d2 = function | port << 8), whose function $37 answers $0201 and touches
|
||||||
|
|
@ -74,6 +74,7 @@ static const uint16_t kX68kRates[X68K_RATE_COUNT_RSDRV] = { 75u, 150u, 300u, 600
|
||||||
#define X68K_RSDRV_IDENT_FN 0x37
|
#define X68K_RSDRV_IDENT_FN 0x37
|
||||||
#define X68K_RSDRV_IDENT 0x0201
|
#define X68K_RSDRV_IDENT 0x0201
|
||||||
#define X68K_RAM_END 0x00C00000ul
|
#define X68K_RAM_END 0x00C00000ul
|
||||||
|
#define X68K_RSDRV_MISSING "serial: RSDRV.SYS is not loaded (DEVICE = \\SYS\\RSDRV.SYS in CONFIG.SYS)"
|
||||||
|
|
||||||
// WR5 as _SET232C writes it -- the ROM and RSDRV alike: DTR, Tx enable and
|
// WR5 as _SET232C writes it -- the ROM and RSDRV alike: DTR, Tx enable and
|
||||||
// RTS, plus the Tx bits-per-char code in bits 6-5 -- so jlpSerialSetDtr can
|
// RTS, plus the Tx bits-per-char code in bits 6-5 -- so jlpSerialSetDtr can
|
||||||
|
|
@ -135,7 +136,7 @@ static bool x68kInSupervisor(void);
|
||||||
static uint16_t x68kIrqMask(void);
|
static uint16_t x68kIrqMask(void);
|
||||||
static void x68kIrqRestore(uint16_t sr);
|
static void x68kIrqRestore(uint16_t sr);
|
||||||
static bool x68kRsdrvPresent(void);
|
static bool x68kRsdrvPresent(void);
|
||||||
static uint16_t x68kSerialMode(const jlSerialConfigT *config, bool rsdrv);
|
static uint16_t x68kSerialMode(const jlSerialConfigT *config);
|
||||||
static void x68kSerialPump(void);
|
static void x68kSerialPump(void);
|
||||||
static int32_t x68kSuperEnter(void);
|
static int32_t x68kSuperEnter(void);
|
||||||
static void x68kSuperLeave(int32_t ssp);
|
static void x68kSuperLeave(int32_t ssp);
|
||||||
|
|
@ -162,8 +163,7 @@ static void sccIsrInstall(void) {
|
||||||
|
|
||||||
// Only a program already in supervisor mode (every jlInit app) takes the
|
// Only a program already in supervisor mode (every jlInit app) takes the
|
||||||
// interrupt over. One in user mode (SERTEST) stays on the polled pump,
|
// interrupt over. One in user mode (SERTEST) stays on the polled pump,
|
||||||
// which needs no privilege and races nothing -- and since there is then
|
// which needs no privilege and races nothing.
|
||||||
// no ISR, jlpSerialWrite needs no interrupt mask either.
|
|
||||||
if (gSccIsrInstalled || !x68kInSupervisor()) {
|
if (gSccIsrInstalled || !x68kInSupervisor()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -252,17 +252,14 @@ static bool x68kRsdrvPresent(void) {
|
||||||
|
|
||||||
|
|
||||||
// The _iocs_set232c mode word for a JoeyLib line configuration (see the table
|
// The _iocs_set232c mode word for a JoeyLib line configuration (see the table
|
||||||
// above): the rate snaps down to the nearest one the IOCS in charge supports,
|
// above): the rate snaps down to the nearest one RSDRV.SYS supports.
|
||||||
// and RTS/CTS becomes no handshaking without RSDRV.SYS.
|
static uint16_t x68kSerialMode(const jlSerialConfigT *config) {
|
||||||
static uint16_t x68kSerialMode(const jlSerialConfigT *config, bool rsdrv) {
|
|
||||||
uint16_t mode;
|
uint16_t mode;
|
||||||
uint8_t rate;
|
uint8_t rate;
|
||||||
uint8_t rateCount;
|
|
||||||
uint8_t dataBits;
|
uint8_t dataBits;
|
||||||
|
|
||||||
rateCount = rsdrv ? X68K_RATE_COUNT_RSDRV : X68K_RATE_COUNT_ROM;
|
rate = 0u;
|
||||||
rate = 0u;
|
while (rate + 1u < X68K_RATE_COUNT && kX68kRates[rate + 1u] <= config->baud) {
|
||||||
while (rate + 1u < rateCount && kX68kRates[rate + 1u] <= config->baud) {
|
|
||||||
rate++;
|
rate++;
|
||||||
}
|
}
|
||||||
dataBits = config->dataBits;
|
dataBits = config->dataBits;
|
||||||
|
|
@ -282,7 +279,7 @@ static uint16_t x68kSerialMode(const jlSerialConfigT *config, bool rsdrv) {
|
||||||
if (config->flow == JL_SERIAL_FLOW_XONXOFF) {
|
if (config->flow == JL_SERIAL_FLOW_XONXOFF) {
|
||||||
mode |= X68K_MODE_XONXOFF;
|
mode |= X68K_MODE_XONXOFF;
|
||||||
}
|
}
|
||||||
if (rsdrv && config->flow == JL_SERIAL_FLOW_RTSCTS) {
|
if (config->flow == JL_SERIAL_FLOW_RTSCTS) {
|
||||||
mode |= X68K_MODE_RTSCTS;
|
mode |= X68K_MODE_RTSCTS;
|
||||||
}
|
}
|
||||||
return mode;
|
return mode;
|
||||||
|
|
@ -332,10 +329,18 @@ static uint8_t x68kWr5ForMode(uint16_t mode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Write channel A's WR5 and remember it. The pointer write and the data write
|
// Write channel A's WR5 and remember it.
|
||||||
// are two accesses to the chip-wide register pointer, so no interrupt (our RX
|
//
|
||||||
// ISR, the channel B mouse) may come between them -- see jlpSerialWrite. The
|
// The Z8530's register pointer is ONE register for the whole chip, shared by
|
||||||
// SCC is supervisor-only, and the app may have left supervisor mode.
|
// both channels, and selecting WR5 is a separate access from writing it. So no
|
||||||
|
// interrupt may come between the two writes below: a handler's own control
|
||||||
|
// write -- our receive ISR's on channel A, the IOCS mouse handler's on channel
|
||||||
|
// B -- would land as the DATA for WR5, our value would then reach WR0 as a
|
||||||
|
// command, and the handler's interrupt-under-service reset would be lost,
|
||||||
|
// which wedges the chip's interrupt chain. verify-x68000-serial-mouse.sh
|
||||||
|
// rewrites WR5 constantly while both channels interrupt, and fails within
|
||||||
|
// seconds without this mask. The SCC is supervisor-only, and the app may have
|
||||||
|
// left supervisor mode.
|
||||||
static void x68kWr5Write(uint8_t wr5) {
|
static void x68kWr5Write(uint8_t wr5) {
|
||||||
int32_t ssp;
|
int32_t ssp;
|
||||||
uint16_t sr;
|
uint16_t sr;
|
||||||
|
|
@ -392,7 +397,11 @@ bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
||||||
if (config == NULL) {
|
if (config == NULL) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
mode = x68kSerialMode(config, x68kRsdrvPresent());
|
if (!x68kRsdrvPresent()) {
|
||||||
|
coreSetError(X68K_RSDRV_MISSING);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mode = x68kSerialMode(config);
|
||||||
gPrevMode = _iocs_set232c((int)mode);
|
gPrevMode = _iocs_set232c((int)mode);
|
||||||
gX68kWr5 = x68kWr5ForMode(mode); // _SET232C just wrote this, DTR on
|
gX68kWr5 = x68kWr5ForMode(mode); // _SET232C just wrote this, DTR on
|
||||||
gSerialOpened = true;
|
gSerialOpened = true;
|
||||||
|
|
@ -436,62 +445,36 @@ void jlpSerialSetDtr(bool on) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// The Z8530's register pointer is ONE register for the whole chip, shared by
|
// Transmit through IOCS: a bounded ready spin, then one trap per byte.
|
||||||
// both channels, and selecting a register is a separate access from using it.
|
|
||||||
// So any two-access sequence has to be atomic or the two accesses belong to
|
|
||||||
// different transactions: the interrupting write lands as DATA for whatever
|
|
||||||
// register the interrupted code had selected, and the interrupted code's own
|
|
||||||
// data write then goes to WR0 as a command.
|
|
||||||
//
|
//
|
||||||
// Both IOCS traps below do such a sequence on channel A, and our own channel A
|
// The traps need no interrupt mask, although our receive ISR and the IOCS
|
||||||
// receive ISR writes the control port to drop the interrupt-under-service. An
|
// mouse handler both write the SCC's chip-wide register pointer (see
|
||||||
// arriving byte can therefore land between a trap's pointer write and its data
|
// x68kWr5Write). In both implementations -- the IPL 1.0 ROM and RSDRV.SYS 1.13,
|
||||||
// access -- corrupting a receiver register AND losing the IUS reset, which
|
// read from their code -- _OSNS232C selects register 0 and reads RR0, and
|
||||||
// wedges the SCC's interrupt daisy chain so no further byte ever interrupts.
|
// _OUT232C writes the data port, which has no pointer. A handler that runs
|
||||||
// Masking around each trap closes that window.
|
// between the select and the read writes WR0 itself, which leaves the pointer
|
||||||
|
// at 0, so the read still gets RR0. (RSDRV's RTS/CTS status read masks
|
||||||
|
// itself.) The masks this call used to put around each trap held every
|
||||||
|
// interrupt off for most of the spin and protected nothing:
|
||||||
|
// verify-x68000-serial-mouse.sh passes without them.
|
||||||
//
|
//
|
||||||
// The mask goes around each CALL and not around the spin loop: holding it for
|
// Nor can the handlers collide with each other: IOCS's channel B mouse handler
|
||||||
// a whole byte time would be the receive latency the ISR exists to avoid. One
|
// and our channel A handler are the same 68000 interrupt level, so taking one
|
||||||
// trap is a few microseconds, far inside a byte at any supported baud.
|
// masks the other, and the Timer C mouse solicitation (level 6) finishes its
|
||||||
//
|
// select-then-write before it returns.
|
||||||
// The two INTERRUPT handlers cannot collide with each other -- IOCS's channel B
|
|
||||||
// mouse handler and our channel A handler are the same 68000 interrupt level,
|
|
||||||
// so taking one masks the other -- and serial.c's own direct RR2 read in
|
|
||||||
// sccIsrInstall masks already. What is left is IOCS's internals interrupting
|
|
||||||
// each other, which is the ROM's business and not reachable from here.
|
|
||||||
uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
|
uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
|
||||||
uint16_t n;
|
uint16_t n;
|
||||||
uint32_t guard;
|
uint32_t guard;
|
||||||
uint16_t sr;
|
|
||||||
int ready;
|
|
||||||
|
|
||||||
// The masks keep our RX ISR out of the IOCS calls' SCC accesses; with no
|
|
||||||
// ISR installed (the polled path) there is nothing to keep out.
|
|
||||||
for (n = 0u; n < len; n++) {
|
for (n = 0u; n < len; n++) {
|
||||||
guard = 0ul;
|
guard = 0ul;
|
||||||
for (;;) {
|
while (_iocs_osns232c() == 0) {
|
||||||
if (gSccIsrInstalled) {
|
|
||||||
sr = x68kIrqMask();
|
|
||||||
ready = _iocs_osns232c();
|
|
||||||
x68kIrqRestore(sr);
|
|
||||||
} else {
|
|
||||||
ready = _iocs_osns232c();
|
|
||||||
}
|
|
||||||
if (ready != 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
guard++;
|
guard++;
|
||||||
if (guard > X68K_TX_SPIN_LIMIT) {
|
if (guard > X68K_TX_SPIN_LIMIT) {
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (gSccIsrInstalled) {
|
_iocs_out232c((int)buf[n]);
|
||||||
sr = x68kIrqMask();
|
|
||||||
_iocs_out232c((int)buf[n]);
|
|
||||||
x68kIrqRestore(sr);
|
|
||||||
} else {
|
|
||||||
_iocs_out232c((int)buf[n]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,375 +1,18 @@
|
||||||
// Sharp X68000 planar tile ops -- adapted verbatim from src/amiga/amigaTile.h.
|
// X68000 tile ops: the shared planar bodies in src/m68k/planarTile.h, bound to
|
||||||
// The surface layout is identical (40 bytes per row per plane), so an 8x8 tile
|
// the X68000's planar surface data, which mirrors the Amiga's field for field
|
||||||
// is 8 rows x 4 planes x 1 byte on both machines and the .tbk payload is
|
// (40 bytes a row, 4 planes), so the .tbk payload is byte-compatible too
|
||||||
// byte-compatible (assetbake --target x68000 writes the Amiga layout under its
|
// (assetbake --target x68000 writes the Amiga layout under its own target
|
||||||
// own target byte); only the type names differ.
|
// byte). Do not include this header directly; it comes with port.h on the
|
||||||
// Tile ops as always-inline functions, so the public wrappers in
|
// X68000.
|
||||||
// src/core/tile.c inline them, deleting the second full cdecl call layer
|
|
||||||
// (~130-190 cycles/op on the 68000). The jlpTile* dispatch names are
|
|
||||||
// macro-aliased to these in port.h, which also #includes this header at its
|
|
||||||
// tail -- AFTER the jlpGenericTile* prototypes the portData == NULL fallbacks
|
|
||||||
// call. Do not include this header directly; it comes with port.h.
|
|
||||||
#ifndef JOEY_X68K_TILE_H
|
#ifndef JOEY_X68K_TILE_H
|
||||||
#define JOEY_X68K_TILE_H
|
#define JOEY_X68K_TILE_H
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include "joey/tile.h"
|
|
||||||
#include "x68kPlanar.h"
|
#include "x68kPlanar.h"
|
||||||
|
|
||||||
// 8-row unrolled tile access (NATIVE-PERF Phase 2 R8): row offsets
|
#define PLANAR_TILE_DATA_T X68kPlanarT
|
||||||
// are compile-time constants row * 40, so each row is one d16(An)
|
#define PLANAR_TILE_BYTES_PER_ROW X68K_BYTES_PER_ROW
|
||||||
// access with no pointer stepping and no loop control. gcc-amigaos
|
#define PLANAR_TILE_PLANES X68K_BITPLANES
|
||||||
// -O2 keeps the equivalent row loops rolled as dbf (objdump-verified,
|
|
||||||
// ~18 cycles/row of pure loop overhead). Only the row dimension
|
|
||||||
// unrolls -- the 4-plane loop keeps real per-plane setup and stays
|
|
||||||
// register-light per the m68k-gcc-unroll-backfire lesson.
|
|
||||||
#define AMIGA_TILE_ROW_OFF(_r) ((uint16_t)((_r) * X68K_BYTES_PER_ROW))
|
|
||||||
|
|
||||||
// Phase 5 planar dual-write for tile ops, fully planar after Phase 9
|
#include "planarTile.h"
|
||||||
// dropped the chunky shadow. All tiles are 8-pixel aligned (8x8 blocks
|
|
||||||
// at multiples of 8), so plane writes are byte-aligned -- one plane
|
|
||||||
// byte per row, 8 rows per tile, no edge masks. Stride between rows
|
|
||||||
// in a plane is X68K_BYTES_PER_ROW (40).
|
|
||||||
|
|
||||||
static inline __attribute__((always_inline)) void x68kTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex) {
|
|
||||||
X68kPlanarT *pd;
|
|
||||||
uint16_t plane;
|
|
||||||
uint8_t fillByte;
|
|
||||||
uint8_t *p;
|
|
||||||
|
|
||||||
pd = (X68kPlanarT *)s->portData;
|
|
||||||
if (pd == NULL) {
|
|
||||||
jlpGenericTileFill(s, bx, by, colorIndex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (plane = 0; plane < X68K_BITPLANES; plane++) {
|
|
||||||
fillByte = ((colorIndex >> plane) & 1u) ? 0xFFu : 0x00u;
|
|
||||||
p = pd->planes[plane] + (uint16_t)by * 8u * X68K_BYTES_PER_ROW + bx;
|
|
||||||
p[AMIGA_TILE_ROW_OFF(0)] = fillByte;
|
|
||||||
p[AMIGA_TILE_ROW_OFF(1)] = fillByte;
|
|
||||||
p[AMIGA_TILE_ROW_OFF(2)] = fillByte;
|
|
||||||
p[AMIGA_TILE_ROW_OFF(3)] = fillByte;
|
|
||||||
p[AMIGA_TILE_ROW_OFF(4)] = fillByte;
|
|
||||||
p[AMIGA_TILE_ROW_OFF(5)] = fillByte;
|
|
||||||
p[AMIGA_TILE_ROW_OFF(6)] = fillByte;
|
|
||||||
p[AMIGA_TILE_ROW_OFF(7)] = fillByte;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline __attribute__((always_inline)) void x68kTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy) {
|
|
||||||
X68kPlanarT *dstPd;
|
|
||||||
X68kPlanarT *srcPd;
|
|
||||||
uint16_t plane;
|
|
||||||
uint8_t *dp;
|
|
||||||
const uint8_t *sp;
|
|
||||||
|
|
||||||
dstPd = (X68kPlanarT *)dst->portData;
|
|
||||||
srcPd = (X68kPlanarT *)src->portData;
|
|
||||||
if (dstPd == NULL || srcPd == NULL) {
|
|
||||||
jlpGenericTileCopy(dst, dstBx, dstBy, src, srcBx, srcBy);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (plane = 0; plane < X68K_BITPLANES; plane++) {
|
|
||||||
dp = dstPd->planes[plane] + (uint16_t)dstBy * 8u * X68K_BYTES_PER_ROW + dstBx;
|
|
||||||
sp = srcPd->planes[plane] + (uint16_t)srcBy * 8u * X68K_BYTES_PER_ROW + srcBx;
|
|
||||||
dp[AMIGA_TILE_ROW_OFF(0)] = sp[AMIGA_TILE_ROW_OFF(0)];
|
|
||||||
dp[AMIGA_TILE_ROW_OFF(1)] = sp[AMIGA_TILE_ROW_OFF(1)];
|
|
||||||
dp[AMIGA_TILE_ROW_OFF(2)] = sp[AMIGA_TILE_ROW_OFF(2)];
|
|
||||||
dp[AMIGA_TILE_ROW_OFF(3)] = sp[AMIGA_TILE_ROW_OFF(3)];
|
|
||||||
dp[AMIGA_TILE_ROW_OFF(4)] = sp[AMIGA_TILE_ROW_OFF(4)];
|
|
||||||
dp[AMIGA_TILE_ROW_OFF(5)] = sp[AMIGA_TILE_ROW_OFF(5)];
|
|
||||||
dp[AMIGA_TILE_ROW_OFF(6)] = sp[AMIGA_TILE_ROW_OFF(6)];
|
|
||||||
dp[AMIGA_TILE_ROW_OFF(7)] = sp[AMIGA_TILE_ROW_OFF(7)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Pure-planar masked copy. For each row of the 8x8 tile, read 4 src
|
|
||||||
// plane bytes; compute a per-pixel "non-transparent" mask via XOR
|
|
||||||
// against the transparent index's per-plane bit pattern (a pixel
|
|
||||||
// matches transparent IFF all 4 plane bits match transparent's 4
|
|
||||||
// bits = OR of 4 XOR'd bytes is 0 in that bit). Then for each plane,
|
|
||||||
// dst = (dst & ~mask) | (src & mask) -- copy src bits at mask-set
|
|
||||||
// positions, preserve dst bits elsewhere.
|
|
||||||
static inline __attribute__((always_inline)) void x68kTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparent) {
|
|
||||||
X68kPlanarT *dstPd;
|
|
||||||
X68kPlanarT *srcPd;
|
|
||||||
uint8_t transBitByte[X68K_BITPLANES];
|
|
||||||
uint16_t i;
|
|
||||||
uint8_t row;
|
|
||||||
uint16_t srcByteOff;
|
|
||||||
uint16_t dstByteOff;
|
|
||||||
uint8_t srcPlaneBytes[X68K_BITPLANES];
|
|
||||||
uint8_t maskByte;
|
|
||||||
|
|
||||||
dstPd = (X68kPlanarT *)dst->portData;
|
|
||||||
srcPd = (X68kPlanarT *)src->portData;
|
|
||||||
if (dstPd == NULL || srcPd == NULL) {
|
|
||||||
jlpGenericTileCopyMasked(dst, dstBx, dstBy, src, srcBx, srcBy, transparent);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
transparent = (uint8_t)(transparent & 0x0Fu);
|
|
||||||
/* Per-plane "all bits set if transparent's bit at this plane is 1
|
|
||||||
* else all 0" -- so XOR gives bit set where pixel differs from
|
|
||||||
* transparent in that plane. */
|
|
||||||
for (i = 0; i < X68K_BITPLANES; i++) {
|
|
||||||
transBitByte[i] = ((transparent >> i) & 1u) ? 0xFFu : 0x00u;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (row = 0; row < 8u; row++) {
|
|
||||||
srcByteOff = (uint16_t)((uint16_t)srcBy * 8u + row) * X68K_BYTES_PER_ROW + srcBx;
|
|
||||||
dstByteOff = (uint16_t)((uint16_t)dstBy * 8u + row) * X68K_BYTES_PER_ROW + dstBx;
|
|
||||||
srcPlaneBytes[0] = srcPd->planes[0][srcByteOff];
|
|
||||||
srcPlaneBytes[1] = srcPd->planes[1][srcByteOff];
|
|
||||||
srcPlaneBytes[2] = srcPd->planes[2][srcByteOff];
|
|
||||||
srcPlaneBytes[3] = srcPd->planes[3][srcByteOff];
|
|
||||||
/* maskByte: bit set where pixel differs from transparent in
|
|
||||||
* ANY plane -- i.e., where the pixel is non-transparent. */
|
|
||||||
maskByte = (uint8_t)((srcPlaneBytes[0] ^ transBitByte[0])
|
|
||||||
| (srcPlaneBytes[1] ^ transBitByte[1])
|
|
||||||
| (srcPlaneBytes[2] ^ transBitByte[2])
|
|
||||||
| (srcPlaneBytes[3] ^ transBitByte[3]));
|
|
||||||
for (i = 0; i < X68K_BITPLANES; i++) {
|
|
||||||
uint8_t existing = dstPd->planes[i][dstByteOff];
|
|
||||||
dstPd->planes[i][dstByteOff] = (uint8_t)((existing & (uint8_t)~maskByte)
|
|
||||||
| (srcPlaneBytes[i] & maskByte));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Phase 9 plane-to-chunky derivation for jlTileSnap. Reads 8 plane
|
|
||||||
// bytes (1 byte per row x 8 rows) from each of 4 planes for the
|
|
||||||
// 8-pixel-aligned tile column at bx, then assembles 32 chunky bytes
|
|
||||||
// (4 per row x 8 rows, packed 2 px/byte high-then-low nibble) into
|
|
||||||
// On Amiga, jlTileT.pixels is opaque port-specific storage (cross-
|
|
||||||
// platform tile.c never reads it directly when s->pixels is NULL).
|
|
||||||
// We use those 32 bytes as 4 planes x 8 rows, plane-major:
|
|
||||||
// bytes [0..7] = plane 0, rows 0..7
|
|
||||||
// bytes [8..15] = plane 1, rows 0..7
|
|
||||||
// bytes [16..23] = plane 2, rows 0..7
|
|
||||||
// bytes [24..31] = plane 3, rows 0..7
|
|
||||||
// snap/paste then become 32 plain byte loads + stores -- no chunky
|
|
||||||
// <-> planar conversion at all. The previous c2p-based path paid
|
|
||||||
// 4 KB LUT lookups + bit shuffling per pixel; this is ~50x cheaper.
|
|
||||||
#define AMIGA_TILE_PLANE_STRIDE 8
|
|
||||||
static inline __attribute__((always_inline)) void x68kTileSnap(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *chunkyOut) {
|
|
||||||
X68kPlanarT *pd;
|
|
||||||
uint16_t rowBase;
|
|
||||||
uint8_t plane;
|
|
||||||
|
|
||||||
pd = (X68kPlanarT *)src->portData;
|
|
||||||
if (pd == NULL) {
|
|
||||||
jlpGenericTileSnap(src, bx, by, chunkyOut);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rowBase = (uint16_t)((uint16_t)by * 8u) * X68K_BYTES_PER_ROW + bx;
|
|
||||||
for (plane = 0; plane < X68K_BITPLANES; plane++) {
|
|
||||||
const uint8_t *p = pd->planes[plane] + rowBase;
|
|
||||||
uint8_t *q = chunkyOut + plane * AMIGA_TILE_PLANE_STRIDE;
|
|
||||||
q[0] = p[AMIGA_TILE_ROW_OFF(0)];
|
|
||||||
q[1] = p[AMIGA_TILE_ROW_OFF(1)];
|
|
||||||
q[2] = p[AMIGA_TILE_ROW_OFF(2)];
|
|
||||||
q[3] = p[AMIGA_TILE_ROW_OFF(3)];
|
|
||||||
q[4] = p[AMIGA_TILE_ROW_OFF(4)];
|
|
||||||
q[5] = p[AMIGA_TILE_ROW_OFF(5)];
|
|
||||||
q[6] = p[AMIGA_TILE_ROW_OFF(6)];
|
|
||||||
q[7] = p[AMIGA_TILE_ROW_OFF(7)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline __attribute__((always_inline)) void x68kTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *chunkyTile) {
|
|
||||||
X68kPlanarT *pd;
|
|
||||||
uint8_t plane;
|
|
||||||
uint16_t rowBase;
|
|
||||||
|
|
||||||
pd = (X68kPlanarT *)dst->portData;
|
|
||||||
if (pd == NULL) {
|
|
||||||
jlpGenericTilePaste(dst, bx, by, chunkyTile);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* jlTileT.pixels[] holds plane-major planar bytes (see
|
|
||||||
* jlpTileSnap header for layout). Paste = 32 byte stores
|
|
||||||
* with no chunky -> planar conversion. */
|
|
||||||
rowBase = (uint16_t)((uint16_t)by * 8u) * X68K_BYTES_PER_ROW + bx;
|
|
||||||
for (plane = 0; plane < X68K_BITPLANES; plane++) {
|
|
||||||
const uint8_t *q = chunkyTile + plane * AMIGA_TILE_PLANE_STRIDE;
|
|
||||||
uint8_t *p = pd->planes[plane] + rowBase;
|
|
||||||
p[AMIGA_TILE_ROW_OFF(0)] = q[0];
|
|
||||||
p[AMIGA_TILE_ROW_OFF(1)] = q[1];
|
|
||||||
p[AMIGA_TILE_ROW_OFF(2)] = q[2];
|
|
||||||
p[AMIGA_TILE_ROW_OFF(3)] = q[3];
|
|
||||||
p[AMIGA_TILE_ROW_OFF(4)] = q[4];
|
|
||||||
p[AMIGA_TILE_ROW_OFF(5)] = q[5];
|
|
||||||
p[AMIGA_TILE_ROW_OFF(6)] = q[6];
|
|
||||||
p[AMIGA_TILE_ROW_OFF(7)] = q[7];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Whole-map paste engine (NATIVE-PERF W3 item 9). jlTileMapPaste's
|
|
||||||
// shared whole-map arm calls this ONCE per map: the portData load +
|
|
||||||
// NULL test, the by*320 row-base derive, and the four plane base
|
|
||||||
// pointers are hoisted out of the per-cell loop -- the per-cell
|
|
||||||
// x68kTilePaste route reloads portData, rebuilds the cell offset,
|
|
||||||
// and re-reads pd->planes[0..3] from the struct every cell (~172 of
|
|
||||||
// its ~776 cyc/cell, objdump-measured). The four dst pointers stay
|
|
||||||
// resident in a0-a3 (one byte per column, one row stride per tile
|
|
||||||
// row), so each cell is 32 move.b off compile-time displacements
|
|
||||||
// with zero per-plane setup. Plane order, row order, and written
|
|
||||||
// values are identical to per-cell x68kTilePaste -- pixel output is
|
|
||||||
// byte-identical. movep is unusable here (plane bytes sit 8000 bytes
|
|
||||||
// apart across separate AllocMem planes, vs the ST's interleaved +2
|
|
||||||
// layout), so C is the right weapon on this port.
|
|
||||||
#define AMIGA_TILE_MAP_ROW_STRIDE ((uint16_t)(TILE_PIXELS_PER_SIDE * X68K_BYTES_PER_ROW))
|
|
||||||
|
|
||||||
#define AMIGA_MAP_CELL_PLANE(_dp, _plane) \
|
|
||||||
do { \
|
|
||||||
(_dp)[AMIGA_TILE_ROW_OFF(0)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 0]; \
|
|
||||||
(_dp)[AMIGA_TILE_ROW_OFF(1)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 1]; \
|
|
||||||
(_dp)[AMIGA_TILE_ROW_OFF(2)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 2]; \
|
|
||||||
(_dp)[AMIGA_TILE_ROW_OFF(3)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 3]; \
|
|
||||||
(_dp)[AMIGA_TILE_ROW_OFF(4)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 4]; \
|
|
||||||
(_dp)[AMIGA_TILE_ROW_OFF(5)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 5]; \
|
|
||||||
(_dp)[AMIGA_TILE_ROW_OFF(6)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 6]; \
|
|
||||||
(_dp)[AMIGA_TILE_ROW_OFF(7)] = src[(_plane) * AMIGA_TILE_PLANE_STRIDE + 7]; \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
static inline __attribute__((always_inline)) void x68kTileMapPaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, uint8_t wTiles, uint8_t hTiles, const jlTileT *tiles, const uint8_t *map) {
|
|
||||||
X68kPlanarT *pd;
|
|
||||||
const uint8_t *tileBytes;
|
|
||||||
const uint8_t *src;
|
|
||||||
uint8_t *p0;
|
|
||||||
uint8_t *p1;
|
|
||||||
uint8_t *p2;
|
|
||||||
uint8_t *p3;
|
|
||||||
uint16_t rowBase;
|
|
||||||
uint16_t rewind;
|
|
||||||
uint8_t idx;
|
|
||||||
uint8_t ty;
|
|
||||||
uint8_t tx;
|
|
||||||
|
|
||||||
tileBytes = (const uint8_t *)tiles;
|
|
||||||
pd = (X68kPlanarT *)dst->portData;
|
|
||||||
if (pd == NULL) {
|
|
||||||
for (ty = 0; ty < hTiles; ty++) {
|
|
||||||
for (tx = 0; tx < wTiles; tx++) {
|
|
||||||
idx = map[tx];
|
|
||||||
if (idx != TILE_MAP_SKIP) {
|
|
||||||
jlpGenericTilePaste(dst, (uint8_t)(bx + tx), (uint8_t)(by + ty), tileBytes + ((uint16_t)idx << 5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
map += wTiles;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rowBase = (uint16_t)((uint16_t)by * AMIGA_TILE_MAP_ROW_STRIDE + bx);
|
|
||||||
rewind = (uint16_t)(AMIGA_TILE_MAP_ROW_STRIDE - wTiles);
|
|
||||||
p0 = pd->planes[0] + rowBase;
|
|
||||||
p1 = pd->planes[1] + rowBase;
|
|
||||||
p2 = pd->planes[2] + rowBase;
|
|
||||||
p3 = pd->planes[3] + rowBase;
|
|
||||||
for (ty = hTiles; ty != 0u; ty--) {
|
|
||||||
for (tx = wTiles; tx != 0u; tx--) {
|
|
||||||
idx = *map++;
|
|
||||||
if (idx != TILE_MAP_SKIP) {
|
|
||||||
src = tileBytes + ((uint16_t)idx << 5);
|
|
||||||
AMIGA_MAP_CELL_PLANE(p0, 0);
|
|
||||||
AMIGA_MAP_CELL_PLANE(p1, 1);
|
|
||||||
AMIGA_MAP_CELL_PLANE(p2, 2);
|
|
||||||
AMIGA_MAP_CELL_PLANE(p3, 3);
|
|
||||||
}
|
|
||||||
p0++;
|
|
||||||
p1++;
|
|
||||||
p2++;
|
|
||||||
p3++;
|
|
||||||
}
|
|
||||||
p0 += rewind;
|
|
||||||
p1 += rewind;
|
|
||||||
p2 += rewind;
|
|
||||||
p3 += rewind;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#undef AMIGA_MAP_CELL_PLANE
|
|
||||||
#undef AMIGA_TILE_MAP_ROW_STRIDE
|
|
||||||
|
|
||||||
|
|
||||||
// Planar monochrome paste. monoTile follows the cross-port mono
|
|
||||||
// contract (include/joey/tile.h + jlpGenericTilePasteMono): 32 chunky
|
|
||||||
// nibble-pair bytes, row-major, TILE_BYTES_PER_ROW bytes per row, two
|
|
||||||
// pixels per byte. A pixel renders fgColor when its source nibble is
|
|
||||||
// nonzero, bgColor when zero; the HIGH nibble is the LEFT pixel. Each
|
|
||||||
// row's 4 source bytes fold into an 8-bit shape mask (bit 7 =
|
|
||||||
// leftmost pixel, matching the planar bit order), then each plane k
|
|
||||||
// writes
|
|
||||||
// outPlaneK = (shape & maskFgK) | (~shape & maskBgK)
|
|
||||||
// where maskXK = $FF if (X & (1 << k)) else $00.
|
|
||||||
// Eight 1bpp shape rows into the bitplanes: set bits take the
|
|
||||||
// foreground's plane bit, clear bits the background's. Shared by the
|
|
||||||
// mono paste (which folds a chunky silhouette into these rows first)
|
|
||||||
// and the glyph paste (whose input already IS these rows).
|
|
||||||
static inline __attribute__((always_inline)) void x68kTilePasteShape(X68kPlanarT *pd, uint8_t bx, uint8_t by, const uint8_t *shape, uint8_t fgColor, uint8_t bgColor) {
|
|
||||||
uint16_t rowBase = (uint16_t)((uint16_t)by * 8u) * X68K_BYTES_PER_ROW + bx;
|
|
||||||
uint8_t plane;
|
|
||||||
uint8_t row;
|
|
||||||
|
|
||||||
for (plane = 0; plane < X68K_BITPLANES; plane++) {
|
|
||||||
uint8_t maskFg = (uint8_t)((fgColor & (1u << plane)) ? 0xFFu : 0x00u);
|
|
||||||
uint8_t maskBg = (uint8_t)((bgColor & (1u << plane)) ? 0xFFu : 0x00u);
|
|
||||||
uint8_t *p = pd->planes[plane] + rowBase;
|
|
||||||
for (row = 0; row < 8u; row++) {
|
|
||||||
uint8_t s = shape[row];
|
|
||||||
p[row * X68K_BYTES_PER_ROW] = (uint8_t)((uint8_t)(s & maskFg) | (uint8_t)((uint8_t)(~s) & maskBg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline __attribute__((always_inline)) void x68kTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *monoTile, uint8_t fgColor, uint8_t bgColor) {
|
|
||||||
X68kPlanarT *pd;
|
|
||||||
uint8_t shape[TILE_PIXELS_PER_SIDE];
|
|
||||||
uint8_t row;
|
|
||||||
uint8_t col;
|
|
||||||
|
|
||||||
pd = (X68kPlanarT *)dst->portData;
|
|
||||||
if (pd == NULL) {
|
|
||||||
jlpGenericTilePasteMono(dst, bx, by, monoTile, fgColor, bgColor);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Fold each chunky nibble-pair row into one 1bpp shape byte.
|
|
||||||
for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
|
|
||||||
uint8_t bits = 0u;
|
|
||||||
for (col = 0; col < TILE_BYTES_PER_ROW; col++) {
|
|
||||||
uint8_t srcByte = *monoTile++;
|
|
||||||
bits = (uint8_t)(bits << 2);
|
|
||||||
if (srcByte & 0xF0u) {
|
|
||||||
bits = (uint8_t)(bits | 0x02u);
|
|
||||||
}
|
|
||||||
if (srcByte & 0x0Fu) {
|
|
||||||
bits = (uint8_t)(bits | 0x01u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
shape[row] = bits;
|
|
||||||
}
|
|
||||||
x68kTilePasteShape(pd, bx, by, shape, fgColor, bgColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 1bpp glyph rows straight to the planes: a glyph byte IS the row shape
|
|
||||||
// the mono paste has to fold out of a chunky silhouette, so this skips
|
|
||||||
// the fold entirely (and cannot be handed the wrong tile format).
|
|
||||||
static inline __attribute__((always_inline)) void x68kTilePasteGlyph(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *bits, uint8_t fgColor, uint8_t bgColor) {
|
|
||||||
X68kPlanarT *pd = (X68kPlanarT *)dst->portData;
|
|
||||||
|
|
||||||
if (pd == NULL) {
|
|
||||||
jlpGenericTilePasteGlyph(dst, bx, by, bits, fgColor, bgColor);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
x68kTilePasteShape(pd, bx, by, bits, fgColor, bgColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -85,14 +85,14 @@ static void pushAll(const uint8_t *chars, int count) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Control characters other than BS/TAB/CR/ESC, plus DEL and 8-bit
|
// Control characters other than BS/TAB/CR/ESC, and 8-bit values, must
|
||||||
// values, must never surface; the documented four and the printable
|
// never surface; those four, the printable range and JL_CHAR_DELETE
|
||||||
// range must pass through unchanged.
|
// (0x7F, forward delete) must pass through unchanged.
|
||||||
static bool runFilter(void) {
|
static bool runFilter(void) {
|
||||||
static const uint8_t rejected[] = { 0x00, 0x01, 0x07, 0x0A, 0x0C, 0x1F, 0x7F };
|
static const uint8_t rejected[] = { 0x00, 0x01, 0x07, 0x0A, 0x0C, 0x1F, 0x80, 0xA0, 0xFF };
|
||||||
static const uint8_t accepted[] = {
|
static const uint8_t accepted[] = {
|
||||||
JL_CHAR_BACKSPACE, JL_CHAR_TAB, JL_CHAR_RETURN, JL_CHAR_ESCAPE,
|
JL_CHAR_BACKSPACE, JL_CHAR_TAB, JL_CHAR_RETURN, JL_CHAR_ESCAPE,
|
||||||
0x20, '0', 'Z', 'z', '~', 0x7E
|
0x20, '0', 'Z', 'z', '~', 0x7E, JL_CHAR_DELETE
|
||||||
};
|
};
|
||||||
bool ok;
|
bool ok;
|
||||||
|
|
||||||
|
|
|
||||||
BIN
tests/host/tileBank.png
(Stored with Git LFS)
Normal file
BIN
tests/host/tileBank.png
(Stored with Git LFS)
Normal file
Binary file not shown.
503
tests/host/tileBankHost.c
Normal file
503
tests/host/tileBankHost.c
Normal file
|
|
@ -0,0 +1,503 @@
|
||||||
|
// tileBankHost.c - Host-cc gate for the baked tile-bank loaders,
|
||||||
|
// jlTileBankLoad and jlTileBankLoadToSurface (src/core/assetLoad.c).
|
||||||
|
//
|
||||||
|
// Built by scripts/check-tilebank.sh as a whole BLANK-port program (core +
|
||||||
|
// generics + src/blank/blank.c + this file), so the REAL loader feeds the
|
||||||
|
// REAL generic tile paste and every pixel is read back through the public
|
||||||
|
// jlSamplePixel. BLANK draws chunky 4bpp like DOS and assetLoad.c expects the
|
||||||
|
// DOS target byte there, so the script stages tests/host/tileBank.png with
|
||||||
|
// tests/host/tileBankStage.py for target dos: the DOS bake is the bank that
|
||||||
|
// must load, and every other assetbake target's bake must be refused.
|
||||||
|
//
|
||||||
|
// Runs in the script's temp directory, whose DATA/ holds (the staging
|
||||||
|
// script's names and layout, shared with examples/tilebank):
|
||||||
|
// own.tbk the DOS bake of tileBank.png
|
||||||
|
// refuse<n>.tbk one bake per other target; their names arrive as argv[1..]
|
||||||
|
// expect.bin the PNG decoded by PIL, independent of assetbake's
|
||||||
|
// encoders: tileCount LE16, palette[16] LE16 $0RGB, then
|
||||||
|
// tileCount * 64 colour indices (tiles in bank order, each
|
||||||
|
// row-major)
|
||||||
|
// The malformed banks (bad magic, short header, truncated payload, no
|
||||||
|
// palette, more tiles than a surface holds) are cut from own.tbk here, so
|
||||||
|
// their parameters live beside the checks that depend on them.
|
||||||
|
//
|
||||||
|
// Prints one line per check and a final tally; exits nonzero on any failure.
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "joey/joey.h"
|
||||||
|
#include "joey/file.h"
|
||||||
|
|
||||||
|
// ----- Constants -----
|
||||||
|
|
||||||
|
#define BAD_MAGIC_BANK "badmagic.tbk"
|
||||||
|
#define BIG_BANK "big.tbk"
|
||||||
|
#define EXPECTED_FILE "expect.bin"
|
||||||
|
#define GOOD_BANK "own.tbk"
|
||||||
|
#define MISSING_BANK "missing.tbk"
|
||||||
|
#define NO_PALETTE_BANK "nopal.tbk"
|
||||||
|
#define SHORT_HEADER_BANK "shorthdr.tbk"
|
||||||
|
#define TRUNCATED_BANK "trunc.tbk"
|
||||||
|
|
||||||
|
// .tbk header fields (the format is documented in tools/assetbake/assetbake.py).
|
||||||
|
#define TBK_HEADER_BYTES 44
|
||||||
|
#define TBK_OFF_MAGIC 0
|
||||||
|
#define TBK_OFF_HAS_PALETTE 5
|
||||||
|
#define TBK_OFF_TILE_COUNT 6
|
||||||
|
|
||||||
|
#define LE16_BYTES 2
|
||||||
|
#define PALETTE_COLORS SURFACE_COLORS_PER_PALETTE
|
||||||
|
#define TILE_PIXELS (TILE_PIXELS_PER_SIDE * TILE_PIXELS_PER_SIDE)
|
||||||
|
#define SURFACE_TILE_BLOCKS (TILE_BLOCKS_PER_ROW * TILE_BLOCKS_PER_COL)
|
||||||
|
|
||||||
|
// expect.bin layout (see the header comment).
|
||||||
|
#define EXPECTED_OFF_PALETTE LE16_BYTES
|
||||||
|
#define EXPECTED_OFF_PIXELS (EXPECTED_OFF_PALETTE + PALETTE_COLORS * LE16_BYTES)
|
||||||
|
#define EXPECTED_MAX_TILES SURFACE_TILE_BLOCKS
|
||||||
|
|
||||||
|
// One tile more than a surface holds, so jlTileBankLoadToSurface's 40x25
|
||||||
|
// cap is exercised. Also the capacity of the jlTileT buffer every load uses.
|
||||||
|
#define BIG_BANK_TILES (SURFACE_TILE_BLOCKS + 1)
|
||||||
|
#define TILE_CAPACITY BIG_BANK_TILES
|
||||||
|
|
||||||
|
#define BAD_MAGIC_BYTE 'X'
|
||||||
|
#define FILL_COLOR 9u // pre-paste colour; blocks past a bank must keep it (examples/tilebank clears to the same, so its hash can match ours)
|
||||||
|
#define PALETTE_SENTINEL 0xBEEFu
|
||||||
|
#define SHORT_HEADER_BYTES (TBK_HEADER_BYTES / 2)
|
||||||
|
#define SMALL_MAX_TILES 5u
|
||||||
|
#define TILE_SENTINEL 0xA5u
|
||||||
|
#define TRUNCATED_EXTRA_BYTES (TILE_BYTES / 2) // half a tile after the last whole one
|
||||||
|
#define TRUNCATED_TILES 10u
|
||||||
|
|
||||||
|
// ----- Prototypes -----
|
||||||
|
|
||||||
|
static void check(const char *bank, const char *what, bool ok);
|
||||||
|
static bool craftBanks(void);
|
||||||
|
static bool loadExpected(void);
|
||||||
|
static bool paletteIsExpected(void);
|
||||||
|
static bool paletteUntouched(void);
|
||||||
|
static void pasteLoadedTiles(uint16_t count);
|
||||||
|
static uint16_t readLe16(const uint8_t *bytes);
|
||||||
|
static void resetBuffers(void);
|
||||||
|
static bool surfaceMatches(uint16_t tilesPasted);
|
||||||
|
static void testArgumentGuards(void);
|
||||||
|
static void testBigBank(void);
|
||||||
|
static void testGoodBank(void);
|
||||||
|
static void testMaxTiles(void);
|
||||||
|
static void testNoPalette(void);
|
||||||
|
static void testRefused(const char *bank);
|
||||||
|
static void testToSurface(void);
|
||||||
|
static void testTruncated(void);
|
||||||
|
static bool tilesUntouched(uint16_t first, uint16_t end);
|
||||||
|
static bool validRangeIs(uint16_t first, uint16_t end, bool want);
|
||||||
|
static bool writeBank(const char *name, uint32_t length);
|
||||||
|
static void writeLe16(uint8_t *bytes, uint16_t value);
|
||||||
|
|
||||||
|
// ----- State -----
|
||||||
|
|
||||||
|
static uint16_t gExpectedCount;
|
||||||
|
static uint16_t gExpectedPalette[PALETTE_COLORS];
|
||||||
|
static uint8_t gExpectedPixels[EXPECTED_MAX_TILES * TILE_PIXELS];
|
||||||
|
static uint16_t gFail = 0u;
|
||||||
|
static uint8_t gFile[TBK_HEADER_BYTES + BIG_BANK_TILES * TILE_BYTES];
|
||||||
|
static uint16_t gPalette[PALETTE_COLORS];
|
||||||
|
static uint16_t gPass = 0u;
|
||||||
|
static jlSurfaceT *gSurface = NULL;
|
||||||
|
static jlTileT gTiles[TILE_CAPACITY];
|
||||||
|
static bool gValid[TILE_CAPACITY];
|
||||||
|
|
||||||
|
|
||||||
|
static void check(const char *bank, const char *what, bool ok) {
|
||||||
|
if (ok) {
|
||||||
|
gPass++;
|
||||||
|
printf(" ok %s: %s\n", bank, what);
|
||||||
|
} else {
|
||||||
|
gFail++;
|
||||||
|
printf(" FAIL %s: %s\n", bank, what);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Cut the malformed banks from own.tbk (see the header comment). The big
|
||||||
|
// bank goes last because it rewrites gFile's count and extends its payload.
|
||||||
|
static bool craftBanks(void) {
|
||||||
|
FILE *fp;
|
||||||
|
uint32_t goodLength;
|
||||||
|
uint16_t i;
|
||||||
|
uint8_t saved;
|
||||||
|
bool ok;
|
||||||
|
|
||||||
|
fp = jlDataOpen(GOOD_BANK, "rb");
|
||||||
|
if (fp == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
goodLength = (uint32_t)fread(gFile, 1, sizeof(gFile), fp);
|
||||||
|
fclose(fp);
|
||||||
|
if (goodLength != TBK_HEADER_BYTES + (uint32_t)gExpectedCount * TILE_BYTES) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!writeBank(SHORT_HEADER_BANK, SHORT_HEADER_BYTES)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!writeBank(TRUNCATED_BANK, TBK_HEADER_BYTES + TRUNCATED_TILES * TILE_BYTES + TRUNCATED_EXTRA_BYTES)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
saved = gFile[TBK_OFF_MAGIC];
|
||||||
|
gFile[TBK_OFF_MAGIC] = BAD_MAGIC_BYTE;
|
||||||
|
ok = writeBank(BAD_MAGIC_BANK, goodLength);
|
||||||
|
gFile[TBK_OFF_MAGIC] = saved;
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
saved = gFile[TBK_OFF_HAS_PALETTE];
|
||||||
|
gFile[TBK_OFF_HAS_PALETTE] = 0u;
|
||||||
|
ok = writeBank(NO_PALETTE_BANK, goodLength);
|
||||||
|
gFile[TBK_OFF_HAS_PALETTE] = saved;
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tile i of the big bank repeats tile (i % tileCount) of the good one.
|
||||||
|
for (i = gExpectedCount; i < BIG_BANK_TILES; i++) {
|
||||||
|
memcpy(&gFile[TBK_HEADER_BYTES + (uint32_t)i * TILE_BYTES], &gFile[TBK_HEADER_BYTES + (uint32_t)(i % gExpectedCount) * TILE_BYTES], TILE_BYTES);
|
||||||
|
}
|
||||||
|
writeLe16(&gFile[TBK_OFF_TILE_COUNT], BIG_BANK_TILES);
|
||||||
|
return writeBank(BIG_BANK, TBK_HEADER_BYTES + (uint32_t)BIG_BANK_TILES * TILE_BYTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Read expect.bin. The fixture must span more than one block row (so the
|
||||||
|
// 40-block wrap is exercised) and fewer blocks than a surface (so blocks past
|
||||||
|
// the bank can be seen keeping their prior content).
|
||||||
|
static bool loadExpected(void) {
|
||||||
|
FILE *fp;
|
||||||
|
uint8_t head[EXPECTED_OFF_PIXELS];
|
||||||
|
uint32_t pixelBytes;
|
||||||
|
uint16_t i;
|
||||||
|
bool ok;
|
||||||
|
|
||||||
|
fp = jlDataOpen(EXPECTED_FILE, "rb");
|
||||||
|
if (fp == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ok = fread(head, 1, sizeof(head), fp) == sizeof(head);
|
||||||
|
if (ok) {
|
||||||
|
gExpectedCount = readLe16(head);
|
||||||
|
ok = gExpectedCount > TILE_BLOCKS_PER_ROW && gExpectedCount < SURFACE_TILE_BLOCKS;
|
||||||
|
}
|
||||||
|
if (ok) {
|
||||||
|
for (i = 0; i < PALETTE_COLORS; i++) {
|
||||||
|
gExpectedPalette[i] = readLe16(&head[EXPECTED_OFF_PALETTE + i * LE16_BYTES]);
|
||||||
|
}
|
||||||
|
pixelBytes = (uint32_t)gExpectedCount * TILE_PIXELS;
|
||||||
|
ok = fread(gExpectedPixels, 1, pixelBytes, fp) == pixelBytes && fgetc(fp) == EOF;
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool paletteIsExpected(void) {
|
||||||
|
return memcmp(gPalette, gExpectedPalette, sizeof(gPalette)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool paletteUntouched(void) {
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < PALETTE_COLORS; i++) {
|
||||||
|
if (gPalette[i] != PALETTE_SENTINEL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Clear the surface to FILL_COLOR, then paste gTiles[0..count) in the same
|
||||||
|
// row-major block order jlTileBankLoadToSurface uses.
|
||||||
|
static void pasteLoadedTiles(uint16_t count) {
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
jlSurfaceClear(gSurface, FILL_COLOR);
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
jlTilePaste(gSurface, (uint8_t)(i % TILE_BLOCKS_PER_ROW), (uint8_t)(i / TILE_BLOCKS_PER_ROW), &gTiles[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint16_t readLe16(const uint8_t *bytes) {
|
||||||
|
return (uint16_t)(bytes[0] | (bytes[1] << CHAR_BIT));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Poison every output so a check can tell written slots from untouched ones:
|
||||||
|
// tiles and palette get sentinels, and outValid is preset TRUE so a loader
|
||||||
|
// that fails to clear it is caught.
|
||||||
|
static void resetBuffers(void) {
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
memset(gTiles, TILE_SENTINEL, sizeof(gTiles));
|
||||||
|
for (i = 0; i < TILE_CAPACITY; i++) {
|
||||||
|
gValid[i] = true;
|
||||||
|
}
|
||||||
|
for (i = 0; i < PALETTE_COLORS; i++) {
|
||||||
|
gPalette[i] = PALETTE_SENTINEL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Every pixel of gSurface: blocks below tilesPasted must show the PNG's tile
|
||||||
|
// (block % tileCount), every other block FILL_COLOR.
|
||||||
|
static bool surfaceMatches(uint16_t tilesPasted) {
|
||||||
|
int16_t x;
|
||||||
|
int16_t y;
|
||||||
|
uint16_t block;
|
||||||
|
uint8_t want;
|
||||||
|
uint8_t got;
|
||||||
|
|
||||||
|
for (y = 0; y < SURFACE_HEIGHT; y++) {
|
||||||
|
for (x = 0; x < SURFACE_WIDTH; x++) {
|
||||||
|
block = (uint16_t)((y / TILE_PIXELS_PER_SIDE) * TILE_BLOCKS_PER_ROW + x / TILE_PIXELS_PER_SIDE);
|
||||||
|
want = FILL_COLOR;
|
||||||
|
if (block < tilesPasted) {
|
||||||
|
want = gExpectedPixels[(uint32_t)(block % gExpectedCount) * TILE_PIXELS + (y % TILE_PIXELS_PER_SIDE) * TILE_PIXELS_PER_SIDE + x % TILE_PIXELS_PER_SIDE];
|
||||||
|
}
|
||||||
|
got = jlSamplePixel(gSurface, x, y);
|
||||||
|
if (got != want) {
|
||||||
|
printf(" first mismatch at (%d,%d): got %u, want %u\n", x, y, got, want);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void testArgumentGuards(void) {
|
||||||
|
resetBuffers();
|
||||||
|
check("args", "NULL path loads nothing", jlTileBankLoad(NULL, gTiles, TILE_CAPACITY, gValid, gPalette) == 0u);
|
||||||
|
check("args", "NULL tile buffer loads nothing", jlTileBankLoad(GOOD_BANK, NULL, TILE_CAPACITY, gValid, gPalette) == 0u);
|
||||||
|
check("args", "maxTiles 0 loads nothing", jlTileBankLoad(GOOD_BANK, gTiles, 0u, gValid, gPalette) == 0u);
|
||||||
|
check("args", "no tile written by any of those", tilesUntouched(0u, TILE_CAPACITY));
|
||||||
|
check("args", "to-surface NULL path pastes nothing", jlTileBankLoadToSurface(NULL, gSurface, gPalette) == 0u);
|
||||||
|
check("args", "to-surface NULL surface pastes nothing", jlTileBankLoadToSurface(GOOD_BANK, NULL, gPalette) == 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void testBigBank(void) {
|
||||||
|
uint16_t loaded;
|
||||||
|
uint16_t last;
|
||||||
|
|
||||||
|
resetBuffers();
|
||||||
|
loaded = jlTileBankLoad(BIG_BANK, gTiles, TILE_CAPACITY, gValid, gPalette);
|
||||||
|
check(BIG_BANK, "loads more tiles than a surface holds", loaded == BIG_BANK_TILES);
|
||||||
|
check(BIG_BANK, "valid true for every tile", validRangeIs(0u, BIG_BANK_TILES, true));
|
||||||
|
pasteLoadedTiles(SURFACE_TILE_BLOCKS);
|
||||||
|
check(BIG_BANK, "a surface-full of loaded tiles matches the PNG", surfaceMatches(SURFACE_TILE_BLOCKS));
|
||||||
|
last = BIG_BANK_TILES - 1u;
|
||||||
|
check(BIG_BANK, "the tile past a surface-full matches the PNG", memcmp(&gTiles[last], &gTiles[last % gExpectedCount], sizeof(jlTileT)) == 0);
|
||||||
|
|
||||||
|
jlSurfaceClear(gSurface, FILL_COLOR);
|
||||||
|
loaded = jlTileBankLoadToSurface(BIG_BANK, gSurface, gPalette);
|
||||||
|
check(BIG_BANK, "to-surface stops at a full surface", loaded == SURFACE_TILE_BLOCKS);
|
||||||
|
check(BIG_BANK, "to-surface fills the surface from the PNG", surfaceMatches(SURFACE_TILE_BLOCKS));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void testGoodBank(void) {
|
||||||
|
uint16_t loaded;
|
||||||
|
|
||||||
|
resetBuffers();
|
||||||
|
loaded = jlTileBankLoad(GOOD_BANK, gTiles, TILE_CAPACITY, gValid, gPalette);
|
||||||
|
check(GOOD_BANK, "loads every tile", loaded == gExpectedCount);
|
||||||
|
check(GOOD_BANK, "valid true for the loaded slots", validRangeIs(0u, gExpectedCount, true));
|
||||||
|
check(GOOD_BANK, "valid false past the bank", validRangeIs(gExpectedCount, TILE_CAPACITY, false));
|
||||||
|
check(GOOD_BANK, "slots past the bank untouched", tilesUntouched(gExpectedCount, TILE_CAPACITY));
|
||||||
|
check(GOOD_BANK, "palette matches the PNG", paletteIsExpected());
|
||||||
|
pasteLoadedTiles(gExpectedCount);
|
||||||
|
check(GOOD_BANK, "pasted tiles match the PNG", surfaceMatches(gExpectedCount));
|
||||||
|
|
||||||
|
resetBuffers();
|
||||||
|
loaded = jlTileBankLoad(GOOD_BANK, gTiles, TILE_CAPACITY, NULL, NULL);
|
||||||
|
check(GOOD_BANK, "loads with NULL outValid and outPalette", loaded == gExpectedCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void testMaxTiles(void) {
|
||||||
|
uint16_t loaded;
|
||||||
|
|
||||||
|
resetBuffers();
|
||||||
|
loaded = jlTileBankLoad(GOOD_BANK, gTiles, SMALL_MAX_TILES, gValid, gPalette);
|
||||||
|
check(GOOD_BANK, "a small maxTiles stops the load there", loaded == SMALL_MAX_TILES);
|
||||||
|
check(GOOD_BANK, "valid true up to maxTiles", validRangeIs(0u, SMALL_MAX_TILES, true));
|
||||||
|
check(GOOD_BANK, "valid past maxTiles untouched", validRangeIs(SMALL_MAX_TILES, TILE_CAPACITY, true));
|
||||||
|
check(GOOD_BANK, "tiles past maxTiles untouched", tilesUntouched(SMALL_MAX_TILES, TILE_CAPACITY));
|
||||||
|
pasteLoadedTiles(SMALL_MAX_TILES);
|
||||||
|
check(GOOD_BANK, "the maxTiles tiles match the PNG", surfaceMatches(SMALL_MAX_TILES));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void testNoPalette(void) {
|
||||||
|
uint16_t loaded;
|
||||||
|
|
||||||
|
resetBuffers();
|
||||||
|
loaded = jlTileBankLoad(NO_PALETTE_BANK, gTiles, TILE_CAPACITY, gValid, gPalette);
|
||||||
|
check(NO_PALETTE_BANK, "loads every tile", loaded == gExpectedCount);
|
||||||
|
check(NO_PALETTE_BANK, "palette untouched", paletteUntouched());
|
||||||
|
|
||||||
|
jlSurfaceClear(gSurface, FILL_COLOR);
|
||||||
|
loaded = jlTileBankLoadToSurface(NO_PALETTE_BANK, gSurface, gPalette);
|
||||||
|
check(NO_PALETTE_BANK, "to-surface pastes every tile", loaded == gExpectedCount);
|
||||||
|
check(NO_PALETTE_BANK, "to-surface palette untouched", paletteUntouched());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A refused bank loads nothing, clears outValid, and leaves the tiles, the
|
||||||
|
// palette and the surface alone.
|
||||||
|
static void testRefused(const char *bank) {
|
||||||
|
uint16_t loaded;
|
||||||
|
|
||||||
|
resetBuffers();
|
||||||
|
loaded = jlTileBankLoad(bank, gTiles, TILE_CAPACITY, gValid, gPalette);
|
||||||
|
check(bank, "refused (loads nothing)", loaded == 0u);
|
||||||
|
check(bank, "valid false everywhere", validRangeIs(0u, TILE_CAPACITY, false));
|
||||||
|
check(bank, "tiles untouched", tilesUntouched(0u, TILE_CAPACITY));
|
||||||
|
check(bank, "palette untouched", paletteUntouched());
|
||||||
|
|
||||||
|
jlSurfaceClear(gSurface, FILL_COLOR);
|
||||||
|
loaded = jlTileBankLoadToSurface(bank, gSurface, gPalette);
|
||||||
|
check(bank, "to-surface refused (pastes nothing)", loaded == 0u);
|
||||||
|
check(bank, "to-surface surface untouched", surfaceMatches(0u));
|
||||||
|
check(bank, "to-surface palette untouched", paletteUntouched());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void testToSurface(void) {
|
||||||
|
uint16_t loaded;
|
||||||
|
|
||||||
|
resetBuffers();
|
||||||
|
jlSurfaceClear(gSurface, FILL_COLOR);
|
||||||
|
loaded = jlTileBankLoadToSurface(GOOD_BANK, gSurface, gPalette);
|
||||||
|
check(GOOD_BANK, "to-surface pastes every tile", loaded == gExpectedCount);
|
||||||
|
check(GOOD_BANK, "to-surface palette matches the PNG", paletteIsExpected());
|
||||||
|
check(GOOD_BANK, "to-surface pixels match the PNG, later blocks keep theirs", surfaceMatches(gExpectedCount));
|
||||||
|
// scripts/verify-<port>-tilebank.sh read this line: each port's own bake,
|
||||||
|
// drawn by examples/tilebank, must hash the same.
|
||||||
|
printf(" info %s: to-surface hash %08lX (a port loading its own bake must match)\n", GOOD_BANK, (unsigned long)jlSurfaceHash(gSurface));
|
||||||
|
|
||||||
|
jlSurfaceClear(gSurface, FILL_COLOR);
|
||||||
|
loaded = jlTileBankLoadToSurface(GOOD_BANK, gSurface, NULL);
|
||||||
|
check(GOOD_BANK, "to-surface pastes with NULL outPalette", loaded == gExpectedCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void testTruncated(void) {
|
||||||
|
uint16_t loaded;
|
||||||
|
|
||||||
|
resetBuffers();
|
||||||
|
loaded = jlTileBankLoad(TRUNCATED_BANK, gTiles, TILE_CAPACITY, gValid, gPalette);
|
||||||
|
check(TRUNCATED_BANK, "loads only the whole tiles", loaded == TRUNCATED_TILES);
|
||||||
|
check(TRUNCATED_BANK, "valid true for the whole tiles", validRangeIs(0u, TRUNCATED_TILES, true));
|
||||||
|
check(TRUNCATED_BANK, "valid false from the cut on", validRangeIs(TRUNCATED_TILES, TILE_CAPACITY, false));
|
||||||
|
pasteLoadedTiles(TRUNCATED_TILES);
|
||||||
|
check(TRUNCATED_BANK, "the whole tiles match the PNG", surfaceMatches(TRUNCATED_TILES));
|
||||||
|
|
||||||
|
jlSurfaceClear(gSurface, FILL_COLOR);
|
||||||
|
loaded = jlTileBankLoadToSurface(TRUNCATED_BANK, gSurface, gPalette);
|
||||||
|
check(TRUNCATED_BANK, "to-surface pastes only the whole tiles", loaded == TRUNCATED_TILES);
|
||||||
|
check(TRUNCATED_BANK, "to-surface leaves the cut tile's block alone", surfaceMatches(TRUNCATED_TILES));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool tilesUntouched(uint16_t first, uint16_t end) {
|
||||||
|
uint16_t i;
|
||||||
|
uint16_t b;
|
||||||
|
|
||||||
|
for (i = first; i < end; i++) {
|
||||||
|
for (b = 0; b < TILE_BYTES; b++) {
|
||||||
|
if (gTiles[i].pixels[b] != TILE_SENTINEL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool validRangeIs(uint16_t first, uint16_t end, bool want) {
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
for (i = first; i < end; i++) {
|
||||||
|
if (gValid[i] != want) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool writeBank(const char *name, uint32_t length) {
|
||||||
|
FILE *fp;
|
||||||
|
bool ok;
|
||||||
|
|
||||||
|
fp = jlDataOpen(name, "wb");
|
||||||
|
if (fp == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ok = fwrite(gFile, 1, length, fp) == length;
|
||||||
|
return (fclose(fp) == 0) && ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void writeLe16(uint8_t *bytes, uint16_t value) {
|
||||||
|
bytes[0] = (uint8_t)value;
|
||||||
|
bytes[1] = (uint8_t)(value >> CHAR_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
jlConfigT config;
|
||||||
|
int i;
|
||||||
|
bool ready;
|
||||||
|
|
||||||
|
printf("tilebank-host: jlTileBankLoad + jlTileBankLoadToSurface on the BLANK port (real files)\n");
|
||||||
|
memset(&config, 0, sizeof(config));
|
||||||
|
if (!jlInit(&config)) {
|
||||||
|
printf("tilebank-host: jlInit failed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
gSurface = jlSurfaceCreate();
|
||||||
|
ready = gSurface != NULL;
|
||||||
|
check("setup", "jlSurfaceCreate", ready);
|
||||||
|
ready = ready && loadExpected();
|
||||||
|
check("setup", EXPECTED_FILE " read (fixture spans more than a block row, less than a surface)", ready);
|
||||||
|
ready = ready && craftBanks();
|
||||||
|
check("setup", "malformed banks cut from " GOOD_BANK, ready);
|
||||||
|
check("setup", "other targets' bakes to refuse were passed in", argc > 1);
|
||||||
|
|
||||||
|
if (ready) {
|
||||||
|
testArgumentGuards();
|
||||||
|
testGoodBank();
|
||||||
|
testMaxTiles();
|
||||||
|
testToSurface();
|
||||||
|
testTruncated();
|
||||||
|
testNoPalette();
|
||||||
|
testBigBank();
|
||||||
|
for (i = 1; i < argc; i++) {
|
||||||
|
testRefused(argv[i]);
|
||||||
|
}
|
||||||
|
testRefused(BAD_MAGIC_BANK);
|
||||||
|
testRefused(SHORT_HEADER_BANK);
|
||||||
|
testRefused(MISSING_BANK);
|
||||||
|
}
|
||||||
|
|
||||||
|
jlSurfaceDestroy(gSurface);
|
||||||
|
jlShutdown();
|
||||||
|
printf("tilebank-host: %u passed, %u failed\n", (unsigned)gPass, (unsigned)gFail);
|
||||||
|
return (gFail == 0u) ? 0 : 1;
|
||||||
|
}
|
||||||
86
tests/host/tileBankStage.py
Normal file
86
tests/host/tileBankStage.py
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# tileBankStage.py -- stage the tile-bank fixture for one target's check.
|
||||||
|
#
|
||||||
|
# Bakes an indexed PNG (tests/host/tileBank.png) with assetbake for TARGET and
|
||||||
|
# for every other target assetbake knows, and decodes the PNG to the pixels a
|
||||||
|
# correct load must show, all into OUTDIR:
|
||||||
|
# own.tbk the TARGET bake: the bank that must load
|
||||||
|
# refuse<n>.tbk n = 1, 2, ...: every other target's bake, in TARGET_NAMES
|
||||||
|
# order; each must be refused. Read from assetbake's own
|
||||||
|
# TARGET_NAMES, so a new target is covered on arrival.
|
||||||
|
# expect.bin the PNG decoded here with PIL, independent of assetbake's
|
||||||
|
# encoders: tileCount LE16, the first 16 palette entries as
|
||||||
|
# LE16 $0RGB, then each tile's 64 colour indices (tiles
|
||||||
|
# row-major across the sheet, each tile row-major)
|
||||||
|
#
|
||||||
|
# Two readers share these names and this layout: tests/host/tileBankHost.c
|
||||||
|
# (the host gate, scripts/check-tilebank.sh, TARGET dos) and
|
||||||
|
# examples/tilebank/tilebank.c (make/<port>.mk stages its DATA/tilebank with
|
||||||
|
# TARGET = the port; scripts/verify-<port>-tilebank.sh runs it).
|
||||||
|
#
|
||||||
|
# Usage: tileBankStage.py TARGET PNG OUTDIR
|
||||||
|
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "tools", "assetbake"))
|
||||||
|
from assetbake import TARGET_NAMES, bakeTileBank # noqa: E402
|
||||||
|
|
||||||
|
TILE_PX = 8
|
||||||
|
COLORS = 16
|
||||||
|
NIBBLE_BITS = 4
|
||||||
|
OWN_BANK = "own.tbk"
|
||||||
|
REFUSE_PATTERN = "refuse%d.tbk"
|
||||||
|
EXPECT_FILE = "expect.bin"
|
||||||
|
|
||||||
|
|
||||||
|
def writeExpected(pngPath, outPath):
|
||||||
|
img = Image.open(pngPath)
|
||||||
|
if img.mode != "P":
|
||||||
|
sys.exit("tileBankStage: %s is not an indexed PNG" % pngPath)
|
||||||
|
w, h = img.size
|
||||||
|
px = img.load()
|
||||||
|
raw = img.getpalette() or []
|
||||||
|
out = bytearray(struct.pack("<H", (w // TILE_PX) * (h // TILE_PX)))
|
||||||
|
for i in range(COLORS):
|
||||||
|
rgb = raw[i * 3:i * 3 + 3]
|
||||||
|
if len(rgb) < 3:
|
||||||
|
rgb = [0, 0, 0]
|
||||||
|
r, g, b = (c >> NIBBLE_BITS for c in rgb)
|
||||||
|
out += struct.pack("<H", (r << (2 * NIBBLE_BITS)) | (g << NIBBLE_BITS) | b)
|
||||||
|
for ty in range(0, h, TILE_PX):
|
||||||
|
for tx in range(0, w, TILE_PX):
|
||||||
|
for y in range(TILE_PX):
|
||||||
|
for x in range(TILE_PX):
|
||||||
|
out.append(px[tx + x, ty + y])
|
||||||
|
with open(outPath, "wb") as f:
|
||||||
|
f.write(out)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) != 4:
|
||||||
|
sys.exit("usage: tileBankStage.py TARGET PNG OUTDIR")
|
||||||
|
target, pngPath, outDir = sys.argv[1:]
|
||||||
|
if target not in TARGET_NAMES:
|
||||||
|
sys.exit("tileBankStage: unknown target '%s' (assetbake knows: %s)" % (target, " ".join(TARGET_NAMES)))
|
||||||
|
os.makedirs(outDir, exist_ok=True)
|
||||||
|
# A target assetbake dropped must not leave its old bake behind to be
|
||||||
|
# counted as a refusal.
|
||||||
|
for stale in glob.glob(os.path.join(outDir, REFUSE_PATTERN.replace("%d", "*"))):
|
||||||
|
os.remove(stale)
|
||||||
|
bakeTileBank(pngPath, os.path.join(outDir, OWN_BANK), TARGET_NAMES[target])
|
||||||
|
n = 0
|
||||||
|
for other in TARGET_NAMES:
|
||||||
|
if other == target:
|
||||||
|
continue
|
||||||
|
n += 1
|
||||||
|
bakeTileBank(pngPath, os.path.join(outDir, REFUSE_PATTERN % n), TARGET_NAMES[other])
|
||||||
|
writeExpected(pngPath, os.path.join(outDir, EXPECT_FILE))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -95,7 +95,6 @@ int main(void) {
|
||||||
uint8_t k;
|
uint8_t k;
|
||||||
|
|
||||||
config.codegenBytes = 160UL * 1024;
|
config.codegenBytes = 160UL * 1024;
|
||||||
config.audioBytes = 32UL * 1024;
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
printf("jlInit failed\n");
|
printf("jlInit failed\n");
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue