arkai2025 Claude Opus 4.8 (1M context) commited on
Commit
2ae67df
Β·
1 Parent(s): d2f9b20

test+docs: repair edit_creature 4-tuple test; wiki for canvas + one-call tweak

Browse files

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 (1M context) <noreply@anthropic.com>

tests/test_freeform_edit.py CHANGED
@@ -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
 
wiki/deployment-strategy.md CHANGED
@@ -1,7 +1,7 @@
1
  ---
2
  title: Deployment Strategy
3
  tags: [deployment, hugging-face, llama-cpp, zerogpu, architecture]
4
- last_updated: 2026-06-14
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
wiki/log.md CHANGED
@@ -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
wiki/voxel-rendering.md CHANGED
@@ -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) β€” thumbnails must share one renderer.** Each live
126
- stage holds a `WebGLRenderer`/context; the family tree grows one node per
127
- creature, so a live context per thumbnail would blow the browser cap and blank
128
- the oldest stages. Thumbnails (`voxel_html(thumb=True)`) render through one
129
- shared offscreen renderer (`window.__voxelThumbRenderer`,
130
- `preserveDrawingBuffer:true`) and snapshot to an `<img>` β€” flat context count
131
- regardless of tree size. Only the two ancestor stages + the child stage keep a
132
- live context. See [gradio-embed-integration](gradio-embed-integration.md).
 
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
- - **Inspect modal must pass `boxes` through, or freeform creatures render as a
144
- chick (fixed 2026-06-15).** `buildModalStage` (the πŸ” zoom overlay) built its
145
- voxel config from the genome dict only. `setupVoxel` picks the geometry with
146
- `(cfg.boxes && cfg.boxes.length) ? voxelsFromBoxes : voxelsFromGenome`, so with
147
- no `boxes` key a freeform creature fell through to the **archetype** voxelizer
148
- and rendered as a generic palette-coloured chick (the "purple chicken" bug) β€”
149
- even though its tree thumbnail (which does pass `boxes`) was correct. Fix: pass
150
- `boxes: (genome && genome.boxes) || null` in the modal cfg. Any new render
151
- entry point must thread `boxes` the same way (`voxel_html` already does via
152
- `eff_boxes`).
153
- - **Detached live stages leak a context if not disposed.** The animation loop
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