kwabs22 Claude Opus 5.5 commited on
Commit
b546563
Β·
1 Parent(s): 447d1d2

Archive QA feedback under feedback/

Browse files

Keeps the 2026-10-30 QA report in the repo for later reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

feedback/vli-qa-feedback-2026-10-30.md ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # VisualLearningIdea β€” QA feedback for the coding agent
2
+
3
+ **Session date:** 2026-10-30 Β· **Method:** full contract re-verification of the MCP tools via live calls, plus a read of repo HEAD (`app.py`, `SPEC.md`, `vli/grammar.py`, `vli/lexicon.py`, `vli/assets.py`, `vli/scene.py`, `vli/pipeline.py`, `vli/html.py`, `vli/simplify.py`).
4
+
5
+ **TL;DR:** The `refine_scene` contract is now **fully verified** β€” including the last open cell (reject on an Objaverse-tier item drops cleanly to a primitive). Everything previously tested still behaves as documented. The feedback below is 4 priority items, 3 robustness/polish items, and one build-parity question. The single most impactful fix is #1 (reroll determinism); the cheapest high-value fix is #2 (q0 labels, one line).
6
+
7
+ ---
8
+
9
+ ## 1. Verified contract (complete as of today)
10
+
11
+ | Change | Status | Evidence |
12
+ |---|---|---|
13
+ | `freeze: true/false` | βœ… | prior session; ledger round-trips `frozen` flag |
14
+ | `use: n` (0-indexed, swap) | βœ… | prior session |
15
+ | `reroll` swaps with the pool | βœ… | reproduced today: seed 0 β†’ chosen ↔ `alternatives[6]`, `4459b90d… ↔ b86b2f4d…`, pool unchanged |
16
+ | `reject` (simready β†’ objaverse β†’ primitive) | βœ… | prior session |
17
+ | **reject on Objaverse item β†’ primitive** | βœ… **new** | today: `rejected_tiers: ["objaverse"]`, chosen = box, height re-derived to 0.3 m (`source: default`), renders clean |
18
+ | `shape` (primitive) | βœ… | prior session |
19
+ | `height_m` | βœ… | prior session; reject correctly resets only non-user heights |
20
+ | seed re-chooses configuration | βœ… | seed 0 and seed 1 re-verified today; placement details in item 6 |
21
+ | reject on primitive-tier β†’ generation | ⚠️ reachable dead end | today: readable error, no fallback (item 3) |
22
+
23
+ Seed-0 baseline reproduced bit-identically from the first session: uid `4459b90d0e87486b96739feb2a40e0ee` (4893 faces, license `by`), yaw βˆ’82.87678304500669Β°, sun 26.638940957447787Β°/βˆ’145.04170934144128Β°, sky `#b8aaa5`, cat z-offset 0.013124391962400764.
24
+
25
+ ---
26
+
27
+ ## 2. Priority items
28
+
29
+ ### P1-1 Β· Reroll at a fixed seed is a toggle, not a sampler
30
+
31
+ - **Evidence:** two sessions days apart, reroll at seed 0 always swaps chosen with `alternatives[6]` (`4459b90d ↔ b86b2f4d`, same z-offset 0.0215100129063485 both times). Seed 1 always picks slot 3 (`7c3383a2…`). Same input β‡’ same pick, forever.
32
+ - **Cause:** `pipeline.apply_changes` builds `rng = np.random.default_rng(seed)` fresh on every call, so a fixed seed always draws the same `k = rng.integers(len(alternatives))`.
33
+ - **Consequence:** to see a *different* asset the caller must change the seed β€” but the seed also re-rolls placement and sun (verified: seed 1 moved the cat to yaw 162.17Β° and the sun to 30.77Β°/134.59Β°, sky `#adacb3`). Asset exploration and configuration exploration are coupled through one number, which the docstring presents as one knob.
34
+ - **Suggested fix (pick one):**
35
+ 1. derive the pick from item state so it walks the pool: `k = int(chosen["uid"][:8], 16) % len(alternatives)` β€” deterministic per state, changes as swaps change state;
36
+ 2. carry a counter in the ledger (e.g. `ledger["revision"] += 1` inside `apply_changes`; seed the changes-rng from `(seed, revision)`);
37
+ 3. at minimum, document the toggle behaviour in the `refine_scene` docstring so MCP callers know to vary the seed (and that this also moves the scene).
38
+
39
+ ### P1-2 Β· `q0` "which cat?" is unanswerable β€” 8 identical labels
40
+
41
+ - **Evidence:** `q0.alternatives = ["cat", "cat", … Γ—8]` on every ledger; unchanged after rerolls; `default_chosen: "cat"`.
42
+ - **Cause:** `pipeline.settle_all` fills question labels via `candidate_label()`, which returns `cand.get("category")` for Objaverse candidates; the uid (present in every candidate dict) is never surfaced.
43
+ - **Impact:** SPEC says determiners open a "which?" question for the discussion to arbitrate; as shipped the question carries zero information β€” arbitration is only possible by reading `n1.decision.chosen.uid`.
44
+ - **Suggested fix (one line in `settle_all`):** for `unresolved` alternatives only, disambiguate, e.g. `f"{label} [{uid[:6]}]"` β†’ `cat [4459b9]`, `cat [b86b2f]`… Keep `use` positional β€” labels are display-only. Optionally append loaded face count later. (Bonus: `check_words`' `maps_to` already helps availability; consider adding the uid there too.)
45
+
46
+ ### P1-3 Β· Generation tier is a user-reachable dead end
47
+
48
+ - **Evidence (today, end-to-end):** `refine_scene` with `[{"id":"n1","reject":true}]` on a primitive-tier item β†’ `Error: no usable generation asset for 'cat' (generation tier is not wired up yet)`.
49
+ - **Good:** this is a readable `gr.Error` (via `assets.load_generation` β†’ `UnusableAsset` β†’ `assets.settle` β†’ `app._fail`), *not* a raw `NotImplementedError` traceback; `app.generate_mesh` (the ZeroGPU stub) is never called, as documented.
50
+ - **Bad:** the `refine_scene` docstring advertises `reject` β†’ generation, so a user/model can hit this dead end, and the mutated ledger is discarded with the error (the caller must resend their previous ledger to recover).
51
+ - **Suggested fix:** pre-empt in `apply_changes`/`next_tier` β€” when the *next* tier would be `generation`, raise a clean `ValueError` **before mutating** the decision, e.g. `"'cat' has no tier left after primitive; generation is not wired up β€” use shape/height instead"`. Keep the stub itself as is.
52
+
53
+ ### P1-4 Β· The canonical example sentence errors as-is
54
+
55
+ - **Evidence:** `translate_to_scene("the cat sat and the sun shone")` β†’ `Not available (no asset): sun … map each to a primitive`. It only works with `primitives_json={"sun":"sphere"}`. This sentence is the example in the `check_words` *and* `translate_to_scene` docstrings and the UI placeholder, so a first-time MCP caller (e.g. HuggingChat) hits an error on the advertised happy path.
56
+ - **Cause:** intended, not a regression β€” the `simready_phrases` sun-visor guard correctly stopped indexing a lone "sun", and LVIS provides no usable sun category. The code is right; the docs lag.
57
+ - **Suggested fix:** swap the canonical example to a sentence that needs no primitives. Verified today: **"the cat sat and the lamp glowed"** β†’ `ok: true` (lamp: 1 SimReady + 79 Objaverse, `maps_to: "desk_lamp"`), and a desk lamp as the light source reads nicely. Alternatively keep the cat/sun sentence but append `… pass primitives_json={"sun":"sphere"}` wherever it appears.
58
+
59
+ ---
60
+
61
+ ## 3. Robustness / polish
62
+
63
+ ### P2-5 Β· Build parity: scene-page face budget is not in effect
64
+
65
+ - **Evidence:** today's scene page for the 4893-face cat inlined **4893 faces** in its `mesh` blob. Repo `vli/html.py` sets `SCENE_FACE_BUDGET = 400` and `_geometry()` unconditionally decimates above budget (`simplify.decimate` β†’ `simplify_quadric_decimation`); with repo HEAD, 4893 inline is impossible. Meanwhile `b86b2f4d` shipped exactly 400 β€” consistent with its native count (no decimation path running).
66
+ - **Ask:** confirm the running build matches repo HEAD (rebuild/restart if stale). If it *does* match, debug the `_geometry` budget path. Untested related path: `ladder_page`'s `LADDER_MAX_FACES = 1000` skip.
67
+
68
+ ### P2-6 Β· Placement couples to asset bounds β€” document it
69
+
70
+ - **Evidence:** same seed 0, same yaw/sun/sky, but the cat's z-offset differs per asset: 0.0131 (`4459b90d`), 0.0215 (`b86b2f4d`), 0.0205 (box). Cause: `scene.configure` draws the same uniforms for the same seed, but multiplies the row z-jitter by the mesh's half-width, and the sun position scales with reach computed from widths. So "same seed = same scene" only holds while assets don't change (exactly what confused the previous session's "seed restored, but not bit-identically").
71
+ - **Ask:** either make the row z-jitter fixed in metres (so small objects don't wiggle proportionally to their own width), or add one docstring line: *placement/sun are seeded but scale with each asset's bounds; swapping an asset shifts offsets*.
72
+
73
+ ### P2-7 Β· Vocabulary can drift silently between rebuilds
74
+
75
+ - `lexicon._simready_packages()` calls the unauthenticated GitHub tree API (60 req/h, shared Space IP) and silently falls back to `SIMREADY_FALLBACK` (only `nvidia/simready-assets`) on failure; `_cached_json` is per-container. The word list β€” the product's core promise ("the UI offers only available words") β€” can change without a code change.
76
+ - **Ask:** make drift visible and/or impossible: commit the built package index (or serve it from a pinned dataset revision), and/or expose dictionary stats in `check_words` (e.g. total words, simready package count) so callers can detect a reduced dictionary.
77
+
78
+ ### P3-8 Β· Candidate pool capped at 9 of 87
79
+
80
+ - `_object_decision` and `next_tier` take `cands[1:9]` β†’ chosen + 8 alternatives, while "cat" has 87 Objaverse candidates (lamp: 79). `use`/`reroll` can never reach the rest, and a random 9 includes junk (one of the nine was a 400-face lump that reads as a rock; another, `7c3383a2`, is excellent β€” the pool has both).
81
+ - **Ask:** raise the cap for large categories, or accept `{"id":"n1","use_uid":"…"}` / an offset parameter; at minimum document the cap. (Fixing #2's labels makes the 9 visible; wider access makes them matter.)
82
+
83
+ ### P3-9 Β· Small DX nits
84
+
85
+ - `use` with an out-of-range index (or on empty alternatives) raises a raw `IndexError` β†’ unhelpful message. Wrap: `"n1 has no alternative at index k (N alternatives)"`.
86
+ - Fallback primitives render neutral grey (`base_color` default `#b4b4b4`): the rejected cat-box is colourless in an otherwise tinted scene. Optional: tint primitives deterministically per word (hash β†’ hue) so primitive-tier scenes read better.
87
+
88
+ ---
89
+
90
+ ## 4. Session probe log (exact)
91
+
92
+ 1. `translate "the cat sat and the sun shone", {"sun":"sphere"}, seed 0` β†’ n1 = `4459b90d…` (4893 faces, license `by`), yaw βˆ’82.8768Β°, sun 26.6389Β°/βˆ’145.0417Β°, sky `#b8aaa5`, z 0.01312 β€” matches the original session bit-for-bit βœ…
93
+ 2. `reroll`, seed 0 β†’ n1 = `b86b2f4d…` (400 faces, pale olive), z 0.02151, slot-6 swap β€” identical to previous session βœ…
94
+ 3. `reject n1` β†’ primitive box, `rejected_tiers: ["objaverse"]`, height 0.3 (default), q0 β†’ `default_chosen: "box"`, `alternatives: []` βœ… *(closes the last untested cell)*
95
+ 4. `reroll`, seed 1 β†’ n1 = `7c3383a2…` (399 faces, reads clearly as a cat), yaw 162.1669Β°, sun 30.7664Β°/134.5948Β°, sky `#adacb3` βœ… *(also demonstrates placement/asset seed coupling)*
96
+ 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)*
97
+ 6. `check_words "the cat sat and the lamp glowed"` β†’ `ok: true`, lamp `available_in {simready: 1, objaverse: 79}`, `maps_to: "desk_lamp"` βœ…
98
+
99
+ **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.