Dev HUD & Debug Modes

You don't need to edit URLs. Press ` (backtick / tilde ~).

While your game is focused in a local dev session, the backtick key opens the on-screen Dev HUD — a panel of clickable toggles for the debug and dev modes that exist where you're running. Flip a switch instead of hand-typing a query string. The HUD is dev-only: a real hosted session (real players) never shows it, so you can't accidentally ship it.

The URL flags still work as a fallback (and they're what automation uses). Every HUD row has a flag equivalent — the HUD is just the friendly front end to the same switches.


One HUD, two panels

There are two local ways to run your game — standalone (npm run dev) and the hosted runner (npm run dev:hosted, see Vite Config & Build) — and they are different machines with different switches. The HUD follows one rule:

Every row you can see does something real, right where you are. A row whose switch has nothing to drive in the current context is hidden, not shown-but-dead. That's why the standalone panel and the dev:hosted panel show different rows — and why a row can appear mid-session, once the thing it drives exists.

Opening it works the same everywhere: press `. The panel starts closed, and the header reads Dev HUD (~ to close). On your first standalone boot a one-time dismissible pill appears that says exactly "press ~ for dev tools" (opening the HUD, or clicking its ×, dismisses it for good).

Rows render as [<key>] <label>: <state> — click a row, or press its digit while the panel is open, and use / to move between rows. A row marked live () toggles in place; a reloads row () sets a URL flag and reloads. The digits are stable per row, not positions — so a panel can legitimately read [1] … [4] … [5].

Standalone — npm run dev

Key Row Kind What it does
1 Input source reloads Cycle the pose source through the sources that actually exist here, and start tracking from it at boot: none (the default — no camera; your game runs on mouse / touch / keys), camera (live webcam), clip (a video you supply via ?inputClip=<video-url>).
4 Performance live A frame meter — render FPS and average frame time over a rolling window. Toggles in place, no reload.
5 Console live An on-screen log panel mirroring your recent console.log / info / warn / error calls, so you can read diagnostics without opening browser devtools. Toggles in place, no reload.

About the Input source cycle:

  • The camera is opt-in. A bare standalone boot starts no camera — standalone never requires one. You cycle to camera only when you want to feel real tracking; picking it (or clip) reloads and actually starts your game's tracking from that source.
  • camera is offered only when the page can actually open one — a secure context with getUserMedia (127.0.0.1 counts).
  • clip is offered only when you've passed ?inputClip=<video-url>.
  • keyboard is not offered, on purpose: the synthetic keyboard pose source is host-side only — there is no standalone implementation, so a "keyboard" position in the cycle would be a dead switch. Hover the row — its tooltip lists exactly what's omitted here and why. Your scaffolded game is still fully playable without any camera: it takes mouse / touch / arrow keys as ordinary input.
  • The row appears once your game has created a standalone tracking controller — the thing the switch drives. No controller yet → no row.

Two rows you may remember are deliberately absent standalone: Skip camera (see the box below) and Debug overlay (the F8 overlay is a host surface — a standalone page mounts no overlay, so the row would be a no-op; it returns in dev:hosted).

Hosted runner — npm run dev:hosted

The hosted runner shows the HUD on the host page around your game — the same side of the fence production's host chrome lives on. Your game iframe itself stays exactly as locked-down as production (its own HUD is inert in a hosted session), so what you're testing stays honest.

Key Row Kind What it does
1 Calibration reloads Cycle how the get-ready calibration is produced: ceremony (the real get-ready — default) → synthetic (a contract-shaped fake answer, plus synthetic pose frames) → game (defer to your game's own request).
2 Camera sim reloads Drive tracking from the runner's bundled camera-fixture clip instead of getUserMedia — real pose detection, no webcam. Hidden when the runner serves no fixture clips.
3 Skip countdown reloads Skip the 3-2-1-GO! beats of the get-ready ceremony countdown.
4 Debug overlay live Cycle the host debug overlay — same as pressing F8. See Debug Overlay.

Where did "Skip camera" go? Older versions of this page showed a [2] Skip camera row. It's gone from both panels, honestly:

  • Standalone needs no camera in the first place. The camera is opt-in via Input source, so there is nothing to skip — and the flag that row flipped is read by the BodyLink host app's camera setup, which doesn't exist on a standalone page. A switch with no reader is a dead switch, so it's hidden.
  • In dev:hosted, where the host does own a camera, the equivalent is Camera sim: instead of skipping the camera you feed it a fixture clip and keep real pose detection.

Which SDK you need

Everything on this page describes bodylink-platform 0.7.4 or newer (0.7.3 shipped an earlier HUD with a different row set). Check what you actually have installed — this command is the truth, nothing else is:

npm ls bodylink-platform
        

If your game's repo on GitHub already pins a newer SDK (game repos have been re-pinned to 0.7.5), remember that your folder is a snapshot: the update reaches your disk only when you pull and reinstall —

git pull
        npm install
        

— see Getting Updates for why, and for what to do when an update doesn't take.


F8 — the debug overlay cycle

F8 cycles the host's tracking-view overlay, in this order:

off  ->  preview  ->  preview-skeleton  ->  full  ->  off  ->  …
        

The overlay is a host surface: it exists in hosted sessions (including npm run dev:hosted), with no game code needed — and that's exactly why the standalone panel has no Debug overlay row (nothing mounts one there). In a hosted session F8 works even when keyboard focus is inside your game iframe — the SDK forwards the keypress to the host for you. preview is the camera crop, preview-skeleton adds the pose wireframe, full adds perf metrics. Full details, including the manifest field that sets the starting mode, are in Debug Overlay.


The kill switch: ?developerMode=0

An explicit ?developerMode=0 on the URL turns the entire dev surface inert — no Dev HUD, no F8 cycle, no overlay, no debug DOM at all. It always wins, over every other flag and over any manifest declaration. Use it for demos, recordings, and kiosk installs where you want a guaranteed-clean screen.


Full URL-flag reference

Every switch, its accepted values, and the HUD row that does the same thing. Append flags to the page URL as a query string. Flags are grouped by the context that actually reads them — a flag set where nothing reads it does nothing, which is the same no-dead-switch rule the HUD rows follow.

Standalone (npm run dev)

Flag Values Effect HUD equivalent
inputSource camera | clip Start the game's standalone tracking from this source at boot. Absent = no auto-start (the default: no camera; mouse / touch / keys play the game). clip needs inputClip or it resolves to no source. Input source row
inputClip <video-url> The video the clip source plays. Supplying it is also what makes clip appear in the HUD cycle. — (enables the clip position)
performance 1 / 0 Pre-enable the FPS meter at boot (the HUD row then toggles it live). Performance row
console 1 / 0 Pre-enable the on-screen log panel at boot (the HUD row then toggles it live). Console row

Hosted runner (npm run dev:hosted)

Flag Values Effect HUD equivalent
calibration ceremony (default) | synthetic | game How the get-ready calibration is produced. Calibration row
camSim 1 Drive tracking from a bundled camera-fixture clip instead of a real webcam — real pose detection, no camera needed. Camera sim row
skipCountdown 1 Skip the 3-2-1-GO! beats of the get-ready countdown. Skip countdown row
(F8 key) Cycle the debug overlay: off → preview → preview-skeleton → full. Debug overlay row

BodyLink host app (the launcher)

These are read by the BodyLink host app itself — useful when you're testing inside the real launcher, not on your local dev server:

Flag Values Effect
skipCamera 1 / 0 Skip the host app's camera step; mouse / touch / keyboard drive input. (This is the flag the retired HUD row used to flip — it lives here, in the host app, which is why no local panel shows it.)
showDebug 1 / 0 Turn on the debug bootstrap (tracking-lab / debug scaffolding).
autogame <game-id> Auto-launch a specific game on load, e.g. ?autogame=myslug. Combine with skipCamera=1 for a one-URL boot.
catalog staging Reviewer-only. Read the staging catalog instead of production. Requires a reviewer session; a normal visitor is ignored and stays on production.

Everywhere

Flag Values Effect
developerMode 0 Kill switch. Force the entire dev surface off; always wins. (There is no in-HUD way to disable the HUD.)

Boolean flags accept 1/true for on and 0/false for off.


Which mode when

  • Building gameplay, no camera handy → just npm run dev. A scaffolded game plays on mouse / touch / arrow keys with no camera at all — you don't have to switch anything off, because nothing camera-shaped is on.
  • Checking tracking feelInput source: camera (or clip with ?inputClip=<url>) standalone — or npm run dev:hosted with Camera sim and the Debug overlay row (F8) to watch the skeleton.
  • Chasing a perf problem → the Performance row; watch FPS and frame time live while you play.
  • Testing what production will actually donpm run dev:hosted — the real handshake, calibration, and frame gating.
  • Recording a clean demo?developerMode=0. Nothing debug-related renders.

Related pages

  • Debug Overlay — the F8 tracking view in depth, and the coordinate.debugOverlay manifest field.
  • Vite Config & Build — how npm run dev works, and the scaffolded dev:hosted script (SDK 0.7.4+) that boots the game inside the host runner.
  • Getting Updates — why your clone doesn't update itself, and the two commands that do.