joeylib2/README.md

14 KiB

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:

#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)

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.

#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).

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.

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.

#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.

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.

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)

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.

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.

#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.

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.