Troubleshooting (Vite)
The failures you'll actually hit building a BodyLink game with Vite, and exactly how to fix each one. Each entry is symptom → cause → fix. Work top to bottom; the first few are by far the most common.
Debug loop. Almost every submission problem reproduces locally. Run
npm run build && npm run validateand read the named-check report before you upload anything — the portal runs the same gate, so a green local run means a green portal run.
1. Blank bundle: works in dev, white-screens after export
Symptom. npm run dev plays perfectly, but the exported game (or
npm run dev:hosted, or the uploaded bundle) shows a white/blank screen, often
with a console error like X is not iterable or undefined is not a function.
Cause. Code splitting got turned on. The bundle flattener inlines only your
single entry script; any module the bundler hoists into a separate shared chunk
is dropped from the runtime bundle, so its exports are undefined at
runtime.
Fix. Confirm your vite.config.js still has:
build: { rollupOptions: { output: { codeSplitting: false } } }
Never remove that line, and always build with npm run build (which calls
node tools/build.mjs), not a bare vite build. Full explanation:
Vite Config & Build.
2. White-screen at runtime (inside the host)
Symptom. The bundle validates and uploads, but shows nothing when it runs in a host session.
Causes & fixes, in order:
- It's actually the blank-bundle bug (#1). Rule it out first — run
npm run dev:hostedlocally and watch the console. - The SDK didn't load (SRI/origin). See #3 below.
- Your entry HTML has no visible content until a frame arrives, and frames are gated. See #4 — the game may be fine but stuck waiting on calibration.
- A thrown error in your
sdk.ready/lifecycle.runhandler. Open the console; an exception in a lifecycle callback can leave the canvas blank. Wrap risky setup and log it withgame.diagnosticLog(...).
Fastest triage: npm run dev:hosted reproduces the real host boot locally,
with the console right there — do that before re-uploading.
3. SDK / SRI load failure
Symptom. The console shows the SDK <script> failing to load, an
integrity/SRI mismatch, or npm install fails with EINTEGRITY.
Cause. A pinned hash no longer matches the bytes it points at. The runtime SDK is loaded cross-origin and SRI-pinned; the browser refuses to run a script whose bytes don't match the pin (it fails closed — a blank game rather than unverified code).
Fixes:
EINTEGRITYonnpm install— your dependency spec and lockfile disagree. Delete the stale lock entry (orpackage-lock.json) and reinstall so the resolved URL and integrity are recaptured together:rm package-lock.json npm installSRI mismatch at runtime — re-export so the bundle re-pins the current SDK:
npm run export:zipA one-line diff where every line of a committed file "changed" — that's a CRLF line-ending rewrite, and it breaks the byte-level SRI pin. Set line endings to LF and never commit the rewrite:
git config core.autocrlf false
The full model — hashed SDK files, the three pins that must agree, and the LF rule — is in SDK Delivery & SRI. If the mismatch is on the hosted side (the served file disagrees with the served manifest), stop and report it; that's a platform fault, not something to patch in your game.
4. Tracking frames never arrive
Symptom. Your onTrackingFrame / onNormalizedTrackingFrame callback never
fires in a hosted session, even though the game booted.
Causes & fixes:
Frames are gated behind readiness (the usual cause). By default frame listeners use
requireReady: true, so nothing is delivered until calibration settles. Either run the get-ready at entry:await game.ensureRuntimeReady({ calibration: 'upperBody' });or listen for readiness and start then:
game.onTrackingReady(() => startPlaying());Call
ensureRuntimeReadyat game entry, not at hello — a hello-time request summons the calibration ceremony under your home screen.You're testing standalone with no pose source. In
npm run devthere's no host streaming frames. Open the Dev HUD (`) and set input source to keyboard or clip, or usenpm run dev:hosted --with?camSim=1.You never declared
tracking.frame. Add it tocapabilitiesinbodylink.game.json.You want frames before readiness (advanced). Pass
{ requireReady: false }to your frame listener and handle the pre-calibration case yourself.
5. Dev works but export/validate fails
Symptom. npm run dev is happy, but npm run build, npm run validate, or
npm run export:zip errors or the gate reports FAIL.
Map the failing check to its fix:
| Report says | Cause | Fix |
|---|---|---|
bundle.sdk_only_unresolved_module_import / bundle.sdk_only_forbidden_reference |
You submitted unbuilt source, or imported something outside the SDK surface | Build + export (npm run export:zip); import only game-kit / iframe-sdk. |
bundle.opaque_origin_module_script / _dynamic_import / _import_meta |
A module <script>, runtime import(), or import.meta in an opaque-origin bundle |
Remove them — an allow-scripts-only iframe can't fetch modules at runtime. |
bundle.entry_kind |
Manifest is a legacy module entry | Set entry.kind: "iframe" + entry.html. |
bundle.launcher_missing / _field_missing |
No launcher block / a field absent |
Add the block; fill the named field. See Manifest Reference. |
bundle.capability_missing |
Iframe entry without iframe.bundle.v1 / sdk.proxy.v1 |
Declare both capabilities. |
bundle.parent_origin_wildcard / bundle.sandbox |
allowedParentOrigins: "*" or allow-same-origin |
Explicit origins; allow-scripts only. |
| tracking-tier + policy conflict | poseMaxDim / poseModel set while trackingTier isn't custom |
Remove them, or set the tier to custom. |
Every code has a one-line recommended fix in the report, and the full list is in
the Validation Reference. Fix, re-export, re-run —
repeat until PASS.
6. Common smaller snags
| Symptom | Fix |
|---|---|
npm run dev won't start / port in use |
Something already holds 127.0.0.1:7500. Stop it, or the dev server will pick another port — read the URL it prints. |
node --version is below 20 |
Install Node 20+. The SDK toolchain and gate require it. |
dev:hosted says the port isn't allowed |
The runner's port must be in your manifest's local allowedParentOrigins (e.g. http://127.0.0.1:7320). Add it and rebuild. |
| Upload rejected before validation | Not a zip, or over the 50 MiB upload cap. Upload the bodylink-bundles/<id>@<version>.zip from export:zip, not a folder. Too big? See §7. |
| Submitted but rejected for the SDK channel | Production requires Stable. Move to Stable, re-export, resubmit. See SDK Channels. |
npm run release:preflight prints a ✗ |
Apply the fix: line printed under the ✗ and re-run until the last line is PREFLIGHT: PASS. Never submit or tag on a FAIL — the portal enforces the same rules fail-closed. - (skip) lines are informational, not failures. |
7. Game too big — the size caps, and what to do
Symptom. npm run release:preflight fails bundle-size, or the portal
returns a 413 at upload.
The caps. Portal uploads take a zip up to 50 MiB. Behind the upload, every asset file must be under 25 MiB (the validation gate's asset-sizes check), and — as of the portal's large-game submission update — a bundle may total up to 512 MiB uncompressed across at most 2000 files. The full table is in the Developer Portal Guide §2.
Fixes, in order:
- Compress the biggest assets first. Textures, audio, and models dominate
bundle size; re-encode them and drop anything unused.
release:preflightprints your exact zip size against the cap after every export. - Split any single file over 25 MiB. That per-file cap applies on every submission lane, regardless of the zip cap.
- Genuinely can't fit 50 MiB? Games with heavy licensed assets submit through the GitHub-Release lane, which — as of the large-game submission update — accepts release assets up to 256 MiB. That lane is for BodyLink-partnered repos: contact BodyLink rather than trying to squeeze an asset-heavy game under the upload cap by degrading it.
If a bundle inside these bounds still gets a 413, the portal you're
submitting to may not have the large-game submission update yet (the older
bounds were 50 MiB archives everywhere and 64 MiB uncompressed) — report it
with the response attached rather than shrinking further.
Related pages
- Vite Config & Build — the blank-bundle trap in depth.
- SDK Delivery & SRI — SRI mismatches and the LF rule.
- Validation Reference — every named check and fix.
- Developer Portal Guide — upload and review.