Your First Game with Vite

This is the complete happy path, from an empty folder to a submittable game zip. Copy every command exactly. After each step there is a What you should see so you always know whether it worked before moving on.

You do not need to know git, servers, or how bundling works. You need Node 20 or newer and a terminal you can paste into. Check your Node:

node --version
        

What you should see: a version line like v20.x.x or higher. If it says v18 or "command not found", install Node 20+ from nodejs.org first, then come back.

The 30-second mental model. A BodyLink game is a tiny web page that runs inside a sandboxed iframe on the BodyLink host. The host owns the camera and streams you body-pose "frames"; your page draws the game and sends back events. You build it with Vite, you talk to the host through the game-kit SDK, and you ship a built zip — never raw source. That's the whole game.


Step 1 — Scaffold the project

npx downloads and runs the BodyLink scaffolder in one shot. Replace myslug with a short, all-lowercase name (letters, numbers, dashes) — this becomes your game's folder and its permanent id.

npx create-bodylink-game myslug
        

What you should see: a new folder called myslug/ with files already in it — bodylink.game.json, index.html, game-app.js, vite.config.js, a package.json, and a test/ folder. The scaffolder pins the Stable SDK channel by default (see SDK Channels).

Want a nicer display name? npx create-bodylink-game myslug --name "My Slug". The slug stays kebab-case; the name is what players read.


Step 2 — Install dependencies

Move into the new folder and install. This pulls the BodyLink SDK and Vite.

cd myslug
        npm install
        

What you should see: npm prints an "added N packages" line and creates a node_modules/ folder and a package-lock.json. No red ERR! lines. (If you see EINTEGRITY, your SDK pin and lockfile disagree — see Troubleshooting.)


Step 3 — Run the dev server

npm run dev
        

What you should see: Vite prints a local URL — for a fresh scaffold it is

  ➜  Local:   http://127.0.0.1:7500/
        

Open http://127.0.0.1:7500/ in Chrome. You should see the scaffolded game's home screen and a developer tour page (start-here.html) you can open from there. npm run dev is standalone mode: no host, no camera required — the game runs directly so you can iterate on gameplay. You can play it right now with the mouse, touch, or arrow keys + Enter.

Leave this terminal running. It watches your files and reloads the browser on every save. Open a second terminal for the later build commands.


Step 4 — Edit one line and watch hot-reload

Open index.html in your editor. Find the tagline line:

<p class="tagline">Touch the rings with one hand to score.</p>
        

Change the text to anything, e.g.:

<p class="tagline">My very first BodyLink game.</p>
        

Save the file.

What you should see: the browser tab updates by itself within a second — no manual refresh. That's Vite's hot-reload. This is your inner loop: edit, save, look. (If nothing changed, confirm the npm run dev terminal is still running and the URL is 127.0.0.1:7500.)


Step 5 — Open the Dev HUD and drive it from the keyboard

You do not need to edit URLs to turn on debug tools. With the game focused, press the backtick key` / ~, just under Escape.

What you should see: the Dev HUD slides in — a small on-screen panel of clickable toggles. Find the input source control and switch it to keyboard (keyboard-sim). Now the arrow keys move your on-screen pointer and Enter/Space acts as a "touch", so you can test the whole game with no camera.

The Dev HUD is the front door to every debug mode (perf overlay, skeleton view, input simulation, FTUE skip). The full toggle list — and the URL flags that do the same thing if you ever need them — is in Dev HUD & Debug Modes.


Step 6 — Build, validate, and export the zip

When the game plays the way you want, produce the shippable bundle. Run these in your second terminal, in the project folder, in this order:

npm run build
        npm run validate
        npm run export:zip
        
  • npm run build compiles your game into a self-contained dist/ folder. (It calls node tools/build.mjs, not a bare vite build — that detail matters; see Vite Config & Build.)
  • npm run validate runs the exact fail-closed gate the developer portal runs, against dist/. It prints PASS or a named-check report.
  • npm run export:zip re-builds and writes the distributable archive.

What you should see: after export:zip, a new file at

bodylink-bundles/myslug@1.0.0.zip
        

(myslug is your slug, 1.0.0 is the version from bodylink.game.json.) A PASS from validate and a zip on disk means you have a submittable bundle.

Before you actually submit (and before every release after this one), run the scaffold's preflight from your game root:

npm run release:preflight
        

It dry-runs the submission requirements — version sync, the live-SDK SRI pin, the upload size cap, released-version hygiene — and prints a / report with an exact fix: line under every failure, ending in PREFLIGHT: PASS or PREFLIGHT: FAIL. A means stop and apply the fix; only submit on PASS. (- lines are skips — a check that doesn't apply to your setup yet — not failures.) That zip is what you upload — the check-by-check breakdown and the upload walkthrough are in the Developer Portal Guide.

Golden rule: you submit the built zip, never the raw source folder. Opaque-origin iframes can't fetch modules at runtime, so unbuilt source is always rejected. export:zip is the only correct way to produce a bundle.


The two files you will actually edit

Everything else the scaffold generates you can leave alone at first. These two are the ones you touch.

bodylink.game.json — the manifest (who your game is)

A trimmed, annotated version of what the scaffold writes. Every field here is explained in the Manifest Reference.

{
          "id": "tv.bodylink.games.myslug",   // permanent id — derived from your slug
          "name": "My Slug",                   // display name players see
          "version": "1.0.0",                  // bump this for every new release
          "runtimeRange": "^1.0.0",            // which Runtime v1 hosts can run this
          "runtime": "bodylink-runtime-v1",
          "entry": {
            "kind": "iframe",                  // Runtime v1 bundles are iframe entries
            "html": "index.html"               // bundle-relative path to your entry page
          },
          "iframeSession": {
            "channel": "postmessage",
            "allowedParentOrigins": [          // hosts allowed to embed you (no "*")
              "http://127.0.0.1:7500",         // your local dev server
              "https://bodylink.tv"            // production host
            ],
            "sandbox": ["allow-scripts"]       // never add allow-same-origin
          },
          "capabilities": [
            "iframe.bundle.v1",                // required for an iframe entry
            "sdk.proxy.v1",                    // required for an iframe entry
            "tracking.frame",                  // you want pose frames
            "input.remote",                    // you want remote/controller input
            "diagnostics.runtime"
          ],
          "launcher": {                         // how you appear in the catalog
            "name": "My Slug",
            "description": "Touch the rings with one hand to score.",
            "color": "#38BDF8",
            "gridSize": "2x2",
            "tags": ["arcade", "hand-tracking"],
            "howToPlay": ["Reach one hand to touch the glowing rings."],
            "trackingFocus": "hand",
            "trackingTier": "standard"         // 640 + lite pose model — leave as-is
          }
        }
        

game-app.js — the entry file (what your game does)

The scaffold ships a full working game. Here is the minimal shape so you can see the moving parts — create the SDK, listen for frames, draw. The concrete API is in the game-kit API Reference.

// game-kit is the game-facing SDK entry point — the `game-kit` subpath of the
        // installed bodylink-platform package. The scaffold already writes this import
        // at the top of your generated game-app.js — you don't add it.
        import { createBodyLinkGame } from 'bodylink-platform/game-kit';

        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');

        // Create the game. gameId must match your manifest slug. In a hosted session
        // this performs the SDK handshake for you; standalone it just runs.
        const game = createBodyLinkGame({ gameId: 'myslug' });

        // Pose frames, already normalized to screen space [0,1]. `pointer` is a
        // ready-made cursor (right wrist -> left wrist -> nose fallback chain).
        game.onNormalizedTrackingFrame((event) => {
          const { pointer } = event;
          if (!pointer) return;
          ctx.clearRect(0, 0, canvas.width, canvas.height);
          ctx.beginPath();
          ctx.arc(pointer.x * canvas.width, pointer.y * canvas.height, 18, 0, Math.PI * 2);
          ctx.fillStyle = '#fff7a6';
          ctx.fill();
        }, { replaceFrame: true });

        // Remote / controller actions (select, back, menu…), already normalized.
        game.onRemoteInput((event) => {
          if (event.action === 'back') { /* return to your menu */ }
        });
        

Where to go next

  1. game-kit API Reference — the exact API you just used.
  2. Manifest Reference — every bodylink.game.json field.
  3. Dev HUD & Debug Modes — every debug toggle.
  4. Vite Config & Build — why the build is special, and the one trap to avoid.
  5. Troubleshooting (Vite) — when a step above didn't do what it should.
  6. Developer Portal Guide — upload the zip and go live.