WolfDavid's picture
docs(01-05): record the spike verdict - CONFIRMED, inline transport - and close the plan
199a6c3
|
Raw
History Blame
11.5 kB

Resume here β€” Japanese Learning Avatar

βœ… 2026-09-05 (latest) β€” 01-05 CLOSED: spike CONFIRMED, AVATAR_TRANSPORT=inline, Space at 487b8cf

Commit 7a26764 poses the arms out of the authored T-pose in avatar/vrm-stage.js (upper arm 1.2 rad about Z toward the body, forearm a further 0.2 rad, applied once on the normalized bones at mount) and publishes armDown.{left,right} — the downward component of the world-space shoulder→elbow direction on the RAW skeleton, read after every render. Measured -0.932 both sides (= sin 1.2). Local screenshot: docs/evidence/2026-09-05-local-rest-pose.png (before: docs/evidence/2026-09-05-deployed-tpose.png).

Tests now assert armDown < -0.7 at all three layers: test_stage_standalone.py::test_arms_rest_at_sides, test_facade_parity.py::test_arms_rest_at_sides_under_both_transports (proves the field crosses the iframe postMessage boundary), and the deployed test_avatar_loop.py::test_arms_at_sides. Mutation-tested: ARM_REST_DROP = 0 fails the standalone test with armDown=0.000. Each waits for the first rendered frame via breathValue !== 0 β€” ready fires before the first frame and that frame compiles every MToon shader (>1.5 s measured headless), so a fixed sleep read boot-time zeros.

Verified: ruff clean; 82 quick-loop; full pytest tests/e2e/ 17 passed as a whole (5 deployed skipped without --space-url).

Watched, not fixed: two later full-suite runs each dropped one to three test_asr_standalone.py tests (speech_ja.wav read as rms 0.00057 β†’ rms-floor; test_tier_wasm_fallback). The ASR harness never imports vrm-stage.js, and the file passes 7/7 alone. Signature matches Chromium's --use-file-for-fake-audio-capture=…%noloop finishing the WAV before the page's getUserMedia attaches when the fleet boots slowly. Pre-existing; not caused by this change.

Deployed and verified. Pushed 41ee90e..487b8cf; Space RUNNING in 106 s. Deployed suite pytest tests/e2e/test_avatar_loop.py --space-url https://wolfdavid-japanese-learning-avatar.hf.space 5 passed, twice, incl. the new test_arms_at_sides (live armDown -0.932 both sides). Live screenshot: docs/evidence/2026-09-05-deployed-rest-pose.png β€” relaxed A-pose.

Verdict written: docs/SPIKE-VERDICT.md β€” CONFIRMED, evidence table complete, including the huggingface.co Space-page embed read from inside the cross-origin iframe (docs/evidence/2026-09-05-hub-embed.png). 01-05-SUMMARY.md closes the plan; DPLY-01 is complete. HANDOFF.json is gone β€” nothing is blocked any more.

Next: /gsd:execute-phase 1 resumes at 01-08, the plan that makes the avatar speak. One human row is still open and does not block 01-08: AVTR-01's mobile check β€” load the Space on a real phone, note device / OS / browser / renders / smooth-or-slideshow, and put it in the verdict's Mobile line.

"It does not speak" is still expected. dispatchTurn() throws until plan 01-08. window.Avatar.__debug is still a stale snapshot β€” always await window.Avatar.getDebug().

Earlier banner (2026-09-05, before the fix) β€” kept for the diagnosis

⚠ 2026-09-05 β€” READ THIS FIRST: the deployed avatar is in a T-pose

The Space is live and working at https://huggingface.co/spaces/WolfDavid/japanese-learning-avatar (revision 41ee90e, RUNNING on zero-a10g) β€” but the avatar stands with both arms straight out. Screenshot evidence: docs/evidence/2026-09-05-deployed-tpose.png.

This is NOT the two-three.js-instances bug. The screenshot shows correct materials, hair, face and toon shading, and the instruments report threeInstanceCount = 1, vrmMetaTitle = 'VRM1_Constraint_Twist_Sample', vrmSpecVersion = '1'. A plain-glTF fallback would have no vrm.meta at all.

Root cause: nothing ever applies an idle POSE. A VRM's authored rest pose is a T-pose. Phase 1's idle life only adds tiny offsets β€” spine 0.012 rad, hips 0.02 rad, head 0.05–0.08 rad, all under 1.5Β° β€” and never touches the arms. No code in this repo has ever posed them.

The fix (detail in .planning/HANDOFF.json β†’ the_fix): hand-pose leftUpperArm/rightUpperArm about Z by ~Β±1.2 rad at mount in avatar/vrm-stage.js, before the render loop starts. Zero new dependencies, ~10 lines, composes with the existing additive idle. The alternative is a .vrma clip via @pixiv/three-vrm-animation@3.5.5, which is better long-term but adds a dependency and an asset to license β€” better as a later polish plan.

Then add a deployed assertion that the arms are DOWN. AVTR-01 passed every existing check while looking visibly wrong. That is the real lesson: do not mark AVTR-01 complete.

"It does not speak" is expected, not a bug. dispatchTurn() throws not wired yet - plan 01-08 implements it. Plan 01-08 is the one that makes it talk.

Diagnostic trap β€” cost real time this session: window.Avatar.__debug is a stale snapshot, refreshed only inside getDebug(). Polling it in a rAF loop returns frozen values forever and looks exactly like a dead render loop. Always await window.Avatar.getDebug() per sample; copy IDLE_SAMPLER from tests/e2e/test_avatar_loop.py.

Updated 2026-09-05 (T-pose fix). Originally written 2026-08-27. Working tree is clean. Everything is committed. The fix IS deployed: the Space runs revision 487b8cf. Local master carries only docs commits on top.

To pick back up, run /gsd:resume-work in this directory. It reads .planning/HANDOFF.json first, which carries the same state in machine-readable form.


One-line status

Phase 01 is 7 of 10 plans complete and unblocked: the Space is deployed, the spike is CONFIRMED, and 01-08 is next.


Why nothing was pushed

This repo's only git remote is space β€” the public Hugging Face Space at WolfDavid/japanese-learning-avatar. There is no GitHub remote. Branch master has no upstream.

So git push here does not mean "back up my work." It means publish the app publicly and trigger a Space rebuild β€” which is precisely plan 01-05 Task 1, the gate that is waiting on you.

Pushing right now would also produce a broken public build. There is no README.md in this repo; the Space's front-matter lives only on the HF side and was auto-generated wrong when the Space was created:

Front-matter key Currently on HF Must become Why
sdk_version 6.26.0 6.22.0 must match gradio==6.22.0 in requirements.txt
python_version '3.12' 3.12.12 ZeroGPU provides only 3.10.13 and 3.12.12

Writing that corrected README is part of plan 01-05. Push after 01-05 runs, not before.

42 commits are unpushed by design, not by omission.

If what you actually want is an off-machine backup, that is a different action from deploying: add a private GitHub remote and push there. That was not done unprompted because the standing rule for this account is to ask before pushing nested repos.


Where the work stands

Plan What it is State
01-01 Hosting decision + Space creation + VRM sourcing Complete
01-02 Toolchain, Git LFS, package skeleton, test scaffolding Complete
01-03 Avatar stage, shared facade + turn loop, both transports Complete (resumed from the crash)
01-04 VOICEVOX TTS, requirements.txt, ground-truth fixtures Complete
01-06 Mora-to-viseme timeline builder Complete
01-07 Push-to-talk mic gate + tiered browser ASR Complete
01-05 Space manifest, deploy the spike, verdict Complete β€” CONFIRMED, inline
01-08 Turn-loop wiring β€” closes the round trip NEXT (/gsd:execute-phase 1)
01-09 Full deployed E2E suite + LICENSES.md Blocked behind 01-08
01-10 Vendor modules, latency harness (user-gated) Blocked behind 01-09

The dependency chain is real, not a preference: 01-08 depends_on [01-05, 01-06, 01-07], 01-09 depends_on [01-08], 01-10 depends_on [01-09]. Nothing proceeds until 01-05 does.

Verified green at handoff

  • 82 quick-loop tests in ~12.8 s
  • 15 end-to-end browser tests
  • ruff check . and ruff format --check . both clean

Run pytest tests/e2e/ as a whole before trusting a green phase. Per-file runs hid two real defects this session β€” each plan's own verification passed while the full suite failed.


What deploying would actually test

The risky part is already retired. three.js + @pixiv/three-vrm render inside a real Gradio 6.22.0 gr.HTML component locally, with threeInstanceCount === 1 and mountCount === 1, and the iframe fallback boots identically under AVATAR_TRANSPORT=iframe.

What remains genuinely deployment-specific: CDN reach from *.hf.space, set_static_paths behind the Space proxy, cold-start behaviour, and mobile.

Also note: 1 of 2 free ZeroGPU slots is already consumed by this Space. The remaining slot is the last free Gradio Space this account can create without PRO β€” budget it against the other HF-profile projects. cpu-basic is not a fallback; it returns HTTP 402 for Gradio Spaces.


Traps found the hard way β€” do not re-derive these

01-RESEARCH.md is wrong on these points. Copy from src/japanese_avatar/ui/avatar_component.py and avatar/asr.js instead.

  1. js_on_load cannot use top-level await. Gradio 6.22.0 compiles it into a plain non-async Function, so the documented snippet throws SyntaxError and window.Avatar never exists. Use an async IIFE with its own .catch.
  2. Custom props are **kwargs, not props={...}. The dict form creates one prop literally named props, so props.vrmUrl comes back undefined.
  3. ASR dtype:'q8' cannot create an ONNX session on the WASM backend at all β€” for every Whisper size tested. It works on WebGPU, so the prescribed default would ship green on a dev machine and dead on exactly the browsers the WASM tier exists to serve. q4 is the only quantisation working on both tiers.
  4. The prescribed WebGPU→WASM fallback does not fall back. A failed WebGPU init poisons the ONNX Runtime Web backend registry for the whole page. avatar/asr.js probes requestAdapter() first so the doomed call is never issued.

Two spec corrections carried forward

  • Frame quantisation: the correct form is round(round(sec * 93.75) / speed), not round(sec / speed * 93.75). Measured 24/24 correct vs 8/24. Already shipped in visemes.py.
  • The slow/long duration ratio is 1.3410852713178294, not exactly 1/0.75 β€” 4 frames out from what 01-VALIDATION.md and plan 01-06 assert. The plan's stated truth was written from a wrong assumption. Update 01-VALIDATION.md when 01-10 touches it, so the phase verifier does not read this as an unmet must-have.

Standing warning about requirements

Phase 1 plan frontmatter over-claims shared requirements. AVTR-01, AVTR-02, VOIC-02/03/04/05, DPLY-01 and DPLY-04 all appear in the requirements: field of plans that do not actually satisfy them. Never blind-run requirements mark-complete β€” verify the acceptance test has really run. Three executors this session correctly declined to mark requirements complete for this reason.