b12n-raylib-jlt — Guide

User-facing documentation for b12n-raylib-jlt: a suite of raylib examples written in jolt (native Clojure on Chez Scheme, no JVM) over jolt.ffi. Each page below covers one FFI pattern or drawing convention, with citations to the source files that implement it.

Why this exists

The examples show you what the suite draws; these pages explain why the binding layer is shaped the way it is. Almost every non-obvious decision in net.b12n.raylib-jlt.raylib traces back to one question — how a given C struct crosses the FFI boundary — and the answer differs per struct. Read these when you want to bind a C library from Jolt yourself, or when an example does something that looks needlessly indirect and you want the ABI reason behind it.

What b12n-raylib-jlt is

A community suite of 75 raylib examples — the classic core/shapes/text demos, a handful of games (asteroids, tetris, pong, vampire-survivors), and a 3D set (orbiting cameras, waving cubes, an rlgl solar system) — each a small Clojure namespace on top of one shared binding layer, net.b12n.raylib-jlt.raylib.

It is the graphics sibling of b12n-tsj (tree-sitter from Jolt, not yet public). Both bind a real external C library directly over its C ABI with jolt.ffi — Chez foreign-procedure under the hood. They differ on how hard the library leans on by-value structs, and that difference is the whole story of the FFI pages here:

raylib hits the mild version of struct-by-value — its hot path is Color, a 4-byte struct that reduces to a uint32, and its only large structs (Camera2D/Camera3D) are by-value arguments it can fake behind a pointer on AArch64. tree-sitter hits the severe version — a 32-byte TSNode passed AND returned by value — so it needs a full C shim. raylib needs none.

Three ABI facts drive every distinctive decision in this repo:

  1. Color packs into a :uint — a 4-byte all-integer struct travels in one general-purpose register, so every draw call passes color as an int, no struct marshaling. (color-by-value.md)
  2. Camera2D/Camera3D go by pointer — a >16-byte composite is passed indirectly on AArch64, so a [:pointer] binding + a hand-built native struct works (with an x86-64 caveat). (struct-by-value-pointer-trick.md)
  3. Vector2/Vector3 geometry uses rlgl — small float structs go in FP registers, which the pointer trick does not cover, so shapes and 3D cubes are drawn with rlgl's scalar immediate mode instead. (rlgl-immediate-mode.md)

Nothing about jolt.ffi is raylib-specific: it binds any C ABI symbol. The analog-clock / digital-clock examples call plain libc time()/localtime() (via rl/local-time) for real wall-clock time — the repo's one non-raylib FFI, reading struct tm's tm_hour/tm_min/tm_sec ints straight out of native memory.

Capability pages

The FFI core (the reason this repo is interesting)

  • color-by-value.md — why raylib's Color crosses the FFI boundary as a packed :uint and not a struct, the little-endian rgba packing, and the two-by-value-Colors-in-one-call case (DrawRectangleGradientV). Source: src/net/b12n/raylib_jlt/raylib.clj (rgba, clear-background, the palette).
  • struct-by-value-pointer-trick.md — how a 24-byte Camera2D / 44-byte Camera3D is passed by value on AArch64 by allocating the struct in native memory and binding BeginMode2D/BeginMode3D as [:pointer]. The x86-64 non-portability caveat is here. Source: raylib.clj (with-camera-2d, with-camera-3d).
  • rlgl-immediate-mode.md — the fallback for by-value Vector2/Vector3 args the pointer trick can't fake: rlgl scalar immediate mode (rlBegin/rlVertex2f/rlVertex3f/rlColor4ub) for the 2D triangle and the 3D cube!, plus the rlgl matrix stack for nested transforms (the solar-system demo). Source: raylib.clj (cube!, quad-3f, the rl-* binds).

The drawing API

  • kwarg-drawing-api.md — the two-layer design: raw positional ffi/defcfn binds at the boundary (mirroring C), ergonomic keyword-argument wrappers (text!/rect!/circle!/…) on top, and the ">3 arguments → keyword args" style convention. Source: raylib.clj.

Verifying without a display

  • headless-smoke-testing.md — how a windowed example proves itself with no person at the keyboard: RAYLIB_APP_AUTO_QUIT_MS (auto-close), RAYLIB_APP_SHOT (dump one PNG), and the batched-geometry flush that makes the screenshot non-empty. Source: raylib.clj (auto-quit-deadline, keep-running?, maybe-screenshot!).

Orientation

  • example-catalog.md — a tour of all 75 examples grouped games / core / shapes / text / 3d / generative, what each demonstrates, and the four-touchpoint recipe for adding one (source ns + deps.edn alias + check.clj require + bb.edn registry row). Read this for the map; the FFI pages for the mechanics.

See also

  • raylib — the upstream C library, and the source of most examples here. Its own examples/ tree is the reference these ports are named after.
  • jolt — the native Clojure implementation whose jolt.ffi does all the binding work described on these pages.
  • b12n-tsj (not yet public) — the Jolt sibling that binds a by-value-returning C API (tree-sitter) and therefore needs a full C shim this repo avoids. struct-by-value-pointer-trick.md is the delta between "fake it with a pointer" (raylib arguments) and "you can't, write a shim" (tree-sitter returns).