Spokes.wiki Search About
Created
Updated

raylib-ios

A gallery of graphics scenes running on an iPhone, drawn by raylib 6.0 over SDL2, driven from Clojure compiled by Jolt onto Chez Scheme — no JVM, no JIT, and nothing generated at run time. EPL-2.0, part of the jlt-commons fleet, first commit 2026-09-03, 3 stars when this page was written.

It matters to this spoke as evidence rather than as software: it is the first artifact here where a host-swapped language ships on a platform its original host cannot reach, with frame times published.

How it gets onto the phone

raylib has no iOS backend and the project does not write one. src/platforms/ in raylib 6.0 holds Android, four desktop variants, DRM, web and a template, and the project reports that raylib issue #330, "Try to support iOS platform," was closed in 2018 without one. Instead: build raylib with PLATFORM=SDL and GRAPHICS_API_OPENGL_ES2 against an SDL2 built for iOS, and SDL's own iOS support becomes the platform layer. The problem collapses to "build SDL," which is well trodden.

Both libraries link in as static archives, -force_loaded rather than -led. jolt build --target emits the whole binary and Chez owns main, so there is no host process to dlopen a shared library and every defcfn resolves against the process image at first call. The project therefore declares no native dependencies at all — there is no libraylib.dylib to find.

Two pins are load-bearing and the README says why: Chez 10.4.1 exactly (a target pack's xpatch is compiled Scheme that loads only into the Chez that produced it) and jolt ≥ 0.8.0 (ffi/write swapped its value and offset arguments at 0.8.0; both are integers, so an older jolt would write the wrong byte to the wrong place in silence).

The numbers, and how to read them

Measured on an iPhone 17 Pro, iOS 26.6.1, running the gallery with Flappy Bird open, every draw call going through libffi:

host:  600 frames, mean 17.03 ms, worst  17.4 ms
host: 1200 frames, mean 17.02 ms, worst  17.1 ms
host: 5400 frames, mean 17.02 ms, worst  17.1 ms

Ninety seconds with a worst case 0.1 ms off the mean and no outliers. The project reads this as a loop that finishes its work and waits for vsync every frame, with the interpreter, the reducer, about twenty FFI calls per frame and the collector all fitting inside the budget. The 279 ms spike in the first window is InitWindow compiling shaders and building the default font.

Two arithmetic notes this page keeps. A 60 Hz frame is 16.67 ms; a 17.02 ms mean is 58.8 frames per second, so the repository description's "60 fps" is a rounding of a number that is consistently above one frame period, and nothing in the README explains the 0.35 ms. And that description says seventeen scenes where the README says forty-eight — the repo was pushed the morning this page was written, so the headline is simply behind. Neither gap changes the finding; both are the sort of thing a reader should not have to discover.

There is also a useful negative result about the measurement itself: raylib's GetFPS is a stateful sampler over a 30-slot ring, so it only returns a frame rate if called every frame. Called once per 300 frames it decays — 1757, 887, 590, … 98 — while GetFrameTime sat still at 17.02 ms. DrawFPS is the only place raylib itself calls it, and nothing documents the requirement.

Provenance of the code

The recipe came from Larry Staton's glimmer-ios-demo, two literate org notebooks that keep the mistakes as well as the answers. The scenes are lifted byte for byte from jasalt/jolt-android-experiment at commit 6d2b291, and the project ships a tool that re-checks the six pure namespaces against the sha256 the notebooks record and refuses to overwrite a file edited since. Pure .cljc simulations, an iOS owner loop of about thirty lines, and a scene contract where a scene is a reducer over frames — nothing in a scene polls, draws, or holds a native value.

Live coding survives the trip: jolt live starts the gallery with an nREPL, an editor drives the running app on the device, and evaluated work is handed to raylib.host/on-next-frame! because an eval lands on the nREPL thread and the toolkit is not thread-safe. Flappy Bird in the recorded GIF is flapped by a loop started from that REPL, not by a finger.

Tier: T3

The load-bearing claim is a benchmark the author ran on their own device, which is the maker reporting how well their own thing works. The artifact facts around it — licence, pins, link strategy, the absent iOS backend — are first-party and checkable, and the repository is the project's own. Read the frame times as unreplicated: one device, one build, no independent run.

Cross-spoke context

../game-engines-wiki owns raylib's own territory (it holds no raylib page yet; this one is a consumer of raylib, not an account of it). The mobile-deployment and toolchain detail is real but subordinate: what is being demonstrated is a language runtime, which is why it files here.

jolt-clojure · clojure · jank · hosted-language · jlt-commons · scheme