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, and no source starts by itself (the camera is opt-in). Open the Dev HUD (`) and cycle Input source to camera — or to clip, after adding?inputClip=<video-url>to the URL. (There is no keyboard pose source standalone.) For real pose detection with no webcam, usenpm run dev:hostedand its Camera sim row (?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. |
| Weird API errors after platform updates | Your installed SDK may be behind. npm ls bodylink-platform tells the truth; update and recover per Getting Updates. |
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 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? Stop before OAuth or browser upload and contact BodyLink with the exact size report. The public external workflow has no larger self-service upload route; do not use an undocumented lane or lower validation/security requirements to force the upload through.
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.