75 raylib examples written in Jolt —
native Clojure on Chez Scheme, no JVM. They call the real
libraylib directly over its C ABI through
jolt.ffi: no wrapper library, no codegen, no C shim.
games 10 · core 9 · shapes 32 · text 5 · 3d 12 · generative 7 — every one of them, full size.
raylib passes structs by value everywhere, and Chez's
foreign-procedure cannot. Nothing here works around that
with a C shim — instead each struct gets the treatment its size and
contents actually earn on the ABI. Three facts drive every distinctive
decision in the binding layer:
Color is 4 bytes of u8, and a 4-byte
all-integer struct travels in one general-purpose register — exactly
like a uint32. So every draw call passes color as an
int, and no struct marshaling happens at all.
A composite larger than 16 bytes is passed indirectly on
AArch64 — the caller allocates a copy and passes its address. So the
24-byte Camera2D and 44-byte Camera3D are
built in native memory and bound as [:pointer].
This one is AArch64-specific, and the guide says so
plainly rather than pretending it ports.
Small float structs go in floating-point registers, which
the pointer trick does not cover. So geometry that takes a
Vector2 or Vector3 by value is drawn with
rlgl's scalar immediate mode instead — rlVertex3f,
rlColor4ub, and the matrix stack for nested transforms.
flowchart LR
subgraph ex["75 example namespaces"]
e["pong · boids · tetris
camera-3d · penrose-tiling · …"]
end
subgraph shared["net.b12n.raylib-jlt.raylib — one shared layer"]
kw["keyword-argument drawing API
text! · rect! · circle! · cube!"]
fb["jolt.ffi/defcfn binds
positional, mirroring C"]
kw --> fb
end
lib["system libraylib
dlopened at runtime, C ABI"]
e --> kw
fb -->|"Color as a packed :uint"| lib
fb -->|"Camera2D/3D by pointer"| lib
fb -->|"rlgl scalar immediate mode"| lib
Every example is a small namespace over that one shared layer. Adding
one touches exactly four places — the source namespace, a
deps.edn alias, a check.clj require, and a
bb.edn registry row.
All the FFI binds, the keyword-argument drawing API, and the named color palette live in a single namespace. Examples stay small enough to read in one sitting.
(rl/circle! :x 400 :y 225 :radius 50 :color rl/MAROON) — positional binds at the boundary, keyword wrappers on top.
RAYLIB_APP_AUTO_QUIT_MS closes the window on a timer and RAYLIB_APP_SHOT dumps a frame to PNG — so a windowed example can prove itself unattended.
bb check requires every example namespace headlessly. No window, no JVM, and it catches a broken binding across the whole suite at once.
A hook rewrites each defcfn into an equivalent defn, so call sites get arity- and return-type-checked instead of producing ~500 false positives.
The two clock examples call plain libc time() / localtime(), reading struct tm straight out of native memory. Nothing about jolt.ffi is raylib-specific.
bb lib:check # is the native libraylib installed for this OS/arch?
bb lib:install # …install it via brew / pacman / apt / dnf / zypper / apk
bb info # grouped cheat-sheet of every example
bb tetris # run one (opens a window)
bb run-all 10 # demo reel: every example, 10s each
bb check # headless compile-check of all 75, no window
Needs jolt (tested against v0.7.16) and a system
libraylib.
babashka
is optional but gives every example a friendly task; without it each one
is a joltc -M:<alias> away.
Released under the zlib/libpng license — the same one raylib itself uses.