More bug squashing.

This commit is contained in:
Scott Duensing 2026-10-19 15:46:47 -05:00
parent c2be2e4b07
commit b28916f930
214 changed files with 3161 additions and 2310 deletions

View file

@ -41,7 +41,7 @@ include $(dir $(firstword $(MAKEFILE_LIST)))make/common.mk
ALL_TARGETS := $(JOEY_PORTS_INSTALLED) ALL_TARGETS := $(JOEY_PORTS_INSTALLED)
.PHONY: all iigs iigs-disk iigs-verify amiga atarist ste dos x68000 dvx sdl3 web macos win32 win16 psp haiku haiku32 beos blank-check tools clean help status verify verify-audio verify-conform verify-serial docs .PHONY: all iigs iigs-disk iigs-game-disks iigs-verify amiga atarist ste dos x68000 dvx sdl3 web macos win32 win16 psp haiku haiku32 beos blank-check tools clean help status verify verify-audio verify-conform verify-serial docs
all: $(ALL_TARGETS) tools all: $(ALL_TARGETS) tools
ifeq ($(ALL_TARGETS),) ifeq ($(ALL_TARGETS),)
@ -58,6 +58,9 @@ iigs:
iigs-disk: iigs-disk:
@$(MAKE) -f $(REPO_DIR)/make/iigs.mk iigs-disk @$(MAKE) -f $(REPO_DIR)/make/iigs.mk iigs-disk
iigs-game-disks:
@$(MAKE) -f $(REPO_DIR)/make/iigs.mk iigs-game-disks
iigs-verify: iigs-verify:
@$(MAKE) -f $(REPO_DIR)/make/iigs.mk iigs-verify @$(MAKE) -f $(REPO_DIR)/make/iigs.mk iigs-verify
@ -169,7 +172,8 @@ status:
help: help:
@echo "Targets: iigs amiga atarist ste dos x68000 dvx sdl3 web macos win32 win16 psp haiku haiku32 beos all clean status help" @echo "Targets: iigs amiga atarist ste dos x68000 dvx sdl3 web macos win32 win16 psp haiku haiku32 beos all clean status help"
@echo " iigs-disk iigs-verify (IIgs MAME visual check)" @echo " iigs-disk (the first examples that fit one 800 KB disk) iigs-game-disks (a bootable disk for every example)"
@echo " 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 (build every port, then run every gate; PORTS= subset)"
@echo " verify-audio (every port's audio gate; PORTS=\"atarist amiga\" for a subset)" @echo " verify-audio (every port's audio gate; PORTS=\"atarist amiga\" for a subset)"

View file

@ -71,15 +71,18 @@ hand, `source toolchains/env.sh` sets the same variables.
the example programs (`hello`, `pattern`, `draw`, `keys`, `serial`, the example programs (`hello`, `pattern`, `draw`, `keys`, `serial`,
`sertest`, `save`, `tilebank`, `joy`, `sprite`, `scroll`, `audio`, `uber`, `sertest`, `save`, `tilebank`, `joy`, `sprite`, `scroll`, `audio`, `uber`,
`adventure`, `adventure2`) for each. The IIgs is the exception: `adventure`, `adventure2`) 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; `make iigs-disk` builds its examples and packs
packages them onto a disk image. DVX needs the `dvx` installer component, the first that fit onto one 800 KB disk image, and `make iigs-game-disks` gives
which unpacks DVX and its SDK from a DVX tree's distribution, `dist/dvx.zip` every example a bootable disk of its own. DVX needs the `dvx` installer
and `dist/sdk.zip` (`./install.sh --dvx-dir PATH install dvx`; the default component, which unpacks DVX and its SDK from a DVX tree's distribution,
is `../dvxgui`); its examples land in `build/dvx/bin/apps/kpunch/<name>/`, `dist/dvx.zip` and `dist/sdk.zip` (`./install.sh --dvx-dir PATH install dvx`;
laid out as they install in DVX. SDL3 needs the `sdl3` component the default is `../dvxgui`); its examples land in `build/dvx/bin/apps/kpunch/<name>/`,
(`./install.sh install toolchain:sdl3`), which builds SDL 3 from its release laid out as they install in DVX, and each is also a DVX add-on package,
source; its examples are native programs in `build/sdl3/bin/`, and running `build/dvx/bin/packages/<name>.dpk`, which DVX's Software program installs.
one takes seconds rather than an emulator's minutes. The web port needs the SDL3 needs the `sdl3` component (`./install.sh install toolchain:sdl3`), which
builds SDL 3 from its release source; its examples are native programs in
`build/sdl3/bin/`, and running one takes seconds rather than an emulator's
minutes. The web port needs the
`web` components (`./install.sh install toolchain:web`: Emscripten through `web` components (`./install.sh install toolchain:web`: Emscripten through
emsdk, and SDL 3 built with it); its examples are pages in emsdk, and SDL 3 built with it); its examples are pages in
`build/web/bin/<NAME>/`. The macOS port needs `toolchain:macos` (zig, `build/web/bin/<NAME>/`. The macOS port needs `toolchain:macos` (zig,
@ -92,7 +95,7 @@ needs `toolchain:win16` (Open Watcom) and, to run its programs,
your own install disc and S3 display driver). The PSP port your own install disc and S3 display driver). The PSP port
needs `toolchain:psp` (pspdev's prebuilt release, and SDL 3 built with it) needs `toolchain:psp` (pspdev's prebuilt release, and SDL 3 built with it)
and, to run its programs, `emulator:psp` (PPSSPP's headless build, patched and, to run its programs, `emulator:psp` (PPSSPP's headless build, patched
for the gates); its examples are folders `build/psp/bin/<NAME>/` holding for the tests); its examples are folders `build/psp/bin/<NAME>/` holding
`EBOOT.PBP` and `DATA/`, ready for a memory stick's `PSP/GAME/`. The Haiku `EBOOT.PBP` and `DATA/`, ready for a memory stick's `PSP/GAME/`. The Haiku
ports need `toolchain:haiku` or `toolchain:haiku32` (cross compilers built ports need `toolchain:haiku` or `toolchain:haiku32` (cross compilers built
from Haiku R1/beta6) and, to run their programs, `emulator:haiku` or from Haiku R1/beta6) and, to run their programs, `emulator:haiku` or
@ -170,7 +173,7 @@ make -f make/x68000.mk EXTRA_CFLAGS=-DJOEY_LOG_SERIAL_TEE
``` ```
## Conformance gate ## Conformance test
`examples/uber` is the conformance vehicle: it exercises every public op and `examples/uber` is the conformance vehicle: it exercises every public op and
logs a surface hash per op. logs a surface hash per op.
@ -194,7 +197,7 @@ scripts/bench-web.sh && build/tools/diffUberHashes tests/goldens/uber/dos.txt bu
scripts/bench-psp.sh && build/tools/diffUberHashes tests/goldens/uber/dos.txt build/psp/bin/UBER/joeylog.txt scripts/bench-psp.sh && build/tools/diffUberHashes tests/goldens/uber/dos.txt build/psp/bin/UBER/joeylog.txt
``` ```
The X68000 has gates of its own: The X68000 has tests of its own:
``` ```
make -f make/x68000.mk x68000-verify-golden # ~1 min under the patched MAME make -f make/x68000.mk x68000-verify-golden # ~1 min under the patched MAME
@ -203,10 +206,10 @@ make -f make/x68000.mk x68000-verify-joy # both joysticks, every line, ~10
make -f make/x68000.mk x68000-verify-serial-scb # RS-232C under per-line palettes, ~3 min make -f make/x68000.mk x68000-verify-serial-scb # RS-232C under per-line palettes, ~3 min
``` ```
These X68000 gates run under the patched MAME and the X68000 firmware that These X68000 tests run under the patched MAME and the X68000 firmware that
`./install.sh install emulator:x68000` sets up. `./install.sh install emulator:x68000` sets up.
DVX's display is gated as well: `scripts/verify-dvx-display.sh` runs UBER's DVX's display is tested as well: `scripts/verify-dvx-display.sh` runs UBER's
probe build (`make dvx` puts it in `build/dvx/probe`) on nine desktop depths probe build (`make dvx` puts it in `build/dvx/probe`) on nine desktop depths
and scales and on the full screen, and checks every displayed pixel against and scales and on the full screen, and checks every displayed pixel against
the stage. the stage.
@ -225,7 +228,7 @@ src/<machine>/ per-machine HAL + per-CPU sprite emitter
src/hosted/ the hosted core (desktop OSes and browsers: RGB src/hosted/ the hosted core (desktop OSes and browsers: RGB
present, virtual frame clock, input events, software present, virtual frame clock, input events, software
mixer) and its backends (src/hosted/sdl3, which the mixer) and its backends (src/hosted/sdl3, which the
sdl3, web and psp ports all build; src/hosted/win32; sdl3, web, and psp ports all build; src/hosted/win32;
src/hosted/win16) src/hosted/win16)
src/m68k/ 68k code shared across machines (planar and word- src/m68k/ 68k code shared across machines (planar and word-
interleaved sprite emitters; surface68k.s, which is interleaved sprite emitters; surface68k.s, which is
@ -258,9 +261,9 @@ tools/ the host tools, all C (make tools builds them into build/t
examples/ example programs examples/ example programs
joey-run run a built program in its emulator joey-run run a built program in its emulator
scripts/ bench / verification scripts (scripts/lib: shared helpers) scripts/ bench / verification scripts (scripts/lib: shared helpers)
tests/gate/ the gates' C helpers (screenshot, audio, and serial checks) tests/gate/ the tests' C helpers (screenshot, audio, and serial checks)
tests/goldens/uber/ frozen UBER conformance hashes tests/goldens/uber/ frozen UBER conformance hashes
install.sh installs toolchains, emulators and firmware install.sh installs toolchains, emulators, and firmware
installer/ the installer's component recipes, patches, lists installer/ the installer's component recipes, patches, lists
toolchains/ everything install.sh downloads and builds (not in toolchains/ everything install.sh downloads and builds (not in
git; delete it any time and re-run install.sh) git; delete it any time and re-run install.sh)
@ -380,11 +383,11 @@ void jlFloodFillBounded (jlSurfaceT *s, int16_t x, int16_t y,
Colors are 12-bit `$0RGB`. Color 0 of every palette is forced to Colors are 12-bit `$0RGB`. Color 0 of every palette is forced to
black on `jlPaletteSet`. Each scanline picks one of the 16 palettes black on `jlPaletteSet`. Each scanline picks one of the 16 palettes
via the SCB. Every port honours it. The X68000 presents to its text via the SCB. Every port honors it. The X68000 presents to its text
plane, which is planar like the stage (so a present is a copy) but has plane, which is planar like the stage (so a present is a copy) but has
one 16-colour palette: a raster interrupt reloads that palette wherever one 16-color palette: a raster interrupt reloads that palette wherever
the SCB changes, which for a single-palette screen is never. A build the SCB changes, which for a single-palette screen is never. A build
switch (`X68K_PRESENT_TEXT=0`) uses the 256-colour graphics plane the switch (`X68K_PRESENT_TEXT=0`) uses the 256-color graphics plane the
way the DOS port uses its DAC instead -- each pixel byte is way the DOS port uses its DAC instead -- each pixel byte is
`(scb[y] << 4) | nibble`, exact with no interrupt, but slower. `(scb[y] << 4) | nibble`, exact with no interrupt, but slower.
@ -426,7 +429,7 @@ void jlTilePasteMono (jlSurfaceT *dst, uint8_t bx, uint8_t by,
const jlTileT *in, uint8_t fgColor, uint8_t bgColor); const jlTileT *in, uint8_t fgColor, uint8_t bgColor);
// Load up to maxTiles tiles from a baked .tbk file (per-target planar // Load up to maxTiles tiles from a baked .tbk file (per-target planar
// bytes from tools/assetbake --type tile --target ...). // bytes from assetbake --type tile --target ...).
// Refuses files baked for the wrong target. outValid (optional) marks // Refuses files baked for the wrong target. outValid (optional) marks
// each loaded slot; outPalette (optional) receives the embedded // each loaded slot; outPalette (optional) receives the embedded
// 16-entry $0RGB palette if present. // 16-entry $0RGB palette if present.
@ -464,7 +467,7 @@ jlSpriteT *jlSpriteCreateFromSurface (const jlSurfaceT *src, int16_t x, int16_t
void jlSpriteDestroy (jlSpriteT *sp); void jlSpriteDestroy (jlSpriteT *sp);
// Load up to maxCels cels from a baked .spr file (cross-target chunky // Load up to maxCels cels from a baked .spr file (cross-target chunky
// 4bpp blob from tools/assetbake --type sprite). Each // 4bpp blob from assetbake --type sprite). Each
// cel becomes a freshly-allocated jlSpriteT (release with // cel becomes a freshly-allocated jlSpriteT (release with
// jlSpriteDestroy). outPalette (optional) receives the embedded // jlSpriteDestroy). outPalette (optional) receives the embedded
// 16-entry $0RGB palette if present. // 16-entry $0RGB palette if present.
@ -472,7 +475,7 @@ uint16_t jlSpriteBankLoad(const char *path, jlSpriteT **outCels,
uint16_t maxCels, uint16_t *outPalette); uint16_t maxCels, uint16_t *outPalette);
// Same contract, but reading a `.spc` bank pre-compiled for THIS target by // Same contract, but reading a `.spc` bank pre-compiled for THIS target by
// the offline baker `tools/spritebake`: the native blit routines are copied // the offline baker `spritebake`: the native blit routines are copied
// straight into the codegen arena, so there is no startup JIT. The runtime // straight into the codegen arena, so there is no startup JIT. The runtime
// twin writes one back out after a jlSpriteCompile pass. // twin writes one back out after a jlSpriteCompile pass.
uint16_t jlSpriteBankLoadPrecompiled(const char *path, jlSpriteT **outCels, uint16_t jlSpriteBankLoadPrecompiled(const char *path, jlSpriteT **outCels,
@ -510,7 +513,7 @@ uint32_t jlSpriteCodegenBytesTotal (void);
### Assets (baked at build time) ### Assets (baked at build time)
Asset PNGs live in each example's `assets/` directory and are baked Asset PNGs live in each example's `assets/` directory and are baked
to native binary blobs at build time by `tools/assetbake` (C, built to native binary blobs at build time by `assetbake` (C, built
into `build/tools/`). The runtime loads those blobs directly with no into `build/tools/`). The runtime loads those blobs directly with no
conversion -- there is no in-memory chunky-to-planar or palette conversion -- there is no in-memory chunky-to-planar or palette
re-indexing on the device. re-indexing on the device.
@ -529,8 +532,8 @@ Two blob formats:
Both formats embed an optional 16-entry `$0RGB` palette. Both formats embed an optional 16-entry `$0RGB` palette.
``` ```
build/tools/assetbake --type tile --target {amiga|atarist|dos|iigs|x68000} in.png out.tbk build/assetbake --type tile --target {amiga|atarist|dos|iigs|x68000} in.png out.tbk
build/tools/assetbake --type sprite --cell WxH in.png out.spr build/assetbake --type sprite --cell WxH in.png out.spr
``` ```
A program's description wires its bake rules once for every target A program's description wires its bake rules once for every target
@ -549,7 +552,7 @@ on-the-fly path stays alongside the baked load API.
Every program gets an icon on each system that shows programs by one -- Every program gets an icon on each system that shows programs by one --
the IIgs Finder, Workbench, the Program Managers of DVX and Windows 3.1, the IIgs Finder, Workbench, the Program Managers of DVX and Windows 3.1,
Explorer, Tracker, the PSP's menu, the SDL3 window, and the web page -- Explorer, Tracker, the PSP's menu, the SDL3 window, and the web page --
baked by `tools/iconBake` from one picture: the program's `ID_ICON` (a baked by `iconBake` from one picture: the program's `ID_ICON` (a
PNG), or JoeyLib's kangaroo (`assets/joeyIcon.png`). `docs/BuildIntegration.adoc` PNG), or JoeyLib's kangaroo (`assets/joeyIcon.png`). `docs/BuildIntegration.adoc`
(Icons) says what each system gets, and `scripts/verify-all.sh icon` checks (Icons) says what each system gets, and `scripts/verify-all.sh icon` checks
that each one shows it. that each one shows it.
@ -559,7 +562,7 @@ that each one shows it.
`docs/Assets.adoc` (Content Creation Tools) recommends a tool for each kind `docs/Assets.adoc` (Content Creation Tools) recommends a tool for each kind
of asset: GrafX2 or LibreSprite for tile and sprite sheets, Furnace for of asset: GrafX2 or LibreSprite for tile and sprite sheets, Furnace for
chip-tracker music (`./install.sh install furnace`; its VGM exports become chip-tracker music (`./install.sh install furnace`; its VGM exports become
`.song` files through `tools/vgm2song`), MilkyTracker or OpenMPT for `.song` files through `vgm2song`), MilkyTracker or OpenMPT for
`.MOD` music, and sfxr, jfxr, Audacity, or SoX for sound effects. `.MOD` music, and sfxr, jfxr, Audacity, or SoX for sound effects.
@ -585,8 +588,8 @@ pops the next typed character (printable ASCII 0x20..0x7E plus
`JL_CHAR_BACKSPACE`/`TAB`/`RETURN`/`ESCAPE`) or -1 when empty, with `JL_CHAR_BACKSPACE`/`TAB`/`RETURN`/`ESCAPE`) or -1 when empty, with
shift, caps lock, and the machine's keyboard layout already applied by shift, caps lock, and the machine's keyboard layout already applied by
the backend -- so punctuation like `.` and `:` arrives correctly on the backend -- so punctuation like `.` and `:` arrives correctly on
every port. Refilled by the same `jlInputPoll`; see `claudeDocs/input.md` every port. Refilled by the same `jlInputPoll`; see the manual's Input
for per-port sources and limitations. chapter (`docs/ApiInput.adoc`) for per-port sources and limitations.
```c ```c
typedef enum { /* JL_KEY_NONE, JL_KEY_A..JL_KEY_Z, JL_KEY_0..JL_KEY_9, JL_KEY_SPACE, typedef enum { /* JL_KEY_NONE, JL_KEY_A..JL_KEY_Z, JL_KEY_0..JL_KEY_9, JL_KEY_SPACE,
@ -594,7 +597,7 @@ typedef enum { /* JL_KEY_NONE, JL_KEY_A..JL_KEY_Z, JL_KEY_0..JL_KEY_9, JL_KEY_SP
JL_KEY_UP/DOWN/LEFT/RIGHT, JL_KEY_LSHIFT/RSHIFT/LCTRL/LALT, JL_KEY_UP/DOWN/LEFT/RIGHT, JL_KEY_LSHIFT/RSHIFT/LCTRL/LALT,
JL_KEY_F1..JL_KEY_F10, JL_KEY_COUNT */ } jlKeyE; JL_KEY_F1..JL_KEY_F10, JL_KEY_COUNT */ } jlKeyE;
typedef enum { JL_MOUSE_BUTTON_NONE, JL_MOUSE_BUTTON_LEFT, JL_MOUSE_BUTTON_RIGHT, typedef enum { JL_MOUSE_BUTTON_NONE, JL_MOUSE_BUTTON_LEFT, JL_MOUSE_BUTTON_RIGHT,
JL_MOUSE_BUTTON_MIDDLE, JL_MOUSE_BUTTON_COUNT } jlMouseButtonE; JL_MOUSE_BUTTON_COUNT } jlMouseButtonE;
typedef enum { JL_JOYSTICK_0, JL_JOYSTICK_1, JL_JOYSTICK_COUNT } jlJoystickE; typedef enum { JL_JOYSTICK_0, JL_JOYSTICK_1, JL_JOYSTICK_COUNT } jlJoystickE;
typedef enum { JL_JOY_BUTTON_0, JL_JOY_BUTTON_1, JL_JOY_BUTTON_COUNT } jlJoyButtonE; typedef enum { JL_JOY_BUTTON_0, JL_JOY_BUTTON_1, JL_JOY_BUTTON_COUNT } jlJoyButtonE;
@ -657,9 +660,9 @@ void jlAudioPlaySfxStream (uint8_t slot, jlAudioStreamFillT fill, void *ctx,
uint16_t rateHz); uint16_t rateHz);
void jlAudioStopSfx (uint8_t slot); void jlAudioStopSfx (uint8_t slot);
// Portable chip-tracker music: a JYM1 event stream baked by tools/songbake // Portable chip-tracker music: a JYM1 event stream baked by songbake
// (from text notation, or from a tracker: write it in Furnace for the // (from text notation, or from a tracker: write it in Furnace for the
// SN76489 or AY-3-8910, export VGM, convert with tools/vgm2song), // SN76489 or AY-3-8910, export VGM, convert with vgm2song),
// played through the tone/noise layer at register-write cost on every port. // played through the tone/noise layer at register-write cost on every port.
// No CPU mixing, so it is viable where jlAudioPlayMod is not. // No CPU mixing, so it is viable where jlAudioPlayMod is not.
bool jlMusicPlay (const uint8_t *data, uint32_t length, bool loop); bool jlMusicPlay (const uint8_t *data, uint32_t length, bool loop);
@ -729,12 +732,13 @@ Per-port device mapping:
Only the IIgs has multiple built-in ports; elsewhere `MODEM`/`PRINTER` fall Only the IIgs has multiple built-in ports; elsewhere `MODEM`/`PRINTER` fall
back to the natural port. The `serial` example is an echo terminal and back to the natural port. The `serial` example is an echo terminal and
loopback self-test. `scripts/check-serial.sh` unit-tests the portable gate loopback self-test. `scripts/check-serial.sh` unit-tests the portable layer
layer against a loopback mock with no hardware. Full reference, per-port against a loopback mock with no hardware. Full reference, per-port behavior,
behavior, and porting notes: [`claudeDocs/serial.md`](claudeDocs/serial.md). and porting notes: the manual's Serial chapter
([`docs/ApiSerial.adoc`](docs/ApiSerial.adoc)).
### Files, saves & disk space (`joey/file.h`) -- opt-in ### Files, saves, and disk space (`joey/file.h`) -- opt-in
File access, not pulled in by `<joey/joey.h>` -- include `<joey/file.h>`. Two File access, not pulled in by `<joey/joey.h>` -- include `<joey/file.h>`. Two
directories sit beside the executable: read-only `DATA/` (game content) and directories sit beside the executable: read-only `DATA/` (game content) and
@ -762,7 +766,8 @@ Directory create / delete / free-space use a per-port HAL (POSIX `mkdir` +
`remove` + `_dos_dskfre` on the X68000). `scripts/check-save.sh` `remove` + `_dos_dskfre` on the X68000). `scripts/check-save.sh`
round-trips the core against real files; `make iigs-verify-save` boots the round-trips the core against real files; `make iigs-verify-save` boots the
IIgs save path under MAME. Full reference, per-port behavior, and porting IIgs save path under MAME. Full reference, per-port behavior, and porting
notes: [`claudeDocs/save.md`](claudeDocs/save.md). notes: the manual's Files and Saves chapter
([`docs/ApiFile.adoc`](docs/ApiFile.adoc)).
### Debug logging (`joey/debug.h`) ### Debug logging (`joey/debug.h`)
@ -785,7 +790,7 @@ Building with `EXTRA_CFLAGS=-DJOEY_LOG_SERIAL_TEE` additionally mirrors every
unbounded, needs no disk, and arrives in real time, which is what makes a unbounded, needs no disk, and arrives in real time, which is what makes a
wedged run diagnosable. It is OFF by default and is currently implemented wedged run diagnosable. It is OFF by default and is currently implemented
only for the X68000 (`jlpLogTee` in `src/x68000/hal.c`); the host end is only for the X68000 (`jlpLogTee` in `src/x68000/hal.c`); the host end is
the `x68kSerialPeer` gate tool (`tests/gate/x68kSerialPeer.c`, built by the `x68kSerialPeer` test tool (`tests/gate/x68kSerialPeer.c`, built by
`make -f make/tools.mk x68kSerialPeer` into `build/tools/`), whose output is `make -f make/tools.mk x68kSerialPeer` into `build/tools/`), whose output is
line-buffered, so a killed peer keeps everything it captured: line-buffered, so a killed peer keeps everything it captured:

View file

@ -227,10 +227,12 @@ with no caller-side allocation.
[IMPORTANT] [IMPORTANT]
==== ====
Every port feeds streams from `jlAudioFrameTick`, so *you must be calling Every port feeds streams from `jlAudioFrameTick`, so *you must be calling
`jlAudioFrameTick` every frame* or the stream stops. On the IIgs and Amiga the `jlAudioFrameTick` every frame* or the stream stops. On the IIgs the engine
engine plays one chunk at a time (7 KB on the IIgs, 4 KB on the Amiga) and plays one chunk at a time (7 KB) and fires the next from it, so a chunk seam
fires the next from it, so a chunk seam on those ports can carry up to one there can carry up to one frame of jitter. The Amiga loops an 8 KB ring that
frame of jitter. `jlAudioFrameTick` tops up as it plays, with no seams; if the calls stop for
longer than the ring lasts (a third of a second at 22 kHz), it plays the
ring's old contents again.
==== ====
=== jlAudioSfxRawFormat / jlAudioPlaySfxStreamRaw === jlAudioSfxRawFormat / jlAudioPlaySfxStreamRaw
@ -268,7 +270,7 @@ typedef enum {
void jlAudioSetPan(uint8_t slot, jlAudioPanE pan); void jlAudioSetPan(uint8_t slot, jlAudioPanE pan);
---- ----
Places a sound effect slot in the stereo field. Slots start centred, and the Places a sound effect slot in the stereo field. Slots start centered, and the
setting sticks to the *slot*, surviving retriggers -- so set it once for a setting sticks to the *slot*, surviving retriggers -- so set it once for a
sound that should come from one side. sound that should come from one side.
@ -285,13 +287,13 @@ What each port can actually do:
[cols="1,4", options="header"] [cols="1,4", options="header"]
|=== |===
|Platform |Behaviour |Platform |Behavior
|IIgs |All three. The DOC pans per oscillator, so a centred sound plays on two |IIgs |All three. The DOC pans per oscillator, so a centered sound plays on two
oscillators, one per side. A stock IIgs sums to mono, so this shows up only on oscillators, one per side. A stock IIgs sums to mono, so this shows up only on
a stereo card or an emulator. a stereo card or an emulator.
|Amiga |Left and right by choosing the Paula channel side; a Paula channel plays |Amiga |Left and right by choosing the Paula channel side; a Paula channel plays
on one side only. A centred sound plays on a free channel each side when both on one side only. A centered sound plays on a free channel each side when both
sides have one, so it reaches both ears; otherwise on the one channel sides have one, so it reaches both ears; otherwise on the one channel
PTPlayer picks. PTPlayer picks.
|X68000 |Ignored. Everything mixes into one ADPCM stream, so panning would move |X68000 |Ignored. Everything mixes into one ADPCM stream, so panning would move

View file

@ -47,7 +47,7 @@ whatever the memory held.
bool jlInit(const jlConfigT *config); bool jlInit(const jlConfigT *config);
---- ----
Initialises the library: takes over the display, allocates the stage and the Initializes the library: takes over the display, allocates the stage and the
codegen arena, brings up input, and last starts audio (unless `noAudio`). Must codegen arena, brings up input, and last starts audio (unless `noAudio`). Must
be called before any other JoeyLib function. Sound that does not start is not be called before any other JoeyLib function. Sound that does not start is not
a failure: modules and sound effects are then silent, and the log says so a failure: modules and sound effects are then silent, and the log says so
@ -104,8 +104,7 @@ const char *jlPlatformName(void);
*Parameters:* none. *Parameters:* none.
*Returns:* a human-readable platform identifier, for example `"Apple IIgs"`, *Returns:* a human-readable platform identifier, for example `"Apple IIgs"`,
`"MS-DOS"`, `"DVX"`, `"SDL3"` (the web port is the SDL3 port in a browser), `"MS-DOS"`, `"DVX"`, `"SDL3"`, `"Web"`, `"macOS"`, or `"Windows"`.
`"macOS"`, or `"Windows"`.
Library-owned. Library-owned.
=== jlVersionString === jlVersionString

View file

@ -4,7 +4,7 @@ Declared in `<joey/debug.h>`.
A crash-tracing logger that writes `joeylog.txt` in the program's folder, A crash-tracing logger that writes `joeylog.txt` in the program's folder,
beside `DATA/` and `SAVES/` (<<_files>>: on the ST, the current directory; on beside `DATA/` and `SAVES/` (<<_files>>: on the ST, the current directory; on
DVX, the program's settings folder, `CONFIG\APPS\VENDOR\NAME\JOEYLOG.TXT`; DVX, the program's user folder, `USER\APPS\VENDOR\NAME\JOEYLOG.TXT`;
on macOS, its Application Support folder). on macOS, its Application Support folder).
Debugging a program on an emulated 1988 machine is awkward, and this is usually Debugging a program on an emulated 1988 machine is awkward, and this is usually
the fastest way to find out what it did before it stopped doing it. The the fastest way to find out what it did before it stopped doing it. The

View file

@ -7,7 +7,7 @@ no-ops, never errors, and there are no unclipped variants in the public API --
you cannot corrupt memory by drawing off the edge. Every primitive marks the you cannot corrupt memory by drawing off the edge. Every primitive marks the
rows it touched, so `jlStagePresent` knows what to copy. rows it touched, so `jlStagePresent` knows what to copy.
Primitives work in colour indices, and use only the low 4 bits of one. Primitives work in color indices, and use only the low 4 bits of one.
Per-scanline palette resolution happens only at display time, so drawing does Per-scanline palette resolution happens only at display time, so drawing does
not care which palette a line uses. not care which palette a line uses.
@ -18,14 +18,14 @@ not care which palette a line uses.
void jlSurfaceClear(jlSurfaceT *s, uint8_t colorIndex); void jlSurfaceClear(jlSurfaceT *s, uint8_t colorIndex);
---- ----
Fills the whole surface with one colour index. Clearing to colour 0 is a Fills the whole surface with one color index. Clearing to color 0 is a
legitimate clear-to-black, not a skipped no-op. legitimate clear-to-black, not a skipped no-op.
[cols="1,1,4", options="header"] [cols="1,1,4", options="header"]
|=== |===
|Parameter |Type |Description |Parameter |Type |Description
|`s` |`jlSurfaceT *` |Target surface. |`s` |`jlSurfaceT *` |Target surface.
|`colorIndex` |`uint8_t` |Colour index 0..15. |`colorIndex` |`uint8_t` |Color index 0..15.
|=== |===
*Returns:* nothing. *Returns:* nothing.
@ -44,7 +44,7 @@ Plots one pixel. Off-surface coordinates are no-ops.
|Parameter |Type |Description |Parameter |Type |Description
|`s` |`jlSurfaceT *` |Target surface. |`s` |`jlSurfaceT *` |Target surface.
|`x`, `y` |`int16_t` |Position. Valid range is 0..319 and 0..199. |`x`, `y` |`int16_t` |Position. Valid range is 0..319 and 0..199.
|`colorIndex` |`uint8_t` |Colour index 0..15. |`colorIndex` |`uint8_t` |Color index 0..15.
|=== |===
*Returns:* nothing. *Returns:* nothing.
@ -65,7 +65,7 @@ Reads one pixel.
|`x`, `y` |`int16_t` |Position. |`x`, `y` |`int16_t` |Position.
|=== |===
*Returns:* the colour index at that position, or 0 for an off-surface read. *Returns:* the color index at that position, or 0 for an off-surface read.
=== jlDrawLine === jlDrawLine
@ -86,7 +86,7 @@ paths.
|`s` |`jlSurfaceT *` |Target surface. |`s` |`jlSurfaceT *` |Target surface.
|`x0`, `y0` |`int16_t` |Start point, inclusive. |`x0`, `y0` |`int16_t` |Start point, inclusive.
|`x1`, `y1` |`int16_t` |End point, inclusive. |`x1`, `y1` |`int16_t` |End point, inclusive.
|`colorIndex` |`uint8_t` |Colour index 0..15. |`colorIndex` |`uint8_t` |Color index 0..15.
|=== |===
*Returns:* nothing. *Returns:* nothing.
@ -108,7 +108,7 @@ a line, and 1x1 to a single pixel. Zero dimensions are no-ops.
|`s` |`jlSurfaceT *` |Target surface. |`s` |`jlSurfaceT *` |Target surface.
|`x`, `y` |`int16_t` |Top-left corner. |`x`, `y` |`int16_t` |Top-left corner.
|`w`, `h` |`uint16_t` |Size in pixels. Zero is a no-op. |`w`, `h` |`uint16_t` |Size in pixels. Zero is a no-op.
|`colorIndex` |`uint8_t` |Colour index 0..15. |`colorIndex` |`uint8_t` |Color index 0..15.
|=== |===
*Returns:* nothing. *Returns:* nothing.
@ -129,7 +129,7 @@ Fills a solid rectangle. Zero dimensions are no-ops.
|`s` |`jlSurfaceT *` |Target surface. |`s` |`jlSurfaceT *` |Target surface.
|`x`, `y` |`int16_t` |Top-left corner. |`x`, `y` |`int16_t` |Top-left corner.
|`w`, `h` |`uint16_t` |Size in pixels. |`w`, `h` |`uint16_t` |Size in pixels.
|`colorIndex` |`uint8_t` |Colour index 0..15. |`colorIndex` |`uint8_t` |Color index 0..15.
|=== |===
*Returns:* nothing. *Returns:* nothing.
@ -149,9 +149,9 @@ plots a single pixel.
|=== |===
|Parameter |Type |Description |Parameter |Type |Description
|`s` |`jlSurfaceT *` |Target surface. |`s` |`jlSurfaceT *` |Target surface.
|`cx`, `cy` |`int16_t` |Centre. |`cx`, `cy` |`int16_t` |Center.
|`r` |`uint16_t` |Radius in pixels. |`r` |`uint16_t` |Radius in pixels.
|`colorIndex` |`uint8_t` |Colour index 0..15. |`colorIndex` |`uint8_t` |Color index 0..15.
|=== |===
*Returns:* nothing. *Returns:* nothing.
@ -171,9 +171,9 @@ of 0 plots a single pixel.
|=== |===
|Parameter |Type |Description |Parameter |Type |Description
|`s` |`jlSurfaceT *` |Target surface. |`s` |`jlSurfaceT *` |Target surface.
|`cx`, `cy` |`int16_t` |Centre. |`cx`, `cy` |`int16_t` |Center.
|`r` |`uint16_t` |Radius in pixels. |`r` |`uint16_t` |Radius in pixels.
|`colorIndex` |`uint8_t` |Colour index 0..15. |`colorIndex` |`uint8_t` |Color index 0..15.
|=== |===
*Returns:* nothing. *Returns:* nothing.
@ -185,7 +185,7 @@ of 0 plots a single pixel.
void jlFloodFill(jlSurfaceT *s, int16_t x, int16_t y, uint8_t newColor); void jlFloodFill(jlSurfaceT *s, int16_t x, int16_t y, uint8_t newColor);
---- ----
Flood-fills a 4-connected region, replacing every pixel of the original colour Flood-fills a 4-connected region, replacing every pixel of the original color
reachable by north, south, east, and west steps. A no-op if the start point is reachable by north, south, east, and west steps. A no-op if the start point is
off-surface or already `newColor`. The fill keeps a 512-entry stack of spans off-surface or already `newColor`. The fill keeps a 512-entry stack of spans
still to visit, and a region complex enough to need more is left partly still to visit, and a region complex enough to need more is left partly
@ -196,7 +196,7 @@ filled, the same way on every port.
|Parameter |Type |Description |Parameter |Type |Description
|`s` |`jlSurfaceT *` |Target surface. |`s` |`jlSurfaceT *` |Target surface.
|`x`, `y` |`int16_t` |Seed point. |`x`, `y` |`int16_t` |Seed point.
|`newColor` |`uint8_t` |Replacement colour index. |`newColor` |`uint8_t` |Replacement color index.
|=== |===
*Returns:* nothing. *Returns:* nothing.
@ -210,13 +210,13 @@ void jlFloodFillBounded(jlSurfaceT *s, int16_t x, int16_t y,
---- ----
Flood-fills a 4-connected region, stopping at pixels of `boundaryColor` rather Flood-fills a 4-connected region, stopping at pixels of `boundaryColor` rather
than at pixels differing from the seed colour. Every reachable pixel that is than at pixels differing from the seed color. Every reachable pixel that is
not the boundary colour becomes `newColor`. Pixels that are `newColor` already not the boundary color becomes `newColor`. Pixels that are `newColor` already
stop the fill too, so a region behind them is not reached, and a start on stop the fill too, so a region behind them is not reached, and a start on
either colour does nothing. The 512-entry span stack of `jlFloodFill` applies. either color does nothing. The 512-entry span stack of `jlFloodFill` applies.
This is the primitive for vector-art playback, such as Sierra-style picture This is the primitive for vector-art playback, such as Sierra-style picture
drawing: outline a closed region with `jlDrawLine` in the boundary colour, then drawing: outline a closed region with `jlDrawLine` in the boundary color, then
fill it with this. fill it with this.
[cols="1,1,4", options="header"] [cols="1,1,4", options="header"]
@ -224,8 +224,8 @@ fill it with this.
|Parameter |Type |Description |Parameter |Type |Description
|`s` |`jlSurfaceT *` |Target surface. |`s` |`jlSurfaceT *` |Target surface.
|`x`, `y` |`int16_t` |Seed point. |`x`, `y` |`int16_t` |Seed point.
|`newColor` |`uint8_t` |Replacement colour index. |`newColor` |`uint8_t` |Replacement color index.
|`boundaryColor` |`uint8_t` |Colour that stops the fill. |`boundaryColor` |`uint8_t` |Color that stops the fill.
|=== |===
*Returns:* nothing. *Returns:* nothing.

View file

@ -24,9 +24,9 @@ goes there too):
its `SAVES/` there. its `SAVES/` there.
* *DVX* keeps the two apart, because its programs share one current * *DVX* keeps the two apart, because its programs share one current
directory: there `DATA/` is beside the program, in its folder under DVX's directory: there `DATA/` is beside the program, in its folder under DVX's
`APPS`, and `SAVES/` is in the program's own settings folder, `APPS`, and `SAVES/` is in the program's own folder in DVX's user folder,
`CONFIG\APPS\VENDOR\NAME` (`VENDOR` its maker's, <<_a_project>>), which `USER\APPS\VENDOR\NAME` (`VENDOR` its maker's, <<_a_project>>), which
DVX keeps apart from the program files so a reinstall never overwrites it. DVX keeps apart from the program files so an upgrade never overwrites it.
* *The web* keeps them apart too: `DATA/` is packed into the program, and * *The web* keeps them apart too: `DATA/` is packed into the program, and
`SAVES/` and the log are in a folder named after the page `SAVES/` and the log are in a folder named after the page
(`games/hero.html` has `/games/hero/`), its `SAVES/` kept in the browser's (`games/hero.html` has `/games/hero/`), its `SAVES/` kept in the browser's

View file

@ -31,8 +31,8 @@ typedef enum {
} jlKeyE; } jlKeyE;
typedef enum { typedef enum {
JL_MOUSE_BUTTON_NONE = 0, JL_MOUSE_BUTTON_LEFT, JL_MOUSE_BUTTON_NONE = 0, JL_MOUSE_BUTTON_LEFT, JL_MOUSE_BUTTON_RIGHT,
JL_MOUSE_BUTTON_RIGHT, JL_MOUSE_BUTTON_MIDDLE, JL_MOUSE_BUTTON_COUNT JL_MOUSE_BUTTON_COUNT
} jlMouseButtonE; } jlMouseButtonE;
typedef enum { JL_JOYSTICK_0 = 0, JL_JOYSTICK_1, JL_JOYSTICK_COUNT } jlJoystickE; typedef enum { JL_JOYSTICK_0 = 0, JL_JOYSTICK_1, JL_JOYSTICK_COUNT } jlJoystickE;
@ -166,8 +166,8 @@ bool jlMousePresent(void);
*Returns:* `true` if the platform confirmed a mouse at init. On DOS that means *Returns:* `true` if the platform confirmed a mouse at init. On DOS that means
the INT 33h driver reset successfully; on the Amiga, that JoeyLib's input the INT 33h driver reset successfully; on the Amiga, that JoeyLib's input
window opened; on Win32 and Win16, that Windows reports a mouse; on SDL3 and window opened; on Win32 and Win16, that Windows reports a mouse; on SDL3 and
the web, that SDL reports one. The IIgs, ST, X68000, DVX, Haiku, and BeOS the web, that SDL reports one. The IIgs, ST, X68000, DVX, Haiku, BeOS, and
always report one, and the PSP never does. PSP always report one; the PSP's is its analog nub.
=== jlMouseX / jlMouseY === jlMouseX / jlMouseY
@ -208,9 +208,14 @@ bool jlMouseReleased(jlMouseButtonE button);
*Returns:* held state, rising edge, and falling edge respectively. *Returns:* held state, rising edge, and falling edge respectively.
The IIgs mouse has one button, `JL_MOUSE_BUTTON_LEFT`. The ST and X68000 have Every port has two buttons, `JL_MOUSE_BUTTON_LEFT` and `JL_MOUSE_BUTTON_RIGHT`,
left and right. The other ports report left, right, and middle, as far as the whatever its mouse has. A left click made with Ctrl held is the right button,
mouse has them. The PSP has no mouse. on every port: it is down from the press until the button is let go, even if
Ctrl is let go first, and it is never the left button too (Ctrl pressed after
the button changes nothing). So a one-button mouse, the IIgs's or many a
Mac's, has both, and a program can tell its users "Ctrl-click" on any machine.
A middle button, where a mouse has one, is not reported. The PSP's buttons are
L (left) and R (right).
=== jlJoystickConnected === jlJoystickConnected
@ -253,9 +258,9 @@ int8_t jlJoystickY(jlJoystickE js);
*Returns:* axis value, -127 to +127. Digital sticks (the Amiga, ST, and *Returns:* axis value, -127 to +127. Digital sticks (the Amiga, ST, and
X68000) snap to the extremes; analog sticks (the IIgs, DOS, DVX, and the X68000) snap to the extremes; analog sticks (the IIgs, DOS, DVX, and the
hosted ports) return their offset from the centre, scaled to that range, with hosted ports) return their offset from the center, scaled to that range, with
the dead zone applied. On the IIgs and DOS the axes read 0 until the stick's the dead zone applied. On the IIgs and DOS the axes read 0 until the stick's
centre has been read (<<_jljoystickreset>>). Test against a threshold rather center has been read (<<_jljoystickreset>>). Test against a threshold rather
than for exact values, so the same code works with both. than for exact values, so the same code works with both.
=== jlJoyDown / jlJoyPressed / jlJoyReleased === jlJoyDown / jlJoyPressed / jlJoyReleased
@ -292,18 +297,18 @@ buttons are the stick's own, and a one-button stick never reports
void jlJoystickReset(jlJoystickE js, uint8_t deadZone); void jlJoystickReset(jlJoystickE js, uint8_t deadZone);
---- ----
Sets the stick's dead zone and, where the port can, re-centres it and looks Sets the stick's dead zone and, where the port can, re-centers it and looks
for it again: for it again:
* *IIgs*: polling starts again if it had stopped for want of a stick, and the * *IIgs*: polling starts again if it had stopped for want of a stick, and the
stick is re-centred at its next reading. stick is re-centered at its next reading.
* *DOS*: the game port is probed again, and the stick is re-centred at its * *DOS*: the game port is probed again, and the stick is re-centered at its
next reading. next reading.
* *Amiga*: the port goes back to `lowlevel.library`'s own detection. * *Amiga*: the port goes back to `lowlevel.library`'s own detection.
* *Elsewhere* only the dead zone changes. * *Elsewhere* only the dead zone changes.
On the IIgs polling for a stick that is not there stops. If a user may plug a On the IIgs polling for a stick that is not there stops. If a user may plug a
stick in while your program runs, call this, with the stick centred, to pick stick in while your program runs, call this, with the stick centered, to pick
it up. it up.
[cols="1,1,4", options="header"] [cols="1,1,4", options="header"]
@ -321,6 +326,6 @@ it up.
[IMPORTANT] [IMPORTANT]
==== ====
On the IIgs and DOS the next reading after this call is taken as the stick's On the IIgs and DOS the next reading after this call is taken as the stick's
centre, so the user must be holding the stick centred when you call it. The center, so the user must be holding the stick centered when you call it. The
call is safe to make unconditionally. call is safe to make unconditionally.
==== ====

View file

@ -2,18 +2,18 @@
Declared in `<joey/palette.h>`. Declared in `<joey/palette.h>`.
Colours are 12-bit `$0RGB`: bits 11..8 red, 7..4 green, 3..0 blue. Each surface Colors are 12-bit `$0RGB`: bits 11..8 red, 7..4 green, 3..0 blue. Each surface
holds 16 palettes of 16 colours, plus a 200-entry table (the SCB table) naming holds 16 palettes of 16 colors, plus a 200-entry table (the SCB table) naming
which palette each scanline uses. That is how a JoeyLib program shows more than which palette each scanline uses. That is how a JoeyLib program shows more than
16 colours at once. 16 colors at once.
[IMPORTANT] [IMPORTANT]
==== ====
Colour 0 of every palette is black, always. `jlPaletteSet` silently masks entry Color 0 of every palette is black, always. `jlPaletteSet` silently masks entry
0 to `$000` whatever you pass, and `jlSurfaceLoadFile` does the same to every 0 to `$000` whatever you pass, and `jlSurfaceLoadFile` does the same to every
palette it loads. This is deliberate: the Amiga and ST hardware border follows palette it loads. This is deliberate: the Amiga and ST hardware border follows
colour 0 of the active palette, and colour 0 is the transparent colour for color 0 of the active palette, and color 0 is the transparent color for
sprite drawing. You have 15 usable colours per palette. sprite drawing. You have 15 usable colors per palette.
==== ====
Palette and SCB changes to the stage reach the display at the next Palette and SCB changes to the stage reach the display at the next
@ -22,6 +22,26 @@ the same way on every port: a palette index above 15 or a line above 199 makes
the setters do nothing (`jlScbSetRange` stops its range at line 199), and the setters do nothing (`jlScbSetRange` stops its range at line 199), and
`jlScbGet` returns 0 for a line off the surface. `jlScbGet` returns 0 for a line off the surface.
=== The Default Palette
Every surface starts with all 16 of its palettes set to the standard 16 EGA
colors, and every line of its SCB on palette 0: the stage at `jlInit`, and a
surface at `jlSurfaceCreate`. A program that never calls `jlPaletteSet` draws
in these colors:
[cols="1,2,1,1,2,1", options="header"]
|===
|Index |Color |`$0RGB` |Index |Color |`$0RGB`
|0 |Black |`$000` |8 |Dark gray |`$555`
|1 |Blue |`$00A` |9 |Light blue |`$55F`
|2 |Green |`$0A0` |10 |Light green |`$5F5`
|3 |Cyan |`$0AA` |11 |Light cyan |`$5FF`
|4 |Red |`$A00` |12 |Light red |`$F55`
|5 |Magenta |`$A0A` |13 |Light magenta |`$F5F`
|6 |Brown |`$A50` |14 |Yellow |`$FF5`
|7 |Light gray |`$AAA` |15 |White |`$FFF`
|===
=== jlPaletteSet === jlPaletteSet
[source,c] [source,c]
@ -29,7 +49,7 @@ the setters do nothing (`jlScbSetRange` stops its range at line 199), and
void jlPaletteSet(jlSurfaceT *s, uint8_t paletteIndex, const uint16_t *colors16); void jlPaletteSet(jlSurfaceT *s, uint8_t paletteIndex, const uint16_t *colors16);
---- ----
Loads all 16 colours of one palette. Loads all 16 colors of one palette.
[cols="1,1,4", options="header"] [cols="1,1,4", options="header"]
|=== |===
@ -49,7 +69,7 @@ Loads all 16 colours of one palette.
void jlPaletteGet(const jlSurfaceT *s, uint8_t paletteIndex, uint16_t *out16); void jlPaletteGet(const jlSurfaceT *s, uint8_t paletteIndex, uint16_t *out16);
---- ----
Reads all 16 colours of one palette, as they were set: the values passed to Reads all 16 colors of one palette, as they were set: the values passed to
`jlPaletteSet` (with entry 0 forced to `$000`, and bits 12 to 15 kept as `jlPaletteSet` (with entry 0 forced to `$000`, and bits 12 to 15 kept as
passed), not what the display can show of them. passed), not what the display can show of them.

View file

@ -24,7 +24,7 @@ and receives on its own SCC interrupt; DVX goes through DVX's serial library,
which owns the COM ports for every program on the desktop (a port another which owns the COM ports for every program on the desktop (a port another
program has open cannot be opened); Win32 and Win16 go through Windows' own program has open cannot be opened); Win32 and Win16 go through Windows' own
communications calls, whose driver owns the ports the same way; SDL3 on Linux, communications calls, whose driver owns the ports the same way; SDL3 on Linux,
Haiku, and BeOS open the port as a terminal device through termios. The web and macOS, Haiku, and BeOS open the port as a terminal device through termios. The web and
the PSP have no serial port: `jlSerialOpen` fails there. the PSP have no serial port: `jlSerialOpen` fails there.
The model is *non-blocking and buffered*. Received bytes accumulate in a The model is *non-blocking and buffered*. Received bytes accumulate in a
@ -137,7 +137,7 @@ Ways to live with them:
=== Flow Control === Flow Control
`flow` in `jlSerialConfigT` picks the handshake, and every port with a serial `flow` in `jlSerialConfigT` picks the handshake, and every port with a serial
port honours it: port honors it:
* `JL_SERIAL_FLOW_RTSCTS`: transmit waits while the peer holds CTS down, and * `JL_SERIAL_FLOW_RTSCTS`: transmit waits while the peer holds CTS down, and
the port drops RTS when its receive buffer is three quarters full, raising the port drops RTS when its receive buffer is three quarters full, raising

View file

@ -3,7 +3,7 @@
Declared in `<joey/sprite.h>`. Declared in `<joey/sprite.h>`.
A sprite is a rectangle of 8x8 tiles drawn at an arbitrary pixel position with A sprite is a rectangle of 8x8 tiles drawn at an arbitrary pixel position with
colour-0 transparency. Sprite pixel data is `widthTiles * heightTiles * 32` color-0 transparency. Sprite pixel data is `widthTiles * heightTiles * 32`
bytes of 4bpp packed data, laid out as a flat blob of tiles in row-major order. bytes of 4bpp packed data, laid out as a flat blob of tiles in row-major order.
The distinguishing feature is that *sprite blitters are compiled at run time*. The distinguishing feature is that *sprite blitters are compiled at run time*.
@ -55,26 +55,22 @@ It is the recorded region you must not overwrite before restoring.
[source,c] [source,c]
---- ----
#define jlSpriteBackupLength(_type, _wT, _hT) /* ... */ #define jlSpriteBackupLength(_wT, _hT) /* ... */
---- ----
How many `_type` elements a backup buffer needs for a `widthTiles` by How many `uint16_t` a backup buffer needs for a `widthTiles` by `heightTiles`
`heightTiles` sprite at any x position, on every port, rounded up. Use it to sprite at any x position, on every port. A backup buffer *must be an array of
size your backing array; with `uint8_t` it is the size in bytes. `uint16_t`*:
[source,c] [source,c]
---- ----
static uint16_t gBacking[jlSpriteBackupLength(uint16_t, 4, 4)]; static uint16_t gBacking[jlSpriteBackupLength(4, 4)];
---- ----
[WARNING] That gives it the 2-byte alignment the Amiga, ST, and X68000 need: an odd
==== address sends the save and restore calls down a much slower path there, and
Give the backing array *at least 2-byte alignment*. On the Amiga, ST, and makes the trusted variants crash. A buffer from `jlAlloc` is
X68000 an odd buffer address sends the save and restore calls down a much `jlSpriteBackupLength(w, h) * sizeof(uint16_t)` bytes.
slower path, and it makes the trusted variants crash; the
other ports do not mind. Declaring it `uint16_t`, as above, guarantees the
alignment.
====
=== jlSpriteBankLoad === jlSpriteBankLoad
@ -219,7 +215,7 @@ says so; raise `codegenBytes` or destroy sprites you no longer need), the
sprite has no source tile data, the 64 KB the compiler works in cannot be sprite has no source tile data, the 64 KB the compiler works in cannot be
allocated, no routine fits the compiler's limits (16 KB a routine), or the allocated, no routine fits the compiler's limits (16 KB a routine), or the
port has no code emitter (the Sprites row of port has no code emitter (the Sprites row of
<<_per_platform_behaviour_summary>>). Only the full arena sets `jlLastError`. <<_per_platform_behavior_summary>>). Only the full arena sets `jlLastError`.
A `false` is never fatal: the sprite still draws, only slower. A `false` is never fatal: the sprite still draws, only slower.
`true` means at least part of the sprite compiled, not all of it. When the `true` means at least part of the sprite compiled, not all of it. When the
@ -254,7 +250,7 @@ variants. Returns 0 if the sprite was never compiled.
void jlSpriteDraw(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y); void jlSpriteDraw(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y);
---- ----
Draws the sprite at a pixel position. Source pixels of colour 0 are skipped. Draws the sprite at a pixel position. Source pixels of color 0 are skipped.
Off-surface portions are clipped. Compiled routines do not clip, so a sprite Off-surface portions are clipped. Compiled routines do not clip, so a sprite
partly off the surface draws through the interpreter, compiled or not. partly off the surface draws through the interpreter, compiled or not.
@ -286,8 +282,8 @@ would write, so it can be restored later.
|`sp` |`jlSpriteT *` |Sprite whose footprint to capture. |`sp` |`jlSpriteT *` |Sprite whose footprint to capture.
|`x`, `y` |`int16_t` |Position the sprite will be drawn at. |`x`, `y` |`int16_t` |Position the sprite will be drawn at.
|`backup` |`jlSpriteBackupT *` |Receives the capture. You must set `bytes` to |`backup` |`jlSpriteBackupT *` |Receives the capture. You must set `bytes` to
a buffer of at least `jlSpriteBackupLength(uint8_t, w, h)` bytes for fully a `uint16_t` buffer of at least `jlSpriteBackupLength(w, h)` elements for
in-bounds draws. For clipped draws only the visible bytes are stored. On the fully in-bounds draws. For clipped draws only the visible bytes are stored. On the
IIgs the buffer must not cross a 64 KB bank boundary; `jlAlloc`, the C heap, IIgs the buffer must not cross a 64 KB bank boundary; `jlAlloc`, the C heap,
and static arrays never give one that does. With `bytes` `NULL` nothing is and static arrays never give one that does. With `bytes` `NULL` nothing is
copied: the call only fills in the region a save would record. copied: the call only fills in the region a save would record.
@ -344,16 +340,16 @@ void jlSpriteSaveAndDrawTrusted(jlSurfaceT *s, jlSpriteT *sp,
Expert inner-loop entry points. Each has the same effect as its plain Expert inner-loop entry points. Each has the same effect as its plain
counterpart but *skips the per-call geometry validation* -- roughly eleven counterpart but *skips the per-call geometry validation* -- roughly eleven
range and alignment checks plus the NULL and sprite-slot gates. range and alignment checks plus the NULL and sprite-slot checks.
Use them only in a game that pre-compiles its sprites and already clamps Use them only in a game that pre-compiles its sprites and already clamps
positions on screen. Anything the fast path does not recognise falls back to positions on screen. Anything the fast path does not recognize falls back to
the fully validated call, so a conservative caller degrades safely rather than the fully validated call, so a conservative caller degrades safely rather than
corrupting -- but the guarantees below are still yours to keep. corrupting -- but the guarantees below are still yours to keep.
[WARNING] [WARNING]
==== ====
Behaviour is *undefined* -- out-of-bounds writes, crashes -- unless all of the Behavior is *undefined* -- out-of-bounds writes, crashes -- unless all of the
following hold: following hold:
* `s`, `backup`, `+backup->sprite+`, and `+backup->bytes+` are non-`NULL`; * `s`, `backup`, `+backup->sprite+`, and `+backup->bytes+` are non-`NULL`;

View file

@ -2,11 +2,11 @@
Declared in `<joey/surface.h>` and `<joey/present.h>`. Declared in `<joey/surface.h>` and `<joey/present.h>`.
A surface is a 320x200 16-colour image with its own 200-entry SCB table and its A surface is a 320x200 16-color image with its own 200-entry SCB table and its
own 16x16 palette set. Storage is target-native -- chunky 4bpp on the IIgs, own 16x16 palette set. Storage is target-native -- chunky 4bpp on the IIgs,
DOS, DVX, and the hosted ports, planar on the Amiga, ST, and X68000 -- but every DOS, DVX, and the hosted ports, planar on the Amiga, ST, and X68000 -- but every
entry point speaks in colour indices, so your code never sees the difference. entry point speaks in color indices, so your code never sees the difference.
A new surface starts with the 16-colour EGA palette in all 16 of its palettes. A new surface starts with the 16-color EGA palette in all 16 of its palettes.
=== Constants === Constants
@ -19,7 +19,7 @@ A new surface starts with the 16-colour EGA palette in all 16 of its palettes.
|`JL_SURFACE_BYTES_PER_ROW` |160 |Bytes per row in chunky 4bpp form. |`JL_SURFACE_BYTES_PER_ROW` |160 |Bytes per row in chunky 4bpp form.
|`JL_SURFACE_PIXELS_SIZE` |32000 |Total pixel bytes. |`JL_SURFACE_PIXELS_SIZE` |32000 |Total pixel bytes.
|`JL_SURFACE_PALETTE_COUNT` |16 |Palettes per surface. |`JL_SURFACE_PALETTE_COUNT` |16 |Palettes per surface.
|`JL_SURFACE_COLORS_PER_PALETTE` |16 |Colours per palette. |`JL_SURFACE_COLORS_PER_PALETTE` |16 |Colors per palette.
|`JL_SURFACE_PALETTE_ENTRIES` |256 |Total palette entries per surface. |`JL_SURFACE_PALETTE_ENTRIES` |256 |Total palette entries per surface.
|=== |===
@ -146,9 +146,9 @@ save and restore in the same region.
void jlSurfacePasteIndexed(jlSurfaceT *dst, int16_t x, int16_t y, uint16_t w, uint16_t h, const uint8_t *src, uint16_t srcStride, bool doubleWide); void jlSurfacePasteIndexed(jlSurfaceT *dst, int16_t x, int16_t y, uint16_t w, uint16_t h, const uint8_t *src, uint16_t srcStride, bool doubleWide);
---- ----
Writes a rectangle of colour indices from your memory onto a surface: one byte Writes a rectangle of color indices from your memory onto a surface: one byte
a pixel, whatever the port stores pixels as. Row `r` of the source starts at a pixel, whatever the port stores pixels as. Row `r` of the source starts at
`src + r * srcStride`. Each byte's low nibble is the colour index; the high `src + r * srcStride`. Each byte's low nibble is the color index; the high
nibble is ignored, so a byte array of 0..15 with flags in the top bits pastes nibble is ignored, so a byte array of 0..15 with flags in the top bits pastes
as it is. as it is.
@ -166,7 +166,7 @@ marked for the next `jlStagePresent`.
|`dst` |`jlSurfaceT *` |Destination surface. |`dst` |`jlSurfaceT *` |Destination surface.
|`x`, `y` |`int16_t` |Top-left pixel on the surface. |`x`, `y` |`int16_t` |Top-left pixel on the surface.
|`w`, `h` |`uint16_t` |Bytes a row and rows. |`w`, `h` |`uint16_t` |Bytes a row and rows.
|`src` |`const uint8_t *` |The colour indices, row by row. |`src` |`const uint8_t *` |The color indices, row by row.
|`srcStride` |`uint16_t` |Bytes from one source row to the next. |`srcStride` |`uint16_t` |Bytes from one source row to the next.
|`doubleWide` |`bool` |Each byte fills two pixels. |`doubleWide` |`bool` |Each byte fills two pixels.
|=== |===
@ -191,7 +191,7 @@ void jlSurfaceSnapIndexed(const jlSurfaceT *src, int16_t x, int16_t y, uint16_t
---- ----
The reverse of `jlSurfacePasteIndexed`: reads a rectangle of a surface into your The reverse of `jlSurfacePasteIndexed`: reads a rectangle of a surface into your
memory as colour indices, one byte a pixel (0..15), row `r` at memory as color indices, one byte a pixel (0..15), row `r` at
`dst + r * dstStride`. With `doubleWide` the rectangle is `2 * w` pixels wide `dst + r * dstStride`. With `doubleWide` the rectangle is `2 * w` pixels wide
and each byte takes the left pixel of its pair. A byte whose pixel is off the and each byte takes the left pixel of its pair. A byte whose pixel is off the
surface reads 0, as `jlSamplePixel` does. Bytes between `w` and `dstStride` are surface reads 0, as `jlSamplePixel` does. Bytes between `w` and `dstStride` are
@ -203,7 +203,7 @@ left alone.
|`src` |`const jlSurfaceT *` |Source surface. |`src` |`const jlSurfaceT *` |Source surface.
|`x`, `y` |`int16_t` |Top-left pixel on the surface. |`x`, `y` |`int16_t` |Top-left pixel on the surface.
|`w`, `h` |`uint16_t` |Bytes a row and rows. |`w`, `h` |`uint16_t` |Bytes a row and rows.
|`dst` |`uint8_t *` |Where the colour indices go, row by row. |`dst` |`uint8_t *` |Where the color indices go, row by row.
|`dstStride` |`uint16_t` |Bytes from one destination row to the next. |`dstStride` |`uint16_t` |Bytes from one destination row to the next.
|`doubleWide` |`bool` |Read every other pixel. |`doubleWide` |`bool` |Read every other pixel.
|=== |===
@ -256,7 +256,7 @@ nothing is reallocated.
*Returns:* `true` on success; `false` if the file is missing, the wrong size, *Returns:* `true` on success; `false` if the file is missing, the wrong size,
has an SCB byte above 15, or unreadable. A read that fails part way can leave has an SCB byte above 15, or unreadable. A read that fails part way can leave
the pixels partly overwritten; the SCBs and palettes are then untouched. Colour the pixels partly overwritten; the SCBs and palettes are then untouched. Color
0 of every palette loaded is set to black, as `jlPaletteSet` does. Neither file 0 of every palette loaded is set to black, as `jlPaletteSet` does. Neither file
call sets `jlLastError`. call sets `jlLastError`.
@ -304,7 +304,7 @@ The Amiga, the ST, DOS, and the X68000 draw into a second screen page and flip
to it at the next vertical blank, so the picture never tears. A present that comes before to it at the next vertical blank, so the picture never tears. A present that comes before
that blank, right after another one, waits for it: the page it would draw into that blank, right after another one, waits for it: the page it would draw into
is still on the screen. Present once a frame, after `jlWaitVBL`, and it never is still on the screen. Present once a frame, after `jlWaitVBL`, and it never
waits. On DOS, a present that changes colours also waits: VGA loads them in waits. On DOS, a present that changes colors also waits: VGA loads them in
the blank that shows the new page, and the `jlWaitVBL` after it returns at the blank that shows the new page, and the `jlWaitVBL` after it returns at
once, so the loop keeps its pace. once, so the loop keeps its pace.

View file

@ -24,7 +24,7 @@ one. Fonts, terrain sets, and spritesheets are all the same thing.
|`JL_TILE_BLOCKS_PER_ROW` |40 |Tile columns on a surface. |`JL_TILE_BLOCKS_PER_ROW` |40 |Tile columns on a surface.
|`JL_TILE_BLOCKS_PER_COL` |25 |Tile rows on a surface. |`JL_TILE_BLOCKS_PER_COL` |25 |Tile rows on a surface.
|`JL_TILE_NO_GLYPH` |0xFFFF |`jlDrawText` map entry meaning "skip this character". |`JL_TILE_NO_GLYPH` |0xFFFF |`jlDrawText` map entry meaning "skip this character".
|`JL_TILE_MAP_SKIP` |0xFF |`jlTileMapPaste` entry meaning "leave this cell alone"; in a tile layer's map, colour 0. |`JL_TILE_MAP_SKIP` |0xFF |`jlTileMapPaste` entry meaning "leave this cell alone"; in a tile layer's map, color 0.
|`JL_TILE_LAYER_MAX_SIDE` |4096 |Largest tile layer map side, in tiles. |`JL_TILE_LAYER_MAX_SIDE` |4096 |Largest tile layer map side, in tiles.
|=== |===
@ -90,7 +90,7 @@ any tileset with see-through pixels.
|`dstBx`, `dstBy` |`uint8_t` |Destination block coordinates. |`dstBx`, `dstBy` |`uint8_t` |Destination block coordinates.
|`src` |`const jlSurfaceT *` |Source surface. |`src` |`const jlSurfaceT *` |Source surface.
|`srcBx`, `srcBy` |`uint8_t` |Source block coordinates. |`srcBx`, `srcBy` |`uint8_t` |Source block coordinates.
|`transparentIndex` |`uint8_t` |Source colour index treated as transparent. |`transparentIndex` |`uint8_t` |Source color index treated as transparent.
|=== |===
*Returns:* nothing. *Returns:* nothing.
@ -102,8 +102,8 @@ any tileset with see-through pixels.
void jlTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex); void jlTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex);
---- ----
Fills one block with a solid colour. Equivalent to Fills one block with a solid color. Equivalent to
`jlFillRect(s, bx*8, by*8, 8, 8, colorIndex)` but skips the clipping maths, `jlFillRect(s, bx*8, by*8, 8, 8, colorIndex)` but skips the clipping math,
since block coordinates are already known to be in range. since block coordinates are already known to be in range.
[cols="1,1,4", options="header"] [cols="1,1,4", options="header"]
@ -111,7 +111,7 @@ since block coordinates are already known to be in range.
|Parameter |Type |Description |Parameter |Type |Description
|`s` |`jlSurfaceT *` |Target surface. |`s` |`jlSurfaceT *` |Target surface.
|`bx`, `by` |`uint8_t` |Block coordinates. |`bx`, `by` |`uint8_t` |Block coordinates.
|`colorIndex` |`uint8_t` |Colour index 0..15. |`colorIndex` |`uint8_t` |Color index 0..15.
|=== |===
*Returns:* nothing. *Returns:* nothing.
@ -218,8 +218,8 @@ void jlTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by,
---- ----
Pastes a tile as a silhouette: any non-zero source pixel draws `fgColor`, zero Pastes a tile as a silhouette: any non-zero source pixel draws `fgColor`, zero
pixels draw `bgColor`. Useful when the bitmap is a fixed glyph but the colour pixels draw `bgColor`. Useful when the bitmap is a fixed glyph but the color
comes from a separate map, as in C64-style colour RAM. Opaque. comes from a separate map, as in C64-style color RAM. Opaque.
[cols="1,1,4", options="header"] [cols="1,1,4", options="header"]
|=== |===
@ -227,8 +227,8 @@ comes from a separate map, as in C64-style colour RAM. Opaque.
|`dst` |`jlSurfaceT *` |Destination surface. |`dst` |`jlSurfaceT *` |Destination surface.
|`bx`, `by` |`uint8_t` |Block coordinates. |`bx`, `by` |`uint8_t` |Block coordinates.
|`in` |`const jlTileT *` |Source tile, read as a silhouette. |`in` |`const jlTileT *` |Source tile, read as a silhouette.
|`fgColor` |`uint8_t` |Colour for non-zero pixels. Masked to 4 bits. |`fgColor` |`uint8_t` |Color for non-zero pixels. Masked to 4 bits.
|`bgColor` |`uint8_t` |Colour for zero pixels. Masked to 4 bits. |`bgColor` |`uint8_t` |Color for zero pixels. Masked to 4 bits.
|=== |===
*Returns:* nothing. *Returns:* nothing.
@ -263,8 +263,8 @@ charset, anything 1bpp -- so nothing needs expanding into a `jlTileT` first.
|`dst` |`jlSurfaceT *` |Destination surface. |`dst` |`jlSurfaceT *` |Destination surface.
|`bx`, `by` |`uint8_t` |Block coordinates. |`bx`, `by` |`uint8_t` |Block coordinates.
|`bits` |`const uint8_t *` |8 bytes, one per glyph row, MSB leftmost. |`bits` |`const uint8_t *` |8 bytes, one per glyph row, MSB leftmost.
|`fgColor` |`uint8_t` |Set-bit colour. Masked to 4 bits. |`fgColor` |`uint8_t` |Set-bit color. Masked to 4 bits.
|`bgColor` |`uint8_t` |Clear-bit colour. Masked to 4 bits. |`bgColor` |`uint8_t` |Clear-bit color. Masked to 4 bits.
|=== |===
*Returns:* nothing. *Returns:* nothing.
@ -279,7 +279,7 @@ void jlDrawText(jlSurfaceT *dst, uint8_t bx, uint8_t by,
---- ----
Draws a NUL-terminated ASCII string using glyphs from a font surface. Glyph Draws a NUL-terminated ASCII string using glyphs from a font surface. Glyph
colour 0 is transparent, so the background shows through. The cursor wraps at color 0 is transparent, so the background shows through. The cursor wraps at
the right edge and truncates at the bottom. A start off the 40x25 grid uses up the right edge and truncates at the bottom. A start off the 40x25 grid uses up
characters without drawing them as the cursor moves on: a start column past characters without drawing them as the cursor moves on: a start column past
39 drops the first character and carries on at the start of the next row, and 39 drops the first character and carries on at the start of the next row, and
@ -404,7 +404,7 @@ Makes a layer over `map`. Nothing is drawn until `jlTileLayerDraw`.
|Parameter |Type |Description |Parameter |Type |Description
|`tiles` |`const jlTileT *` |The tileset. Kept, not copied. |`tiles` |`const jlTileT *` |The tileset. Kept, not copied.
|`map` |`uint8_t *` |`mapWidth * mapHeight` tileset indices, row-major, so |`map` |`uint8_t *` |`mapWidth * mapHeight` tileset indices, row-major, so
`map[row * mapWidth + col]`. `JL_TILE_MAP_SKIP` draws colour 0. Kept, not `map[row * mapWidth + col]`. `JL_TILE_MAP_SKIP` draws color 0. Kept, not
copied; `jlTileLayerSetTile` writes it. copied; `jlTileLayerSetTile` writes it.
|`mapWidth`, `mapHeight` |`uint16_t` |The map's size in tiles, 1 to |`mapWidth`, `mapHeight` |`uint16_t` |The map's size in tiles, 1 to
`JL_TILE_LAYER_MAX_SIDE`. `JL_TILE_LAYER_MAX_SIDE`.
@ -468,7 +468,7 @@ Sets map cell `(tx, ty)` to `tile`. The next draw shows it.
|Parameter |Type |Description |Parameter |Type |Description
|`layer` |`jlTileLayerT *` |The layer. |`layer` |`jlTileLayerT *` |The layer.
|`tx`, `ty` |`uint16_t` |The cell, in tiles. Out-of-range cells are ignored. |`tx`, `ty` |`uint16_t` |The cell, in tiles. Out-of-range cells are ignored.
|`tile` |`uint8_t` |The tileset index, or `JL_TILE_MAP_SKIP` for colour 0. |`tile` |`uint8_t` |The tileset index, or `JL_TILE_MAP_SKIP` for color 0.
|=== |===
*Returns:* nothing. *Returns:* nothing.

View file

@ -11,7 +11,7 @@
|`JL_SURFACE_BYTES_PER_ROW` |160 |Bytes per row, chunky 4bpp. |`JL_SURFACE_BYTES_PER_ROW` |160 |Bytes per row, chunky 4bpp.
|`JL_SURFACE_PIXELS_SIZE` |32000 |Total pixel bytes. |`JL_SURFACE_PIXELS_SIZE` |32000 |Total pixel bytes.
|`JL_SURFACE_PALETTE_COUNT` |16 |Palettes per surface. |`JL_SURFACE_PALETTE_COUNT` |16 |Palettes per surface.
|`JL_SURFACE_COLORS_PER_PALETTE` |16 |Colours per palette. |`JL_SURFACE_COLORS_PER_PALETTE` |16 |Colors per palette.
|`JL_SURFACE_PALETTE_ENTRIES` |256 |Total palette entries. |`JL_SURFACE_PALETTE_ENTRIES` |256 |Total palette entries.
|=== |===

View file

@ -73,7 +73,7 @@ run on Linux, and all but OpenMPT are native there.
animation frames. *GIMP* (GPLv3), to bring existing art down to an indexed animation frames. *GIMP* (GPLv3), to bring existing art down to an indexed
PNG. PNG.
|In GrafX2, set the palette's RGB scale to 16 (right-click the palette |In GrafX2, set the palette's RGB scale to 16 (right-click the palette
button for the secondary palette menu): every colour you pick is then one a button for the secondary palette menu): every color you pick is then one a
JoeyLib palette holds exactly, 4 bits a gun. The sheet rules are in JoeyLib palette holds exactly, 4 bits a gun. The sheet rules are in
<<_authoring_the_png>>. <<_authoring_the_png>>.
@ -117,14 +117,14 @@ build uses. Only Furnace has an installer component, because
Both tile and sprite sheets start as an *indexed* PNG with strict rules: Both tile and sprite sheets start as an *indexed* PNG with strict rules:
* Mode `P` (indexed / palette), *not* RGB or RGBA. * Mode `P` (indexed / palette), *not* RGB or RGBA.
* At most *16 colours*. * At most *16 colors*.
* Width and height both *multiples of 8*. * Width and height both *multiples of 8*.
* Palette entry 0 is the transparent / background colour. Remember the library * Palette entry 0 is the transparent / background color. Remember the library
forces it to black on screen. forces it to black on screen.
Any decent pixel editor can produce this. In GIMP: *Image > Mode > Indexed*, Any decent pixel editor can produce this. In GIMP: *Image > Mode > Indexed*,
"Generate optimum palette" with maximum 16 colours, and no dithering -- "Generate optimum palette" with maximum 16 colors, and no dithering --
dithering invents colours and will push you over the limit. dithering invents colors and will push you over the limit.
Lay the artwork out on a grid. A tile sheet is read as 8x8 cells row by row; a Lay the artwork out on a grid. A tile sheet is read as 8x8 cells row by row; a
sprite sheet is read as cells of whatever size you declare with `--cell`, also sprite sheet is read as cells of whatever size you declare with `--cell`, also
@ -148,7 +148,7 @@ loader refuses a file baked for a different target rather than drawing garbage.
==== Maps from Tiled ==== Maps from Tiled
`tools/tiled2joey` turns a map made in Tiled into what a JoeyLib program `tiled2joey` turns a map made in Tiled into what a JoeyLib program
pastes: a tile sheet, which `assetbake` bakes into a tile bank for each pastes: a tile sheet, which `assetbake` bakes into a tile bank for each
port, and a map for each tile layer, which `jlTileMapPaste` takes as it is: port, and a map for each tile layer, which `jlTileMapPaste` takes as it is:
@ -156,16 +156,16 @@ port, and a map for each tile layer, which `jlTileMapPaste` takes as it is:
---- ----
$ build/tools/tiled2joey --header build/level1.h level1.tmx \ $ build/tools/tiled2joey --header build/level1.h level1.tmx \
build/level1.png build/level1 build/level1.png build/level1
tiled2joey: 60x25 map, 87 tiles, 14 colours, layers: ground.map, detail.map tiled2joey: 60x25 map, 87 tiles, 14 colors, layers: ground.map, detail.map
$ build/tools/assetbake --type tile --target iigs \ $ build/tools/assetbake --type tile --target iigs \
build/level1.png build/iigs/bin/DATA/level1.tbk build/level1.png build/iigs/bin/DATA/level1.tbk
---- ----
* *The map* is Tiled's own TMX or its JSON (TMJ), orthogonal and finite, with * *The map* is Tiled's own TMX or its JSON (TMJ), orthogonal and finite, with
8x8 tiles. Its tilesets, in the map or in files of their own (TSX or TSJ), 8x8 tiles. Its tilesets, in the map or in files of their own (TSX or TSJ),
are single images: indexed PNGs whose colours make one palette of at most are single images: indexed PNGs whose colors make one palette of at most
16 between them. The first tileset's colours keep their indices, so its 16 between them. The first tileset's colors keep their indices, so its
colour 0 stays the background; another's are matched by colour or take a color 0 stays the background; another's are matched by color or take a
slot the first does not use. slot the first does not use.
* *The sheet* holds the tiles the map's layers use, in the order they first * *The sheet* holds the tiles the map's layers use, in the order they first
appear, so the bank holds nothing the map does not place. A tile drawn appear, so the bank holds nothing the map does not place. A tile drawn
@ -193,7 +193,7 @@ for (row = 0; row < 25u; row++) {
A level that scrolls is a tile layer over the whole map (see A level that scrolls is a tile layer over the whole map (see
<<_scrolling_tile_layers>>). A layer is opaque and draws `JL_TILE_MAP_SKIP` as <<_scrolling_tile_layers>>). A layer is opaque and draws `JL_TILE_MAP_SKIP` as
colour 0, so it scrolls one map layer; flatten the scenery into one layer in color 0, so it scrolls one map layer; flatten the scenery into one layer in
Tiled. Tiled.
[source,c] [source,c]
@ -217,7 +217,7 @@ because it holds chunky 4bpp tiles and each port converts a sprite to its own
form when the sprite is created. Bake once, ship everywhere. form when the sprite is created. Bake once, ship everywhere.
Both formats can embed the 16-entry `$0RGB` palette, and both loaders will hand Both formats can embed the 16-entry `$0RGB` palette, and both loaders will hand
it back through their `outPalette` parameter so artwork carries its own colours it back through their `outPalette` parameter so artwork carries its own colors
rather than depending on whatever the stage happens to have set. rather than depending on whatever the stage happens to have set.
==== Pre-Compiling Sprites ==== Pre-Compiling Sprites
@ -316,7 +316,7 @@ same time; `songbake` warns when they overlap.
A tracker is easier than a text file for anything longer than a jingle. Write A tracker is easier than a text file for anything longer than a jingle. Write
the song in Furnace, export it as VGM (a log of the chip's register writes as the song in Furnace, export it as VGM (a log of the chip's register writes as
the song plays), and `tools/vgm2song` turns that into a `.song` file, which the song plays), and `vgm2song` turns that into a `.song` file, which
is still plain text you can read and touch up: is still plain text you can read and touch up:
. Start the song on a system with a *TI SN76489* (the Sega Master System, BBC . Start the song on a system with a *TI SN76489* (the Sega Master System, BBC
@ -478,7 +478,7 @@ lists it as a prerequisite, so make builds it first.
`examples/tilebank` is a working reference: one rule in `make/examples.mk` `examples/tilebank` is a working reference: one rule in `make/examples.mk`
bakes the same PNG for each target into its `DATA/tilebank` (`TILEBANK_DATA`), bakes the same PNG for each target into its `DATA/tilebank` (`TILEBANK_DATA`),
and the program loads that bank both ways and shows the loader refusing the 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 other targets' bakes. Its `scripts/verify-<port>-tilebank.sh` tests run it on
every port. Space Taxi, a game built on JoeyLib in a repository of its own, every port. Space Taxi, a game built on JoeyLib in a repository of its own,
has the full-sized one in its `program.mk`: levels extracted from a C64 image, has the full-sized one in its `program.mk`: levels extracted from a C64 image,
a cel bank generated by a host tool of its own and pre-compiled per target, a cel bank generated by a host tool of its own and pre-compiled per target,
@ -513,7 +513,7 @@ and the streaming loader needs only a small internal scratch buffer.
The JoeyLib logo lives in `assets/` as both the original 320x200 PNG and a The JoeyLib logo lives in `assets/` as both the original 320x200 PNG and a
traced SVG. If the source art ever changes, regenerate the vector with traced SVG. If the source art ever changes, regenerate the vector with
`tools/traceLogo`, which needs the potrace program (the distribution's `traceLogo`, which needs the potrace program (the distribution's
`potrace` package, or set `POTRACE` to one installed elsewhere): `potrace` package, or set `POTRACE` to one installed elsewhere):
[source,console] [source,console]
@ -523,10 +523,10 @@ $ build/tools/traceLogo assets/joeylib_logo_-_320x200.png \
assets/joeylibLogo.svg assets/joeylibLogo.svg
---- ----
The art is four flat colours (ink, skin, the green shorts, and the beige The art is four flat colors (ink, skin, the green shorts, and the beige
monitor), so the default of four clusters is the trace that looks best: each monitor), so the default of four clusters is the trace that looks best: each
layer is filled with the exact colour most of its pixels have, and the layer is filled with the exact color most of its pixels have, and the
clustering is done in CIELAB, where an antialiased grey edge joins the ink or clustering is done in CIELAB, where an antialiased gray edge joins the ink or
the beige it lies between rather than the skin colour nearest it in RGB. A the beige it lies between rather than the skin color nearest it in RGB. A
fifth cluster and beyond only carve those grey edges into layers of their fifth cluster and beyond only carve those gray edges into layers of their
own, which render as speckled outlines rather than detail. own, which render as speckled outlines rather than detail.

View file

@ -85,7 +85,7 @@ with `MYGAME_IIGS_GAME_DISK` the disks `build/iigs/bin/MYGAME.2mg` and
|MS-DOS |`build/dos/bin/MYGAME.EXE`, with the DPMI server built in |MS-DOS |`build/dos/bin/MYGAME.EXE`, with the DPMI server built in
|Sharp X68000 |`build/x68000/bin/MYGAME.X` |Sharp X68000 |`build/x68000/bin/MYGAME.X`
|DVX |`build/dvx/bin/apps/acme/mygame/`: `mygame.app`, `mygame.hlp`, and its |DVX |`build/dvx/bin/apps/acme/mygame/`: `mygame.app`, `mygame.hlp`, and its
`DATA/` `DATA/`; and its package, `build/dvx/bin/packages/mygame.dpk`
|SDL3 |`build/sdl3/bin/MYGAME`, a native program with SDL 3 linked in |SDL3 |`build/sdl3/bin/MYGAME`, a native program with SDL 3 linked in
|Web |`build/web/bin/MYGAME/`: `MYGAME.html`, `.js`, `.wasm`, and `.data` |Web |`build/web/bin/MYGAME/`: `MYGAME.html`, `.js`, `.wasm`, and `.data`
(its `DATA/`, packed in), and `icon.png` (its `DATA/`, packed in), and `icon.png`
@ -142,8 +142,8 @@ otherwise. `PROGRAMS` lists the ids. A project may describe several programs.
|Variable |Meaning |Variable |Meaning
|`PROGRAMS` |The ids that `make` builds. |`PROGRAMS` |The ids that `make` builds.
|`TEST_PROGRAMS` |Ids with rules that are built only when asked for (test |`TEST_PROGRAMS` |Ids with rules that are built only when asked for (the
probes for gates). tests' probes).
|`ID_SRCS` |Its C sources, in link order. |`ID_SRCS` |Its C sources, in link order.
|`ID_CFLAGS` |Extra compiler flags: `-I` folders, `-D` defines. |`ID_CFLAGS` |Extra compiler flags: `-I` folders, `-D` defines.
|`ID_LDFLAGS` |Extra link flags (`-s` to strip). |`ID_LDFLAGS` |Extra link flags (`-s` to strip).
@ -154,12 +154,15 @@ ST, DOS, X68000, SDL3, Win32, Win16; on the IIgs, DVX, the web, macOS, and
the PSP every program is packaged apart anyway). the PSP every program is packaged apart anyway).
|`ID_BINDIR` |Build it there instead of `bin/`. |`ID_BINDIR` |Build it there instead of `bin/`.
|`ID_DATA` |Its data files (<<_data_files>>). |`ID_DATA` |Its data files (<<_data_files>>).
|`ID_TITLE` |DVX: its window title. PSP: its title in the PSP's menu. |`ID_TITLE` |DVX: its window title and its package's. PSP: its title in the PSP's menu.
macOS: its app's display name (`CFBundleDisplayName`). By default its name. macOS: its app's display name (`CFBundleDisplayName`). By default its name.
|`ID_ICON` |Its icon: a picture (a PNG, best square, transparent around the |`ID_ICON` |Its icon: a picture (a PNG, best square, transparent around the
figure), baked for every target whose system shows programs by an icon figure), baked for every target whose system shows programs by an icon
(<<_icons>>). JoeyLib's kangaroo by default. (<<_icons>>). JoeyLib's kangaroo by default.
|`ID_HELP` |DVX: the line its help file says about it. |`ID_HELP` |DVX: the line its help file says about it, and its package's
summary (at most 127 characters, and no apostrophes).
|`ID_VERSION` |Its version, `major.minor.patch` (its DVX package's). By
default the project's `PROGRAM_VERSION`, else `1.0.0`.
|`ID_IIGS_OVERFLOW_CAP` |IIgs: link large overflow segments (`0xF000`), which |`ID_IIGS_OVERFLOW_CAP` |IIgs: link large overflow segments (`0xF000`), which
a big program needs to leave the Memory Manager room (see a big program needs to leave the Memory Manager room (see
`make/support/iigs/clang-build.sh`). `make/support/iigs/clang-build.sh`).
@ -178,8 +181,10 @@ MYGAME_IIGS_DATA := # nothing staged on the IIgs
DVX's help and resource texts call a program "a `$(DVX_KIND)` program" DVX's help and resource texts call a program "a `$(DVX_KIND)` program"
(`JoeyLib` by default) and name `$(DVX_AUTHOR)` as its author; `DVX_VENDOR` (`JoeyLib` by default) and name `$(DVX_AUTHOR)` as its author; `DVX_VENDOR`
is the folder it installs in under `APPS\` (<<_a_project>>). Set them in the is the folder it installs in under `APPS\` (<<_a_project>>). Its DVX package
description. also takes `DVX_PUBLISHER`, `DVX_LICENSE`, `DVX_HOMEPAGE`, and `DVX_PKG_KEY`,
and each program's `ID_VERSION` (<<_disk_images_and_packaging>>). Set them in
the description.
=== Data Files === Data Files
@ -250,7 +255,7 @@ program's id:
|Apple IIgs |Uppercase, 8 characters |`MYGAME` |Apple IIgs |Uppercase, 8 characters |`MYGAME`
|Atari ST |Uppercase 8.3, `.PRG` |`MYGAME.PRG` |Atari ST |Uppercase 8.3, `.PRG` |`MYGAME.PRG`
|MS-DOS |Uppercase 8.3, `.EXE` |`MYGAME.EXE` |MS-DOS |Uppercase 8.3, `.EXE` |`MYGAME.EXE`
|Amiga |Mixed case, no extension: the id Capitalised unless `ID_AMIGA_NAME` |Amiga |Mixed case, no extension: the id Capitalized unless `ID_AMIGA_NAME`
says otherwise |`Mygame` says otherwise |`Mygame`
|Sharp X68000 |Uppercase 8.3, `.X` |`MYGAME.X` |Sharp X68000 |Uppercase 8.3, `.X` |`MYGAME.X`
|DVX |Lowercase, 8 characters, a folder of its own |`apps/acme/mygame/mygame.app` |DVX |Lowercase, 8 characters, a folder of its own |`apps/acme/mygame/mygame.app`
@ -296,10 +301,23 @@ needs to be on the disk.
*DVX.* Every program is a folder laid out as it installs in DVX: *DVX.* Every program is a folder laid out as it installs in DVX:
`build/dvx/bin/apps/acme/mygame/` holds `mygame.app`, its help file `build/dvx/bin/apps/acme/mygame/` holds `mygame.app`, its help file
`mygame.hlp` (DVX gives every program one; the rules write a one-topic help `mygame.hlp` (DVX gives every program one; the rules write a one-topic help
from `MYGAME_HELP`) and its `DATA/`. Copy that folder into a DVX installation's from `MYGAME_HELP`), and its `DATA/`. Copy that folder into a DVX installation's
`APPS\ACME\` and start it from DVX, or let `joey-run` do it `APPS\ACME\` and start it from DVX, or let `joey-run` do it
(<<_dvx_programs>>). (<<_dvx_programs>>).
It is also a package, `build/dvx/bin/packages/mygame.dpk`, which is how a DVX
program is handed out: anyone installs it with DVX's Software program (or
`SYSTEM\DVXPKG.EXE install`), and DVX checks it was built for its binary
interface. The package is `acme.mygame`; Software shows its title and
summary from `MYGAME_TITLE` and `MYGAME_HELP` (at most 127 characters), its
version from `MYGAME_VERSION` (else the project's `PROGRAM_VERSION`, else
1.0.0), and its publisher, license, and web site from the project's
`DVX_PUBLISHER` (`DVX_AUTHOR` by default), `DVX_LICENSE`, and
`DVX_HOMEPAGE`. Set `DVX_PKG_KEY` to a secret key file to sign every package,
so whoever installs one can tell it is yours: the SDK's tool makes the pair
(`toolchains/opt/dvx/sdk/bin/dvxpkg keygen "Acme Games" acme` writes
`acme.SEC`, which you keep, and `acme.KEY`, which you hand out).
*SDL3.* A program is one native executable with SDL 3 linked in statically, *SDL3.* A program is one native executable with SDL 3 linked in statically,
so the folder it is in, with its `DATA/`, is all it needs on a machine like so the folder it is in, with its `DATA/`, is all it needs on a machine like
the one that built it. the one that built it.
@ -334,10 +352,10 @@ stick; the PSP shows it by its icon and its `ID_TITLE`. The unstripped ELF stays
A program has an icon on every system that shows programs by one. `ID_ICON` A program has an icon on every system that shows programs by one. `ID_ICON`
names a picture, a PNG, best square and transparent around the figure, and names a picture, a PNG, best square and transparent around the figure, and
`make` bakes it for each target with `tools/iconBake`; a program without `make` bakes it for each target with `iconBake` (`build/tools/iconBake`); a program without
one gets JoeyLib's kangaroo, `assets/joeyIcon.png`. A picture of 64 pixels or one gets JoeyLib's kangaroo, `assets/joeyIcon.png`. A picture of 64 pixels or
less is taken for pixel art and enlarged by whole pixels; a bigger one is less is taken for pixel art and enlarged by whole pixels; a bigger one is
resampled smoothly. Where an icon has few colours, each pixel takes the resampled smoothly. Where an icon has few colors, each pixel takes the
nearest. nearest.
[cols="1,5", options="header"] [cols="1,5", options="header"]
@ -346,28 +364,28 @@ nearest.
|Apple IIgs |The Finder's: an icon file (type `$CA`) for each program, which |Apple IIgs |The Finder's: an icon file (type `$CA`) for each program, which
the disk scripts put in the volume's `Icons` folder as `NAME.ICONS`, where the the disk scripts put in the volume's `Icons` folder as `NAME.ICONS`, where the
Finder reads icon files when it starts. 24x20 and 12x10 colour icons, which Finder reads icon files when it starts. 24x20 and 12x10 color icons, which
the Finder shows as they are: in its 640 mode each icon pixel is two screen the Finder shows as they are: in its 640 mode each icon pixel is two screen
pixels, black, red, green, or white beside black, blue, yellow, or white, seen pixels, black, red, green, or white beside black, blue, yellow, or white, seen
together as one of 16 colours (an icon drawn at an odd screen column would together as one of 16 colors (an icon drawn at an odd screen column would
pair them with the other halves of the palette; the Finder drew every one at pair them with the other halves of the palette; the Finder drew every one at
an even column). A data disk takes the icons an even column). A data disk takes the icons
last, while there is room; a game disk keeps its program's at its root. A last, while there is room; a game disk keeps its program's at its root. A
program copied to another volume takes its icon file along, into that program copied to another volume takes its icon file along, into that
volume's `Icons` folder. volume's `Icons` folder.
|Amiga |Workbench's: `Name.info` beside the program, a tool's icon in |Amiga |Workbench's: `Name.info` beside the program, a tool's icon in
Workbench's four colours, 48x24 hires pixels, which starts it with Workbench's four colors, 48x24 hires pixels, which starts it with
`AMIGA_ICON_STACK` (32768) bytes of stack. Started from it, the program runs `AMIGA_ICON_STACK` (32768) bytes of stack. Started from it, the program runs
in its own folder, as it does from a shell. in its own folder, as it does from a shell.
|DVX |Program Manager's: the program's `icon32` resource, 32x32 on DVX's grey. |DVX |Program Manager's: the program's `icon32` resource, 32x32 on DVX's gray.
|Win16 |Program Manager's and the minimised window's: 32x32 in the 16 VGA |Win16 |Program Manager's and the minimized window's: 32x32 in the 16 VGA
colours, bound into the `.EXE`. colors, bound into the `.EXE`.
|Win32 |Explorer's and the window's: 16, 32, and 48 pixels, each in 256 |Win32 |Explorer's and the window's: 16, 32, and 48 pixels, each in 256
colours and in 32-bit, linked into the `.EXE`. colors and in 32-bit, linked into the `.EXE`.
|Haiku, BeOS |Tracker's and the Deskbar's: 32x32 and 16x16 in the BeOS |Haiku, BeOS |Tracker's and the Deskbar's: 32x32 and 16x16 in the BeOS
palette, in the program's resources, and on Haiku the same two as one vector palette, in the program's resources, and on Haiku the same two as one vector
icon, which draws each at its own size and the 32x32 cleanly at any larger icon, which draws each at its own size and the 32x32 cleanly at any larger
one (an icon with too many colour patches for the vector format goes one (an icon with too many color patches for the vector format goes
without). Tracker shows a file by the icons in its without). Tracker shows a file by the icons in its
attributes, which a program built on another machine does not have; the attributes, which a program built on another machine does not have; the
program copies them across the first time it runs, so Tracker shows it by its program copies them across the first time it runs, so Tracker shows it by its
@ -414,16 +432,18 @@ the JoeyLib checkout, everything lands in its `build/`:
[source,console] [source,console]
---- ----
$ make # the library and the examples, every installed target $ make # the library and the examples, every installed target
$ make iigs-disk # the IIgs examples on build/iigs/bin/joey.2mg $ make iigs-disk # the first IIgs examples that fit on build/iigs/bin/joey.2mg
$ make iigs-game-disks # every IIgs example on a bootable disk of its own
$ make -f make/iigs.mk iigs-game-disk GAME=DRAW $ make -f make/iigs.mk iigs-game-disk GAME=DRAW
---- ----
`make iigs` builds only the IIgs library; `make iigs-disk` builds the examples `make iigs` builds only the IIgs library; `make iigs-disk` and
and packs them. Space Taxi and JoeyAGI, two complete games, are projects of `make iigs-game-disks` build the examples and pack them. Space Taxi and
JoeyAGI, two complete games, are projects of
their own built exactly as this chapter describes: each repository's Makefile their own built exactly as this chapter describes: each repository's Makefile
includes its `program.mk` description between JoeyLib's two makefiles. includes its `program.mk` description between JoeyLib's two makefiles.
=== The Gates === The Tests
The repository carries automated checks. They exist because several classes of The repository carries automated checks. They exist because several classes of
bug on these machines are invisible until measured, and they are worth running bug on these machines are invisible until measured, and they are worth running
@ -438,7 +458,7 @@ Human68k floppies, environment checks) takes paths.
|Command |What it proves |Command |What it proves
|`make verify-audio` |`make verify-audio`
|Every port's audio gate: the IIgs DOC and X68000 chips are read directly, |Every port's audio test: the IIgs DOC and X68000 chips are read directly,
the ST's YM2149 register writes are traced (and for the STe build the the ST's YM2149 register writes are traced (and for the STe build the
samples the DMA sound chip plays), the Amiga, DOS, and DVX output samples the DMA sound chip plays), the Amiga, DOS, and DVX output
is recorded and graded, SDL3's mix is written to a file by SDL's disk is recorded and graded, SDL3's mix is written to a file by SDL's disk
@ -446,7 +466,7 @@ Human68k floppies, environment checks) takes paths.
Chromium and graded, as is the Win32 program's under Wine, the Win16 Chromium and graded, as is the Win32 program's under Wine, the Win16
program's on Windows 3.11 in DOSBox, and what reaches the PSP's audio output program's on Windows 3.11 in DOSBox, and what reaches the PSP's audio output
under PPSSPP. Exits 3, not 0, under PPSSPP. Exits 3, not 0,
if a gate had to be skipped. if a test had to be skipped.
|`make iigs-verify` |`make iigs-verify`
|The IIgs actually reached the program and drew something. |The IIgs actually reached the program and drew something.
@ -464,7 +484,7 @@ if a gate had to be skipped.
|Every program carries its icon where its system looks |Every program carries its icon where its system looks
(`scripts/check-icons.sh`, no emulator), and each system shows it: the IIgs (`scripts/check-icons.sh`, no emulator), and each system shows it: the IIgs
Finder's window, the Amiga's icon.library (and a Workbench start), DVX's Finder's window, the Amiga's icon.library (and a Workbench start), DVX's
Program Manager, Windows 3.11's desktop with the program minimised, the Program Manager, Windows 3.11's desktop with the program minimized, the
Win32 and SDL3 windows' icons on X, and Tracker on Haiku and BeOS. Win32 and SDL3 windows' icons on X, and Tracker on Haiku and BeOS.
|`scripts/bench-<port>.sh` |`scripts/bench-<port>.sh`
@ -484,7 +504,7 @@ different pixels from the others.
Debugging a program running on an emulated 1988 machine is awkward. JoeyLib Debugging a program running on an emulated 1988 machine is awkward. JoeyLib
provides a logger that writes to a file next to the executable (on DVX, in provides a logger that writes to a file next to the executable (on DVX, in
the program's settings folder): the program's user folder):
[source,c] [source,c]
---- ----

View file

@ -74,7 +74,7 @@ repository.
| `list` | `list`
| Every component, the groups it belongs to, and whether it is current, | Every component, the groups it belongs to, and whether it is current,
outdated or missing. outdated, or missing.
| `install TARGETS` | `install TARGETS`
| Installs or updates the targets and everything they depend on, and | Installs or updates the targets and everything they depend on, and
@ -118,7 +118,7 @@ programs), `win32` (llvm-mingw; its emulator is a portable Wine), `win16`
(Open Watcom; its emulator is Windows for Workgroups 3.11 in DOSBox Staging, (Open Watcom; its emulator is Windows for Workgroups 3.11 in DOSBox Staging,
set up from your own install disc, see <<Firmware that is not downloaded>>), set up from your own install disc, see <<Firmware that is not downloaded>>),
or `psp` (pspdev's prebuilt release, and SDL 3 built with it; its emulator or `psp` (pspdev's prebuilt release, and SDL 3 built with it; its emulator
is PPSSPP's headless build, patched for the gates). is PPSSPP's headless build, patched for the tests).
| `emulator:PORT` | `emulator:PORT`
| Everything needed to run that port's programs: the emulator and its | Everything needed to run that port's programs: the emulator and its
@ -231,14 +231,16 @@ produces a bootable disk image:
[source,console] [source,console]
---- ----
$ make iigs-disk # build/iigs/bin/joey.2mg $ make iigs-disk # build/iigs/bin/joey.2mg: the first examples that fit
$ make iigs-game-disks # build/iigs/bin/NAME.2mg: every example, each bootable
---- ----
[TIP] [TIP]
==== ====
Never judge a retro build by its exit status alone. Check that the file you Never judge a retro build by its exit status alone. Check that the file you
expect actually appeared and has a recent timestamp. `make iigs` builds the expect actually appeared and has a recent timestamp. `make iigs` builds the
library but *no examples* -- `make iigs-disk` is what builds and packages them. library but *no examples* -- `make iigs-disk` and `make iigs-game-disks` are
what build and package them.
==== ====
=== Hello, JoeyLib === Hello, JoeyLib
@ -348,9 +350,9 @@ $ ./install.sh install emulator:dos # or just one
---- ----
MAME (for the IIgs and X68000) is always built from source, because the test MAME (for the IIgs and X68000) is always built from source, because the test
gates need JoeyLib's patches to it. DOSBox Staging is upstream's own Linux tests need JoeyLib's patches to it. DOSBox Staging is upstream's own Linux
release. Hatari and FS-UAE are distribution packages on Debian and Ubuntu; release. Hatari and FS-UAE are distribution packages on Debian and Ubuntu;
Fedora does not ship the versions the gates expect, so there the installer Fedora does not ship the versions the tests expect, so there the installer
builds both from source. builds both from source.
Point `joey-run` at a program, or name an example together with `--port`: Point `joey-run` at a program, or name an example together with `--port`:
@ -371,7 +373,7 @@ $ ./joey-run --port haiku draw
$ ./joey-run --port beos draw $ ./joey-run --port beos draw
---- ----
It recognises the machine from the file itself. Interactive runs use the It recognizes the machine from the file itself. Interactive runs use the
program's own folder or disk image, so saves and logs persist from one run to program's own folder or disk image, so saves and logs persist from one run to
the next. Nothing is written to your home directory. the next. Nothing is written to your home directory.
@ -405,7 +407,7 @@ ST-build program (`make atarist`) gets a plain ST and an STe-build one
(`make ste`) an STe; the STe build runs only on `ste` and `megaste`. (`make ste`) an STe; the STe build runs only on `ste` and `megaste`.
| `--machine a1200` | `--machine a1200`
| Amiga: FS-UAE's machine, `a500` (the default), `a1200` (68EC020, AGA) or | Amiga: FS-UAE's machine, `a500` (the default), `a1200` (68EC020, AGA), or
`a4000` (68040, AGA), each on its own Kickstart 3.1. The AGA ones need their `a4000` (68040, AGA), each on its own Kickstart 3.1. The AGA ones need their
Kickstarts (`./install.sh install emulator:aga`). Kickstarts (`./install.sh install emulator:aga`).
@ -439,8 +441,8 @@ installer took from a DVX tree (`./install.sh install dvx`), set up so DVX
boots straight into the program: it is DVX's desktop program, in its own boots straight into the program: it is DVX's desktop program, in its own
window on a 1024x768 desktop (640x480 with `--headless`), with a Sound window on a 1024x768 desktop (640x480 with `--headless`), with a Sound
Blaster 16. The DVX of interactive runs is kept in `build/dvx/run`, so what Blaster 16. The DVX of interactive runs is kept in `build/dvx/run`, so what
the program saves, in DVX's `CONFIG` folder, is there the next time; closing the program saves, in its folder in DVX's `USER` folder, is there the next
DOSBox ends the run. When the program ends, DVX stays up with an empty time; closing DOSBox ends the run. When the program ends, DVX stays up with an empty
desktop. desktop.
==== SDL3 ==== SDL3
@ -449,8 +451,9 @@ An SDL3 program is a native program, so `joey-run` simply starts it, in a
window; running the file yourself does the same. `--headless` runs it on a window; running the file yourself does the same. `--headless` runs it on a
private virtual display at one window pixel per stage pixel, so a private virtual display at one window pixel per stage pixel, so a
`--snapshot` is exactly the 320x200 stage. The window's size, shape, and full `--snapshot` is exactly the 320x200 stage. The window's size, shape, and full
screen are set by environment variables (<<_sdl3_programs>>). The SDL3 port screen are set by environment variables (<<_sdl3_programs>>). `joey-run`
has no serial port. connects no serial port to it: the program opens the computer's own
(<<_serial_ports>>).
==== Web ==== Web
@ -480,7 +483,7 @@ pixel, so a `--snapshot` is the stage.
A Windows 3.x program runs on Windows for Workgroups 3.11 in DOSBox Staging, A Windows 3.x program runs on Windows for Workgroups 3.11 in DOSBox Staging,
on a fresh copy, each run, of the Windows the installer set up from your own on a fresh copy, each run, of the Windows the installer set up from your own
disc (`./install.sh install emulator:win16`): 800x600 in 256 colours through disc (`./install.sh install emulator:win16`): 800x600 in 256 colors through
the S3 Trio64V+ driver, with Windows' Sound Blaster driver and two drivers the S3 Trio64V+ driver, with Windows' Sound Blaster driver and two drivers
of JoeyLib's own: KPJOY.DRV, the joystick driver Windows 3.1 lacks (DOSBox's of JoeyLib's own: KPJOY.DRV, the joystick driver Windows 3.1 lacks (DOSBox's
game port, two sticks), and KPCOMM.DRV in place of Windows' COMM.DRV (COM1 game port, two sticks), and KPCOMM.DRV in place of Windows' COMM.DRV (COM1
@ -517,7 +520,7 @@ and powers off when the program ends; what the program wrote in its folder
(saves, its log) is then copied back. The window's size, shape, and full (saves, its log) is then copied back. The window's size, shape, and full
screen are set by the same environment variables as SDL3's. Haiku's VM has screen are set by the same environment variables as SDL3's. Haiku's VM has
sound; BeOS's has none, and is taken through R5's boot loader menu to its sound; BeOS's has none, and is taken through R5's boot loader menu to its
fail-safe video mode (1024x768, 16-bit colour), as R5 has no driver for any fail-safe video mode (1024x768, 16-bit color), as R5 has no driver for any
of QEMU's video cards. With `--headless` it runs without a window at one of QEMU's video cards. With `--headless` it runs without a window at one
window pixel per stage pixel; `--seconds` counts from when the program window pixel per stage pixel; `--seconds` counts from when the program
starts, after the boot, and a `--snapshot` is QEMU's picture of the whole starts, after the boot, and a `--snapshot` is QEMU's picture of the whole
@ -534,9 +537,9 @@ $ ./joey-run --headless --seconds 30 --snapshot draw.png --port dos draw
==== The Apple IIgs ==== The Apple IIgs
The IIgs runs programs from disk images. `make -f make/iigs.mk iigs-game-disk The IIgs runs programs from disk images. `make iigs-game-disks` builds a
GAME=DRAW` builds a bootable one, `build/iigs/bin/DRAW.2mg`, which is also how bootable one for every example, such as `build/iigs/bin/DRAW.2mg`, which is
a game ships. Given a data disk instead, such as `joey.2mg`, `joey-run` boots also how a game ships. Given a data disk instead, such as `joey.2mg`, `joey-run` boots
GS/OS beside it and you open the program in the Finder. Given a bare program, 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.
@ -599,9 +602,16 @@ arrives. To connect out, `socat` joins a pseudo-terminal to the network.
| As on the ST: Wine's COM1 can only be a device, so `socat` joins a | As on the ST: Wine's COM1 can only be a device, so `socat` joins a
pseudo-terminal to the network both ways. pseudo-terminal to the network both ways.
| SDL3, Web, PSP, Haiku, BeOS | SDL3, macOS, Haiku, BeOS
| The computer's own
| Not connected by `joey-run`: these programs run on a real computer (or
Haiku's and BeOS's VMs), and open its serial device themselves, a USB
adapter included. See <<_sdl3_programs>>, <<_macos_programs>>, and
<<_haiku_and_beos_programs>> for which device and how to choose another.
| Web, PSP
| None | None
| These ports have no serial port; `jlSerialOpen` fails there. | These have no serial port; `jlSerialOpen` fails there.
|=== |===
`socat` comes from your distribution: `./install.sh deps host --install` `socat` comes from your distribution: `./install.sh deps host --install`
@ -652,7 +662,7 @@ $ ./install.sh import ~/my-roms
(point it at your Workbench 3.1 disk for `serial.device` and (point it at your Workbench 3.1 disk for `serial.device` and
`lowlevel.library`, and at the S3 `lowlevel.library`, and at the S3
driver's `.zip` for its three files), identifies each file by its checksum driver's `.zip` for its three files), identifies each file by its checksum
(the list is `installer/files.sha1`) and puts it in place. (the list is `installer/files.sha1`), and puts it in place.
A project that hands its setup to testers can host these files instead: A project that hands its setup to testers can host these files instead:
`./install.sh bundle joeylib-private.tar.gz` packs the ones present on `./install.sh bundle joeylib-private.tar.gz` packs the ones present on
@ -662,25 +672,30 @@ in `installer/bundle.conf`, makes every `install` fetch it automatically.
=== The Example Programs === The Example Programs
The `examples/` directory is the best available reference, and every example is The `examples/` directory is the best available reference, and every example is
built for every platform: built for, and runs on, every platform. JoeyLib draws no text, so the examples
that show words share a small 5x7 font, `examples/common/exampleFont.c`:
[cols="1,4", options="header"] [cols="1,4", options="header"]
|=== |===
|Example |What it shows |Example |What it shows
|`hello` |Minimal init, version query, shutdown. |`hello` |The smallest program: its greeting, version, and machine on the screen in the default palette, and a key to end it.
|`draw` |Every drawing primitive, laid out in four quadrants. |`draw` |Every drawing primitive, laid out in four quadrants.
|`pattern` |Palette and per-scanline palette (SCB) work. |`pattern` |Palette and per-scanline palette (SCB) work.
|`keys` |Typed-character input and the key predicates. |`keys` |Typed-character input, the key predicates, and the mouse.
|`joy` |Joystick axes, buttons, and recalibration. |`joy` |Joystick axes, buttons, and recalibration.
|`sprite` |Sprite loading, compiling, save-under, and restore. |`sprite` |Sprite loading, compiling, save-under, and restore.
|`scroll` |A scrolling tile layer under a sprite, with a frame-rate bar. |`scroll` |A scrolling tile layer under a sprite, with a frame-rate bar.
|`audio` |Tracker music, `.MOD` playback, digital sound effects, a streamed sound, and the noise generator. |`audio` |Tracker music, `.MOD` playback, digital sound effects, a streamed sound, and the noise generator.
|`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. |`tilebank` |Loading a baked tile bank both ways, and a bank for another machine refused.
|`serial` |The optional RS-232 add-on. |`serial` |A terminal on the optional RS-232 add-on.
|`adventure` |A small complete game. |`sertest` |A serial self-test: a banner, then an echo of every byte received.
|`adventure` |A Sierra-style adventure demo: two rooms, a verb cursor, hotspots, an inventory, walk-mask pathfinding, and walking behind things.
|`adventure2` |One room drawn the way Sierra's SCI engine does, with visual, priority, and control screens.
|`uber` |The validation and benchmark harness. |`uber` |The validation and benchmark harness.
|`scb` |The per-scanline palette under the heaviest load: all 16 palettes, changing as often as every line.
|`sermouse` |The serial port and the mouse together: a stream both ways while the mouse moves (the X68000 runs both on one chip).
|=== |===
Two complete games built on JoeyLib live in repositories of their own and Two complete games built on JoeyLib live in repositories of their own and
@ -690,7 +705,8 @@ from its own directory against a JoeyLib checkout, as any program of yours
would (<<_building_your_program>>). would (<<_building_your_program>>).
On DVX every example is a folder of its own under `build/dvx/bin/apps/kpunch/` On DVX every example is a folder of its own under `build/dvx/bin/apps/kpunch/`
with its help file (<<_dvx_programs>>). On SDL3 every example is a native with its help file, and a package in `build/dvx/bin/packages/`
(<<_dvx_programs>>). On SDL3 every example is a native
program in `build/sdl3/bin/`, beside the shared `DATA/` (<<_sdl3_programs>>), program in `build/sdl3/bin/`, beside the shared `DATA/` (<<_sdl3_programs>>),
on the web a page in a folder of its own, `build/web/bin/NAME/` on the web a page in a folder of its own, `build/web/bin/NAME/`
(<<_web_programs>>), on macOS an app, `build/macos/bin/NAME.app`, with its (<<_web_programs>>), on macOS an app, `build/macos/bin/NAME.app`, with its

View file

@ -40,7 +40,7 @@ and runs on all of them:
|Sharp X68000 |Sharp X68000
|68000 at 10 MHz |68000 at 10 MHz
|stock |stock
|Draws into the text plane, 320x200 centred in a 512x512 screen. |Draws into the text plane, 320x200 centered in a 512x512 screen.
|DVX |DVX
|486 or later |486 or later
@ -74,7 +74,7 @@ and runs on all of them:
|what Windows 3.1 in 386 enhanced mode needs |what Windows 3.1 in 386 enhanced mode needs
|A Windows 3.x program: the hosted core with a backend written to the |A Windows 3.x program: the hosted core with a backend written to the
Windows 3.1 API, in 32-bit code through Open Watcom's Win386 extender; 8-bit Windows 3.1 API, in 32-bit code through Open Watcom's Win386 extender; 8-bit
indexed, so every palette is exact on a 256-colour display, and sprites indexed, so every palette is exact on a 256-color display, and sprites
compiled as on DOS. See <<_windows_programs>>. compiled as on DOS. See <<_windows_programs>>.
|PSP |PSP
@ -82,8 +82,9 @@ and runs on all of them:
|32 MB (PSP-1000) |32 MB (PSP-1000)
|PSP homebrew, for custom firmware and PPSSPP: the SDL3 port built with |PSP homebrew, for custom firmware and PPSSPP: the SDL3 port built with
pspdev. The stage sits at 1:1 in the middle of the 480x272 screen; the pspdev. The stage sits at 1:1 in the middle of the 480x272 screen; the
d-pad, nub, and buttons are joystick 0; Start is Return, Select is Escape, d-pad, cross, and circle are joystick 0; the analog nub is the mouse, with
and Square opens a keyboard on the screen for every other key. See L and R its buttons; Start is Return, Select is Escape, and Square opens a
keyboard on the screen for every other key. See
<<_psp_programs>>. <<_psp_programs>>.
|=== |===
@ -95,19 +96,25 @@ something performs acceptably there, it performs acceptably everywhere.
Every JoeyLib program gets exactly the same screen on every platform: Every JoeyLib program gets exactly the same screen on every platform:
* *320 x 200 pixels*, always. There is no other resolution. * *320 x 200 pixels*, always. There is no other resolution.
* *16 colours on screen at once*, chosen per scanline from one of 16 palettes. * *16 colors per scanline*: each line shows one of 16 palettes of 16 colors,
* *Colour 0 of every palette is black*, always, and is forced to black by the so up to 241 colors are on the screen at once (256, less the 15 extra
blacks, since color 0 is black in every palette).
* *Color 0 of every palette is black*, always, and is forced to black by the
library even if you try to set it to something else. This is not negotiable: library even if you try to set it to something else. This is not negotiable:
the Amiga and ST hardware border follows colour 0 of the active palette, and the Amiga and ST hardware border follows color 0 of the active palette, and
colour 0 is the transparent colour for sprite drawing. You have 15 usable color 0 is the transparent color for sprite drawing. You have 15 usable
colours per palette. colors per palette.
* Colours are *12-bit `$0RGB`*: bits 11..8 red, 7..4 green, 3..0 blue. * Colors are *12-bit `$0RGB`*: bits 11..8 red, 7..4 green, 3..0 blue.
* Origin `(0, 0)` is *top-left*. X increases right, Y increases down. * Origin `(0, 0)` is *top-left*. X increases right, Y increases down.
Coordinates are signed `int16_t`, so off-screen positions are representable. Coordinates are signed `int16_t`, so off-screen positions are representable.
Every drawing primitive clips to the surface rectangle. An out-of-bounds draw Every drawing primitive clips to the surface rectangle. An out-of-bounds draw
is a silent no-op, never an error. There are no "fast unclipped" variants in is a silent no-op, never an error, so you cannot corrupt memory by drawing off
the public API, so you cannot corrupt memory by drawing off the edge. the edge. The exceptions are the sprite calls whose names end in `Trusted`
(`jlSpriteDrawTrusted`, `jlSpriteSaveUnderTrusted`,
`jlSpriteRestoreUnderTrusted`, and `jlSpriteSaveAndDrawTrusted`): for speed
they skip the checks, so the sprite must lie wholly on the surface, and one
that does not corrupts memory (<<_the_trusted_variants>>).
=== What the Library Hides === What the Library Hides
@ -132,7 +139,7 @@ that out of your program:
copied to is the text plane, four planes 128 KB apart. copied to is the text plane, four planes 128 KB apart.
|=== |===
Every public entry point speaks in *colour indices, 0 to 15*. Your code never Every public entry point speaks in *color indices, 0 to 15*. Your code never
sees the storage format. The one place the difference leaks through is sees the storage format. The one place the difference leaks through is
documented and flagged in this manual -- see <<_writing_portable_code>>. documented and flagged in this manual -- see <<_writing_portable_code>>.
@ -141,10 +148,10 @@ documented and flagged in this manual -- see <<_writing_portable_code>>.
* Surfaces, palettes, and per-scanline palette selection * Surfaces, palettes, and per-scanline palette selection
* Drawing primitives: pixels, lines, rectangles, circles, flood fill * Drawing primitives: pixels, lines, rectangles, circles, flood fill
* 8x8 tiles, tile banks, tile maps, and bitmap text * 8x8 tiles, tile banks, tile maps, and bitmap text
* Sprites with colour-0 transparency and *runtime-compiled blitters* -- the * Sprites with color-0 transparency and *runtime-compiled blitters* -- the
library emits native 65816, 68000, or x86 code for each sprite at run time library emits native 65816, 68000, or x86 code for each sprite at run time
(on the hosted ports other than Win16 every sprite is interpreted) (on the hosted ports other than Win16 every sprite is interpreted)
* Keyboard (both game-style predicates and a typed-character queue), mouse * Keyboard (both game-style predicates and a typed-character queue), mouse,
and joystick and joystick
* Four-channel Protracker `.MOD` music, a portable chip-tracker music format, * Four-channel Protracker `.MOD` music, a portable chip-tracker music format,
digital one-shot and streaming sound effects, and a PSG-style tone and digital one-shot and streaming sound effects, and a PSG-style tone and
@ -180,5 +187,5 @@ library can load.
directory of its own, compiling for every target. directory of its own, compiling for every target.
The API Reference documents every public function: its exact signature, each The API Reference documents every public function: its exact signature, each
parameter, what it returns, and any per-platform behaviour you need to know parameter, what it returns, and any per-platform behavior you need to know
about. about.

View file

@ -9,10 +9,10 @@ usually that target.
. Use `jlAlloc` for anything large, never `malloc`. . Use `jlAlloc` for anything large, never `malloc`.
. Never assume a frame rate. Ask `jlFrameHz()` or use `jlMillisElapsed()`. . Never assume a frame rate. Ask `jlFrameHz()` or use `jlMillisElapsed()`.
. Never assume colour 0 is anything but black. . Never assume color 0 is anything but black.
. Compile your sprites before you draw them. . Compile your sprites before you draw them.
. Keep large objects out of function-local variables on the IIgs. . Keep large objects out of function-local variables on the IIgs.
. Use `jlRandom`, not `rand`, when identical behaviour across ports matters. . Use `jlRandom`, not `rand`, when identical behavior across ports matters.
. Keep file names short, uppercase-safe, and 8.3-compatible. . Keep file names short, uppercase-safe, and 8.3-compatible.
. Do not mix save-under with clean-buffer erasing in the same screen region. . Do not mix save-under with clean-buffer erasing in the same screen region.
. Pick one of the audio layers per scene, not two. . Pick one of the audio layers per scene, not two.
@ -81,14 +81,14 @@ Scale by `jlFrameHz()` or work from `jlMillisElapsed()`.
`jlFrameCount()` is a 16-bit counter that wraps roughly every 18 minutes at `jlFrameCount()` is a 16-bit counter that wraps roughly every 18 minutes at
60 Hz, so compare two readings by subtracting them. 60 Hz, so compare two readings by subtracting them.
=== Colour and the Palette === Color and the Palette
Colour 0 is black in every palette on every platform, and `jlPaletteSet` Color 0 is black in every palette on every platform, and `jlPaletteSet`
silently rewrites entry 0 to `$000` no matter what you pass. Do not design silently rewrites entry 0 to `$000` no matter what you pass. Do not design
artwork that needs colour 0 to be something else -- you have 15 free colours per artwork that needs color 0 to be something else -- you have 15 free colors per
palette, and 16 palettes to switch between per scanline. palette, and 16 palettes to switch between per scanline.
Colour 0 is also the transparent colour for sprites. A sprite pixel of colour 0 Color 0 is also the transparent color for sprites. A sprite pixel of color 0
is never drawn. If you need an opaque rectangle, use the tile operations, which is never drawn. If you need an opaque rectangle, use the tile operations, which
have both opaque and masked forms. have both opaque and masked forms.
@ -100,19 +100,11 @@ Two rules.
uncompiled sprite runs through the interpreter, two to six times slower uncompiled sprite runs through the interpreter, two to six times slower
depending on the machine, and nothing warns you. depending on the machine, and nothing warns you.
*Align your backup buffers.* Give any `jlSpriteBackupT` backing array 2-byte *Backup buffers are `uint16_t` arrays.* The buffer of a `jlSpriteBackupT` is
alignment, by declaring it `uint16_t` or with an alignment attribute. On the declared `uint16_t gBacking[jlSpriteBackupLength(w, h)]`, which gives it the
Amiga, ST, and X68000 an odd buffer address sends save and restore down a much 2-byte alignment save and restore need: on the Amiga, ST, and X68000 an odd
slower path, and it crashes the trusted variants. address sends them down a much slower path, and it crashes the trusted
variants.
[source,c]
----
// Right: 2-byte aligned by construction.
static uint16_t gBacking[jlSpriteBackupLength(uint16_t, 4, 4)];
// Risky: may land on an odd address.
static uint8_t gBacking[jlSpriteBackupLength(uint8_t, 4, 4)];
----
Note also that the region a save-under actually captures is often not the Note also that the region a save-under actually captures is often not the
sprite rectangle: it can be the *16-pixel-aligned region* containing the sprite rectangle: it can be the *16-pixel-aligned region* containing the
@ -217,7 +209,7 @@ modules and sound effects become silent no-ops (the tone and noise voices
still play on the Amiga, the ST, DOS, and the X68000), so a program need not still play on the Amiga, the ST, DOS, and the X68000), so a program need not
treat missing sound as an error. treat missing sound as an error.
=== Per-Platform Behaviour Summary === Per-Platform Behavior Summary
The API is uniform; these are the places where what you get differs. The API is uniform; these are the places where what you get differs.
@ -257,7 +249,7 @@ The API is uniform; these are the places where what you get differs.
|Stereo pan |Stereo pan
|all three |all three
|all three (centred on a free channel each side) |all three (centered on a free channel each side)
|ignored (mono); all three in the STe build |ignored (mono); all three in the STe build
|all three on an SB16; ignored (mono) before it |all three on an SB16; ignored (mono) before it
|ignored |ignored
@ -357,8 +349,8 @@ is first pressed; a CD32 pad has both from the start.
On DOS the game port's counts depend on the stick and on the CPU's speed, so a On DOS the game port's counts depend on the stick and on the CPU's speed, so a
stick's first reading -- after `jlInit`, after `jlJoystickReset`, or when it is stick's first reading -- after `jlInit`, after `jlJoystickReset`, or when it is
plugged in -- is taken as its centre, and its axes are read in proportion to plugged in -- is taken as its center, and its axes are read in proportion to
that: a count of 0 or twice the centre is a full push. that: a count of 0 or twice the center is a full push.
The X68000's display refreshes at 55.46 Hz. `jlFrameHz` times it when the The X68000's display refreshes at 55.46 Hz. `jlFrameHz` times it when the
program starts and rounds to the nearest whole number, so it reports 55 -- program starts and rounds to the nearest whole number, so it reports 55 --
@ -367,11 +359,15 @@ and 56 under MAME, whose X68000 display runs slightly fast (56.07 Hz).
timer to the millisecond. timer to the millisecond.
Per-scanline palettes cost CPU time on the ST and the X68000, whose displays Per-scanline palettes cost CPU time on the ST and the X68000, whose displays
have one 16-colour palette. Both reload it as the beam scans, and store each have one 16-color palette. Both reload it as the beam scans, and store each
band's 16 colours in the horizontal blanking before the band's first line, so band's 16 colors in the horizontal blanking before the band's first line, so
the line shows them from its first pixel. The ST takes a timer interrupt a the line shows them from its first pixel. The ST takes a timer interrupt a
line above every change of palette, every frame, and waits in it for that line above a change of palette, every frame, and waits in it for that
line's display to end: about one line of CPU time (64 microseconds) a change. line's display to end; changes on the lines after it, one a line, it stores
before it returns. That is about one line of CPU time (64 microseconds) a
change, and a little more where a change is not on the line after another.
A change on line 1 is timed from the vertical blank instead, and costs about
three lines.
A screen with one palette costs nothing on either and arms no interrupt. On A screen with one palette costs nothing on either and arms no interrupt. On
the X68000 each change of palette between one the X68000 each change of palette between one
line and the next costs about one line of CPU time (32 microseconds); a band line and the next costs about one line of CPU time (32 microseconds); a band
@ -383,11 +379,11 @@ does the same for palettes one line apart, and spends those lines in the
interrupt. On the Amiga the copper changes the palettes at no CPU cost: a interrupt. On the Amiga the copper changes the palettes at no CPU cost: a
present that changes palettes or the SCB writes a list of the bands' copper present that changes palettes or the SCB writes a list of the bands' copper
instructions, a few hundred bytes. On the Amiga, the ST, DOS, and the X68000 instructions, a few hundred bytes. On the Amiga, the ST, DOS, and the X68000
a present's colours and pixels show from the same frame: the Amiga's VBL a present's colors and pixels show from the same frame: the Amiga's VBL
switches the copper to the new list with the page it flips to, the ST and switches the copper to the new list with the page it flips to, the ST and
the X68000 hand their interrupts the new colours with the page they flip to, the X68000 hand their interrupts the new colors with the page they flip to,
and DOS loads changed colours in the vertical blank that shows the new page. and DOS loads changed colors in the vertical blank that shows the new page.
A present that changes colours right after another, before the vertical A present that changes colors right after another, before the vertical
blank that shows the first, waits for it on the Amiga and DOS (and on DOS blank that shows the first, waits for it on the Amiga and DOS (and on DOS
the `jlWaitVBL` after it returns at once). the `jlWaitVBL` after it returns at once).
@ -412,24 +408,24 @@ names the ports, says how they differ, and gives the reason.
* *X68000: a small, wide picture.* The machine has no 320x200 mode, so the stage * *X68000: a small, wide picture.* The machine has no 320x200 mode, so the stage
sits in the middle of a 512x512 screen, a picture about 2.1 times as wide as sits in the middle of a 512x512 screen, a picture about 2.1 times as wide as
it is tall. That screen is a 31 kHz mode, so the monitor must take 31 kHz. it is tall. That screen is a 31 kHz mode, so the monitor must take 31 kHz.
* *ST: eight levels a channel.* The plain ST's video chip has 3 bits per colour * *ST: eight levels a channel.* The plain ST's video chip has 3 bits per color
channel, so `$0RGB` values one apart can look the same. The STe build shows channel, so `$0RGB` values one apart can look the same. The STe build shows
all 4 bits. all 4 bits.
* *X68000: one intensity bit for three channels.* Its colours have 5 bits per * *X68000: one intensity bit for three channels.* Its colors have 5 bits per
channel and an intensity bit the three share. JoeyLib sets that bit for every channel and an intensity bit the three share. JoeyLib sets that bit for every
colour but black, so white is full white, and a channel that is 0 in any color but black, so white is full white, and a channel that is 0 in any
other colour shows at 1/63 rather than 0: `$F00` carries a trace of green and other color shows at 1/63 rather than 0: `$F00` carries a trace of green and
blue. blue.
* *Windows and DVX on a 256-colour display.* The display has one palette for * *Windows and DVX on a 256-color display.* The display has one palette for
everything on it. Windows keeps 20 of its entries for itself, and DVX shares everything on it. Windows keeps 20 of its entries for itself, and DVX shares
it among all its programs, so Win32 and a DVX window show each colour as the it among all its programs, so Win32 and a DVX window show each color as the
nearest one the display has, and Win16 shows them exactly only while the 236 nearest one the display has, and Win16 shows them exactly only while the 236
entries left are enough (<<_windows_programs>>). entries left are enough (<<_windows_programs>>).
* *ST and X68000: per-scanline palettes cost time.* Both displays have one * *ST and X68000: per-scanline palettes cost time.* Both displays have one
16-colour palette at a time, so the CPU loads each band's colours as the beam 16-color palette at a time, so the CPU loads each band's colors as the beam
reaches it: the ST in a timer interrupt at every change of palette, every reaches it: the ST in a timer interrupt at every change of palette, every
frame (about a line of CPU time each), and the X68000 in a raster interrupt. frame (about a line of CPU time each), and the X68000 in a raster interrupt.
A screen with one palette costs nothing (<<_per_platform_behaviour_summary>>). A screen with one palette costs nothing (<<_per_platform_behavior_summary>>).
[[portsDifferInput]] [[portsDifferInput]]
==== Input ==== Input
@ -461,8 +457,9 @@ names the ports, says how they differ, and gives the reason.
* *IIgs joystick: one stick, sharing two keys.* The game port takes one stick, * *IIgs joystick: one stick, sharing two keys.* The game port takes one stick,
and its buttons are wired to the same lines as the Command and Option keys, and its buttons are wired to the same lines as the Command and Option keys,
so holding either key reads as a button. so holding either key reads as a button.
* *Mouse buttons.* The IIgs mouse has one button, and the ST and X68000 mice * *Mouse buttons.* Every port has two, left and right, and a left click made
have two; the other ports report three where the mouse has them. with Ctrl held is the right button everywhere, so the IIgs's one-button
mouse has both. A middle button is not reported on any port.
* *ST and X68000: a stick is always connected.* An empty joystick port reads * *ST and X68000: a stick is always connected.* An empty joystick port reads
exactly as a stick at rest, so `jlJoystickConnected` is true for both. exactly as a stick at rest, so `jlJoystickConnected` is true for both.
* *ST: the mouse port's stick.* The keyboard processor reports port 0 as the * *ST: the mouse port's stick.* The keyboard processor reports port 0 as the
@ -471,9 +468,10 @@ names the ports, says how they differ, and gives the reason.
* *Amiga: one button or two.* `lowlevel.library` reports a one-button and a * *Amiga: one button or two.* `lowlevel.library` reports a one-button and a
two-button stick alike, so a stick counts as a one-button one until its two-button stick alike, so a stick counts as a one-button one until its
second button is first pressed. second button is first pressed.
* *PSP: no mouse, one key at a time.* The PSP has no mouse, and its on-screen * *PSP: the nub for a mouse, one key at a time.* The PSP's mouse is its
keyboard has one cursor, so it presses one key at a time (Shift, Ctrl, and analog nub, which sets the pointer's speed rather than its place, and its
Alt stay down until the next key is let go). on-screen keyboard has one cursor, so it presses one key at a time (Shift,
Ctrl, and Alt stay down until the next key is let go).
[[portsDifferAudio]] [[portsDifferAudio]]
==== Audio ==== Audio
@ -498,7 +496,7 @@ names the ports, says how they differ, and gives the reason.
on each side, shared by the module, the effects, the three tone voices, and on each side, shared by the module, the effects, the three tone voices, and
the noise. At most four sounds play at once, an effect can take a channel a the noise. At most four sounds play at once, an effect can take a channel a
voice was using, and a module plays on all four. A channel plays on one side voice was using, and a module plays on all four. A channel plays on one side
only, so a centred effect needs a free channel on each side, and without only, so a centered effect needs a free channel on each side, and without
them it plays on one channel, from one side; and the tone voices, one them it plays on one channel, from one side; and the tone voices, one
channel each, are heard from one side: voice 0 (and `jlAudioTone`) on the channel each, are heard from one side: voice 0 (and `jlAudioTone`) on the
left, voices 1 and 2 on the right. left, voices 1 and 2 on the right.
@ -633,7 +631,7 @@ names the ports, says how they differ, and gives the reason.
is one Memory Manager block inside one bank, 32 KB to 48 KB, and a `jlAlloc` is one Memory Manager block inside one bank, 32 KB to 48 KB, and a `jlAlloc`
block is a Memory Manager handle, so `jlFree` and `free` must never be given block is a Memory Manager handle, so `jlFree` and `free` must never be given
each other's blocks. each other's blocks.
* *ST: a `<math.h>` of five functions.* The ST's C library has no maths * *ST: a `<math.h>` of five functions.* The ST's C library has no math
library, so JoeyLib brings `ceil`, `floor`, `log`, `pow`, and `trunc` (and library, so JoeyLib brings `ceil`, `floor`, `log`, `pow`, and `trunc` (and
their `float` forms), good to a few hundred parts per million, and nothing their `float` forms), good to a few hundred parts per million, and nothing
else: a program that calls `sin` or `sqrt` does not link there. Every else: a program that calls `sin` or `sqrt` does not link there. Every
@ -721,7 +719,7 @@ Nothing in the API differs between them:
|=== |===
The pixels are identical: UBER's 51 surface hashes match the ST's golden for The pixels are identical: UBER's 51 surface hashes match the ST's golden for
the STe build on an STe and a Mega STe, and the gates check that under Hatari. the STe build on an STe and a Mega STe, and the tests check that under Hatari.
`./joey-run` gives an ST-build program a plain ST and an STe-build program an `./joey-run` gives an ST-build program a plain ST and an STe-build program an
STe; `--machine st`, `megast`, `ste`, or `megaste` picks another. A project STe; `--machine st`, `megast`, `ste`, or `megaste` picks another. A project
builds the STe variant with `make ste`, like any other port; it takes the ST's builds the STe variant with `make ste`, like any other port; it takes the ST's
@ -729,7 +727,7 @@ baked assets.
What the STe build buys, measured with UBER under Hatari against the ST build What the STe build buys, measured with UBER under Hatari against the ST build
on a plain ST: a large `jlFillRect` runs up to 1.76 times as fast (80x80), a on a plain ST: a large `jlFillRect` runs up to 1.76 times as fast (80x80), a
vertical line 1.4 times and `jlSurfaceClear` 1.1 times; everything else runs vertical line 1.4 times, and `jlSurfaceClear` 1.1 times; everything else runs
as on an ST. On a Mega STe at 16 MHz every operation runs about twice as fast. as on an ST. On a Mega STe at 16 MHz every operation runs about twice as fast.
Hatari models that 16 MHz without the RAM wait states a real Mega STe has, so Hatari models that 16 MHz without the RAM wait states a real Mega STe has, so
expect less on memory-bound operations there. expect less on memory-bound operations there.
@ -750,18 +748,18 @@ hashes are DOS's -- and the rest behaves like this:
`JOEYLIB_ASPECT=square` gives the SDL3 window). In 4:3 that is 1x at `JOEYLIB_ASPECT=square` gives the SDL3 window). In 4:3 that is 1x at
640x480 and 2x at 800x600 and 1024x768; with square pixels 1x, 2x, and 640x480 and 2x at 800x600 and 1024x768; with square pixels 1x, 2x, and
3x. The choice is kept with the full-screen one. The stage reaches the 3x. The choice is kept with the full-screen one. The stage reaches the
desktop through one colour table per palette, so SCB palettes are exact on desktop through one color table per palette, so SCB palettes are exact on
15-, 16-, and 32-bit desktops. On a 256-colour desktop every colour is the 15-, 16-, and 32-bit desktops. On a 256-color desktop every color is the
nearest one in DVX's palette, which all programs share: gradients band. nearest one in DVX's palette, which all programs share: gradients band.
* *Full screen.* Game > Full Screen, or Alt+Enter, puts the program on the * *Full screen.* Game > Full Screen, or Alt+Enter, puts the program on the
whole screen in DVX's full-screen mode, 320x200 in 256 colours (VGA mode whole screen in DVX's full-screen mode, 320x200 in 256 colors (VGA mode
13h, or a VBE mode of that size), and back in its window. There it looks 13h, or a VBE mode of that size), and back in its window. There it looks
exactly as on MS-DOS: the screen's 256 colours hold all 16 palettes, so exactly as on MS-DOS: the screen's 256 colors hold all 16 palettes, so
SCB palettes are exact at any desktop depth and palette changes are free. SCB palettes are exact at any desktop depth and palette changes are free.
The other programs run on meanwhile. Alt+Tab, Ctrl+Esc or another window The other programs run on meanwhile. Alt+Tab, Ctrl+Esc, or another window
coming to the front brings the desktop back, with the program still coming to the front brings the desktop back, with the program still
running and drawing in its window; giving its window the focus again puts running and drawing in its window; giving its window the focus again puts
it back on the whole screen. The choice is kept in the program's settings it back on the whole screen. The choice is kept in the program's user
folder (`JOEYLIB.INI`), so it starts the way it was left. It is the user's folder (`JOEYLIB.INI`), so it starts the way it was left. It is the user's
choice: JoeyLib's API has no full-screen call. (Under DOSBox, Alt+Enter is choice: JoeyLib's API has no full-screen call. (Under DOSBox, Alt+Enter is
DOSBox's own full-screen key; use Alt with the keypad's Enter, or the DOSBox's own full-screen key; use Alt with the keypad's Enter, or the
@ -774,12 +772,12 @@ hashes are DOS's -- and the rest behaves like this:
does not tear. The frame count is the same clock in both, so switching does not tear. The frame count is the same clock in both, so switching
never makes it jump. never makes it jump.
* *Keys DVX keeps.* While the window is active, or the program has the whole * *Keys DVX keeps.* While the window is active, or the program has the whole
screen, every key goes to the program, F1, F10, and Alt included, except screen, every key goes to the program, F1, F10, and Alt included. The
Alt+Tab (switch programs), Ctrl+Esc (Task Manager) and Alt+F4 (close the exceptions are the three DVX keeps, Alt+Tab (switch programs), Ctrl+Esc
window), and Alt+Enter, which the port keeps for full screen. The window's (Task Manager), and Alt+F4 (close the window), and Alt+Enter, which the
Game menu has Full Screen, Square Pixels, Help (the program's help file), port keeps for full screen. The window's Game menu has Full Screen, Square
and Exit. Typed Pixels, Help (the program's help file), and Exit. Typed characters come
characters come through DVX's keyboard layout. through DVX's keyboard layout.
* *Closing ends the program.* Alt+F4, the close box, and Game > Exit end the * *Closing ends the program.* Alt+F4, the close box, and Game > Exit end the
program at its next `jlWaitVBL`, `jlInputPoll`, `jlStagePresent`, or program at its next `jlWaitVBL`, `jlInputPoll`, `jlStagePresent`, or
`jlSerialPoll`, as `exit()` does anywhere: its `atexit` handlers run, then `jlSerialPoll`, as `exit()` does anywhere: its `atexit` handlers run, then
@ -791,9 +789,9 @@ hashes are DOS's -- and the rest behaves like this:
it ends. it ends.
* *Files.* DVX's programs share one current directory, so JoeyLib roots its * *Files.* DVX's programs share one current directory, so JoeyLib roots its
files at run time: `DATA/` is found beside the program (its folder under files at run time: `DATA/` is found beside the program (its folder under
`APPS`), while `SAVES/` and `joeylog.txt` go to the program's own settings `APPS`), while `SAVES/` and `joeylog.txt` go to the program's own folder in
folder, `CONFIG\APPS\VENDOR\NAME`, which DVX keeps apart from the program DVX's user folder, `USER\APPS\VENDOR\NAME`, which DVX keeps apart from the
files so a reinstall never overwrites them. program files so an upgrade never overwrites them.
* *Sound* goes through DVX's sound and music libraries, mixed with every * *Sound* goes through DVX's sound and music libraries, mixed with every
other program's: `.MOD` music, SFX in stereo, and the tone voices and the other program's: `.MOD` music, SFX in stereo, and the tone voices and the
noise as the hosted ports make them, square waves and the YM2149's noise noise as the hosted ports make them, square waves and the YM2149's noise
@ -812,6 +810,11 @@ hashes are DOS's -- and the rest behaves like this:
every port, closing one leaves DTR as it is. every port, closing one leaves DTR as it is.
* A program's `main` is compiled as `jlGameMain`, which the port calls with * A program's `main` is compiled as `jlGameMain`, which the port calls with
`argv[0]` the `.app`'s path. `argv[0]` the `.app`'s path.
* *Packages.* Each program is also a DVX add-on package,
`build/dvx/bin/packages/NAME.dpk`, which anyone installs with DVX's
Software program (<<_disk_images_and_packaging>>). DVX refuses a program
built for another release's binary interface, so build for the DVX it
runs on: `./install.sh install dvx` takes the DVX tree's newest.
`make dvx` builds every example as a DVX program (the `dvx` installer `make dvx` builds every example as a DVX program (the `dvx` installer
component supplies DVX and its SDK, <<_setting_up_the_toolchains>>), and component supplies DVX and its SDK, <<_setting_up_the_toolchains>>), and
@ -954,7 +957,7 @@ on macOS 13 (Ventura) or newer, with SDL 3 built the same way and linked in
(`scripts/check-macos-apps.sh`) -- but what an app does on a Mac has not (`scripts/check-macos-apps.sh`) -- but what an app does on a Mac has not
been seen yet. been seen yet.
`make macos` builds every example as an app; `./joey-run` recognises one `make macos` builds every example as an app; `./joey-run` recognizes one
and says it runs on a Mac. and says it runs on a Mac.
=== PSP Programs === PSP Programs
@ -977,11 +980,15 @@ is the same chunky one, so UBER's hashes are DOS's. Everything in
* *The screen.* The PSP's 480x272 screen shows the stage at 1:1 in its * *The screen.* The PSP's 480x272 screen shows the stage at 1:1 in its
middle, black around it. The scale and aspect settings do not apply (a PSP middle, black around it. The scale and aspect settings do not apply (a PSP
has no environment), and there is no full screen to switch to. has no environment), and there is no full screen to switch to.
* *Its own controls, and a keyboard on the screen.* The PSP's controls are * *Its own controls, a mouse, and a keyboard on the screen.* The PSP's
joystick 0, through SDL's gamepad mapping: the d-pad or the analog nub, controls come through SDL's gamepad mapping. The d-pad is joystick 0, with
cross as `JL_JOY_BUTTON_0` and circle as `JL_JOY_BUTTON_1`; joystick 1 is cross as `JL_JOY_BUTTON_0` and circle as `JL_JOY_BUTTON_1`; joystick 1 is
never there. Start is also the Return key and Select the Escape key, held never there. The analog nub is the mouse: tilted past a small dead zone
(`JL_KEY_RETURN`, `JL_KEY_ESCAPE`) and typed (`JL_CHAR_RETURN`, (where a worn nub may rest), it moves the pointer the way it leans, faster
the further it leans, up to 320 stage pixels a second (across the stage in
a second), and the pointer stays on the stage. L and R are the left and
right mouse buttons. Start is also the Return key and Select the Escape
key, held (`JL_KEY_RETURN`, `JL_KEY_ESCAPE`) and typed (`JL_CHAR_RETURN`,
`JL_CHAR_ESCAPE` from `jlInputGetChar`) as those keys are, so `JL_CHAR_ESCAPE` from `jlInputGetChar`) as those keys are, so
`jlWaitForAnyKey`, Return to go on, and Escape to quit work at a press. `jlWaitForAnyKey`, Return to go on, and Escape to quit work at a press.
Square opens a PC keyboard drawn over the bottom of the screen, US layout, Square opens a PC keyboard drawn over the bottom of the screen, US layout,
@ -993,8 +1000,8 @@ is the same chunky one, so UBER's hashes are DOS's. Everything in
letters on and off. Its keys go down and up and type as a desktop's do, letters on and off. Its keys go down and up and type as a desktop's do,
so `jlKeyDown`, `jlKeyPressed`, and `jlInputGetChar` see the same as they so `jlKeyDown`, `jlKeyPressed`, and `jlInputGetChar` see the same as they
would there, F1 to F10, the arrows, and Ctrl and Alt chords included. would there, F1 to F10, the arrows, and Ctrl and Alt chords included.
While it is open, joystick 0 rests: the d-pad and cross are the While it is open, joystick 0 and the mouse buttons rest (the d-pad,
keyboard's. There is no pointer to move. cross, L, and R are the keyboard's), and the pointer stays where it is.
* *HOME quits.* The HOME menu's Exit ends the program as closing a window * *HOME quits.* The HOME menu's Exit ends the program as closing a window
does. does.
* *Free space* (`jlDiskFree`) is that of the memory stick (or a PSP Go's * *Free space* (`jlDiskFree`) is that of the memory stick (or a PSP Go's
@ -1005,12 +1012,12 @@ is the same chunky one, so UBER's hashes are DOS's. Everything in
a 16- or 32-bit value at an address that is not a multiple of its size, a 16- or 32-bit value at an address that is not a multiple of its size,
where an x86 does not notice (a 68000 stops too, but only on odd where an x86 does not notice (a 68000 stops too, but only on odd
addresses). Read a multi-byte value from a file buffer a byte at a time. addresses). Read a multi-byte value from a file buffer a byte at a time.
The gates run every PSP program with PPSSPP checking every access the same The tests run every PSP program with PPSSPP checking every access the same
way. way.
`make psp` builds every example, and `./joey-run --port psp --headless NAME` `make psp` builds every example, and `./joey-run --port psp --headless NAME`
runs one under PPSSPP's headless build (the `ppsspp` installer component, runs one under PPSSPP's headless build (the `ppsspp` installer component,
patched for the gates); `--snapshot` saves the PSP's screen. There is no patched for the tests); `--snapshot` saves the PSP's screen. There is no
window to play in here: to play, copy the folder to a PSP, or open its window to play in here: to play, copy the folder to a PSP, or open its
`EBOOT.PBP` in PPSSPP. None of it has run on a real PSP yet. `EBOOT.PBP` in PPSSPP. None of it has run on a real PSP yet.
@ -1036,7 +1043,7 @@ the same chunky one, so UBER's hashes are DOS's. Everything in
shape, 4:3 or square (the same `JOEYLIB_SCALE`, `JOEYLIB_ASPECT`, and shape, 4:3 or square (the same `JOEYLIB_SCALE`, `JOEYLIB_ASPECT`, and
`JOEYLIB_FULLSCREEN` settings), black around it; full screen is a `JOEYLIB_FULLSCREEN` settings), black around it; full screen is a
borderless window over the whole monitor, which leaves the display mode borderless window over the whole monitor, which leaves the display mode
alone. On a 256-colour desktop Windows maps every colour to the nearest alone. On a 256-color desktop Windows maps every color to the nearest
one it has. Each frame draws only what changed since the last: a game one it has. Each frame draws only what changed since the last: a game
that moves a few sprites pays for their rectangles, not for the whole that moves a few sprites pays for their rectangles, not for the whole
window, which on a Windows 95 machine is most of a frame's time. window, which on a Windows 95 machine is most of a frame's time.
@ -1049,7 +1056,7 @@ the same chunky one, so UBER's hashes are DOS's. Everything in
`make win32` builds every example as a Windows program in `build/win32/bin/`, `make win32` builds every example as a Windows program in `build/win32/bin/`,
and `./joey-run --port win32 NAME` runs one under Wine (the `wine` installer and `./joey-run --port win32 NAME` runs one under Wine (the `wine` installer
component, a portable Wine kept in `toolchains/`); the gates run there too, component, a portable Wine kept in `toolchains/`); the tests run there too,
`scripts/verify-win32-display.sh` among them, though that checks Wine's GDI `scripts/verify-win32-display.sh` among them, though that checks Wine's GDI
rather than Windows'. None of it has run on a real Windows 9x or NT 4 here. rather than Windows'. None of it has run on a real Windows 9x or NT 4 here.
@ -1064,12 +1071,12 @@ scan code, DTR dropped at close -- except:
32-bit code bound to Watcom's supervisor, which turns each call into the 32-bit code bound to Watcom's supervisor, which turns each call into the
16-bit one Windows takes, so it needs Windows in 386 enhanced mode. It 16-bit one Windows takes, so it needs Windows in 386 enhanced mode. It
calls only what Windows 3.1 has: USER, GDI, KERNEL, and MMSYSTEM. calls only what Windows 3.1 has: USER, GDI, KERNEL, and MMSYSTEM.
* *Exact colours.* The frame is 8-bit, each pixel one entry of a 256-entry * *Exact colors.* The frame is 8-bit, each pixel one entry of a 256-entry
colour table holding all 16 palettes, which on a 256-colour display is a color table holding all 16 palettes, which on a 256-color display is a
logical palette too, so every colour is exact, SCB palettes included, as logical palette too, so every color is exact, SCB palettes included, as
far as the display has room: Windows keeps 20 of a 256-colour display's far as the display has room: Windows keeps 20 of a 256-color display's
entries for itself, so with more than 14 palettes in use and more than 236 entries for itself, so with more than 14 palettes in use and more than 236
different colours on screen, some show as the nearest it has. different colors on screen, some show as the nearest it has.
`JOEYLIB_SCALE` defaults to 2, and the settings are read from the DOS `JOEYLIB_SCALE` defaults to 2, and the settings are read from the DOS
environment Windows started from (`joey-run` puts its own there). Under environment Windows started from (`joey-run` puts its own there). Under
DOSBox, Alt+Enter is DOSBox's own full-screen key; use Alt with the DOSBox, Alt+Enter is DOSBox's own full-screen key; use Alt with the
@ -1081,14 +1088,14 @@ scan code, DTR dropped at close -- except:
the machine (its runtime in Windows' `SYSTEM` folder) the rows go straight the machine (its runtime in Windows' `SYSTEM` folder) the rows go straight
into a WinG bitmap the size of the window, and drawing a rectangle is a into a WinG bitmap the size of the window, and drawing a rectangle is a
plain copy to the screen; without it, each rectangle goes through a device plain copy to the screen; without it, each rectangle goes through a device
bitmap on a 256-colour display, and only another kind of display pays for bitmap on a 256-color display, and only another kind of display pays for
Windows matching every pixel's colour. `JOEYLIB_WING=0` leaves WinG alone. Windows matching every pixel's color. `JOEYLIB_WING=0` leaves WinG alone.
JoeyLib does not ship WinG: a game that wants it on its players' machines JoeyLib does not ship WinG: a game that wants it on its players' machines
ships the runtime under Microsoft's terms (see `LICENSES.md`). While the ships the runtime under Microsoft's terms (see `LICENSES.md`). While the
SCBs use 14 palettes or fewer, the frame's colours are the screen's own (an SCBs use 14 palettes or fewer, the frame's colors are the screen's own (an
identity palette, as WinG recommends), so a palette change, colour cycling identity palette, as WinG recommends), so a palette change, color cycling
included, reaches the screen at once with nothing redrawn; more palettes included, reaches the screen at once with nothing redrawn; more palettes
than that, or a display without a palette, redraw the window when colours than that, or a display without a palette, redraw the window when colors
change. On a 386SX-16 (DOSBox's model of one), a game loop presenting change. On a 386SX-16 (DOSBox's model of one), a game loop presenting
every frame runs at, in frames a second, with an Apple IIgs (under MAME) every frame runs at, in frames a second, with an Apple IIgs (under MAME)
for scale: for scale:
@ -1096,7 +1103,7 @@ scan code, DTR dropped at close -- except:
[cols="3,1,1,1,1", options="header"] [cols="3,1,1,1,1", options="header"]
|=== |===
| |2x with WinG |2x without |1:1 |IIgs | |2x with WinG |2x without |1:1 |IIgs
|Nothing moving, or colour cycling |60 |60 |60 |60 |Nothing moving, or color cycling |60 |60 |60 |60
|One 16x16 sprite moving |60 |60 |60 |60 |One 16x16 sprite moving |60 |60 |60 |60
|Eight sprites moving |26 |20 |30 |20 |Eight sprites moving |26 |20 |30 |20
|Eight sprites in a column |30 |20 |30 |20 |Eight sprites in a column |30 |20 |30 |20
@ -1118,7 +1125,7 @@ screen every frame, so a game that redraws everything every frame wants
driver, which Windows 3.1 itself does not have; without one no stick is driver, which Windows 3.1 itself does not have; without one no stick is
seen. JoeyLib's Windows runs KPJOY.DRV (the `kpjoy` installer component, seen. JoeyLib's Windows runs KPJOY.DRV (the `kpjoy` installer component,
built from the kpjoy project): the PC game port's two sticks, each axis's built from the kpjoy project): the PC game port's two sticks, each axis's
rest taken from its first readings, so leave the stick centred as the rest taken from its first readings, so leave the stick centered as the
program starts. A game shipped for Windows 3.x can ship KPJOY.DRV with it program starts. A game shipped for Windows 3.x can ship KPJOY.DRV with it
(`[drivers] joystick=kpjoy.drv` in SYSTEM.INI). (`[drivers] joystick=kpjoy.drv` in SYSTEM.INI).
* *Files.* The program's folder, drive included, is the current directory * *Files.* The program's folder, drive included, is the current directory
@ -1149,11 +1156,11 @@ which the installer puts beside the driver in `toolchains/opt/`.
`make win16` builds every example as a Windows 3.x program in `make win16` builds every example as a Windows 3.x program in
`build/win16/bin/`, and `./joey-run --port win16 NAME` runs one on Windows `build/win16/bin/`, and `./joey-run --port win16 NAME` runs one on Windows
for Workgroups 3.11 in DOSBox Staging (the `win311` installer component, set for Workgroups 3.11 in DOSBox Staging (the `win311` installer component, set
up from your own install disc); the gates run there too, among them up from your own install disc); the tests run there too, among them
`scripts/verify-win16-display.sh`, which checks every pixel the window shows `scripts/verify-win16-display.sh`, which checks every pixel the window shows
against the stage after every present, with WinG and without (the optional against the stage after every present, with WinG and without (the optional
`wing` installer component: `./install.sh import WING10.EXE`, Microsoft's `wing` installer component: `./install.sh import WING10.EXE`, Microsoft's
WinG 1.0 SDK, then `./install.sh install wing`; the gates then install its WinG 1.0 SDK, then `./install.sh install wing`; the tests then install its
runtime in their Windows). None of it has run on a real 386 runtime in their Windows). None of it has run on a real 386
here; the speeds above are DOSBox's 386SX-16 model's, and the IIgs's are here; the speeds above are DOSBox's 386SX-16 model's, and the IIgs's are
MAME's. MAME's.
@ -1195,8 +1202,8 @@ Their stage is the same chunky one, so UBER's hashes are DOS's. Everything in
* *Serial* through termios: the first entry of `/dev/ports` (Haiku's * *Serial* through termios: the first entry of `/dev/ports` (Haiku's
`pc_serial0`, BeOS's `serial1`), or the device the `JOEYLIB_SERIAL` `pc_serial0`, BeOS's `serial1`), or the device the `JOEYLIB_SERIAL`
environment variable names, and the unit'th entry in name order for a environment variable names, and the unit'th entry in name order for a
slot; 7 or 8 data bits only. The gates run it on Haiku; BeOS R5 shares the slot; 7 or 8 data bits only. The tests run it on Haiku; BeOS R5 shares the
code but not the test, since the gates' BeOS VM has no serial ports. code but not the test, since the tests' BeOS VM has no serial ports.
* *BeOS R5's loader* refuses any program with a dynamic tag it does not * *BeOS R5's loader* refuses any program with a dynamic tag it does not
know, so `make beos` links without combined relocations (`-z know, so `make beos` links without combined relocations (`-z
nocombreloc`). Build for R5 with `make beos`, not `make haiku32`. nocombreloc`). Build for R5 with `make beos`, not `make haiku32`.
@ -1207,8 +1214,8 @@ port's VM under QEMU: Haiku R1/beta6 (the `haikuVm` and `haikuVm32` installer
components) or BeOS R5 (`beosVm`, made from your CD), booted from a disk it components) or BeOS R5 (`beosVm`, made from your CD), booted from a disk it
never writes to, with the program's folder on a second disk. R5 drives none never writes to, with the program's folder on a second disk. R5 drives none
of QEMU's video cards, so its VM is taken through the boot loader's menu to of QEMU's video cards, so its VM is taken through the boot loader's menu to
the fail-safe (VESA) mode, 1024x768 in 16-bit colour; it has no sound card. the fail-safe (VESA) mode, 1024x768 in 16-bit color; it has no sound card.
The gates run in the same VMs (`scripts/verify-be-input.sh`, The tests run in the same VMs (`scripts/verify-be-input.sh`,
`scripts/verify-be-display.sh`, `scripts/verify-be-icon.sh`, and `scripts/verify-be-display.sh`, `scripts/verify-be-icon.sh`, and
`scripts/bench-be.sh`). None of it has `scripts/bench-be.sh`). None of it has
run on a real BeOS or Haiku machine here. run on a real BeOS or Haiku machine here.

View file

@ -1,11 +1,11 @@
== Anatomy of a JoeyLib Program == Anatomy of a JoeyLib Program
Every JoeyLib program has the same four-part shape: configure and initialise, Every JoeyLib program has the same four-part shape: configure and initialize,
set up the screen and load assets, run a frame loop, shut down. This chapter set up the screen and load assets, run a frame loop, and shut down. This chapter
walks through each part and ends with a complete annotated skeleton you can walks through each part and ends with a complete annotated skeleton you can
copy. copy.
=== Part 1: Configure and Initialise === Part 1: Configure and Initialize
[source,c] [source,c]
---- ----
@ -57,7 +57,9 @@ The *stage* is the library-owned back buffer. You never create or destroy it;
it is valid from `jlInit` to `jlShutdown`. All drawing goes here, and it is valid from `jlInit` to `jlShutdown`. All drawing goes here, and
`jlStagePresent()` copies it to the real display. `jlStagePresent()` copies it to the real display.
Set up colour before drawing: The stage starts black, every line on palette 0, and every palette set to the
standard 16 EGA colors (<<_the_default_palette>>), so a program can draw
straight away. To use colors of your own, set them up before drawing:
[source,c] [source,c]
---- ----
@ -67,11 +69,11 @@ jlPaletteSet(stage, 0, colors);
jlScbSetRange(stage, 0, JL_SURFACE_HEIGHT - 1, 0); jlScbSetRange(stage, 0, JL_SURFACE_HEIGHT - 1, 0);
---- ----
Each surface carries *16 palettes of 16 colours* plus a 200-entry table (one Each surface carries *16 palettes of 16 colors* plus a 200-entry table (one
per scanline) selecting which palette that line uses. That table is the *SCB*, per scanline) selecting which palette that line uses. That table is the *SCB*,
or scanline control byte table. Using one palette everywhere is the simple or scanline control byte table. Using one palette everywhere is the simple
case; assigning different palettes to different bands is how a JoeyLib program case; assigning different palettes to different bands is how a JoeyLib program
shows more than 16 colours at once. shows more than 16 colors at once.
Load assets after init, because sprite loading needs the codegen arena: Load assets after init, because sprite loading needs the codegen arena:
@ -345,7 +347,7 @@ Before drawing a sprite, capture what is underneath; next frame, put it back.
[source,c] [source,c]
---- ----
uint16_t backing[jlSpriteBackupLength(uint16_t, 2, 2)]; uint16_t backing[jlSpriteBackupLength(2, 2)];
jlSpriteBackupT backup; jlSpriteBackupT backup;
backup.bytes = (uint8_t *)backing; backup.bytes = (uint8_t *)backing;
@ -356,10 +358,8 @@ jlSpriteRestoreUnder(stage, &backup); // erase last frame's sprite
jlSpriteSaveAndDraw(stage, sprite, x, y, &backup); jlSpriteSaveAndDraw(stage, sprite, x, y, &backup);
---- ----
Size the buffer with `jlSpriteBackupLength`, and declare it as `uint16_t` (or The buffer is an array of `uint16_t`, sized with `jlSpriteBackupLength`;
otherwise 2-byte aligned): on the Amiga, ST, and X68000 an odd address sends that is what save and restore require (<<_jlspritebackuplength>>).
save and restore down a much slower path, and it crashes the trusted
variants.
==== The Clean-Buffer Pattern ==== The Clean-Buffer Pattern

View file

@ -24,8 +24,9 @@
// Controls: // Controls:
// Mouse - left-click to act with the current verb on the // Mouse - left-click to act with the current verb on the
// thing under the cursor (or walk if no hotspot) // thing under the cursor (or walk if no hotspot)
// Right-click - cycle verb (WALK / LOOK / TAKE / USE / TALK) // Right-click - cycle verb (WALK / LOOK / TAKE / USE / TALK); Ctrl-click
// Space - cycle verb (one-button-mouse fallback for IIgs) // is a right-click on every machine
// Space - cycle verb from the keyboard
// 1..5 - jump straight to a verb // 1..5 - jump straight to a verb
// I - toggle "look at inventory" cursor mode // I - toggle "look at inventory" cursor mode
// Click item - select item; cursor becomes the item icon. Next // Click item - select item; cursor becomes the item icon. Next
@ -34,7 +35,7 @@
// ESC - quit // ESC - quit
// //
// This is intentionally one file: every system (art, engine, rooms, // This is intentionally one file: every system (art, engine, rooms,
// pathfinding, UI) lives below as a labelled section so a reader can // pathfinding, UI) lives below as a labeled section so a reader can
// follow the whole engine without chasing headers. // follow the whole engine without chasing headers.
#include <stdio.h> #include <stdio.h>
@ -42,6 +43,8 @@
#include <joey/joey.h> #include <joey/joey.h>
#include "exampleFont.h"
/* =================================================================== /* ===================================================================
* Section 1 -- Layout, constants, types * Section 1 -- Layout, constants, types
@ -217,7 +220,7 @@ static RoomT gRooms[ROOM_COUNT];
static jlSpriteT *gEgo[DIR_COUNT][EGO_FRAMES]; static jlSpriteT *gEgo[DIR_COUNT][EGO_FRAMES];
static uint8_t gEgoTiles[DIR_COUNT][EGO_FRAMES][EGO_TILE_BYTES]; static uint8_t gEgoTiles[DIR_COUNT][EGO_FRAMES][EGO_TILE_BYTES];
static uint16_t gEgoBackup[jlSpriteBackupLength(uint16_t, EGO_W / 8, EGO_H / 8)]; static uint16_t gEgoBackup[jlSpriteBackupLength(EGO_W / 8, EGO_H / 8)];
static jlSpriteBackupT gEgoBak; static jlSpriteBackupT gEgoBak;
// Cursor sprite per verb (+ item-icon for "using" mode) // Cursor sprite per verb (+ item-icon for "using" mode)
@ -225,7 +228,7 @@ static jlSpriteT *gCursor[VERB_COUNT];
static jlSpriteT *gItemSprite[ITEM_COUNT]; // index 0 = unused static jlSpriteT *gItemSprite[ITEM_COUNT]; // index 0 = unused
static jlSpriteT *gLampLitSprite; // lit-state inventory icon static jlSpriteT *gLampLitSprite; // lit-state inventory icon
static uint8_t gLampLitTiles[2 * 2 * JL_TILE_BYTES]; static uint8_t gLampLitTiles[2 * 2 * JL_TILE_BYTES];
static uint16_t gCursorBackup[jlSpriteBackupLength(uint16_t, CURSOR_TILES_X, CURSOR_TILES_Y)]; static uint16_t gCursorBackup[jlSpriteBackupLength(CURSOR_TILES_X, CURSOR_TILES_Y)];
static jlSpriteBackupT gCursorBak; static jlSpriteBackupT gCursorBak;
// Ego state // Ego state
@ -293,7 +296,7 @@ static uint16_t rgb(uint8_t r, uint8_t g, uint8_t b) {
* Every sprite is authored as an ASCII "row of pixel chars" grid; * Every sprite is authored as an ASCII "row of pixel chars" grid;
* a tiny helper converts that to JoeyLib's 4bpp packed tile-major * a tiny helper converts that to JoeyLib's 4bpp packed tile-major
* layout at startup. Same approach as examples/sprite/sprite.c but * layout at startup. Same approach as examples/sprite/sprite.c but
* generalised so we can author dozens of frames in one file. * generalized so we can author dozens of frames in one file.
* *
* Char palette in the strings: * Char palette in the strings:
* ' ' or '.' -> transparent (color 0) * ' ' or '.' -> transparent (color 0)
@ -1505,7 +1508,7 @@ static void cellToFoot(int16_t cx, int16_t cy, int16_t *px, int16_t *py) {
/* BFS parents stored as a (row,col)-indexed grid. Each entry is a /* BFS parents stored as a (row,col)-indexed grid. Each entry is a
* direction byte (0=N,1=E,2=S,3=W) telling us which neighbour we * direction byte (0=N,1=E,2=S,3=W) telling us which neighbor we
* came from; 0xFF means unvisited. */ * came from; 0xFF means unvisited. */
static uint8_t gBfsParent[WALK_GRID_H][WALK_GRID_W]; static uint8_t gBfsParent[WALK_GRID_H][WALK_GRID_W];
static int16_t gBfsQueueX[WALK_GRID_H * WALK_GRID_W]; static int16_t gBfsQueueX[WALK_GRID_H * WALK_GRID_W];
@ -1536,7 +1539,7 @@ static __attribute__((noinline)) void bfsClear(void) {
// gBfsParent. Returns true if the target cell was reached. Split out of // gBfsParent. Returns true if the target cell was reached. Split out of
// findPath to keep the hot loop's live-value count low enough for the // findPath to keep the hot loop's live-value count low enough for the
// w65816 register allocator. // w65816 register allocator.
// Visit one neighbour of (cx,cy) in direction d. If that neighbour is // Visit one neighbor of (cx,cy) in direction d. If that neighbor is
// walkable and not yet visited, record its incoming direction and append // walkable and not yet visited, record its incoming direction and append
// it to the BFS queue, advancing *qtail. Kept noinline so bfsSearch's // it to the BFS queue, advancing *qtail. Kept noinline so bfsSearch's
// inner loop reduces to a single call and stays within the w65816 // inner loop reduces to a single call and stays within the w65816
@ -1868,134 +1871,13 @@ static int hotspotAt(int16_t mx, int16_t my) {
/* =================================================================== /* ===================================================================
* Section 11 -- Tiny inline 5x7 bitmap font * Section 11 -- Text
* *
* Drawing text uses jlDrawPixel directly (set bits only). The * The bars' text is the examples' shared 5x7 font
* message bar updates rarely so per-pixel plot is fine. * (examples/common/exampleFont.c), drawn on the stage pixel by pixel:
* * the bars change rarely, so that is fast enough.
* Each glyph is 5 wide x 7 tall, stored as 7 bytes (one per row),
* with the high 5 bits as pixel pattern. The font covers space,
* '0'..'9', 'A'..'Z', '.', ',', ':', '!', '?', '-', '\'', '"'.
* Other chars render as space.
* =================================================================== */ * =================================================================== */
#define FONT_W 5
#define FONT_H 7
static const uint8_t kFontGlyph[][FONT_H] = {
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 0 space
{ 0x70, 0x88, 0x98, 0xA8, 0xC8, 0x88, 0x70 }, // 1 '0'
{ 0x20, 0x60, 0x20, 0x20, 0x20, 0x20, 0x70 }, // 2 '1'
{ 0x70, 0x88, 0x08, 0x10, 0x20, 0x40, 0xF8 }, // 3 '2'
{ 0x70, 0x88, 0x08, 0x30, 0x08, 0x88, 0x70 }, // 4 '3'
{ 0x10, 0x30, 0x50, 0x90, 0xF8, 0x10, 0x10 }, // 5 '4'
{ 0xF8, 0x80, 0xF0, 0x08, 0x08, 0x88, 0x70 }, // 6 '5'
{ 0x30, 0x40, 0x80, 0xF0, 0x88, 0x88, 0x70 }, // 7 '6'
{ 0xF8, 0x08, 0x10, 0x20, 0x40, 0x40, 0x40 }, // 8 '7'
{ 0x70, 0x88, 0x88, 0x70, 0x88, 0x88, 0x70 }, // 9 '8'
{ 0x70, 0x88, 0x88, 0x78, 0x08, 0x10, 0x60 }, // 10 '9'
{ 0x70, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88 }, // 11 'A'
{ 0xF0, 0x88, 0x88, 0xF0, 0x88, 0x88, 0xF0 }, // 12 'B'
{ 0x70, 0x88, 0x80, 0x80, 0x80, 0x88, 0x70 }, // 13 'C'
{ 0xF0, 0x88, 0x88, 0x88, 0x88, 0x88, 0xF0 }, // 14 'D'
{ 0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0xF8 }, // 15 'E'
{ 0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0x80 }, // 16 'F'
{ 0x70, 0x88, 0x80, 0xB8, 0x88, 0x88, 0x70 }, // 17 'G'
{ 0x88, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88 }, // 18 'H'
{ 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70 }, // 19 'I'
{ 0x38, 0x10, 0x10, 0x10, 0x90, 0x90, 0x60 }, // 20 'J'
{ 0x88, 0x90, 0xA0, 0xC0, 0xA0, 0x90, 0x88 }, // 21 'K'
{ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF8 }, // 22 'L'
{ 0x88, 0xD8, 0xA8, 0xA8, 0x88, 0x88, 0x88 }, // 23 'M'
{ 0x88, 0xC8, 0xA8, 0x98, 0x88, 0x88, 0x88 }, // 24 'N'
{ 0x70, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70 }, // 25 'O'
{ 0xF0, 0x88, 0x88, 0xF0, 0x80, 0x80, 0x80 }, // 26 'P'
{ 0x70, 0x88, 0x88, 0x88, 0xA8, 0x90, 0x68 }, // 27 'Q'
{ 0xF0, 0x88, 0x88, 0xF0, 0xA0, 0x90, 0x88 }, // 28 'R'
{ 0x70, 0x88, 0x80, 0x70, 0x08, 0x88, 0x70 }, // 29 'S'
{ 0xF8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 }, // 30 'T'
{ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70 }, // 31 'U'
{ 0x88, 0x88, 0x88, 0x88, 0x88, 0x50, 0x20 }, // 32 'V'
{ 0x88, 0x88, 0x88, 0xA8, 0xA8, 0xD8, 0x88 }, // 33 'W'
{ 0x88, 0x88, 0x50, 0x20, 0x50, 0x88, 0x88 }, // 34 'X'
{ 0x88, 0x88, 0x50, 0x20, 0x20, 0x20, 0x20 }, // 35 'Y'
{ 0xF8, 0x08, 0x10, 0x20, 0x40, 0x80, 0xF8 }, // 36 'Z'
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60 }, // 37 '.'
{ 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x40 }, // 38 ','
{ 0x00, 0x60, 0x60, 0x00, 0x60, 0x60, 0x00 }, // 39 ':'
{ 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x20 }, // 40 '!'
{ 0x70, 0x88, 0x08, 0x10, 0x20, 0x00, 0x20 }, // 41 '?'
{ 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00 }, // 42 '-'
{ 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 43 '\''
{ 0x50, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 44 '"'
};
static int glyphIdx(char c) {
if (c == ' ') {
return 0;
}
if (c >= '0' && c <= '9') {
return 1 + (c - '0');
}
if (c >= 'A' && c <= 'Z') {
return 11 + (c - 'A');
}
if (c >= 'a' && c <= 'z') {
return 11 + (c - 'a'); // uppercase fallback
}
if (c == '.') {
return 37;
}
if (c == ',') {
return 38;
}
if (c == ':') {
return 39;
}
if (c == '!') {
return 40;
}
if (c == '?') {
return 41;
}
if (c == '-') {
return 42;
}
if (c == '\'') {
return 43;
}
if (c == '"') {
return 44;
}
return 0;
}
static void drawTextLine(int16_t x, int16_t y, const char *text, uint8_t color) {
int16_t cx;
int16_t i;
int idx;
int16_t row;
int16_t col;
uint8_t bits;
cx = x;
for (i = 0; text[i] != '\0'; i++) {
idx = glyphIdx(text[i]);
for (row = 0; row < FONT_H; row++) {
bits = kFontGlyph[idx][row];
for (col = 0; col < FONT_W; col++) {
if (bits & (0x80 >> col)) {
jlDrawPixel(jlgScreen, (int16_t)(cx + col), (int16_t)(y + row), color);
}
}
}
cx = (int16_t)(cx + FONT_W + 1);
if (cx > JL_SURFACE_WIDTH - FONT_W) {
return;
}
}
}
/* =================================================================== /* ===================================================================
* Section 12 -- UI bars (message + inventory) * Section 12 -- UI bars (message + inventory)
@ -2009,7 +1891,7 @@ static void drawMessageBar(void) {
jlFillRect(jlgScreen, 0, MSG_BAR_Y, JL_SURFACE_WIDTH, MSG_BAR_H, UI_BG); jlFillRect(jlgScreen, 0, MSG_BAR_Y, JL_SURFACE_WIDTH, MSG_BAR_H, UI_BG);
jlFillRect(jlgScreen, 0, MSG_BAR_Y, JL_SURFACE_WIDTH, 1, UI_DIM); jlFillRect(jlgScreen, 0, MSG_BAR_Y, JL_SURFACE_WIDTH, 1, UI_DIM);
if (gMessage[0] != '\0') { if (gMessage[0] != '\0') {
drawTextLine(4, MSG_BAR_Y + 2, gMessage, UI_INK); exampleFontDraw(jlgScreen, 4, MSG_BAR_Y + 2, gMessage, UI_INK);
} }
} }
@ -2024,7 +1906,7 @@ static void drawInventoryBar(void) {
// Verb readout at the left. // Verb readout at the left.
snprintf(buf, sizeof(buf), "VERB: %s", verbName(gCurVerb)); snprintf(buf, sizeof(buf), "VERB: %s", verbName(gCurVerb));
drawTextLine(4, INV_BAR_Y + 2, buf, UI_HILITE); exampleFontDraw(jlgScreen, 4, INV_BAR_Y + 2, buf, UI_HILITE);
// Item icons at the right. // Item icons at the right.
x = JL_SURFACE_WIDTH - (ITEM_ICON_W + 4); x = JL_SURFACE_WIDTH - (ITEM_ICON_W + 4);
@ -2356,8 +2238,6 @@ static void enterRoom(RoomE room, bool fromEast, bool fromWest) {
gEgoFrameIdx = 0; gEgoFrameIdx = 0;
gPathLen = 0; gPathLen = 0;
gPathStep = 0; gPathStep = 0;
programSkyScb(jlgScreen, room);
} }
@ -2378,6 +2258,12 @@ static bool buildBackgrounds(void) {
} }
drawForestBackground(gBgScene[ROOM_FOREST]); drawForestBackground(gBgScene[ROOM_FOREST]);
drawCottageBackground(gBgScene[ROOM_COTTAGE]); drawCottageBackground(gBgScene[ROOM_COTTAGE]);
// jlSurfaceCopy brings a background's palettes and SCB to the stage with
// its pixels, so each carries the scene's colors.
buildPalettes(gBgScene[ROOM_FOREST]);
buildPalettes(gBgScene[ROOM_COTTAGE]);
programSkyScb(gBgScene[ROOM_FOREST], ROOM_FOREST);
programSkyScb(gBgScene[ROOM_COTTAGE], ROOM_COTTAGE);
return true; return true;
} }
@ -2420,9 +2306,6 @@ int main(void) {
return 1; return 1;
} }
buildPalettes(jlgScreen);
programSkyScb(jlgScreen, ROOM_FOREST);
if (!buildAllSprites()) { if (!buildAllSprites()) {
fprintf(stderr, "buildAllSprites failed: %s\n", jlLastError()); fprintf(stderr, "buildAllSprites failed: %s\n", jlLastError());
jlShutdown(); jlShutdown();
@ -2473,9 +2356,8 @@ int main(void) {
if (jlKeyPressed(JL_KEY_5)) { if (jlKeyPressed(JL_KEY_5)) {
gCurVerb = VERB_TALK; gCurVerb = VERB_TALK;
} }
/* SPACE cycles verb -- one-button-mouse fallback for the /* SPACE cycles verb from the keyboard. Mirrors the
* IIgs, where right-click does not exist. Mirrors the * right-click behavior: drops any in-progress item-use
* right-click behaviour: drops any in-progress item-use
* mode first, then advances the verb. */ * mode first, then advances the verb. */
if (jlKeyPressed(JL_KEY_SPACE)) { if (jlKeyPressed(JL_KEY_SPACE)) {
if (gUsingItem != ITEM_NONE) { if (gUsingItem != ITEM_NONE) {

View file

@ -48,7 +48,7 @@
// Click on a tree trunk and the BFS still finds a path to the // Click on a tree trunk and the BFS still finds a path to the
// nearest walkable pixel. // nearest walkable pixel.
// //
// *** Visualising the buffers *** // *** Visualizing the buffers ***
// ------------------------------------------------------------------ // ------------------------------------------------------------------
// Press P to swap the visible screen with the priority buffer; the // Press P to swap the visible screen with the priority buffer; the
// palette is replaced with a rainbow ramp so each priority band is // palette is replaced with a rainbow ramp so each priority band is
@ -62,7 +62,7 @@
// Controls: // Controls:
// Mouse left walk to / act on whatever is under the cursor // Mouse left walk to / act on whatever is under the cursor
// Mouse right cycle verb (WALK / LOOK / TAKE) // Mouse right cycle verb (WALK / LOOK / TAKE)
// SPACE cycle verb (one-button-mouse fallback) // SPACE cycle verb from the keyboard
// 1 / 2 / 3 jump straight to a verb // 1 / 2 / 3 jump straight to a verb
// P toggle priority-screen overlay // P toggle priority-screen overlay
// C toggle control-screen overlay // C toggle control-screen overlay
@ -78,6 +78,8 @@
#include <joey/joey.h> #include <joey/joey.h>
#include "exampleFont.h"
/* =================================================================== /* ===================================================================
* Section 1 -- Layout, priority bands, control codes * Section 1 -- Layout, priority bands, control codes
@ -110,7 +112,7 @@
#define CTRL_TRIGGER_LOOK 2 // "you smell the forest" trigger zone #define CTRL_TRIGGER_LOOK 2 // "you smell the forest" trigger zone
/* Pathfinding grid -- one cell per 4x4 pixel block, queried against /* Pathfinding grid -- one cell per 4x4 pixel block, queried against
* the control surface (sampled at the cell centre). */ * the control surface (sampled at the cell center). */
#define WALK_CELL_PX 4 #define WALK_CELL_PX 4
#define WALK_GRID_W (JL_SURFACE_WIDTH / WALK_CELL_PX) // 80 #define WALK_GRID_W (JL_SURFACE_WIDTH / WALK_CELL_PX) // 80
#define WALK_GRID_H (PLAY_AREA_H / WALK_CELL_PX) // 42 #define WALK_GRID_H (PLAY_AREA_H / WALK_CELL_PX) // 42
@ -540,114 +542,15 @@ static const PropDef kProps[] = {
/* =================================================================== /* ===================================================================
* Section 6 -- Tiny 5x7 font (lifted from adventure.c for self- * Section 6 -- Text
* containment). *
* The bars' text is the examples' shared 5x7 font
* (examples/common/exampleFont.c), drawn on the stage.
* =================================================================== */ * =================================================================== */
#define FONT_W 5
#define FONT_H 7
static const uint8_t kFontGlyph[][FONT_H] = {
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 0 space
{ 0x70, 0x88, 0x98, 0xA8, 0xC8, 0x88, 0x70 }, // 1 '0'
{ 0x20, 0x60, 0x20, 0x20, 0x20, 0x20, 0x70 }, // 2 '1'
{ 0x70, 0x88, 0x08, 0x10, 0x20, 0x40, 0xF8 }, // 3 '2'
{ 0x70, 0x88, 0x08, 0x30, 0x08, 0x88, 0x70 }, // 4 '3'
{ 0x10, 0x30, 0x50, 0x90, 0xF8, 0x10, 0x10 }, // 5 '4'
{ 0xF8, 0x80, 0xF0, 0x08, 0x08, 0x88, 0x70 }, // 6 '5'
{ 0x30, 0x40, 0x80, 0xF0, 0x88, 0x88, 0x70 }, // 7 '6'
{ 0xF8, 0x08, 0x10, 0x20, 0x40, 0x40, 0x40 }, // 8 '7'
{ 0x70, 0x88, 0x88, 0x70, 0x88, 0x88, 0x70 }, // 9 '8'
{ 0x70, 0x88, 0x88, 0x78, 0x08, 0x10, 0x60 }, // 10 '9'
{ 0x70, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88 }, // 11 'A'
{ 0xF0, 0x88, 0x88, 0xF0, 0x88, 0x88, 0xF0 }, // 12 'B'
{ 0x70, 0x88, 0x80, 0x80, 0x80, 0x88, 0x70 }, // 13 'C'
{ 0xF0, 0x88, 0x88, 0x88, 0x88, 0x88, 0xF0 }, // 14 'D'
{ 0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0xF8 }, // 15 'E'
{ 0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0x80 }, // 16 'F'
{ 0x70, 0x88, 0x80, 0xB8, 0x88, 0x88, 0x70 }, // 17 'G'
{ 0x88, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88 }, // 18 'H'
{ 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70 }, // 19 'I'
{ 0x38, 0x10, 0x10, 0x10, 0x90, 0x90, 0x60 }, // 20 'J'
{ 0x88, 0x90, 0xA0, 0xC0, 0xA0, 0x90, 0x88 }, // 21 'K'
{ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF8 }, // 22 'L'
{ 0x88, 0xD8, 0xA8, 0xA8, 0x88, 0x88, 0x88 }, // 23 'M'
{ 0x88, 0xC8, 0xA8, 0x98, 0x88, 0x88, 0x88 }, // 24 'N'
{ 0x70, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70 }, // 25 'O'
{ 0xF0, 0x88, 0x88, 0xF0, 0x80, 0x80, 0x80 }, // 26 'P'
{ 0x70, 0x88, 0x88, 0x88, 0xA8, 0x90, 0x68 }, // 27 'Q'
{ 0xF0, 0x88, 0x88, 0xF0, 0xA0, 0x90, 0x88 }, // 28 'R'
{ 0x70, 0x88, 0x80, 0x70, 0x08, 0x88, 0x70 }, // 29 'S'
{ 0xF8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 }, // 30 'T'
{ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70 }, // 31 'U'
{ 0x88, 0x88, 0x88, 0x88, 0x88, 0x50, 0x20 }, // 32 'V'
{ 0x88, 0x88, 0x88, 0xA8, 0xA8, 0xD8, 0x88 }, // 33 'W'
{ 0x88, 0x88, 0x50, 0x20, 0x50, 0x88, 0x88 }, // 34 'X'
{ 0x88, 0x88, 0x50, 0x20, 0x20, 0x20, 0x20 }, // 35 'Y'
{ 0xF8, 0x08, 0x10, 0x20, 0x40, 0x80, 0xF8 }, // 36 'Z'
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60 }, // 37 '.'
{ 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x40 }, // 38 ','
{ 0x00, 0x60, 0x60, 0x00, 0x60, 0x60, 0x00 }, // 39 ':'
{ 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x20 }, // 40 '!'
};
static int glyphIdx(char c) {
if (c == ' ') {
return 0;
}
if (c >= '0' && c <= '9') {
return 1 + (c - '0');
}
if (c >= 'A' && c <= 'Z') {
return 11 + (c - 'A');
}
if (c >= 'a' && c <= 'z') {
return 11 + (c - 'a');
}
if (c == '.') {
return 37;
}
if (c == ',') {
return 38;
}
if (c == ':') {
return 39;
}
if (c == '!') {
return 40;
}
return 0;
}
static void drawText(int16_t x, int16_t y, const char *text, uint8_t color) {
int16_t cx;
int i;
int idx;
int16_t row;
int16_t col;
uint8_t bits;
cx = x;
for (i = 0; text[i] != '\0'; i++) {
idx = glyphIdx(text[i]);
for (row = 0; row < FONT_H; row++) {
bits = kFontGlyph[idx][row];
for (col = 0; col < FONT_W; col++) {
if (bits & (0x80 >> col)) {
jlDrawPixel(jlgScreen, (int16_t)(cx + col), (int16_t)(y + row), color);
}
}
}
cx = (int16_t)(cx + FONT_W + 1);
if (cx > JL_SURFACE_WIDTH - FONT_W) {
return;
}
}
}
static void setMessage(const char *text) { static void setMessage(const char *text) {
size_t n; size_t n;
n = strlen(text); n = strlen(text);
if (n >= sizeof(gMessage)) { if (n >= sizeof(gMessage)) {
n = sizeof(gMessage) - 1; n = sizeof(gMessage) - 1;
@ -668,7 +571,7 @@ static uint16_t rgb(uint8_t r, uint8_t g, uint8_t b) {
} }
static void buildPalettes(void) { static void buildPalettes(jlSurfaceT *s) {
uint16_t scene[16]; uint16_t scene[16];
uint16_t pri[16]; uint16_t pri[16];
uint16_t ctl[16]; uint16_t ctl[16];
@ -736,18 +639,19 @@ static void buildPalettes(void) {
ui[UI_HILITE] = rgb(15, 13, 3); ui[UI_HILITE] = rgb(15, 13, 3);
ui[UI_DIM] = rgb(7, 7, 8); ui[UI_DIM] = rgb(7, 7, 8);
jlPaletteSet(jlgScreen, PAL_SCENE, scene); jlPaletteSet(s, PAL_SCENE, scene);
jlPaletteSet(jlgScreen, PAL_DEBUG_PRI, pri); jlPaletteSet(s, PAL_DEBUG_PRI, pri);
jlPaletteSet(jlgScreen, PAL_DEBUG_CTRL, ctl); jlPaletteSet(s, PAL_DEBUG_CTRL, ctl);
jlPaletteSet(jlgScreen, PAL_UI, ui); jlPaletteSet(s, PAL_UI, ui);
} }
/* The stage's SCB selects which of the 4 palettes is active on each /* A surface's SCB selects which of the 4 palettes is active on each
* scanline. Normal view uses PAL_SCENE for the play area and PAL_UI * scanline. Normal view uses PAL_SCENE for the play area and PAL_UI
* for the message/inventory bars. Debug views swap PAL_SCENE for * for the message/inventory bars. Debug views swap PAL_SCENE for
* the matching debug palette. */ * the matching debug palette. Each of the three screens carries its
static void programScbForView(DebugViewE view) { * view's, which jlSurfaceCopy brings to the stage with its pixels. */
static void programScbForView(jlSurfaceT *s, DebugViewE view) {
int16_t line; int16_t line;
uint8_t playPal; uint8_t playPal;
@ -763,10 +667,10 @@ static void programScbForView(DebugViewE view) {
break; break;
} }
for (line = 0; line < PLAY_AREA_H; line++) { for (line = 0; line < PLAY_AREA_H; line++) {
jlScbSet(jlgScreen, (uint16_t)line, playPal); jlScbSet(s, (uint16_t)line, playPal);
} }
for (line = PLAY_AREA_H; line < JL_SURFACE_HEIGHT; line++) { for (line = PLAY_AREA_H; line < JL_SURFACE_HEIGHT; line++) {
jlScbSet(jlgScreen, (uint16_t)line, PAL_UI); jlScbSet(s, (uint16_t)line, PAL_UI);
} }
} }
@ -1333,7 +1237,7 @@ static void drawMessageBar(void) {
jlFillRect(jlgScreen, 0, MSG_BAR_Y, JL_SURFACE_WIDTH, MSG_BAR_H, UI_BG); jlFillRect(jlgScreen, 0, MSG_BAR_Y, JL_SURFACE_WIDTH, MSG_BAR_H, UI_BG);
jlFillRect(jlgScreen, 0, MSG_BAR_Y, JL_SURFACE_WIDTH, 1, UI_DIM); jlFillRect(jlgScreen, 0, MSG_BAR_Y, JL_SURFACE_WIDTH, 1, UI_DIM);
if (gMessage[0] != '\0') { if (gMessage[0] != '\0') {
drawText(4, (int16_t)(MSG_BAR_Y + 2), gMessage, UI_INK); exampleFontDraw(jlgScreen, 4, (int16_t)(MSG_BAR_Y + 2), gMessage, UI_INK);
} }
} }
@ -1346,7 +1250,7 @@ static void drawInventoryBar(void) {
jlFillRect(jlgScreen, 0, INV_BAR_Y, JL_SURFACE_WIDTH, 1, UI_DIM); jlFillRect(jlgScreen, 0, INV_BAR_Y, JL_SURFACE_WIDTH, 1, UI_DIM);
snprintf(buf, sizeof(buf), "VERB: %s", verbName(gCurVerb)); snprintf(buf, sizeof(buf), "VERB: %s", verbName(gCurVerb));
drawText(4, (int16_t)(INV_BAR_Y + 2), buf, UI_HILITE); exampleFontDraw(jlgScreen, 4, (int16_t)(INV_BAR_Y + 2), buf, UI_HILITE);
switch (gDebugView) { switch (gDebugView) {
case VIEW_PRIORITY: case VIEW_PRIORITY:
@ -1359,7 +1263,7 @@ static void drawInventoryBar(void) {
vw = "VIEW: NORMAL (P pri, C ctrl)"; vw = "VIEW: NORMAL (P pri, C ctrl)";
break; break;
} }
drawText(4, (int16_t)(INV_BAR_Y + 11), vw, UI_INK); exampleFontDraw(jlgScreen, 4, (int16_t)(INV_BAR_Y + 11), vw, UI_INK);
} }
@ -1525,9 +1429,15 @@ int main(void) {
return 1; return 1;
} }
buildPalettes(); // jlSurfaceCopy brings a screen's palettes and SCB to the stage with
// its pixels, so each carries them.
buildPalettes(gVisual);
buildPalettes(gPriority);
buildPalettes(gControl);
programScbForView(gVisual, VIEW_NORMAL);
programScbForView(gPriority, VIEW_PRIORITY);
programScbForView(gControl, VIEW_CONTROL);
gDebugView = VIEW_NORMAL; gDebugView = VIEW_NORMAL;
programScbForView(gDebugView);
paintForestVisual(); paintForestVisual();
paintForestPriority(); paintForestPriority();
@ -1574,15 +1484,12 @@ int main(void) {
* on every target's standard keyboard. */ * on every target's standard keyboard. */
if (jlKeyPressed(JL_KEY_P)) { if (jlKeyPressed(JL_KEY_P)) {
gDebugView = (gDebugView == VIEW_PRIORITY) ? VIEW_NORMAL : VIEW_PRIORITY; gDebugView = (gDebugView == VIEW_PRIORITY) ? VIEW_NORMAL : VIEW_PRIORITY;
programScbForView(gDebugView);
} }
if (jlKeyPressed(JL_KEY_C)) { if (jlKeyPressed(JL_KEY_C)) {
gDebugView = (gDebugView == VIEW_CONTROL) ? VIEW_NORMAL : VIEW_CONTROL; gDebugView = (gDebugView == VIEW_CONTROL) ? VIEW_NORMAL : VIEW_CONTROL;
programScbForView(gDebugView);
} }
if (jlKeyPressed(JL_KEY_N)) { if (jlKeyPressed(JL_KEY_N)) {
gDebugView = VIEW_NORMAL; gDebugView = VIEW_NORMAL;
programScbForView(gDebugView);
} }
mx = jlMouseX(); mx = jlMouseX();

View file

@ -369,7 +369,7 @@ int main(void) {
} }
if (keyPressed(JL_KEY_N, frame) && !noiseOn) { if (keyPressed(JL_KEY_N, frame) && !noiseOn) {
// The song's own drums would fight the sweep for the noise // The song's own drums would fight the sweep for the noise
// generator, so it pauses and picks up again afterwards. // generator, so it pauses and picks up again afterward.
noiseResumes = jlMusicIsPlaying() && !musicPaused; noiseResumes = jlMusicIsPlaying() && !musicPaused;
jlMusicPause(); jlMusicPause();
noiseOn = true; noiseOn = true;

View file

@ -0,0 +1,122 @@
// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
// See LICENSE.md for license information.
// SPDX-License-Identifier: MIT
//
// exampleFont.c - the examples' 5x7 font (exampleFont.h). Each glyph is seven
// rows, the left pixel in each row's top bit.
#include <string.h>
#include "exampleFont.h"
#define GLYPH_SPACE 0
#define GLYPH_DIGIT_BASE 1
#define GLYPH_ALPHA_BASE 11
#define GLYPH_PUNCT_BASE 37
#define GLYPH_UNKNOWN 52
#define GLYPH_TOP_BIT 0x80u
static uint8_t glyphIndex(char c);
// The punctuation the font draws, in the order of its glyphs from
// GLYPH_PUNCT_BASE.
static const char kPunctuation[] = ".,:!?-'\"/;=+_()";
static const uint8_t kGlyphs[][EXAMPLE_FONT_H] = {
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 0 space
{ 0x70, 0x88, 0x98, 0xA8, 0xC8, 0x88, 0x70 }, // 1 '0'
{ 0x20, 0x60, 0x20, 0x20, 0x20, 0x20, 0x70 }, // 2 '1'
{ 0x70, 0x88, 0x08, 0x10, 0x20, 0x40, 0xF8 }, // 3 '2'
{ 0x70, 0x88, 0x08, 0x30, 0x08, 0x88, 0x70 }, // 4 '3'
{ 0x10, 0x30, 0x50, 0x90, 0xF8, 0x10, 0x10 }, // 5 '4'
{ 0xF8, 0x80, 0xF0, 0x08, 0x08, 0x88, 0x70 }, // 6 '5'
{ 0x30, 0x40, 0x80, 0xF0, 0x88, 0x88, 0x70 }, // 7 '6'
{ 0xF8, 0x08, 0x10, 0x20, 0x40, 0x40, 0x40 }, // 8 '7'
{ 0x70, 0x88, 0x88, 0x70, 0x88, 0x88, 0x70 }, // 9 '8'
{ 0x70, 0x88, 0x88, 0x78, 0x08, 0x10, 0x60 }, // 10 '9'
{ 0x70, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88 }, // 11 'A'
{ 0xF0, 0x88, 0x88, 0xF0, 0x88, 0x88, 0xF0 }, // 12 'B'
{ 0x70, 0x88, 0x80, 0x80, 0x80, 0x88, 0x70 }, // 13 'C'
{ 0xF0, 0x88, 0x88, 0x88, 0x88, 0x88, 0xF0 }, // 14 'D'
{ 0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0xF8 }, // 15 'E'
{ 0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0x80 }, // 16 'F'
{ 0x70, 0x88, 0x80, 0xB8, 0x88, 0x88, 0x70 }, // 17 'G'
{ 0x88, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88 }, // 18 'H'
{ 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70 }, // 19 'I'
{ 0x38, 0x10, 0x10, 0x10, 0x90, 0x90, 0x60 }, // 20 'J'
{ 0x88, 0x90, 0xA0, 0xC0, 0xA0, 0x90, 0x88 }, // 21 'K'
{ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF8 }, // 22 'L'
{ 0x88, 0xD8, 0xA8, 0xA8, 0x88, 0x88, 0x88 }, // 23 'M'
{ 0x88, 0xC8, 0xA8, 0x98, 0x88, 0x88, 0x88 }, // 24 'N'
{ 0x70, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70 }, // 25 'O'
{ 0xF0, 0x88, 0x88, 0xF0, 0x80, 0x80, 0x80 }, // 26 'P'
{ 0x70, 0x88, 0x88, 0x88, 0xA8, 0x90, 0x68 }, // 27 'Q'
{ 0xF0, 0x88, 0x88, 0xF0, 0xA0, 0x90, 0x88 }, // 28 'R'
{ 0x70, 0x88, 0x80, 0x70, 0x08, 0x88, 0x70 }, // 29 'S'
{ 0xF8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 }, // 30 'T'
{ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70 }, // 31 'U'
{ 0x88, 0x88, 0x88, 0x88, 0x88, 0x50, 0x20 }, // 32 'V'
{ 0x88, 0x88, 0x88, 0xA8, 0xA8, 0xD8, 0x88 }, // 33 'W'
{ 0x88, 0x88, 0x50, 0x20, 0x50, 0x88, 0x88 }, // 34 'X'
{ 0x88, 0x88, 0x50, 0x20, 0x20, 0x20, 0x20 }, // 35 'Y'
{ 0xF8, 0x08, 0x10, 0x20, 0x40, 0x80, 0xF8 }, // 36 'Z'
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60 }, // 37 '.'
{ 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x40 }, // 38 ','
{ 0x00, 0x60, 0x60, 0x00, 0x60, 0x60, 0x00 }, // 39 ':'
{ 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x20 }, // 40 '!'
{ 0x70, 0x88, 0x08, 0x10, 0x20, 0x00, 0x20 }, // 41 '?'
{ 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00 }, // 42 '-'
{ 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 43 '\''
{ 0x50, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 44 '"'
{ 0x00, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00 }, // 45 '/'
{ 0x00, 0x60, 0x60, 0x00, 0x60, 0x60, 0x40 }, // 46 ';'
{ 0x00, 0x00, 0xF8, 0x00, 0xF8, 0x00, 0x00 }, // 47 '='
{ 0x00, 0x20, 0x20, 0xF8, 0x20, 0x20, 0x00 }, // 48 '+'
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8 }, // 49 '_'
{ 0x10, 0x20, 0x40, 0x40, 0x40, 0x20, 0x10 }, // 50 '('
{ 0x40, 0x20, 0x10, 0x10, 0x10, 0x20, 0x40 }, // 51 ')'
{ 0xF8, 0x88, 0x88, 0x88, 0x88, 0x88, 0xF8 } // 52 unknown box
};
// The glyph that draws c.
static uint8_t glyphIndex(char c) {
const char *punct;
if (c == ' ') {
return GLYPH_SPACE;
}
if (c >= '0' && c <= '9') {
return (uint8_t)(GLYPH_DIGIT_BASE + (c - '0'));
}
if (c >= 'A' && c <= 'Z') {
return (uint8_t)(GLYPH_ALPHA_BASE + (c - 'A'));
}
if (c >= 'a' && c <= 'z') {
return (uint8_t)(GLYPH_ALPHA_BASE + (c - 'a'));
}
punct = strchr(kPunctuation, c);
if (c != '\0' && punct != NULL) {
return (uint8_t)(GLYPH_PUNCT_BASE + (punct - kPunctuation));
}
return GLYPH_UNKNOWN;
}
void exampleFontDraw(jlSurfaceT *s, int16_t x, int16_t y, const char *text, uint8_t color) {
const uint8_t *glyph;
int16_t row;
int16_t col;
for (; *text != '\0' && x <= JL_SURFACE_WIDTH - EXAMPLE_FONT_W; text++) {
glyph = kGlyphs[glyphIndex(*text)];
for (row = 0; row < EXAMPLE_FONT_H; row++) {
for (col = 0; col < EXAMPLE_FONT_W; col++) {
if ((glyph[row] & (GLYPH_TOP_BIT >> col)) != 0u) {
jlDrawPixel(s, (int16_t)(x + col), (int16_t)(y + row), color);
}
}
}
x = (int16_t)(x + EXAMPLE_FONT_ADVANCE);
}
}

View file

@ -0,0 +1,23 @@
// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
// See LICENSE.md for license information.
// SPDX-License-Identifier: MIT
//
// exampleFont.h - the examples' 5x7 font. JoeyLib draws no text of its own,
// so the examples that show words share this one: the digits, the letters
// (lower case drawn as upper case), and . , : ! ? - ' " / ; = + _ ( ),
// each on a 6-pixel advance. Any other character is drawn as a box.
#ifndef EXAMPLE_FONT_HEADER
#define EXAMPLE_FONT_HEADER
#include <joey/joey.h>
#define EXAMPLE_FONT_W 5
#define EXAMPLE_FONT_H 7
#define EXAMPLE_FONT_ADVANCE (EXAMPLE_FONT_W + 1)
// Draw text on s with its top-left corner at (x, y), in color; it stops at
// the surface's right edge.
void exampleFontDraw(jlSurfaceT *s, int16_t x, int16_t y, const char *text, uint8_t color);
#endif

View file

@ -2,28 +2,57 @@
// See LICENSE.md for license information. // See LICENSE.md for license information.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// JoeyLib hello-world example. // JoeyLib hello-world example: the smallest program that shows something. It
// // starts the library, writes a greeting, the library's version, and the
// Validates that the C API headers, library, and per-platform link // machine it runs on on the stage in the default palette (no jlPaletteSet:
// path all work end-to-end. // every surface starts with the 16 EGA colors), presents it, says the same on
// the console where there is one, and waits for a key.
#include <stdio.h> #include <stdio.h>
#include <joey/joey.h> #include <joey/joey.h>
#include "exampleFont.h"
// No sprites, so the smallest compiled-sprite cache.
#define CODEGEN_BYTES ((uint32_t)8 * 1024)
// The lines, each a label and its value, and where they go: the greeting
// in yellow, the version and the platform in white below it, the prompt in
// light gray.
#define TEXT_X 64
#define GREETING_Y 72
#define VERSION_Y 96
#define PLATFORM_Y 108
#define PROMPT_Y 132
#define VALUE_X (TEXT_X + 10 * EXAMPLE_FONT_ADVANCE)
#define COLOR_YELLOW 14
#define COLOR_WHITE 15
#define COLOR_GRAY 7
int main(void) { int main(void) {
jlConfigT config; jlConfigT config;
jlSurfaceT *stage;
config.codegenBytes = 8 * 1024; config.codegenBytes = CODEGEN_BYTES;
config.noAudio = true; config.noAudio = true;
if (!jlInit(&config)) { if (!jlInit(&config)) {
fprintf(stderr, "jlInit failed: %s\n", jlLastError()); fprintf(stderr, "jlInit failed: %s\n", jlLastError());
return 1; return 1;
} }
printf("JoeyLib %s\n", jlVersionString()); stage = jlStageGet();
printf("Platform: %s\n", jlPlatformName()); exampleFontDraw(stage, TEXT_X, GREETING_Y, "Hello from JoeyLib!", COLOR_YELLOW);
printf("Hello from JoeyLib.\n"); exampleFontDraw(stage, TEXT_X, VERSION_Y, "Version:", COLOR_WHITE);
exampleFontDraw(stage, VALUE_X, VERSION_Y, jlVersionString(), COLOR_WHITE);
exampleFontDraw(stage, TEXT_X, PLATFORM_Y, "Platform:", COLOR_WHITE);
exampleFontDraw(stage, VALUE_X, PLATFORM_Y, jlPlatformName(), COLOR_WHITE);
exampleFontDraw(stage, TEXT_X, PROMPT_Y, "Press any key.", COLOR_GRAY);
jlStagePresent();
// The same on the console, where there is one (scripts/verify-ste-machine.sh
// reads it through Hatari).
printf("JoeyLib %s\nPlatform: %s\n", jlVersionString(), jlPlatformName());
jlWaitForAnyKey();
jlShutdown(); jlShutdown();
return 0; return 0;

View file

@ -31,6 +31,8 @@
#include <joey/joey.h> #include <joey/joey.h>
#include <joey/debug.h> #include <joey/debug.h>
#include "exampleFont.h"
#define GRID_COLS 10 #define GRID_COLS 10
#define GRID_ROWS 6 #define GRID_ROWS 6
#define CELL_W 28 #define CELL_W 28
@ -51,8 +53,6 @@
// Typed-text line under the grid (the grid ends at y = 178 with the // Typed-text line under the grid (the grid ends at y = 178 with the
// 26-pixel cells above). // 26-pixel cells above).
#define FONT_W 5
#define FONT_H 7
#define TEXT_STRIP_Y 180 #define TEXT_STRIP_Y 180
#define TEXT_X 4 #define TEXT_X 4
#define TEXT_Y 182 #define TEXT_Y 182
@ -75,8 +75,8 @@
#define VERIFY_WORD_NIBBLES 4 #define VERIFY_WORD_NIBBLES 4
// Mouse verification scanline (the row below the typed-character one): // Mouse verification scanline (the row below the typed-character one):
// its own 5A5A sentinel, then present/left/right/middle as one nibble // its own 5A5A sentinel, then present/left/right as one nibble each and a
// each, then jlMouseX and jlMouseY as four nibbles each. // zero nibble, then jlMouseX and jlMouseY as four nibbles each.
// Re-stamped only when the reported state changes. // Re-stamped only when the reported state changes.
#define VERIFY_MOUSE_Y 199 #define VERIFY_MOUSE_Y 199
#define VERIFY_MOUSE_SENTINEL 0x5A5A #define VERIFY_MOUSE_SENTINEL 0x5A5A
@ -90,7 +90,6 @@
#define MOUSE_FLAG_PRESENT 0x1000 #define MOUSE_FLAG_PRESENT 0x1000
#define MOUSE_FLAG_LEFT 0x0100 #define MOUSE_FLAG_LEFT 0x0100
#define MOUSE_FLAG_RIGHT 0x0010 #define MOUSE_FLAG_RIGHT 0x0010
#define MOUSE_FLAG_MIDDLE 0x0001
static void buildPalette(jlSurfaceT *screen); static void buildPalette(jlSurfaceT *screen);
static void cellAtPoint(int16_t px, int16_t py, int16_t *outCol, int16_t *outRow); static void cellAtPoint(int16_t px, int16_t py, int16_t *outCol, int16_t *outRow);
@ -98,9 +97,7 @@ static bool cellTargetLit(int16_t col, int16_t row, int16_t cursorCol, int16_t c
static void drawCell(jlSurfaceT *screen, int16_t col, int16_t row, bool lit); static void drawCell(jlSurfaceT *screen, int16_t col, int16_t row, bool lit);
static void drawCursor(jlSurfaceT *screen, int16_t x, int16_t y); static void drawCursor(jlSurfaceT *screen, int16_t x, int16_t y);
static void drawMouseVerifyRow(jlSurfaceT *screen); static void drawMouseVerifyRow(jlSurfaceT *screen);
static void drawTextLine(jlSurfaceT *screen, int16_t x, int16_t y, const char *text, uint8_t color);
static void drawVerifyRow(jlSurfaceT *screen); static void drawVerifyRow(jlSurfaceT *screen);
static int glyphIdx(char c);
static void initialPaint(jlSurfaceT *screen); static void initialPaint(jlSurfaceT *screen);
static void logTypedHistory(void); static void logTypedHistory(void);
static uint16_t mouseFlags(void); static uint16_t mouseFlags(void);
@ -142,86 +139,6 @@ static int16_t gLineLen = 0;
static uint8_t gReceived[MAX_RECEIVED]; static uint8_t gReceived[MAX_RECEIVED];
static int16_t gReceivedCount = 0; static int16_t gReceivedCount = 0;
// Tiny 5x7 font (high 5 bits of each row byte), borrowed from the
// adventure example and extended with a few typed-punctuation glyphs.
// Coverage: space, 0-9, A-Z (lowercase renders uppercase), and
// . , : ! ? - ' " / ; = + _ -- everything else renders as the hollow
// box at the end of the table.
//
// Row indices used by glyphIdx. The table below must keep this order;
// the trailing row comments carry the same numbers.
#define GLYPH_SPACE 0
#define GLYPH_DIGIT_BASE 1
#define GLYPH_ALPHA_BASE 11
#define GLYPH_PERIOD 37
#define GLYPH_COMMA 38
#define GLYPH_COLON 39
#define GLYPH_BANG 40
#define GLYPH_QUESTION 41
#define GLYPH_MINUS 42
#define GLYPH_APOSTROPHE 43
#define GLYPH_QUOTE 44
#define GLYPH_SLASH 45
#define GLYPH_SEMICOLON 46
#define GLYPH_EQUALS 47
#define GLYPH_PLUS 48
#define GLYPH_UNDERSCORE 49
#define GLYPH_UNKNOWN 50
static const uint8_t kFontGlyph[][FONT_H] = {
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 0 space
{ 0x70, 0x88, 0x98, 0xA8, 0xC8, 0x88, 0x70 }, // 1 '0'
{ 0x20, 0x60, 0x20, 0x20, 0x20, 0x20, 0x70 }, // 2 '1'
{ 0x70, 0x88, 0x08, 0x10, 0x20, 0x40, 0xF8 }, // 3 '2'
{ 0x70, 0x88, 0x08, 0x30, 0x08, 0x88, 0x70 }, // 4 '3'
{ 0x10, 0x30, 0x50, 0x90, 0xF8, 0x10, 0x10 }, // 5 '4'
{ 0xF8, 0x80, 0xF0, 0x08, 0x08, 0x88, 0x70 }, // 6 '5'
{ 0x30, 0x40, 0x80, 0xF0, 0x88, 0x88, 0x70 }, // 7 '6'
{ 0xF8, 0x08, 0x10, 0x20, 0x40, 0x40, 0x40 }, // 8 '7'
{ 0x70, 0x88, 0x88, 0x70, 0x88, 0x88, 0x70 }, // 9 '8'
{ 0x70, 0x88, 0x88, 0x78, 0x08, 0x10, 0x60 }, // 10 '9'
{ 0x70, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88 }, // 11 'A'
{ 0xF0, 0x88, 0x88, 0xF0, 0x88, 0x88, 0xF0 }, // 12 'B'
{ 0x70, 0x88, 0x80, 0x80, 0x80, 0x88, 0x70 }, // 13 'C'
{ 0xF0, 0x88, 0x88, 0x88, 0x88, 0x88, 0xF0 }, // 14 'D'
{ 0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0xF8 }, // 15 'E'
{ 0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0x80 }, // 16 'F'
{ 0x70, 0x88, 0x80, 0xB8, 0x88, 0x88, 0x70 }, // 17 'G'
{ 0x88, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88 }, // 18 'H'
{ 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70 }, // 19 'I'
{ 0x38, 0x10, 0x10, 0x10, 0x90, 0x90, 0x60 }, // 20 'J'
{ 0x88, 0x90, 0xA0, 0xC0, 0xA0, 0x90, 0x88 }, // 21 'K'
{ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF8 }, // 22 'L'
{ 0x88, 0xD8, 0xA8, 0xA8, 0x88, 0x88, 0x88 }, // 23 'M'
{ 0x88, 0xC8, 0xA8, 0x98, 0x88, 0x88, 0x88 }, // 24 'N'
{ 0x70, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70 }, // 25 'O'
{ 0xF0, 0x88, 0x88, 0xF0, 0x80, 0x80, 0x80 }, // 26 'P'
{ 0x70, 0x88, 0x88, 0x88, 0xA8, 0x90, 0x68 }, // 27 'Q'
{ 0xF0, 0x88, 0x88, 0xF0, 0xA0, 0x90, 0x88 }, // 28 'R'
{ 0x70, 0x88, 0x80, 0x70, 0x08, 0x88, 0x70 }, // 29 'S'
{ 0xF8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 }, // 30 'T'
{ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70 }, // 31 'U'
{ 0x88, 0x88, 0x88, 0x88, 0x88, 0x50, 0x20 }, // 32 'V'
{ 0x88, 0x88, 0x88, 0xA8, 0xA8, 0xD8, 0x88 }, // 33 'W'
{ 0x88, 0x88, 0x50, 0x20, 0x50, 0x88, 0x88 }, // 34 'X'
{ 0x88, 0x88, 0x50, 0x20, 0x20, 0x20, 0x20 }, // 35 'Y'
{ 0xF8, 0x08, 0x10, 0x20, 0x40, 0x80, 0xF8 }, // 36 'Z'
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60 }, // 37 '.'
{ 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x40 }, // 38 ','
{ 0x00, 0x60, 0x60, 0x00, 0x60, 0x60, 0x00 }, // 39 ':'
{ 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x20 }, // 40 '!'
{ 0x70, 0x88, 0x08, 0x10, 0x20, 0x00, 0x20 }, // 41 '?'
{ 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00 }, // 42 '-'
{ 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 43 '\''
{ 0x50, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 44 '"'
{ 0x00, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00 }, // 45 '/'
{ 0x00, 0x60, 0x60, 0x00, 0x60, 0x60, 0x40 }, // 46 ';'
{ 0x00, 0x00, 0xF8, 0x00, 0xF8, 0x00, 0x00 }, // 47 '='
{ 0x00, 0x20, 0x20, 0xF8, 0x20, 0x20, 0x00 }, // 48 '+'
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8 }, // 49 '_'
{ 0xF8, 0x88, 0x88, 0x88, 0x88, 0x88, 0xF8 }, // 50 unknown box
};
static void buildPalette(jlSurfaceT *screen) { static void buildPalette(jlSurfaceT *screen) {
uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE]; uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE];
@ -326,33 +243,6 @@ static void drawMouseVerifyRow(jlSurfaceT *screen) {
} }
static void drawTextLine(jlSurfaceT *screen, int16_t x, int16_t y, const char *text, uint8_t color) {
int16_t cx;
int16_t i;
int16_t row;
int16_t col;
int idx;
uint8_t bits;
cx = x;
for (i = 0; text[i] != '\0'; i++) {
idx = glyphIdx(text[i]);
for (row = 0; row < FONT_H; row++) {
bits = kFontGlyph[idx][row];
for (col = 0; col < FONT_W; col++) {
if (bits & (0x80 >> col)) {
jlDrawPixel(screen, (int16_t)(cx + col), (int16_t)(y + row), color);
}
}
}
cx = (int16_t)(cx + FONT_W + 1);
if (cx > JL_SURFACE_WIDTH - FONT_W) {
return;
}
}
}
// Re-stamp the verification scanline: sentinel, received count, then // Re-stamp the verification scanline: sentinel, received count, then
// every received character as two nibble pixels. Only the count and // every received character as two nibble pixels. Only the count and
// the newest characters actually change, but the row is cheap enough // the newest characters actually change, but the row is cheap enough
@ -371,62 +261,6 @@ static void drawVerifyRow(jlSurfaceT *screen) {
} }
static int glyphIdx(char c) {
if (c == ' ') {
return GLYPH_SPACE;
}
if (c >= '0' && c <= '9') {
return GLYPH_DIGIT_BASE + (c - '0');
}
if (c >= 'A' && c <= 'Z') {
return GLYPH_ALPHA_BASE + (c - 'A');
}
if (c >= 'a' && c <= 'z') {
return GLYPH_ALPHA_BASE + (c - 'a'); // uppercase fallback
}
if (c == '.') {
return GLYPH_PERIOD;
}
if (c == ',') {
return GLYPH_COMMA;
}
if (c == ':') {
return GLYPH_COLON;
}
if (c == '!') {
return GLYPH_BANG;
}
if (c == '?') {
return GLYPH_QUESTION;
}
if (c == '-') {
return GLYPH_MINUS;
}
if (c == '\'') {
return GLYPH_APOSTROPHE;
}
if (c == '"') {
return GLYPH_QUOTE;
}
if (c == '/') {
return GLYPH_SLASH;
}
if (c == ';') {
return GLYPH_SEMICOLON;
}
if (c == '=') {
return GLYPH_EQUALS;
}
if (c == '+') {
return GLYPH_PLUS;
}
if (c == '_') {
return GLYPH_UNDERSCORE;
}
return GLYPH_UNKNOWN;
}
static void initialPaint(jlSurfaceT *screen) { static void initialPaint(jlSurfaceT *screen) {
int16_t col; int16_t col;
int16_t row; int16_t row;
@ -473,7 +307,7 @@ static void logTypedHistory(void) {
// drawMouseVerifyRow stamps them. Sampled once per use so the drawn // drawMouseVerifyRow stamps them. Sampled once per use so the drawn
// row and updateMouseVerifyRow's change check can never disagree. // row and updateMouseVerifyRow's change check can never disagree.
static uint16_t mouseFlags(void) { static uint16_t mouseFlags(void) {
return (uint16_t)((jlMousePresent() ? MOUSE_FLAG_PRESENT : 0) | (jlMouseDown(JL_MOUSE_BUTTON_LEFT) ? MOUSE_FLAG_LEFT : 0) | (jlMouseDown(JL_MOUSE_BUTTON_RIGHT) ? MOUSE_FLAG_RIGHT : 0) | (jlMouseDown(JL_MOUSE_BUTTON_MIDDLE) ? MOUSE_FLAG_MIDDLE : 0)); return (uint16_t)((jlMousePresent() ? MOUSE_FLAG_PRESENT : 0) | (jlMouseDown(JL_MOUSE_BUTTON_LEFT) ? MOUSE_FLAG_LEFT : 0) | (jlMouseDown(JL_MOUSE_BUTTON_RIGHT) ? MOUSE_FLAG_RIGHT : 0));
} }
@ -545,8 +379,8 @@ static void processTypedChars(jlSurfaceT *screen) {
static void redrawTextStrip(jlSurfaceT *screen) { static void redrawTextStrip(jlSurfaceT *screen) {
gLine[gLineLen] = '\0'; gLine[gLineLen] = '\0';
jlFillRect(screen, 0, TEXT_STRIP_Y, JL_SURFACE_WIDTH, JL_SURFACE_HEIGHT - TEXT_STRIP_Y, COLOR_BACKGROUND); jlFillRect(screen, 0, TEXT_STRIP_Y, JL_SURFACE_WIDTH, JL_SURFACE_HEIGHT - TEXT_STRIP_Y, COLOR_BACKGROUND);
drawTextLine(screen, TEXT_X, TEXT_Y, "TYPE HERE:", COLOR_UNLIT); exampleFontDraw(screen, TEXT_X, TEXT_Y, "TYPE HERE:", COLOR_UNLIT);
drawTextLine(screen, TEXT_INPUT_X, TEXT_Y, gLine, COLOR_CURSOR); exampleFontDraw(screen, TEXT_INPUT_X, TEXT_Y, gLine, COLOR_CURSOR);
drawVerifyRow(screen); drawVerifyRow(screen);
} }

View file

@ -8,9 +8,9 @@
// //
// It times, over PROBE_FRAMES frames each, a palette change followed by a // It times, over PROBE_FRAMES frames each, a palette change followed by a
// present, and an SCB change followed by a present, on a screen of four SCB // present, and an SCB change followed by a present, on a screen of four SCB
// bands, and logs the rates. Then it paints the bands (colour 1 of palettes // bands, and logs the rates. Then it paints the bands (color 1 of palettes
// 0..3 red, green, blue, white), makes one palette change (palette 2's // 0..3 red, green, blue, white), makes one palette change (palette 2's
// colour 1 to yellow) and one SCB change that keeps every band where it was // color 1 to yellow) and one SCB change that keeps every band where it was
// (band 3 to palette 0), logs "PALPROBE: ready" and holds the screen for // (band 3 to palette 0), logs "PALPROBE: ready" and holds the screen for
// HOLD_FRAMES frames, so a gate can look at it: top to bottom red, green, // HOLD_FRAMES frames, so a gate can look at it: top to bottom red, green,
// yellow, red. scripts/verify-amiga-palette.sh is that gate. // yellow, red. scripts/verify-amiga-palette.sh is that gate.

View file

@ -3,19 +3,19 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// //
// SCB stress screen: the per-scanline palette under the heaviest load a // SCB stress screen: the per-scanline palette under the heaviest load a
// program can reasonably put on it. Written for the ports that honour the SCB // program can reasonably put on it. Written for the ports that honor the SCB
// by reloading ONE hardware palette as the beam scans (the X68000's text-plane // by reloading ONE hardware palette as the beam scans (the X68000's text-plane
// present, the Atari ST's Timer B), where every palette change is a deadline // present, the Atari ST's Timer B), where every palette change is a deadline
// an interrupt has to meet. Portable C only: nothing here knows which port it // an interrupt has to meet. Portable C only: nothing here knows which port it
// runs on. // runs on.
// //
// All sixteen palettes are in use, and colour i of each palette differs from // All sixteen palettes are in use, and color i of each palette differs from
// colour i of every other palette, so a line shown in the wrong palette shows // color i of every other palette, so a line shown in the wrong palette shows
// the wrong colour everywhere but in colour 0 (black in every palette by // the wrong color everywhere but in color 0 (black in every palette by
// library contract). Twenty 16-pixel vertical stripes run the full height, // library contract). Twenty 16-pixel vertical stripes run the full height,
// stripe s in colour s mod 16, so every line uses every colour. // stripe s in color s mod 16, so every line uses every color.
// //
// Colour i of palette p is $0RGB with red = p, green = i and // Color i of palette p is $0RGB with red = p, green = i and
// blue = (p * 7 + i) mod 16. // blue = (p * 7 + i) mod 16.
// //
// The SCB, in three zones: // The SCB, in three zones:
@ -32,7 +32,7 @@
// //
// Every pass of the loop redraws and presents the whole stage, so the palette // Every pass of the loop redraws and presents the whole stage, so the palette
// reloads land while the present is streaming into video memory, and sets all // reloads land while the present is streaming into video memory, and sets all
// sixteen palettes again -- to the same colours, so the picture never changes // sixteen palettes again -- to the same colors, so the picture never changes
// -- so a port that keeps per-band state rebuilds it every pass while the // -- so a port that keeps per-band state rebuilds it every pass while the
// frame it describes is on screen. Typed characters are read and dropped, so a // frame it describes is on screen. Typed characters are read and dropped, so a
// gate can keep the keyboard interrupt busy during the check; ESC exits. // gate can keep the keyboard interrupt busy during the check; ESC exits.

View file

@ -39,11 +39,11 @@
#define COLOR_GRASS 3 #define COLOR_GRASS 3
#define COLOR_DIRT_DARK 4 #define COLOR_DIRT_DARK 4
#define COLOR_BRICK 5 #define COLOR_BRICK 5
#define COLOR_GREY 6 #define COLOR_GRAY 6
#define COLOR_WATER 7 #define COLOR_WATER 7
#define COLOR_FOAM 8 #define COLOR_FOAM 8
#define COLOR_CLOUD 9 #define COLOR_CLOUD 9
#define COLOR_GREY_DARK 10 #define COLOR_GRAY_DARK 10
#define COLOR_PETAL 11 #define COLOR_PETAL 11
#define COLOR_BALL 12 #define COLOR_BALL 12
#define COLOR_SHINE 13 #define COLOR_SHINE 13
@ -81,7 +81,7 @@ static const uint16_t gPalette[16] = {
0x8CF, 0xFFF, 0x555, 0xFC2, 0xFD0, 0xFFF, 0x113, 0x4E4 0x8CF, 0xFFF, 0x555, 0xFC2, 0xFD0, 0xFFF, 0x113, 0x4E4
}; };
// The ball, 16x16 at 4 bits a pixel, high nibble left; colour 0 is clear. // The ball, 16x16 at 4 bits a pixel, high nibble left; color 0 is clear.
static const uint8_t gBallRows[BALL_SIDE * BALL_SIDE / 2] = { static const uint8_t gBallRows[BALL_SIDE * BALL_SIDE / 2] = {
0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, 0x00,
0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x00,
@ -205,20 +205,20 @@ static void makeTiles(jlSurfaceT *screen) {
jlDrawPixel(screen, 17, 2, COLOR_DIRT_DARK); jlDrawPixel(screen, 17, 2, COLOR_DIRT_DARK);
jlDrawPixel(screen, 21, 5, COLOR_DIRT_DARK); jlDrawPixel(screen, 21, 5, COLOR_DIRT_DARK);
jlFillRect(screen, 24, 0, 8, 8, COLOR_BRICK); jlFillRect(screen, 24, 0, 8, 8, COLOR_BRICK);
jlDrawLine(screen, 24, 3, 31, 3, COLOR_GREY); jlDrawLine(screen, 24, 3, 31, 3, COLOR_GRAY);
jlDrawLine(screen, 24, 7, 31, 7, COLOR_GREY); jlDrawLine(screen, 24, 7, 31, 7, COLOR_GRAY);
jlDrawLine(screen, 24, 0, 24, 2, COLOR_GREY); jlDrawLine(screen, 24, 0, 24, 2, COLOR_GRAY);
jlDrawLine(screen, 28, 4, 28, 6, COLOR_GREY); jlDrawLine(screen, 28, 4, 28, 6, COLOR_GRAY);
jlFillRect(screen, 32, 0, 8, 8, COLOR_WATER); jlFillRect(screen, 32, 0, 8, 8, COLOR_WATER);
jlDrawLine(screen, 32, 1, 34, 1, COLOR_FOAM); jlDrawLine(screen, 32, 1, 34, 1, COLOR_FOAM);
jlDrawLine(screen, 36, 5, 38, 5, COLOR_FOAM); jlDrawLine(screen, 36, 5, 38, 5, COLOR_FOAM);
jlFillRect(screen, 40, 0, 8, 8, COLOR_SKY); jlFillRect(screen, 40, 0, 8, 8, COLOR_SKY);
jlFillRect(screen, 41, 3, 6, 3, COLOR_CLOUD); jlFillRect(screen, 41, 3, 6, 3, COLOR_CLOUD);
jlFillRect(screen, 43, 2, 3, 1, COLOR_CLOUD); jlFillRect(screen, 43, 2, 3, 1, COLOR_CLOUD);
jlFillRect(screen, 48, 0, 8, 8, COLOR_GREY); jlFillRect(screen, 48, 0, 8, 8, COLOR_GRAY);
jlDrawPixel(screen, 50, 2, COLOR_GREY_DARK); jlDrawPixel(screen, 50, 2, COLOR_GRAY_DARK);
jlDrawPixel(screen, 53, 5, COLOR_GREY_DARK); jlDrawPixel(screen, 53, 5, COLOR_GRAY_DARK);
jlDrawPixel(screen, 49, 6, COLOR_GREY_DARK); jlDrawPixel(screen, 49, 6, COLOR_GRAY_DARK);
jlFillRect(screen, 56, 0, 8, 8, COLOR_SKY); jlFillRect(screen, 56, 0, 8, 8, COLOR_SKY);
jlDrawLine(screen, 59, 3, 59, 7, COLOR_GRASS); jlDrawLine(screen, 59, 3, 59, 7, COLOR_GRASS);
jlFillRect(screen, 58, 1, 3, 2, COLOR_PETAL); jlFillRect(screen, 58, 1, 3, 2, COLOR_PETAL);

View file

@ -27,8 +27,11 @@
// //
// Mouse: a small pointer follows it. // Mouse: a small pointer follows it.
// //
// A panel at the top says the same in words, for whoever runs it: the line,
// the bytes each way, and the mouse, redrawn every STATUS_FRAMES frames.
//
// Built with -DDENSE_SCB_LINES=n, stage lines 0 .. n-1 alternate between two // Built with -DDENSE_SCB_LINES=n, stage lines 0 .. n-1 alternate between two
// palettes, a palette change on every one of them. A port that honours the SCB // palettes, a palette change on every one of them. A port that honors the SCB
// by reloading its one hardware palette as the beam scans (the X68000's raster // by reloading its one hardware palette as the beam scans (the X68000's raster
// interrupt) then holds that interrupt for the whole run of lines every frame, // interrupt) then holds that interrupt for the whole run of lines every frame,
// and the serial checks show whether receive survives it. The default, 0, is // and the serial checks show whether receive survives it. The default, 0, is
@ -38,7 +41,7 @@
// high nibble first, behind a sentinel, for a harness that reads the // high nibble first, behind a sentinel, for a harness that reads the
// framebuffer back: // framebuffer back:
// row 198 serial: 3C3C, status, received, bad, sent, short writes, first bad // 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) // row 199 mouse: 5A5A, present/left/right/0, x, y (examples/keys' row)
// The serial row is restamped at most every SERIAL_ROW_FRAMES frames, so a // 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 // 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. // catch a present half done. The mouse row changes only with the mouse.
@ -46,9 +49,13 @@
// Serial is an OPT-IN add-on: it is reached through <joey/serial.h>, which the // 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. // umbrella <joey/joey.h> deliberately does NOT pull in.
#include <stdio.h>
#include <joey/joey.h> #include <joey/joey.h>
#include <joey/serial.h> #include <joey/serial.h>
#include "exampleFont.h"
#ifndef LINE_BAUD #ifndef LINE_BAUD
#define LINE_BAUD 9600u #define LINE_BAUD 9600u
#endif #endif
@ -91,11 +98,11 @@
#define MOUSE_XPOS_X (MOUSE_FLAGS_X + WORD_NIBBLES) #define MOUSE_XPOS_X (MOUSE_FLAGS_X + WORD_NIBBLES)
#define MOUSE_YPOS_X (MOUSE_XPOS_X + WORD_NIBBLES) #define MOUSE_YPOS_X (MOUSE_XPOS_X + WORD_NIBBLES)
// The mouse flags word, one nibble per predicate, in examples/keys' order. // The mouse flags word, one nibble per predicate (the last always 0), in
// examples/keys' order.
#define MOUSE_FLAG_PRESENT 0x1000u #define MOUSE_FLAG_PRESENT 0x1000u
#define MOUSE_FLAG_LEFT 0x0100u #define MOUSE_FLAG_LEFT 0x0100u
#define MOUSE_FLAG_RIGHT 0x0010u #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 MOUSE_FLAGS_NONE 0xFFFFu // never a real flags word: forces the first stamp
#define POINTER_SIZE 4 #define POINTER_SIZE 4
@ -103,9 +110,24 @@
#define COLOR_POINTER 15 #define COLOR_POINTER 15
#define PALETTE_RED_SHIFT 8 #define PALETTE_RED_SHIFT 8
#define PALETTE_GREEN_SHIFT 4 #define PALETTE_GREEN_SHIFT 4
#define GREY_PALETTE 0u #define GRAY_PALETTE 0u
#define BLUE_PALETTE 1u // the dense SCB's other palette #define BLUE_PALETTE 1u // the dense SCB's other palette
#define BLUE_FLOOR 0x00Fu // added to every colour but black #define BLUE_FLOOR 0x00Fu // added to every color but black
// The status panel: its place, its lines, and how often it is redrawn.
#define PANEL_X 8
#define PANEL_Y 8
#define PANEL_W (JL_SURFACE_WIDTH - 2 * PANEL_X)
#define PANEL_LINE_H 10
#define PANEL_LINES 6
#define PANEL_H (PANEL_LINES * PANEL_LINE_H)
#define PANEL_LINE_SERIAL 2
#define PANEL_LINE_COUNTS 3
#define PANEL_LINE_MOUSE 4
#define PANEL_LINE_KEYS 5
#define PANEL_TEXT_BYTES 64
#define STATUS_FRAMES 30u
#define COLOR_TEXT 15
typedef struct StreamStatsT { typedef struct StreamStatsT {
uint32_t received; // peer bytes read so far = index of the next one expected uint32_t received; // peer bytes read so far = index of the next one expected
@ -129,6 +151,7 @@ typedef struct PointerT {
static void buildPalette(jlSurfaceT *screen); static void buildPalette(jlSurfaceT *screen);
static void buildScb(jlSurfaceT *screen); static void buildScb(jlSurfaceT *screen);
static void drawStatus(jlSurfaceT *screen, const StreamStatsT *stats, uint16_t status, PointerT *pointer);
static void fillPointer(jlSurfaceT *screen, int16_t x, int16_t y, uint8_t color); static void fillPointer(jlSurfaceT *screen, int16_t x, int16_t y, uint8_t color);
static uint16_t mouseFlags(void); static uint16_t mouseFlags(void);
static void movePointer(jlSurfaceT *screen, PointerT *pointer); static void movePointer(jlSurfaceT *screen, PointerT *pointer);
@ -140,7 +163,7 @@ static uint8_t streamByte(uint32_t index, uint8_t mask);
static void updateMouseRow(jlSurfaceT *screen, MouseRowT *last); static void updateMouseRow(jlSurfaceT *screen, MouseRowT *last);
// A grey ramp, so every index the verification rows stamp is visible. // A gray ramp, so every index the verification rows stamp is visible.
static void buildPalette(jlSurfaceT *screen) { static void buildPalette(jlSurfaceT *screen) {
uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE]; uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE];
uint16_t i; uint16_t i;
@ -148,21 +171,21 @@ static void buildPalette(jlSurfaceT *screen) {
for (i = 0u; i < JL_SURFACE_COLORS_PER_PALETTE; i++) { for (i = 0u; i < JL_SURFACE_COLORS_PER_PALETTE; i++) {
colors[i] = (uint16_t)((i << PALETTE_RED_SHIFT) | (i << PALETTE_GREEN_SHIFT) | i); colors[i] = (uint16_t)((i << PALETTE_RED_SHIFT) | (i << PALETTE_GREEN_SHIFT) | i);
} }
jlPaletteSet(screen, GREY_PALETTE, colors); jlPaletteSet(screen, GRAY_PALETTE, colors);
} }
// The grey palette everywhere, or DENSE_SCB_LINES lines alternating between // The gray palette everywhere, or DENSE_SCB_LINES lines alternating between
// it and the same ramp in blue. // it and the same ramp in blue.
static void buildScb(jlSurfaceT *screen) { static void buildScb(jlSurfaceT *screen) {
jlScbSetRange(screen, 0, JL_SURFACE_HEIGHT - 1, GREY_PALETTE); jlScbSetRange(screen, 0, JL_SURFACE_HEIGHT - 1, GRAY_PALETTE);
#if DENSE_SCB_LINES > 0 #if DENSE_SCB_LINES > 0
{ {
uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE]; uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE];
uint16_t i; uint16_t i;
uint16_t line; uint16_t line;
jlPaletteGet(screen, GREY_PALETTE, colors); jlPaletteGet(screen, GRAY_PALETTE, colors);
for (i = 1u; i < JL_SURFACE_COLORS_PER_PALETTE; i++) { for (i = 1u; i < JL_SURFACE_COLORS_PER_PALETTE; i++) {
colors[i] = (uint16_t)(colors[i] | BLUE_FLOOR); colors[i] = (uint16_t)(colors[i] | BLUE_FLOOR);
} }
@ -175,6 +198,30 @@ static void buildScb(jlSurfaceT *screen) {
} }
// The panel: the line, the bytes each way, the mouse, and the keys, over the
// panel cleared. A pointer it covered is drawn again on the next move.
static void drawStatus(jlSurfaceT *screen, const StreamStatsT *stats, uint16_t status, PointerT *pointer) {
char text[PANEL_TEXT_BYTES];
jlFillRect(screen, PANEL_X, PANEL_Y, PANEL_W, PANEL_H, COLOR_BACKGROUND);
exampleFontDraw(screen, PANEL_X, PANEL_Y, "Serial and mouse together", COLOR_TEXT);
if ((status & STATUS_OPEN) == 0u) {
(void)snprintf(text, sizeof(text), "Serial: no port to open");
} else {
(void)snprintf(text, sizeof(text), "Serial: %u baud, 8N1%s", (unsigned)LINE_BAUD, (status & STATUS_PAUSED) != 0u ? ", paused" : "");
}
exampleFontDraw(screen, PANEL_X, PANEL_Y + PANEL_LINE_SERIAL * PANEL_LINE_H, text, COLOR_TEXT);
(void)snprintf(text, sizeof(text), "Sent %lu, received %lu, bad %u", (unsigned long)stats->sent, (unsigned long)stats->received, (unsigned)stats->bad);
exampleFontDraw(screen, PANEL_X, PANEL_Y + PANEL_LINE_COUNTS * PANEL_LINE_H, text, COLOR_TEXT);
(void)snprintf(text, sizeof(text), "Mouse %d,%d", (int)jlMouseX(), (int)jlMouseY());
exampleFontDraw(screen, PANEL_X, PANEL_Y + PANEL_LINE_MOUSE * PANEL_LINE_H, text, COLOR_TEXT);
exampleFontDraw(screen, PANEL_X, PANEL_Y + PANEL_LINE_KEYS * PANEL_LINE_H, "Space pauses sending; Escape quits.", COLOR_TEXT);
if (pointer->x < PANEL_X + PANEL_W && pointer->x + POINTER_SIZE > PANEL_X && pointer->y < PANEL_Y + PANEL_H && pointer->y + POINTER_SIZE > PANEL_Y) {
pointer->drawn = false;
}
}
// The pointer block at (x, y), clipped above the verification rows so it can // The pointer block at (x, y), clipped above the verification rows so it can
// never draw over them: a harness samples those between frames. // never draw over them: a harness samples those between frames.
static void fillPointer(jlSurfaceT *screen, int16_t x, int16_t y, uint8_t color) { static void fillPointer(jlSurfaceT *screen, int16_t x, int16_t y, uint8_t color) {
@ -197,7 +244,7 @@ static void fillPointer(jlSurfaceT *screen, int16_t x, int16_t y, uint8_t color)
static uint16_t mouseFlags(void) { static uint16_t mouseFlags(void) {
return (uint16_t)((jlMousePresent() ? MOUSE_FLAG_PRESENT : 0u) | (jlMouseDown(JL_MOUSE_BUTTON_LEFT) ? MOUSE_FLAG_LEFT : 0u) | (jlMouseDown(JL_MOUSE_BUTTON_RIGHT) ? MOUSE_FLAG_RIGHT : 0u) | (jlMouseDown(JL_MOUSE_BUTTON_MIDDLE) ? MOUSE_FLAG_MIDDLE : 0u)); return (uint16_t)((jlMousePresent() ? MOUSE_FLAG_PRESENT : 0u) | (jlMouseDown(JL_MOUSE_BUTTON_LEFT) ? MOUSE_FLAG_LEFT : 0u) | (jlMouseDown(JL_MOUSE_BUTTON_RIGHT) ? MOUSE_FLAG_RIGHT : 0u));
} }
@ -327,12 +374,14 @@ int main(void) {
PointerT pointer; PointerT pointer;
uint16_t status; uint16_t status;
uint16_t rowFrame; uint16_t rowFrame;
uint16_t statusFrame;
uint16_t i; uint16_t i;
bool paused; bool paused;
config.codegenBytes = 0u; config.codegenBytes = 0u;
config.noAudio = true; config.noAudio = true;
if (!jlInit(&config)) { if (!jlInit(&config)) {
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
return 1; return 1;
} }
screen = jlStageGet(); screen = jlStageGet();
@ -362,10 +411,12 @@ int main(void) {
paused = false; paused = false;
jlInputPoll(); jlInputPoll();
drawStatus(screen, &stats, status, &pointer);
stampSerialRow(screen, &stats, status); stampSerialRow(screen, &stats, status);
updateMouseRow(screen, &mouseRow); updateMouseRow(screen, &mouseRow);
jlStagePresent(); jlStagePresent();
rowFrame = jlFrameCount(); rowFrame = jlFrameCount();
statusFrame = rowFrame;
for (;;) { for (;;) {
jlInputPoll(); jlInputPoll();
@ -382,6 +433,10 @@ int main(void) {
for (i = 0u; i < DTR_WRITES_PER_LOOP; i++) { for (i = 0u; i < DTR_WRITES_PER_LOOP; i++) {
jlSerialSetDtr(true); jlSerialSetDtr(true);
} }
if ((uint16_t)(jlFrameCount() - statusFrame) >= STATUS_FRAMES) {
statusFrame = jlFrameCount();
drawStatus(screen, &stats, (uint16_t)(status | (paused ? STATUS_PAUSED : 0u)), &pointer);
}
movePointer(screen, &pointer); movePointer(screen, &pointer);
updateMouseRow(screen, &mouseRow); updateMouseRow(screen, &mouseRow);
if ((uint16_t)(jlFrameCount() - rowFrame) >= SERIAL_ROW_FRAMES) { if ((uint16_t)(jlFrameCount() - rowFrame) >= SERIAL_ROW_FRAMES) {

View file

@ -61,7 +61,7 @@ static const uint8_t gBallAuthored[BALL_H * (BALL_W / 2)] = {
}; };
static uint8_t gBallTiles[BALL_TILE_BYTES]; static uint8_t gBallTiles[BALL_TILE_BYTES];
static uint16_t gBallBackup[jlSpriteBackupLength(uint16_t, BALL_W / 8, BALL_H / 8)]; static uint16_t gBallBackup[jlSpriteBackupLength(BALL_W / 8, BALL_H / 8)];
// Repack from authored "wide row" layout to tile-major jlSpriteT // Repack from authored "wide row" layout to tile-major jlSpriteT

View file

@ -15,7 +15,7 @@
// refuse<n>.tbk n = 1, 2, ...: every other target's bake // refuse<n>.tbk n = 1, 2, ...: every other target's bake
// expect.bin the PNG decoded by PIL, independent of assetbake's // expect.bin the PNG decoded by PIL, independent of assetbake's
// encoders: tileCount LE16, palette[16] LE16 $0RGB, then // encoders: tileCount LE16, palette[16] LE16 $0RGB, then
// tileCount * 64 colour indices (tiles in bank order, each // tileCount * 64 color indices (tiles in bank order, each
// row-major) // row-major)
// //
// The checks, all on the stage: // The checks, all on the stage:
@ -33,7 +33,7 @@
// Result: one jlLog line, flushed before anything is shown, // 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 // 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 // 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 host's), then the tiles in the bank's own colors over a lamp centered on
// the stage: LAMP_PASS_COLOR or LAMP_FAIL_COLOR in palette LAMP_PALETTE, with // 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 // the rest of its rows left at FILL_COLOR. The MAME gates find the verdict
// there before they read the log. Any key exits. // there before they read the log. Any key exits.
@ -70,8 +70,8 @@
// No sprites here, so the port's default codegen cache is plenty. // No sprites here, so the port's default codegen cache is plenty.
#define CODEGEN_DEFAULT 0u #define CODEGEN_DEFAULT 0u
// The verdict lamp, centred on the stage. Its rows get their own palette so // The verdict lamp, centered on the stage. Its rows get their own palette so
// the lamp colours cannot collide with the bank's sixteen. // the lamp colors cannot collide with the bank's sixteen.
#define LAMP_W 64 #define LAMP_W 64
#define LAMP_H 64 #define LAMP_H 64
#define LAMP_X ((JL_SURFACE_WIDTH - LAMP_W) / 2) #define LAMP_X ((JL_SURFACE_WIDTH - LAMP_W) / 2)
@ -248,7 +248,7 @@ static bool runChecks(void) {
} }
// Show the bank's tiles in its own colours, and the lamp. // Show the bank's tiles in its own colors, and the lamp.
static void showVerdict(bool pass) { static void showVerdict(bool pass) {
uint16_t colors[PALETTE_COLORS]; uint16_t colors[PALETTE_COLORS];
uint16_t i; uint16_t i;

View file

@ -140,7 +140,7 @@ typedef void (*OpFn)(void);
static jlSurfaceT *jlgStage = NULL; static jlSurfaceT *jlgStage = NULL;
static jlSpriteT *gSprite = NULL; static jlSpriteT *gSprite = NULL;
static jlSpriteBackupT gBackup; static jlSpriteBackupT gBackup;
static unsigned char gBackupBytes[jlSpriteBackupLength(unsigned char, 2, 2)] __attribute__((aligned(2))); static uint16_t gBackupWords[jlSpriteBackupLength(2, 2)];
static jlTileT gTileScratch; static jlTileT gTileScratch;
@ -154,7 +154,7 @@ static jlTileT gTileMapB;
#define UBER_FRAME_SPRITES 3 #define UBER_FRAME_SPRITES 3
static jlSpriteBackupT gFrameBackups[UBER_FRAME_SPRITES]; static jlSpriteBackupT gFrameBackups[UBER_FRAME_SPRITES];
static unsigned char gFrameBackupBytes[UBER_FRAME_SPRITES][jlSpriteBackupLength(unsigned char, 2, 2)] __attribute__((aligned(2))); static uint16_t gFrameBackupWords[UBER_FRAME_SPRITES][jlSpriteBackupLength(2, 2)];
// Current timed-op number (1-based); mirrored into the mailbox. Lives // Current timed-op number (1-based); mirrored into the mailbox. Lives
@ -719,7 +719,7 @@ static void PROBE_ATTR op_probePresentIdle(void) {
// rows in this same log. Stage pixels are never written (saves read; // rows in this same log. Stage pixels are never written (saves read;
// the restoreVal call fails validation), so these rows are hash-inert. // the restoreVal call fails validation), so these rows are hash-inert.
static jlSpriteBackupT gProbeSpriteBackup; static jlSpriteBackupT gProbeSpriteBackup;
static unsigned char gProbeSpriteBackupBytes[jlSpriteBackupLength(unsigned char, 2, 2)] __attribute__((aligned(2))); static uint16_t gProbeSpriteBackupWords[jlSpriteBackupLength(2, 2)];
static void PROBE_ATTR op_probeSpriteSaveVal(void) { static void PROBE_ATTR op_probeSpriteSaveVal(void) {
jlSpriteSaveUnder(jlgStage, gSprite, -100, -100, &gProbeSpriteBackup); jlSpriteSaveUnder(jlgStage, gSprite, -100, -100, &gProbeSpriteBackup);
@ -750,7 +750,7 @@ static void PROBE_ATTR op_probeSpriteSaveG16(void) {
// and gBackup untouched, so the row is inert to the golden hashes. // and gBackup untouched, so the row is inert to the golden hashes.
static jlSurfaceT *gProbeRestoreWork = NULL; static jlSurfaceT *gProbeRestoreWork = NULL;
static jlSpriteBackupT gProbeRestoreBackup; static jlSpriteBackupT gProbeRestoreBackup;
static unsigned char gProbeRestoreBackupBytes[jlSpriteBackupLength(unsigned char, 2, 2)] __attribute__((aligned(2))); static uint16_t gProbeRestoreBackupWords[jlSpriteBackupLength(2, 2)];
static void PROBE_ATTR op_probeRestoreWork(void) { static void PROBE_ATTR op_probeRestoreWork(void) {
jlSpriteRestoreUnder(gProbeRestoreWork, &gProbeRestoreBackup); jlSpriteRestoreUnder(gProbeRestoreWork, &gProbeRestoreBackup);
@ -1259,7 +1259,7 @@ extern uint32_t jlHostBeProbeDisplayMismatch(const jlSurfaceT *src, uint32_t *co
// counts the ones that differ. The hosted backends draw only the // counts the ones that differ. The hosted backends draw only the
// rectangles that changed at each show; their probes count the frame's // rectangles that changed at each show; their probes count the frame's
// pixels that differ from the stage and the window's that differ from the // pixels that differ from the stage and the window's that differ from the
// whole frame drawn afresh. A strip of one-pixel stripes in all 16 colours stays on screen, // whole frame drawn afresh. A strip of one-pixel stripes in all 16 colors stays on screen,
// so every table entry, and both pixels of a byte, are checked; the small // so every table entry, and both pixels of a byte, are checked; the small
// rect is on odd pixels. After P12's steps (tall band, small inner rect, // rect is on odd pixels. After P12's steps (tall band, small inner rect,
// settle, idle) two more work the tables: the strip's rows moved to another // settle, idle) two more work the tables: the strip's rows moved to another
@ -1304,7 +1304,7 @@ static void PROBE_ATTR op_probeWindowDisplayCheck(void) {
jlPaletteGet(jlgStage, (uint8_t)PROBE_DVX_PALETTE, savedPalette); jlPaletteGet(jlgStage, (uint8_t)PROBE_DVX_PALETTE, savedPalette);
jlPaletteSet(jlgStage, (uint8_t)PROBE_DVX_PALETTE, probePalette); jlPaletteSet(jlgStage, (uint8_t)PROBE_DVX_PALETTE, probePalette);
// Shown before the steps, so the palette's own full redraw (Win16 // Shown before the steps, so the palette's own full redraw (Win16
// repaints its window when colours change) is behind them; and a frame // repaints its window when colors change) is behind them; and a frame
// between B's presents, so each is a show of its own (Win16 shows at // between B's presents, so each is a show of its own (Win16 shows at
// most once a frame, everything changed since the last). // most once a frame, everything changed since the last).
jlStagePresent(); jlStagePresent();
@ -1968,7 +1968,7 @@ static void __attribute__((noinline)) checkTileMonoFloodExtra(void) {
// across the map's seams (64x40 tiles = 512x320 pixels) both ways, a // across the map's seams (64x40 tiles = 512x320 pixels) both ways, a
// jump of more than a view, an origin past the map's size, and cell // jump of more than a view, an origin past the map's size, and cell
// changes in and out of view. The layer is a band (rows 24-159) over a // changes in and out of view. The layer is a band (rows 24-159) over a
// stage of colour 6, so the rows outside it are in every hash. One hash // stage of color 6, so the rows outside it are in every hash. One hash
// folds the stage's after each draw. // folds the stage's after each draw.
#define UBER_LAYER_CHECK_MOVES 20 #define UBER_LAYER_CHECK_MOVES 20
@ -2005,10 +2005,10 @@ static void __attribute__((noinline)) checkTileLayer(void) {
} }
// jlTilePasteGlyph, which nothing else here calls: every colour on both // jlTilePasteGlyph, which nothing else here calls: every color on both
// sides of a glyph whose rows hold every pixel pair, on the stage (each // sides of a glyph whose rows hold every pixel pair, on the stage (each
// port's own paste, mark included) and on another surface, with colours // port's own paste, mark included) and on another surface, with colors
// above 15 (their low nibble is the colour) and the calls that must draw // above 15 (their low nibble is the color) and the calls that must draw
// nothing -- a cell off the surface, no bits, no surface. One hash folds // nothing -- a cell off the surface, no bits, no surface. One hash folds
// both surfaces'. // both surfaces'.
static void __attribute__((noinline)) checkTilePasteGlyph(void) { static void __attribute__((noinline)) checkTilePasteGlyph(void) {
@ -2048,7 +2048,7 @@ static void __attribute__((noinline)) checkTilePasteGlyph(void) {
// jlFillRect one row high, the shape a planar picture is pushed in, which // jlFillRect one row high, the shape a planar picture is pushed in, which
// UBER's timed fills never are: widths 1 to 41 at every pixel phase, all // UBER's timed fills never are: widths 1 to 41 at every pixel phase, all
// sixteen colours, both edges of the surface, and rows that clip or are // sixteen colors, both edges of the surface, and rows that clip or are
// empty -- on the stage and on another surface. One hash folds both. // empty -- on the stage and on another surface. One hash folds both.
static void __attribute__((noinline)) checkFillRectRows(void) { static void __attribute__((noinline)) checkFillRectRows(void) {
jlSurfaceT *other; jlSurfaceT *other;
@ -2354,9 +2354,9 @@ static void __attribute__((noinline)) runAllTests(void) {
} }
} }
} }
gFrameBackups[0].bytes = gFrameBackupBytes[0]; gFrameBackups[0].bytes = (uint8_t *)gFrameBackupWords[0];
gFrameBackups[1].bytes = gFrameBackupBytes[1]; gFrameBackups[1].bytes = (uint8_t *)gFrameBackupWords[1];
gFrameBackups[2].bytes = gFrameBackupBytes[2]; gFrameBackups[2].bytes = (uint8_t *)gFrameBackupWords[2];
timeOp("jlTilePaste map 10x5", op_tileMapPaste); timeOp("jlTilePaste map 10x5", op_tileMapPaste);
timeOp("gameFrame composite", op_gameFrame); timeOp("gameFrame composite", op_gameFrame);
@ -2453,7 +2453,7 @@ static void __attribute__((noinline)) runAllTests(void) {
// before op_probeShrParity. timeOp (not probeOp) to match the // before op_probeShrParity. timeOp (not probeOp) to match the
// rest of this arm (see probeOp). // rest of this arm (see probeOp).
gProbeRestoreBackup.sprite = gSprite; gProbeRestoreBackup.sprite = gSprite;
gProbeRestoreBackup.bytes = gProbeRestoreBackupBytes; gProbeRestoreBackup.bytes = (uint8_t *)gProbeRestoreBackupWords;
jlSpriteSaveUnder(jlgStage, gSprite, 40, 30, &gProbeRestoreBackup); jlSpriteSaveUnder(jlgStage, gSprite, 40, 30, &gProbeRestoreBackup);
timeOp("probe restoreTrusted", op_probeRestoreTrusted); timeOp("probe restoreTrusted", op_probeRestoreTrusted);
timeOp("probe saveTrusted", op_probeSaveTrusted); timeOp("probe saveTrusted", op_probeSaveTrusted);
@ -2495,7 +2495,7 @@ static void __attribute__((noinline)) runAllTests(void) {
// demote it to the metadata-only path); the odd-x fields AFTER the // demote it to the metadata-only path); the odd-x fields AFTER the
// saves (which overwrite the metadata), right before restoreVal. // saves (which overwrite the metadata), right before restoreVal.
gProbeSpriteBackup.sprite = gSprite; gProbeSpriteBackup.sprite = gSprite;
gProbeSpriteBackup.bytes = gProbeSpriteBackupBytes; gProbeSpriteBackup.bytes = (uint8_t *)gProbeSpriteBackupWords;
probeOp("probe spriteSaveVal", op_probeSpriteSaveVal); probeOp("probe spriteSaveVal", op_probeSpriteSaveVal);
probeOp("probe spriteSave g16", op_probeSpriteSaveG16); probeOp("probe spriteSave g16", op_probeSpriteSaveG16);
gProbeSpriteBackup.x = 41; gProbeSpriteBackup.x = 41;
@ -2509,7 +2509,7 @@ static void __attribute__((noinline)) runAllTests(void) {
// difference vs the golden jlSpriteRestoreUnder row = the mark. // difference vs the golden jlSpriteRestoreUnder row = the mark.
gProbeRestoreWork = jlSurfaceCreate(); gProbeRestoreWork = jlSurfaceCreate();
gProbeRestoreBackup.sprite = gSprite; gProbeRestoreBackup.sprite = gSprite;
gProbeRestoreBackup.bytes = gProbeRestoreBackupBytes; gProbeRestoreBackup.bytes = (uint8_t *)gProbeRestoreBackupWords;
jlSpriteSaveUnder(jlgStage, gSprite, 40, 30, &gProbeRestoreBackup); jlSpriteSaveUnder(jlgStage, gSprite, 40, 30, &gProbeRestoreBackup);
if (gProbeRestoreWork != NULL) { if (gProbeRestoreWork != NULL) {
probeOp("probe restoreWork", op_probeRestoreWork); probeOp("probe restoreWork", op_probeRestoreWork);
@ -2577,7 +2577,7 @@ static void __attribute__((noinline)) setupPalette(void) {
int i; int i;
for (i = 0; i < 16; i++) { for (i = 0; i < 16; i++) {
pal[i] = (uint16_t)((i << 8) | (i << 4) | i); // grey ramp pal[i] = (uint16_t)((i << 8) | (i << 4) | i); // gray ramp
} }
pal[ 0] = 0x000; pal[ 0] = 0x000;
pal[ 1] = 0x800; // dark red (running) pal[ 1] = 0x800; // dark red (running)
@ -2625,7 +2625,7 @@ static bool __attribute__((noinline)) setupSprite(void) {
// wait for next VBL edge // wait for next VBL edge
} }
jlLogF("UBER: jlSpriteCompile: 1 call in <= 1 frame"); jlLogF("UBER: jlSpriteCompile: 1 call in <= 1 frame");
gBackup.bytes = gBackupBytes; gBackup.bytes = (uint8_t *)gBackupWords;
return true; return true;
} }

View file

@ -10,7 +10,7 @@
// //
// GetDiskObject reads FOLDER/NAME.info; its type, stack and image are // GetDiskObject reads FOLDER/NAME.info; its type, stack and image are
// logged, and the image is drawn at ICON_X,ICON_Y on a hires screen of // logged, and the image is drawn at ICON_X,ICON_Y on a hires screen of
// Workbench's four colours, with the mouse pointer hidden, held HOLD_TICKS // Workbench's four colors, with the mouse pointer hidden, held HOLD_TICKS
// for a screenshot. Then // for a screenshot. Then
// FOLDER/NAME is started as Workbench starts a tool -- LoadSeg, CreateProc // FOLDER/NAME is started as Workbench starts a tool -- LoadSeg, CreateProc
// with the icon's stack, and a WBStartup message whose one argument is the // with the icon's stack, and a WBStartup message whose one argument is the
@ -63,14 +63,14 @@ static bool drawIcon(struct DiskObject *icon);
static bool launch(const char *folder, const char *name, ULONG stack); static bool launch(const char *folder, const char *name, ULONG stack);
static void logLine(const char *format, ...); static void logLine(const char *format, ...);
// Workbench's four colours (2.0 on), 4 bits a gun: grey, black, white and // Workbench's four colors (2.0 on), 4 bits a gun: gray, black, white and
// blue (tools/iconBake/iconBake.c's kAmigaColours). // blue (tools/iconBake/iconBake.c's kAmigaColors).
static const UWORD gWorkbenchColours[] = { 0xAAA, 0x000, 0xFFF, 0x68B }; static const UWORD gWorkbenchColors[] = { 0xAAA, 0x000, 0xFFF, 0x68B };
static FILE *gLog = NULL; static FILE *gLog = NULL;
// The icon's image on a screen of Workbench's colours, held for the gate's // The icon's image on a screen of Workbench's colors, held for the gate's
// screenshot. A window over the whole screen, active, hides the pointer. // screenshot. A window over the whole screen, active, hides the pointer.
static bool drawIcon(struct DiskObject *icon) { static bool drawIcon(struct DiskObject *icon) {
struct Screen *screen; struct Screen *screen;
@ -90,8 +90,8 @@ static bool drawIcon(struct DiskObject *icon) {
logLine("FAIL cannot open a screen and a window"); logLine("FAIL cannot open a screen and a window");
} else { } else {
SetPointer(window, blank, BLANK_POINTER_H, BLANK_POINTER_W, 0, 0); SetPointer(window, blank, BLANK_POINTER_H, BLANK_POINTER_W, 0, 0);
for (i = 0; i < sizeof(gWorkbenchColours) / sizeof(gWorkbenchColours[0]); i++) { for (i = 0; i < sizeof(gWorkbenchColors) / sizeof(gWorkbenchColors[0]); i++) {
SetRGB4(&screen->ViewPort, i, (gWorkbenchColours[i] >> RED_SHIFT) & GUN_MASK, (gWorkbenchColours[i] >> GREEN_SHIFT) & GUN_MASK, gWorkbenchColours[i] & GUN_MASK); SetRGB4(&screen->ViewPort, i, (gWorkbenchColors[i] >> RED_SHIFT) & GUN_MASK, (gWorkbenchColors[i] >> GREEN_SHIFT) & GUN_MASK, gWorkbenchColors[i] & GUN_MASK);
} }
SetRast(window->RPort, 0); SetRast(window->RPort, 0);
DrawImage(window->RPort, (struct Image *)icon->do_Gadget.GadgetRender, ICON_X, ICON_Y); DrawImage(window->RPort, (struct Image *)icon->do_Gadget.GadgetRender, ICON_X, ICON_Y);

View file

@ -118,11 +118,14 @@ typedef uint32_t (*jlAudioStreamFillT)(void *ctx, int8_t *dst, uint32_t count);
// rate. If the slot is currently playing, the new stream replaces // rate. If the slot is currently playing, the new stream replaces
// it. Implemented on every port: DOS/ST/X68000/hosted pull through the // it. Implemented on every port: DOS/ST/X68000/hosted pull through the
// shared SFX overlay mixer as jlAudioFrameTick refills their sound // shared SFX overlay mixer as jlAudioFrameTick refills their sound
// buffers, while the IIgs (NTPstreamsound, 7 KB chunks) and Amiga // buffers, the IIgs (NTPstreamsound, 7 KB chunks) plays one chunk at a
// (PTPlayer mt_playfx, 4 KB chunks) play one chunk at a time and fire the // time and fires the next from jlAudioFrameTick as the current one runs
// next from jlAudioFrameTick as the current one runs down -- so the host // down, and the Amiga (PTPlayer mt_loopfx) loops an 8 KB ring that
// must be calling jlAudioFrameTick for a stream to continue, and on those // jlAudioFrameTick tops up as Paula plays it -- so the host must be calling
// two a seam can carry up to one frame of jitter. // jlAudioFrameTick for a stream to continue. On the IIgs a seam can carry
// up to one frame of jitter; the Amiga's ring has none, and plays its old
// contents again if the calls stop for longer than it lasts (a third of a
// second at 22 kHz).
void jlAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz); void jlAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz);
// Raw-format streaming: jlAudioPlaySfxStream, except `fill` delivers // Raw-format streaming: jlAudioPlaySfxStream, except `fill` delivers
@ -154,12 +157,12 @@ typedef enum {
// What each port can actually do, because the hardware differs: // What each port can actually do, because the hardware differs:
// IIgs all three. The DOC pans per OSCILLATOR (control bits 7:4 are the // IIgs all three. The DOC pans per OSCILLATOR (control bits 7:4 are the
// output channel, and per Apple TN #19 odd = left, even = right), // output channel, and per Apple TN #19 odd = left, even = right),
// so a centred sound is played on two oscillators, one per side. // so a centered sound is played on two oscillators, one per side.
// A stock IIgs sums to mono anyway; this only shows up on a stereo // A stock IIgs sums to mono anyway; this only shows up on a stereo
// card or an emulator. // card or an emulator.
// Amiga left and right, by picking the Paula channel side (0 and 3 are // Amiga left and right, by picking the Paula channel side (0 and 3 are
// left, 1 and 2 right). A channel plays on one side only, so a // left, 1 and 2 right). A channel plays on one side only, so a
// centred sound plays on a free channel each side when both sides // centered sound plays on a free channel each side when both sides
// have one, and otherwise on the one channel PTPlayer picks. // have one, and otherwise on the one channel PTPlayer picks.
// X68000 ignored. Everything is mixed into ONE MSM6258 ADPCM stream, so // X68000 ignored. Everything is mixed into ONE MSM6258 ADPCM stream, so
// its pan bits would move all audio at once, not one slot. // its pan bits would move all audio at once, not one slot.
@ -308,7 +311,7 @@ void jlMusicResume(void);
// Global music attenuation, added to every authored voice/noise // Global music attenuation, added to every authored voice/noise
// attenuation (0 = as authored .. 15 = silent). Applies immediately // attenuation (0 = as authored .. 15 = silent). Applies immediately
// to sounding voices and to everything played afterwards; jlMusicPlay // to sounding voices and to everything played afterward; jlMusicPlay
// does NOT reset it, so a fade-out can carry into the next song. // does NOT reset it, so a fade-out can carry into the next song.
// Step it over successive frames for fades. // Step it over successive frames for fades.
void jlMusicSetAtten(uint8_t atten); void jlMusicSetAtten(uint8_t atten);

View file

@ -32,6 +32,10 @@
// jlMousePressed(b) -- rising edge since last poll // jlMousePressed(b) -- rising edge since last poll
// jlMouseReleased(b) -- falling edge since last poll // jlMouseReleased(b) -- falling edge since last poll
// //
// Every port has two buttons, left and right, whatever its mouse has: a
// left click made with Ctrl held is the right button (from the press until
// the button is let go), so a one-button mouse has both.
//
// The joystick predicates return per-stick state: // The joystick predicates return per-stick state:
// //
// jlJoystickConnected(js) -- true if the platform reports a // jlJoystickConnected(js) -- true if the platform reports a
@ -39,7 +43,7 @@
// jlJoystickX/Y(js) -- axis values, signed -127..+127. // jlJoystickX/Y(js) -- axis values, signed -127..+127.
// Digital sticks snap to the // Digital sticks snap to the
// extremes; analog sticks return // extremes; analog sticks return
// their offset from centre, scaled, // their offset from center, scaled,
// dead zone applied. // dead zone applied.
// jlJoyDown(js, b) -- is button b held on stick js // jlJoyDown(js, b) -- is button b held on stick js
// jlJoyPressed(js, b) -- rising edge since last poll // jlJoyPressed(js, b) -- rising edge since last poll
@ -80,7 +84,6 @@ typedef enum {
JL_MOUSE_BUTTON_NONE = 0, JL_MOUSE_BUTTON_NONE = 0,
JL_MOUSE_BUTTON_LEFT, JL_MOUSE_BUTTON_LEFT,
JL_MOUSE_BUTTON_RIGHT, JL_MOUSE_BUTTON_RIGHT,
JL_MOUSE_BUTTON_MIDDLE,
JL_MOUSE_BUTTON_COUNT JL_MOUSE_BUTTON_COUNT
} jlMouseButtonE; } jlMouseButtonE;
@ -109,7 +112,7 @@ typedef enum {
#define JL_CHAR_TAB 0x09 #define JL_CHAR_TAB 0x09
#define JL_CHAR_RETURN 0x0D #define JL_CHAR_RETURN 0x0D
#define JL_CHAR_ESCAPE 0x1B #define JL_CHAR_ESCAPE 0x1B
// The FORWARD delete (the key labelled Del/Delete on a PC, ST or Amiga keyboard - not backspace). A text // The FORWARD delete (the key labeled Del/Delete on a PC, ST or Amiga keyboard - not backspace). A text
// editor needs both: backspace removes the character BEFORE the caret, this one removes the character // editor needs both: backspace removes the character BEFORE the caret, this one removes the character
// UNDER it. 0x7F is ASCII DEL. The IIgs never delivers it: its Delete key is backspace. // UNDER it. 0x7F is ASCII DEL. The IIgs never delivers it: its Delete key is backspace.
#define JL_CHAR_DELETE 0x7F #define JL_CHAR_DELETE 0x7F
@ -164,8 +167,7 @@ bool jlKeyReleased(jlKeyE key);
// True when the platform confirmed an attached mouse at init (DOS: the INT 33h driver reset; // True when the platform confirmed an attached mouse at init (DOS: the INT 33h driver reset;
// Amiga: JoeyLib's input window opened; Win32/Win16: Windows reports one; SDL3/web: SDL reports one; // Amiga: JoeyLib's input window opened; Win32/Win16: Windows reports one; SDL3/web: SDL reports one;
// IIgs/ST/X68000/DVX/Haiku/BeOS: always; PSP: never). The IIgs mouse has one button, the ST's and // IIgs/ST/X68000/DVX/Haiku/BeOS: always; PSP: always, its analog nub, with L and R its buttons).
// X68000's two.
bool jlMousePresent(void); bool jlMousePresent(void);
int16_t jlMouseX(void); int16_t jlMouseX(void);
int16_t jlMouseY(void); int16_t jlMouseY(void);
@ -189,8 +191,8 @@ bool jlJoyReleased(jlJoystickE js, jlJoyButtonE button);
// Set the stick's dead zone (8 at jlInit): axis values within `deadZone` of 0 (on the // Set the stick's dead zone (8 at jlInit): axis values within `deadZone` of 0 (on the
// -127..127 scale) read 0 on the ports with analog sticks (IIgs, DOS, // -127..127 scale) read 0 on the ports with analog sticks (IIgs, DOS,
// DVX, hosted); use 0 to disable it. On the IIgs and DOS the call also // DVX, hosted); use 0 to disable it. On the IIgs and DOS the call also
// re-centres the stick: the next reading after it is taken as the // re-centers the stick: the next reading after it is taken as the
// centre, so the user must hold the stick centred when calling. The // center, so the user must hold the stick centered when calling. The
// IIgs port stops polling after about a second (60 polls) with no stick // IIgs port stops polling after about a second (60 polls) with no stick
// seen (an absent stick costs ~1 ms a frame of busy-wait) and does NOT // seen (an absent stick costs ~1 ms a frame of busy-wait) and does NOT
// re-probe by itself: this call resumes polling. DOS probes the game // re-probe by itself: this call resumes polling. DOS probes the game

View file

@ -103,7 +103,7 @@
// The stage is four separate 1bpp planes in main RAM (src/x68000/halPlanar.c, // The stage is four separate 1bpp planes in main RAM (src/x68000/halPlanar.c,
// the Amiga's layout), and a present copies its dirty rows into the text // the Amiga's layout), and a present copies its dirty rows into the text
// plane at $E00000, which is four 1bpp planes too. The text plane has one // plane at $E00000, which is four 1bpp planes too. The text plane has one
// 16-colour palette, so per-line SCB palettes are reloaded as the beam scans // 16-color palette, so per-line SCB palettes are reloaded as the beam scans
// by a CRTC raster interrupt (src/x68000/jlX68kRasterIsr.s). // by a CRTC raster interrupt (src/x68000/jlX68kRasterIsr.s).
#define JL_CPU_68000 #define JL_CPU_68000
#define JL_ENDIAN_BIG #define JL_ENDIAN_BIG
@ -139,6 +139,8 @@
#define JL_PLATFORM_NAME "PSP" #define JL_PLATFORM_NAME "PSP"
#elif defined(JOEYLIB_HOSTED_SDL3) && defined(__APPLE__) #elif defined(JOEYLIB_HOSTED_SDL3) && defined(__APPLE__)
#define JL_PLATFORM_NAME "macOS" #define JL_PLATFORM_NAME "macOS"
#elif defined(JOEYLIB_HOSTED_SDL3) && defined(__EMSCRIPTEN__)
#define JL_PLATFORM_NAME "Web"
#elif defined(JOEYLIB_HOSTED_SDL3) #elif defined(JOEYLIB_HOSTED_SDL3)
#define JL_PLATFORM_NAME "SDL3" #define JL_PLATFORM_NAME "SDL3"
#elif defined(JOEYLIB_HOSTED_WIN32) #elif defined(JOEYLIB_HOSTED_WIN32)

View file

@ -168,7 +168,7 @@ void jlSerialSetDtr(bool on);
// "my buffer is nearly full, stop sending" and later "go ahead", which is what a client with a small // "my buffer is nearly full, stop sending" and later "go ahead", which is what a client with a small
// ring and a peer that can be told to pause actually needs. RetroNet's 6551 clients (C64, Apple II) // ring and a peer that can be told to pause actually needs. RetroNet's 6551 clients (C64, Apple II)
// have done exactly this for a long time and the 16-bit tier could not, so a JoeyLib target's only // have done exactly this for a long time and the 16-bit tier could not, so a JoeyLib target's only
// defence against an overrun was the packet layer's retransmit. // defense against an overrun was the packet layer's retransmit.
// //
// `on` = true asserts RTS (ready to receive). A port with no controllable RTS line no-ops, and a // `on` = true asserts RTS (ready to receive). A port with no controllable RTS line no-ops, and a
// closed port no-ops; neither is an error, so a caller can drive it unconditionally. // closed port no-ops; neither is an error, so a caller can drive it unconditionally.

View file

@ -146,24 +146,26 @@ void jlSpriteDraw(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y);
// sprites and on ports without a compiled fast path. // sprites and on ports without a compiled fast path.
void jlSpriteDrawTrusted(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y); void jlSpriteDrawTrusted(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y);
// How many _type elements a backup buffer needs for a widthTiles x // How many uint16_t a backup buffer needs for a widthTiles x heightTiles
// heightTiles sprite at ANY x, on every port (rounded up), so that // sprite at ANY x, on every port. A backup buffer is an array of uint16_t,
// which gives it the 2-byte alignment it must have (on the Amiga, ST, and
// X68000 an odd address sends save and restore down a much slower path,
// and it crashes the trusted variants):
// //
// static uint16_t gBacking[jlSpriteBackupLength(uint16_t, 4, 4)]; // static uint16_t gBacking[jlSpriteBackupLength(4, 4)];
// //
// is big enough, and jlSpriteBackupLength(uint8_t, w, h) is the size in // One from jlAlloc is jlSpriteBackupLength(w, h) * sizeof(uint16_t) bytes
// bytes (for jlAlloc, say). The worst case is the 16-pixel-group-aligned // (on the Amiga, ST, and X68000 a jlAlloc block is malloc's, aligned for
// region covering the sprite (x rounded DOWN to a 16-px boundary, width // any type). The worst case is the 16-pixel-group-aligned region covering
// UP): one extra 16-px group, (ceil(w/16) + 1) * 8 bytes per row. Give // the sprite (x rounded DOWN to a 16-px boundary, width UP): one extra 16-px
// the buffer 2-byte alignment (a uint16_t array has it): on the Amiga, // group, (ceil(w/16) + 1) * 8 bytes per row, always a whole number of
// ST, and X68000 an odd address sends save and restore down a much // uint16_t.
// slower path, and it crashes the trusted variants. #define jlSpriteBackupLength(_wT, _hT) \
#define jlSpriteBackupLength(_type, _wT, _hT) \ ((uint16_t)((((((uint16_t)(_wT) * 8u + 15u) >> 4) + 1u) * 8u * ((uint16_t)(_hT) * 8u)) / sizeof(uint16_t)))
((uint16_t)((((((((uint16_t)(_wT) * 8u) + 15u) >> 4) + 1u) * 8u * ((uint16_t)(_hT) * 8u)) + sizeof(_type) - 1u) / sizeof(_type)))
// Capture the destination region a subsequent jlSpriteDraw at the same // Capture the destination region a subsequent jlSpriteDraw at the same
// (x,y) would write to. backup->bytes must have room for // (x,y) would write to. backup->bytes must have room for
// jlSpriteBackupLength(uint8_t, widthTiles, heightTiles) bytes for // jlSpriteBackupLength(widthTiles, heightTiles) uint16_t for
// fully in-bounds draws; for clipped draws only the visible bytes are // fully in-bounds draws; for clipped draws only the visible bytes are
// stored. The captured region's exact geometry is reported in // stored. The captured region's exact geometry is reported in
// backup->x/y/width/height/sizeBytes: x is rounded down to even on every // backup->x/y/width/height/sizeBytes: x is rounded down to even on every
@ -240,7 +242,7 @@ void jlSpriteSaveAndDrawTrusted(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t
// Snapshot an 8x8-aligned region of a jlSurfaceT into a new jlSpriteT. // Snapshot an 8x8-aligned region of a jlSurfaceT into a new jlSpriteT.
// The captured pixel data is copied into a sprite-owned buffer so // The captured pixel data is copied into a sprite-owned buffer so
// the source surface can be modified afterwards. Width and height // the source surface can be modified afterward. Width and height
// are in TILES (each tile = 8x8 pixels). x and y are in pixels and // are in TILES (each tile = 8x8 pixels). x and y are in pixels and
// must be aligned to a tile boundary (multiple of 8) on the source // must be aligned to a tile boundary (multiple of 8) on the source
// surface. Returns NULL if src is NULL, a dimension is 0, x or y is // surface. Returns NULL if src is NULL, a dimension is 0, x or y is

View file

@ -101,7 +101,7 @@ bool jlSurfaceSaveFile(const jlSurfaceT *src, const char *path);
// Read a surface from disk, overwriting dst. Returns false if the // Read a surface from disk, overwriting dst. Returns false if the
// file is missing, the wrong size, has an SCB byte above 15, or the // file is missing, the wrong size, has an SCB byte above 15, or the
// read fails; a read that fails part way can leave dst's pixels partly // read fails; a read that fails part way can leave dst's pixels partly
// overwritten, but its SCBs and palettes are then untouched. Colour 0 // overwritten, but its SCBs and palettes are then untouched. Color 0
// of every palette loaded is set to black, as jlPaletteSet does. dst // of every palette loaded is set to black, as jlPaletteSet does. dst
// keeps its identity (no reallocation). Neither file call sets // keeps its identity (no reallocation). Neither file call sets
// jlLastError. // jlLastError.

View file

@ -181,7 +181,7 @@ void jlTileMapPaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, uint8_t wTiles, uin
// the band with the map as seen from a pixel origin, to the pixel both // the band with the map as seen from a pixel origin, to the pixel both
// ways, so moving the origin a little each frame scrolls it. The map // ways, so moving the origin a little each frame scrolls it. The map
// wraps at its edges (an origin past them, or a map smaller than the // wraps at its edges (an origin past them, or a map smaller than the
// view, repeats it). An index of JL_TILE_MAP_SKIP draws colour 0. // view, repeats it). An index of JL_TILE_MAP_SKIP draws color 0.
// //
// The layer keeps the tiles around the view drawn, so a draw only draws // The layer keeps the tiles around the view drawn, so a draw only draws
// the edge tiles a move uncovered (a move of a whole view or more draws // the edge tiles a move uncovered (a move of a whole view or more draws

View file

@ -346,7 +346,7 @@ cmdDoctor() {
header "host" header "host"
family="$(distroFamily)" family="$(distroFamily)"
if [ "$family" = "unknown" ]; then if [ "$family" = "unknown" ]; then
warn "unrecognised distribution: host packages must be installed by hand" warn "unrecognized distribution: host packages must be installed by hand"
failures=$((failures + 1)) failures=$((failures + 1))
else else
ok "package manager: $family" ok "package manager: $family"

View file

@ -10,7 +10,7 @@
# The build clones its sub-projects by BRANCH name, which would make the # The build clones its sub-projects by BRANCH name, which would make the
# result drift; every sub-project is pre-cloned here at a pinned commit so # result drift; every sub-project is pre-cloned here at a pinned commit so
# the Makefile finds it in place and skips its own clone. It also wget's # the Makefile finds it in place and skips its own clone. It also wget's
# the NDK and ixemul SDK on every build; those are checked afterwards. # the NDK and ixemul SDK on every build; those are checked afterward.
compDescription="Amiga compiler: m68k-amigaos-gcc 6.5 + NDK 3.2 + vasm (source build)" compDescription="Amiga compiler: m68k-amigaos-gcc 6.5 + NDK 3.2 + vasm (source build)"
compGroups="toolchain:amiga" compGroups="toolchain:amiga"

View file

@ -49,7 +49,32 @@ dvxSdkPin() {
} }
compPin="dir:$dvxDir:dvx:$([ -f "$dvxRuntimeZip" ] && sha256Of "$dvxRuntimeZip"):sdk:$(dvxSdkPin)" compPin="dir:$dvxDir:dvx:$([ -f "$dvxRuntimeZip" ] && sha256Of "$dvxRuntimeZip"):sdk:$(dvxSdkPin):foldcase"
# dvxFoldCase DIR -- DOS names are case-blind, and dvx.zip has folders that
# differ only by case (system/ and SYSTEM/PACKAGES/): fold each into its
# lower-case one, all the way down, so the tree is one DVX as DOS sees it.
dvxFoldCase() {
local dir="$1"
local entry
local name
local lower
for entry in "$dir"/*/; do
[ -d "$entry" ] || continue
name="$(basename "$entry")"
lower="${name,,}"
if [ "$name" != "$lower" ] && [ -d "$dir/$lower" ]; then
cp -a "$dir/$name/." "$dir/$lower/"
rm -rf "${dir:?}/${name:?}"
fi
done
for entry in "$dir"/*/; do
if [ -d "$entry" ]; then
dvxFoldCase "${entry%/}"
fi
done
}
compInstall() { compInstall() {
@ -57,6 +82,7 @@ compInstall() {
[ -f "$dvxRuntimeZip" ] || die "$dvxDir has no dist/dvx.zip; make DVX's distribution there, or point --dvx-dir at a DVX tree that has one" [ -f "$dvxRuntimeZip" ] || die "$dvxDir has no dist/dvx.zip; make DVX's distribution there, or point --dvx-dir at a DVX tree that has one"
rm -rf "$compDir" rm -rf "$compDir"
extractArchive "$dvxRuntimeZip" "$compDir" extractArchive "$dvxRuntimeZip" "$compDir"
dvxFoldCase "$compDir"
if [ -f "$dvxSdkZip" ]; then if [ -f "$dvxSdkZip" ]; then
tmp="$(freshBuildDir "$compId")" tmp="$(freshBuildDir "$compId")"
extractArchive "$dvxSdkZip" "$tmp" extractArchive "$dvxSdkZip" "$tmp"
@ -73,7 +99,7 @@ compInstall() {
compInstalled() { compInstalled() {
[ -f "$compDir/dvx.exe" ] && [ -f "$compDir/dvx.exe" ] &&
[ -f "$compDir/config/DVX.INI" ] && [ -f "$compDir/system/DVX.INI" ] &&
[ -f "$compDir/$dvxSdk/sdk.mk" ] && [ -f "$compDir/$dvxSdk/sdk.mk" ] &&
[ -x "$compDir/$dvxSdk/bin/dvxhlpc" ] && [ -x "$compDir/$dvxSdk/bin/dvxhlpc" ] &&
[ -x "$compDir/$dvxSdk/bin/dvxres" ] [ -x "$compDir/$dvxSdk/bin/dvxres" ]

View file

@ -3,9 +3,9 @@
# See LICENSE.md for license information. # See LICENSE.md for license information.
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
# #
# llvm-mingw 20260922, msvcrt flavour: the cross compiler the win32 port # llvm-mingw 20260922, msvcrt flavor: the cross compiler the win32 port
# (make win32) builds Windows programs with -- clang, lld and the mingw-w64 # (make win32) builds Windows programs with -- clang, lld and the mingw-w64
# headers and libraries, prebuilt for Linux x86-64. The msvcrt flavour links # headers and libraries, prebuilt for Linux x86-64. The msvcrt flavor links
# against MSVCRT.DLL, the C library every Windows since 95 OSR2 carries (the # against MSVCRT.DLL, the C library every Windows since 95 OSR2 carries (the
# ucrt one needs Windows 10's), so a program runs on the old Windows the # ucrt one needs Windows 10's), so a program runs on the old Windows the
# port aims at. Programs are 32-bit (i686). # port aims at. Programs are 32-bit (i686).

View file

@ -11,7 +11,7 @@
# from the mirror osxcross users share (github.com/joseluisq/macosx-sdks), # from the mirror osxcross users share (github.com/joseluisq/macosx-sdks),
# pinned to the release's SHA-256; a copy of the same tarball put in # pinned to the release's SHA-256; a copy of the same tarball put in
# toolchains/cache/downloads first (MacOSX15.5.sdk.tar.xz) is used instead. # toolchains/cache/downloads first (MacOSX15.5.sdk.tar.xz) is used instead.
# Its licence is Apple's (LICENSES.md). llvm-lipo joins each architecture's # Its license is Apple's (LICENSES.md). llvm-lipo joins each architecture's
# build into one universal file and llvm-libtool-darwin archives universal # build into one universal file and llvm-libtool-darwin archives universal
# objects (make/support/macos/cc.sh, make/macos.mk). # objects (make/support/macos/cc.sh, make/macos.mk).

View file

@ -17,23 +17,23 @@ compHostDeps="tar"
pspdevRelease="v20261001" pspdevRelease="v20261001"
pspdevHostId="$( (. /etc/os-release 2>/dev/null && printf '%s' "${ID:-}") || true)" pspdevHostId="$( (. /etc/os-release 2>/dev/null && printf '%s' "${ID:-}") || true)"
if [ "$(distroFamily)" = "dnf" ]; then if [ "$(distroFamily)" = "dnf" ]; then
pspdevFlavour="fedora-latest" pspdevFlavor="fedora-latest"
compSha256="7874d4d696f1abe58d3d17fd8ae365e9b129c4bea16b383bf708655037c0e21c" compSha256="7874d4d696f1abe58d3d17fd8ae365e9b129c4bea16b383bf708655037c0e21c"
elif [ "$pspdevHostId" = "debian" ]; then elif [ "$pspdevHostId" = "debian" ]; then
pspdevFlavour="debian-latest" pspdevFlavor="debian-latest"
compSha256="39b20efc7c8492cdbd9929178f45afc801dabb0967b7c882573520f22536920c" compSha256="39b20efc7c8492cdbd9929178f45afc801dabb0967b7c882573520f22536920c"
else else
pspdevFlavour="ubuntu-latest-x86_64" pspdevFlavor="ubuntu-latest-x86_64"
compSha256="a86efe624e770005290919617859e9df24bd8fcd3e364f4587f23ec7364b0acd" compSha256="a86efe624e770005290919617859e9df24bd8fcd3e364f4587f23ec7364b0acd"
fi fi
compUrl="https://github.com/pspdev/pspdev/releases/download/$pspdevRelease/pspdev-$pspdevFlavour.tar.gz" compUrl="https://github.com/pspdev/pspdev/releases/download/$pspdevRelease/pspdev-$pspdevFlavor.tar.gz"
compPin="$compSha256" compPin="$compSha256"
compInstall() { compInstall() {
local archive local archive
# Every flavour's file has the same name: cache it under the release's. # Every flavor's file has the same name: cache it under the release's.
archive="$(fetchFile "$compUrl" "$compSha256" "pspdev-$pspdevRelease-$pspdevFlavour.tar.gz")" archive="$(fetchFile "$compUrl" "$compSha256" "pspdev-$pspdevRelease-$pspdevFlavor.tar.gz")"
rm -rf "$compDir" rm -rf "$compDir"
extractArchive "$archive" "$compDir" 1 extractArchive "$archive" "$compDir" 1
} }

View file

@ -4,7 +4,7 @@
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
# #
# Windows for Workgroups 3.11, set up unattended on a DOSBox Staging PC with # Windows for Workgroups 3.11, set up unattended on a DOSBox Staging PC with
# the S3 Trio64V+ driver at 800x600 in 256 colours and Windows' own Sound # the S3 Trio64V+ driver at 800x600 in 256 colors and Windows' own Sound
# Blaster driver (installer/lib/win16.sh): what the win16 port's programs # Blaster driver (installer/lib/win16.sh): what the win16 port's programs
# run on for joey-run and the gates (scripts/lib/win16Gate.sh). Made from # run on for joey-run and the gates (scripts/lib/win16Gate.sh). Made from
# your own install disc and S3 driver disk -- private files, never # your own install disc and S3 driver disk -- private files, never

View file

@ -20,7 +20,7 @@
# BFS system volume it finds: a copy of the x86 track, on the machine's # BFS system volume it finds: a copy of the x86 track, on the machine's
# first disk, with the job runner as its UserBootscript. The runner is the # first disk, with the job runner as its UserBootscript. The runner is the
# Haiku VMs' (installer/lib/haiku.sh), so the gates drive both alike: a disk # Haiku VMs' (installer/lib/haiku.sh), so the gates drive both alike: a disk
# labelled HAIKU_JOB_VOLUME holding job.sh (Tracker mounts it), job.sh run # labeled HAIKU_JOB_VOLUME holding job.sh (Tracker mounts it), job.sh run
# there with its output in job.log, then the machine powered off (the # there with its output in job.log, then the machine powered off (the
# kernel's apm and enable_shutdown settings), so QEMU exits. R5 has no # kernel's apm and enable_shutdown settings), so QEMU exits. R5 has no
# serial port to say so on: its serial driver finds the ports through the # serial port to say so on: its serial driver finds the ports through the
@ -70,7 +70,7 @@ beosBfs() {
beosJobRunner() { beosJobRunner() {
cat <<EOF cat <<EOF
# JoeyLib's job runner (installer/lib/beos.sh): when Tracker mounts a disk # JoeyLib's job runner (installer/lib/beos.sh): when Tracker mounts a disk
# labelled $HAIKU_JOB_VOLUME holding job.sh, job.sh is run there (its output in # labeled $HAIKU_JOB_VOLUME holding job.sh, job.sh is run there (its output in
# job.log) and the machine powers off. Without one, an ordinary boot. # job.log) and the machine powers off. Without one, an ordinary boot.
tries=0 tries=0
while [ ! -f /$HAIKU_JOB_VOLUME/job.sh ]; do while [ ! -f /$HAIKU_JOB_VOLUME/job.sh ]; do

View file

@ -13,7 +13,7 @@
# for BeOS compatibility). # for BeOS compatibility).
# #
# The job runner: a VM image's UserBootscript (written by haikuVmPrepare) # The job runner: a VM image's UserBootscript (written by haikuVmPrepare)
# looks for a disk labelled HAIKU_JOB_VOLUME, mounts it, waits for the # looks for a disk labeled HAIKU_JOB_VOLUME, mounts it, waits for the
# desktop and the media services (the boot script runs before they are up, # desktop and the media services (the boot script runs before they are up,
# and a program opening its sound before the media server has started gets # and a program opening its sound before the media server has started gets
# none), runs its job.sh there with the output going to job.log beside it, # none), runs its job.sh there with the output going to job.log beside it,
@ -171,7 +171,7 @@ haikuVmVerify() {
haikuJobRunner() { haikuJobRunner() {
cat <<EOF cat <<EOF
#!/bin/sh #!/bin/sh
# JoeyLib's job runner (installer/lib/haiku.sh): a disk labelled # JoeyLib's job runner (installer/lib/haiku.sh): a disk labeled
# $HAIKU_JOB_VOLUME holding job.sh is mounted, job.sh is run there (its output # $HAIKU_JOB_VOLUME holding job.sh is mounted, job.sh is run there (its output
# in job.log) and the machine shuts down. Without one, an ordinary boot. # in job.log) and the machine shuts down. Without one, an ordinary boot.
serial=/dev/ports/pc_serial0 serial=/dev/ports/pc_serial0

View file

@ -16,7 +16,7 @@
# PS/2 mouse, nothing optional, Setup back to DOS at the end. Then the # PS/2 mouse, nothing optional, Setup back to DOS at the end. Then the
# Sound Blaster 1.5 driver and its virtual device are expanded from the # Sound Blaster 1.5 driver and its virtual device are expanded from the
# disc with its own EXPAND.EXE. # disc with its own EXPAND.EXE.
# 2. The S3 Trio64V+ 1.70.04 driver is put in at 800x600 in 256 colours, # 2. The S3 Trio64V+ 1.70.04 driver is put in at 800x600 in 256 colors,
# and the Sound Blaster driver turned on, editing SYSTEM.INI the way # and the Sound Blaster driver turned on, editing SYSTEM.INI the way
# the driver disk's OEMSETUP.INF (its D808S profile) and Control Panel # the driver disk's OEMSETUP.INF (its D808S profile) and Control Panel
# would. The swap file is off. # would. The swap file is off.

View file

@ -21,10 +21,10 @@
# change there makes the component out # change there makes the component out
# of date # of date
# winDriverSources DIR the files a driver is made from, # winDriverSources DIR the files a driver is made from,
# and its licence # and its license
# The source files' names: C sources and headers, link and module # The source files' names: C sources and headers, link and module
# definition files, makefiles, the licence. # definition files, makefiles, the license.
WIN_DRIVER_LICENSE=LICENSE.md WIN_DRIVER_LICENSE=LICENSE.md
WIN_DRIVER_SOURCES=(-name '*.c' -o -name '*.h' -o -name '*.lnk' -o -name '*.def' -o -name 'Makefile' -o -name '*.mk' -o -name "$WIN_DRIVER_LICENSE") WIN_DRIVER_SOURCES=(-name '*.c' -o -name '*.h' -o -name '*.lnk' -o -name '*.def' -o -name 'Makefile' -o -name '*.mk' -o -name "$WIN_DRIVER_LICENSE")
@ -35,7 +35,7 @@ winDriverBuild() {
local file="$3" local file="$3"
local build local build
[ -f "$dir/$makefile" ] || die "no $makefile in $dir: the driver's project must be beside this repository (or named by its *_DIR variable)" [ -f "$dir/$makefile" ] || die "no $makefile in $dir: the driver's project must be beside this repository (or named by its *_DIR variable)"
[ -f "$dir/$WIN_DRIVER_LICENSE" ] || die "no $WIN_DRIVER_LICENSE in $dir: the driver ships with its licence" [ -f "$dir/$WIN_DRIVER_LICENSE" ] || die "no $WIN_DRIVER_LICENSE in $dir: the driver ships with its license"
build="$(freshBuildDir "$compId")" build="$(freshBuildDir "$compId")"
make -C "$dir" -f "$makefile" WATCOM="$JOEY_OPT/openWatcom" OBJ="$build/obj" BIN="$build/bin" > "$build/make.log" 2>&1 || make -C "$dir" -f "$makefile" WATCOM="$JOEY_OPT/openWatcom" OBJ="$build/obj" BIN="$build/bin" > "$build/make.log" 2>&1 ||
die "building $file failed; the log: $build/make.log" die "building $file failed; the log: $build/make.log"

View file

@ -6,7 +6,7 @@ toolchain.
| Patch | What it fixes | Needed by | | Patch | What it fixes | Needed by |
|---|---|---| |---|---|---|
| 0001-bbb-moveq-mask | A miscompile in Bebbo's post-pass optimizer (`gcc/bbb-opts.c`, on by default at `-O2`). The backend loads a data register's word or byte constant in -128..127 with `moveq`, which writes all 32 bits, sign-extended; the optimizer's register tracker took it for a word or byte write and kept the upper word's old "known clear" mask. After a negative one (a `moveq #-1,dN` for a compare with 0xFFFF, say), its `(e0)` step then deleted a later zero-extension's `moveq #0,dN` before `move.w x,dN` as superfluous, so a 16-bit offset carried 0xFFFF in its upper word into a 32-bit pointer add. It struck `jlSpriteRestoreUnder`'s fused stage restore, which wrote 64 KB below the planes once a neighbouring inline asm call changed the register allocation. Upstream (`amiga6.5`, 2026-09-26) has the same code. | every Amiga build | | 0001-bbb-moveq-mask | A miscompile in Bebbo's post-pass optimizer (`gcc/bbb-opts.c`, on by default at `-O2`). The backend loads a data register's word or byte constant in -128..127 with `moveq`, which writes all 32 bits, sign-extended; the optimizer's register tracker took it for a word or byte write and kept the upper word's old "known clear" mask. After a negative one (a `moveq #-1,dN` for a compare with 0xFFFF, say), its `(e0)` step then deleted a later zero-extension's `moveq #0,dN` before `move.w x,dN` as superfluous, so a 16-bit offset carried 0xFFFF in its upper word into a 32-bit pointer add. It struck `jlSpriteRestoreUnder`'s fused stage restore, which wrote 64 KB below the planes once a neighboring inline asm call changed the register allocation. Upstream (`amiga6.5`, 2026-09-26) has the same code. | every Amiga build |
## License ## License

View file

@ -8,7 +8,7 @@ drivers. The gates depend on every one of them.
|---|---|---| |---|---|---|
| 0002-z80scc-rx-fifo | Makes the Z8530 receive FIFO depth selectable at run time (see below). By default it is 512 bytes, so a POLLED receiver (no RX interrupt) survives the emulator's deaf windows; `JOEY_Z80SCC_RX_FIFO=3` restores the real chip's depth. Originally written for RetroNet. | IIgs serial gates (512); `verify-iigs-serial-loss.sh` (3) | | 0002-z80scc-rx-fifo | Makes the Z8530 receive FIFO depth selectable at run time (see below). By default it is 512 bytes, so a POLLED receiver (no RX interrupt) survives the emulator's deaf windows; `JOEY_Z80SCC_RX_FIFO=3` restores the real chip's depth. Originally written for RetroNet. | IIgs serial gates (512); `verify-iigs-serial-loss.sh` (3) |
| 0003-x68k-crtc-timing | The X68000 CRTC: the graphic high-speed clear's timing (the main defect), and the text raster copy firing on both hsync edges instead of only the front porch | X68000 gates and timing | | 0003-x68k-crtc-timing | The X68000 CRTC: the graphic high-speed clear's timing (the main defect), and the text raster copy firing on both hsync edges instead of only the front porch | X68000 gates and timing |
| 0004-x68k-waitstates-rs232-mouse | Three X68000 changes that edit the same files: VRAM wait states (MAME modelled none), the RS-232C port wired to a slot so `-rs232c null_modem` works, and a backport of upstream commit ab6b6fc9e797 (Patrick Mackinlay, "x68k: add serial mouse"), which provides the `:mouse_port:x68k:X/Y/BTN` inputs | `verify-x68000-serial.sh`, `verify-x68000-mouse.sh`, X68000 timing | | 0004-x68k-waitstates-rs232-mouse | Three X68000 changes that edit the same files: VRAM wait states (MAME modeled none), the RS-232C port wired to a slot so `-rs232c null_modem` works, and a backport of upstream commit ab6b6fc9e797 (Patrick Mackinlay, "x68k: add serial mouse"), which provides the `:mouse_port:x68k:X/Y/BTN` inputs | `verify-x68000-serial.sh`, `verify-x68000-mouse.sh`, X68000 timing |
There is no 0001 any more. It gave the IIgs SCC a 3.6864 MHz PCLK and no There is no 0001 any more. It gave the IIgs SCC a 3.6864 MHz PCLK and no
RTxC clock, to suit a HAL that ran the baud rate generator from PCLK. Stock RTxC clock, to suit a HAL that ran the baud rate generator from PCLK. Stock

View file

@ -8,7 +8,7 @@ the Amiga's playback the same as theirs.
| Patch | What it fixes | Needed by | | Patch | What it fixes | Needed by |
|---|---|---| |---|---|---|
| 0001-protracker-2.3-effects | Four places PTPlayer strays from Protracker 2.3, whose behaviour the module player keeps: its volume slide sets Paula's period back to the note's, which is right for `Axy` but undid `6xy`'s vibrato, `5xy`'s glissando, and a tick of either under `EAx`/`EBx`; its arpeggio counts from the note's index, not the period a slide has left (and never reads on into the next finetune's periods past B-3); and its tremolo is half Protracker's depth, with its saw ramping by its own position rather than the vibrato's. And `6xy` read its vibrato depth into a register whose high byte held whatever came before, so the vibrato read past its table (hidden while the period was set back each tick). | the Amiga's module playback; `tests/host/modPlayerHost.c` checks the module player's side | | 0001-protracker-2.3-effects | Four places PTPlayer strays from Protracker 2.3, whose behavior the module player keeps: its volume slide sets Paula's period back to the note's, which is right for `Axy` but undid `6xy`'s vibrato, `5xy`'s glissando, and a tick of either under `EAx`/`EBx`; its arpeggio counts from the note's index, not the period a slide has left (and never reads on into the next finetune's periods past B-3); and its tremolo is half Protracker's depth, with its saw ramping by its own position rather than the vibrato's. And `6xy` read its vibrato depth into a register whose high byte held whatever came before, so the vibrato read past its table (hidden while the period was set back each tick). | the Amiga's module playback; `tests/host/modPlayerHost.c` checks the module player's side |
## License ## License

View file

@ -4,7 +4,7 @@
# SDL3 port, natively, for the web port, in a browser, for the Win32 port, # SDL3 port, natively, for the web port, in a browser, for the Win32 port,
# under Wine, and for the Win16 port, on Windows 3.11 in DOSBox; the PSP # under Wine, and for the Win16 port, on Windows 3.11 in DOSBox; the PSP
# port's in headless PPSSPP; the Haiku and BeOS ports' in their QEMU VMs). # port's in headless PPSSPP; the Haiku and BeOS ports' in their QEMU VMs).
# A macOS port's app is recognised but not run: nothing here runs Mac # A macOS port's app is recognized but not run: nothing here runs Mac
# programs. # programs.
# #
# Usage: # Usage:
@ -22,7 +22,7 @@
# an IIgs disk image build/iigs/bin/DRAW.2mg (make iigs-game-disk), or # an IIgs disk image build/iigs/bin/DRAW.2mg (make iigs-game-disk), or
# a data disk such as joey.2mg (boots GS/OS with it) # a data disk such as joey.2mg (boots GS/OS with it)
# an example name draw, audio, uber, ... together with --port # an example name draw, audio, uber, ... together with --port
# The machine is recognised from the file itself. A program built outside # The machine is recognized from the file itself. A program built outside
# JoeyLib (make/project.mk) runs the same way: name its file, folder or disk # JoeyLib (make/project.mk) runs the same way: name its file, folder or disk
# image in the project's build/, or give its name with --project and --port. # image in the project's build/, or give its name with --project and --port.
# #
@ -117,8 +117,8 @@
# with the keypad's Enter (DOSBox keeps Alt+Enter for its own full screen), # with the keypad's Enter (DOSBox keeps Alt+Enter for its own full screen),
# puts it on the whole screen. Interactive runs keep that DVX in # puts it on the whole screen. Interactive runs keep that DVX in
# build/dvx/run (the project's with --project), so what the program saves # build/dvx/run (the project's with --project), so what the program saves
# (in DVX's CONFIG folder), and whether it was left on the whole screen, is # (in its folder in DVX's USER folder), and whether it was left on the whole
# still there next time. # screen, is still there next time.
# #
# The serial port is the machine's usual one: the IIgs modem port (the # 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 # printer port and a Super Serial Card are not connected), the X68000's
@ -1025,7 +1025,12 @@ runPsp() {
# ends, and what it wrote in its folder (saves, its log) is copied back. # ends, and what it wrote in its folder (saves, its log) is copied back.
# --headless: no window and no sound, at one window pixel per stage pixel, # --headless: no window and no sound, at one window pixel per stage pixel,
# and --snapshot is QEMU's picture of the whole screen. --seconds counts # and --snapshot is QEMU's picture of the whole screen. --seconds counts
# from when the job starts, not from the boot. # from when the job starts, not from the boot. In a headless run the host
# times them and stops the VM after them (and the snapshot), so the shot never
# misses a VM whose clock strays (BeOS R5's ran twice as fast before it got
# an instruction-counted TSC, BEOS_VM_ICOUNT; it still runs about 5% slow). A
# run in a window brings its files back, so it ends by itself, in the VM's
# seconds.
runBe() { runBe() {
local name local name
local dir local dir
@ -1052,8 +1057,11 @@ runBe() {
done done
fi fi
# The job outlasts --seconds a little, so the snapshot finds the # The job outlasts --seconds a little, so the snapshot finds the
# program still up. # program still up; a headless one runs until the host stops it.
jobSeconds=$((${seconds:-$beForeverSeconds} + beSnapshotSeconds)) jobSeconds=$((${seconds:-$beForeverSeconds} + beSnapshotSeconds))
if [ "$headless" = "1" ]; then
jobSeconds=$beForeverSeconds
fi
beJobScript "$name" "$name" "" "$jobSeconds" "${settings[@]}" > "$work/job/job.sh" beJobScript "$name" "$name" "" "$jobSeconds" "${settings[@]}" > "$work/job/job.sh"
beJobDisk "$work/job.img" "$work/job" || fail "could not make the VM's job disk" beJobDisk "$work/job.img" "$work/job" || fail "could not make the VM's job disk"
echo "joey-run: running $name in the $detected VM (QEMU); it powers off when the program ends." echo "joey-run: running $name in the $detected VM (QEMU); it powers off when the program ends."
@ -1071,7 +1079,11 @@ runBe() {
if [ -n "$snapshot" ]; then if [ -n "$snapshot" ]; then
beQmp "$work" screendump "$snapshot" || echo "joey-run: no screen to snapshot" >&2 beQmp "$work" screendump "$snapshot" || echo "joey-run: no screen to snapshot" >&2
fi fi
if [ "$headless" = "1" ]; then
beStop
else
beWaitExit "$work" "$((beSnapshotSeconds + beShutdownSeconds))" || beStop beWaitExit "$work" "$((beSnapshotSeconds + beShutdownSeconds))" || beStop
fi
else else
wait "$emuPid" || true wait "$emuPid" || true
fi fi

View file

@ -20,7 +20,7 @@ $(call requireTool,$(AMIGA_PTPLAYER)/ptplayer.asm,toolchain:amiga)
# 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)
# -fno-common: gcc 6 merges a program's uninitialised global into a library # -fno-common: gcc 6 merges a program's uninitialized global into a library
# one of the same name; gcc 10 on (every other port) refuses the link. # one of the same name; gcc 10 on (every other port) refuses the link.
CFLAGS := $(COMMON_CFLAGS) -m68000 -fomit-frame-pointer -fno-common -noixemul -D__OSCOMPAT -I$(SRC_DIR)/amiga -I$(SRC_68K) -I$(PTPLAYER_DIR) -MMD -MP CFLAGS := $(COMMON_CFLAGS) -m68000 -fomit-frame-pointer -fno-common -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

View file

@ -15,7 +15,7 @@ $(call requireTool,$(HAIKU32_ROOT)/cross/bin/i586-pc-haiku-g++,toolchain:beos)
$(call requireTool,$(BEOS_R5_SYSROOT)/system-lib/libbe.so,toolchain:beos) $(call requireTool,$(BEOS_R5_SYSROOT)/system-lib/libbe.so,toolchain:beos)
BEOS_GCC2 := BE_R5_SYSROOT=$(BEOS_R5_SYSROOT) $(HAIKU32_GCC2) $(HAIKU32_ROOT) BEOS_GCC2 := BE_R5_SYSROOT=$(BEOS_R5_SYSROOT) $(HAIKU32_GCC2) $(HAIKU32_ROOT)
# -fno-common: gcc 2.95 merges a program's uninitialised global into a # -fno-common: gcc 2.95 merges a program's uninitialized global into a
# library one of the same name; every other port's compiler refuses the link. # library one of the same name; every other port's compiler refuses the link.
HOSTED_CC := $(BEOS_GCC2) gcc -fno-common HOSTED_CC := $(BEOS_GCC2) gcc -fno-common
HOSTED_CXX := $(BEOS_GCC2) g++ HOSTED_CXX := $(BEOS_GCC2) g++

View file

@ -74,6 +74,8 @@ PROJECT_BUILD ?= $(PROJECT_DIR)/build
export JOEY_BUILD export JOEY_BUILD
INCLUDE_DIR := $(REPO_DIR)/include INCLUDE_DIR := $(REPO_DIR)/include
# JoeyLib's version, from the one place it is set (joey/platform.h).
JOEYLIB_VERSION := $(shell sed -n 's/^\#define JL_VERSION_STRING "\(.*\)"$$/\1/p' $(INCLUDE_DIR)/joey/platform.h)
# Machine-specific code lives in src/<machine>/ (iigs, amiga, atarist, dos, # Machine-specific code lives in src/<machine>/ (iigs, amiga, atarist, dos,
# x68000, dvx); code shared across machines of one CPU lives in a processor # x68000, dvx); code shared across machines of one CPU lives in a processor
# folder (src/m68k, src/x86); the hosted core and its backends in src/hosted # folder (src/m68k, src/x86); the hosted core and its backends in src/hosted
@ -109,7 +111,7 @@ GENERIC_C_SRCS := $(filter-out %/spriteEmitStub.c, $(wildcard $(SRC_DIR)/generic
# 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. EXTRA_CFLAGS is the one # 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); # 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. # make/iigs.mk, which does not use COMMON_CFLAGS, honors 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)
ifdef PLATFORM ifdef PLATFORM
@ -170,6 +172,10 @@ programTarget = $(call programBin,$(1))
# DVX SDK) build/<port>/data/<name>/DATA. # DVX SDK) build/<port>/data/<name>/DATA.
programDataDir = $(if $(PROGRAM_DATA_APART),$(PROGRAM_BUILD)/data/$(call programName,$(1))/DATA,$(call programDir,$(1))/DATA) programDataDir = $(if $(PROGRAM_DATA_APART),$(PROGRAM_BUILD)/data/$(call programName,$(1))/DATA,$(call programDir,$(1))/DATA)
# $(call programVersion,ID): its version, major.minor.patch: its own
# VERSION, else the project's PROGRAM_VERSION, else 1.0.0.
programVersion = $(or $(call programVar,$(1),VERSION),$(PROGRAM_VERSION),1.0.0)
# $(call programOptionalDir,ID): where its optional DATA files are staged: # $(call programOptionalDir,ID): where its optional DATA files are staged:
# ones it runs without (a game that JIT-compiles a level's sprites when the # ones it runs without (a game that JIT-compiles a level's sprites when the
# level's pre-compiled bank is missing). Only the IIgs disk packer treats # level's pre-compiled bank is missing). Only the IIgs disk packer treats

View file

@ -114,13 +114,25 @@ $(LIB): $(LIB_OBJS)
# DVX_KIND what the help and resource texts call a program: "<title> is a # DVX_KIND what the help and resource texts call a program: "<title> is a
# <kind> program" (default JoeyLib) # <kind> program" (default JoeyLib)
# The vendor and the author are the project's own: it must set them. # The vendor and the author are the project's own: it must set them.
#
# Each program is also an add-on package, bin/packages/<name>.dpk, which
# anyone installs with DVX's Software program: its id is vendor.name, its
# title and summary the program's TITLE and HELP, its version the program's
# (programVersion), and the rest the project's (no apostrophes in any):
# DVX_PUBLISHER who publishes it (default DVX_AUTHOR)
# DVX_LICENSE its license, as Software shows it (MIT, say)
# DVX_HOMEPAGE its web site
# DVX_PKG_KEY the secret key file (BASE.SEC, from the SDK's
# `dvxpkg keygen "Your Name" BASE`) to sign it with, so
# whoever has BASE.KEY can tell it is yours; unsigned
# without it
DVX_KIND := JoeyLib DVX_KIND := JoeyLib
PROGRAM_EXT := .app PROGRAM_EXT := .app
PROGRAM_DATA_APART := yes PROGRAM_DATA_APART := yes
# What every program's help says after "<title> is a <kind> program.". # What every program's help says after "<title> is a <kind> program.".
DVX_HELP_WINDOW := It runs in its own window at a virtual 70 frames a second; while the window is active every key goes to it except Alt+Tab (switch programs), Ctrl+Esc (Task Manager) and Alt+F4 (close it). Game > Exit also closes it. DVX_HELP_WINDOW := It runs in its own window at a virtual 70 frames a second; while the window is active every key goes to it except Alt+Tab (switch programs), Ctrl+Esc (Task Manager) and Alt+F4 (close it). Game > Exit also closes it.
DVX_HELP_SCREEN := Game > Full Screen, or Alt+Enter, puts it on the whole screen at 320x200 in 256 colours, and back in its window. It starts the way it was left. Alt+Tab or Ctrl+Esc brings the desktop back while it runs on; coming back to it gives it the whole screen again. DVX_HELP_SCREEN := Game > Full Screen, or Alt+Enter, puts it on the whole screen at 320x200 in 256 colors, and back in its window. It starts the way it was left. Alt+Tab or Ctrl+Esc brings the desktop back while it runs on; coming back to it gives it the whole screen again.
programNameRule = $(call lc,$(1)) programNameRule = $(call lc,$(1))
programDir = $(or $(call programVar,$(1),BINDIR),$(BINDIR))/apps/$(DVX_VENDOR)/$(call programName,$(1)) programDir = $(or $(call programVar,$(1),BINDIR),$(BINDIR))/apps/$(DVX_VENDOR)/$(call programName,$(1))
programTarget = app-$(call programName,$(1)) programTarget = app-$(call programName,$(1))
@ -140,6 +152,7 @@ programRules = $(call programDvxRules,$(1),$(call programName,$(1)))
# $(1) the id, $(2) the name. Everything that may hold a comma (the title, # $(1) the id, $(2) the name. Everything that may hold a comma (the title,
# the help line) is looked up here rather than passed in, and every line # the help line) is looked up here rather than passed in, and every line
# written out is shellQuote'd, so a title or help line may hold quotes. # written out is shellQuote'd, so a title or help line may hold quotes.
# PKG_LICENCE is spelled the British way because sdk.mk names it so.
define programDvxRules define programDvxRules
$(call requireSet,DVX_VENDOR,the project's own vendor folder$(comma) which its programs install in: 1 to 8 letters$(comma) digits or _) $(call requireSet,DVX_VENDOR,the project's own vendor folder$(comma) which its programs install in: 1 to 8 letters$(comma) digits or _)
$(call requireSet,DVX_AUTHOR,the author DVX names for the project's programs) $(call requireSet,DVX_AUTHOR,the author DVX names for the project's programs)
@ -153,8 +166,16 @@ app-$(2): $(LIB) $(call programVar,$(1),SRCS) $(PORT_ENTRY) $(call programVar,$(
'KIND = app' \ 'KIND = app' \
'NAME = $(2)' \ 'NAME = $(2)' \
'VENDOR = $(DVX_VENDOR)' \ 'VENDOR = $(DVX_VENDOR)' \
$(call shellQuote,PKG_TITLE = $(call programTitle,$(1))) \
'PKG_VERSION = $(call programVersion,$(1))' \
$(call shellQuote,PKG_SUMMARY = $(call programVar,$(1),HELP)) \
$(call shellQuote,PKG_PUBLISHER = $(or $(DVX_PUBLISHER),$(DVX_AUTHOR))) \
$(call shellQuote,PKG_AUTHOR = $(DVX_AUTHOR)) \
$(call shellQuote,PKG_LICENCE = $(DVX_LICENSE)) \
$(call shellQuote,PKG_HOMEPAGE = $(DVX_HOMEPAGE)) \
'PKG_KEY = $(if $(DVX_PKG_KEY),$(abspath $(DVX_PKG_KEY)))' \
'LIBS = $(LIB)' \ 'LIBS = $(LIB)' \
'DATA = $(if $(call programVar,$(1),DATA),$(call programDataDir,$(1)))' \ 'DATA = $(if $(call programVar,$(1),DATA),$$$$(wildcard $(call programDataDir,$(1))/*))' \
$(call shellQuote,CFLAGS = $(APP_CFLAGS) $(call programVar,$(1),CFLAGS) -I$(dir $(abspath $(firstword $(call programVar,$(1),SRCS)))) -DDVX_PORT_APP_NAME=$(call shellQuote,$(call cQuote,$(call programTitle,$(1))))) \ $(call shellQuote,CFLAGS = $(APP_CFLAGS) $(call programVar,$(1),CFLAGS) -I$(dir $(abspath $(firstword $(call programVar,$(1),SRCS)))) -DDVX_PORT_APP_NAME=$(call shellQuote,$(call cQuote,$(call programTitle,$(1))))) \
'include $$$$(SDK_DIR)/sdk.mk' | $(REPO_DIR)/make/support/dvx/writeIfChanged.sh $(APPSDIR)/$(2)/Makefile 'include $$$$(SDK_DIR)/sdk.mk' | $(REPO_DIR)/make/support/dvx/writeIfChanged.sh $(APPSDIR)/$(2)/Makefile
@printf '%s\n' \ @printf '%s\n' \
@ -182,9 +203,11 @@ app-$(2): $(LIB) $(call programVar,$(1),SRCS) $(PORT_ENTRY) $(call programVar,$(
@mkdir -p $(or $(call programVar,$(1),BINDIR),$(BINDIR)) @mkdir -p $(or $(call programVar,$(1),BINDIR),$(BINDIR))
rm -rf $(call programDir,$(1)) rm -rf $(call programDir,$(1))
cp -R $(APPSDIR)/$(2)/pkg/apps $(or $(call programVar,$(1),BINDIR),$(BINDIR))/ cp -R $(APPSDIR)/$(2)/pkg/apps $(or $(call programVar,$(1),BINDIR),$(BINDIR))/
@mkdir -p $(or $(call programVar,$(1),BINDIR),$(BINDIR))/packages
cp $(APPSDIR)/$(2)/$(2).dpk $(or $(call programVar,$(1),BINDIR),$(BINDIR))/packages/
# Its icon, the one Program Manager shows: icon32 among its resources, a # Its icon, the one Program Manager shows: icon32 among its resources, a
# 32x32 BMP on DVX's grey, which sdk.mk takes from the project's folder. # 32x32 BMP on DVX's gray, which sdk.mk takes from the project's folder.
$(APPSDIR)/$(2)/icon32.bmp: $(call programIcon,$(1)) $(ICON_BAKE) $(APPSDIR)/$(2)/icon32.bmp: $(call programIcon,$(1)) $(ICON_BAKE)
@mkdir -p $$(dir $$@) @mkdir -p $$(dir $$@)
$(ICON_BAKE) bmp $$< $$@ $(ICON_BAKE) bmp $$< $$@

View file

@ -49,26 +49,33 @@ PROGRAMS := HELLO PATTERN DRAW KEYS SERIAL SERTEST SERMOUSE SAVE TILEBANK S
TEST_PROGRAMS := SCCPROBE SERLOSS SERLOSSS SERSLOT SERFAST DIRTYMRK UBERPROB AUDIOPRB AUDPAR PALPROBE FASTRAM WBLAUNCH TEST_PROGRAMS := SCCPROBE SERLOSS SERLOSSS SERSLOT SERFAST DIRTYMRK UBERPROB AUDIOPRB AUDPAR PALPROBE FASTRAM WBLAUNCH
# Whose these are, as a project names its own: on DVX the vendor folder # Whose these are, as a project names its own: on DVX the vendor folder
# they install in and their author (make/dvx.mk), on macOS the start of # they install in, their author, and their package's license (make/dvx.mk),
# their apps' identifiers (make/macos.mk). DVX's help and resource texts # on macOS the start of their apps' identifiers (make/macos.mk). DVX's help
# call them JoeyLib examples. # and resource texts call them JoeyLib examples. Their version is
# JoeyLib's.
DVX_VENDOR := kpunch DVX_VENDOR := kpunch
DVX_AUTHOR := Scott Duensing DVX_AUTHOR := Scott Duensing
DVX_LICENSE := MIT
PROGRAM_VERSION := $(JOEYLIB_VERSION)
DVX_KIND := JoeyLib example DVX_KIND := JoeyLib example
MACOS_ID_PREFIX := com.kangaroopunch.joeylib MACOS_ID_PREFIX := com.kangaroopunch.joeylib
# The IIgs build has never included HELLO. # The examples that show text share one font (examples/common).
HELLO_SRCS := $(EXAMPLES)/hello/hello.c EXAMPLE_FONT := $(EXAMPLES)/common/exampleFont.c
HELLO_PORTS := amiga atarist ste dos x68000 dvx EXAMPLE_FONT_CFLAGS := -I$(EXAMPLES)/common
HELLO_SRCS := $(EXAMPLES)/hello/hello.c $(EXAMPLE_FONT)
HELLO_CFLAGS := $(EXAMPLE_FONT_CFLAGS)
HELLO_TITLE := Hello HELLO_TITLE := Hello
HELLO_HELP := The smallest JoeyLib program: it clears the screen and waits for a key. HELLO_HELP := The smallest JoeyLib program: it says hello, its version, and the machine, and waits for a key.
PATTERN_SRCS := $(EXAMPLES)/pattern/pattern.c PATTERN_SRCS := $(EXAMPLES)/pattern/pattern.c
PATTERN_TITLE := Pattern PATTERN_TITLE := Pattern
PATTERN_HELP := Eight bands of gradient palettes, one per band of scan lines. PATTERN_HELP := Eight bands of gradient palettes, one per band of scan lines.
DRAW_SRCS := $(EXAMPLES)/draw/draw.c DRAW_SRCS := $(EXAMPLES)/draw/draw.c
DRAW_TITLE := Draw DRAW_TITLE := Draw
DRAW_HELP := The drawing primitives: pixels, lines, circles, rectangles and flood fills. DRAW_HELP := The drawing primitives: pixels, lines, circles, rectangles and flood fills.
KEYS_SRCS := $(EXAMPLES)/keys/keys.c KEYS_SRCS := $(EXAMPLES)/keys/keys.c $(EXAMPLE_FONT)
KEYS_CFLAGS := $(EXAMPLE_FONT_CFLAGS)
KEYS_TITLE := Keys KEYS_TITLE := Keys
KEYS_HELP := Keyboard, typed characters and mouse: held keys light up and typed text appears. Escape ends it. KEYS_HELP := Keyboard, typed characters and mouse: held keys light up and typed text appears. Escape ends it.
SERIAL_SRCS := $(EXAMPLES)/serial/serial.c SERIAL_SRCS := $(EXAMPLES)/serial/serial.c
@ -78,17 +85,21 @@ SERTEST_SRCS := $(EXAMPLES)/sertest/sertest.c
SERTEST_TITLE := SerTest SERTEST_TITLE := SerTest
SERTEST_HELP := The serial port test program. SERTEST_HELP := The serial port test program.
SERTEST_AMIGA_NAME := SerTest SERTEST_AMIGA_NAME := SerTest
SERMOUSE_SRCS := $(EXAMPLES)/sermouse/sermouse.c SERMOUSE_SRCS := $(EXAMPLES)/sermouse/sermouse.c $(EXAMPLE_FONT)
SERMOUSE_PORTS := x68000 SERMOUSE_CFLAGS := $(EXAMPLE_FONT_CFLAGS)
SERMOUSE_TITLE := SerMouse
SERMOUSE_HELP := The serial port and the mouse together: an echo test on the line while the mouse is read and drawn.
SERMOUSE_AMIGA_NAME := SerMouse
SAVE_SRCS := $(EXAMPLES)/save/save.c SAVE_SRCS := $(EXAMPLES)/save/save.c
SAVE_TITLE := Save SAVE_TITLE := Save
SAVE_HELP := Saved files: counts its runs in a file in its settings folder. SAVE_HELP := Saved files: counts its runs in a file in its user folder.
TILEBANK_SRCS := $(EXAMPLES)/tilebank/tilebank.c TILEBANK_SRCS := $(EXAMPLES)/tilebank/tilebank.c
TILEBANK_TITLE := TileBank TILEBANK_TITLE := TileBank
TILEBANK_HELP := The tile bank check: loads a baked tile bank two ways and checks every pixel. TILEBANK_HELP := The tile bank check: loads a baked tile bank two ways and checks every pixel.
TILEBANK_AMIGA_NAME := TileBank TILEBANK_AMIGA_NAME := TileBank
SCB_SRCS := $(EXAMPLES)/scb/scb.c SCB_SRCS := $(EXAMPLES)/scb/scb.c
SCB_PORTS := x68000 SCB_TITLE := SCB
SCB_HELP := The per-scanline palette under its heaviest load: all sixteen palettes, changing as often as every line.
JOY_SRCS := $(EXAMPLES)/joy/joy.c JOY_SRCS := $(EXAMPLES)/joy/joy.c
JOY_TITLE := Joy JOY_TITLE := Joy
JOY_HELP := Joysticks: shows the position and buttons of each joystick. JOY_HELP := Joysticks: shows the position and buttons of each joystick.
@ -103,17 +114,19 @@ AUDIO_TITLE := Audio
AUDIO_HELP := Sound: tracker music, a MOD, sound effects, a streamed tone and a noise sweep. AUDIO_HELP := Sound: tracker music, a MOD, sound effects, a streamed tone and a noise sweep.
UBER_SRCS := $(EXAMPLES)/uber/uber.c UBER_SRCS := $(EXAMPLES)/uber/uber.c
UBER_TITLE := Uber UBER_TITLE := Uber
UBER_HELP := The JoeyLib conformance and speed test: every drawing call, timed and checked. Its results go to joeylog.txt in its settings folder. UBER_HELP := The JoeyLib conformance and speed test: every drawing call timed and checked, its results in joeylog.txt in its user folder.
ADV_SRCS := $(EXAMPLES)/adventure/adventure.c ADV_SRCS := $(EXAMPLES)/adventure/adventure.c $(EXAMPLE_FONT)
ADV_CFLAGS := $(EXAMPLE_FONT_CFLAGS)
ADV_TITLE := Adventure ADV_TITLE := Adventure
ADV_HELP := A small adventure game. ADV_HELP := A Sierra-style adventure demo: two rooms, a verb cursor, hotspots, and an inventory.
ADV2_SRCS := $(EXAMPLES)/adventure2/adventure2.c ADV2_SRCS := $(EXAMPLES)/adventure2/adventure2.c $(EXAMPLE_FONT)
ADV2_CFLAGS := $(EXAMPLE_FONT_CFLAGS)
ADV2_TITLE := Adventure 2 ADV2_TITLE := Adventure 2
ADV2_HELP := A second small adventure game. ADV2_HELP := One room drawn as Sierra SCI games draw it, with priority and control screens.
# Every example that ships as its own bootable IIgs volume (make # Every example also ships as its own bootable IIgs volume (make
# iigs-game-disks). # iigs-game-disks): the shared disk (make iigs-disk) holds only the first few.
$(foreach p,DRAW PATTERN KEYS JOY SPRITE SCROLL SAVE AUDIO ADV ADV2 UBER SERIAL SERTEST,$(eval $(p)_IIGS_GAME_DISK := yes)) $(foreach p,$(PROGRAMS),$(eval $(p)_IIGS_GAME_DISK := yes))
# IIgs serial probes for the gates: SCCPROBE (verify-iigs-serial.sh: does # IIgs serial probes for the gates: SCCPROBE (verify-iigs-serial.sh: does
# opening the modem port leave the printer port, AppleTalk's channel, alive?) # opening the modem port leave the printer port, AppleTalk's channel, alive?)
@ -174,7 +187,7 @@ UBERPROB_SRCS := $(UBER_SRCS)
UBERPROB_PORTS := iigs dvx win16 win32 haiku haiku32 beos atarist ste amiga UBERPROB_PORTS := iigs dvx win16 win32 haiku haiku32 beos atarist ste amiga
UBERPROB_CFLAGS := -DUBER_PROBE -DUBER_FRAMES=1 UBERPROB_CFLAGS := -DUBER_PROBE -DUBER_FRAMES=1
UBERPROB_TITLE := Uber Probe UBERPROB_TITLE := Uber Probe
UBERPROB_HELP := UBER with its probe rows: the present checked against the stage on screen. Its results go to joeylog.txt in its settings folder. UBERPROB_HELP := UBER with its probe rows: the present checked against the stage on screen, the results in joeylog.txt in its user folder.
UBERPROB_IIGS_NAME := UBER UBERPROB_IIGS_NAME := UBER
UBERPROB_IIGS_BINDIR := $(PROGRAM_BUILD)/probe/bin UBERPROB_IIGS_BINDIR := $(PROGRAM_BUILD)/probe/bin
UBERPROB_DVX_BINDIR := $(PROGRAM_BUILD)/probe UBERPROB_DVX_BINDIR := $(PROGRAM_BUILD)/probe
@ -208,7 +221,7 @@ AUDPAR_IIGS_BINDIR := $(PROGRAM_BUILD)/probe/bin
AUDPAR_DATA := $(call programDataDir,AUDPAR)/test.mod AUDPAR_DATA := $(call programDataDir,AUDPAR)/test.mod
AUDPAR_IIGS_DATA := $(call programDataDir,AUDPAR)/TEST.NTP AUDPAR_IIGS_DATA := $(call programDataDir,AUDPAR)/TEST.NTP
$(call programDataDir,AUDPAR)/TEST.NTP: $(REPO_DIR)/assets/test.mod $(REPO_DIR)/tools/joeymod/joeymod $(MOD_TO_NTP) $(call programDataDir,AUDPAR)/TEST.NTP $(call programDataDir,AUDIO)/TEST.NTP: $(REPO_DIR)/assets/test.mod $(REPO_DIR)/tools/joeymod/joeymod $(MOD_TO_NTP)
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
$(REPO_DIR)/tools/joeymod/joeymod $< $@ $(REPO_DIR)/tools/joeymod/joeymod $< $@
@ -216,10 +229,10 @@ $(call programDataDir,AUDPAR)/%: $(REPO_DIR)/assets/%
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
cp $< $@ cp $< $@
# AUDIO loads DATA/test.mod and DATA/test.sfx from beside the program. The # AUDIO loads DATA/test.mod and DATA/test.sfx from beside the program; the
# IIgs plays its chip music through NTP and stages no MOD. # IIgs plays the module as NinjaTrackerPlus's TEST.NTP (AUDPAR's rule).
AUDIO_DATA := $(addprefix $(call programDataDir,AUDIO)/,test.mod test.sfx) AUDIO_DATA := $(addprefix $(call programDataDir,AUDIO)/,test.mod test.sfx)
AUDIO_IIGS_DATA := AUDIO_IIGS_DATA := $(addprefix $(call programDataDir,AUDIO)/,TEST.NTP test.sfx)
$(call programDataDir,AUDIO)/%: $(REPO_DIR)/assets/% $(call programDataDir,AUDIO)/%: $(REPO_DIR)/assets/%
@mkdir -p $(dir $@) @mkdir -p $(dir $@)

View file

@ -13,7 +13,7 @@ include $(dir $(lastword $(MAKEFILE_LIST)))/common.mk
$(call requireTool,$(HAIKU32_ROOT)/cross/bin/i586-pc-haiku-g++,toolchain:haiku32) $(call requireTool,$(HAIKU32_ROOT)/cross/bin/i586-pc-haiku-g++,toolchain:haiku32)
# -fno-common: gcc 2.95 merges a program's uninitialised global into a # -fno-common: gcc 2.95 merges a program's uninitialized global into a
# library one of the same name; every other port's compiler refuses the link. # library one of the same name; every other port's compiler refuses the link.
HOSTED_CC := $(HAIKU32_GCC2) $(HAIKU32_ROOT) gcc -fno-common HOSTED_CC := $(HAIKU32_GCC2) $(HAIKU32_ROOT) gcc -fno-common
HOSTED_CXX := $(HAIKU32_GCC2) $(HAIKU32_ROOT) g++ HOSTED_CXX := $(HAIKU32_GCC2) $(HAIKU32_ROOT) g++

View file

@ -141,11 +141,11 @@ $(LIB): $(LIB_OBJS)
PROGRAM_FOLDERS := yes PROGRAM_FOLDERS := yes
# The file one program, $(1) its id, is linked to: the program file itself # The file one program, $(1) its id, is linked to: the program file itself
# unless the port packs it afterwards. # unless the port packs it afterward.
hostedLinkOut = $(or $(call HOSTED_LINK_OUT,$(1)),$(call programBin,$(1))) hostedLinkOut = $(or $(call HOSTED_LINK_OUT,$(1)),$(call programBin,$(1)))
# One program, $(1) its id: compiled and linked in one step with the library # One program, $(1) its id: compiled and linked in one step with the library
# and the backend's libraries (and the maths library the mixer's tables use), its DATA too where the port packs it in, and its icon where # and the backend's libraries (and the math library the mixer's tables use), its DATA too where the port packs it in, and its icon where
# the link takes it, then packed where the port packs it. -MMD leaves the # the link takes it, then packed where the port packs it. -MMD leaves the
# dependency file beside what is linked, for header edits; it is named after # dependency file beside what is linked, for header edits; it is named after
# the program only while the link compiles one source, so an icon reaches # the program only while the link compiles one source, so an icon reaches

View file

@ -25,7 +25,7 @@
# ID_LDFLAGS extra link flags (-s) # ID_LDFLAGS extra link flags (-s)
# ID_PORTS the ports it builds for; every port when unset # ID_PORTS the ports it builds for; every port when unset
# ID_NAME its file name without the extension; by default the ID, # ID_NAME its file name without the extension; by default the ID,
# on the Amiga Capitalised (Hello), on DVX lower case # on the Amiga Capitalized (Hello), on DVX lower case
# ID_FOLDER non-empty: it ships in a folder of its own, bin/<name>/, # ID_FOLDER non-empty: it ships in a folder of its own, bin/<name>/,
# with its DATA beside it (the Amiga, ST, DOS and X68000; # with its DATA beside it (the Amiga, ST, DOS and X68000;
# on the IIgs, DVX and macOS every program is packaged # on the IIgs, DVX and macOS every program is packaged
@ -44,8 +44,11 @@
# ID_TITLE its title: DVX's window title (default: its Amiga-style # ID_TITLE its title: DVX's window title (default: its Amiga-style
# name), the PSP menu's, and a Mac app's display name # name), the PSP menu's, and a Mac app's display name
# (default: its name) # (default: its name)
# ID_HELP DVX: the line of its help file (no apostrophes: the help is # ID_HELP DVX: the line of its help file and its package's summary
# (at most 127 characters, and no apostrophes: both are
# written through the shell in quotes) # written through the shell in quotes)
# ID_VERSION its version, major.minor.patch (DVX's package); by
# default the project's PROGRAM_VERSION, else 1.0.0
# ID_IIGS_OVERFLOW_CAP IIgs: link large overflow segments (clang-build.sh's # ID_IIGS_OVERFLOW_CAP IIgs: link large overflow segments (clang-build.sh's
# JOEY_OVERFLOW_CAP) # JOEY_OVERFLOW_CAP)
# ID_IIGS_GAME_DISK IIgs: non-empty: it ships as its own bootable volume # ID_IIGS_GAME_DISK IIgs: non-empty: it ships as its own bootable volume

View file

@ -103,7 +103,7 @@ while [ $# -gt 0 ]; do
-MP) -MP)
;; ;;
-lm) -lm)
# R5 keeps the maths in libroot and has no libm. # R5 keeps the math in libroot and has no libm.
if [ -z "$r5" ]; then if [ -z "$r5" ]; then
args+=(-lm) args+=(-lm)
fi fi

View file

@ -243,7 +243,7 @@ JOEY_MIN_HEAP="${JOEY_MIN_HEAP:-0}"
# so this stays below 0x10000. # so this stays below 0x10000.
# #
# DEFAULT 0 = overflow segments use --segment-cap (the historical # DEFAULT 0 = overflow segments use --segment-cap (the historical
# behaviour); a program opts in with ID_IIGS_OVERFLOW_CAP in its description # behavior); a program opts in with ID_IIGS_OVERFLOW_CAP in its description
# (make/programs.mk). AGI links 5 segments instead of 19, STAXI 4 instead of # (make/programs.mk). AGI links 5 segments instead of 19, STAXI 4 instead of
# 26 (both now separate projects built on JoeyLib). # 26 (both now separate projects built on JoeyLib).
# #
@ -252,7 +252,7 @@ JOEY_MIN_HEAP="${JOEY_MIN_HEAP:-0}"
# The real chain: 26 bank-aligned segments starved the Memory Manager, so # The real chain: 26 bank-aligned segments starved the Memory Manager, so
# NTP's 34K NewHandle failed, gNTPReady stayed 0 and every SFX call # NTP's 34K NewHandle failed, gNTPReady stayed 0 and every SFX call
# short-circuited. IIgs builds were simply SILENT. Freeing that memory let # short-circuited. IIgs builds were simply SILENT. Freeing that memory let
# audio initialise, and a latent bug fired the first time NTPstreamsound # audio initialize, and a latent bug fired the first time NTPstreamsound
# was ever reached (fixed in src/iigs/audio_full.c: SFX now requires a # was ever reached (fixed in src/iigs/audio_full.c: SFX now requires a
# running replayer, as jlpAudioStopSfx already did). # running replayer, as jlpAudioStopSfx already did).
# #

View file

@ -23,13 +23,13 @@
# owcc: # owcc:
# -O2 -O3 owcc's include -oa, which assumes no global is ever reached # -O2 -O3 owcc's include -oa, which assumes no global is ever reached
# through a pointer (JoeyLib's are), and -on; this is -ox # through a pointer (JoeyLib's are), and -on; this is -ox
# (branch, inline, loop and scheduling optimisation, no stack # (branch, inline, loop and scheduling optimization, no stack
# checks) and -ot instead # checks) and -ot instead
# -MMD -MP Watcom's dependency file names the object and its source as # -MMD -MP Watcom's dependency file names the object and its source as
# given (-ad, -adt, -add); each header is then made a target of # given (-ad, -adt, -add); each header is then made a target of
# its own (-MP), so a deleted header does not stop make # its own (-MP), so a deleted header does not stop make
# -Wno-error dropped (owcc misreads it); -w is owcc's -w0 # -Wno-error dropped (owcc misreads it); -w is owcc's -w0
# -lm dropped: the maths is in Watcom's C library # -lm dropped: the math is in Watcom's C library
# A program (no -c): each source is compiled as above, the objects and # A program (no -c): each source is compiled as above, the objects and
# libraries are linked into a 32-bit image (NAME.rex, its stack STACK_BYTES) # libraries are linked into a 32-bit image (NAME.rex, its stack STACK_BYTES)
# and that is bound to the supervisor (WIN386.EXT), which makes NAME.EXE, a # and that is bound to the supervisor (WIN386.EXT), which makes NAME.EXE, a

View file

@ -30,7 +30,7 @@
# tiled2joey -- a Tiled map (TMX or TMJ) -> a tile sheet and maps. # tiled2joey -- a Tiled map (TMX or TMJ) -> a tile sheet and maps.
# tileBankExpect -- the tile-bank gates' expected pixels # tileBankExpect -- the tile-bank gates' expected pixels
# (tests/host/tileBankStage.sh). # (tests/host/tileBankStage.sh).
# traceLogo -- a flat-colour PNG logo -> a layered SVG, through the # traceLogo -- a flat-color PNG logo -> a layered SVG, through the
# potrace program: remakes assets/joeylibLogo.svg. # potrace program: remakes assets/joeylibLogo.svg.
# uberPerfTable -- every port's UBER log -> one Markdown table of ops/sec # uberPerfTable -- every port's UBER log -> one Markdown table of ops/sec
# against a reference port. # against a reference port.
@ -45,6 +45,15 @@ include $(dir $(lastword $(MAKEFILE_LIST)))/common.mk
BUILD_DIR := $(JOEY_BUILD)/tools BUILD_DIR := $(JOEY_BUILD)/tools
TOOLS_DIR := $(REPO_DIR)/tools TOOLS_DIR := $(REPO_DIR)/tools
# A tool lands under its name in one rename. The ports' makes, run side by
# side (make -j all), each ask this file for the tools they bake with, so two
# can build one tool at once, and one ran it while another was still writing
# it ("Permission denied"). Each links to a name of its own (the shell's
# process id) and renames it into place: whoever runs the tool finds the old
# one or the new one, whole.
TOOL_TMP = $@.$$$$.tmp
TOOL_LAND = mv -f $(TOOL_TMP) $@
# The tools of one C file (and any headers beside it) over tools/common (PNG, # The tools of one C file (and any headers beside it) over tools/common (PNG,
# DEFLATE, whole files, byte buffers): tools/<name>/<name>.c, or a test's # DEFLATE, whole files, byte buffers): tools/<name>/<name>.c, or a test's
# helper in tests/host. # helper in tests/host.
@ -92,7 +101,7 @@ spritebake-x68000: $(BUILD_DIR)/spritebake-x68000
define commonToolRule define commonToolRule
$(BUILD_DIR)/$(1): $(2) $(wildcard $(dir $(2))*.h) $(TOOL_COMMON_SRC) $(TOOL_COMMON_HDRS) $(BUILD_DIR)/$(1): $(2) $(wildcard $(dir $(2))*.h) $(TOOL_COMMON_SRC) $(TOOL_COMMON_HDRS)
@mkdir -p $$(dir $$@) @mkdir -p $$(dir $$@)
$(HOST_CC) $(HOST_CFLAGS) -I$(TOOLS_DIR)/common $$(filter %.c,$$^) -o $$@ -lm $(HOST_CC) $(HOST_CFLAGS) -I$(TOOLS_DIR)/common $$(filter %.c,$$^) -o $$(TOOL_TMP) -lm && $$(TOOL_LAND)
endef endef
$(foreach t,$(COMMON_TOOLS),$(eval $(call commonToolRule,$(t),$(TOOLS_DIR)/$(t)/$(t).c))) $(foreach t,$(COMMON_TOOLS),$(eval $(call commonToolRule,$(t),$(TOOLS_DIR)/$(t)/$(t).c)))
$(eval $(call commonToolRule,tileBankExpect,$(REPO_DIR)/tests/host/tileBankExpect.c)) $(eval $(call commonToolRule,tileBankExpect,$(REPO_DIR)/tests/host/tileBankExpect.c))
@ -102,35 +111,35 @@ $(eval $(call commonToolRule,tileBankExpect,$(REPO_DIR)/tests/host/tileBankExpec
define gateToolRule define gateToolRule
$(BUILD_DIR)/$(1): $(GATE_DIR)/$(1).c $(GATE_COMMON_SRC) $(GATE_COMMON_HDRS) $(TOOL_COMMON_SRC) $(TOOL_COMMON_HDRS) $(BUILD_DIR)/$(1): $(GATE_DIR)/$(1).c $(GATE_COMMON_SRC) $(GATE_COMMON_HDRS) $(TOOL_COMMON_SRC) $(TOOL_COMMON_HDRS)
@mkdir -p $$(dir $$@) @mkdir -p $$(dir $$@)
$(HOST_CC) $(HOST_CFLAGS) -I$(TOOLS_DIR)/common -I$(GATE_DIR)/common $$(filter %.c,$$^) -o $$@ -lm $(HOST_CC) $(HOST_CFLAGS) -I$(TOOLS_DIR)/common -I$(GATE_DIR)/common $$(filter %.c,$$^) -o $$(TOOL_TMP) -lm && $$(TOOL_LAND)
endef endef
$(foreach t,$(GATE_TOOLS),$(eval $(call gateToolRule,$(t)))) $(foreach t,$(GATE_TOOLS),$(eval $(call gateToolRule,$(t))))
$(MODLOOPEND_BIN): $(MODLOOPEND_SRC) $(SRC_CORE)/modStopMarker.h $(MODLOOPEND_BIN): $(MODLOOPEND_SRC) $(SRC_CORE)/modStopMarker.h
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
$(HOST_CC) $(HOST_CFLAGS) -I$(SRC_CORE) $(MODLOOPEND_SRC) -o $@ $(HOST_CC) $(HOST_CFLAGS) -I$(SRC_CORE) $(MODLOOPEND_SRC) -o $(TOOL_TMP) && $(TOOL_LAND)
$(BUILD_DIR)/spritebake-dos: $(SPRITEBAKE_SRC) $(SRC_X86)/spriteEmitX86.c $(SPRITEBAKE_HDRS) $(BUILD_DIR)/spritebake-dos: $(SPRITEBAKE_SRC) $(SRC_X86)/spriteEmitX86.c $(SPRITEBAKE_HDRS)
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
$(HOST_CC) $(HOST_CFLAGS) -DJOEYLIB_PLATFORM_DOS $(SPRITEBAKE_INC) -I$(SRC_X86) $(filter %.c,$^) -o $@ $(HOST_CC) $(HOST_CFLAGS) -DJOEYLIB_PLATFORM_DOS $(SPRITEBAKE_INC) -I$(SRC_X86) $(filter %.c,$^) -o $(TOOL_TMP) && $(TOOL_LAND)
$(BUILD_DIR)/spritebake-amiga: $(SPRITEBAKE_SRC) $(SRC_68K)/spriteEmitPlanar68k.c $(SRC_68K)/spriteC2p.c $(SPRITEBAKE_HDRS) $(BUILD_DIR)/spritebake-amiga: $(SPRITEBAKE_SRC) $(SRC_68K)/spriteEmitPlanar68k.c $(SRC_68K)/spriteC2p.c $(SPRITEBAKE_HDRS)
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
$(HOST_CC) $(HOST_CFLAGS) -DJOEYLIB_PLATFORM_AMIGA $(SPRITEBAKE_INC) -I$(SRC_DIR)/amiga -I$(SRC_68K) $(filter %.c,$^) -o $@ $(HOST_CC) $(HOST_CFLAGS) -DJOEYLIB_PLATFORM_AMIGA $(SPRITEBAKE_INC) -I$(SRC_DIR)/amiga -I$(SRC_68K) $(filter %.c,$^) -o $(TOOL_TMP) && $(TOOL_LAND)
# The X68000 runs the Amiga's planar 68k emitter unchanged (see # The X68000 runs the Amiga's planar 68k emitter unchanged (see
# make/x68000.mk), so its bake is the same source under its own target byte. # make/x68000.mk), so its bake is the same source under its own target byte.
$(BUILD_DIR)/spritebake-x68000: $(SPRITEBAKE_SRC) $(SRC_68K)/spriteEmitPlanar68k.c $(SRC_68K)/spriteC2p.c $(SPRITEBAKE_HDRS) $(BUILD_DIR)/spritebake-x68000: $(SPRITEBAKE_SRC) $(SRC_68K)/spriteEmitPlanar68k.c $(SRC_68K)/spriteC2p.c $(SPRITEBAKE_HDRS)
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
$(HOST_CC) $(HOST_CFLAGS) -DJOEYLIB_PLATFORM_X68000 $(SPRITEBAKE_INC) -I$(SRC_DIR)/x68000 -I$(SRC_68K) $(filter %.c,$^) -o $@ $(HOST_CC) $(HOST_CFLAGS) -DJOEYLIB_PLATFORM_X68000 $(SPRITEBAKE_INC) -I$(SRC_DIR)/x68000 -I$(SRC_68K) $(filter %.c,$^) -o $(TOOL_TMP) && $(TOOL_LAND)
$(BUILD_DIR)/spritebake-atarist: $(SPRITEBAKE_SRC) $(SRC_68K)/spriteEmitInterleaved68k.c $(SRC_68K)/spriteC2p.c $(SPRITEBAKE_HDRS) $(BUILD_DIR)/spritebake-atarist: $(SPRITEBAKE_SRC) $(SRC_68K)/spriteEmitInterleaved68k.c $(SRC_68K)/spriteC2p.c $(SPRITEBAKE_HDRS)
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
$(HOST_CC) $(HOST_CFLAGS) -DJOEYLIB_PLATFORM_ATARIST $(SPRITEBAKE_INC) -I$(SRC_DIR)/atarist -I$(SRC_68K) $(filter %.c,$^) -o $@ $(HOST_CC) $(HOST_CFLAGS) -DJOEYLIB_PLATFORM_ATARIST $(SPRITEBAKE_INC) -I$(SRC_DIR)/atarist -I$(SRC_68K) $(filter %.c,$^) -o $(TOOL_TMP) && $(TOOL_LAND)
$(BUILD_DIR)/spritebake-iigs: $(SPRITEBAKE_SRC) $(SRC_DIR)/iigs/spriteEmitIigs.c $(SPRITEBAKE_HDRS) $(BUILD_DIR)/spritebake-iigs: $(SPRITEBAKE_SRC) $(SRC_DIR)/iigs/spriteEmitIigs.c $(SPRITEBAKE_HDRS)
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
$(HOST_CC) $(HOST_CFLAGS) -DJOEYLIB_PLATFORM_IIGS $(SPRITEBAKE_INC) $(filter %.c,$^) -o $@ $(HOST_CC) $(HOST_CFLAGS) -DJOEYLIB_PLATFORM_IIGS $(SPRITEBAKE_INC) $(filter %.c,$^) -o $(TOOL_TMP) && $(TOOL_LAND)
clean-tools: clean-tools:
rm -rf $(BUILD_DIR) rm -rf $(BUILD_DIR)

View file

@ -15,7 +15,7 @@
# pointers -- bound to Watcom's supervisor, so a program is one Windows 3.x # pointers -- bound to Watcom's supervisor, so a program is one Windows 3.x
# NE executable NAME.EXE, in bin/ or in its own bin/NAME/ with its DATA # NE executable NAME.EXE, in bin/ or in its own bin/NAME/ with its DATA
# beside it, calling USER, GDI, KERNEL and MMSYSTEM through the supervisor. # beside it, calling USER, GDI, KERNEL and MMSYSTEM through the supervisor.
# The frame is 8-bit indexed (a 256-colour DIB holding all 16 palettes) and # The frame is 8-bit indexed (a 256-color DIB holding all 16 palettes) and
# the sound 22.05 kHz mono, what Windows 3.1 and a 386 handle well. The port # the sound 22.05 kHz mono, what Windows 3.1 and a 386 handle well. The port
# makes the program's folder the current directory when the window opens. # makes the program's folder the current directory when the window opens.
@ -36,8 +36,8 @@ HOSTED_DEFINES := -DJOEYLIB_PLATFORM_HOSTED -DJOEYLIB_HOSTED_WIN16 -DHOSTED_F
HOSTED_SDK_CFLAGS := -I$(WIN16_WATCOM)/h/win -I$(SRC_X86) HOSTED_SDK_CFLAGS := -I$(WIN16_WATCOM)/h/win -I$(SRC_X86)
HOSTED_LINK := HOSTED_LINK :=
PROGRAM_EXT := .EXE PROGRAM_EXT := .EXE
# Its icon, the one Program Manager shows and the window shows minimised: # Its icon, the one Program Manager shows and the window shows minimized:
# 32x32 in the 16 VGA colours, bound in from a resource script. # 32x32 in the 16 VGA colors, bound in from a resource script.
HOSTED_ICON = $(call programIconDir,$(1))/icon.rc HOSTED_ICON = $(call programIconDir,$(1))/icon.rc
HOSTED_ICON_BAKE = $(call hostedIconScript,$(1),ico16) HOSTED_ICON_BAKE = $(call hostedIconScript,$(1),ico16)
HOSTED_ICON_LINK = $(call HOSTED_ICON,$(1)) HOSTED_ICON_LINK = $(call HOSTED_ICON,$(1))

View file

@ -30,7 +30,7 @@ HOSTED_SDK_CFLAGS := -I$(SRC_X86)
HOSTED_LINK := -mwindows -static -lwinmm -lgdi32 -Wl,--subsystem,windows:4.0 -Wl,--major-os-version,4 -Wl,--minor-os-version,0 HOSTED_LINK := -mwindows -static -lwinmm -lgdi32 -Wl,--subsystem,windows:4.0 -Wl,--major-os-version,4 -Wl,--minor-os-version,0
PROGRAM_EXT := .EXE PROGRAM_EXT := .EXE
# Its icon, the one Explorer and the window show: 16, 32 and 48 pixels, each # Its icon, the one Explorer and the window show: 16, 32 and 48 pixels, each
# in 256 colours and in 32-bit, compiled into a resource object it links. # in 256 colors and in 32-bit, compiled into a resource object it links.
WIN32_WINDRES := $(patsubst %-clang,%-windres,$(WIN32_CC)) WIN32_WINDRES := $(patsubst %-clang,%-windres,$(WIN32_CC))
HOSTED_ICON = $(call programIconDir,$(1))/icon.o HOSTED_ICON = $(call programIconDir,$(1))/icon.o
HOSTED_ICON_BAKE = $(call hostedIconScript,$(1),ico) && $(WIN32_WINDRES) $(call programIconDir,$(1))/icon.rc -o $(call HOSTED_ICON,$(1)) HOSTED_ICON_BAKE = $(call hostedIconScript,$(1),ico) && $(WIN32_WINDRES) $(call programIconDir,$(1))/icon.rc -o $(call HOSTED_ICON,$(1))

View file

@ -21,7 +21,7 @@
# log lands directly in build/<build>/bin/joeylog.txt (for the ST build # log lands directly in build/<build>/bin/joeylog.txt (for the ST build
# where build/tools/uberPerfTable expects it). A copy is kept as # where build/tools/uberPerfTable expects it). A copy is kept as
# joeylog-MACHINE.txt beside it, so runs on several machines can be # joeylog-MACHINE.txt beside it, so runs on several machines can be
# compared afterwards. # compared afterward.
# #
# scripts/bench-atarist.sh # the ST build on a plain ST, default 90s timeout # scripts/bench-atarist.sh # the ST build on a plain ST, default 90s timeout
# scripts/bench-atarist.sh megast # the ST build on a Mega ST # scripts/bench-atarist.sh megast # the ST build on a Mega ST

View file

@ -7,8 +7,8 @@
# #
# Stages a scratch DVX (scripts/lib/dvxGate.sh) whose desktop is UBER, boots # Stages a scratch DVX (scripts/lib/dvxGate.sh) whose desktop is UBER, boots
# it in DOSBox Staging under Xvfb, and waits for UBER's "press any key to # it in DOSBox Staging under Xvfb, and waits for UBER's "press any key to
# exit" line in its joeylog.txt -- which a DVX program writes to its settings # exit" line in its joeylog.txt -- which a DVX program writes to its user
# folder, CONFIG\APPS\<VENDOR>\UBER on the staged drive. The log is copied to # folder, USER\APPS\<VENDOR>\UBER on the staged drive. The log is copied to
# build/dvx/bin/joeylog.txt (build/tools/diffUberHashes and # build/dvx/bin/joeylog.txt (build/tools/diffUberHashes and
# build/tools/uberPerfTable read it from there) and DOSBox is stopped. # build/tools/uberPerfTable read it from there) and DOSBox is stopped.
# #

View file

@ -16,7 +16,7 @@
# Prints the UBER rows and leaves the full log in build/x68000/joeylog.txt. # Prints the UBER rows and leaves the full log in build/x68000/joeylog.txt.
# #
# Takes a few minutes. The run EXITS ON COMPLETION, not on a frame budget: UBER # Takes a few minutes. The run EXITS ON COMPLETION, not on a frame budget: UBER
# ends by clearing the whole stage to colour index 2 and presenting it before it # ends by clearing the whole stage to color index 2 and presenting it before it
# blocks in jlWaitForAnyKey, so the Lua below watches GVRAM for that screen. # blocks in jlWaitForAnyKey, so the Lua below watches GVRAM for that screen.
# (It used to burn a flat 300,000 frames -- ~90 minutes of emulated time -- a # (It used to burn a flat 300,000 frames -- ~90 minutes of emulated time -- a
# budget sized back when this port still drew through the slow generic renderer. # budget sized back when this port still drew through the slow generic renderer.
@ -70,13 +70,13 @@ x68kDataDisk "$work/data.xdf" || exit 1
x68kXdf add "$work/data.xdf" "$work/UBER.X" UBER.X >/dev/null || exit 1 x68kXdf add "$work/data.xdf" "$work/UBER.X" UBER.X >/dev/null || exit 1
# Completion-driven exit. UBER's last act before jlWaitForAnyKey is # Completion-driven exit. UBER's last act before jlWaitForAnyKey is
# jlSurfaceClear(jlgStage, 2) + jlStagePresent, so an all-colour-2 stage in GVRAM # jlSurfaceClear(jlgStage, 2) + jlStagePresent, so an all-color-2 stage in GVRAM
# is the "hashes are computed and flushed" signal. The benchmark's own # is the "hashes are computed and flushed" signal. The benchmark's own
# jlSurfaceClear/jlStagePresent ops can paint that colour for an instant, so the # jlSurfaceClear/jlStagePresent ops can paint that color for an instant, so the
# reading only counts once it has held for GREEN_STABLE frames -- the finished # reading only counts once it has held for GREEN_STABLE frames -- the finished
# screen never changes again, a mid-benchmark frame never lasts. # screen never changes again, a mid-benchmark frame never lasts.
# #
# jlLogFlush only fflush()es; the directory entry is finalised by the atexit # jlLogFlush only fflush()es; the directory entry is finalized by the atexit
# fclose. So the keypress that releases jlWaitForAnyKey still has to be posted, # fclose. So the keypress that releases jlWaitForAnyKey still has to be posted,
# and MAME must not be killed until UBER has returned to Human68k -- hence the # and MAME must not be killed until UBER has returned to Human68k -- hence the
# repeated posts and the EXIT_SETTLE wait before exiting. # repeated posts and the EXIT_SETTLE wait before exiting.
@ -94,11 +94,11 @@ local EXIT_SETTLE = 900 -- keypress -> fclose -> back at the Human68k prompt
-- Where the present puts pixels, which depends on the port's -- Where the present puts pixels, which depends on the port's
-- X68K_PRESENT_TEXT switch: -- X68K_PRESENT_TEXT switch:
-- text (default) -- crtmod 12, the four 1bpp TEXT planes at \$E00000, 128 -- text (default) -- crtmod 12, the four 1bpp TEXT planes at \$E00000, 128
-- bytes per row, planes 128 KB apart. A pixel's colour is -- bytes per row, planes 128 KB apart. A pixel's color is
-- one bit from each plane at the same offset. -- one bit from each plane at the same offset.
-- gfx -- crtmod 12, 256-colour GRAPHICS plane at \$C00000, one -- gfx -- crtmod 12, 256-color GRAPHICS plane at \$C00000, one
-- 16-bit word per pixel, 512 words per row. -- 16-bit word per pixel, 512 words per row.
-- The 320x200 stage is centred at origin (96, 156) either way. -- The 320x200 stage is centered at origin (96, 156) either way.
local presentPlane = "$PRESENT_PLANE" local presentPlane = "$PRESENT_PLANE"
-- The text plane holds two pages, and the port flips between them with the -- The text plane holds two pages, and the port flips between them with the
-- text Y scroll (CRTC R11, which reads back): rows are counted from the page -- text Y scroll (CRTC R11, which reads back): rows are counted from the page

View file

@ -45,4 +45,4 @@ if [ "$fails" != 0 ]; then
echo "check-fileroots: FAIL" >&2 echo "check-fileroots: FAIL" >&2
exit 1 exit 1
fi fi
echo "check-fileroots: PASS (roots honoured under JL_HAS_FILE_ROOTS; the flagless layout unchanged)" echo "check-fileroots: PASS (roots honored under JL_HAS_FILE_ROOTS; the flagless layout unchanged)"

View file

@ -7,7 +7,7 @@
# every port that mixes .MOD in software plays through (DOS, the ST, the # every port that mixes .MOD in software plays through (DOS, the ST, the
# X68000, and the hosted ports). Builds tests/host/modPlayerHost.c, which # X68000, and the hosted ports). Builds tests/host/modPlayerHost.c, which
# includes the player, with the host cc and the address and undefined- # includes the player, with the host cc and the address and undefined-
# behaviour sanitizers, and runs it: the Protracker behaviours the player # behavior sanitizers, and runs it: the Protracker behaviors the player
# keeps, where a song ends, the 8-bit and 16-bit outputs, and mangled # keeps, where a song ends, the 8-bit and 16-bit outputs, and mangled
# modules played without reading outside the player's copy. When the # modules played without reading outside the player's copy. When the
# Amiga's PTPlayer is installed, its period and vibrato tables are handed # Amiga's PTPlayer is installed, its period and vibrato tables are handed
@ -81,4 +81,4 @@ if [ "$status" -ne 0 ]; then
echo "$label: FAIL - ${counts:-harness error} checks pass" echo "$label: FAIL - ${counts:-harness error} checks pass"
exit 1 exit 1
fi fi
echo "$label: PASS ($counts checks: Protracker's behaviours, song ends, both outputs, and mangled modules, under the sanitizers)" echo "$label: PASS ($counts checks: Protracker's behaviors, song ends, both outputs, and mangled modules, under the sanitizers)"

View file

@ -5,7 +5,7 @@
# #
# machine = svga_s3 an S3 Trio64, the card the S3 Trio64V+ # machine = svga_s3 an S3 Trio64, the card the S3 Trio64V+
# Windows driver the image is set up with # Windows driver the image is set up with
# drives (800x600, 256 colours) # drives (800x600, 256 colors)
# memsize = 16 16 MB, plenty for Windows 3.11; the image # memsize = 16 16 MB, plenty for Windows 3.11; the image
# runs without a swap file # runs without a swap file
# cputype = 386 the port's floor is a 386: an instruction a # cputype = 386 the port's floor is a 386: an instruction a

View file

@ -34,7 +34,7 @@
# taken through its boot loader's menu to the # taken through its boot loader's menu to the
# fail-safe (VESA) video mode BEOS_VIDEO_MODE: # fail-safe (VESA) video mode BEOS_VIDEO_MODE:
# its own drivers know none of QEMU's cards, # its own drivers know none of QEMU's cards,
# and without it the screen is 16 greys. # and without it the screen is 16 grays.
# Serial port: WORK/serial.log, QMP: # Serial port: WORK/serial.log, QMP:
# WORK/qmp.sock # WORK/qmp.sock
# beStop stop QEMU # beStop stop QEMU
@ -61,9 +61,16 @@ BE_AUDIODEV="${BE_AUDIODEV:-none,id=beSound}"
# Haiku's machine: memory (MB) and processors. # Haiku's machine: memory (MB) and processors.
HAIKU_VM_MEMORY_MB=1024 HAIKU_VM_MEMORY_MB=1024
HAIKU_VM_CPUS=2 HAIKU_VM_CPUS=2
# BeOS R5's machine: memory (MB) and processor. # BeOS R5's machine: memory (MB) and processor, and its time stamp counter
# counted in instructions (icount): R5 times itself by the TSC, rated at
# boot, and the host's (5 GHz here) overflowed the rating -- it said -842 MHz
# one boot and -735 the next, and the clock ran about twice as fast as the
# host's, a different amount each boot. With shift=auto the TSC rates 1000
# MHz and the clock keeps the host's time to about 5%, for a boot about 3 s
# longer.
BEOS_VM_MEMORY_MB=256 BEOS_VM_MEMORY_MB=256
BEOS_VM_CPU="pentium2" BEOS_VM_CPU="pentium2"
BEOS_VM_ICOUNT="${BEOS_VM_ICOUNT-shift=auto}" # empty: no icount
# Its boot loader's menu: open it (space, pressed while the loader starts), # Its boot loader's menu: open it (space, pressed while the loader starts),
# go to "Select fail-safe video mode", choose the mode BEOS_VIDEO_ROWS rows # go to "Select fail-safe video mode", choose the mode BEOS_VIDEO_ROWS rows
# below Default in the mode list (the list QEMU 11.1.2's VGA BIOS gives: # below Default in the mode list (the list QEMU 11.1.2's VGA BIOS gives:
@ -221,7 +228,7 @@ beStartBeos() {
local presses=() local presses=()
local rows=() local rows=()
shift 2 shift 2
"$BE_QEMU" -machine pc -cpu "$BEOS_VM_CPU" -m "$BEOS_VM_MEMORY_MB" \ "$BE_QEMU" -machine pc -cpu "$BEOS_VM_CPU" -m "$BEOS_VM_MEMORY_MB" ${BEOS_VM_ICOUNT:+-icount "$BEOS_VM_ICOUNT"} \
-drive "file=$BEOS_VM_DIR/floppy.img,format=raw,if=floppy,readonly=on" \ -drive "file=$BEOS_VM_DIR/floppy.img,format=raw,if=floppy,readonly=on" \
-drive "file=$BEOS_VM_DIR/system.img,format=raw,index=0,media=disk,snapshot=on" \ -drive "file=$BEOS_VM_DIR/system.img,format=raw,index=0,media=disk,snapshot=on" \
-drive "file=$disk,format=raw,index=1,media=disk" -boot a -vga std \ -drive "file=$disk,format=raw,index=1,media=disk" -boot a -vga std \

View file

@ -21,7 +21,7 @@ DVX_PROBE_DIR="${DVX_PROBE_DIR:-$JOEY_REPO/build/dvx/probe}"
# log. # log.
DVX_VENDOR="${DVX_VENDOR:-$(make -s -f "$JOEY_REPO/make/dvx.mk" --eval 'dvxVendor: ; @echo $(DVX_VENDOR)' dvxVendor 2> /dev/null)}" DVX_VENDOR="${DVX_VENDOR:-$(make -s -f "$JOEY_REPO/make/dvx.mk" --eval 'dvxVendor: ; @echo $(DVX_VENDOR)' dvxVendor 2> /dev/null)}"
DVX_LOG_NAME=joeylog.txt DVX_LOG_NAME=joeylog.txt
# The port's own settings file in a program's settings folder, and its # The port's own settings file in a program's user folder, and its
# full-screen and square-pixels switches (src/dvx/hal.c). # full-screen and square-pixels switches (src/dvx/hal.c).
DVX_SETTINGS_FILE=JOEYLIB.INI DVX_SETTINGS_FILE=JOEYLIB.INI
DVX_SETTINGS_SECTION=display DVX_SETTINGS_SECTION=display
@ -33,9 +33,9 @@ DVX_FULL_SCREEN_LOG="Video: full screen 320x200 at 8 bpp"
# DVX.LOG's line when a program cannot be loaded (a missing file, a name # DVX.LOG's line when a program cannot be loaded (a missing file, a name
# that is not 8.3, an unresolved import). # that is not 8.3, an unresolved import).
DVX_LOAD_FAILED_LOG="DXE load failed" DVX_LOAD_FAILED_LOG="DXE load failed"
# The runtime parts of toolchains/opt/dvx (all but the SDK). # The runtime parts of toolchains/opt/dvx (all but the SDK and SETUP.EXE).
DVX_RUNTIME="dvx.exe config system libs widgets apps" DVX_RUNTIME="dvx.exe system libs widgets apps shared"
# The desktop the gates run: 640x480 in 16-bit colour, DVX's own DOS test # The desktop the gates run: 640x480 in 16-bit color, DVX's own DOS test
# mode, where a program's window shows the stage at 1x. A caller may set # mode, where a program's window shows the stage at 1x. A caller may set
# others before dvxStage (joey-run shows 1024x768, the stage at 3x). # others before dvxStage (joey-run shows 1024x768, the stage at 3x).
DVX_VIDEO_W=${DVX_VIDEO_W:-640} DVX_VIDEO_W=${DVX_VIDEO_W:-640}
@ -70,7 +70,8 @@ dvxNeedBuild() {
# APPDIRs (each a folder holding NAME.app, from dvxAppDir or anywhere else), # APPDIRs (each a folder holding NAME.app, from dvxAppDir or anywhere else),
# installed as apps/VENDOR/NAME. The first one is the desktop. DIR may hold # installed as apps/VENDOR/NAME. The first one is the desktop. DIR may hold
# an earlier staging: the runtime and the programs are copied over it, and # an earlier staging: the runtime and the programs are copied over it, and
# what DVX wrote there (its settings, the programs' CONFIG folders) stays. # what DVX wrote there (its user folder, USER\: its settings and log, and
# each program's) stays.
dvxStage() { dvxStage() {
local dir="$1" local dir="$1"
local part local part
@ -87,21 +88,21 @@ dvxStage() {
cp -a "$app" "$dir/DVX/apps/$DVX_VENDOR/" cp -a "$app" "$dir/DVX/apps/$DVX_VENDOR/"
done done
name="$(basename "$1")" name="$(basename "$1")"
dvxIniSet "$dir/DVX/config/DVX.INI" "apps/$DVX_VENDOR/$name/$name.app" dvxIniSet "$(dvxIni "$dir")" "apps/$DVX_VENDOR/$name/$name.app"
} }
# dvxSettings DIR PROGRAM SWITCH... -- have PROGRAM start with the port's # dvxSettings DIR PROGRAM SWITCH... -- have PROGRAM start with the port's
# own display switches on (DVX_SETTINGS_FULL_SCREEN, DVX_SETTINGS_SQUARE) in # own display switches on (DVX_SETTINGS_FULL_SCREEN, DVX_SETTINGS_SQUARE) in
# the DVX staged in DIR: its JOEYLIB.INI, [display], in the program's # the DVX staged in DIR: its JOEYLIB.INI, [display], in the program's user
# settings folder, as the Game menu leaves it. DOS names are case-blind, so # folder, as the Game menu leaves it. DOS names are case-blind, so an
# an existing folder of any case is used. # existing folder of any case is used.
dvxSettings() { dvxSettings() {
local conf local conf
local key local key
conf="$(find "$1/DVX" -ipath "*/config/apps/$DVX_VENDOR/$2" -type d -print -quit 2>/dev/null)" conf="$(find "$1/DVX" -ipath "*/user/apps/$DVX_VENDOR/$2" -type d -print -quit 2>/dev/null)"
if [ -z "$conf" ]; then if [ -z "$conf" ]; then
conf="$1/DVX/config/apps/$DVX_VENDOR/$2" conf="$(dvxUserDir "$1")/apps/$DVX_VENDOR/$2"
mkdir -p "$conf" mkdir -p "$conf"
fi fi
printf '[%s]\r\n' "$DVX_SETTINGS_SECTION" > "$conf/$DVX_SETTINGS_FILE" printf '[%s]\r\n' "$DVX_SETTINGS_SECTION" > "$conf/$DVX_SETTINGS_FILE"
@ -118,6 +119,37 @@ dvxFullScreen() {
} }
# dvxUserDir DIR -- print the user folder (USER\) of the DVX staged in DIR,
# where DVX writes everything (its DVX.INI and DVX.LOG, and each program's
# settings and saves), making it when DVX has not yet. DOS names are
# case-blind, so a folder of any case is DVX's.
dvxUserDir() {
local user
user="$(find "$1/DVX" -mindepth 1 -maxdepth 1 -type d -iname user -print -quit 2>/dev/null)"
if [ -z "$user" ]; then
user="$1/DVX/user"
mkdir -p "$user"
fi
printf '%s\n' "$user"
}
# dvxIni DIR -- print the DVX.INI the DVX staged in DIR starts with: the user
# folder's, made from the one DVX ships (SYSTEM\DVX.INI) when it is not there
# yet, as DVX's first start makes it.
dvxIni() {
local user
local ini
user="$(dvxUserDir "$1")"
ini="$(find "$user" -maxdepth 1 -type f -iname dvx.ini -print -quit)"
if [ -z "$ini" ]; then
ini="$user/DVX.INI"
cp "$(find "$1/DVX/system" -maxdepth 1 -type f -iname dvx.ini -print -quit)" "$ini"
fi
printf '%s\n' "$ini"
}
# dvxIniSet INI DESKTOP -- DVX.INI for a run: DESKTOP as the desktop program, # dvxIniSet INI DESKTOP -- DVX.INI for a run: DESKTOP as the desktop program,
# no first-run Welcome window, the gate video mode, and no desktop sounds (a # no first-run Welcome window, the gate video mode, and no desktop sounds (a
# recording gate must hear only the program). DOS line ends are kept. # recording gate must hear only the program). DOS line ends are kept.
@ -192,10 +224,10 @@ EOF
# dvxProgramLog DIR PROGRAM -- print the path of PROGRAM's log in the staged # dvxProgramLog DIR PROGRAM -- print the path of PROGRAM's log in the staged
# DVX: in its settings folder, CONFIG\ plus the folder the .app is in (DOS # DVX: in its user folder, USER\APPS\ plus the folder the .app is in (DOS
# names are upper case on the host). Prints nothing while there is none. # names are upper case on the host). Prints nothing while there is none.
dvxProgramLog() { dvxProgramLog() {
find "$1/DVX" -ipath "*/config/apps/$DVX_VENDOR/$2/$DVX_LOG_NAME" -print -quit 2>/dev/null find "$1/DVX" -ipath "*/user/apps/$DVX_VENDOR/$2/$DVX_LOG_NAME" -print -quit 2>/dev/null
} }
@ -235,7 +267,7 @@ dvxKeysTyped() {
# place a program that failed to load is explained. # place a program that failed to load is explained.
dvxDvxLog() { dvxDvxLog() {
local log local log
log="$(find "$1/DVX" -maxdepth 1 -iname dvx.log -print -quit)" log="$(find "$1/DVX" -maxdepth 2 -ipath "*/user/dvx.log" -print -quit)"
[ -n "$log" ] && tr -d '\r' < "$log" | tail -"${2:-15}" [ -n "$log" ] && tr -d '\r' < "$log" | tail -"${2:-15}"
return 0 return 0
} }
@ -245,7 +277,7 @@ dvxDvxLog() {
# ends); FILE is left out when there is none. # ends); FILE is left out when there is none.
dvxDvxLogCopy() { dvxDvxLogCopy() {
local log local log
log="$(find "$1/DVX" -maxdepth 1 -iname dvx.log -print -quit)" log="$(find "$1/DVX" -maxdepth 2 -ipath "*/user/dvx.log" -print -quit)"
[ -n "$log" ] && tr -d '\r' < "$log" > "$2" [ -n "$log" ] && tr -d '\r' < "$log" > "$2"
return 0 return 0
} }

View file

@ -10,7 +10,10 @@
# go first, which must leave Up down (src/core/inputInternal.h, # go first, which must leave Up down (src/core/inputInternal.h,
# INPUT_KEY_SECOND). Each step holds its keys (letting go of every other), # INPUT_KEY_SECOND). Each step holds its keys (letting go of every other),
# waits KEYS_SETTLE_SECONDS, and reads KEYS's Return, Ctrl, Alt, Up, Down, # waits KEYS_SETTLE_SECONDS, and reads KEYS's Return, Ctrl, Alt, Up, Down,
# Left, and Right cells (examples/keys) as seven digits, keysCells's. # Left, and Right cells (examples/keys) as seven digits, keysCells's --
# again every KEYS_SETTLE_SECONDS until they read right or KEYS_WAIT_SECONDS
# have gone, so a machine whose frame lags the key passes and a wrong state
# that stays still fails.
# #
# The gate sets KEYS_LABEL (its name, for the lines it prints) and defines, # The gate sets KEYS_LABEL (its name, for the lines it prints) and defines,
# by the key names below: # by the key names below:
@ -25,6 +28,7 @@
# ralt. # ralt.
KEYS_SETTLE_SECONDS="${KEYS_SETTLE_SECONDS:-0.5}" KEYS_SETTLE_SECONDS="${KEYS_SETTLE_SECONDS:-0.5}"
KEYS_WAIT_SECONDS="${KEYS_WAIT_SECONDS:-5}"
KEYS_FAILS="" KEYS_FAILS=""
KEYS_STEPS=0 KEYS_STEPS=0
KEYS_HELD=() KEYS_HELD=()
@ -37,6 +41,7 @@ keysHold() {
local want="$2" local want="$2"
local key local key
local lit local lit
local deadline
shift 2 shift 2
for key in "${KEYS_HELD[@]}"; do for key in "${KEYS_HELD[@]}"; do
[[ " $* " == *" $key "* ]] || keysRelease "$key" [[ " $* " == *" $key "* ]] || keysRelease "$key"
@ -45,8 +50,14 @@ keysHold() {
[[ " ${KEYS_HELD[*]} " == *" $key "* ]] || keysPress "$key" [[ " ${KEYS_HELD[*]} " == *" $key "* ]] || keysPress "$key"
done done
KEYS_HELD=("$@") KEYS_HELD=("$@")
deadline=$((SECONDS + KEYS_WAIT_SECONDS))
while true; do
sleep "$KEYS_SETTLE_SECONDS" sleep "$KEYS_SETTLE_SECONDS"
lit=$(keysRead) lit=$(keysRead)
if [ "$lit" = "$want" ] || [ "$SECONDS" -ge "$deadline" ]; then
break
fi
done
KEYS_STEPS=$((KEYS_STEPS + 1)) KEYS_STEPS=$((KEYS_STEPS + 1))
echo "$KEYS_LABEL: $name: Return/Ctrl/Alt/Up/Down/Left/Right $lit" echo "$KEYS_LABEL: $name: Return/Ctrl/Alt/Up/Down/Left/Right $lit"
[ "$lit" = "$want" ] || KEYS_FAILS="${KEYS_FAILS:+$KEYS_FAILS; }$name: lit $lit, want $want" [ "$lit" = "$want" ] || KEYS_FAILS="${KEYS_FAILS:+$KEYS_FAILS; }$name: lit $lit, want $want"

View file

@ -95,7 +95,7 @@ win16Stage() {
# display the first time a program uses it and keeps the result in WIN.INI # display the first time a program uses it and keeps the result in WIN.INI
# (WIN16_WING_PROFILE), which on a copy made afresh each run would be every # (WIN16_WING_PROFILE), which on a copy made afresh each run would be every
# run: about 165 s at 386SX-16 speed. Its result for this Windows (the S3 # run: about 165 s at 386SX-16 speed. Its result for this Windows (the S3
# driver at 800x600 in 256 colours), taken at that speed, goes in instead, # driver at 800x600 in 256 colors), taken at that speed, goes in instead,
# under WinG's key for the display: the driver, the mode, the driver's # under WinG's key for the display: the driver, the mode, the driver's
# version, the driver file's date as DOS keeps it (year, month and day in # version, the driver file's date as DOS keeps it (year, month and day in
# one 16-bit word, in hex) and WinG's version. The copy is dated the day it # one 16-bit word, in hex) and WinG's version. The copy is dated the day it

View file

@ -169,7 +169,7 @@ local sampler = coroutine.create(function()
if pc >= base and pc < base + textSize then if pc >= base and pc < base + textSize then
-- The EXACT offset, not a 64-byte bucket. Buckets straddle -- The EXACT offset, not a 64-byte bucket. Buckets straddle
-- function boundaries, and a bucket credited to the symbol at -- function boundaries, and a bucket credited to the symbol at
-- its start hands one function's samples to its neighbour: -- its start hands one function's samples to its neighbor:
-- that is how a vblank wait came out looking like an -- that is how a vblank wait came out looking like an
-- expensive input poll. -- expensive input poll.
key = pc - base key = pc - base

View file

@ -17,7 +17,7 @@
# pulseEnsureServer -- locate the audio server, or fail with a real reason. # pulseEnsureServer -- locate the audio server, or fail with a real reason.
# #
# A sanitised environment (cron, CI, `env -i`, a systemd unit without the # A sanitized environment (cron, CI, `env -i`, a systemd unit without the
# user session) has no XDG_RUNTIME_DIR, and pactl then reports "no server" # user session) has no XDG_RUNTIME_DIR, and pactl then reports "no server"
# even though one is running -- measured: `env -i make verify-audio` skipped # even though one is running -- measured: `env -i make verify-audio` skipped
# both recording gates for exactly this. The socket lives at a predictable # both recording gates for exactly this. The socket lives at a predictable

View file

@ -3,7 +3,7 @@
# See LICENSE.md for license information. # See LICENSE.md for license information.
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
# #
# verify-all.sh - Run JoeyLib's gates, one kind or all of them, and summarise. # verify-all.sh - Run JoeyLib's gates, one kind or all of them, and summarize.
# #
# Driven by `make verify` (all), `make verify-audio` and `make verify-serial`. # Driven by `make verify` (all), `make verify-audio` and `make verify-serial`.
# #
@ -52,7 +52,7 @@
# repainted, and the frame against the stage # repainted, and the frame against the stage
# x68000: the SCB -- every text-palette reload of a 147-change # x68000: the SCB -- every text-palette reload of a 147-change
# screen lands in the blanking before its band, timed on the beam # screen lands in the blanking before its band, timed on the beam
# atarist and ste: the palette -- every shifter colour the ST # atarist and ste: the palette -- every shifter color the ST
# build writes is the 3-bit truncation, the STe build's (on an # build writes is the 3-bit truncation, the STe build's (on an
# STe and a Mega STe) the exact 4-bit $0RGB # STe and a Mega STe) the exact 4-bit $0RGB
# amiga: the palette -- palette and SCB changes present one a # amiga: the palette -- palette and SCB changes present one a
@ -100,12 +100,15 @@
# virtualPad; needs /dev/uinput), then unplugged; # virtualPad; needs /dev/uinput), then unplugged;
# sdl3 again with two joysticks SDL cannot map # sdl3 again with two joysticks SDL cannot map
# web the page's Gamepad API, stood in for by the driver # web the page's Gamepad API, stood in for by the driver
# psp: no keyboard; its d-pad, nub, cross and circle as joystick # psp: no keyboard or mouse; its d-pad, cross and circle as
# 0 (read back from examples/joy's display, resting while the # joystick 0 (read back from examples/joy's display, resting while
# keyboard on the screen is open), Start as Return (held, and # the keyboard on the screen is open), the nub as the mouse and L
# typed: examples/keys's received row), Select as Escape, and the # and R as its buttons (examples/keys's mouse row: the dead zone,
# keyboard on the screen (F5 and Shift lit in examples/keys, a # the edges, a half-tilt speed, still under the keyboard), Start
# string typed on it), pressed through PPSSPP's input script # as Return (held, and typed: examples/keys's received row),
# Select as Escape, and the keyboard on the screen (F5 and Shift
# lit in examples/keys, a string typed on it), pressed through
# PPSSPP's input script
# audio twelve gates, two kinds (the IIgs, X68000 and PSP on AUDIOPRB, # audio twelve gates, two kinds (the IIgs, X68000 and PSP on AUDIOPRB,
# AUDIO pressing its own keys; the rest drive AUDIO's keys): # AUDIO pressing its own keys; the rest drive AUDIO's keys):
# iigs DOC wavetable + sound IRQ vector (register tap) # iigs DOC wavetable + sound IRQ vector (register tap)
@ -129,7 +132,7 @@
# one-shot module and ticks; its sound is recorded and graded # one-shot module and ticks; its sound is recorded and graded
# against what it logged (scripts/verify-audio-conformance.sh, # against what it logged (scripts/verify-audio-conformance.sh,
# tests/gate/audioGate.c): pitch, level per attenuation # tests/gate/audioGate.c): pitch, level per attenuation
# step, noise rate, mix balance, centring and pan where the port # step, noise rate, mix balance, centering and pan where the port
# is stereo, the module's end, the tick rate. Every port that # is stereo, the module's end, the tick rate. Every port that
# records sound: all but beos, whose VM has no sound card # records sound: all but beos, whose VM has no sound card
# serial the host check of the shared layer, then each port's loopback # serial the host check of the shared layer, then each port's loopback
@ -151,6 +154,8 @@
# R5's VM has no serial ports) # R5's VM has no serial ports)
# mouse x68000: deltas, clamps and buttons through the IOCS mouse # mouse x68000: deltas, clamps and buttons through the IOCS mouse
# (and with serial streaming both ways on the same SCC) # (and with serial streaming both ways on the same SCC)
# iigs: the one-button ADB mouse has two: a click is left, and a
# Ctrl-click right until it is let go (examples/keys's mouse row)
# save iigs: a SAVES/ blob written and read back through GS/OS # save iigs: a SAVES/ blob written and read back through GS/OS
# sdl3: SAVE's launch count beside the program after two runs # sdl3: SAVE's launch count beside the program after two runs
# web: SAVE's launch count back from IndexedDB after a reload # web: SAVE's launch count back from IndexedDB after a reload
@ -177,7 +182,7 @@
# amiga icon.library reads Draw.info's kind (a tool, its # amiga icon.library reads Draw.info's kind (a tool, its
# stack) and draws it, and a Workbench start runs the # stack) and draws it, and a Workbench start runs the
# program in its own folder # program in its own folder
# win16 the program minimised on Windows 3.11's desktop # win16 the program minimized on Windows 3.11's desktop
# win32 the window's icon as Wine hands it to X # win32 the window's icon as Wine hands it to X
# sdl3 the window's icon as SDL hands it to X # sdl3 the window's icon as SDL hands it to X
# dvx Program Manager # dvx Program Manager
@ -368,6 +373,7 @@ serial haiku serial scripts/verify-be-serial.sh haiku
serial haiku32 serial scripts/verify-be-serial.sh haiku32 serial haiku32 serial scripts/verify-be-serial.sh haiku32
mouse x68000 mouse scripts/verify-x68000-mouse.sh mouse x68000 mouse scripts/verify-x68000-mouse.sh
mouse x68000 sermouse scripts/verify-x68000-serial-mouse.sh mouse x68000 sermouse scripts/verify-x68000-serial-mouse.sh
mouse iigs mouse scripts/verify-iigs-mouse.sh
save iigs save make -s -f make/iigs.mk iigs-verify-save || exit 1 save iigs save make -s -f make/iigs.mk iigs-verify-save || exit 1
save sdl3 save scripts/verify-sdl3-save.sh save sdl3 save scripts/verify-sdl3-save.sh
save web save scripts/verify-web-save.sh save web save scripts/verify-web-save.sh

View file

@ -26,8 +26,8 @@
# #
# Firing the SFX into a paused song is the point: mixed under the music an # Firing the SFX into a paused song is the point: mixed under the music an
# 8-bit one-shot barely moves the total and "did the SFX play" becomes a # 8-bit one-shot barely moves the total and "did the SFX play" becomes a
# judgement call, while against silence it is unambiguous. Coexistence is # judgment call, while against silence it is unambiguous. Coexistence is
# still covered, because the music has to come back afterwards on the same # still covered, because the music has to come back afterward on the same
# hardware. Measured margins: music ~5700, paused ~240, sfx ~1170. # hardware. Measured margins: music ~5700, paused ~240, sfx ~1170.
# #
# Keys are HELD (keydown, pause, keyup), not tapped: the demo tests # Keys are HELD (keydown, pause, keyup), not tapped: the demo tests

View file

@ -7,12 +7,12 @@
# Amiga's own icon.library, and the program started from it. # Amiga's own icon.library, and the program started from it.
# #
# make bakes Name.info beside every Amiga program (tools/iconBake info: # make bakes Name.info beside every Amiga program (tools/iconBake info:
# a tool's icon in Workbench's four colours, with the stack it starts # a tool's icon in Workbench's four colors, with the stack it starts
# with). WBLAUNCH (examples/wblaunch) runs under FS-UAE on an A500 with # with). WBLAUNCH (examples/wblaunch) runs under FS-UAE on an A500 with
# PALPROBE and its icon in a folder of their own, Prog/. It reads the icon # PALPROBE and its icon in a folder of their own, Prog/. It reads the icon
# with GetDiskObject -- it must be a tool's, of AMIGA_ICON_STACK bytes of # with GetDiskObject -- it must be a tool's, of AMIGA_ICON_STACK bytes of
# stack and the baked size -- and draws it; the screenshot must show every # stack and the baked size -- and draws it; the screenshot must show every
# pixel of the icon the file holds, in Workbench's colours. Then it starts # pixel of the icon the file holds, in Workbench's colors. Then it starts
# PALPROBE as Workbench starts a tool, from RAM:, and its message must come # PALPROBE as Workbench starts a tool, from RAM:, and its message must come
# back with PALPROBE's log, which opens in the current directory, in Prog/: # back with PALPROBE's log, which opens in the current directory, in Prog/:
# libnix's start-up moves into the folder Workbench names. # libnix's start-up moves into the folder Workbench names.

View file

@ -46,7 +46,7 @@ joeyNeed HATARI hatari
joeyNeed EMUTOS_IMG emutos joeyNeed EMUTOS_IMG emutos
build="${1:-atarist}" build="${1:-atarist}"
# The ST build writes 3-bit colours on any machine (joyScreen --st). # The ST build writes 3-bit colors on any machine (joyScreen --st).
case "$build" in case "$build" in
atarist) machine="${2:-st}"; JOY_SCREEN_ARGS=(--st) ;; atarist) machine="${2:-st}"; JOY_SCREEN_ARGS=(--st) ;;
ste) machine="${2:-ste}" ;; ste) machine="${2:-ste}" ;;

View file

@ -5,12 +5,12 @@
# #
# verify-atarist-palette.sh - The ST family's palette, read off the shifter. # verify-atarist-palette.sh - The ST family's palette, read off the shifter.
# #
# JoeyLib colours are 12-bit $0RGB. A plain ST's shifter has 3 bits per # JoeyLib colors are 12-bit $0RGB. A plain ST's shifter has 3 bits per
# channel, so it keeps each channel's top three bits; an STe's has 4, with # channel, so it keeps each channel's top three bits; an STe's has 4, with
# the channel's LOW bit in bit 3 of the register nibble. The ST build (make # the channel's LOW bit in bit 3 of the register nibble. The ST build (make
# atarist) must write the 3-bit truncation -- on every machine, an STe # atarist) must write the 3-bit truncation -- on every machine, an STe
# included, since it never uses STe hardware -- and the STe build (make # included, since it never uses STe hardware -- and the STe build (make
# ste) the exact 4-bit colour. # ste) the exact 4-bit color.
# #
# Subject: the PATTERN example -- eight bands, each its own palette, each # Subject: the PATTERN example -- eight bands, each its own palette, each
# palette a gradient whose entry i has i in some of its channels. So every # palette a gradient whose entry i has i in some of its channels. So every
@ -56,7 +56,7 @@ for machine in $machines; do
mkdir -p "$work/c" mkdir -p "$work/c"
cp "$prog" "$work/c/" cp "$prog" "$work/c/"
# The trace is filtered as it is produced: only palette register writes # The trace is filtered as it is produced: only palette register writes
# from the application, not TOS's ($Exxxxx) or Hatari's other colour # from the application, not TOS's ($Exxxxx) or Hatari's other color
# bookkeeping. --run-vbls ends the run; timeout is the backstop. # bookkeeping. --run-vbls ends the run; timeout is the backstop.
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy timeout -s KILL "$wall" "$HATARI" \ SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy timeout -s KILL "$wall" "$HATARI" \
--machine "$machine" \ --machine "$machine" \
@ -76,7 +76,7 @@ for machine in $machines; do
function channel(v) { function channel(v) {
return build == "atarist" ? rshift(v, 1) : or(rshift(v, 1), lshift(and(v, 1), 3)) return build == "atarist" ? rshift(v, 1) : or(rshift(v, 1), lshift(and(v, 1), 3))
} }
# A set of colour words as Python prints a sorted list of their hex. # A set of color words as Python prints a sorted list of their hex.
function hexList(set, count, names, i, n, out) { function hexList(set, count, names, i, n, out) {
n = 0 n = 0
for (i = 0; i < count; i++) { for (i = 0; i < count; i++) {

View file

@ -45,7 +45,7 @@
# on the left). # on the left).
# Checks a port's hardware cannot pass are graded SKIP, by name: the # Checks a port's hardware cannot pass are graded SKIP, by name: the
# Amiga's tone and voices each play on one Paula channel, which feeds one # Amiga's tone and voices each play on one Paula channel, which feeds one
# side only, so none of them can be centred (its centred sound effects # side only, so none of them can be centered (its centered sound effects
# take a channel each side and are graded). # take a channel each side and are graded).
# A port whose chip cannot land every 2 dB step gets a level tolerance of # A port whose chip cannot land every 2 dB step gets a level tolerance of
# its own: the YM2149's sixteen levels are about 3.5 dB apart, so # its own: the YM2149's sixteen levels are about 3.5 dB apart, so
@ -371,7 +371,7 @@ LUA
# A scratch DVX in DOSBox Staging with a Sound Blaster 16, AUDPAR its # A scratch DVX in DOSBox Staging with a Sound Blaster 16, AUDPAR its
# desktop (scripts/lib/dvxGate.sh), recorded as the DVX audio gate records # desktop (scripts/lib/dvxGate.sh), recorded as the DVX audio gate records
# it. DVX stays up when AUDPAR ends; the log is in AUDPAR's settings folder. # it. DVX stays up when AUDPAR ends; the log is in AUDPAR's user folder.
runDvx() { runDvx() {
local display=":$((90 + RANDOM % 9))" local display=":$((90 + RANDOM % 9))"
local dosPid local dosPid
@ -541,7 +541,7 @@ esac
case "$port" in case "$port" in
atarist) flags+=(--level-tolerance 1.75) ;; atarist) flags+=(--level-tolerance 1.75) ;;
ste) flags+=(--level-tolerance 1.95) ;; ste) flags+=(--level-tolerance 1.95) ;;
amiga) flags+=(--skip "tone centred" --skip "voice 0 centred" --skip "voice 1 centred" --skip "voice 2 centred") ;; amiga) flags+=(--skip "tone centered" --skip "voice 0 centered" --skip "voice 1 centered" --skip "voice 2 centered") ;;
esac esac
"$grader" conformance "$port" "$work/log.txt" "$work/out.wav" "${flags[@]}" > "$work/grade.txt" "$grader" conformance "$port" "$work/log.txt" "$work/out.wav" "${flags[@]}" > "$work/grade.txt"
status=$? status=$?

View file

@ -15,7 +15,7 @@
# attribute is there (attributeSeconds at most), stops it, and opens a # attribute is there (attributeSeconds at most), stops it, and opens a
# Tracker window on its folder: a screendump must hold the baked icon, one # Tracker window on its folder: a screendump must hold the baked icon, one
# size or the other, every pixel it leaves opaque in the BeOS palette's # size or the other, every pixel it leaves opaque in the BeOS palette's
# colour (give or take TOLERANCE: BeOS R5 runs in 16-bit colour). # color (give or take TOLERANCE: BeOS R5 runs in 16-bit color).
# #
# Usage: scripts/verify-be-icon.sh [haiku|haiku32|beos] (default haiku) # Usage: scripts/verify-be-icon.sh [haiku|haiku32|beos] (default haiku)
# Env: BE_ICON_KEEP keep the last screendump as build/PORT/be-icon.png # Env: BE_ICON_KEEP keep the last screendump as build/PORT/be-icon.png
@ -36,14 +36,17 @@ program=$(joeyExampleProgram draw "$port")
iconDir=$repo/build/$port/icon/DRAW iconDir=$repo/build/$port/icon/DRAW
[ -x "$program" ] && [ -f "$iconDir/icon32.raw" ] && [ -f "$iconDir/icon16.raw" ] || { echo "$label: build DRAW first (make $port)" >&2; exit 2; } [ -x "$program" ] && [ -f "$iconDir/icon32.raw" ] && [ -f "$iconDir/icon16.raw" ] || { echo "$label: build DRAW first (make $port)" >&2; exit 2; }
bootSeconds=120 bootSeconds=120
# Seconds the gate looks for the icon in Tracker's window, and the job keeps
# the machine up after (longer: the screendumps must not outlast it).
lookSeconds=90
holdSeconds=150
# Seconds DRAW has to give itself its icon attribute, and the attributes # Seconds DRAW has to give itself its icon attribute, and the attributes
# then have to settle. # then have to settle.
attributeSeconds=60 attributeSeconds=60
settleSeconds=5 settleSeconds=5
# Seconds the gate looks for the icon in Tracker's window -- past the job's
# wait for the attribute and the settling, by the time Tracker may take to
# open its window -- and the job keeps the machine up after (longer: the
# screendumps must not outlast it).
trackerSeconds=55
lookSeconds=$((attributeSeconds + settleSeconds + trackerSeconds))
holdSeconds=150
case "$port" in case "$port" in
beos) tracker=/boot/beos/system/Tracker ;; beos) tracker=/boot/beos/system/Tracker ;;
*) tracker=/system/Tracker ;; *) tracker=/system/Tracker ;;
@ -67,25 +70,30 @@ iconShown() {
mkdir -p "$work/job/DRAW" mkdir -p "$work/job/DRAW"
cp "$program" "$work/job/DRAW/DRAW" cp "$program" "$work/job/DRAW/DRAW"
# The job: DRAW runs until it has given itself its icon attribute, then # The job: DRAW runs until it has given itself its icon attributes (the
# Tracker opens a window on its folder (Tracker runs already: a second start # 32x32, and the 16x16 Tracker's list view shows), then Tracker opens a
# hands it the folder) and the machine stays up a while. # window on a fresh copy of it, data and attributes (Tracker runs already: a
# second start hands it the folder), and the machine stays up a while. A
# copy because R5's Tracker keeps the first icon it saw for a file, which may
# be the generic one from before the registrar wrote DRAW's: it failed one
# run in three on that.
cat > "$work/job/job.sh" <<EOF cat > "$work/job/job.sh" <<EOF
cd DRAW cd DRAW
./DRAW & ./DRAW &
pid=\$! pid=\$!
waited=0 waited=0
while [ \$waited -lt $attributeSeconds ] && ! catattr BEOS:L:STD_ICON DRAW > /dev/null 2>&1; do while [ \$waited -lt $attributeSeconds ] && ! { catattr BEOS:L:STD_ICON DRAW && catattr BEOS:M:STD_ICON DRAW; } > /dev/null 2>&1; do
sleep 1 sleep 1
waited=\$((waited + 1)) waited=\$((waited + 1))
done done
# All of it: on BeOS R5 a plain kill ends only the main thread, and the # All of it: on BeOS R5 a plain kill ends only the main thread, and the
# window stays. The registrar writes the other attributes after the first, # window stays. The registrar writes the other attributes after the icons:
# and R5's Tracker keeps the icon it first finds: a moment for them. # a moment for them.
kill -9 \$pid kill -9 \$pid
sleep $settleSeconds sleep $settleSeconds
cd .. mkdir shown
$tracker "\$(pwd)/DRAW" & copyattr -d DRAW shown/DRAW
$tracker "\$(pwd)/shown" &
sleep $holdSeconds sleep $holdSeconds
EOF EOF
beJobDisk "$work/job.img" "$work/job" beJobDisk "$work/job.img" "$work/job"

View file

@ -16,7 +16,7 @@
# and PULSE_SINK sends this ONE process to a null sink created for the # and PULSE_SINK sends this ONE process to a null sink created for the
# run, so the host's default sink is never touched and the gate is safe on # run, so the host's default sink is never touched and the gate is safe on
# a desktop that is playing something else. parec records that sink's # a desktop that is playing something else. parec records that sink's
# monitor. Note the WAV header is only finalised when parec exits, so it # monitor. Note the WAV header is only finalized when parec exits, so it
# is stopped and waited for before the file is read. # is stopped and waited for before the file is read.
# #
# What is graded: the AUDIO example is driven through four phases whose # What is graded: the AUDIO example is driven through four phases whose

View file

@ -9,7 +9,7 @@
# #
# The golden hashes gate the stage, never the display. UBER's probe build # The golden hashes gate the stage, never the display. UBER's probe build
# (make dvx builds it: examples/uber with UBER_PROBE) ends with a row for DVX # (make dvx builds it: examples/uber with UBER_PROBE) ends with a row for DVX
# (P13) that draws one-pixel stripes in all 16 colours, bands and a small rect, moves # (P13) that draws one-pixel stripes in all 16 colors, bands and a small rect, moves
# rows to another palette and changes that palette, presenting after each, # rows to another palette and changes that palette, presenting after each,
# and asks the port after every present how many pixels differ from the # and asks the port after every present how many pixels differ from the
# stage (jlDvxPortProbeDisplayMismatch in src/dvx/hal.c): the window's content # stage (jlDvxPortProbeDisplayMismatch in src/dvx/hal.c): the window's content
@ -28,7 +28,7 @@
# The default desktops cover each pixel size the present writes (1, 2 and # The default desktops cover each pixel size the present writes (1, 2 and
# 4 bytes) at each window scale (1x, 2x, 3x: 3x is square pixels only, as 4:3 # 4 bytes) at each window scale (1x, 2x, 3x: 3x is square pixels only, as 4:3
# at 3x does not fit 1024x768), in both shapes, the 8-bit desktop's nearest # at 3x does not fit 1024x768), in both shapes, the 8-bit desktop's nearest
# colours and both 16-bit packings (5-5-5 and 5-6-5), then the full screen # colors and both 16-bit packings (5-5-5 and 5-6-5), then the full screen
# (320x200 at 8 bits, its palette the stage's 16). # (320x200 at 8 bits, its palette the stage's 16).
# #
# Usage: scripts/verify-dvx-display.sh [WxHxBPP:SCALE[:square] | full]... # Usage: scripts/verify-dvx-display.sh [WxHxBPP:SCALE[:square] | full]...

View file

@ -216,7 +216,7 @@ if [ -z "$log" ]; then
else else
dvxKeysTyped "$log" "$target" || fail "KEYS's typed log" dvxKeysTyped "$log" "$target" || fail "KEYS's typed log"
fi fi
setting=$(find "$work/keys/c/DVX" -ipath "*/config/apps/$DVX_VENDOR/keys/$DVX_SETTINGS_FILE" -print -quit) setting=$(find "$work/keys/c/DVX" -ipath "*/user/apps/$DVX_VENDOR/keys/$DVX_SETTINGS_FILE" -print -quit)
tr -d '\r' < "$setting" 2> /dev/null | grep -qi "^$DVX_SETTINGS_FULL_SCREEN *= *true" || fail "the setting does not say full screen after switching back ($setting)" tr -d '\r' < "$setting" 2> /dev/null | grep -qi "^$DVX_SETTINGS_FULL_SCREEN *= *true" || fail "the setting does not say full screen after switching back ($setting)"
if [ -n "${DVX_FULLSCREEN_SHOTS:-}" ]; then if [ -n "${DVX_FULLSCREEN_SHOTS:-}" ]; then

View file

@ -6,12 +6,12 @@
# verify-dvx-icon.sh - a DVX program's icon as Program Manager shows it. # verify-dvx-icon.sh - a DVX program's icon as Program Manager shows it.
# #
# make gives every DVX program its icon (make/dvx.mk: tools/iconBake bmp, # make gives every DVX program its icon (make/dvx.mk: tools/iconBake bmp,
# a 32x32 24-bit BMP on DVX's grey, appended by dvxres as the icon32 # a 32x32 24-bit BMP on DVX's gray, appended by dvxres as the icon32
# resource). DVX is staged with DRAW (examples/draw) installed and Program # resource). DVX is staged with DRAW (examples/draw) installed and Program
# Manager, DVX's own, as the desktop (scripts/lib/dvxGate.sh), and boots in # Manager, DVX's own, as the desktop (scripts/lib/dvxGate.sh), and boots in
# DOSBox Staging; Program Manager draws each program by its icon32, so # DOSBox Staging; Program Manager draws each program by its icon32, so
# DOSBox's own screenshot must hold the baked icon: every pixel of it that # DOSBox's own screenshot must hold the baked icon: every pixel of it that
# is not the grey, in its colour (give or take TOLERANCE: the desktop is # is not the gray, in its color (give or take TOLERANCE: the desktop is
# 16-bit). # 16-bit).
# #
# Usage: scripts/verify-dvx-icon.sh # Usage: scripts/verify-dvx-icon.sh
@ -64,7 +64,7 @@ xvfb=$!
sleep 1 sleep 1
dvxStage "$work/c" "$(dvxAppDir "$program")" dvxStage "$work/c" "$(dvxAppDir "$program")"
dvxIniSet "$work/c/DVX/config/DVX.INI" "$progman" dvxIniSet "$(dvxIni "$work/c")" "$progman"
dvxDosboxConf "$work/dosbox.conf" "$work/c" dvxDosboxConf "$work/dosbox.conf" "$work/c"
printf '\n[capture]\ncapture_dir = %s\ndefault_image_capture_formats = raw\n' "$work/shots" >> "$work/dosbox.conf" printf '\n[capture]\ncapture_dir = %s\ndefault_image_capture_formats = raw\n' "$work/shots" >> "$work/dosbox.conf"
mkdir -p "$work/xdg" "$work/shots" mkdir -p "$work/xdg" "$work/shots"

View file

@ -19,7 +19,7 @@
# the typed bytes, every stage pixel a whole scale x scale block # the typed bytes, every stage pixel a whole scale x scale block
# (tests/gate/keysRow.c); # (tests/gate/keysRow.c);
# 3. the typed history: after Escape, KEYS logs every received character # 3. the typed history: after Escape, KEYS logs every received character
# as hex to its joeylog.txt (in its settings folder), which must equal # as hex to its joeylog.txt (in its user folder), which must equal
# the typed ASCII. # the typed ASCII.
# Each desktop given (default 640x480 at 16 bits, where the window is 1x) is # Each desktop given (default 640x480 at 16 bits, where the window is 1x) is
# a run of its own, the window 4:3 or (":square") with square pixels; # a run of its own, the window 4:3 or (":square") with square pixels;

View file

@ -21,7 +21,7 @@
# joystick nothing else moves the stick: it rests in the middle, buttons up. # joystick nothing else moves the stick: it rests in the middle, buttons up.
# #
# JOY (examples/joy) draws, for each stick, a dot (in the middle at rest, # JOY (examples/joy) draws, for each stick, a dot (in the middle at rest,
# grey; green when moved), two button squares and a patch that says whether # gray; green when moved), two button squares and a patch that says whether
# the port reports a stick. It runs with square pixels (dvxSettings): the # the port reports a stick. It runs with square pixels (dvxSettings): the
# 4:3 window repeats some rows, and the dots are read pixel for pixel. # 4:3 window repeats some rows, and the dots are read pixel for pixel.
# tests/gate/joyScreen.c reads the WHOLE state of # tests/gate/joyScreen.c reads the WHOLE state of

View file

@ -13,7 +13,7 @@
# gate's fixture. The program loads its bake through jlTileBankLoadToSurface # gate's fixture. The program loads its bake through jlTileBankLoadToSurface
# and through jlTileBankLoad + jlTilePaste, checks every pixel of both against # and through jlTileBankLoad + jlTilePaste, checks every pixel of both against
# the PNG, hashes both, and tries every other target's bake; its TILEBANK line # the PNG, hashes both, and tries every other target's bake; its TILEBANK line
# in its joeylog.txt (in its settings folder) must say PASS with both hashes # in its joeylog.txt (in its user folder) must say PASS with both hashes
# equal to the host gate's (scripts/check-tilebank.sh, run here for the value) # 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 # and every other target refused. DOSBox is stopped as soon as the line
# appears. # appears.

View file

@ -4,7 +4,7 @@
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
# #
# verify-iigs-audio.sh - Headless gate proving NTP is actually running on # verify-iigs-audio.sh - Headless gate proving NTP is actually running on
# the IIgs, modelled on verify-iigs.sh (same Finder launch timeline, same # the IIgs, modeled on verify-iigs.sh (same Finder launch timeline, same
# "read emulated memory, not a snapshot" approach). # "read emulated memory, not a snapshot" approach).
# #
# Sound cannot be graded from a -sound none run, so this grades the two # Sound cannot be graded from a -sound none run, so this grades the two

View file

@ -17,7 +17,7 @@
# opaque, as the two screen pixels QuickDraw draws a 4-bit icon pixel as in # opaque, as the two screen pixels QuickDraw draws a 4-bit icon pixel as in
# 640 mode, the first its high two bits and the second its low two. The # 640 mode, the first its high two bits and the second its low two. The
# screen's own 2-bit values are compared, so the check does not lean on the # screen's own 2-bit values are compared, so the check does not lean on the
# palette; the icon's colours are the Finder's palette's (tools/iconBake). # palette; the icon's colors are the Finder's palette's (tools/iconBake).
# #
# Usage: scripts/verify-iigs-icon.sh # Usage: scripts/verify-iigs-icon.sh
# Env: IIGS_ICON_KEEP keep the screen as build/iigs/iigs-icon.png # Env: IIGS_ICON_KEEP keep the screen as build/iigs/iigs-icon.png

161
scripts/verify-iigs-mouse.sh Executable file
View file

@ -0,0 +1,161 @@
#!/usr/bin/env bash
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
# See LICENSE.md for license information.
# SPDX-License-Identifier: MIT
#
# verify-iigs-mouse.sh - Headless gate for the IIgs's mouse buttons. Every
# port has two, and the IIgs's ADB mouse has one: a left click made with
# Ctrl held is the right button (src/core/input.c, mouseCtrlClick), read
# through the asm snapshot and predicates (joeyDraw.s). Boots GS/OS under
# MAME and launches the KEYS example off joey.2mg, which stamps the mouse's
# state on its stage row 199: the sentinel 5A5A, then present, left,
# right, and a zero nibble. Presses the emulated mouse button and Ctrl in
# turn and reads the row straight out of SHR memory after each:
#
# start present, no button
# click left
# click let go none
# Ctrl-click right, not left
# Ctrl let go first still right (until the button is let go)
# Ctrl-click let go none
# click, then Ctrl left: Ctrl after the press changes nothing
# both let go none
#
# Then Escape: KEYS must quit to the Finder.
#
# Usage: scripts/verify-iigs-mouse.sh
# Requires: ./install.sh install iigs; build/iigs/bin/joey.2mg built
# with the current KEYS (make iigs-disk).
set -euo pipefail
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo/scripts/lib/joeyEnv.sh"
joeyNeed MAME mame
joeyNeed IIGS_ROMPATH iigsFirmware
joeyNeed GSOS_SYSTEM_DISK iigsFirmware
label=verify-iigs-mouse
sys_disk=$GSOS_SYSTEM_DISK
data_disk=$repo/build/iigs/bin/joey.2mg
for f in "$sys_disk" "$data_disk"; do
[ -f "$f" ] || { echo "$label: missing $f (run 'make iigs-disk')" >&2; exit 2; }
done
work=$(mktemp -d -t joeylib-mouse-verify.XXXXXX)
trap 'rm -rf "$work"' EXIT
cp "$sys_disk" "$work/boot.po"
cp "$data_disk" "$work/joey.2mg"
# Finder keystroke timeline as in verify-iigs-keys.sh. KEYS' mouse row is
# stage row 199, the SHR bytes from $E1/2000 + 199*160 = $E1/9C60, two
# nibble pixels a byte: 5A 5A, then the flags word (present 1000, left
# 0100, right 0010). SCB 0 is $E1/9D00. Each step waits 90 frames (1.5 s)
# before the row is read: KEYS restamps it at its next frame.
cat > "$work/verify.lua" <<'LUA'
local cpu = manager.machine.devices[":maincpu"]
local mem = cpu.spaces["program"]
local nat = manager.machine.natkeyboard
local frame = 0
local idx = 1
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 key_cmd = field(":macadb:KEY3", "Command / Open Apple")
local key_ctrl = field(":macadb:KEY3", "Control")
local key_esc = field(":macadb:KEY3", "Esc")
local button = field(":macadb:MOUSE0", "Mouse Button")
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 report(tag)
local row = ""
for i = 0, 3 do
row = row .. string.format("%02X", mem:read_u8(0xE19C60 + i))
end
io.write(string.format("VERIFY-IIGS-MOUSE %s scb0=%02X row=%s\n", tag, mem:read_u8(0xE19D00), row))
io.flush()
end
local steps = {
{3000, function() nat:post("J") end},
{3120, function() press(key_cmd) end},
{3126, function() nat:post("o") end},
{3180, function() release(key_cmd) end},
{3540, function() nat:post("KEYS") end},
{3660, function() press(key_cmd) end},
{3666, function() nat:post("o") end},
{3720, function() release(key_cmd) end},
{6600, function() report("start") end},
{6610, function() press(button) end},
{6700, function() report("click") end},
{6710, function() release(button) end},
{6800, function() report("clickLetGo") end},
{6810, function() press(key_ctrl) end},
{6840, function() press(button) end},
{6930, function() report("ctrlClick") end},
{6940, function() release(key_ctrl) end},
{7030, function() report("ctrlLetGoFirst") end},
{7040, function() release(button) end},
{7130, function() report("ctrlClickLetGo") end},
{7140, function() press(button) end},
{7170, function() press(key_ctrl) end},
{7260, function() report("clickThenCtrl") end},
{7270, function() release(button) end},
{7280, function() release(key_ctrl) end},
{7370, function() report("bothLetGo") end},
{7380, function() press(key_esc) end},
{7400, function() release(key_esc) end},
{9600, function() report("escape"); manager.machine:exit() end},
}
emu.register_frame_done(function()
frame = frame + 1
while idx <= #steps and frame >= steps[idx][1] do
steps[idx][2]()
idx = idx + 1
end
end)
LUA
cd "$work"
out=$(QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
timeout 300 "$MAME" apple2gs \
-rompath "$IIGS_ROMPATH" \
-flop3 "$work/boot.po" -flop4 "$work/joey.2mg" \
-video none -sound none -nothrottle \
-autoboot_script "$work/verify.lua" </dev/null 2>&1) || true
report=$(echo "$out" | grep -E '^VERIFY-IIGS-MOUSE ' || true)
echo "$report"
value() {
echo "$report" | grep " $1 " | sed -E "s/.*$2=([0-9A-Fa-f]+).*/\1/"
}
fails=()
[ "$(value start scb0)" = "00" ] || fails+=("KEYS never launched (SCB 0 $(value start scb0))")
# step, the row it must show, what that means
for check in "start 5A5A1000 present, no button" \
"click 5A5A1100 the left button" \
"clickLetGo 5A5A1000 no button" \
"ctrlClick 5A5A1010 the right button" \
"ctrlLetGoFirst 5A5A1010 the right button" \
"ctrlClickLetGo 5A5A1000 no button" \
"clickThenCtrl 5A5A1100 the left button" \
"bothLetGo 5A5A1000 no button"; do
read -r step want meaning <<< "$check"
got=$(value "$step" row)
[ "$got" = "$want" ] || fails+=("$step: row ${got:-none}, want $want ($meaning)")
done
scb=$(value escape scb0)
[ -n "$scb" ] && [ $((16#$scb)) -ge 128 ] || fails+=("Escape did not quit KEYS (SCB 0 ${scb:-none})")
if [ ${#fails[@]} -eq 0 ]; then
echo "$label: PASS (the mouse button is left, and right when Ctrl is held as it goes down, until it is let go; Escape quit KEYS)"
exit 0
fi
for f in "${fails[@]}"; do
echo "$label: FAIL - $f" >&2
done
exit 1

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