joeylib2/README.md

390 lines
14 KiB
Markdown

# JoeyLib
A unified C game-development library targeting four early 16-bit
platforms from a single codebase:
- Apple IIgs (reference platform)
- Commodore Amiga (A500 / 68000 baseline)
- Atari ST (STF / 68000 baseline)
- MS-DOS (386 / VGA, DJGPP)
The Apple IIgs defines the capability ceiling. Stronger platforms coast.
Hot paths are hand-written assembly per port; the public API is C.
See `docs/DESIGN.md` for the full 1.0 design.
## Quick start
```
git clone <repo> joeylib
cd joeylib
./toolchains/install.sh
# follow any non-free-tool placement instructions the script prints
source toolchains/env.sh
make
```
This builds `libjoey.a` for every target whose toolchain is installed,
plus the example programs (`hello`, `pattern`, `keys`, `joy`, `sprite`,
`audio`) for each.
## Building for a single target
```
source toolchains/env.sh
make iigs
make amiga
make atarist
make dos
```
## Repository layout
```
docs/ design and reference documentation
include/joey/ public headers
src/core/ portable library code
src/codegen/ runtime sprite codegen (per-CPU emitters)
src/port/<plat>/ per-platform HAL implementations
src/shared68k/ assembly shared by Amiga and Atari ST
tools/joeyasset/ sprite and tile asset converter
tools/joeymod/ Protracker .MOD converter (passthrough or .NTP)
examples/ example programs
toolchains/ self-contained cross-build tools
make/ per-target Makefile fragments
build/<plat>/ per-target build outputs
```
## Public API
Game code includes a single umbrella header:
```c
#include <joey/joey.h>
```
That pulls in every public surface listed below. Full documentation
lives in the per-feature headers under `include/joey/`; what follows
is a quick reference. Every entry point is plain C, no C++ extensions.
### Lifecycle (`joey/core.h`)
```c
typedef struct {
HostModeE hostMode; // HOST_MODE_TAKEOVER or HOST_MODE_OS
uint32_t codegenBytes; // runtime compiled-sprite cache size
uint16_t maxSurfaces; // maximum concurrent surfaces
uint32_t audioBytes; // audio sample / module RAM pool
uint32_t assetBytes; // tileset / sprite / map RAM pool
} JoeyConfigT;
bool joeyInit (const JoeyConfigT *config);
void joeyShutdown (void);
const char *joeyLastError (void);
const char *joeyPlatformName (void);
const char *joeyVersionString(void);
void joeyWaitVBL (void); // block until next VBL
uint16_t joeyFrameCount (void); // monotonic 16-bit frame counter
uint16_t joeyFrameHz (void); // 50 / 60 / 70 depending on port
```
### Surfaces (`joey/surface.h`)
All surfaces are 320x200 4bpp packed (high nibble = left pixel) with
a 200-entry SCB table and 16 palettes of 16 `$0RGB` colors.
```c
#define SURFACE_WIDTH 320
#define SURFACE_HEIGHT 200
#define SURFACE_BYTES_PER_ROW 160
#define SURFACE_PIXELS_SIZE (SURFACE_BYTES_PER_ROW * SURFACE_HEIGHT)
#define SURFACE_PALETTE_COUNT 16
#define SURFACE_COLORS_PER_PALETTE 16
typedef struct SurfaceT SurfaceT; // opaque
SurfaceT *surfaceCreate (void);
void surfaceDestroy(SurfaceT *s);
SurfaceT *stageGet (void); // library back-buffer
void surfaceCopy (SurfaceT *dst, const SurfaceT *src);
bool surfaceSaveFile(const SurfaceT *src, const char *path);
bool surfaceLoadFile(SurfaceT *dst, const char *path);
uint32_t surfaceHash (const SurfaceT *s); // FNV-1a of logical pixels
```
`surfaceSaveFile` writes the surface in **target-native** form. Files
are NOT cross-port portable; the asset pipeline handles conversion.
### Drawing (`joey/draw.h`)
All primitives clip to the surface; off-surface coords are silent
no-ops. Color 0 is plotted normally (use the masked variants if you
need transparency).
```c
void surfaceClear (SurfaceT *s, uint8_t color);
void drawPixel (SurfaceT *s, int16_t x, int16_t y, uint8_t color);
uint8_t samplePixel (const SurfaceT *s, int16_t x, int16_t y);
void drawLine (SurfaceT *s, int16_t x0, int16_t y0,
int16_t x1, int16_t y1, uint8_t color);
void drawRect (SurfaceT *s, int16_t x, int16_t y,
uint16_t w, uint16_t h, uint8_t color);
void fillRect (SurfaceT *s, int16_t x, int16_t y,
uint16_t w, uint16_t h, uint8_t color);
void drawCircle (SurfaceT *s, int16_t cx, int16_t cy,
uint16_t r, uint8_t color);
void fillCircle (SurfaceT *s, int16_t cx, int16_t cy,
uint16_t r, uint8_t color);
void floodFill (SurfaceT *s, int16_t x, int16_t y, uint8_t newColor);
void floodFillBounded (SurfaceT *s, int16_t x, int16_t y,
uint8_t newColor, uint8_t boundaryColor);
void surfaceBlit (SurfaceT *dst, const JoeyAssetT *src, int16_t x, int16_t y);
void surfaceBlitMasked (SurfaceT *dst, const JoeyAssetT *src,
int16_t x, int16_t y, uint8_t transparentIndex);
```
### Palette and SCB (`joey/palette.h`)
Colors are 12-bit `$0RGB`. Color 0 of every palette is forced to
black on `paletteSet`. Each scanline picks one of the 16 palettes
via the SCB.
```c
void paletteSet (SurfaceT *s, uint8_t paletteIndex, const uint16_t *colors16);
void paletteGet (const SurfaceT *s, uint8_t paletteIndex, uint16_t *out16);
void scbSet (SurfaceT *s, uint16_t line, uint8_t paletteIndex);
void scbSetRange (SurfaceT *s, uint16_t firstLine, uint16_t lastLine,
uint8_t paletteIndex);
uint8_t scbGet (const SurfaceT *s, uint16_t line);
```
### Tiles (`joey/tile.h`)
A "tile" is just an 8x8-aligned region of any surface. The API moves
32-byte chunks between surfaces and provides a small `TileT` value
type so callers can stash a copy without allocating a scratch surface.
```c
#define TILE_PIXELS_PER_SIDE 8
#define TILE_BYTES_PER_ROW 4
#define TILE_BYTES (TILE_BYTES_PER_ROW * TILE_PIXELS_PER_SIDE)
#define TILE_BLOCKS_PER_ROW (SURFACE_WIDTH / TILE_PIXELS_PER_SIDE) // 40
#define TILE_BLOCKS_PER_COL (SURFACE_HEIGHT / TILE_PIXELS_PER_SIDE) // 25
#define TILE_NO_GLYPH ((uint16_t)0xFFFFu)
typedef struct TileT { uint8_t pixels[TILE_BYTES]; } TileT;
void tileCopy (SurfaceT *dst, uint8_t dstBx, uint8_t dstBy,
const SurfaceT *src, uint8_t srcBx, uint8_t srcBy);
void tileCopyMasked (SurfaceT *dst, uint8_t dstBx, uint8_t dstBy,
const SurfaceT *src, uint8_t srcBx, uint8_t srcBy,
uint8_t transparentIndex);
void tileFill (SurfaceT *s, uint8_t bx, uint8_t by, uint8_t color);
void tileSnap (const SurfaceT *src, uint8_t bx, uint8_t by, TileT *out);
void tilePaste (SurfaceT *dst, uint8_t bx, uint8_t by, const TileT *in);
void drawText (SurfaceT *dst, uint8_t bx, uint8_t by,
const SurfaceT *fontSurface, const uint16_t *asciiMap,
const char *str);
```
### Sprites (`joey/sprite.h`)
Rectangles of 8x8 tiles drawn at arbitrary pixel positions with
color-0 transparency. Tile data is `widthTiles * heightTiles * 32`
bytes, tile-major 4bpp packed. Sprites can be runtime-compiled
into per-shift code variants for fast draws.
```c
typedef enum { SPRITE_FLAGS_NONE = 0 } SpriteFlagsE;
typedef struct SpriteT SpriteT; // opaque
typedef struct {
SpriteT *sprite;
int16_t x, y;
uint16_t width, height; // pixels
uint8_t *bytes; // caller-owned save-under buffer
uint16_t sizeBytes;
} SpriteBackupT;
SpriteT *spriteCreate (const uint8_t *tileData,
uint8_t widthTiles, uint8_t heightTiles,
SpriteFlagsE flags);
SpriteT *spriteCreateFromSurface (const SurfaceT *src, int16_t x, int16_t y,
uint8_t widthTiles, uint8_t heightTiles,
SpriteFlagsE flags);
SpriteT *spriteLoadFile (const char *path, SpriteFlagsE flags);
SpriteT *spriteFromCompiledMem (const uint8_t *data, uint32_t length,
SpriteFlagsE flags);
bool spriteSaveFile (SpriteT *sp, const char *path);
void spriteDestroy (SpriteT *sp);
bool spriteCompile (SpriteT *sp); // build per-shift fast path
void spritePrewarm (SpriteT *sp); // hint: compile if not already
void spriteDraw (SurfaceT *s, SpriteT *sp, int16_t x, int16_t y);
void spriteSaveUnder (const SurfaceT *s, SpriteT *sp,
int16_t x, int16_t y, SpriteBackupT *backup);
void spriteRestoreUnder (SurfaceT *s, const SpriteBackupT *backup);
void spriteSaveAndDraw (SurfaceT *s, SpriteT *sp, int16_t x, int16_t y,
SpriteBackupT *backup);
void spriteCompact (void); // defrag the codegen arena
uint32_t spriteCodegenBytesUsed (void);
uint32_t spriteCodegenBytesTotal (void);
```
### Assets (`joey/asset.h`)
Small bitmap blits with optional embedded palette, in `.jas` format.
Use embedded `const JoeyAssetT` for ship-with-binary art; use the
loaders for on-disk assets.
```c
typedef struct {
uint16_t width;
uint16_t height;
bool hasPalette;
uint16_t palette[16]; // valid only if hasPalette
const uint8_t *pixels; // 4bpp packed, rowBytes = (width+1)/2
} JoeyAssetT;
JoeyAssetT *joeyAssetLoadFile (const char *path);
JoeyAssetT *joeyAssetFromMem (const uint8_t *data, uint32_t length);
void joeyAssetFree (JoeyAssetT *asset);
void joeyAssetApplyPalette (SurfaceT *dst, uint8_t paletteIndex,
const JoeyAssetT *asset);
```
### Present (`joey/present.h`)
```c
void stagePresent(void);
```
Flips the dirty rows of the stage to the display, then clears dirty
state. Drawing primitives mark dirty as a side effect, so calling
`stagePresent` once at end-of-frame is enough.
### Input (`joey/input.h`)
Call `joeyInputPoll` once per frame, then query the state predicates.
Edge predicates (`*Pressed`, `*Released`) fire only in the frame the
transition happened.
```c
typedef enum { /* KEY_NONE, KEY_A..KEY_Z, KEY_0..KEY_9, KEY_SPACE,
KEY_ESCAPE, KEY_RETURN, KEY_TAB, KEY_BACKSPACE,
KEY_UP/DOWN/LEFT/RIGHT, KEY_LSHIFT/RSHIFT/LCTRL/LALT,
KEY_F1..KEY_F10, KEY_COUNT */ } JoeyKeyE;
typedef enum { MOUSE_BUTTON_NONE, MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT,
MOUSE_BUTTON_MIDDLE, MOUSE_BUTTON_COUNT } JoeyMouseButtonE;
typedef enum { JOYSTICK_0, JOYSTICK_1, JOYSTICK_COUNT } JoeyJoystickE;
typedef enum { JOY_BUTTON_0, JOY_BUTTON_1, JOY_BUTTON_COUNT } JoeyJoyButtonE;
#define JOYSTICK_AXIS_MAX 127
#define JOYSTICK_AXIS_MIN (-127)
void joeyInputPoll (void);
void joeyWaitForAnyKey (void);
bool joeyKeyDown (JoeyKeyE key);
bool joeyKeyPressed (JoeyKeyE key);
bool joeyKeyReleased (JoeyKeyE key);
int16_t joeyMouseX (void);
int16_t joeyMouseY (void);
bool joeyMouseDown (JoeyMouseButtonE b);
bool joeyMousePressed (JoeyMouseButtonE b);
bool joeyMouseReleased (JoeyMouseButtonE b);
bool joeyJoystickConnected(JoeyJoystickE js);
int8_t joeyJoystickX (JoeyJoystickE js);
int8_t joeyJoystickY (JoeyJoystickE js);
bool joeyJoyDown (JoeyJoystickE js, JoeyJoyButtonE b);
bool joeyJoyPressed (JoeyJoystickE js, JoeyJoyButtonE b);
bool joeyJoyReleased (JoeyJoystickE js, JoeyJoyButtonE b);
void joeyJoystickReset (JoeyJoystickE js, uint8_t deadZone);
```
### Audio (`joey/audio.h`)
4-channel Protracker-style music plus four one-shot SFX slots. Module
data must be the platform-native form produced by `tools/joeymod`
(`.mod` for Amiga/DOS/ST; `.ntp` for IIgs; `.amod` if you want
loop=false on Amiga). A failed `joeyAudioInit` is non-fatal; the rest
of the API stays callable as no-ops.
```c
#define JOEY_AUDIO_SFX_SLOTS 4
bool joeyAudioInit (void);
void joeyAudioShutdown (void);
void joeyAudioPlayMod (const uint8_t *data, uint32_t length, bool loop);
void joeyAudioStopMod (void);
bool joeyAudioIsPlayingMod (void);
void joeyAudioPlaySfx (uint8_t slot, const uint8_t *sample,
uint32_t length, uint16_t rateHz);
void joeyAudioStopSfx (uint8_t slot);
void joeyAudioFrameTick (void);
```
### Debug logging (`joey/debug.h`)
Crash-tracing logger. Writes are buffered and durable across normal
exit; call `joeyLogFlush` ahead of suspected hang points if you want
a guaranteed last-line-on-disk.
```c
void joeyLog (const char *msg);
void joeyLogF (const char *fmt, ...);
void joeyLogFlush(void);
void joeyLogReset(void);
```
Output goes to `joeylog.txt` in the program's working directory.
### Platform macros (`joey/platform.h`)
The build system normally sets the platform via `-D`; auto-detection
from compiler-predefined macros is a fallback. Game code can
conditionally compile on these:
```
JOEYLIB_PLATFORM_IIGS / _AMIGA / _ATARIST / _DOS // exactly one defined
JOEYLIB_CPU_65816 / _68000 / _X86
JOEYLIB_ENDIAN_LITTLE / _BIG
JOEYLIB_NATIVE_CHUNKY / _NATIVE_PLANAR
JOEYLIB_HAS_BLITTER / _HAS_COPPER // Amiga only
JOEYLIB_PLATFORM_NAME // human-readable string
JOEYLIB_VERSION_MAJOR / _MINOR / _PATCH / _STRING
```
## License
TBD.