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 validate and 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:

  1. It's actually the blank-bundle bug (#1). Rule it out first — run npm run dev:hosted locally and watch the console.
  2. The SDK didn't load (SRI/origin). See #3 below.
  3. 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.
  4. A thrown error in your sdk.ready / lifecycle.run handler. Open the console; an exception in a lifecycle callback can leave the canvas blank. Wrap risky setup and log it with game.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:

  • EINTEGRITY on npm install — your dependency spec and lockfile disagree. Delete the stale lock entry (or package-lock.json) and reinstall so the resolved URL and integrity are recaptured together:

    rm package-lock.json
            npm install
            
  • SRI mismatch at runtime — re-export so the bundle re-pins the current SDK:

    npm run export:zip
            
  • A 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:

  1. 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 ensureRuntimeReady at game entry, not at hello — a hello-time request summons the calibration ceremony under your home screen.

  2. You're testing standalone with no pose source. In npm run dev there's no host streaming frames. Open the Dev HUD (`) and set input source to keyboard or clip, or use npm run dev:hosted -- with ?camSim=1.

  3. You never declared tracking.frame. Add it to capabilities in bodylink.game.json.

  4. 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:

  1. Compress the biggest assets first. Textures, audio, and models dominate bundle size; re-encode them and drop anything unused. release:preflight prints your exact zip size against the cap after every export.
  2. Split any single file over 25 MiB. That per-file cap applies on every submission lane, regardless of the zip cap.
  3. 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