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: …
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>
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>
refactor(parts): split catalog into parts_data.py + add category schema
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): 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>
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 (…
merge: share lineage GIF + voxel build animation into main
Add a full-screen share view after a buddy hatches: a looping GIF that
dollies along the direct bloodline (A × B → hatched descendant), each
creature assembling cube-by-cube bottom-up, with a near-frontal hero
camera (azimuth ~22°, elevation ~18°), shaped ShadowMaterial projection,
and bright studio lighting.
- fusion.py: Lineage.bloodline(focus_id) returns the bounded direct
vertical lineage (ancestors breadth-first + representative descendant
chain), oldest-first by (generation, creation order).
- voxel_embed.py: progressive drawRange "build" animation on every live
rebuild (solid + non-solid modes); client-side GIF pipeline via gifenc
(jsdelivr ESM) with frame coalescing; shadow-mapped form-based shadow.
- fusion_ui.py: screen_share state, icon-only X/FB/LinkedIn buttons,
Save GIF + native share; homepage share extended to the same platforms
with corrected Facebook/X/LinkedIn URLs.
- tests: Lineage.bloodline coverage + share_text formatter test.
Co-Authored-By: Claude …
feat(bench): big 3D scenes + Gemma-suggested hint bubbles in a halo arc
Splice Bench redesign from user feedback:
- Title centered to the VIEWPORT (Back pulled out of flow), not the row.
- Each side becomes a large 3D scene (440x380); the creature fills it and the
name is merged into the A/B header. The colour-swatch card is removed.
- The 4 quick-tweak chips become glassy hint bubbles floating in a halo arc
across the top of the scene (outer-low, inner-high; right side anchored with
right: so fixed-width pills never clip). Gentle float animation
(reduced-motion-aware). They keep the buddy-chip class, so clicks still route
through the existing buddyChip -> on_chip bridge (no JS change).
- Hints are full imperative sentences ("Add two curved horns"), shown verbatim
and sent as the tweak prompt.
- Hints are now AI-driven: first render samples a static pool; after EVERY tweak
on a side, Gemma regenerates that side's 4 hints for the new form
(runtime.suggest_tweaks + build_freeform_suggest_messages + TWEAK_SCHEMA;
fake runtime / model failure…
The bench's controls drifted off-centre because each side mixed separate-width
components (260px stage, 540px tweak row, 260px chips), so nothing shared a
centre line, and the × used a brittle padding hack.
Redesign, each side is one centred stack:
- Quick-tweak chips become game-style ability buttons that FLANK the model (two
per side), moved into the bench HTML; short labels, full instruction in
data-prompt. Widen the bench columns so both chip columns stay beside the model.
- × is a soft circular game token, measured dead-centre on the model (xVsStage 0).
- Comfortable gap between the model and its name plate (was cramped).
- Custom tweak input + Apply unified to the pod width and centred.
Verified on the fake runtime: chips flank on one line, splice still reveals the
chimera. 73 tests pass (test_bench_ui updated for chips-in-bench-side).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(compendium): data module + report tool + first 12 creatures
Claude-authored freeform creatures across themes (alien/mythical/plant/aquatic/
mascot/bug). compendium.py exposes all_entries/get/random_sample/as_genome;
compendium_data.py holds the growing roster (toward ~50). tools/compendium/
report.html (+ sync_report_data.py) renders the whole roster with the app's
AO voxelizer so the set can be browsed offline anytime.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(freeform): route the whole main flow through the freeform path
Phase 1 of all-freeform: ancestors seed as freeform (exemplars, stopgap for the
compendium), AI Tweak edits box geometry via a new edit_creature path instead of
the recipe interpret_genome_command (which stripped boxes), splice already routes
to fuse_creatures for box parents, Draw-from-prompt moved into the Lab, warmup
fires the Gemma freeform model. Qwen recipe code kept dormant.
New: prompts.build_freeform_edit_messages, fallback.edit_boxes_fallback
(deterministic box editor), runtime.edit_creature (Protocol + Fake + LlamaCpp).
Verified end-to-end on the fake runtime (seed -> tweak -> splice -> reveal).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(freeform): steer one dominant body colour + accents in prompts
Re-running a model that is already in report_data.js previously appended a
second set of rows (duplicate model in the matrix). Add merge_rows(): drop
existing rows whose model label is being re-tested, then append the fresh
run. New models still append; brand-new themes need the model re-run.
Document the behavior in README and cover merge_rows with unit tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Consolidate the throwaway scratch/voxel-spike/ exploration (11 scripts,
scattered HTML viewers) into a tracked, reusable tool so the next model
test does not start from zero.
tools/voxel-model-eval/:
- core.py: pure base (SYS prompt, 8 themes, BOX_SCHEMA, extract_json,
voxelize, metrics); no model loading, llama_cpp never imported here.
- models.py: the editable (label, repo) list.
- exemplars.py: few-shot reference layouts for --fewshot.
- eval.py: single CLI; one model at a time (download -> grammar-enforced
test -> optional --delete-after -> merge); llama_cpp imported lazily so
the pytest suite runs without the sibling venv.
- report.html: Three.js viewer with a theme filter; live WebGL contexts
are reconciled by distance-to-viewport-center on scroll (fixes the
evict-furthest blanking bug where still-visible cards never re-mounted).
- report_data.js: committed 19-model x 8-theme sweep so the report opens
populated.
Align voxelize() to JS Math.round (round half up) so P…
Add an opt-in freeform path where the model draws raw box-layout geometry
({name, boxes:[{x,y,z,w,h,d,color}]}) instead of the recipe, so creatures can
take cross-form shapes (five arms, a house on the head) the closed recipe
vocabulary cannot express. Gemma 4 12B (the model-selection spike winner),
grammar-enforced, seeded with a hand-authored reference exemplar.
Freeform creatures are first-class: they enter the family tree and breed. A
splice routes to a Gemma "merge two creatures" call when either parent carries
boxes (freeform x freeform and freeform x recipe both yield a box child);
otherwise the recipe fuse runs. Fallbacks (fake runtime / model failure) use a
reference exemplar or a deterministic box merge, so the app never renders empty.
- genome: optional Genome.boxes + freeform_genome(); round-trips, recipe
creatures stay box-free
- prompts: BOX_SCHEMA, build_freeform_messages, build_freeform_fuse_messages
- runtime: generate_creature / fuse_creatures on Gemma via per-cal…
interpret_genome_command merged the model edit as `edit.get(x) or
genome.get(x)`, so a valid-but-empty model edit silently kept the original
genome with no exception — bypassing the keyword-editor fallback (which only
fired on exception). On the small ZeroGPU model this made every tweak look
like it did nothing.
When the model's result leaves the rendered look (archetype/palette/parts)
unchanged but the deterministic keyword editor does change it, surface the
keyword-editor result instead so a tweak always lands. Add LlamaCppBuddyRuntime
tests for the empty-edit fallback and the model-edit-used paths.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat: Build Buddy — voxel pet fusion (AI genetics engine)
Hatch two voxel ancestors, voice-tweak them, and let a small local model
splice their genomes into an unpredictable next-generation chimera that
joins the family tree. AI is the load-bearing genetics engine; a
deterministic voxelizer renders Crossy-Road-style animals from the
recipe-level genome the model produces.