raylib-jlt Guide
User-facing documentation for 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 raylib-jlt is
A community suite of 119 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 auint32, 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-byteTSNodepassed AND returned by value, so it needs a full C shim. raylib needs none.
Four ABI facts drive every distinctive decision in this repo:
Colorpacks 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)Camera2D/Camera3Dgo 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)Vector2/Vector3geometry 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)- Structs by value, in both directions: jolt 0.7.23's
[:by-value [:struct ...]]passes and returns C structs directly, which is what makesLoadShaderand the tenshadersexamples possible. (structs-by-value.md)
Facts 2 and 3 predate it and describe what the suite still mostly does; before 0.7.23 a returned struct could not be expressed at all, and LoadTexture's 20-byte Texture2D coming back through AArch64's x8 indirect-result register is why textures and framebuffers are still reached through rlgl's scalar layer. (textures-via-rlgl.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)
- ✅
structs-by-value.md:[:by-value [:struct ...]]in argument and return position, the leading-destination-pointer return convention, why the descriptor must be a literal,ffi/layout/with-layout/read-field/write-field, and why a uniform-type enum is read from the header actually being linked. Source:raylib.clj(the shader bindings,set-uniform-texture!). - ✅
color-by-value.md: why raylib'sColorcrosses the FFI boundary as a packed:uintand not a struct, the little-endianrgbapacking, 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-byteCamera2D/ 44-byteCamera3Dis passed by value on AArch64 by allocating the struct in native memory and bindingBeginMode2D/BeginMode3Das[: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-valueVector2/Vector3args the pointer trick can't fake: rlgl scalar immediate mode (rlBegin/rlVertex2f/rlVertex3f/rlColor4ub) for the 2D triangle and the 3Dcube!, plus the rlgl matrix stack for nested transforms (the solar-system demo). Source:raylib.clj(cube!,quad-3f, therl-*binds). - ✅
textures-via-rlgl.md: why every raylibLoad*function is unbindable (a >16-byte struct returned throughx8), and howrlLoadTexture/rlLoadFramebufferreach the same GPU objects with nothing but ints. Includes the HiDPI restore thatEndTextureModedoes across two functions and which is easy to get subtly wrong. Source:raylib.clj(texture-from-fn,texture!,render-texture,with-render-texture).
The drawing API
- ✅
kwarg-drawing-api.md: the two-layer design: raw positionalffi/defcfnbinds 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.
Working from an editor
- ✅
repl-driven-development.md: why evaluating(-main)over nREPL kills the whole process on macOS (AppKit only initializes on the main thread, an nREPL eval does not run there), therl/run!call that fixes it, and what does and does not work against a running window. Source:raylib.clj(run!).
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 119 examples grouped games / core / shapes / text / 3d / generative / textures, what each demonstrates, and the five-touchpoint recipe for adding one (source ns +deps.ednalias +check.cljrequire +examples_registry.cljrow +bb.edntask). 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.ffidoes 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.mdis the delta between "fake it with a pointer" (raylib arguments) and "you can't, write a shim" (tree-sitter returns).