Spaces:
Running on Zero
Running on Zero
kwabs22 Claude Opus 5.5 commited on
Commit Β·
e0f9b43
1
Parent(s): f9a8763
Weld before decimating so stalled meshes reach their budget; stop repeated ladder rungs
Browse filesQA round 2 found decimation flooring far above target (4,893 faces in a
400-face page; ladder rungs 1000/250/60 all 1,141 faces). Asset files split
vertices at UV and normal seams, so edges aren't shared and the decimator
stalls. decimate now welds first and re-welds between passes while that
still helps. A split-seam test mesh that stalled at 3,840 now reaches 400.
The ladder labels rungs with their real face count and stops descending
when a rung doesn't reduce. Inline meshes in the page state faces, budget
and floor, so an over-budget mesh is visible instead of silent.
Also archives QA verification round 2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- SPEC.md +5 -0
- feedback/vli-qa-feedback-2026-10-30.md +19 -1
- vli/html.py +2 -1
- vli/simplify.py +30 -6
SPEC.md
CHANGED
|
@@ -106,6 +106,9 @@ that exists but maps to the wrong thing shows up before submitting.
|
|
| 106 |
|
| 107 |
Ladder rungs: original β 20k / 5k / 1k / 250 / 60 faces β convex hull β oriented
|
| 108 |
box. Where the object stops reading as its word is judged in the discussion.
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
### Mini HTML
|
| 111 |
|
|
@@ -114,6 +117,8 @@ data object holding the sentence, the reading (word β commits), relations,
|
|
| 114 |
unresolved questions, sun, ground, and each object's position, yaw and colour.
|
| 115 |
Each object also has either inline low-poly geometry (β€400 faces in a scene,
|
| 116 |
rungs β€1000 faces in a ladder) or a primitive spec. Vertices are rounded to cm.
|
|
|
|
|
|
|
| 117 |
The geometry is inline, so a saved page works after Gradio's temporary file URLs expire.
|
| 118 |
|
| 119 |
**Geometry is for the handoff, not the loop.** Even at 400 faces a page costs
|
|
|
|
| 106 |
|
| 107 |
Ladder rungs: original β 20k / 5k / 1k / 250 / 60 faces β convex hull β oriented
|
| 108 |
box. Where the object stops reading as its word is judged in the discussion.
|
| 109 |
+
Decimation welds split vertices (UV and normal seams) between passes. Rungs are
|
| 110 |
+
labelled with the faces they actually have, and the ladder stops descending when
|
| 111 |
+
a rung doesn't reduce, so a stalled mesh never shows as several identical rungs.
|
| 112 |
|
| 113 |
### Mini HTML
|
| 114 |
|
|
|
|
| 117 |
unresolved questions, sun, ground, and each object's position, yaw and colour.
|
| 118 |
Each object also has either inline low-poly geometry (β€400 faces in a scene,
|
| 119 |
rungs β€1000 faces in a ladder) or a primitive spec. Vertices are rounded to cm.
|
| 120 |
+
Each inline mesh states `faces`, `budget` and `floor`: `floor: true` means the mesh
|
| 121 |
+
wouldn't decimate down to the budget, so the page carries more than asked.
|
| 122 |
The geometry is inline, so a saved page works after Gradio's temporary file URLs expire.
|
| 123 |
|
| 124 |
**Geometry is for the handoff, not the loop.** Even at 400 faces a page costs
|
feedback/vli-qa-feedback-2026-10-30.md
CHANGED
|
@@ -105,4 +105,22 @@ Seed-0 baseline reproduced bit-identically from the first session: uid `4459b90d
|
|
| 105 |
5. `reject n1` (primitive tier) β `Error: no usable generation asset for 'cat' (generation tier is not wired up yet)` β οΈ *(documented gap, now confirmed end-to-end)*
|
| 106 |
6. `check_words "the cat sat and the lamp glowed"` β `ok: true`, lamp `available_in {simready: 1, objaverse: 79}`, `maps_to: "desk_lamp"` β
|
| 107 |
|
| 108 |
-
**Suggested follow-ups for the agent's own QA:** `simplify_item` end-to-end (ladder rungs + `LADDER_MAX_FACES` skip), NC/ND license skip recording with a live `decision.skipped` entry, `use` back-swap idempotence after a skip shifted indices, and `check_words` on a low-memory/fallback build to observe dictionary drift.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
5. `reject n1` (primitive tier) β `Error: no usable generation asset for 'cat' (generation tier is not wired up yet)` β οΈ *(documented gap, now confirmed end-to-end)*
|
| 106 |
6. `check_words "the cat sat and the lamp glowed"` β `ok: true`, lamp `available_in {simready: 1, objaverse: 79}`, `maps_to: "desk_lamp"` β
|
| 107 |
|
| 108 |
+
**Suggested follow-ups for the agent's own QA:** `simplify_item` end-to-end (ladder rungs + `LADDER_MAX_FACES` skip), NC/ND license skip recording with a live `decision.skipped` entry, `use` back-swap idempotence after a skip shifted indices, and `check_words` on a low-memory/fallback build to observe dictionary drift.
|
| 109 |
+
---
|
| 110 |
+
|
| 111 |
+
## 5. Verification round 2 (2026-10-30, after commits 447d1d2 / f9a8763) β deployed Space, live MCP
|
| 112 |
+
|
| 113 |
+
| Fix | Result |
|
| 114 |
+
|---|---|
|
| 115 |
+
| P0-0 html modes | β default `url` = file link + empty text (verified on `translate_to_scene` and `refine_scene`); `inline` returns the page text. Iterate-loop cost dropped from ~60k to ~2-3k tokens/call β measured on this session itself. `skip` code-verified, not yet exercised live |
|
| 116 |
+
| P0-0 rounding | β pages show 1 cm (2-decimal) coords |
|
| 117 |
+
| P1-1 reroll | β walks the pool by next-alternative rotation; bonus: exercised the license-skip path live β `1f32bc` skipped `by-nc` and recorded in `decision.skipped`, landed on `138377` (`by-sa`), old choice parked at pool end, q0 labels refreshed |
|
| 118 |
+
| P1-2 uid labels | β `cat [4459b9]`, `cat [138377]`, β¦ |
|
| 119 |
+
| P1-3 primitive reject | β clean pre-empted error: `'cat' has no tier left after primitive: generation is not wired up yet. Use shape or height_m instead, or rephrase.` |
|
| 120 |
+
| P1-4 example sentence | β `the cat sat and the lamp glowed` runs with no primitives; lamp via the **SimReady tier working live for the first time** (ForgeCAD/simready-assets `desk_lamp`, metric height 0.472 m from asset, CC-BY-4.0). Note: the SimReady index now builds from `ForgeCAD/β¦`, not `nvidia/β¦` β P2-7 drift applies to it |
|
| 121 |
+
| P2-5 face budget | **β partially β decimation floors, it doesn't fail.** Ladder proved it: 135,934 β 19,999 β 5,000 β **1,141**, then 250/60 rungs return 1,141 (identical GLBs). Quadric decimation stalls per-mesh when it runs out of weldable structure (`watertight: false`, ~1.1 verts/tri in the stalled output); 4459b9's floor is 4,893, so the page still ships 4,893 despite budget 400 |
|
| 122 |
+
|
| 123 |
+
**Round-3 items:**
|
| 124 |
+
1. **Decimate floor:** weld between passes β `out.merge_vertices()` then re-run `simplify_quadric_decimation` in a loop until the target or no further progress (Objaverse GLBs split vertices; the decimator starves on unshared edges). If a floor persists, surface it in the page (`"faces": n, "budget": 400, "floor": true`) instead of silently exceeding.
|
| 125 |
+
2. **Ladder duplicate rungs:** the rung guard compares against the requested level, so a stalled decimation still appends lower rungs (1000/250/60 all rendered as the same 1,141-face mesh). Stop descending when a rung fails to actually reduce.
|
| 126 |
+
3. Still open: `use` back-swap after a skip shifts indices; live `skip`-mode; the UI visual click-test (mode radio + preview-from-file β code verified, needs eyes).
|
vli/html.py
CHANGED
|
@@ -28,8 +28,9 @@ def _sky_hex(sun):
|
|
| 28 |
|
| 29 |
|
| 30 |
def _geometry(mesh, budget):
|
|
|
|
| 31 |
m = decimate(mesh, budget) if len(mesh.faces) > budget else mesh
|
| 32 |
-
return {"faces": int(len(m.faces)),
|
| 33 |
"p": np.round(m.vertices, 2).ravel().tolist(), # cm: plenty at low poly, fewer tokens
|
| 34 |
"i": m.faces.ravel().astype(int).tolist()}
|
| 35 |
|
|
|
|
| 28 |
|
| 29 |
|
| 30 |
def _geometry(mesh, budget):
|
| 31 |
+
"""Inline mesh; "floor" marks a mesh that wouldn't decimate down to the budget."""
|
| 32 |
m = decimate(mesh, budget) if len(mesh.faces) > budget else mesh
|
| 33 |
+
return {"faces": int(len(m.faces)), "budget": budget, "floor": bool(len(m.faces) > budget),
|
| 34 |
"p": np.round(m.vertices, 2).ravel().tolist(), # cm: plenty at low poly, fewer tokens
|
| 35 |
"i": m.faces.ravel().astype(int).tolist()}
|
| 36 |
|
vli/simplify.py
CHANGED
|
@@ -22,12 +22,31 @@ def base_color(mesh):
|
|
| 22 |
return np.array([180, 180, 180, 255], dtype=np.uint8)
|
| 23 |
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
def decimate(mesh, faces):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
color = base_color(mesh)
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
| 31 |
out.visual = trimesh.visual.ColorVisuals(out, face_colors=np.tile(color, (len(out.faces), 1)))
|
| 32 |
return out
|
| 33 |
|
|
@@ -41,8 +60,13 @@ def ladder(mesh):
|
|
| 41 |
color = base_color(mesh)
|
| 42 |
rungs = [("original", mesh)]
|
| 43 |
for n in FACE_LEVELS:
|
| 44 |
-
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
rungs.append(("convex hull", _recolor(mesh.convex_hull, color)))
|
| 47 |
box = mesh.bounding_box_oriented
|
| 48 |
rungs.append(("bounding box", _recolor(trimesh.Trimesh(box.vertices, box.faces), color)))
|
|
|
|
| 22 |
return np.array([180, 180, 180, 255], dtype=np.uint8)
|
| 23 |
|
| 24 |
|
| 25 |
+
def _welded(mesh):
|
| 26 |
+
"""Geometry only, with coincident vertices merged, in fresh writable arrays.
|
| 27 |
+
|
| 28 |
+
Asset files split vertices at UV and normal seams; the decimator can't collapse
|
| 29 |
+
edges that aren't shared, so an unwelded mesh stalls far above its target.
|
| 30 |
+
fast-simplification also rejects read-only arrays (cached views), hence the copies.
|
| 31 |
+
"""
|
| 32 |
+
m = trimesh.Trimesh(np.array(mesh.vertices, dtype=np.float64), np.array(mesh.faces, dtype=np.int64))
|
| 33 |
+
return trimesh.Trimesh(np.array(m.vertices, dtype=np.float64), np.array(m.faces, dtype=np.int64),
|
| 34 |
+
process=False)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
def decimate(mesh, faces):
|
| 38 |
+
"""Reduce toward `faces`, re-welding between passes while that still helps.
|
| 39 |
+
|
| 40 |
+
The result can stay above `faces` when the mesh has no more structure to collapse
|
| 41 |
+
(many disconnected parts): callers compare len(result.faces) with what they asked.
|
| 42 |
+
"""
|
| 43 |
color = base_color(mesh)
|
| 44 |
+
out = _welded(mesh)
|
| 45 |
+
while len(out.faces) > faces:
|
| 46 |
+
nxt = _welded(out.simplify_quadric_decimation(face_count=faces))
|
| 47 |
+
if len(nxt.faces) > 0.95 * len(out.faces):
|
| 48 |
+
break
|
| 49 |
+
out = nxt
|
| 50 |
out.visual = trimesh.visual.ColorVisuals(out, face_colors=np.tile(color, (len(out.faces), 1)))
|
| 51 |
return out
|
| 52 |
|
|
|
|
| 60 |
color = base_color(mesh)
|
| 61 |
rungs = [("original", mesh)]
|
| 62 |
for n in FACE_LEVELS:
|
| 63 |
+
prev = len(rungs[-1][1].faces)
|
| 64 |
+
if n >= 0.8 * prev:
|
| 65 |
+
continue
|
| 66 |
+
rung = decimate(mesh, n)
|
| 67 |
+
if len(rung.faces) > 0.8 * prev:
|
| 68 |
+
break # decimation stalled: lower levels would repeat this mesh
|
| 69 |
+
rungs.append((f"{len(rung.faces)} faces", rung))
|
| 70 |
rungs.append(("convex hull", _recolor(mesh.convex_hull, color)))
|
| 71 |
box = mesh.bounding_box_oriented
|
| 72 |
rungs.append(("bounding box", _recolor(trimesh.Trimesh(box.vertices, box.faces), color)))
|