// Internal platform-override registry (the new joeylib's joey.h equivalent).
//
// Every overridable operation is a jlp<Op>() resolved at COMPILE TIME: a
// portable-C default lives in src/generic/ and a machine supplies its own
// version when it defines JL_HAS_<OP> in include/joey/platform.h. There is no
// runtime dispatch. This header wires the two together (the dispatch tail at
// the bottom) and holds the IIgs asm-macro overrides.
//
// Two flavors of op (both use the same jlp + JL_HAS mechanism):
//   (A) ops with a genuine portable generic -- draw/tile/sprite primitives,
//       surface readers, allocation, millisElapsed. A machine overrides only
//       where it needs native speed/behavior; otherwise it links the generic.
//   (B) platform SERVICES with no portable behavior -- init/shutdown/present,
//       input, vbl/frame timing, the audio engine. The "generic" is a no-op/
//       zero stub so a blank/template port still links; every real port
//       overrides via JL_HAS_<OP>.
//
// Included by src/core/*.c, src/<machine>/*.c, and src/generic/*.c. NOT public.

#ifndef JOEYLIB_PORT_H
#define JOEYLIB_PORT_H

#include <stdio.h>

#include "joey/audio.h"
#include "joey/core.h"
#include "joey/input.h"
#include "joey/serial.h"
#include "joey/sprite.h"
#include "joey/surface.h"

// Every op's declaration + its generic/override dispatch lives in the "Op
// dispatch" section at the bottom of this header. The block immediately below
// holds the IIgs's asm-macro overrides: it #define-s jlp<Op> directly to the
// iigs*Inner asm entry points, so IIgs primitives never pay a function call.


#ifdef JOEYLIB_PLATFORM_IIGS
// Root the save tree at the APPLICATION directory via the GS/OS "1/" prefix
// designator (the Loader sets prefix 1 to the app's directory at launch).
// Unrooted paths resolve against prefix 0, which a Finder launch leaves
// pointing away from (or nowhere near) the app's volume - measured: every
// plain "SAVES/x" open failed under MAME GS/OS 6 while the same file opened
// fine through "1/SAVES/x".  libc's fopen passes the string verbatim to
// GS/OS Open, which accepts prefix-designator syntax.
#define JL_SAVE_DIR        "1/SAVES"
#define JL_SAVE_DIR_PREFIX "1/SAVES/"

// =====================================================================
// IIgs direct-dispatch macros.
//
// IIgs overrides the draw/tile/flood primitives by #define-ing jlp<Op>
// directly to its asm entry points (iigs*Inner), so each primitive inlines
// the asm call at the call site with no function-call plumbing. A C wrapper
// would cost ~60-80 cyc/call of pure overhead here (prologue PHB/PHD/TCD,
// redundant arg re-push for the inner JSL, then epilogue); the macro form
// eliminates it. Cross-platform code in src/core/*.c calls jlp<Op>(...)
// unchanged and the preprocessor swaps in the asm before compile.
//
// Ops the IIgs does not accelerate (or only accelerates for the stage)
// fall through to the portable-C generic jlpGeneric<Op>, like every other
// machine. Some macros use comma-expression form to evaluate to a `bool`
// (e.g. fillCircle: stage-only, returns false off-stage so the core falls
// back to its C span fill).
// =====================================================================

extern void iigsDrawPixelInner   (uint8_t *pixels, uint16_t x, uint16_t y, uint16_t nibble);
extern void iigsDrawPixelMark    (uint16_t x, uint16_t y, uint16_t nibble);
extern void iigsDrawLineInner    (uint8_t *pixels, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t nibble);
extern void iigsDrawCircleInner  (uint8_t *pixels, uint16_t cx, uint16_t cy, uint16_t r, uint16_t nibble);
extern void iigsFillCircleInner  (uint8_t *pixels, uint16_t cx, uint16_t cy, uint16_t r, uint16_t nibble);
extern void iigsSurfaceClearInner(uint8_t *pixels, uint16_t fillWord);
extern void iigsSurfaceClearFastInner(uint8_t *pixels, uint16_t fillWord);
extern void iigsTileFillInner    (uint8_t *dstRow0, uint16_t nibble);
extern void iigsTileFillMark     (uint16_t nibble, uint16_t bx, uint16_t by);
extern void iigsTileCopyInner    (uint8_t *dstRow0, const uint8_t *srcRow0);
extern void iigsTileCopyMark     (const uint8_t *srcRow0, uint16_t dstBx, uint16_t dstBy);
extern void iigsTileCopyMaskedInner(uint8_t *dstRow0, const uint8_t *srcRow0, uint16_t transparent);
extern void iigsTilePasteInner   (uint8_t *dstRow0, const uint8_t *srcTilePixels);
extern void iigsTilePasteMark    (const uint8_t *srcTilePixels, uint16_t bx, uint16_t by);
extern void iigsTileMapPasteMark (uint16_t bx, uint16_t by, uint16_t wTiles, uint16_t hTiles, const uint8_t *tiles, const uint8_t *map);
extern void iigsTilePasteMonoInner(uint8_t *dstRow0, const uint8_t *monoTile, uint16_t fgColor, uint16_t bgColor);
extern void iigsTileSnapInner    (uint8_t *dstTilePixels, const uint8_t *srcRow0);
extern void iigsCopyRectRows     (uint8_t *dst, uint16_t srcLo, uint16_t srcBank, uint16_t rowBytes, uint16_t rows);
extern void iigsQuitGS           (void);
extern void iigsFillRectMark     (uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t nibble);
extern void iigsFillRectNoMark   (uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t nibble);
extern void iigsFloodWalkAndScansInner(uint8_t *pixels, uint16_t x, uint16_t y, uint16_t matchColor, uint16_t newColor, uint16_t matchEqual, int16_t *stackX, int16_t *stackY, uint16_t *spInOut, uint16_t maxSp);
extern uint16_t gFloodSeedMatch;
extern uint16_t gFloodLeftX;
extern uint16_t gFloodRightX;

// Stage identity for the stage-gated macros below: a direct extern
// read of surface.c's gStage (also declared in surfaceInternal.h --
// duplicated here because port.h is included by TUs that never pull in
// surfaceInternal.h) instead of the jlStageGet() accessor, which costs
// a cross-segment JSL per primitive/span (PERF-AUDIT #10/#17). Same
// direct-extern precedent as codegenArenaInternal.h's arena globals.
// Read-only here.
extern jlSurfaceT *gStage;

// drawPixel: migrated to jlpDrawPixel. The asm plots into s->pixels for any
// IIgs (chunky) surface; no generic defer needed.
#define jlpDrawPixel(_s, _x, _y, _c) \
    iigsDrawPixelInner((_s)->pixels, (uint16_t)(_x), (uint16_t)(_y), \
                       (uint16_t)((_c) & 0x0F))

// drawLine / drawCircle migrated to jlp<Op>: asm on any IIgs (chunky) surface,
// callers gate the on-surface fast path on JL_HAS_DRAW_LINE / _CIRCLE.
#define jlpDrawLine(_s, _x0, _y0, _x1, _y1, _c) \
    iigsDrawLineInner((_s)->pixels, (uint16_t)(_x0), (uint16_t)(_y0), \
                      (uint16_t)(_x1), (uint16_t)(_y1), \
                      (uint16_t)((_c) & 0x0F))

#define jlpDrawCircle(_s, _cx, _cy, _r, _c) \
    iigsDrawCircleInner((_s)->pixels, (uint16_t)(_cx), (uint16_t)(_cy), \
                        (_r), (uint16_t)((_c) & 0x0F))

// fillCircle migrated to jlpFillCircle: stage-only asm, returns bool so the core
// falls to the C span fill for non-stage / off-surface. JL_HAS_FILL_CIRCLE-gated.
// The asm derives its fill bytes from the raw nibble (PERF-AUDIT #41): the
// macro passes (c & 0x0F), never the old * 0x1111 fill word, which lowered
// to a software-multiply helper call for a runtime color (the only call
// site, draw.c, always passes a variable). The jlp layer owns the & 0x0F.
#define jlpFillCircle(_s, _cx, _cy, _r, _c) \
    ((_s) == gStage \
     ? (iigsFillCircleInner((_s)->pixels, (uint16_t)(_cx), (uint16_t)(_cy), \
                            (_r), (uint16_t)((_c) & 0x0F)), \
        true) \
     : false)

// surfaceClear: migrated to the jlp<Op> override model. The asm clears the
// stage surface; non-stage surfaces defer to the portable-C generic default.
#define jlpSurfaceClear(_s, _d) \
    ((_s) == gStage \
     ? iigsSurfaceClearFastInner((_s)->pixels, \
                              (uint16_t)((uint16_t)(_d) | ((uint16_t)(_d) << 8))) \
     : jlpGenericSurfaceClear((_s), (_d)))

// fillRect: STAGE-GATED onto the FUSED Mark entry (NATIVE-PERF Phase 6
// W1, the tile Step-D pattern): ONE JSL derives the destination in asm
// from the pinned stage base + gRowOffsetLut, fills W x H with the same
// leading/middle/trailing nibble handling as the old Inner, and widens
// the H-row dirty band via stageRectMark -- replacing the fill JSL plus
// the C-side surfaceMarkDirtyRect -> iigsMarkDirtyRowsInner JSL pair
// (draw.c compiles that mark out under JL_FILL_RECT_MARK_FUSED below).
// Non-stage surfaces defer to the chunky generic and need no mark. The
// asm derives the fill word from the raw nibble (PERF-AUDIT #41); the
// jlp layer owns the & 0x0F.
#define jlpFillRect(_s, _x, _y, _w, _h, _c) \
    ((_s) == gStage \
     ? iigsFillRectMark((uint16_t)(_x), (uint16_t)(_y), \
                        (uint16_t)(_w), (uint16_t)(_h), \
                        (uint16_t)((_c) & 0x0F)) \
     : jlpGenericFillRect((_s), (_x), (_y), (_w), (_h), (_c)))

// Unfused twin for callers that batch their own mark (jlDrawRect's
// on-surface fast path keeps its single whole-outline mark instead of
// paying two (h-2)-row fused walks on the vertical edges; jlFillCircle
// batches one bounding-square mark over its spans). Same body in asm;
// a flag skips the stageRectMark tail.
#define jlpFillRectNoMark(_s, _x, _y, _w, _h, _c) \
    ((_s) == gStage \
     ? iigsFillRectNoMark((uint16_t)(_x), (uint16_t)(_y), \
                          (uint16_t)(_w), (uint16_t)(_h), \
                          (uint16_t)((_c) & 0x0F)) \
     : jlpGenericFillRect((_s), (_x), (_y), (_w), (_h), (_c)))

// The stage arm of jlpFillRect fuses its dirty mark into the asm.
#define JL_FILL_RECT_MARK_FUSED 1

// Tile primitives operate on caller-computed row pointers; just
// forward the args. by/bx are tile coords -> bx*4 + by*8*160 byte
// offset within the surface. Use SURFACE_ROW_OFFSET (LUT lookup) to
// dodge a software multiply helper for the *160 multiply.
// tileFill / tileCopy / tilePaste are STAGE-GATED onto the FUSED Mark
// entries (NATIVE-PERF Phase 2 Step D / R2): for the stage, ONE JSL
// derives the destination in asm from the pinned stage base + the
// gRowOffsetLut, runs the op, and widens the dirty band -- replacing
// C-side pointer math plus two JSLs (op inner + iigsMarkDirtyRowsInner
// via tile.c's tileMarkDirty, which is compiled out for these three
// ops on IIgs). Non-stage surfaces keep the pointer-based inners.
// tileFill: the macro passes the raw nibble (the jlp layer owns the & 0x0F);
// the asm derives the $NNNN fill word (PERF-AUDIT #41 -- the old C-side
// * 0x1111 lowered to a software-multiply helper for a runtime color).
#define jlpTileFill(_s, _bx, _by, _c) \
    ((_s) == gStage \
     ? iigsTileFillMark((uint16_t)((_c) & 0x0F), (uint16_t)(_bx), (uint16_t)(_by)) \
     : iigsTileFillInner(&(_s)->pixels[SURFACE_ROW_OFFSET((uint16_t)(_by) << 3) \
                                       + ((uint16_t)(_bx) << 2)], \
                         (uint16_t)((_c) & 0x0F)))

// tileCopy / tileCopyMasked: take surface+tile-coords; the asm macro derives
// row-0 pointers (row = by*8 -> SURFACE_ROW_OFFSET(by<<3); col byte = bx*4 -> bx<<2).
// tileCopy's src may be ANY surface, so its row-0 pointer stays C-derived
// even on the fused stage-dst arm.
#define jlpTileCopy(_d, _dbx, _dby, _s, _sbx, _sby) \
    ((_d) == gStage \
     ? iigsTileCopyMark(&(_s)->pixels[SURFACE_ROW_OFFSET((uint16_t)(_sby) << 3) + ((uint16_t)(_sbx) << 2)], (uint16_t)(_dbx), (uint16_t)(_dby)) \
     : iigsTileCopyInner(&(_d)->pixels[SURFACE_ROW_OFFSET((uint16_t)(_dby) << 3) + ((uint16_t)(_dbx) << 2)], \
                         &(_s)->pixels[SURFACE_ROW_OFFSET((uint16_t)(_sby) << 3) + ((uint16_t)(_sbx) << 2)]))

#define jlpTileCopyMasked(_d, _dbx, _dby, _s, _sbx, _sby, _t) \
    iigsTileCopyMaskedInner(&(_d)->pixels[SURFACE_ROW_OFFSET((uint16_t)(_dby) << 3) + ((uint16_t)(_dbx) << 2)], \
                            &(_s)->pixels[SURFACE_ROW_OFFSET((uint16_t)(_sby) << 3) + ((uint16_t)(_sbx) << 2)], \
                            (uint16_t)(_t))

// tilePaste / tileSnap: take surface+coords + the chunky tile buffer; the asm
// macros derive row-0.
#define jlpTilePaste(_d, _bx, _by, _ct) \
    ((_d) == gStage \
     ? iigsTilePasteMark((_ct), (uint16_t)(_bx), (uint16_t)(_by)) \
     : iigsTilePasteInner(&(_d)->pixels[SURFACE_ROW_OFFSET((uint16_t)(_by) << 3) + ((uint16_t)(_bx) << 2)], (_ct)))

#define jlpTileSnap(_s, _bx, _by, _out) \
    iigsTileSnapInner((_out), &(_s)->pixels[SURFACE_ROW_OFFSET((uint16_t)(_by) << 3) + ((uint16_t)(_bx) << 2)])

// tilePasteMono: fused mono colorize + paste in one asm pass (PERF-AUDIT #3)
// -- no intermediate jlTileT and no second paste call. Stage-only like the
// other stage-gated macros; non-stage surfaces defer to the portable-C
// generic (colorize + jlpTilePaste). fg/bg arrive PRE-MASKED to 4 bits (the
// public jlTilePasteMono wrapper owns the & 0x0F, PERF-AUDIT #69), so they
// are passed through un-re-masked per the dispatch-stanza contract below.
#define jlpTilePasteMono(_d, _bx, _by, _mt, _fg, _bg) \
    ((_d) == gStage \
     ? iigsTilePasteMonoInner(&(_d)->pixels[SURFACE_ROW_OFFSET((uint16_t)(_by) << 3) + ((uint16_t)(_bx) << 2)], \
                              (_mt), (uint16_t)(_fg), (uint16_t)(_bg)) \
     : jlpGenericTilePasteMono((_d), (_bx), (_by), (_mt), (_fg), (_bg)))

// Flood: IIgs overrides only the all-in-one tier (JL_HAS_FLOOD_WALK_AND_SCANS).
// The dispatch tail leaves jlpFloodWalkAndScans defined here; the planar flood
// hooks (jlpFloodWalkPlanes / jlpFloodScanRowPlanes) belong to the planar 68k
// ports (Amiga + Atari ST) and IIgs never declares them (their call sites are
// #if'd out on ports without the JL_HAS_FLOOD_*_PLANES flags).
//
// Tier-1 flood: multi-output. Asm sets gFloodSeedMatch / gFloodLeftX /
// gFloodRightX; macro reads those into the caller's out-ptrs.
#define jlpFloodWalkAndScans(_pix, _x, _y, _mc, _nc, _me, _sx, _sy, _sp, _ms, _smOut, _lxOut, _rxOut) \
    (iigsFloodWalkAndScansInner((_pix), (uint16_t)(_x), (uint16_t)(_y), \
                                (uint16_t)((_mc) & 0x0F), \
                                (uint16_t)((_nc) & 0x0F), \
                                (uint16_t)((_me) ? 1 : 0), \
                                (_sx), (_sy), \
                                (uint16_t *)(_sp), \
                                (uint16_t)(_ms)), \
     *(_smOut) = (gFloodSeedMatch != 0), \
     *(_lxOut) = (int16_t)gFloodLeftX, \
     *(_rxOut) = (int16_t)gFloodRightX, \
     true)

// =====================================================================
// IIgs planar-hook elision.
//
// The planar dual-write hooks (surfaceCopyPlanes, the sprite *Planes ops)
// let Amiga/ST mirror draws into their bitplanes. IIgs is chunky-native, so
// it #define-s them to ((void)0): the call site is deleted at preprocess time
// rather than paying a cross-segment JSL/RTL for a no-op. Arg lists match each
// op's arity so the preprocessor parses and discards the exact call; all args
// are plain lvalues / field reads, so dropping them changes no behavior. (The
// other chunky ports get the same elision from the shared chunky block after
// the IIgs section -- PERF-AUDIT #42. The tile/fillRect planar work is folded
// into each jlp<Op> op, so there are no separate planar hooks left to elide
// for those.)
// =====================================================================

#define jlpSurfaceCopyPlanes(_dst, _src) ((void)0)

// sprite planes mirror: no-op on IIgs (chunky -> codegen does the work).
#define jlpSpriteDrawPlanes(_s, _sp, _x, _y) ((void)0)
#define jlpSpriteSavePlanes(_s, _x, _y, _w, _h, _dst) ((void)0)
#define jlpSpriteRestorePlanes(_s, _x, _y, _w, _h, _src) ((void)0)

// frameCount: straight to the GetTick asm wrapper (peislam.s). The hal.c
// C wrapper this replaces was one of two pure forwarding layers between
// jlFrameCount and the Misc Toolset GetTick call -- ~60-80 cycles each on
// a clock that game loops (and the UBER bench loop) poll constantly.
extern uint16_t iigsGetTickWord(void);
#define jlpFrameCount() iigsGetTickWord()

// =====================================================================
// JL_HAS registry consistency checks (PERF-AUDIT #26).
//
// The dispatch stanzas below short-circuit on #if !defined(jlp<Op>)
// before the JL_HAS_<OP> flag is ever tested, and the core fast-path
// gates (JL_HAS_DRAW_LINE / DRAW_CIRCLE / FILL_CIRCLE /
// FLOOD_WALK_AND_SCANS in core/draw.c) consult only the flag. So a
// macro override above whose flag is missing from the
// include/joey/platform.h registry either turns that registry into
// false documentation or silently drops a core fast path back to
// generic C on the perf-reference machine. These checks make a
// missing/removed flag a compile failure instead: one #error per
// macro override defined above. (The reverse drift -- flag present,
// macro removed -- already fails loudly: the dispatch stanza then
// declares a function override that nothing defines.) Extend this
// list whenever a new IIgs macro override is added.
//
// Deliberately unchecked: jlpFrameCount (registry documents it as
// macro-only, no JL_HAS_FRAME_COUNT on IIgs) and jlpSurfaceCopyPlanes
// (JL_HAS_SURFACE_COPY_PLANES means a real planar function override;
// the IIgs elision above pairs with the JL_HAS_SPRITE_* trio instead).
// =====================================================================

#if !defined(JL_HAS_DRAW_PIXEL)
  #error "IIgs jlpDrawPixel macro without JL_HAS_DRAW_PIXEL in platform.h"
#endif
#if !defined(JL_HAS_DRAW_LINE)
  #error "IIgs jlpDrawLine macro without JL_HAS_DRAW_LINE in platform.h"
#endif
#if !defined(JL_HAS_DRAW_CIRCLE)
  #error "IIgs jlpDrawCircle macro without JL_HAS_DRAW_CIRCLE in platform.h"
#endif
#if !defined(JL_HAS_FILL_CIRCLE)
  #error "IIgs jlpFillCircle macro without JL_HAS_FILL_CIRCLE in platform.h"
#endif
#if !defined(JL_HAS_SURFACE_CLEAR)
  #error "IIgs jlpSurfaceClear macro without JL_HAS_SURFACE_CLEAR in platform.h"
#endif
#if !defined(JL_HAS_FILL_RECT)
  #error "IIgs jlpFillRect macro without JL_HAS_FILL_RECT in platform.h"
#endif
#if !defined(JL_HAS_TILE_FILL)
  #error "IIgs jlpTileFill macro without JL_HAS_TILE_FILL in platform.h"
#endif
#if !defined(JL_HAS_TILE_COPY)
  #error "IIgs jlpTileCopy macro without JL_HAS_TILE_COPY in platform.h"
#endif
#if !defined(JL_HAS_TILE_COPY_MASKED)
  #error "IIgs jlpTileCopyMasked macro without JL_HAS_TILE_COPY_MASKED in platform.h"
#endif
#if !defined(JL_HAS_TILE_PASTE)
  #error "IIgs jlpTilePaste macro without JL_HAS_TILE_PASTE in platform.h"
#endif
#if !defined(JL_HAS_TILE_PASTE_MONO)
  #error "IIgs jlpTilePasteMono macro without JL_HAS_TILE_PASTE_MONO in platform.h"
#endif
#if !defined(JL_HAS_TILE_SNAP)
  #error "IIgs jlpTileSnap macro without JL_HAS_TILE_SNAP in platform.h"
#endif
#if !defined(JL_HAS_SPRITE_DRAW)
  #error "IIgs jlpSpriteDrawPlanes elision without JL_HAS_SPRITE_DRAW in platform.h"
#endif
#if !defined(JL_HAS_SPRITE_SAVE)
  #error "IIgs jlpSpriteSavePlanes elision without JL_HAS_SPRITE_SAVE in platform.h"
#endif
#if !defined(JL_HAS_SPRITE_RESTORE)
  #error "IIgs jlpSpriteRestorePlanes elision without JL_HAS_SPRITE_RESTORE in platform.h"
#endif
#if !defined(JL_HAS_FLOOD_WALK_AND_SCANS)
  #error "IIgs jlpFloodWalkAndScans macro without JL_HAS_FLOOD_WALK_AND_SCANS in platform.h"
#endif

#endif /* JOEYLIB_PLATFORM_IIGS */

// =====================================================================
// 68k tile ops as always-inline headers (NATIVE-PERF Phase 2 R4a).
//
// The ST/Amiga tile overrides live in stTile.h / amigaTile.h as
// static inline functions named stTile* / amigaTile*. Aliasing the
// jlpTile* dispatch names to them HERE (before the dispatch tail)
// makes the tail's #if !defined(jlpTile*) guards skip their extern
// prototypes -- same mechanism as the IIgs macro block above -- and
// every caller (tile.c wrappers, genericTile.c fallback routing)
// inlines the op instead of paying a second cdecl call. The headers
// themselves are #included at the END of this file so their
// portData == NULL fallbacks can see the jlpGenericTile* prototypes
// declared in the dispatch tail.
// =====================================================================
#if defined(JOEYLIB_PLATFORM_ATARIST)
  #define jlpTileFill        stTileFill
  #define jlpTileCopy        stTileCopy
  #define jlpTileCopyMasked  stTileCopyMasked
  #define jlpTilePaste       stTilePaste
  #define jlpTilePasteMono   stTilePasteMono
  #define jlpTileSnap        stTileSnap
  // Whole-map engine alias (W3 seam): tile.c's jlTileMapPaste
  // dispatches through this spelling when a port defines it, so the
  // union-mark stanza lives exactly twice in tile.c (shared arm +
  // generic) no matter how many ports bring engines.
  #define jlpTileMapPaste    stTileMapPaste
#elif defined(JOEYLIB_PLATFORM_AMIGA)
  #define jlpTileFill        amigaTileFill
  #define jlpTileCopy        amigaTileCopy
  #define jlpTileCopyMasked  amigaTileCopyMasked
  #define jlpTilePaste       amigaTilePaste
  #define jlpTilePasteMono   amigaTilePasteMono
  #define jlpTileSnap        amigaTileSnap
  #define jlpTileMapPaste    amigaTileMapPaste
#elif defined(JOEYLIB_PLATFORM_X68000)
  #define jlpTileFill        x68kTileFill
  #define jlpTileCopy        x68kTileCopy
  #define jlpTileCopyMasked  x68kTileCopyMasked
  #define jlpTilePaste       x68kTilePaste
  #define jlpTilePasteMono   x68kTilePasteMono
  #define jlpTileSnap        x68kTileSnap
  #define jlpTileMapPaste    x68kTileMapPaste
#elif defined(JOEYLIB_PLATFORM_DOS)
  // DOS overrides only the whole-map walker (dosTile.h, W3); the
  // single-tile ops stay on the chunky generics.
  #define jlpTileMapPaste    dosTileMapPaste
#endif

// =====================================================================
// Chunky planar-hook elision for the other chunky ports (PERF-AUDIT #42).
//
// DOS and the blank template are chunky-native like the IIgs: the planar
// dual-write hooks have no bitplanes to mirror into. Elide them to
// ((void)0) exactly like the IIgs block above instead of paying a real
// call into the empty generic bodies (genericSprite.c and
// jlpGenericSurfaceCopyPlanes) on every sprite draw/save/restore and
// surface copy. Those generic no-op bodies stay in the build only as the
// link-time safety net for a new planar port that has not implemented
// its hooks yet.
// =====================================================================

#if defined(JOEYLIB_NATIVE_CHUNKY) && !defined(JOEYLIB_PLATFORM_IIGS)

#define jlpSurfaceCopyPlanes(_dst, _src) ((void)0)
#define jlpSpriteDrawPlanes(_s, _sp, _x, _y) ((void)0)
#define jlpSpriteSavePlanes(_s, _x, _y, _w, _h, _dst) ((void)0)
#define jlpSpriteRestorePlanes(_s, _x, _y, _w, _h, _src) ((void)0)

#endif /* JOEYLIB_NATIVE_CHUNKY && !JOEYLIB_PLATFORM_IIGS */

// =====================================================================
// Migrated-op dispatch (generic default unless a machine overrides).
//
// jlpGenericSurfaceClear is the portable-C reference (src/generic/genericDraw.c),
// always available. Machine overrides are selected at compile time via
// JL_HAS_<OP> (include/joey/platform.h): a macro override (IIgs, above) already
// #define-d jlp<Op>; a function override (Amiga/ST) just declares its prototype
// here; otherwise jlp<Op> aliases the generic default. No runtime dispatch.
// =====================================================================

void jlpGenericSurfaceClear(jlSurfaceT *s, uint8_t doubled);
#if !defined(jlpSurfaceClear)
  #if defined(JL_HAS_SURFACE_CLEAR)
    void jlpSurfaceClear(jlSurfaceT *s, uint8_t doubled);   /* function override */
  #else
    #define jlpSurfaceClear(_s, _d) jlpGenericSurfaceClear((_s), (_d))
  #endif
#endif

void jlpGenericDrawPixel(jlSurfaceT *s, uint16_t x, uint16_t y, uint8_t colorIndex);
#if !defined(jlpDrawPixel)
  #if defined(JL_HAS_DRAW_PIXEL)
    void jlpDrawPixel(jlSurfaceT *s, uint16_t x, uint16_t y, uint8_t colorIndex);  /* function override */
  #else
    #define jlpDrawPixel(_s, _x, _y, _c) jlpGenericDrawPixel((_s), (_x), (_y), (_c))
  #endif
#endif

// drawLine / drawCircle have no generic default: the portable fallback is the
// clipping Bresenham in core/draw.c, and the on-surface fast path is gated by
// `#if defined(JL_HAS_DRAW_LINE/_CIRCLE)`. So here we only declare the function
// override prototype for non-macro overriders (Amiga/ST).
#if defined(JL_HAS_DRAW_LINE) && !defined(jlpDrawLine)
void jlpDrawLine(jlSurfaceT *s, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t colorIndex);
#endif
#if defined(JL_HAS_DRAW_CIRCLE) && !defined(jlpDrawCircle)
void jlpDrawCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex);
#endif
// fillCircle returns bool (stage/portData check is inherent); the platform
// dispatch is compile-time via JL_HAS_FILL_CIRCLE.
#if defined(JL_HAS_FILL_CIRCLE) && !defined(jlpFillCircle)
bool jlpFillCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex);
#endif

void jlpGenericFillRect(jlSurfaceT *s, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t colorIndex);
#if !defined(jlpFillRect)
  #if defined(JL_HAS_FILL_RECT)
    void jlpFillRect(jlSurfaceT *s, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t colorIndex);  /* function override (planar) */
  #else
    #define jlpFillRect(_s, _x, _y, _w, _h, _c) jlpGenericFillRect((_s), (_x), (_y), (_w), (_h), (_c))
  #endif
#endif

// jlpFillRectNoMark: fillRect guaranteed not to touch dirty state. On
// every port but the IIgs the ops never mark, so it IS jlpFillRect;
// the IIgs macro block above overrides it with the unfused asm twin.
#if !defined(jlpFillRectNoMark)
  #define jlpFillRectNoMark(_s, _x, _y, _w, _h, _c) jlpFillRect((_s), (_x), (_y), (_w), (_h), (_c))
#endif

void jlpGenericTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex);
#if !defined(jlpTileFill)
  #if defined(JL_HAS_TILE_FILL)
    void jlpTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex);  /* function override (planar) */
  #else
    #define jlpTileFill(_s, _bx, _by, _c) jlpGenericTileFill((_s), (_bx), (_by), (_c))
  #endif
#endif

void jlpGenericTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy);
#if !defined(jlpTileCopy)
  #if defined(JL_HAS_TILE_COPY)
    void jlpTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy);
  #else
    #define jlpTileCopy(_d, _dbx, _dby, _s, _sbx, _sby) jlpGenericTileCopy((_d), (_dbx), (_dby), (_s), (_sbx), (_sby))
  #endif
#endif

void jlpGenericTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparent);
#if !defined(jlpTileCopyMasked)
  #if defined(JL_HAS_TILE_COPY_MASKED)
    void jlpTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparent);
  #else
    #define jlpTileCopyMasked(_d, _dbx, _dby, _s, _sbx, _sby, _t) jlpGenericTileCopyMasked((_d), (_dbx), (_dby), (_s), (_sbx), (_sby), (_t))
  #endif
#endif

void jlpGenericTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *chunkyTile);
#if !defined(jlpTilePaste)
  #if defined(JL_HAS_TILE_PASTE)
    void jlpTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *chunkyTile);
  #else
    #define jlpTilePaste(_d, _bx, _by, _ct) jlpGenericTilePaste((_d), (_bx), (_by), (_ct))
  #endif
#endif

void jlpGenericTileSnap(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *chunkyOut);
#if !defined(jlpTileSnap)
  #if defined(JL_HAS_TILE_SNAP)
    void jlpTileSnap(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *chunkyOut);
  #else
    #define jlpTileSnap(_s, _bx, _by, _out) jlpGenericTileSnap((_s), (_bx), (_by), (_out))
  #endif
#endif

// tilePasteMono color-masking contract (PERF-AUDIT #69): fgColor/bgColor
// arrive already masked to 4 bits -- the public jlTilePasteMono wrapper owns
// the & 0x0F. Neither the generic nor a machine override may re-mask.
// (tileFill is the opposite: its wrapper does NOT mask, so the jlp layer owns
// the & 0x0F there -- see the IIgs macro above and the planar overrides.)
void jlpGenericTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *monoTile, uint8_t fgColor, uint8_t bgColor);
#if !defined(jlpTilePasteMono)
  #if defined(JL_HAS_TILE_PASTE_MONO)
    void jlpTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *monoTile, uint8_t fgColor, uint8_t bgColor);
  #else
    #define jlpTilePasteMono(_d, _bx, _by, _mt, _fg, _bg) jlpGenericTilePasteMono((_d), (_bx), (_by), (_mt), (_fg), (_bg))
  #endif
#endif

void jlpGenericSpriteDrawPlanes(jlSurfaceT *s, const jlSpriteT *sp, int16_t x, int16_t y);
#if !defined(jlpSpriteDrawPlanes)
  #if defined(JL_HAS_SPRITE_DRAW)
    void jlpSpriteDrawPlanes(jlSurfaceT *s, const jlSpriteT *sp, int16_t x, int16_t y);
  #else
    #define jlpSpriteDrawPlanes(_s, _sp, _x, _y) jlpGenericSpriteDrawPlanes((_s), (_sp), (_x), (_y))
  #endif
#endif

void jlpGenericSpriteSavePlanes(const jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t *dstPlaneBytes);
#if !defined(jlpSpriteSavePlanes)
  #if defined(JL_HAS_SPRITE_SAVE)
    void jlpSpriteSavePlanes(const jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t *dstPlaneBytes);
  #else
    #define jlpSpriteSavePlanes(_s, _x, _y, _w, _h, _dst) jlpGenericSpriteSavePlanes((_s), (_x), (_y), (_w), (_h), (_dst))
  #endif
#endif

void jlpGenericSpriteRestorePlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, const uint8_t *srcPlaneBytes);
#if !defined(jlpSpriteRestorePlanes)
  #if defined(JL_HAS_SPRITE_RESTORE)
    void jlpSpriteRestorePlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, const uint8_t *srcPlaneBytes);
  #else
    #define jlpSpriteRestorePlanes(_s, _x, _y, _w, _h, _src) jlpGenericSpriteRestorePlanes((_s), (_x), (_y), (_w), (_h), (_src))
  #endif
#endif

// Flood ops (PATTERN B: no extractable generic -- the portable default is the
// inline C scanline fallback in floodFillInternal, gated per-tier by
// `#if defined(JL_HAS_FLOOD_*)` at the call site). Each real override is just a
// prototype here for the claiming machine. jlpFloodWalkAndScans is a macro on
// IIgs (defined above), so its `!defined` guard skips the proto there.
#if defined(JL_HAS_FLOOD_WALK_AND_SCANS) && !defined(jlpFloodWalkAndScans)
bool jlpFloodWalkAndScans(uint8_t *pixels, int16_t x, int16_t y, uint8_t matchColor, uint8_t newColor, bool matchEqual, int16_t *stackX, int16_t *stackY, int16_t *spInOut, int16_t maxSp, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut);
#endif
#if defined(JL_HAS_FLOOD_WALK_PLANES) && !defined(jlpFloodWalkPlanes)
bool jlpFloodWalkPlanes(const jlSurfaceT *s, int16_t startX, int16_t y, uint8_t matchColor, uint8_t newColor, bool matchEqual, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut);
#endif
#if defined(JL_HAS_FLOOD_SCAN_ROW_PLANES) && !defined(jlpFloodScanRowPlanes)
bool jlpFloodScanRowPlanes(const jlSurfaceT *s, int16_t leftX, int16_t rightX, int16_t scanY, uint8_t matchColor, uint8_t newColor, bool matchEqual, uint8_t *markBuf);
#endif

// =====================================================================
// Category B: structural port services (generic default + JL_HAS override).
//
// Unlike Category A accelerators these are resolved purely at link/compile
// time (no runtime dispatch ever existed). Each op has a portable-C default in
// src/generic/ -- a real implementation where one exists, or a no-op/zero stub
// for inherently platform-specific services so a blank/template port still
// links. A port that supplies its own version defines JL_HAS_<OP> in
// include/joey/platform.h. Same jlp<Op> stanza as Category A.
// =====================================================================

// --- Generic-only surface services (no port overrides them today) ---
void jlpGenericSurfaceCopyChunky(jlSurfaceT *dst, const jlSurfaceT *src);
#if !defined(jlpSurfaceCopyChunky)
  #if defined(JL_HAS_SURFACE_COPY_CHUNKY)
    void jlpSurfaceCopyChunky(jlSurfaceT *dst, const jlSurfaceT *src);
  #else
    #define jlpSurfaceCopyChunky(_d, _s) jlpGenericSurfaceCopyChunky((_d), (_s))
  #endif
#endif

// Same-position group-snapped rect copy (jlSurfaceCopyRect payload).
// The generic body serves the chunky ports (incl. the IIgs stage --
// its pixels pointer reaches bank $01 through ordinary large-model
// stores); the planar ports override with their plane-layout walks.
void jlpGenericSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, uint16_t x, uint16_t y, uint16_t w, uint16_t h);
#if !defined(jlpSurfaceCopyRect)
  #if defined(JL_HAS_SURFACE_COPY_RECT)
    void jlpSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, uint16_t x, uint16_t y, uint16_t w, uint16_t h);
  #else
    #define jlpSurfaceCopyRect(_d, _s, _x, _y, _w, _h) jlpGenericSurfaceCopyRect((_d), (_s), (_x), (_y), (_w), (_h))
  #endif
#endif

void jlpGenericSurfaceFreePixels(uint8_t *pixels);
#if !defined(jlpSurfaceFreePixels)
  #if defined(JL_HAS_SURFACE_FREE_PIXELS)
    void jlpSurfaceFreePixels(uint8_t *pixels);
  #else
    #define jlpSurfaceFreePixels(_p) jlpGenericSurfaceFreePixels((_p))
  #endif
#endif

const char *jlpGenericLastError(void);
#if !defined(jlpLastError)
  #if defined(JL_HAS_LAST_ERROR)
    const char *jlpLastError(void);
  #else
    #define jlpLastError() jlpGenericLastError()
  #endif
#endif

// --- Planar dual-write hook (no-op generic default; planar ports override) ---
void jlpGenericSurfaceCopyPlanes(jlSurfaceT *dst, const jlSurfaceT *src);
#if !defined(jlpSurfaceCopyPlanes)
  #if defined(JL_HAS_SURFACE_COPY_PLANES)
    void jlpSurfaceCopyPlanes(jlSurfaceT *dst, const jlSurfaceT *src);
  #else
    #define jlpSurfaceCopyPlanes(_d, _s) jlpGenericSurfaceCopyPlanes((_d), (_s))
  #endif
#endif

// --- Surface readers (chunky generic default; planar ports decode portData) ---
uint8_t jlpGenericSamplePixel(const jlSurfaceT *s, int16_t x, int16_t y);
#if !defined(jlpSamplePixel)
  #if defined(JL_HAS_SAMPLE_PIXEL)
    uint8_t jlpSamplePixel(const jlSurfaceT *s, int16_t x, int16_t y);
  #else
    #define jlpSamplePixel(_s, _x, _y) jlpGenericSamplePixel((_s), (_x), (_y))
  #endif
#endif

uint32_t jlpGenericSurfaceHash(const jlSurfaceT *s);
#if !defined(jlpSurfaceHash)
  #if defined(JL_HAS_SURFACE_HASH)
    uint32_t jlpSurfaceHash(const jlSurfaceT *s);
  #else
    #define jlpSurfaceHash(_s) jlpGenericSurfaceHash((_s))
  #endif
#endif

bool jlpGenericSurfaceLoadFile(jlSurfaceT *dst, FILE *fp);
#if !defined(jlpSurfaceLoadFile)
  #if defined(JL_HAS_SURFACE_LOAD_FILE)
    bool jlpSurfaceLoadFile(jlSurfaceT *dst, FILE *fp);
  #else
    #define jlpSurfaceLoadFile(_d, _fp) jlpGenericSurfaceLoadFile((_d), (_fp))
  #endif
#endif

bool jlpGenericSurfaceSaveFile(const jlSurfaceT *src, FILE *fp);
#if !defined(jlpSurfaceSaveFile)
  #if defined(JL_HAS_SURFACE_SAVE_FILE)
    bool jlpSurfaceSaveFile(const jlSurfaceT *src, FILE *fp);
  #else
    #define jlpSurfaceSaveFile(_s, _fp) jlpGenericSurfaceSaveFile((_s), (_fp))
  #endif
#endif

// --- Allocation ops (chunky generic defaults; planar/pinned overrides) ---
uint8_t *jlpGenericStageAllocPixels(void);
#if !defined(jlpStageAllocPixels)
  #if defined(JL_HAS_STAGE_ALLOC_PIXELS)
    uint8_t *jlpStageAllocPixels(void);
  #else
    #define jlpStageAllocPixels() jlpGenericStageAllocPixels()
  #endif
#endif

void jlpGenericStageFreePixels(uint8_t *pixels);
#if !defined(jlpStageFreePixels)
  #if defined(JL_HAS_STAGE_FREE_PIXELS)
    void jlpStageFreePixels(uint8_t *pixels);
  #else
    #define jlpStageFreePixels(_p) jlpGenericStageFreePixels((_p))
  #endif
#endif

uint8_t *jlpGenericSurfaceAllocPixels(void);
#if !defined(jlpSurfaceAllocPixels)
  #if defined(JL_HAS_SURFACE_ALLOC_PIXELS)
    uint8_t *jlpSurfaceAllocPixels(void);
  #else
    #define jlpSurfaceAllocPixels() jlpGenericSurfaceAllocPixels()
  #endif
#endif

void *jlpGenericSurfaceAllocPortData(jlSurfaceT *s, bool isStage);
#if !defined(jlpSurfaceAllocPortData)
  #if defined(JL_HAS_SURFACE_ALLOC_PORT_DATA)
    void *jlpSurfaceAllocPortData(jlSurfaceT *s, bool isStage);
  #else
    #define jlpSurfaceAllocPortData(_s, _st) jlpGenericSurfaceAllocPortData((_s), (_st))
  #endif
#endif

void jlpGenericSurfaceFreePortData(jlSurfaceT *s, bool isStage, void *portData);
#if !defined(jlpSurfaceFreePortData)
  #if defined(JL_HAS_SURFACE_FREE_PORT_DATA)
    void jlpSurfaceFreePortData(jlSurfaceT *s, bool isStage, void *portData);
  #else
    #define jlpSurfaceFreePortData(_s, _st, _pd) jlpGenericSurfaceFreePortData((_s), (_st), (_pd))
  #endif
#endif

// (jlpSurfacePlanePtr was retired in NATIVE-PERF Phase 2: its only
// callers were spriteCompile.c's 68k dispatchers, which now read
// portData directly via the inline accessors in amigaPlanar.h /
// stPlanar.h instead of paying a cross-TU call per plane.)

// --- Timing services (platform-only stub generics; millisElapsed has a real default) ---
uint16_t jlpGenericFrameHz(void);
#if !defined(jlpFrameHz)
  #if defined(JL_HAS_FRAME_HZ)
    uint16_t jlpFrameHz(void);
  #else
    #define jlpFrameHz() jlpGenericFrameHz()
  #endif
#endif

uint16_t jlpGenericFrameCount(void);
#if !defined(jlpFrameCount)
  #if defined(JL_HAS_FRAME_COUNT)
    uint16_t jlpFrameCount(void);
  #else
    #define jlpFrameCount() jlpGenericFrameCount()
  #endif
#endif

void jlpGenericWaitVBL(void);
#if !defined(jlpWaitVBL)
  #if defined(JL_HAS_WAIT_VBL)
    void jlpWaitVBL(void);
  #else
    #define jlpWaitVBL() jlpGenericWaitVBL()
  #endif
#endif

uint32_t jlpGenericMillisElapsed(void);
#if !defined(jlpMillisElapsed)
  #if defined(JL_HAS_MILLIS_ELAPSED)
    uint32_t jlpMillisElapsed(void);
  #else
    #define jlpMillisElapsed() jlpGenericMillisElapsed()
  #endif
#endif


// --- Audio services (platform-only; blank-port stub generic + JL_HAS override) ---
bool jlpGenericAudioInit(void);
#if !defined(jlpAudioInit)
  #if defined(JL_HAS_AUDIO_INIT)
    bool jlpAudioInit(void);
  #else
    #define jlpAudioInit() jlpGenericAudioInit()
  #endif
#endif
void jlpGenericAudioShutdown(void);
#if !defined(jlpAudioShutdown)
  #if defined(JL_HAS_AUDIO_SHUTDOWN)
    void jlpAudioShutdown(void);
  #else
    #define jlpAudioShutdown() jlpGenericAudioShutdown()
  #endif
#endif
void jlpGenericAudioPlayMod(const uint8_t *data, uint32_t length, bool loop);
#if !defined(jlpAudioPlayMod)
  #if defined(JL_HAS_AUDIO_PLAY_MOD)
    void jlpAudioPlayMod(const uint8_t *data, uint32_t length, bool loop);
  #else
    #define jlpAudioPlayMod(_a, _b, _c) jlpGenericAudioPlayMod((_a), (_b), (_c))
  #endif
#endif
void jlpGenericAudioStopMod(void);
#if !defined(jlpAudioStopMod)
  #if defined(JL_HAS_AUDIO_STOP_MOD)
    void jlpAudioStopMod(void);
  #else
    #define jlpAudioStopMod() jlpGenericAudioStopMod()
  #endif
#endif
bool jlpGenericAudioIsPlayingMod(void);
#if !defined(jlpAudioIsPlayingMod)
  #if defined(JL_HAS_AUDIO_IS_PLAYING_MOD)
    bool jlpAudioIsPlayingMod(void);
  #else
    #define jlpAudioIsPlayingMod() jlpGenericAudioIsPlayingMod()
  #endif
#endif
void jlpGenericAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16_t rateHz);
#if !defined(jlpAudioPlaySfx)
  #if defined(JL_HAS_AUDIO_PLAY_SFX)
    void jlpAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16_t rateHz);
  #else
    #define jlpAudioPlaySfx(_a, _b, _c, _d) jlpGenericAudioPlaySfx((_a), (_b), (_c), (_d))
  #endif
#endif
void jlpGenericAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz);
#if !defined(jlpAudioPlaySfxStream)
  #if defined(JL_HAS_AUDIO_PLAY_SFX_STREAM)
    void jlpAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz);
  #else
    #define jlpAudioPlaySfxStream(_a, _b, _c, _d) jlpGenericAudioPlaySfxStream((_a), (_b), (_c), (_d))
  #endif
#endif
void jlpGenericAudioStopSfx(uint8_t slot);
#if !defined(jlpAudioStopSfx)
  #if defined(JL_HAS_AUDIO_STOP_SFX)
    void jlpAudioStopSfx(uint8_t slot);
  #else
    #define jlpAudioStopSfx(_a) jlpGenericAudioStopSfx((_a))
  #endif
#endif
void jlpGenericAudioTone(uint16_t freqHz);
#if !defined(jlpAudioTone)
  #if defined(JL_HAS_AUDIO_TONE)
    void jlpAudioTone(uint16_t freqHz);
  #else
    #define jlpAudioTone(_a) jlpGenericAudioTone((_a))
  #endif
#endif
void jlpGenericAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten);
#if !defined(jlpAudioVoice)
  #if defined(JL_HAS_AUDIO_VOICE)
    void jlpAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten);
  #else
    #define jlpAudioVoice(_a, _b, _c) jlpGenericAudioVoice((_a), (_b), (_c))
  #endif
#endif
void jlpGenericAudioNoise(uint8_t pitch, uint8_t atten);
#if !defined(jlpAudioNoise)
  #if defined(JL_HAS_AUDIO_NOISE)
    void jlpAudioNoise(uint8_t pitch, uint8_t atten);
  #else
    #define jlpAudioNoise(_a, _b) jlpGenericAudioNoise((_a), (_b))
  #endif
#endif
bool jlpGenericAudioTickRegister(jlAudioTickFnT fn, uint16_t hz);
#if !defined(jlpAudioTickRegister)
  #if defined(JL_HAS_AUDIO_TICK_REGISTER)
    bool jlpAudioTickRegister(jlAudioTickFnT fn, uint16_t hz);
  #else
    #define jlpAudioTickRegister(_a, _b) jlpGenericAudioTickRegister((_a), (_b))
  #endif
#endif
void jlpGenericAudioCriticalEnter(void);
#if !defined(jlpAudioCriticalEnter)
  #if defined(JL_HAS_AUDIO_CRITICAL_ENTER)
    void jlpAudioCriticalEnter(void);
  #else
    #define jlpAudioCriticalEnter() jlpGenericAudioCriticalEnter()
  #endif
#endif
void jlpGenericAudioCriticalExit(void);
#if !defined(jlpAudioCriticalExit)
  #if defined(JL_HAS_AUDIO_CRITICAL_EXIT)
    void jlpAudioCriticalExit(void);
  #else
    #define jlpAudioCriticalExit() jlpGenericAudioCriticalExit()
  #endif
#endif
void jlpGenericAudioFrameTick(void);
#if !defined(jlpAudioFrameTick)
  #if defined(JL_HAS_AUDIO_FRAME_TICK)
    void jlpAudioFrameTick(void);
  #else
    #define jlpAudioFrameTick() jlpGenericAudioFrameTick()
  #endif
#endif


// --- Lifecycle / present / input (platform-only; blank stub generic + JL_HAS) ---
bool jlpGenericInit(const jlConfigT *config);
#if !defined(jlpInit)
  #if defined(JL_HAS_INIT)
    bool jlpInit(const jlConfigT *config);
  #else
    #define jlpInit(_a) jlpGenericInit((_a))
  #endif
#endif
void jlpGenericShutdown(void);
#if !defined(jlpShutdown)
  #if defined(JL_HAS_SHUTDOWN)
    void jlpShutdown(void);
  #else
    #define jlpShutdown() jlpGenericShutdown()
  #endif
#endif
void jlpGenericPresent(const jlSurfaceT *src);
#if !defined(jlpPresent)
  #if defined(JL_HAS_PRESENT)
    void jlpPresent(const jlSurfaceT *src);
  #else
    #define jlpPresent(_a) jlpGenericPresent((_a))
  #endif
#endif
void jlpGenericInputInit(void);
#if !defined(jlpInputInit)
  #if defined(JL_HAS_INPUT_INIT)
    void jlpInputInit(void);
  #else
    #define jlpInputInit() jlpGenericInputInit()
  #endif
#endif
void jlpGenericInputShutdown(void);
#if !defined(jlpInputShutdown)
  #if defined(JL_HAS_INPUT_SHUTDOWN)
    void jlpInputShutdown(void);
  #else
    #define jlpInputShutdown() jlpGenericInputShutdown()
  #endif
#endif
void jlpGenericInputPoll(void);
#if !defined(jlpInputPoll)
  #if defined(JL_HAS_INPUT_POLL)
    void jlpInputPoll(void);
  #else
    #define jlpInputPoll() jlpGenericInputPoll()
  #endif
#endif
void jlpGenericJoystickReset(jlJoystickE js);
#if !defined(jlpJoystickReset)
  #if defined(JL_HAS_JOYSTICK_RESET)
    void jlpJoystickReset(jlJoystickE js);
  #else
    #define jlpJoystickReset(_a) jlpGenericJoystickReset((_a))
  #endif
#endif


// --- Serial add-on (platform-only; blank-port stub generic + JL_HAS override) ---
// Function overrides (not IIgs asm macros), so no entry in the macro-consistency
// #error block above. Types come from joey/serial.h (included at the top).
bool jlpGenericSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config);
#if !defined(jlpSerialOpen)
  #if defined(JL_HAS_SERIAL_OPEN)
    bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config);
  #else
    #define jlpSerialOpen(_d, _c) jlpGenericSerialOpen((_d), (_c))
  #endif
#endif
void jlpGenericSerialClose(void);
#if !defined(jlpSerialClose)
  #if defined(JL_HAS_SERIAL_CLOSE)
    void jlpSerialClose(void);
  #else
    #define jlpSerialClose() jlpGenericSerialClose()
  #endif
#endif
void jlpGenericSerialPoll(void);
#if !defined(jlpSerialPoll)
  #if defined(JL_HAS_SERIAL_POLL)
    void jlpSerialPoll(void);
  #else
    #define jlpSerialPoll() jlpGenericSerialPoll()
  #endif
#endif
uint16_t jlpGenericSerialAvailable(void);
#if !defined(jlpSerialAvailable)
  #if defined(JL_HAS_SERIAL_AVAILABLE)
    uint16_t jlpSerialAvailable(void);
  #else
    #define jlpSerialAvailable() jlpGenericSerialAvailable()
  #endif
#endif
uint16_t jlpGenericSerialRead(uint8_t *buf, uint16_t max);
#if !defined(jlpSerialRead)
  #if defined(JL_HAS_SERIAL_READ)
    uint16_t jlpSerialRead(uint8_t *buf, uint16_t max);
  #else
    #define jlpSerialRead(_b, _m) jlpGenericSerialRead((_b), (_m))
  #endif
#endif
uint16_t jlpGenericSerialWrite(const uint8_t *buf, uint16_t len);
#if !defined(jlpSerialWrite)
  #if defined(JL_HAS_SERIAL_WRITE)
    uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len);
  #else
    #define jlpSerialWrite(_b, _l) jlpGenericSerialWrite((_b), (_l))
  #endif
#endif
void jlpGenericSerialFlush(void);
#if !defined(jlpSerialFlush)
  #if defined(JL_HAS_SERIAL_FLUSH)
    void jlpSerialFlush(void);
  #else
    #define jlpSerialFlush() jlpGenericSerialFlush()
  #endif
#endif


// --- Save files + disk space (platform-only; blank-port stub generic + JL_HAS override) ---
// Function overrides (not IIgs asm macros), so no entry in the macro-consistency
// #error block above. Core layer is src/core/save.c; publics in joey/file.h.
bool jlpGenericSaveDirEnsure(const char *dir);
#if !defined(jlpSaveDirEnsure)
  #if defined(JL_HAS_SAVE_DIR_ENSURE)
    bool jlpSaveDirEnsure(const char *dir);
  #else
    #define jlpSaveDirEnsure(_d) jlpGenericSaveDirEnsure((_d))
  #endif
#endif
bool jlpGenericSaveDelete(const char *path);
#if !defined(jlpSaveDelete)
  #if defined(JL_HAS_SAVE_DELETE)
    bool jlpSaveDelete(const char *path);
  #else
    #define jlpSaveDelete(_p) jlpGenericSaveDelete((_p))
  #endif
#endif
uint32_t jlpGenericDiskFree(void);
#if !defined(jlpDiskFree)
  #if defined(JL_HAS_DISK_FREE)
    uint32_t jlpDiskFree(void);
  #else
    #define jlpDiskFree() jlpGenericDiskFree()
  #endif
#endif


// --- Large allocation (malloc/free generic default; IIgs Memory Manager override) ---
void *jlpGenericBigAlloc(uint32_t bytes);
void jlpGenericBigFree(void *p);
#if !defined(jlpBigAlloc)
  #if defined(JL_HAS_BIG_ALLOC)
    void *jlpBigAlloc(uint32_t bytes);
    void jlpBigFree(void *p);
  #else
    #define jlpBigAlloc(_b) jlpGenericBigAlloc((_b))
    #define jlpBigFree(_p)  jlpGenericBigFree((_p))
  #endif
#endif

// Per-port tile-op inline bodies (see the alias block above the
// dispatch tail). The 68k headers are included LAST so their
// portData == NULL fallbacks can call the jlpGenericTile* prototypes
// declared above; the DOS header (whole-map engine only, W3) needs
// nothing from the tail but sits here for the same one-include-point.
#if defined(JOEYLIB_PLATFORM_ATARIST)
  #include "stTile.h"
#elif defined(JOEYLIB_PLATFORM_AMIGA)
  #include "amigaTile.h"
#elif defined(JOEYLIB_PLATFORM_DOS)
  #include "dosTile.h"
#elif defined(JOEYLIB_PLATFORM_X68000)
  #include "x68kTile.h"
#endif
#endif
