raylib examples in Jolt

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.

A few of them

games 10 · core 9 · shapes 32 · text 5 · 3d 12 · generative 7 — every one of them, full size.

Why this is interesting

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:

How it fits together

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.

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?
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.