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.