Debugging and Logs
When local Air Jam development breaks, the fastest fix is usually knowing which surface is failing:
- game host
- controller
- local Air Jam server
- Arcade embed/runtime wiring
This page explains the practical debugging loop.
The Canonical Stream
Before you start jumping between browser tabs and terminals, remember that Air Jam has one canonical local dev stream:
That stream is the intended first place to inspect issues that cross:
- server lifecycle
- host runtime
- controller runtime
- embedded runtime edges
Read the dedicated guide here:
1. Start With The Canonical Local Loop
For scaffolded games, the default local command is:
pnpm run devThat gives you the most useful first signal:
- server logs in the same terminal
- Vite host output
- QR/join flow using the same local runtime shape the template expects
If that terminal is not healthy, fix it before debugging the controller UI.
2. Check The Local Server First
The local server should answer:
If /health is down:
- your local runtime server is not running
- the game will not create or join rooms
- controller QR flow will fail no matter what the UI shows
3. Know Which Console To Inspect
Use the right browser console for the right problem:
- Host tab for room creation, host bootstrap, input consumption, and gameplay errors
- Controller tab/phone remote inspector for join failures, input publishing, haptics, and controller UI state
- Server terminal for room lifecycle, socket disconnects, auth/bootstrap problems, and routing issues
Do not debug everything from only one surface.
4. Common Failure Buckets
Host boots but no room appears
Check:
- server terminal is running
- host browser console has no bootstrap/config errors
VITE_AIR_JAM_SERVER_URLis correct for your environment- production/static deploy has a valid
VITE_AIR_JAM_APP_ID
Controller cannot join
Check:
- QR or join URL contains the right room code
- host and controller are talking to the same server origin
- SPA rewrites are enabled for
/controller?room=XXXX - phone can actually reach the host URL you are showing
If local phone testing is the issue, use:
pnpm run dev -- --secureInput arrives late or not at all
Check:
- controller is publishing input via
useControllerTick+useInputWriter - host is reading input via
getInputoruseGetInput - you are not trying to send per-frame input through store actions
- the input schema still matches the payload shape
Store actions do nothing
Check:
- actions are dispatched via
useActions() - payloads are plain serializable objects
- your controller is connected before dispatching
- identity-dependent actions use
ctx.actorIdon the host side
Arcade embed works in standalone but breaks in platform
Check:
- game code is not parsing Arcade runtime params directly
- gameplay state lives in the game's own store, not in local iframe lifecycle state
- you are treating the bridge as transport only, not as app-state truth
5. Use SDK Diagnostics In Development
Air Jam exposes structured diagnostics for common misuse paths:
import { onAirJamDiagnostic, setAirJamDiagnosticsEnabled } from "@air-jam/sdk";
setAirJamDiagnosticsEnabled(true);
const unsubscribe = onAirJamDiagnostic((diagnostic) => {
console.log(diagnostic.code, diagnostic.message, diagnostic.details);
});This is especially useful for:
- missing provider/session scope
- invalid input payload shape
- state-action dispatch before session readiness
- runtime config mistakes
6. Use The Reference Games As Sanity Checks
If you are unsure whether the framework or your game is the problem, compare your approach against:
pongfor the small canonical starter pathair-capturefor the heavier reference-app path
If your code disagrees with both patterns, your code is probably the outlier.
7. Query The Unified Stream
Scaffolded games should use:
pnpm exec air-jam-server logsInside the Air Jam monorepo, the equivalent maintainer command is:
pnpm run repo -- workspace logsBoth commands read the same canonical local observability model.