1221 lines
64 KiB
Text
1221 lines
64 KiB
Text
== Writing Portable Code
|
|
|
|
A JoeyLib program that follows the rules in this chapter compiles and behaves
|
|
the same on all five machines, on DVX, under SDL3, in a browser, and on Windows. Most of the rules exist because one
|
|
particular target cannot do something the others can, and the Apple IIgs is
|
|
usually that target.
|
|
|
|
=== The Rules, in Short
|
|
|
|
. Use `jlAlloc` for anything large, never `malloc`.
|
|
. Never assume a frame rate. Ask `jlFrameHz()` or use `jlMillisElapsed()`.
|
|
. Never assume color 0 is anything but black.
|
|
. Compile your sprites before you draw them.
|
|
. Keep large objects out of function-local variables on the IIgs.
|
|
. Use `jlRandom`, not `rand`, when identical behavior across ports matters.
|
|
. Keep file names short, uppercase-safe, and 8.3-compatible.
|
|
. Do not mix save-under with clean-buffer erasing in the same screen region.
|
|
. Pick one of the audio layers per scene, not two.
|
|
|
|
The rest of this chapter explains each one.
|
|
|
|
=== Memory
|
|
|
|
This is the rule that catches everyone. *The C heap on the IIgs is tiny.* The
|
|
whole heap is one Memory Manager block of 32 KB to 48 KB, and if that block
|
|
cannot be had at start-up it is only what the program's link left in its
|
|
bank, which can be a few hundred bytes. A program that `malloc`s a
|
|
32000-byte buffer works on every other target and can fail on the reference
|
|
one.
|
|
|
|
Use the library allocator, which is backed by each platform's native allocator
|
|
(the Memory Manager on the IIgs):
|
|
|
|
[source,c]
|
|
----
|
|
uint8_t *buffer = (uint8_t *)jlAlloc(32000);
|
|
if (buffer == NULL) {
|
|
// handle it -- this genuinely can fail on a stock machine
|
|
}
|
|
// ...
|
|
jlFree(buffer);
|
|
----
|
|
|
|
`jlAlloc` does *not* zero the memory it returns. Never free a `jlAlloc` block
|
|
with `free`, or a `malloc` block with `jlFree`: on most ports they are the
|
|
same, but on the IIgs a `jlAlloc` block is a Memory Manager handle, and mixing
|
|
them corrupts memory.
|
|
|
|
For bulk copies, `jlMemCopy` and `memcpy` are the same thing: `jlMemCopy` is
|
|
`memcpy` with a 32-bit count. On the IIgs both are `MVN` block moves at 7
|
|
cycles per byte, split correctly at bank boundaries.
|
|
|
|
==== Large Local Variables
|
|
|
|
On the IIgs, a large function-local variable in a deep call chain can silently
|
|
overflow the soft stack and crash -- roughly 2 KB is enough to do it, and the
|
|
trigger is call depth rather than anything the compiler warns about. Make big
|
|
buffers `static`, or allocate them with `jlAlloc`:
|
|
|
|
[source,c]
|
|
----
|
|
// DANGEROUS on the IIgs, especially deep in a call chain:
|
|
void renderScene(void) {
|
|
uint8_t scratch[2048];
|
|
// ...
|
|
}
|
|
|
|
// Safe everywhere:
|
|
void renderScene(void) {
|
|
static uint8_t scratch[2048];
|
|
// ...
|
|
}
|
|
----
|
|
|
|
=== Timing
|
|
|
|
Covered in <<_frame_rate_independence>>, and worth repeating because it is the
|
|
second most common portability bug: the targets run at 50, 55, 60, and 70 Hz.
|
|
Scale by `jlFrameHz()` or work from `jlMillisElapsed()`.
|
|
|
|
`jlFrameCount()` is a 16-bit counter that wraps roughly every 18 minutes at
|
|
60 Hz, so compare two readings by subtracting them.
|
|
|
|
=== Color and the Palette
|
|
|
|
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
|
|
artwork that needs color 0 to be something else -- you have 15 free colors per
|
|
palette, and 16 palettes to switch between per scanline.
|
|
|
|
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
|
|
have both opaque and masked forms.
|
|
|
|
=== Sprites
|
|
|
|
Two rules.
|
|
|
|
*Compile before drawing.* `jlSpriteDraw` never compiles on demand. An
|
|
uncompiled sprite runs through the interpreter, two to six times slower
|
|
depending on the machine, and nothing warns you.
|
|
|
|
*Backup buffers are `uint16_t` arrays.* The buffer of a `jlSpriteBackupT` is
|
|
declared `uint16_t gBacking[jlSpriteBackupLength(w, h)]`, which gives it the
|
|
2-byte alignment save and restore need: on the Amiga, ST, and X68000 an odd
|
|
address sends them down a much slower path, and it crashes the trusted
|
|
variants.
|
|
|
|
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 (the Amiga, ST, and X68000), only the rows holding an opaque pixel (the
|
|
IIgs), or only the visible part of a sprite partly off the surface (every
|
|
port). `jlSpriteSaveUnder` reports the true geometry in the backup structure's
|
|
`x`, `y`, `width`, `height`, and `sizeBytes` fields. It is that recorded region
|
|
you must not overwrite before restoring. On the IIgs the buffer must not cross
|
|
a 64 KB bank boundary, which a `jlAlloc` block and the C heap never do.
|
|
|
|
=== Randomness
|
|
|
|
`rand()` differs wildly between C libraries, so a procedurally generated level
|
|
would not match across ports. `jlRandom` is xorshift32 and produces
|
|
*bit-identical* output on every platform for a given seed:
|
|
|
|
[source,c]
|
|
----
|
|
jlRandomSeed(12345);
|
|
uint16_t roll = jlRandomRange(6); // 0..5, same everywhere
|
|
----
|
|
|
|
A seed of 0 is remapped to 1, because xorshift cannot escape the all-zero
|
|
state. This generator is fine for games and useless for cryptography.
|
|
|
|
=== Files
|
|
|
|
Two directories sit beside the executable, and the library prefixes them for
|
|
you:
|
|
|
|
* `DATA/` -- read-only game content, opened with `jlDataOpen`
|
|
* `SAVES/` -- writable saves and preferences, via the `jlSave*` calls
|
|
|
|
Pass bare names: `jlDataOpen("levels/title.dat", "rb")`, never
|
|
`"DATA/levels/title.dat"`. On DVX the two are not in the current directory
|
|
at all (<<_dvx_programs>>), so a program that opens `+"DATA/..."+` itself finds
|
|
nothing there. DOS, the ST, the Amiga, and the X68000 look in the current
|
|
directory, so start a program there from its own folder; the IIgs looks for
|
|
`DATA/` under GS/OS prefix 0 but keeps `SAVES/` in the application's folder
|
|
(<<_files_and_saves>>).
|
|
|
|
Keep names portable across the retro filesystems this library targets: *at most
|
|
8 characters plus a short extension, letters and digits only, starting with a
|
|
letter*, so they satisfy both ProDOS and DOS 8.3. Nothing checks them for you.
|
|
Do not rely on case being preserved, and spell a name the same way every
|
|
time: on Linux, Haiku, BeOS, and the web, case matters.
|
|
|
|
A failed save is not fatal -- the calls report failure and your program should
|
|
degrade gracefully, because on some volumes `SAVES/` simply is not writable.
|
|
|
|
=== Audio
|
|
|
|
JoeyLib gives you three sound layers, and on some platforms they share physical
|
|
hardware:
|
|
|
|
[cols="2,5", options="header"]
|
|
|===
|
|
|Layer |What it is
|
|
|
|
|Module playback
|
|
|`jlAudioPlayMod` -- four-channel Protracker `.MOD`, mixed in software by
|
|
JoeyLib's module player on DOS, the ST, the X68000, and the hosted ports,
|
|
played by DVX's music library on DVX, and replayed by the sound hardware on
|
|
the Amiga and IIgs. The module player is the Amiga's replayer, PTPlayer,
|
|
translated to C, so those ports play a module as the Amiga does, at a PAL
|
|
Amiga's pitch, Protracker 2.3's quirks and all.
|
|
|
|
|Chip tracker
|
|
|`jlMusicPlay` -- a portable event stream driving three tone voices plus
|
|
noise, at register-write cost on the machines' sound chips (mixed in
|
|
software on DVX and the hosted ports).
|
|
|
|
|Voices and effects
|
|
|`jlAudioVoice`, `jlAudioNoise`, `jlAudioPlaySfx`, and the streaming forms.
|
|
|===
|
|
|
|
[WARNING]
|
|
====
|
|
On the Amiga the tone voices are the hardware the module player drives, and
|
|
while a `.MOD` is playing, `jlAudioVoice` writes fight the player's register
|
|
writes and lose. On the plain ST, digital sound takes the chip channel tone
|
|
voice 0 plays on, so voice 0 is silent while a module or effect plays. On the
|
|
IIgs the voices share sound effect slots 0 to 2 and the noise slot 3. Choose
|
|
the module layer or the voice and tracker layer *per scene*, not both at once.
|
|
====
|
|
|
|
The chip tracker is the portable choice for music. It costs almost nothing on
|
|
every port -- unlike `.MOD` playback, which needs real CPU mixing where the
|
|
sound hardware cannot replay a module. On a plain 8 MHz ST a four-channel
|
|
module takes about three quarters of the machine (measured under Hatari: a
|
|
third for the interrupt that plays each sample through the YM2149, the rest
|
|
the mix), leaving a quarter for the game; the STe's DMA sound needs no such
|
|
interrupt.
|
|
|
|
A `jlAudioVoice` or `jlAudioNoise` note sounds until the next call for it, as
|
|
long as `jlAudioFrameTick` runs every frame: the IIgs plays each note as a
|
|
short sample and restarts the held ones from there.
|
|
|
|
`jlInit` starts audio (`jlConfigT.noAudio` leaves it off) and `jlShutdown`
|
|
stops it. Sound that does not start is *not* fatal: `jlInit` still succeeds,
|
|
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
|
|
treat missing sound as an error.
|
|
|
|
=== Per-Platform Behavior Summary
|
|
|
|
The API is uniform; these are the places where what you get differs.
|
|
|
|
[cols="2,2,2,2,2,2,2,2,2", options="header"]
|
|
|===
|
|
|Feature |IIgs |Amiga |Atari ST |MS-DOS |X68000 |DVX |SDL3, Web, macOS, Win32, PSP, Haiku, BeOS |Win16
|
|
|
|
|Frame rate
|
|
|60 Hz (50 on a PAL machine)
|
|
|50 Hz on a PAL machine, 60 on an NTSC one
|
|
|50 or 60 Hz, as the machine is set
|
|
|70 Hz
|
|
|55 Hz (56 under MAME)
|
|
|70 Hz (virtual; the real retrace on the whole screen)
|
|
|60 Hz (virtual)
|
|
|60 Hz (virtual)
|
|
|
|
|Pixel storage
|
|
|chunky 4bpp
|
|
|planar
|
|
|planar, interleaved
|
|
|chunky 4bpp
|
|
|planar (shown through the text plane)
|
|
|chunky 4bpp
|
|
|chunky 4bpp
|
|
|chunky 4bpp
|
|
|
|
|Sprites
|
|
|compiled
|
|
|compiled
|
|
|compiled
|
|
|compiled
|
|
|compiled
|
|
|compiled
|
|
|interpreted
|
|
|compiled
|
|
|
|
|Stereo pan
|
|
|all three
|
|
|all three (centered on a free channel each side)
|
|
|ignored (mono); all three in the STe build
|
|
|all three on an SB16; ignored (mono) before it
|
|
|ignored
|
|
|all three
|
|
|all three
|
|
|ignored (mono)
|
|
|
|
|Program icon (<<_icons>>)
|
|
|the Finder's (`Icons` folder)
|
|
|Workbench's (`Name.info`)
|
|
|none
|
|
|none
|
|
|none
|
|
|Program Manager's
|
|
|the window's (SDL3), the page's (web), Finder's and the Dock's (macOS),
|
|
Explorer's (Win32), the menu's (PSP), Tracker's (Haiku, BeOS)
|
|
|Program Manager's
|
|
|
|
|Tone voices (`jlAudioTone` is voice 0)
|
|
|DOC oscillators on sound effect slots 0 to 2
|
|
|Paula channels 0 to 2
|
|
|YM2149 (voice 0 silent while digital sound plays)
|
|
|OPL2 (with none, voice 0 on the PC speaker)
|
|
|YM2151 (sine waves)
|
|
|DVX tones (FM chip or mixer)
|
|
|square waves in the mixer
|
|
|square waves in the mixer
|
|
|
|
|Joystick
|
|
|one, analog, auto-disables
|
|
|digital
|
|
|digital
|
|
|analog
|
|
|digital
|
|
|analog (DVX calibrates)
|
|
|SDL's gamepads and joysticks (SDL3, web, macOS); Windows' joysticks (Win32); the system's joysticks
|
|
(Haiku, BeOS); the PSP's own controls
|
|
|Windows' joysticks (Control Panel calibrates)
|
|
|
|
|Keyboard layout
|
|
|machine's own
|
|
|machine's own
|
|
|machine's own
|
|
|fixed US
|
|
|machine's own
|
|
|DVX's
|
|
|the OS's; on the PSP, a US one on the screen
|
|
|Windows'
|
|
|
|
|Serial ports
|
|
|modem, printer, slot card
|
|
|`serial.device`
|
|
|Modem port
|
|
|COM1..COM4
|
|
|RS-232C (needs RSDRV.SYS)
|
|
|COM1..COM4 (DVX's library)
|
|
|Win32: COM1 and up; SDL3 (Linux), macOS, Haiku, BeOS: terminal devices; web, PSP: none
|
|
|COM1..COM9
|
|
|===
|
|
|
|
The joystick row deserves a note: the IIgs port stops polling for a stick
|
|
after about a second (60 polls) with none detected, because polling for an
|
|
absent stick costs about 1 ms a frame there; `jlJoystickReset` starts it again
|
|
(<<_jljoystickreset>>).
|
|
|
|
On the X68000 both joystick ports always report a stick, as on the ST: an
|
|
empty port cannot be told from a stick at rest. `JL_JOY_BUTTON_0` is a stick's
|
|
trigger A and `JL_JOY_BUTTON_1` its trigger B. Holding both directions of an axis
|
|
reads as the positive one (right, down), as on the Amiga and ST.
|
|
|
|
On the ST, `JL_JOYSTICK_0` is port 1, the joystick port, and `JL_JOYSTICK_1` port 0.
|
|
JoeyLib keeps TOS's keyboard-processor mode, in which port 0 is the mouse's:
|
|
the ST reports no stick movement there, so `JL_JOYSTICK_1` stays at rest whatever
|
|
is plugged in. A stick there has one button, so a quick press is
|
|
`JL_JOY_BUTTON_0` and a held one `JL_JOY_BUTTON_1` (<<_jljoydown_jljoypressed_jljoyreleased>>).
|
|
Port 1's fire button is the same line as the mouse's right button, so a press
|
|
reads as both the stick's button and `JL_MOUSE_BUTTON_RIGHT`.
|
|
|
|
The 1040 STe also has two enhanced joystick ports, and the STe build reads an
|
|
Atari Jaguar pad in each. A pad in enhanced port A is `JL_JOYSTICK_0` whenever
|
|
it was used more recently than port 1's stick, so either one plays, and a pad
|
|
in enhanced port B is `JL_JOYSTICK_1`. A pad's A and B buttons are
|
|
`JL_JOY_BUTTON_0` and `JL_JOY_BUTTON_1`. The Mega STe has no enhanced ports.
|
|
|
|
On the Amiga the sticks are read through `lowlevel.library`, which Kickstart
|
|
3.1 does not have in ROM: AmigaOS loads it from `LIBS:`, so the boot drive must
|
|
carry Workbench's `Libs/lowlevel.library`, or no stick is seen. `joey-run` puts
|
|
the installer's copy there (component `amigaLowlevel`); a game shipped to boot
|
|
on its own must do the same. `lowlevel.library` reports a stick only once it
|
|
has moved, and never a mouse. It also checks each port again now and then, and
|
|
a two-button stick with both buttons held can read as nothing at that moment,
|
|
so once a port has shown a stick JoeyLib keeps it one; `jlJoystickReset` hands
|
|
the port back to the library's own detection. `lowlevel.library` reports
|
|
a one-button stick and a two-button one alike, so a stick is taken for a
|
|
one-button one, a held button being `JL_JOY_BUTTON_1`, until its second button
|
|
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
|
|
stick's first reading -- after `jlInit`, after `jlJoystickReset`, or when it is
|
|
plugged in -- is taken as its center, and its axes are read in proportion to
|
|
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
|
|
program starts and rounds to the nearest whole number, so it reports 55 --
|
|
and 56 under MAME, whose X68000 display runs slightly fast (56.07 Hz).
|
|
`jlMillisElapsed` does not depend on it: it reads the machine's 100 Hz system
|
|
timer to the millisecond.
|
|
|
|
Per-scanline palettes cost CPU time on the ST and the X68000, whose displays
|
|
have one 16-color palette. Both reload it as the beam scans, and store each
|
|
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
|
|
line above a change of palette, every frame, and waits in it for that
|
|
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
|
|
the X68000 each change of palette between one
|
|
line and the next costs about one line of CPU time (32 microseconds); a band
|
|
more than two lines from the one before costs about two. A screen that changes
|
|
palette on every line therefore spends a third of each frame in that
|
|
interrupt, during which serial and mouse interrupts wait (see the serial
|
|
chapter for how long a run can be before received bytes are lost). The ST
|
|
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
|
|
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
|
|
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
|
|
the X68000 hand their interrupts the new colors with the page they flip to,
|
|
and DOS loads changed colors in the vertical blank that shows the new page.
|
|
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
|
|
the `jlWaitVBL` after it returns at once).
|
|
|
|
=== Where the Ports Still Differ
|
|
|
|
The API is the same on every port, and where a port behaved differently from
|
|
the rest because of the library, the library has been changed. What is left is
|
|
set by the machines and by the systems the ports run on, and stays. Each entry
|
|
names the ports, says how they differ, and gives the reason.
|
|
|
|
[[portsDifferDisplay]]
|
|
==== Display
|
|
|
|
* *IIgs: a big present can tear.* The Super Hi-Res display reads one fixed
|
|
buffer, with no second page to flip to, and a full-screen present takes
|
|
several frames, so the screen can show part of the old picture and part of
|
|
the new.
|
|
* *Win16, and DVX in a window: no vertical blank.* Windows 3.1 has no call that
|
|
waits for the display's retrace, and DVX syncs to it only on its full screen,
|
|
so `jlWaitVBL` keeps a virtual frame clock there and nothing stops a present
|
|
from tearing.
|
|
* *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
|
|
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 color
|
|
channel, so `$0RGB` values one apart can look the same. The STe build shows
|
|
all 4 bits.
|
|
* *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
|
|
color but black, so white is full white, and a channel that is 0 in any
|
|
other color shows at 1/63 rather than 0: `$F00` carries a trace of green and
|
|
blue.
|
|
* *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
|
|
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
|
|
entries left are enough (<<_windows_programs>>).
|
|
* *ST and X68000: per-scanline palettes cost time.* Both displays have one
|
|
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
|
|
frame (about a line of CPU time each), and the X68000 in a raster interrupt.
|
|
A screen with one palette costs nothing (<<_per_platform_behavior_summary>>).
|
|
|
|
[[portsDifferInput]]
|
|
==== Input
|
|
|
|
* *IIgs keyboard: one key at a time, by character.* The keyboard reports only
|
|
the last character typed and whether any key is down. So JoeyLib sees one key
|
|
besides the modifiers at a time, knows a key by the character it types rather
|
|
than by its place on the keyboard, and reads it released only when every key
|
|
is up. With Ctrl held, Ctrl+H, I, J, K, M, and U read as those letters, not as
|
|
the left arrow, Tab, the down and up arrows, Return, and the right arrow,
|
|
whose codes they share; a shifted digit key reads as its digit only on
|
|
the US layout; and the keypad's keys read as the characters they type, so
|
|
its 8, 2, 4, and 6 are the digits, not the arrows as on the other ports.
|
|
* *IIgs keyboard: the keys it lacks.* It has no F1 to F10, no forward Delete
|
|
(Delete is the backspace key), and one Shift as far as JoeyLib can tell:
|
|
`JL_KEY_LSHIFT` and `JL_KEY_RSHIFT` both read either. Option is
|
|
`JL_KEY_LALT`.
|
|
* *ST keyboard: no Alt layer on two countries' older TOS.* A national keyboard
|
|
types some ASCII with Alt (a German one's @ [ ] { } \). TOS 4.00 and later,
|
|
and EmuTOS, keep that layer in the keyboard tables JoeyLib reads; older TOS
|
|
keeps it in the keyboard code JoeyLib replaces, so JoeyLib carries its own
|
|
copies of those layers, by the ROM's country: Germany, France, Spain,
|
|
Italy, Sweden, Finland, Norway, and German Switzerland. On the French Swiss
|
|
and Danish TOS before 4.00, whose layers it does not know, Alt with a key
|
|
types nothing.
|
|
* *X68000 keyboard: one Shift.* Both Shift keys send the same code, so
|
|
`JL_KEY_LSHIFT` and `JL_KEY_RSHIFT` both read either. OPT.1 and OPT.2 are
|
|
`JL_KEY_LALT`.
|
|
* *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,
|
|
so holding either key reads as a button.
|
|
* *Mouse buttons.* Every port has two, left and right, and a left click made
|
|
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
|
|
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
|
|
mouse in the mode JoeyLib keeps it in, so `JL_JOYSTICK_1` never moves, and
|
|
port 1's fire button is the mouse's right button too.
|
|
* *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
|
|
second button is first pressed.
|
|
* *PSP: the nub for a mouse, one key at a time.* The PSP's mouse is its
|
|
analog nub, which sets the pointer's speed rather than its place, and its
|
|
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]]
|
|
==== Audio
|
|
|
|
* *Plain ST: voice 0 is silent while digital sound plays.* The ST has no
|
|
digital sound chip, so JoeyLib plays modules and effects by changing the
|
|
sound chip's channel A volume 12288 times a second, and channel A is tone
|
|
voice 0's. Voice 0 sounds again when the digital sound stops, and voices 1
|
|
and 2 play on. The STe build uses the STe's DMA sound and keeps all three.
|
|
* *ST: noise shares voice 2's volume.* The YM2149 has one volume per channel,
|
|
and its noise is mixed into channel C, voice 2's. Author noise hits and
|
|
voice-2 notes apart; `songbake` warns when they overlap.
|
|
* *ST: volume steps.* The YM2149 has 16 volume levels about 3.5 dB apart, so a
|
|
2 dB attenuation step is matched to within 1.4 dB on the plain ST and 1.9 dB
|
|
on the STe, whose voices start a level lower to balance its louder digital
|
|
sound.
|
|
* *Plain ST: a loud module clips.* The plain ST plays all its digital sound
|
|
through one sound chip channel, so the mix gives a module channel half of
|
|
it: a module whose three or four channels peak at the same moment clips.
|
|
Everywhere else all four fit.
|
|
* *Amiga: four channels for everything.* Paula has four sound channels, two
|
|
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
|
|
voice was using, and a module plays on all four. A channel plays on one side
|
|
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
|
|
channel each, are heard from one side: voice 0 (and `jlAudioTone`) on the
|
|
left, voices 1 and 2 on the right.
|
|
* *IIgs: voices on the effect slots.* By design the IIgs plays tone voices 0 to
|
|
2 on sound effect slots 0 to 2 and the noise on slot 3, so an effect on one
|
|
of those slots and a voice cut each other off. Use slot 4 for effects while
|
|
voices or `jlMusicPlay` sound.
|
|
* *IIgs: mono.* A stock IIgs mixes its stereo channels into one speaker, so
|
|
panning is heard only with a stereo card.
|
|
* *IIgs: tone pitch in steps.* The DOC sets a voice's pitch in steps of
|
|
1.6 Hz for notes up to 346 Hz and 6.4 Hz above, so a note can be up to
|
|
half a step off: about 12 cents at 110 Hz, 13 at 440 Hz, and 6 at 880 Hz.
|
|
* *Plain ST and X68000: no panning.* Each has one output for digital sound:
|
|
the ST its channel A, the X68000 its one ADPCM channel, so `jlAudioSetPan`
|
|
is ignored. The STe build plays stereo through the DMA chip: the module's
|
|
channels on Paula's sides, as on the Amiga, and each sound effect where
|
|
`jlAudioSetPan` puts it.
|
|
* *DOS: stereo on an SB16 only.* An SB16 plays the module's channels on
|
|
Paula's sides and each sound effect where `jlAudioSetPan` puts it; the
|
|
Sound Blasters before it play mono (an SB Pro's stereo needs the DSP's
|
|
high-speed mode, which takes no commands while it runs). Mono cards also
|
|
play 22050 Hz as 22222 Hz, the rate their time constant gives, which
|
|
JoeyLib mixes for, so pitch is right on both.
|
|
* *DOS: the mix needs a mixer.* The OPL2's loudest voice is about 5 dB under
|
|
the quarter of the Sound Blaster's digital full scale where JoeyLib mixes a
|
|
sound effect or a module channel (each keeps 6 of its 8 bits, so the four
|
|
channels fit), so on an SB Pro or SB16 JoeyLib sets the
|
|
card's mixer, FM at full and digital sound about 4 dB under it, and
|
|
`jlShutdown` puts both back. The cards before the SB Pro have no mixer, and
|
|
there sound effects and modules play about 5 dB over the voices.
|
|
* *DOS: the noise is a drum.* The OPL2 has no free-running noise generator; its
|
|
rhythm-mode snare is the nearest, a tuned drum whose pitch cannot follow the
|
|
YM2149's noise rate.
|
|
* *X68000: digital sound at 7812 Hz.* Modules and effects are mixed at 7812 Hz
|
|
and encoded to the MSM6258's 4-bit ADPCM, so nothing above 3.9 kHz is
|
|
heard. The chip can play 15625 Hz, but the mix and the encode run on the
|
|
10 MHz 68000 for every sample: twice the rate would take twice the
|
|
~13% of the machine they take now.
|
|
* *DOS and X68000: tones are not square.* The OPL2 and the YM2151 make their
|
|
waves from sines and have no square wave, so their tone voices (DOS's when it
|
|
has an OPL2) are not the square waves the other ports play.
|
|
* *Amiga and IIgs: the brightest noise.* Paula plays a sample no faster than
|
|
28.6 kHz and the IIgs's DOC no faster than 26 kHz, so noise pitches 0 to 4
|
|
sound alike there.
|
|
* *Amiga and IIgs: fast effects lose their top.* For the same reason, an effect
|
|
or stream faster than 28.6 kHz plays every second sample on the Amiga (every
|
|
third above 57.2 kHz), and one faster than the DOC's 26 kHz output steps over
|
|
samples on the IIgs, both at its own pitch and length: 44.1 kHz plays as
|
|
about 22 kHz, so what it holds above 11 kHz is gone, where the other ports
|
|
keep it up to their own output's limit.
|
|
* *Where tick callbacks fall.* The calls average exactly `hz` a second on every
|
|
port. The ST and X68000 deliver them from the system's own timer, at 200 and
|
|
100 Hz, so the calls due between two of its interrupts come together. DVX
|
|
delivers them at `jlAudioFrameTick`, because its sound library may not be
|
|
called from an interrupt, and so does the IIgs, counting them against the
|
|
display's beats.
|
|
* *DOS, the ST, and the X68000 mix in program time.* The module player and
|
|
the SFX overlay run too long for an interrupt, so their sound buffers are
|
|
refilled at `jlAudioFrameTick`: a program that stops calling it for longer
|
|
than the buffer lasts stops or repeats its sound.
|
|
* *Win16: a module's end is reported early.* `jlAudioIsPlayingMod` turns false
|
|
up to about 0.15 s before the module's last notes are heard. Windows 3.1's
|
|
Sound Blaster driver counts a buffer as played as soon as it takes it into
|
|
its own DMA buffer, and reports the same through `waveOutGetPosition`, so
|
|
nothing tells what has been heard.
|
|
* *Win16 with a Sound Blaster 2.0: 8-bit sound.* Its driver, SNDBLST2.DRV, takes
|
|
only 8-bit samples, so the quietest attenuation steps are coarser there, and
|
|
sound effects and modules, mixed at a quarter of full scale like every
|
|
software mix, keep 6 of their 8 bits.
|
|
|
|
[[portsDifferSerial]]
|
|
==== Serial
|
|
|
|
* *Top speeds.* The ST runs at 19200 baud at most (its MFP's rate table), a
|
|
IIgs Super Serial Card at 19200 (the 6551's), the X68000 at 38400 (RSDRV.SYS's
|
|
table; really 39062), and Win16 with Windows' own COMM.DRV at 57600.
|
|
* *One port.* The base ST has one RS-232 port, and so has the X68000, whose
|
|
second serial channel is the mouse's. A Mega STe, TT, or Falcon has more,
|
|
which `JL_SERIAL_SLOT` opens through TOS's Bconmap; their DTR and RTS
|
|
stay as TOS sets them.
|
|
* *Haiku and BeOS: 7 or 8 data bits.* Their termios cannot ask for 5 or 6,
|
|
so an open that does fails.
|
|
* *IIgs modem and printer ports: no RTS.* Their mini-DIN-8 connector's one
|
|
handshake output is DTR, so under RTS/CTS nothing can tell the peer to
|
|
pause: receive needs XON/XOFF or a protocol that paces itself. The printer
|
|
port's channel is also AppleTalk's.
|
|
* *IIgs Super Serial Card: lines tied together.* The 6551's DTR is also its
|
|
receiver's enable, and dropping its RTS stops its transmitter.
|
|
* *X68000: DTR rises at every line setup.* The ROM's and RSDRV.SYS's line setup
|
|
raise DTR whenever it runs.
|
|
* *Windows: DTR drops at close.* Win32's driver, and Win16 with Windows' own
|
|
COMM.DRV, drop DTR when the port is closed, and neither has a call that
|
|
prevents it.
|
|
* *IIgs: disk access stops interrupts.* GS/OS holds interrupts off for 65 to
|
|
220 ms at a time while it reads or writes a 3.5-inch floppy, which can lose
|
|
received bytes (<<_what_can_lose_received_bytes>>).
|
|
|
|
[[portsDifferFiles]]
|
|
==== Files and Saves
|
|
|
|
* *Name rules.* ProDOS names on the IIgs are at most 15 characters and start
|
|
with a letter; DOS, the ST, and Win16 use 8.3 names; and Linux, Haiku, BeOS,
|
|
and the web tell upper and lower case apart. The save calls refuse a name
|
|
outside the portable rule on every port (<<_files>>); a `DATA/` name is
|
|
passed through, so keep those to it too.
|
|
* *ST: the current directory.* Every other port finds `DATA/`, `SAVES/`, and
|
|
the log in the program's own folder however it was started; TOS tells a
|
|
program nothing of where it was started from, so the ST uses the current
|
|
directory, which is the program's folder when the desktop starts it.
|
|
* *Pre-compiled sprites per processor.* A `.spc` holds machine code, so it is
|
|
baked per port: DVX and Win16 run DOS's x86 code, the STe the ST's, and the
|
|
ports that interpret sprites use only its pixels.
|
|
* *Web saves are the browser's.* A browser may clear a site's stored data to
|
|
make room, and a private window keeps nothing once it closes.
|
|
* *macOS: saves in Application Support.* An app's own folder may not be
|
|
writable, so `SAVES/` and the log go in the user's
|
|
`~/Library/Application Support/NAME/` (<<_macos_programs>>).
|
|
|
|
[[portsDifferMemory]]
|
|
==== Sprites and Memory
|
|
|
|
* *IIgs: code and copies stay inside 64 KB banks.* The 65816 runs code within
|
|
one 64 KB bank and its block copy stays within one, so each sprite's code
|
|
must fit in one bank of the codegen arena, the arena is at most four banks,
|
|
compacting never merges free space across banks, and a save-under buffer
|
|
must not cross a bank.
|
|
* *Code size differs by processor.* The 68000 and x86 emitters make code four
|
|
to eight times larger than the 65816 one, so with the same `codegenBytes` a
|
|
large sprite can compile on the IIgs and fall back to the interpreter
|
|
elsewhere.
|
|
* *IIgs: a small C heap, and `jlFree` apart from `free`.* The C library's heap
|
|
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
|
|
each other's blocks.
|
|
* *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
|
|
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
|
|
other port has its C library's whole `<math.h>`.
|
|
|
|
[[portsDifferTiming]]
|
|
==== Timing
|
|
|
|
* *Frame rates.* The displays run at 50, 55, 60, and 70 Hz, as the table above
|
|
shows; the hosted ports and DVX keep a virtual 60 and 70 Hz.
|
|
* *IIgs: the clock stops while interrupts are off.* The IIgs has no free-running
|
|
timer JoeyLib can read, so its clock counts the display's vertical blanks,
|
|
and blanks that come while interrupts are held off are lost: a long stretch
|
|
with interrupts off, a floppy access for one, leaves the clock behind.
|
|
* *DOS: frames counted by the clock.* VGA has no dependable retrace interrupt,
|
|
so `jlFrameCount` counts 70ths of a second of the millisecond clock while
|
|
`jlWaitVBL` waits for the real retrace (70.086 Hz), and the two slowly drift
|
|
apart.
|
|
|
|
=== A Portability Checklist
|
|
|
|
Before you call a program portable, confirm:
|
|
|
|
* [ ] No `malloc` of anything over a few hundred bytes -- use `jlAlloc`.
|
|
* [ ] No large function-local arrays; they are `static` or heap.
|
|
* [ ] Movement and animation scale with `jlFrameHz()` or `jlMillisElapsed()`.
|
|
* [ ] `jlInputPoll()` is called exactly once per frame.
|
|
* [ ] `jlAudioFrameTick()` is called every frame.
|
|
* [ ] Every sprite is compiled during loading.
|
|
* [ ] Sprite backup buffers are 2-byte aligned and sized with the macro.
|
|
* [ ] Save file names are 8.3-safe.
|
|
* [ ] Only one audio layer is active per scene.
|
|
* [ ] `jlShutdown()` runs on every exit path.
|
|
* [ ] The program has actually been run on every target, not just built.
|
|
|
|
That last item is the one that matters. Every port has emulator run scripts
|
|
under `scripts/`, and building successfully proves considerably less than
|
|
running.
|
|
|
|
=== The Atari ST Family
|
|
|
|
The ST port builds two ways, and a program ships the one that suits its
|
|
audience (or both):
|
|
|
|
* *The ST build* (`make atarist`, programs in `build/atarist/bin`) is the
|
|
baseline. It is pure 68000 and *never uses STe hardware*: it runs on every
|
|
ST -- STF, STFM, Mega ST, STe, and Mega STe alike -- exactly as on a plain
|
|
ST, at no cost for the STe's existence.
|
|
* *The STe build* (`make ste`, programs in `build/ste/bin`) is the same port
|
|
compiled with `JOEYLIB_ATARI_STE`. It uses the STe's hardware
|
|
unconditionally, so it *needs an STe or Mega STe*: on any other machine
|
|
`jlInit` fails and `jlLastError` says why (a Mega ST has a BLiTTER but no
|
|
DMA sound, so it is refused too). `jlPlatformName` reports `Atari STe`.
|
|
|
|
Nothing in the API differs between them:
|
|
|
|
[cols="2,3,3", options="header"]
|
|
|===
|
|
|Feature |ST build (any ST) |STe build (STe, Mega STe)
|
|
|
|
|Palette
|
|
|3 bits per channel: each channel's low bit is dropped, so `$0RGB` values one
|
|
apart can look the same
|
|
|4 bits per channel: every `$0RGB` value exactly
|
|
|
|
|Digital sound (`.MOD`, SFX, streams)
|
|
|PWM through the YM2149's channel A volume, driven by a 12288 Hz timer
|
|
interrupt, each sample the nearest of the YM's sixteen logarithmic levels
|
|
(so about three bits near full scale); tone voice 0 is silent while it
|
|
plays, and for a fifth of a second after
|
|
|The 8-bit DMA sound chip, stereo at 12517 Hz, with no interrupt; all three
|
|
tone voices keep playing
|
|
|
|
|Fills and copies
|
|
|The CPU
|
|
|The BLiTTER, for fills of 8 or more rows (24 for one 16-pixel column),
|
|
copies of 4 or more rows, and a tile layer's band; smaller work stays on
|
|
the CPU, which is faster there
|
|
|
|
|CPU clock
|
|
|As the machine is (8 MHz; TOS's setting on a Mega STe)
|
|
|8 MHz on an STe; a Mega STe is switched to 16 MHz with its cache on while
|
|
the program runs, and back to what TOS had when it ends (checked once, at
|
|
`jlInit`)
|
|
|===
|
|
|
|
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 tests check that under Hatari.
|
|
`./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
|
|
builds the STe variant with `make ste`, like any other port; it takes the ST's
|
|
baked assets.
|
|
|
|
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
|
|
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.
|
|
Hatari models that 16 MHz without the RAM wait states a real Mega STe has, so
|
|
expect less on memory-bound operations there.
|
|
|
|
=== DVX Programs
|
|
|
|
DVX is a DOS desktop: a VESA graphics GUI with windows, cooperative
|
|
multitasking, and its own libraries for sound, music, joysticks, and serial
|
|
ports. On DVX a JoeyLib program is one of its programs, a `.app` module
|
|
running in a window of its own beside the others. The graphics are the
|
|
MS-DOS port's -- the same chunky stage and the same x86 code, so UBER's
|
|
hashes are DOS's -- and the rest behaves like this:
|
|
|
|
* *A window, or the whole screen.* The window's content is the 320x200 stage
|
|
at the largest whole scale, up to 3x, that fits the desktop, in 4:3 as a
|
|
CRT shows it and as the SDL3 window does (each stage row a fifth taller:
|
|
1x is 320x240), or with Game > Square Pixels, square pixels (16:10, as
|
|
`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
|
|
3x. The choice is kept with the full-screen one. The stage reaches the
|
|
desktop through one color table per palette, so SCB palettes are exact on
|
|
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.
|
|
* *Full screen.* Game > Full Screen, or Alt+Enter, puts the program on the
|
|
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
|
|
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.
|
|
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
|
|
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 user
|
|
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
|
|
DOSBox's own full-screen key; use Alt with the keypad's Enter, or the
|
|
menu.)
|
|
* *A virtual 70 Hz.* A window has no vertical blank. `jlFrameHz()` is 70, as
|
|
on DOS, and `jlWaitVBL()` waits for the next seventieth of a second of
|
|
DVX's clock while the other programs run; nothing prevents tearing. On the
|
|
whole screen `jlWaitVBL()` gives the other programs a turn, then waits for
|
|
the screen's real vertical retrace, as on DOS, so a present right after it
|
|
does not tear. The frame count is the same clock in both, so switching
|
|
never makes it jump.
|
|
* *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. The
|
|
exceptions are the three DVX keeps, Alt+Tab (switch programs), Ctrl+Esc
|
|
(Task Manager), and Alt+F4 (close the window), and Alt+Enter, which the
|
|
port keeps for full screen. The window's Game menu has Full Screen, Square
|
|
Pixels, Help (the program's help file), and Exit. Typed characters come
|
|
through DVX's keyboard layout.
|
|
* *Closing ends the program.* Alt+F4, the close box, and Game > Exit end the
|
|
program at its next `jlWaitVBL`, `jlInputPoll`, `jlStagePresent`, or
|
|
`jlSerialPoll`, as `exit()` does anywhere: its `atexit` handlers run, then
|
|
`jlShutdown`, and its log is closed.
|
|
* *Sharing the machine.* DVX multitasks cooperatively: the other programs,
|
|
and this one's own window events, run only while it yields. `jlWaitVBL`
|
|
yields, and so do `jlInputPoll`, `jlStagePresent`, and `jlSerialPoll`, at
|
|
most once a frame. A loop that calls none of them freezes the desktop until
|
|
it ends.
|
|
* *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
|
|
`APPS`), while `SAVES/` and `joeylog.txt` go to the program's own folder in
|
|
DVX's user folder, `USER\APPS\VENDOR\NAME`, which DVX keeps apart from the
|
|
program files so an upgrade never overwrites them.
|
|
* *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
|
|
noise as the hosted ports make them, square waves and the YM2149's noise
|
|
mixed in software into a stream of DVX's mixer, so their levels and
|
|
balance are the same on every card (a machine whose only sound is the PC
|
|
speaker plays DVX's own tones instead). A `jlAudioTickRegister` callback
|
|
runs in program time from that mixing, between exactly the samples where
|
|
its tick falls, as on the hosted ports.
|
|
* *Joysticks* are DVX's, calibrated in its Control Panel: real analog axes.
|
|
Two sticks on one game port (a Y cable, or a four-axis stick in DVX's
|
|
default two-stick mode) are joysticks 0 and 1. An axis that was never
|
|
calibrated sets its range as it moves, so until it is calibrated a full
|
|
push can read a little short of 127. DVX's keyboard joystick is not
|
|
reported as one.
|
|
* *Serial ports* belong to DVX's serial library, one program each. As on
|
|
every port, closing one leaves DTR as it is.
|
|
* A program's `main` is compiled as `jlGameMain`, which the port calls with
|
|
`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
|
|
component supplies DVX and its SDK, <<_setting_up_the_toolchains>>), and
|
|
`./joey-run --port dvx NAME` starts one in DVX under DOSBox Staging.
|
|
|
|
=== SDL3 Programs
|
|
|
|
The SDL3 port builds a JoeyLib program as a native program for the machine
|
|
you work on: Linux today, through SDL 3, which the installer builds into
|
|
JoeyLib and every program links in (the `sdl3` component). It is the hosted
|
|
core in `src/hosted` -- everything that is the same under any desktop
|
|
operating system -- with an SDL3 backend in `src/hosted/sdl3`. Its stage is
|
|
the generic chunky one, so UBER's hashes are DOS's, and the rest behaves like
|
|
this:
|
|
|
|
* *A window, or the whole screen.* The stage is scaled to fill the window at
|
|
4:3, the shape 320x200 has on the old machines' monitors (each pixel 1.2
|
|
times as tall as it is wide), with black bars as needed; SDL's pixel-art
|
|
scaling keeps the pixels sharp at any size. Each scanline goes through its
|
|
own palette, so SCB palettes are exact. Three environment variables set it
|
|
up: `JOEYLIB_SCALE=N` opens the window N times 320 pixels wide (default 3),
|
|
`JOEYLIB_ASPECT=square` keeps the pixels square (16:10) instead, and
|
|
`JOEYLIB_FULLSCREEN=1` starts on the whole screen. Alt+Enter switches
|
|
between the window and the whole screen. JoeyLib's API has no full-screen
|
|
call; it is the user's choice.
|
|
* *A virtual 60 Hz.* A window has no vertical blank. `jlFrameHz()` is 60 and
|
|
`jlWaitVBL()` sleeps to the next sixtieth of a second of the machine's
|
|
clock. The window is redrawn at most once a frame, however often the
|
|
program presents.
|
|
* *Sprites are interpreted.* There is no code emitter for the host's CPU, so
|
|
`jlSpriteCompile` returns false and every sprite is drawn by the
|
|
interpreter -- far faster than any of the old machines all the same. (The
|
|
Win16 port, below, compiles them as DOS does.)
|
|
`joey/platform.h` defines `JL_SPRITE_INTERPRETED`, for a program that
|
|
checks how many of its sprites compiled. A pre-compiled bank (`.spc`)
|
|
loads its cels' pixels and leaves their code: the DOS bake is used.
|
|
* *Input.* Keys by where they are on the keyboard, so another layout does not
|
|
move the arrow keys; typed characters through the OS's keyboard layout. The
|
|
mouse is the pointer over the stage, in stage pixels (the OS pointer is
|
|
hidden over the window: JoeyLib programs draw their own). The first two
|
|
gamepads or joysticks are joysticks 0 and 1, coming and going as they are
|
|
plugged in. A gamepad SDL knows how to map gives its left stick or its
|
|
d-pad, its south button (A on an Xbox pad) as `JL_JOY_BUTTON_0`, and its
|
|
east button (B) as `JL_JOY_BUTTON_1`; any other joystick its first two axes
|
|
or its first hat, and its first two buttons.
|
|
* *Closing ends the program.* Closing the window ends the program at its next
|
|
`jlWaitVBL`, `jlInputPoll`, or `jlStagePresent`, as `exit()` does anywhere:
|
|
its `atexit` handlers run and the window and the sound are closed.
|
|
* *Files.* The program's own folder is the current directory from `jlInit`
|
|
on, as on the machine ports: `DATA/`, `SAVES/`, and `joeylog.txt` are beside
|
|
the program.
|
|
* *Sound* is mixed in software into 16-bit stereo at 44.1 kHz: `.MOD` music
|
|
through the module player (as on DOS), Paula's channels hard left and
|
|
right as on the Amiga, the SFX slots in stereo, the three tone
|
|
voices as square waves, and the noise voice as the ST's YM2149 makes it. It
|
|
is mixed in program time by `jlAudioFrameTick`, `jlWaitVBL`,
|
|
`jlInputPoll`, and `jlStagePresent`, a few frames ahead, so a
|
|
`jlAudioTickRegister` callback runs in program time too -- but between
|
|
exactly the samples it falls between, so its notes start in the right
|
|
place in the sound. Every sound needs audio started (the default), the
|
|
tone voices included.
|
|
* *Serial* through termios on Linux: `/dev/ttyS0`, or the device the
|
|
`JOEYLIB_SERIAL` environment variable names (a USB adapter:
|
|
`JOEYLIB_SERIAL=/dev/ttyUSB0`), and `/dev/ttyS<unit - 1>` for a slot. The
|
|
user running the program needs the device's permissions (on most
|
|
distributions, the `dialout` group).
|
|
|
|
`make sdl3` builds every example as a native program, each in
|
|
`build/sdl3/bin/` (a program that ships in a folder, in a folder of its own
|
|
with its `DATA/`), and `./joey-run --port sdl3 NAME` runs one; so does the
|
|
program itself, from anywhere.
|
|
|
|
=== Web Programs
|
|
|
|
The web port is the SDL3 port compiled to WebAssembly with Emscripten (the
|
|
`web` installer components: Emscripten through emsdk, and SDL 3 built with
|
|
it): the same hosted core, the same backend, the same stage, so UBER's hashes
|
|
are DOS's here too. Everything in <<_sdl3_programs>> holds, except:
|
|
|
|
* *A page.* A program is a folder, `build/web/bin/NAME/`: `NAME.html` (the
|
|
page), `NAME.js`, `NAME.wasm`, and `NAME.data`, the program's `DATA/`
|
|
packed in when it is built. Put the folder on any web server and open
|
|
`NAME.html`; a browser will not run it from a `file:` address. The page's
|
|
address takes the window's settings: `NAME.html?scale=2&aspect=square`.
|
|
* *Your loop still works.* A browser does not let a page's program block,
|
|
and a JoeyLib program owns its loop and blocks in `jlWaitVBL`. Programs are
|
|
built with Emscripten's ASYNCIFY, and the port's waits hand the browser its
|
|
turn and carry on where they were, so an unmodified program runs. It gets
|
|
its turn back at least once a frame even when it never waits (a loop on
|
|
`jlInputPoll` alone, as `jlWaitForAnyKey` is).
|
|
* *Sound starts with the first click or key press*, as browsers require; the
|
|
page says so.
|
|
* *Saves persist, page by page.* `SAVES/` is kept in the browser's
|
|
IndexedDB, read back the first time the program asks for it and written
|
|
back as each save changes, so a save outlives the page however it ends.
|
|
Each page keeps its own, as each program keeps its own `SAVES/` folder on
|
|
a machine: `games/hero.html` and `games/maze.html` on one site never see
|
|
each other's saves, and moving or renaming a page leaves its saves behind
|
|
under the old address. `jlDiskFree` there is the browser's own estimate of
|
|
what the whole site may still store.
|
|
* *No serial port.* A page reaches one only through Web Serial, which only
|
|
Chromium-based browsers have and which opens a port only when the person
|
|
picks it from the browser's own chooser, opened by a click: nothing a
|
|
program calls can do that for it.
|
|
* *No full screen by default and no closing.* Alt+Enter asks the browser for
|
|
the whole screen; the program ends when the page is closed.
|
|
|
|
`make web` builds every example as a page, and `./joey-run --port web NAME`
|
|
serves its folder on this machine and opens it in your browser.
|
|
|
|
=== macOS Programs
|
|
|
|
The macOS port is the SDL3 port built as Mac apps, on Linux: the same hosted
|
|
core, the same backend, the same stage (UBER's hashes are DOS's). It is
|
|
compiled with zig against Apple's macOS SDK, once for Apple silicon and once
|
|
for Intel, the two joined into one universal program that runs on either,
|
|
on macOS 13 (Ventura) or newer, with SDL 3 built the same way and linked in
|
|
(the `macos` installer components: zig, the SDK, and SDL 3). Everything in
|
|
<<_sdl3_programs>> holds, except:
|
|
|
|
* *An app.* A program is `build/macos/bin/NAME.app`: the program in
|
|
`Contents/MacOS/`, its `DATA/` and its icon in `Contents/Resources/`, which
|
|
is its folder and the current directory, and the `Info.plist` that names
|
|
them. Copy the app to a Mac, or zip it to send it; it opens from anywhere.
|
|
* *Saves in Application Support.* `SAVES/` and `joeylog.txt` go in
|
|
`~/Library/Application Support/NAME/`, not beside the program: an app in
|
|
`/Applications` may not be writable by its user, and macOS runs a
|
|
downloaded app from a read-only copy until it is moved. `jlDiskFree` is
|
|
the free space there.
|
|
* *Retina screens.* The window draws at the screen's own resolution, so the
|
|
stage is scaled up once, sharply, and not blurred again by the system.
|
|
* *Serial* through termios, as on Linux: a Mac has no serial port of its
|
|
own, so the ports are USB adapters' call-out devices (`/dev/cu.*`, the
|
|
Mac's own Bluetooth and debug devices left out), in name order, or the
|
|
device `JOEYLIB_SERIAL` names.
|
|
* *Untested on a Mac.* Nothing here runs Mac programs: `make verify` builds
|
|
every example and checks every app is one macOS opens -- both processors'
|
|
programs, the Apple silicon one signed, for macOS 13 on, linking only the
|
|
system's own libraries, with its `Info.plist` and icon
|
|
(`scripts/check-macos-apps.sh`) -- but what an app does on a Mac has not
|
|
been seen yet.
|
|
|
|
`make macos` builds every example as an app; `./joey-run` recognizes one
|
|
and says it runs on a Mac.
|
|
|
|
=== PSP Programs
|
|
|
|
The PSP port is the SDL3 port built as PSP homebrew: the same hosted core
|
|
and backend, cross-compiled with pspdev's `psp-gcc` against SDL 3's PSP port
|
|
(the `psp` installer components: pspdev, and SDL 3 built with it). Its stage
|
|
is the same chunky one, so UBER's hashes are DOS's. Everything in
|
|
<<_sdl3_programs>> holds, except:
|
|
|
|
* *A folder for `PSP/GAME`.* A program is a folder, `build/psp/bin/NAME/`:
|
|
`EBOOT.PBP`, titled with the program's name (or its description's `TITLE`)
|
|
in the PSP's menu, and its `DATA/`. Copy the folder into `PSP/GAME/` on the
|
|
memory stick. Its folder is its current directory, so `DATA/`, `SAVES/`,
|
|
and `joeylog.txt` are there. It runs on a PSP with custom firmware (it is
|
|
not signed) and in PPSSPP. The ELF it is packed from stays in
|
|
`build/psp/elf/`, for `psp-addr2line`.
|
|
* *No serial port.* The PSP's one UART, on the remote connector, is
|
|
reached only from kernel mode, which a homebrew program does not run in.
|
|
* *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
|
|
has no environment), and there is no full screen to switch to.
|
|
* *Its own controls, a mouse, and a keyboard on the screen.* The PSP's
|
|
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
|
|
never there. The analog nub is the mouse: tilted past a small dead zone
|
|
(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
|
|
`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,
|
|
for every other key: the d-pad moves its cursor, cross presses the key
|
|
under it (held down as long as cross is), L and R move the keyboard to
|
|
the top of the screen and back, out of the way of what the program shows
|
|
there, and Square closes it. Shift, Ctrl, and Alt stay down until the
|
|
next other key is let go (or until pressed again), and Caps turns capital
|
|
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
|
|
would there, F1 to F10, the arrows, and Ctrl and Alt chords included.
|
|
While it is open, joystick 0 and the mouse buttons rest (the d-pad,
|
|
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
|
|
does.
|
|
* *Free space* (`jlDiskFree`) is that of the memory stick (or a PSP Go's
|
|
internal storage) the program is on.
|
|
* *Memory.* A PSP-1000 has 32 MB, about 24 MB of it for programs; later
|
|
models have more.
|
|
* *Alignment.* The PSP's MIPS processor stops a program that reads or writes
|
|
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
|
|
addresses). Read a multi-byte value from a file buffer a byte at a time.
|
|
The tests run every PSP program with PPSSPP checking every access the same
|
|
way.
|
|
|
|
`make psp` builds every example, and `./joey-run --port psp --headless NAME`
|
|
runs one under PPSSPP's headless build (the `ppsspp` installer component,
|
|
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
|
|
`EBOOT.PBP` in PPSSPP. None of it has run on a real PSP yet.
|
|
|
|
=== Windows Programs
|
|
|
|
The Win32 port is the hosted core with a backend of its own, written to the
|
|
Win32 API (`src/hosted/win32`), for the Windows SDL3 does not reach --
|
|
Windows 95, 98, ME, NT 4, and 2000 -- and every Windows since. Its stage is
|
|
the same chunky one, so UBER's hashes are DOS's. Everything in
|
|
<<_sdl3_programs>> holds, except:
|
|
|
|
* *Old Windows on purpose.* A program is a 32-bit `NAME.EXE` built by
|
|
llvm-mingw against `MSVCRT.DLL` (the `win32` installer component), marked
|
|
for Windows 4.0 so 95 and NT 4 will load it, linked statically, and
|
|
calling only what USER, GDI, WINMM, and KERNEL32 had in 1995. `MSVCRT.DLL`
|
|
came with Windows 95 OSR2 and Internet Explorer; a first-release Windows
|
|
95 needs it installed.
|
|
* *One thread.* The window is made on the program's thread, so its messages
|
|
arrive there and the port dispatches them at `jlInputPoll`, `jlWaitVBL`,
|
|
and `jlStagePresent`; the sound goes out through waveOut buffers handed
|
|
over at the same calls. There are no callbacks and no locks.
|
|
* *The window.* The stage is stretched with `StretchBlt` to the stage's
|
|
shape, 4:3 or square (the same `JOEYLIB_SCALE`, `JOEYLIB_ASPECT`, and
|
|
`JOEYLIB_FULLSCREEN` settings), black around it; full screen is a
|
|
borderless window over the whole monitor, which leaves the display mode
|
|
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
|
|
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.
|
|
* *Keys* by the PC scan code every key message carries (the same table as
|
|
DOS), typed characters through the user's keyboard layout, and the first
|
|
two joysticks through `joyGetPosEx`, calibrated in the Control Panel. Keys
|
|
held when the window loses the keyboard are let go.
|
|
* *Serial* through the Win32 COM calls (COM1, or COM<unit> for a slot).
|
|
Closing the port lets Windows drop DTR; Win32 has no call to prevent it.
|
|
|
|
`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
|
|
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
|
|
rather than Windows'. None of it has run on a real Windows 9x or NT 4 here.
|
|
|
|
The Win16 port is the hosted core with a backend written to the Windows 3.1
|
|
API (`src/hosted/win16`), for Windows 3.1 and Windows for Workgroups 3.11 on
|
|
a 386 or better. Its stage is the same chunky one, so UBER's hashes are DOS's
|
|
too. Everything said of Win32 above holds -- one thread, the window, keys by
|
|
scan code, DTR dropped at close -- except:
|
|
|
|
* *32-bit code on Windows 3.1.* A program is a Windows 3.x `NAME.EXE` built
|
|
with Open Watcom's Win386 target (the `win16` installer component): flat
|
|
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
|
|
calls only what Windows 3.1 has: USER, GDI, KERNEL, and MMSYSTEM.
|
|
* *Exact colors.* The frame is 8-bit, each pixel one entry of a 256-entry
|
|
color table holding all 16 palettes, which on a 256-color display is a
|
|
logical palette too, so every color is exact, SCB palettes included, as
|
|
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
|
|
different colors on screen, some show as the nearest it has.
|
|
`JOEYLIB_SCALE` defaults to 2, and the settings are read from the DOS
|
|
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
|
|
keypad's Enter.
|
|
* *Speed on a 386.* Sprites are compiled to x86 code, as on DOS. The port
|
|
expands the stage into the window's pixels itself, a presented row at a
|
|
time and scaled as it goes, and hands Windows only the rectangles that
|
|
changed, those close enough together as one. With Microsoft's WinG 1.0 on
|
|
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
|
|
plain copy to the screen; without it, each rectangle goes through a device
|
|
bitmap on a 256-color display, and only another kind of display pays for
|
|
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
|
|
ships the runtime under Microsoft's terms (see `LICENSES.md`). While the
|
|
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, color cycling
|
|
included, reaches the screen at once with nothing redrawn; more palettes
|
|
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
|
|
every frame runs at, in frames a second, with an Apple IIgs (under MAME)
|
|
for scale:
|
|
+
|
|
[cols="3,1,1,1,1", options="header"]
|
|
|===
|
|
| |2x with WinG |2x without |1:1 |IIgs
|
|
|Nothing moving, or color cycling |60 |60 |60 |60
|
|
|One 16x16 sprite moving |60 |60 |60 |60
|
|
|Eight sprites moving |26 |20 |30 |20
|
|
|Eight sprites in a column |30 |20 |30 |20
|
|
|The whole screen changing |6 |4 |10 |8.5
|
|
|===
|
|
+
|
|
The 2x window has nearly five times the IIgs's pixels to fill (640x480 for
|
|
its 320x200): a 386 keeps up with that for sprites, but not for a whole new
|
|
screen every frame, so a game that redraws everything every frame wants
|
|
`JOEYLIB_SCALE=1` there.
|
|
* *Sound* is 16-bit mono at 22.222 kHz through waveOut, what a Sound Blaster
|
|
and a 386 handle well (a Sound Blaster plays 22.05 kHz as 22.222, 0.8%
|
|
sharp, so JoeyLib asks for the rate it plays exactly; a card that refuses
|
|
it gets 22.05 kHz), or 8-bit on a card whose driver has no 16-bit (a
|
|
Sound Blaster 2.0, whose SNDBLST2.DRV is 8-bit only, which makes the quiet
|
|
attenuation steps coarser); stereo pan is ignored.
|
|
* *Joysticks* are the first two MMSYSTEM sticks, through `joyGetPos`, with
|
|
their buttons 1 and 2. Windows 3.x reads sticks only through a joystick
|
|
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,
|
|
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 centered as the
|
|
program starts. A game shipped for Windows 3.x can ship KPJOY.DRV with it
|
|
(`[drivers] joystick=kpjoy.drv` in SYSTEM.INI).
|
|
* *Files.* The program's folder, drive included, is the current directory
|
|
from the moment the window opens.
|
|
* *Serial* through Windows 3.1's communications calls (`OpenComm` and the
|
|
rest): COM1, or COM<unit> for a slot. JoeyLib's Windows runs KPCOMM.DRV
|
|
in place of COMM.DRV (the `kpcomm` installer component, built from the
|
|
mscomm project): both 16550A FIFOs and 115200 baud. Windows 3.1's own
|
|
driver goes up to 57600, and a faster rate gets that there.
|
|
* *Shipping.* Every Win16 program carries Open Watcom's C library and its
|
|
Win386 supervisor, and KPJOY.DRV and KPCOMM.DRV carry Open Watcom code
|
|
too. Open Watcom's license (the Sybase Open Watcom Public License,
|
|
section 2.2(d)) asks for a notice, in the program and in its
|
|
documentation, that the source of that code is available and where. The
|
|
port builds the notice into every program and the drivers carry it, never
|
|
shown; the documentation's part is this line, in the game's readme or
|
|
manual:
|
|
+
|
|
....
|
|
This software contains code from Open Watcom. Its source code is available
|
|
under the terms of the Sybase Open Watcom Public License version 1.0 from
|
|
https://github.com/open-watcom/open-watcom-v2
|
|
....
|
|
+
|
|
KPJOY.DRV and KPCOMM.DRV are MIT licensed: ship each with its `LICENSE.md`,
|
|
which the installer puts beside the driver in `toolchains/opt/`.
|
|
|
|
`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
|
|
for Workgroups 3.11 in DOSBox Staging (the `win311` installer component, set
|
|
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
|
|
against the stage after every present, with WinG and without (the optional
|
|
`wing` installer component: `./install.sh import WING10.EXE`, Microsoft's
|
|
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
|
|
here; the speeds above are DOSBox's 386SX-16 model's, and the IIgs's are
|
|
MAME's.
|
|
|
|
=== Haiku and BeOS Programs
|
|
|
|
The Be API ports are the hosted core with a backend written to the Be API
|
|
(`src/hosted/be`, C++), which Haiku reimplements, kept to the calls BeOS R5
|
|
already had. One source builds three ports: `haiku` (Haiku x86_64, gcc 13),
|
|
`haiku32` (Haiku x86, gcc 2.95, the BeOS C++ ABI), and `beos` (BeOS R5 x86,
|
|
built with the same gcc 2.95 against R5's own headers and libraries, which
|
|
the `beosR5` installer component copies off your BeOS 5 Professional CD).
|
|
Their stage is the same chunky one, so UBER's hashes are DOS's. Everything in
|
|
<<_sdl3_programs>> holds, except:
|
|
|
|
* *Threads.* The program's `main` keeps its own thread; the application and
|
|
the window run on threads of their own, as every Be program's do. Key and
|
|
mouse messages reach the program as events taken at `jlInputPoll`,
|
|
`jlWaitVBL`, and `jlStagePresent`, and the sound comes from a
|
|
`BSoundPlayer`.
|
|
* *The window* shows the stage scaled without filtering, at the stage's
|
|
shape, 4:3 or square, black around it (the same `JOEYLIB_SCALE`,
|
|
`JOEYLIB_ASPECT`, and `JOEYLIB_FULLSCREEN` settings; `JOEYLIB_SCALE`
|
|
defaults to 3). The port scales the frame itself and draws only what
|
|
changed since the last frame. Alt+Enter switches between the window and
|
|
the whole screen. The pointer is hidden over the window.
|
|
* *Keys* by raw key code, the key's place on the keyboard whatever the
|
|
keymap; typed characters are the keymap's, 7-bit ASCII only. The first
|
|
two joysticks (`BJoystick`) are `JL_JOYSTICK_0` and `JL_JOYSTICK_1`, their first
|
|
two axes and buttons.
|
|
* *Files.* The program's folder is the current directory from the moment
|
|
the window opens.
|
|
* *Its icon* is in its resources (<<_icons>>), but Tracker shows a file by
|
|
the icons in its attributes, which a program built on another machine does
|
|
not have. When it opens its window without them, the program copies them
|
|
across from its resources (as `mimeset -F` does), so Tracker shows it by
|
|
its icon once it has run; that is the one time a program writes to its own
|
|
file.
|
|
* *Serial* through termios: the first entry of `/dev/ports` (Haiku's
|
|
`pc_serial0`, BeOS's `serial1`), or the device the `JOEYLIB_SERIAL`
|
|
environment variable names, and the unit'th entry in name order for a
|
|
slot; 7 or 8 data bits only. The tests run it on Haiku; BeOS R5 shares the
|
|
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
|
|
know, so `make beos` links without combined relocations (`-z
|
|
nocombreloc`). Build for R5 with `make beos`, not `make haiku32`.
|
|
|
|
`make haiku`, `make haiku32`, and `make beos` build every example into
|
|
`build/<port>/bin/`, and `./joey-run --port <port> NAME` runs one in that
|
|
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
|
|
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
|
|
the fail-safe (VESA) mode, 1024x768 in 16-bit color; it has no sound card.
|
|
The tests run in the same VMs (`scripts/verify-be-input.sh`,
|
|
`scripts/verify-be-display.sh`, `scripts/verify-be-icon.sh`, and
|
|
`scripts/bench-be.sh`). None of it has
|
|
run on a real BeOS or Haiku machine here.
|