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