How I Built Run Reachy Run
I built Run Reachy Run for the Gradio × Hugging Face Build Small Hackathon — a juice-heavy infinite runner that lives entirely inside one Hugging Face Space. The hero is Reachy Mini, the open-source robot from Pollen Robotics, but the whole hook is that you don't have to play as Reachy. You type anything — or upload a photo — and an in-Space AI pipeline turns it into a playable character in seconds.
One idea organized every decision: the world is fixed and hand-crafted; only the hero is generated. That single constraint is what makes arbitrary AI output instantly playable and fair.
Architecture
A couple of small models behind one route, and a Hugging Face Bucket holding everything that has to persist across players.
Sprites with FLUX.2-klein-9B
Sprite generation has killed more of my game ideas than I'd like to admit, so this was the part I was most nervous about — and FLUX.2-klein made it a non-issue. It's a distilled model: four steps, and one GPU attach renders all four sprites (player, background, two enemies) at once. I keep the GPU reservation tight on purpose — over-reserve, and one player locks out the next.
FLUX renders on a magenta background that I key out into transparent pixel-art sprites. The key is a little smarter than an exact color match, so it catches FLUX's habit of adding a faint drop shadow without eating skin tones.
Photo mode is my favorite touch. Upload a photo and two things happen: a vision model captions it so the world themes itself around who's in the shot (upload Thor, get Asgard), and the player sprite becomes a FLUX image-edit of your actual photo — same person, now a side-profile runner. The captioning runs through a remote VLM chain (MiniCPM-V → NVIDIA Nemotron → Qwen2.5-VL), so any single failure just falls through gracefully.
MiniCPM dresses the whole world
Here's the trick behind "fixed world, generated hero": one MiniCPM call re-themes everything. From your one-line character it writes a realm name, a hero name, an announcer voice, in-world names for all six power-ups, and the FLUX sprite prompts — one internally-consistent theme pack. It only renames the fixed effects the engine already supports; it never invents new mechanics, so the game stays balanced no matter what you type.
The same model handles live commentary — near-miss hype and death epitaphs. If the API's down, the browser falls back to canned lines so the game keeps its personality offline.
Keeping it sane: the content-safety gate
Players type anything, so before a single sprite is generated I screen the text (and any photo caption) through NVIDIA Nemotron-3.5-Content-Safety — a Gemma-3 4B classifier I host on a Modal GPU and call as a tiny inference API. It runs the Aegis taxonomy and tells me exactly which categories an input trips.
I only block what matters for an all-ages game — sexual content, anything sexualizing minors, self-harm, hate, real-world weapon/drug/bomb instructions, fraud, malware, PII — and deliberately allow fictional violence and cartoon weapons, so "a barbarian with a battle-axe" sails through while "how to build a bomb" gets stopped cold. The blocklist is just an env var, so I can re-tune the policy without redeploying anything.
Two choices I'd repeat: the gate is fail-open (any error, timeout, or cold start allows — a safety hiccup never blocks gameplay), and it lives off the Space on Modal, which scales to zero so it costs almost nothing when nobody's playing.
A leaderboard using HF buckets
A Hugging Face Bucket mounted at /data is the single source of truth for the global leaderboard and the community gallery
A score is a single file named after the score itself, zero-padded so a plain alphabetical directory listing is the ranking. Community worlds are opt-in: press SHARE and the whole pack is saved as one object, with a tiny separate "card" file so the gallery lists fast. Even likes are one empty file each, so concurrent players never collide. No read-modify-write means no race conditions to babysit.
The game itself
The runner is one big procedural canvas — fixed-timestep loop, parallax, day/night, particles. There's no sprite sheet; the run animation is faked with bob, lean, and squash. The feel comes from the classic juice trio: trauma-based screen shake, hit-stop freeze, and near-miss slow-mo. The whole soundtrack is hand-built WebAudio — a lo-fi loop and synth SFX, no audio files. Hitboxes are authored in tiles, and any generated creature of any size is drawn to fit — that's what keeps an arbitrary AI sprite both playable and fair. There's a deep persistent economy on top: skill upgrades, an Ultra Bullet unlock, and a four-tier Prestige multiplier that even reskins your coins.
What I learned
A few things that stuck with me:
- Small, distilled models are the unlock. Sprite art used to kill my game ideas; FLUX.2-klein doing it in four steps erased that wall — and the same held across the stack, with a 4B safety model and a small reasoning model doing plenty. Open + small beat big + closed here.
- Constrain the AI, not the player. "Fixed world, generated hero" is the game: let the model re-skin a fixed, balanced engine and never invent mechanics, and any typed character is instantly playable and fair.
- ZeroGPU only funds real Gradio events. My biggest time-sink — a plain
fetch()from the game iframe gets zero GPU quota. The fix (a postMessage bridge into a hidden Gradio event) is the kind of thing no doc warns you about until it bites. - Run each model where it fits. I first ran the safety model in-Space on CPU and fought platform/arch issues and an OOM-killed build; moving it to Modal as a usage-based, scale-to-zero API was simpler, cheaper, and platform-independent.
- A safety gate must never break the game. Fail-open and tunable per-category (block real harm, allow fictional violence) — moderation that fits the product, not a blanket switch.
- Boring persistence wins. A Hugging Face Bucket with one immutable file per write gave me a global leaderboard and a community gallery — no database, no locking.
And the meta one: leaning on Codex and Claude for planning and debugging let me spend my time on the genuinely hard, non-obvious problems instead of boilerplate.
Take the leaderboard
It's all running in one Space, persisted in one bucket. Go type something absurd, watch FLUX conjure it in four steps, and put your name on the board — Reachy Mini is sitting at the top, waiting for someone to knock him off.
Run Reachy run.
