--- 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. [![Watch the demo](https://img.youtube.com/vi/CZ5-xUl1l-M/hqdefault.jpg)](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/