| --- |
| title: Hatchimera - Voxel Pet Fusion |
| emoji: 𧬠|
| colorFrom: purple |
| colorTo: yellow |
| sdk: gradio |
| sdk_version: 6.15.2 |
| python_version: "3.12" |
| app_file: app.py |
| short_description: Draw voxel pets, splice two, breed a family tree |
| pinned: false |
| license: mit |
| tags: |
| - track:wood |
| - achievement:offgrid |
| - achievement:offbrand |
| - achievement:llama |
| - achievement:sharing |
| - achievement:fieldnotes |
| --- |
| |
| # Hatchimera β Voxel Pet Fusion |
|
|
| **Hatchimera = hatch a chimera β draw two voxel pets, splice them, breed |
| something new.** |
|
|
| Draw voxel pets from a sentence, splice two into a chimera no menu could give |
| you, and breed a whole family tree. The model does the drawing. Hatchimera is |
| the toy that doesn't exist without it. |
|
|
| [](https://www.youtube.com/watch?v=CZ5-xUl1l-M) |
|
|
| ## The hook |
|
|
| Pick two voxel creatures, hit **Splice**, and watch **Gemma 4 12B** recombine |
| their box geometry into a chimera no menu could give you: one parent's body, the |
| other's signature feature, plus a mutation it makes up on the spot. The newborn |
| lands on a 3D stage and joins the family tree, where it can breed again. You can't |
| reproduce it from a dropdown, and it *is* the main thing on screen. |
|
|
| ## How it works |
|
|
| One moment is the model β the splice; everything around it is deterministic code. |
|
|
| | Layer | What runs | |
| | --- | --- | |
| | **Splice** | `fuse_creatures` β one Gemma 4 12B call merges two box layouts into a chimeric child β the only model call in the app | |
| | **Draw / Tweak** | **model-free**: a keyword picks a reference body template (`pick_exemplar`), and `edit_parser` maps "add two horns" onto a ~100-part catalog that `assemble_part` snaps on β instant, no GPU | |
| | **Render** | a Three.js voxelizer turns the box layout into an `InstancedMesh` stage, an animated pedigree tree, and a Figma-style lab canvas | |
| | **Runtime** | the model runs through **llama.cpp** (`llama-cpp-python`), on **ZeroGPU** on the Space; no cloud APIs | |
|
|
| The split is deliberate: the model is load-bearing where it earns its tokens β |
| recombining two creatures into something new β and the predictable parts (a |
| starter body, snapping on catalog parts) stay deterministic on the CPU. If |
| inference is unavailable or returns junk, the splice falls back to a deterministic |
| box-merge, so the demo never crashes; it just gets less surprising. |
|
|
| ## Run it |
|
|
| ```bash |
| pip install -r requirements.txt |
| |
| # Fake runtime β no model, instant; for UI / interaction work |
| BUDDY_FORCE_FAKE_RUNTIME=1 python app.py # or: scripts/start-local.sh |
| |
| # Real model β Gemma 4 12B through llama.cpp |
| scripts/start-local-real-model.sh |
| ``` |
|
|
| Both scripts bind `0.0.0.0:7860`, auto-pick a free port, and honor `PORT=` / |
| `HOST=`. Tests: `python -m pytest -q`. |
|
|
| Real-time real-model inference needs a GPU β on the Space that's ZeroGPU. On a |
| plain CPU the CUDA wheel can't initialize, so every model action silently falls |
| back to the deterministic path (still playable, just templated). Gemma 4 12B is |
| heavy to run on a local CPU. |
|
|
| ## How to play |
|
|
| 1. **Landing** β **Quick Start** drops you into the Lab with two random starter |
| parents on the tree; **βοΈ Build from scratch** instead opens an empty bench to |
| describe both parents yourself (a fresh family). |
| 2. **Lab** (the pedigree tree) β tap two creatures to stage them as A / B, |
| `π` inspects one in live 3D, or **βοΈ Build from scratch** adds a fresh buddy. |
| Staging two opens the Splice Bench. |
| 3. **Splice Bench** β tweak each side in its box (or `π²` for a random buddy) β |
| tweaks snap catalog parts on instantly, model-free; **Splice!** is the one |
| model call that breeds the two into a child. |
| 4. **Reveal** β the newborn appears beside its parents and joins the family tree. |
| 5. Keep breeding β every child stays in the tree, ready to be staged again. |
|
|
| ## The model, and how we got here |
|
|
| Hatchimera started as the *opposite* of what it is now, and the rewrite is the |
| whole story. |
|
|
| **v1 β recipe-level on a small model.** The first design had the model pick from a |
| closed vocabulary (archetype + parts + palette + mutation) and let a deterministic |
| voxelizer build the geometry. The bet: small models are weak at spatial reasoning, |
| so don't ask them to draw; ask them to *choose*. It kept a 3B model reliable, but |
| it could never draw "five arms" or "a house on its head". The AI felt like a |
| garnish. |
|
|
| **The bug that hid every model.** For a long stretch, tweaks looked like they |
| ignored the model. The cause wasn't the model. It was `response_format`. |
| llama-cpp-python honors `{"type": "json_object", "schema": β¦}` and **silently |
| ignores** the OpenAI-style `{"type": "json_schema"}`. With the wrong key the model |
| was completely unconstrained, returned malformed JSON, and every edit fell through |
| to the deterministic fallback. Fixing the key compiles the schema to a GBNF |
| grammar, and only then is any model's real capability visible. Lesson: measure |
| model quality *after* grammar enforcement works, never before. |
|
|
| **The spike.** To find out whether a small model could draw freehand at all, we |
| benchmarked **19 models across 9 families, 1Bβ32B**, on box-layout geometry. The |
| findings ran against intuition: |
|
|
| - Grammar enforcement fixes JSON validity across the board; it's a prerequisite, |
| not a model trait. |
| - **Geometry quality tracks neither size nor family.** A 12B isn't "better at |
| shapes" than a 3B by default; most models produce schema-valid but shapeless |
| blobs. |
| - **Gemma 4 is the only family that draws recognizable hard forms** freehand. Not |
| the biggest, not a whole tier β one family. |
|
|
| So Hatchimera went **all-freeform on Gemma 4 12B**. The earlier model ranking |
| (Qwen2.5-3B followed the genome *schema* best, Llama-3.2-3B overran its token |
| budget, SmolLM2 was middling) didn't carry over at all: schema-following and |
| shape-drawing turned out to be different skills. |
|
|
| **Shipping a 12B on ZeroGPU.** Two lessons stuck: |
|
|
| - **Grammar costs ~2Γ throughput.** The GBNF per-token allowed-set check is serial |
| CPU work that doesn't ride the GPU: 16.7 tok/s on vs 35.9 off on the same |
| prompt. Intrinsic to constrained decode, not schema bloat. |
| - **The wheel pin is load-bearing.** Gemma 4's chat template throws `unknown tag` |
| on llama-cpp-python 0.3.19's old Jinja engine, so the Space pins **0.3.29** |
| (installed via a `py3-none-manylinux` wheel, so `python_version "3.12"` stays |
| put). ZeroGPU here is an RTX Pro 6000 Blackwell. |
|
|
| Full measurements live in the wiki: per-call timing, the grammar bench, the model |
| spike report. |
|
|
| ## The wiki is the project's memory |
|
|
| This repo carries a git-tracked engineering wiki under [`wiki/`](wiki/), and the |
| coding agents actually read and write it. |
|
|
| - **It loads itself.** A `SessionStart` hook injects [`wiki/index.md`](wiki/index.md) |
| into every coding-agent session, so the agent starts with the project's hard-won |
| knowledge instead of re-deriving it. |
| - **Writes are gated, not trusted.** The policy lives in `wiki.config.json`. Under |
| `auto`, a `PreToolUse` gate judges every wiki write by the `confidence` the |
| author assigns it: `high` is allowed (and its diff is shown to a human), anything |
| lower (or a delete, or a write to the wrong path) is blocked and must be |
| proposed instead. It fails closed. |
| - **It's maintained.** Every change updates `index.md` and appends to |
| [`wiki/log.md`](wiki/log.md); a `Stop` hook checks that each session actually |
| evaluated whether it learned something worth recording. Codex runs the same flow |
| via `.codex/`. |
|
|
| The point: the messy middle (the `json_object` bug, the 19-model spike, the |
| ZeroGPU timing, the wheel-pin saga above) gets captured where the next person or |
| agent will find it, instead of evaporating. Start at |
| [`wiki/index.md`](wiki/index.md); [`model-selection-spike.md`](wiki/model-selection-spike.md) |
| and [`deployment-strategy.md`](wiki/deployment-strategy.md) are that journey in |
| full. |
|
|
| ## Layout |
|
|
| ``` |
| app.py entry point; loads buddy_fusion.fusion_ui |
| src/buddy_fusion/ |
| runtime.py BuddyRuntime: fake + llama.cpp; the single Gemma model |
| fusion_ui.py the Gradio Blocks game flow, all CSS, the JSβPython bridges |
| voxel_embed.py the Three.js voxelizer, pedigree, detail modal |
| fusion.py Creature / Lineage store + splice routing |
| prompts.py box-layout schema + few-shot message builders |
| edit_parser.py / assembler.py / parts_data.py the model-free Tweak path |
| fallback.py / exemplars.py deterministic content; never-crash net |
| wiki/ the engineering wiki (see above) |
| scripts/ start-local.sh (fake) / start-local-real-model.sh (real) |
| tools/ the model-selection spike + parts-catalog reports |
| ``` |
|
|
| ## Built for the Build Small Hackathon |
|
|
| Track: **Thousand Token Wood** β a delightful AI toy that wouldn't exist without |
| the model doing the interesting work. Badges this build claims: |
|
|
| - **Off the Grid** β no cloud APIs; the model runs in front of you. |
| - **Off-Brand** β a custom Three.js voxel frontend, well past the default Gradio look. |
| - **Llama Champion** β the model runs through the llama.cpp runtime. |
| - **Sharing is Caring** β agent traces shared on the Hub. |
| - **Field Notes** β the wiki above, plus the spike report, are the write-up. |
|
|
| ## Submission |
|
|
| - Team (Hugging Face): [`arkai2025`](https://huggingface.co/arkai2025) |
| - Demo video: https://www.youtube.com/watch?v=CZ5-xUl1l-M |
| - Social post: https://www.linkedin.com/posts/arkai_buildsmallhackathon-gradio-huggingface-share-7472427797077139456-XG1F/ |
| |