test+docs: repair edit_creature 4-tuple test; wiki for canvas + one-call tweak
Browse filesA 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 (1M context) <noreply@anthropic.com>
- tests/test_freeform_edit.py +2 -1
- wiki/deployment-strategy.md +16 -1
- wiki/log.md +17 -0
- wiki/voxel-rendering.md +57 -21
|
@@ -38,8 +38,9 @@ def test_fake_runtime_edit_creature():
|
|
| 38 |
from buddy_fusion.runtime import FakeBuddyRuntime
|
| 39 |
rt = FakeBuddyRuntime()
|
| 40 |
genome = {"name": "X", "archetype": "chick", "parts": []}
|
| 41 |
-
g, boxes, status = rt.edit_creature(genome, BASE, "add two horns")
|
| 42 |
assert len(boxes) > len(BASE)
|
| 43 |
assert status.used_fallback
|
| 44 |
assert g["name"] == "X"
|
|
|
|
| 45 |
|
|
|
|
| 38 |
from buddy_fusion.runtime import FakeBuddyRuntime
|
| 39 |
rt = FakeBuddyRuntime()
|
| 40 |
genome = {"name": "X", "archetype": "chick", "parts": []}
|
| 41 |
+
g, boxes, tweaks, status = rt.edit_creature(genome, BASE, "add two horns")
|
| 42 |
assert len(boxes) > len(BASE)
|
| 43 |
assert status.used_fallback
|
| 44 |
assert g["name"] == "X"
|
| 45 |
+
assert len(tweaks) == 4 and all(isinstance(t, str) and t for t in tweaks)
|
| 46 |
|
|
@@ -1,7 +1,7 @@
|
|
| 1 |
---
|
| 2 |
title: Deployment Strategy
|
| 3 |
tags: [deployment, hugging-face, llama-cpp, zerogpu, architecture]
|
| 4 |
-
last_updated: 2026-06-
|
| 5 |
confidence: high
|
| 6 |
sources:
|
| 7 |
- src/buddy_fusion/runtime.py
|
|
@@ -57,6 +57,21 @@ background while the player reads the intro. It runs once per container
|
|
| 57 |
These are recipe-level JSON, not prose, so the budgets are small β keeping a
|
| 58 |
playthrough's GPU seconds lean so a judge's first splice isn't spent on the JIT.
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
### Schema enforcement (response_format) β load-bearing
|
| 61 |
|
| 62 |
`_blocking_completion` constrains output with
|
|
|
|
| 1 |
---
|
| 2 |
title: Deployment Strategy
|
| 3 |
tags: [deployment, hugging-face, llama-cpp, zerogpu, architecture]
|
| 4 |
+
last_updated: 2026-06-15
|
| 5 |
confidence: high
|
| 6 |
sources:
|
| 7 |
- src/buddy_fusion/runtime.py
|
|
|
|
| 57 |
These are recipe-level JSON, not prose, so the budgets are small β keeping a
|
| 58 |
playthrough's GPU seconds lean so a judge's first splice isn't spent on the JIT.
|
| 59 |
|
| 60 |
+
### A bench tweak is ONE GPU call, not two (2026-06-15)
|
| 61 |
+
|
| 62 |
+
`edit_creature` returns `{name, boxes, tweaks[4]}` from a **single** completion
|
| 63 |
+
(combined `EDIT_SCHEMA`): the model edits the geometry *and* proposes the next
|
| 64 |
+
four hint bubbles in one shot. This removed the separate `suggest_tweaks` GPU
|
| 65 |
+
call that fired after every tweak (the old `_side_hints` path) β each bubble tap
|
| 66 |
+
or typed tweak previously held the GPU for two back-to-back ZeroGPU allocations,
|
| 67 |
+
the likely cause of the "Running on ZERO" indicator sticking at 1 / never
|
| 68 |
+
clearing. The fallback path (and any short hint list) tops up to four from the
|
| 69 |
+
deterministic `suggest_tweaks_fallback` pool, so no second GPU call ever runs.
|
| 70 |
+
Trade-off: the small model now does two tasks in one grammar-constrained JSON; if
|
| 71 |
+
geometry or hint quality regresses, split `EDIT_SCHEMA` back into `BOX_SCHEMA` +
|
| 72 |
+
`TWEAK_SCHEMA` and re-add the second call. `suggest_tweaks` is retained (tests,
|
| 73 |
+
and as the standalone fallback source) but no longer called per tweak by the UI.
|
| 74 |
+
|
| 75 |
### Schema enforcement (response_format) β load-bearing
|
| 76 |
|
| 77 |
`_blocking_completion` constrains output with
|
|
@@ -2,6 +2,23 @@
|
|
| 2 |
|
| 3 |
## 2026-06-15
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
### voice-input-asr: documented the English-only exception for fallback.py CJK match tables
|
| 6 |
|
| 7 |
- `voice-input-asr.md`: added "English-only exception: keep the CJK match tables
|
|
|
|
| 2 |
|
| 3 |
## 2026-06-15
|
| 4 |
|
| 5 |
+
### voxel-rendering + deployment-strategy: Figma canvas (pan + pooled live 3D) and one-call bench tweak
|
| 6 |
+
|
| 7 |
+
- `voxel-rendering.md`: added "Lab canvas: pan + viewport-pooled live 3D" β tree
|
| 8 |
+
nodes are live rotating 3D (not thumbnails), kept under the WebGL context cap by
|
| 9 |
+
`cullCanvasNodes()` (boot on-view / teardown off-view; verified disposeβreboot).
|
| 10 |
+
Documented the `setPointerCapture`-swallows-clicks trap (use window move/up +
|
| 11 |
+
`__buddyPanned`), the cached pan offset + dot-grid background-position shift, and
|
| 12 |
+
JS bench-Γ centering. Updated the context-cap failure mode (Compendium = shared
|
| 13 |
+
thumbnail renderer; Lab = pooled live), added the 0Γ0 size-guard fix (blank
|
| 14 |
+
Splice Bench root) and the culled-stage disposal path, and retired the obsolete
|
| 15 |
+
inspect-modal "purple chicken" bullet (modal removed; nodes are live now).
|
| 16 |
+
- `deployment-strategy.md`: added "A bench tweak is ONE GPU call, not two" β
|
| 17 |
+
`edit_creature` now returns `{name, boxes, tweaks[4]}` from a single completion
|
| 18 |
+
(combined `EDIT_SCHEMA`), removing the per-tweak `suggest_tweaks` GPU call (the
|
| 19 |
+
likely "Running on ZERO" stuck-indicator cause). Notes the fallback top-up and
|
| 20 |
+
the revert path.
|
| 21 |
+
|
| 22 |
### voice-input-asr: documented the English-only exception for fallback.py CJK match tables
|
| 23 |
|
| 24 |
- `voice-input-asr.md`: added "English-only exception: keep the CJK match tables
|
|
@@ -122,14 +122,15 @@ system does; the AI only sets the composition.**
|
|
| 122 |
Detection: watch a tall/long archetype (horse, hippo, dog) through a full
|
| 123 |
turn; if it swings toward an edge at some angles instead of rotating in place,
|
| 124 |
the pivot is at the origin, not the center.
|
| 125 |
-
- **WebGL context cap (~16)
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
`preserveDrawingBuffer:true`
|
| 131 |
-
|
| 132 |
-
|
|
|
|
| 133 |
- **Coincident cubes Z-fight (fixed 2026-06-14).** `voxelsFromGenome` layers
|
| 134 |
body + archetype features + genome parts by pushing cubes, and several land on
|
| 135 |
the *same* cell β e.g. the `earsnout` snout writes a cube at `(0, hyB, frontZ)`
|
|
@@ -140,20 +141,55 @@ system does; the AI only sets the composition.**
|
|
| 140 |
layer over features) β coincident geometry is removed, not merely offset. Do
|
| 141 |
not "fix" this with polygonOffset (one shared InstancedMesh material β no
|
| 142 |
per-instance offset) or position jitter (leaves overlapping edges).
|
| 143 |
-
- **
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
`
|
| 153 |
-
|
| 154 |
-
guards on `root.isConnected`; when a stage leaves the DOM it calls
|
| 155 |
-
`renderer.dispose()` + `forceContextLoss()` and stops the RAF. Skipping this
|
| 156 |
leaks a context per re-render and eventually trips the cap above.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
|
| 158 |
## Offline export & the landing backdrop
|
| 159 |
|
|
|
|
| 122 |
Detection: watch a tall/long archetype (horse, hippo, dog) through a full
|
| 123 |
turn; if it swings toward an edge at some angles instead of rotating in place,
|
| 124 |
the pivot is at the origin, not the center.
|
| 125 |
+
- **WebGL context cap (~16).** Each live stage holds a `WebGLRenderer`/context;
|
| 126 |
+
the browser drops the oldest when you exceed ~16 (and the mounted bench/reveal
|
| 127 |
+
stages eat into that), blanking it with a "Too many active WebGL contexts"
|
| 128 |
+
warning. Two strategies coexist: the **Compendium grid** still uses static
|
| 129 |
+
thumbnails (`voxel_html(thumb=True)` β one shared offscreen
|
| 130 |
+
`window.__voxelThumbRenderer`, `preserveDrawingBuffer:true`, snapshot to `<img>`)
|
| 131 |
+
so its 50+ cards cost one context total; the **Lab pedigree canvas** uses live
|
| 132 |
+
3D nodes that are viewport-pooled (see "Lab canvas" below). See
|
| 133 |
+
[gradio-embed-integration](gradio-embed-integration.md).
|
| 134 |
- **Coincident cubes Z-fight (fixed 2026-06-14).** `voxelsFromGenome` layers
|
| 135 |
body + archetype features + genome parts by pushing cubes, and several land on
|
| 136 |
the *same* cell β e.g. the `earsnout` snout writes a cube at `(0, hyB, frontZ)`
|
|
|
|
| 141 |
layer over features) β coincident geometry is removed, not merely offset. Do
|
| 142 |
not "fix" this with polygonOffset (one shared InstancedMesh material β no
|
| 143 |
per-instance offset) or position jitter (leaves overlapping edges).
|
| 144 |
+
- **Any new render entry point must thread `boxes`.** `setupVoxel` picks geometry
|
| 145 |
+
with `(cfg.boxes && cfg.boxes.length) ? voxelsFromBoxes : voxelsFromGenome`, so a
|
| 146 |
+
freeform creature whose cfg omits `boxes` falls through to the **archetype**
|
| 147 |
+
voxelizer and renders as a generic palette-coloured chick (the historical "purple
|
| 148 |
+
chicken" bug, when the old inspect modal built its cfg from the genome dict
|
| 149 |
+
alone). `voxel_html` threads it via `eff_boxes`; replicate that in any hand-built
|
| 150 |
+
cfg. (The inspect modal itself was removed once tree nodes became live 3D.)
|
| 151 |
+
- **Detached / culled live stages must dispose, or they leak a context.** The RAF
|
| 152 |
+
loop stops + disposes (`renderer.dispose()` + `forceContextLoss()`, remove the
|
| 153 |
+
canvas, clear `voxelInit`) when `!root.isConnected` OR when
|
| 154 |
+
`root.dataset.voxelTeardown === "1"` (set by the canvas pool). Skipping disposal
|
|
|
|
|
|
|
| 155 |
leaks a context per re-render and eventually trips the cap above.
|
| 156 |
+
- **Boot only after the stage has layout (fixed 2026-06-15 β blank Splice Bench).**
|
| 157 |
+
`setupVoxel` used to mark `voxelInit=1` immediately then fall back to a 320Γ300
|
| 158 |
+
size. On the Space, Gradio reveals a hidden column and writes its HTML in the
|
| 159 |
+
same tick, so the stage is 0Γ0 for a frame; booting then locked the renderer to
|
| 160 |
+
the fallback size and the bench rendered blank until a manual refresh. Fix: bail
|
| 161 |
+
**without** marking inited when `stage.clientWidth/Height` is 0, so the 400ms
|
| 162 |
+
poll retries once layout settles.
|
| 163 |
+
|
| 164 |
+
## Lab canvas: pan + viewport-pooled live 3D
|
| 165 |
+
|
| 166 |
+
The Lab pedigree is a Figma / Google-Stitch-style canvas (`lineage_html` β
|
| 167 |
+
`.buddy-canvas` clipping viewport β `.buddy-canvas-pan` transformed layer β
|
| 168 |
+
`.buddy-tree`). Tree nodes are **live rotating 3D** (`voxel_html(build=False)`),
|
| 169 |
+
not thumbnails.
|
| 170 |
+
|
| 171 |
+
- **Viewport pooling keeps live nodes under the context cap.** A 400ms poll (and
|
| 172 |
+
the pan `pointermove`) runs `cullCanvasNodes()`: a node whose stage rect is
|
| 173 |
+
within a margin of the `.buddy-canvas` rect gets `setupVoxel`; one panned outside
|
| 174 |
+
gets `root.dataset.voxelTeardown="1"` and its RAF loop disposes the context.
|
| 175 |
+
Verified: pan the only nodes off-canvas β live canvas count drops to 0; pan back
|
| 176 |
+
β they re-boot. So "everything visible is live 3D" without ever exceeding ~16
|
| 177 |
+
contexts as the tree grows. `scanAndSetupVoxels` and the MutationObserver
|
| 178 |
+
**skip** any root inside `.buddy-canvas` (`r.closest('.buddy-canvas')`) so only
|
| 179 |
+
the pool manages those.
|
| 180 |
+
- **Left-drag pan β do NOT `setPointerCapture`.** Capturing the pointer on the
|
| 181 |
+
canvas swallows the node's `click`, so tapping a creature never selects it. Use
|
| 182 |
+
`pointerdown` on the canvas + `pointermove`/`pointerup` on `window`, and a
|
| 183 |
+
movement-threshold flag `window.__buddyPanned` that suppresses the node-select
|
| 184 |
+
click only when an actual drag happened (a clean tap selects). The pan offset is
|
| 185 |
+
cached on `window.__buddyCanvasOff` so a tree re-render (which replaces the
|
| 186 |
+
canvas HTML) restores the player's position instead of snapping to centre. The
|
| 187 |
+
dot grid is a tiled `background-image` on `.buddy-canvas` whose
|
| 188 |
+
`background-position` is shifted by the same offset β reads as an infinite canvas.
|
| 189 |
+
- **Bench Γ centering is JS, not a magic margin.** `centerBenchX()` (in the poll)
|
| 190 |
+
measures the scene rect and nudges `.buddy-bench-x` `margin-top` so its centre
|
| 191 |
+
matches the scene's β self-correcting across font/scene-size changes (the old
|
| 192 |
+
fixed `margin-top` drifted when the root font size changed).
|
| 193 |
|
| 194 |
## Offline export & the landing backdrop
|
| 195 |
|