hatchimera / README.md
arkai2025's picture
docs(readme): update social post link
1b84e41
|
Raw
History Blame Contribute Delete
9.66 kB
---
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/