# 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 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// 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// per-target build outputs ``` ## Public API Game code includes a single umbrella header: ```c #include ``` 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 { 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 16-color images with a 200-entry SCB table and 16 palettes of 16 `$0RGB` colors. In-memory storage is target-native: chunky 4bpp packed on IIgs and DOS, native planar (separate bitplanes on Amiga, word-interleaved planes on Atari ST) on the 68k ports. The public API speaks in color indices (0..15) and hides the storage format. ```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 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); SpriteT *spriteCreateFromSurface (const SurfaceT *src, int16_t x, int16_t y, uint8_t widthTiles, uint8_t heightTiles); SpriteT *spriteLoadFile (const char *path); SpriteT *spriteFromCompiledMem (const uint8_t *data, uint32_t length); 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.