Save GIF / Share image / Voxel data are client-built <button>s, so Gradio's
theme paints them with --button-secondary-background-fill (white); our
non-!important background lost to it, leaving the white .l label invisible on a
white pill. Mark the pill backgrounds !important (base + .save/.native, and add
an explicit .voxel) so they beat the theme and the labels read again.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(lab): stop the canvas grid overflowing the right edge on wide screens
.buddy-canvas is 98vw but its Gradio parent column is max-width-capped and
centered on wide screens, so margin:auto let the 98vw box overflow the column
to the right (visible in the HF iframe; hidden locally only because a narrow
window doesn't hit the cap). Center it on the viewport with the full-bleed
margin trick (margin:2px calc(50% - 49vw)) so the parent width cancels out and
it never spills past the right edge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Landing now has two entries again: "Quick Start →" (restored Start → Lab with
seeded parents) and "✏️ Build from scratch" (re-added). Both route through the
Lab before any splice can happen, which mounts the lazily-mounted screen_lab and
fixes the blank-screen bug on the reveal's "← Back" (next_btn → screen_lab: an
unmounted Lab, half-mounted by do_fuse's slots_view/tree_view writes, rendered
blank).
Splice Bench input box is now fully model-free in both runtimes: LlamaCpp
generate_creature (pick_exemplar only) and edit_creature (keyword → assemble_part,
dropped the _freeform_edit_creature model fallback) match MockRuntime. The only
model call left in the app is fuse_creatures (Splice).
Reveal Share/Back are hidden over the fuse loading state via CSS
(.screen:has(.buddy-reveal-row) .buddy-reveal-actions) instead of gr.update(
visible=), which races on the freshly-revealed lazy column.
Docs/wiki: README hook + "How it works" + How-to-play and wiki system-overview /
index / log / grad…
feat(landing): Start opens the from-scratch bench, drop the second button
The landing "✏️ Build from scratch" button is removed; Start → now runs the
FreeLand chain (freeland_open → freeland_fill), opening the Splice Bench with
both slots empty instead of the Lab pick screen. Dead #buddy-freeland-btn CSS
dropped; the in-Lab Build-from-scratch button is unchanged.
Docs follow the code: rewrite README "How to play" and correct the wiki
(system-overview, index) to drop the long-removed "🪄 Freeform Lab" screen
(do_freeform / free_enter_lab no longer exist) — drawing-from-text now happens
in the bench's per-side box.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(lab): resize tree GL buffer on height drift, not just width
The scissor renderer only called setSize when the canvas WIDTH changed.
.buddy-canvas height is min(calc(100vh-142px),1040px), and inside the
auto-resizing HF iframe 100vh keeps shifting after init (the documented
height runaway). When height changed but width didn't, the GL drawing
buffer stayed stale while the scissor/viewport math used the live
clientHeight, so every tree card below the first painted shifted up and
stretched. Reproduced only through the Space wrapper (100vh is stable
locally). Guard now resizes when either dimension drifts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs(readme): lead with the hatch-a-chimera pun line
The Space was renamed build-buddy -> hatchimera. Update SPACE_URL (the
share/OG target for X, Facebook, LinkedIn, native share), the wiki Deployment
note, and the CLAUDE.md verification wrapper URL.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs(readme): list team HF username for hackathon submission
shareStamp now emits YYYYMMDD-HHMMSS so saved files (Save GIF / Voxel data)
are fully dated and sort chronologically: e.g. 20260616-031529_Hatchimera_Share.gif.
Move the Voxel data button to sit immediately right of Save GIF by prepending it
(it builds before the GIF resolves, so Save GIF / native share prepend ahead of
it) — row now reads: Share image | Save GIF | Voxel data | X | fb | in.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the dual Qwen2.5-3B / SmolLM2-1.7B recipe-model setup. Every AI moment —
freeform generation and hatch fusion — now runs through a single model,
Gemma 4 12B, the only family that draws hard voxel forms in the model-selection
spike. LlamaRuntimeConfig defaults and env overrides collapse onto the freeform
model; the now-dead fallback-model fields and Qwen/SmolLM references are removed.
Retitle the project to "Hatchimera — Voxel Pet Fusion" across the README and
wiki, and align the wiki (deployment, hackathon, overview, local-dev, voice)
with the single-model story.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-bake all landing-backdrop buddy PNGs from the current Compendium box
templates, grafting 0-2 random parts (wings, horns, hats, tails, feet, etc.)
onto each so the drifting grid reads as varied chimeras rather than bare
templates. 52 transparent 512x512 RGBA thumbnails (was 50), baked headless via
the proven voxel bake harness (bounds-driven hero camera, ACES 1.4, alpha).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(share): download voxel data + dated, readable download filenames
- New "Voxel data" button on the share screen exports the focus buddy as JSON
(format/version, name, palette, the freeform box layout when freeform, and the
resolved unit voxel cells [{x,y,z,color}] — portable into other voxel software).
Offered independently of the GIF (works even if the GIF render fails).
- Download filenames are now MMDD-HHMMSS_Hatchimera_<label>.<ext> (local time) via
shareStamp/shareFilename, so the GIF saves as e.g. 0616-023920_Hatchimera_Share.gif
instead of a blob UUID, and the voxel export as ..._Hatchimera_VoxelData.json.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(bench): show Splice only when both slots filled + a periodic wobble nudge
The Splice button is hidden until BOTH bench slots hold a creature, and once shown
it gives a brief wobble every ~4.6s (after a 1.6s beat) as a 'you can splice now'
reminder — paused on hover/focus, off under prefers-reduced-motion.
Visibility is driven CLIENT-SIDE (buddyToggleSplice in the voxel poll counts filled
vs from-scratch bench cells and toggles .buddy-can-splice) rather than Gradio
`visible`: a never-mounted visible=False button's first reveal in a multi-output
response silently dropped (the same mount race as the bench/FAB), so the button
flickered between not-showing and showing depending on which slot filled last. The
DOM is the source of truth, so it's robust across re-renders.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docs(wiki): dot-grid multiply fix + the two bench hint vocabularies
- voxel-rendering: the scissor canvas occludes the dot-grid on real Chrome;
mix-blend-mode:multiply is the fix.
- system-overview: bench chips run the edit path (match_part -> parts catalog)
while empty-slot input/placeholder run the generate path (pick_exemplar, 6
shapes, quadruped fallback); verify hint text against the matching function.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(canvas): restore the dot-grid under the scissor 3D layer via multiply
The single scissor WebGL canvas (.buddy-canvas first child) composites opaque-
white over the CSS dot-grid behind it on real Chrome, leaving a plain white
canvas. Putting the dots on the GL canvas's own background fails identically
(the element composites white though the buffer is [0,0,0,0]). mix-blend-mode:
multiply turns the transparent-white gaps into a no-op so the .buddy-canvas dots
show through, while cards stay vivid (near-white backdrop). Verified locally.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(bench): random parts on 🎲, well-supported hints, Lab scratch entry
- 🎲 Random now grafts 1-2 catalog parts from distinct decorative categories
(props excluded) onto the picked Compendium template, so re-rolling the same
template still looks different (_graft_random_parts).
- Suggestion chips: every SUGGEST_POOL entry now resolves to a real catalog part
via edit_parser.match_part. Dropped the two body-shape phrasings that silently
mis-matched ("make it short and round" -> round ears, "make it tall and skinny"
-> a tophat), since the edit path can only ADD parts, not reshape the body.
- Bench input placeholder is randomized per open (GEN_EXAMPLE_POOL) and every
example maps to one of pick_exemplar's 6 body shapes via a recognized keyword
(no silent quadruped fallback), 2 per shape.
- New Lab top-right "✏️ Build from scratch" (scratch_open/scratch_fill): opens the
bench with both slots empty but KEEPS the current family (a splice adds to the
tree); distinct from the landing FreeLand which starts a fresh family.
- From-scratch panel cop…
docs(wiki): record client-side selection badge + the FAB parent-Column reset
Two Gradio embed rules verified while merging the FreeLand branch:
- on_select never re-renders the tree; the A/B badge is applied client-side so
selecting does not reset the canvas pan or reboot the live-3D nodes (no flash).
- A child visible=False bundled with its parent Column's visible-toggle is
dropped (the bench FAB reappears on reveal/share Back); fix is a chained .then.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
merge: bring main's part-assembly-edit feature into the FreeLand branch
Merges main (the parallel parts-catalog + keyword/parametric assembler edit
path, single-context scissor tree rendering, runtime selector rework) into the
FreeLand-from-scratch / Random / draft-creature / canvas-and-nav-polish branch.
fusion_ui.py and voxel_embed.py auto-merged cleanly (the two efforts edited
disjoint regions); wiki/log.md was the only content conflict (append-only on
both sides, kept both). Two semantic artifacts the line-merge could not catch,
both fixed and verified locally:
- Canvas pan dead-zone: this branch's installCanvasPan suppressed panning when a
drag began on a node (it assumed per-node OrbitControls to rotate). main's
scissor renderer draws the tree through ONE pointer-events:none context with
auto-rotate, so node-drag rotated nothing AND no longer panned. Now a drag
anywhere pans (matches main's installTreeInteractions intent); the __buddyPanned
threshold still keeps a clean tap selecting.
- Splice Bench FAB resurrected on reveal -> Lab Back: …
docs(wiki): record part fusion/accumulation + symmetric straddle
system-overview.md: parts fuse not stack (_part/_pc tags survive the
freeform_genome -> Lineage round-trip; display cfg strips them harmlessly), and
symmetric parts straddle the body by mirroring across the body centre.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(edit): parts fuse and redistribute instead of stacking
Adding the same part again now accumulates its instance count (tracked via _part/
_pc tags on the placed boxes, which survive the freeform_genome -> genome dict
round-trip) and RE-distributes the whole set evenly across the anchor face, instead
of piling new copies on the previous ones. So 'add wing' then 'add wing' gives four
wings (two per side, fanned front-to-back), not two stacked pairs.
Also fixes symmetric parts drifting far off to one side: they now straddle the body
(left half hugged to the left face, mirrored across the BODY center to the right)
instead of mirroring within the part and face-aligning the whole pair to one side.
Distribution spans the body's own extent so many instances hug the body.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
voxel-rendering.md: rewrite the WebGL context-cap strategy around one shared
context drawn via the scissor test (tree installTreeScissor + report pages'
voxel-cards.js), with the three load-bearing gotchas (canvas behind content,
full clear per frame, re-key on real canvas child). system-overview.md: record
the box-edit path going model-free (keyword parser + parametric assembler,
MiniCPM selector removed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
perf(tree): render the family tree from one WebGL context via scissor
The pedigree tree booted one WebGL context per node card (pooled by cullCanvasNodes
to on-screen nodes). A tree with many nodes visible at once could still exceed the
browser's ~16-context cap and blank/flicker. Replace it with installTreeScissor: one
renderer per .buddy-canvas (sitting above its dot-grid, below the panning card layer)
draws every node into its on-screen rect via the scissor test, clipped to the canvas
viewport — so the tree uses exactly 1 context regardless of size.
Node cards become transparent windows onto the shared canvas: the A/B badge moves
into the footer and selection shows as a card ring (both outside the thumb) so the
behind-content canvas never occludes them. The renderer keys on its real canvas
child (not a JS-prop lock) so it re-attaches after Gradio swaps the tree HTML, and
the loop self-disposes when its canvas is stripped. cullCanvasNodes is now a no-op.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(tools): place shared GL canvas behind content so page text shows
The single scissor canvas was z-index:50 (on top, pointer-events:none). That
composites fine in DevTools screenshots but on real Chrome the full-viewport
transparent WebGL layer occluded all page text (header, filter pills, card names).
Move it to z-index:-1 behind content (three.js multi-element pattern): the page
backdrop moves to <html>, <body> and card stages go transparent so the 3D shows
through the stage windows while all text/chrome paints normally on top.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
perf(tools): render all report cards from one WebGL context via scissor
Replace the per-card-context approach (capped at ~12 to stay under the browser's
~16-context hard limit, which also caused flicker as the browser killed and
recreated the oldest contexts) with a single shared renderer that draws every
card into its on-screen rect via the scissor test. Removes the cap entirely: all
106 parts / 52 creatures stay live at once with no flicker. Per-frame transparent
clear avoids scroll ghosting; per-card 256px shadow maps bound memory; minimal
document-level pointer hit-testing keeps drag-to-orbit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
UX follow-ups on the FreeLand/bench flow:
- Selection no longer re-renders the canvas. on_select stops returning tree_view;
the A/B badge is applied client-side (buddySelect reads the DOM badges as the
source of truth and re-applies the same toggle logic). Picking a creature after
panning no longer resets the pan to center or reboots the live-3D nodes — the
"jump back to blank center then show selection" flash is gone. The server still
returns sel_a/sel_b + data-ready + the FAB so the bench and auto-advance stay
in sync.
- Canvas enlarged toward full-bleed: width 98vw (max 1860px), height
min(100vh - 142px, 1040px); collapsed the hidden slots carrier and tightened the
Lab column gap to reclaim the strip above the canvas.
- Reveal screen: fixed the Share button position (the Next button's margin:auto
was absorbing the flex row and shoving Share to the edge); relabeled
"Next -> back to the family" to "<- Back" with the shared white-pill back style;
share screen "<-…
feat(tools): live-3D Part Catalog page + compendium rework
Both report pages render every part/creature with the real in-game rig (shared
voxel-rig.js) instead of static thumbnails: production lighting, contact shadows,
ACES tone mapping, turntable, drag-to-orbit. A shared IntersectionObserver
context-cap manager (voxel-cards.js) keeps live WebGL contexts capped (~12,
nearest-to-viewport-center prioritized) so 106 parts / 52 creatures never blow the
browser's context limit. Part Catalog adds category sections + a full table.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(edit): replace small-model selector with keyword + parametric assembler
Edit path is now pure Python: an English keyword parser maps the instruction to
a catalog part plus count/scale/anchor/rotation/color, and the assembler snaps it
on deterministically (symmetric pairs first, overflow to rear; float scale rounded
to the grid; exact 90deg integer-grid rotation). Instant, no GPU. Gemma 12B stays
only for full generation, hatch fusion, and the freeform-edit safety valve.
Removes select_part, the MiniCPM selector constants/preflight/warmup bench, and
SELECT_SCHEMA/build_select_messages.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(tools): extract shared voxel rig + parts-catalog data sync
Build a buddy from scratch and splice it, reworking the Lab/bench flow.
- FreeLand: landing "Build from scratch" button opens the bench with both
slots empty; each empty slot generates a freeform creature from a typed
description (runtime.generate_creature, exemplar fallback). The bench is now
the single A/B workspace: a slot is either a tree-picked creature or one
built from scratch.
- Advance relaxed from two picks to >=1: the unselected side becomes a
from-scratch panel; splice is gated until both slots are filled.
- 🎲 Random per slot: fills with a random Compendium template, kept distinct
from the other slot (no duplicate sides).
- Draft creatures: bench candidates (Random / from-scratch) are drafts hidden
from the family tree until a splice commits their two parents, so re-rolling
no longer litters the tree with orphan "ancestors" (Creature.draft, serialized).
- Bench Back returns to its origin (landing for FreeLand, Lab otherwise) via a
bench_origin State, ins…
feat(runtime): log selector picks; fake edit keyword-matches the catalog
Print [selector] picked part=.. anchor=.. scale=.. on each catalog hit so the
Space log shows what the small model chose. FakeBuddyRuntime.edit_creature now
keyword-matches the instruction to a catalog part (not always horns) so local UI
testing exercises the whole part set.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(runtime): default selector to MiniCPM5-1B-GGUF with auto-fallback to Qwen
Wire the selector default to openbmb/MiniCPM5-1B-GGUF / MiniCPM5-1B-Q4_K_M.gguf
(the base openbmb/MiniCPM5-1B has no GGUF). If it fails to load on the Space's
llama-cpp-python 0.3.19 (e.g. chat-template unknown tag), select_part logs it,
switches the container to Qwen2.5-3B, and retries immediately so the edit still
lands fast — no repeated attempts on the broken model. Override via
BUDDY_SELECTOR_MODEL_REPO/_FILE.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(runtime): selector->assembler edit path with freeform fallback
Route edit_creature through a small grammar-enforced selector (select_part) that
maps free text to {part, anchor, scale, color} from the catalog; assemble_part
snaps it on (~2-3s). Off-catalog / transform / selector miss falls through to the
freeform Gemma edit (renamed _freeform_edit_creature); model failure is logged,
not swallowed. Selector model is env-configurable (BUDDY_SELECTOR_MODEL_REPO/_FILE),
default Qwen2.5-3B; env-gated _selector_preflight() prints load ok/FAILED so a
0.3.19 chat-template failure (e.g. when trying MiniCPM5-1B) is visible.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(parts/assembler): catalog parts + deterministic anchor placement
parts.py: hand-authored additive part catalog (horns/wings/tail/ears + props
house/hat/crown) in local coords with anchor/scale/symmetry metadata.
assembler.py: pure-function assemble_part — face-aligns a (scaled, mirrored)
part to the creature's bounding box at the chosen anchor, clamps via repair_boxes,
appends. Unknown part / empty boxes -> unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Today edit/suggest sit behind `except: pass`, so a 0.3.19 chat-template load
failure is silently swallowed into the fallback with no log. Add an env-gated
selector preflight that prints `[selector-preflight] ok/FAILED: <err>`, and make
the production path log the exception + auto-fall to Qwen2.5-3B with a printed
line. One restart + page open tells you if MiniCPM5-1B loads and why not.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dual-path edit: keep Gemma-12B freeform for from-scratch generation and hatch
fusion (the low-frequency big moments); route in-bench edits (suggestion chips +
A/B text) to a fast template part-assembly path. A small selector (MiniCPM5-1B,
grammar-enforced, thinking off; Qwen2.5-3B fallback) outputs {part, anchor,
scale}; a deterministic assembler snaps hand-authored part geometry onto the
creature's bounding box. Additive parts only for MVP; transforms fall back to the
freeform Gemma edit. Lists the 0.3.19-load / GGUF / thinking-off / latency
verification gates.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs(wiki): record measured Gemma-4-12B freeform timing on ZeroGPU
deployment-strategy.md: add "Freeform 12B timing" — reload ~3s, ~17-22 tok/s
with grammar (25-41s/call), grammar ~2x cost (16.7->35.9 on/off, intrinsic to
grammar-constrained decode), the duration=30->60 abort fix, the 1500-token >60s
edge, and that ZeroGPU aborts a 3rd GPU acquisition stacked in one demo.load
handler. Bump wrapper list to three @spaces.GPU wrappers.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On-Space bench: Gemma-4-12B freeform decodes ~17-22 tok/s WITH the GBNF grammar
vs 35.9 tok/s without (same 400-token prompt) — grammar costs ~2x, and it is
intrinsic per-token CPU sampling overhead, not schema bloat (BOX_SCHEMA is
already lean). Two levers:
- A: BUDDY_FREEFORM_NO_GRAMMAR=1 drops the grammar on the freeform box path only
(heavy=True calls: generate/fuse/edit), leaning on parse_json_object +
repair_boxes for the unconstrained output. Recipe GENOME_* calls always keep
the grammar (fragile without it). Env-gated so it can be A/B'd live and
reverted instantly.
- B: lower the default BUDDY_FREEFORM_MAX_TOKENS 1500 -> 1000. Trims the typical
wait and closes the residual crash edge (1500 tok / ~20 tok/s ~= 75s overran
the duration=60 budget; 1000 -> ~50s fits).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(runtime): grammar bench measures OFF only to avoid ZeroGPU abort
Running grammar-on + grammar-off back-to-back inside the single demo.load
warmup handler stacked a 3rd GPU acquisition after the warmup gen, which
ZeroGPU aborted ("GPU task aborted"). grammar-on throughput is already the
normal [zerogpu-bench] number every real freeform call prints (~17-22 tok/s),
so measure only the unconstrained grammar-off run here — keeping the handler
to 2 GPU acquisitions (warmup + bench), the range the log shows succeeds.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
test+docs: repair edit_creature 4-tuple test; wiki for canvas + one-call tweak
A parallel commit landed the single-call edit_creature change (now returns
(genome, boxes, tweaks, status)) but left test_freeform_edit.py unpacking the
old 3-tuple, so the committed test failed. Update the unpack + assert the 4
returned tweaks. Full suite: 81 passed.
Wiki:
- voxel-rendering.md: "Lab canvas: pan + viewport-pooled live 3D" (live nodes
kept under the WebGL context cap by cullCanvasNodes; the setPointerCapture
click-swallow trap; cached pan offset + dot-grid shift; JS bench-x centering),
refreshed the context-cap failure mode, added the 0x0 size-guard fix (blank
Splice Bench) + culled-stage disposal, retired the obsolete inspect-modal note.
- deployment-strategy.md: "A bench tweak is ONE GPU call, not two" — edit_creature
returns geometry + 4 hints from a single completion (combined EDIT_SCHEMA),
removing the per-tweak suggest_tweaks GPU call (likely "Running on ZERO" stuck
cause), with the fallback top-up and revert path.
Co-Authored-By: Claude Opus 4.8 (…
fix(readme): trim short_description to <=60 chars for HF metadata gate
HF's pre-receive YAML check rejects short_description > 60 chars; the previous
copy was 71. Trim to 56 without touching the load-bearing sdk_version /
python_version pins.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the static pedigree tree + detail modal with a fixed-px pannable
canvas (dot-grid background, left-drag pan) holding live 3D nodes that
boot/teardown as they pan in/out of view, plus a floating top-left toolbar.
Recolor the bench hint bubbles to opaque white + navy text for legibility,
and unpack the new 4-tuple edit_creature return at the bench caller.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>