japanese-learning-avatar / docs /SPIKE-VERDICT.md
WolfDavid's picture
docs(01-10): complete the vendoring, latency and manual-verification plan
fa8e2de
|
Raw History Blame
6.89 kB
# Wave 0 spike verdict (AVTR-01)
**Date:** 2026-09-05 **Space revision:** `487b8cf` (code; `82717b7` docs on top) **Transport:** inline
The question this spike existed to answer: can three.js + `@pixiv/three-vrm` render a VRM with
idle life inside a Gradio 6 `gr.HTML` component on a real, public, ZeroGPU Hugging Face Space,
and survive the host's re-render paths β€” or does the avatar have to live in an iframe?
`01-RESEARCH.md` Β§ "The honest gap" found no prior art either way: every three.js Space on the
Hub is `sdk: static`, and none of Gradio's own `gr.HTML` examples load a third-party ES module.
## Automated evidence
All values are read as numbers out of `window.Avatar.getDebug()` on the public URL by
`tests/e2e/test_avatar_loop.py --space-url https://wolfdavid-japanese-learning-avatar.hf.space`
(5 passed, run twice on 2026-09-05 against `487b8cf`). Never a screenshot diff.
| Check | Value | Pass |
|---|---|---|
| threeInstanceCount (deployed) | 1 | βœ… |
| mountCount after 20 interactions | 1 (send Γ—7, fill+send Γ—7, checkbox Γ—6; `ready` still true, threeInstanceCount still 1) | βœ… |
| vrmMetaTitle | `VRM1_Constraint_Twist_Sample` (vrmSpecVersion `1`) | βœ… |
| blink max over 12 s | 1.0 β€” 3 completed blinks (`blinkCount`), full-closure frame observed | βœ… |
| distinct breath values over 12 s | 169 (170 samples at animation-frame rate) | βœ… |
| console: "Multiple instances of Three.js" | absent | βœ… |
| console: "import map" | absent | βœ… |
| armDown left / right (rest pose, raw skeleton) | βˆ’0.932 / βˆ’0.932 (threshold < βˆ’0.7; 0 would be the T-pose) | βœ… |
| Space-page embed (`huggingface.co/spaces/…` cross-origin iframe) | `ready: true`, threeInstanceCount 1, mountCount 1, armDown βˆ’0.932, transport `inline` β€” read inside the iframe | βœ… |
| tutor.vrm served from the Space | 10,776,332 bytes transferred (`model/vrml`), not an LFS pointer | βœ… |
| Cold wake (Space already warm) | 0.2 s to first HTTP 200 — true sleep→wake is a 01-10 measurement | recorded |
| Page load β†’ `ready` | 4.09 s / 5.61 s / 5.66 s / 8.30 s across four loads; tutor.vrm 2.96–6.79 s of that | recorded |
| Space runtime | `RUNNING`, hardware `zero-a10g`, `DISABLE_GPU=1`; rebuilt to RUNNING in 106 s after push | βœ… |
**Console noise on the deployed page, none of it a spike token:** one `404` from
`https://huggingface.co/api/organizations/WolfDavid/avatar` (Gradio's own Hub-integration
lookup, not a project resource); deprecation warnings for `VRMUtils.removeUnnecessaryJoints`
(three-vrm says use `combineSkeletons`, which we already call) and `THREE.Clock` (use
`THREE.Timer`); headless-GPU `ReadPixels` stall warnings that do not occur on a real GPU. The
two deprecations are cheap to retire when plan 01-10 vendors the modules.
## Human observation
**Desktop:** Chromium 151.0.7922.34 (Playwright, headless, 1100Γ—900 and 1280Γ—900) β€”
`docs/evidence/2026-09-05-deployed-rest-pose.png` (direct URL) and
`docs/evidence/2026-09-05-hub-embed.png` (the Space page on huggingface.co, "Running on ZERO"
badge visible). Character renders upper-body, face readable, not clipped; hair, skin and
T-shirt in correct MToon toon shading; arms hang in a relaxed A-pose; the pixiv credit line is
visible under the controls. The earlier build `41ee90e` was confirmed **T-posed** by the project
owner and by `docs/evidence/2026-09-05-deployed-tpose.png` β€” that was a missing rest pose
(nothing ever moved the arms), *not* the two-instances symptom, and it is fixed in `7a26764`.
The project owner gave the verdict on this evidence on 2026-09-05.
**Mobile:** ⚠ **NOT YET TESTED ON A REAL DEVICE.** Plan 01-10 (2026-09-06) ran Playwright device
emulation instead β€” the `Pixel 7` descriptor on Chromium renders and completes a turn on the direct
URL and inside the Hub page; the `iPhone 14` descriptor could not be exercised (Playwright's Windows
WebKit has no Web Audio) β€” recorded as EMULATED in `docs/LATENCY.md Β§ Mobile`. Fill in from a real
phone: `<device> / <OS version> / <browser>` β€” renders y/n β€” smooth or slideshow. This row is a
plan-01-05 acceptance criterion and an AVTR-01 manual-only verification; until it is
recorded, **AVTR-01 stays Pending** even though every automated check passes.
**Space-page embed (huggingface.co/spaces/WolfDavid/japanese-learning-avatar):** the app
loads inside the Hub's cross-origin iframe (`?__theme=system`), the avatar reaches `ready`,
and the same debug numbers read identically from inside the frame. No mixed-content, no
third-party-cookie, no `allow=` issue surfaced for rendering. (Microphone permission inside
this embed is a plan 01-08/01-09 question, not this spike's.)
## Verdict
**CONFIRMED: `gr.HTML` hosts three.js/VRM on a deployed Gradio 6 Space.** This is a publicly
novel result β€” no prior art existed. Keeping `AVATAR_TRANSPORT=inline`.
The iframe transport (`avatar/avatar-iframe.js` + `avatar/stage.html`) stays in the tree as
the tested fallback it was built to be; `tests/e2e/test_facade_parity.py` keeps proving both
transports expose byte-identical method and debug surfaces, so flipping the env var remains a
config change rather than a re-plan.
## Consequences for later plans
- **None for the architecture.** The `window.Avatar` facade, `turn-loop.js` and the
six-method `stagePort` seam are exactly as designed; 01-08 wires `dispatchTurn` /
`requestSlower` against them with no transport awareness.
- **AVTR-01 is not closed by this verdict.** Its last open item is the mobile row above.
Record it, then mark the requirement.
- **A green suite is not a correct picture.** The deployed avatar passed `test_vrm_ready`,
`test_idle_life` and `test_no_remount` while standing in a T-pose. The fix added
`armDown` and a `< βˆ’0.7` assertion at three layers (standalone stage, both transports,
deployed). The standing rule for 01-08/01-09: **any visual property a human would notice must
be published as a number and asserted**, before the phase is called done.
- **`ready` fires before the first frame renders,** and the first frame compiles every MToon
shader (>1.5 s measured headless). Post-render measurements must wait for
`breathValue !== 0`, never a fixed sleep. 01-09's deployed lip-sync tests inherit this.
- **`window.Avatar.__debug` is a stale snapshot** refreshed only by `getDebug()`. Always
`await window.Avatar.getDebug()` per sample; `IDLE_SAMPLER` in `test_avatar_loop.py` is the
reference pattern.
- **01-10 latency harness inputs recorded here:** pageβ†’ready 4.1–8.3 s warm; tutor.vrm
3.0–6.8 s of that over the public internet; Space rebuild-to-RUNNING 106 s; warm wake 0.2 s.
True cold-from-sleep wake (48 h `gcTimeout`) is still unmeasured.
- **01-10 vendoring:** replace `THREE.Clock` with `THREE.Timer` and drop the deprecated
`removeUnnecessaryJoints` call while pinning the vendored modules.