Architecture

Air Jam consists of four main components in a monorepo structure. This page explains how they work together to enable multiplayer gaming with smartphone controllers.

The important current rule is that Air Jam has one framework model, not one model for standalone games and a different model for Arcade.

Arcade is an Air Jam app around another Air Jam app.

System Overview

Air Jam Platform(apps/platform - Next.js)Game CatalogDeveloper PortalArcade ModeGame LauncherController Runtime(Mobile Wrapper)Air Jam Server(packages/server - Node.js)Room Manager(State + Rooms)Auth Service(App IDs)Event Router(Socket.IO)Your Game(Host)@air-jam/sdkYour Game(Host)@air-jam/sdkPhone 1CtrlSDK...Phone NCtrlSDK

Components

1. Platform (apps/platform)

Role: Central hub for the Air Jam ecosystem

Technology: Next.js 16, TypeScript, tRPC, BetterAuth, PostgreSQL (Drizzle ORM)

Key Features:

  • Developer Portal - Account management, app ID issuance, analytics
  • Game Catalog - Submit, manage, and discover Air Jam games
  • Arcade Mode - Browse and launch games from a unified interface
  • Controller Runtime - Persistent mobile wrapper that loads game controllers

Arcade Mode Flow:

1. Player scans QR on arcade screen
2. Platform loads controller runtime on phone
3. Player browses games using phone as remote
4. Game launches → controller switches to game's controller
5. Game ends → returns to arcade browser

2. Server (packages/server)

Role: Real-time communication backbone

Technology: Node.js, Express, Socket.IO, PostgreSQL

Core Services:

Core ServicesRoom ManagerCreates/destroys roomsTracks connected playersAuth ServiceValidates app IDsEnforces rate limitsEvent RouterRoutes input/signalsBetween hosts and controllersFocus SystemControls input routingSystem or game receives input

Socket Events:

Socket EventsHostServerControllerhost:createRoomCreate/join roomcontroller:joinJoin existing roomcontroller:inputcontroller:inputSend input to gamehost:signalhost:signalSend haptics/toastsserver:stateserver:stateBroadcast game state

3. SDK (packages/sdk)

Role: Developer toolkit for building Air Jam games

Technology: React, TypeScript, Socket.IO Client, Zustand, Zod

Architecture:

createAirJamApp / Runtime BoundariesRealtime Client(WebSocket)Connection handlingRuntime State(Zustand-backed)Players, status, game stateInputManager• Schema Validation (Zod)• Input Behavior (pulse / hold / latest)• Per-Controller BufferinguseAirJamHost()(consumer)useGetInput(Perf)useAirJamController()(Mobile)

Key Design Decisions:

  1. Provider Pattern - Single provider for configuration, multiple hooks for access
  2. Lightweight Hooks - useGetInput, useSendSignal don't trigger re-renders
  3. Input Latching - Ensures rapid button taps are never missed
  4. Schema Validation - Type-safe input with runtime validation

4. Air Capture (games/air-capture)

Role: Reference implementation showcasing SDK capabilities

Technology: React, Vite, React Three Fiber, Rapier Physics

Demonstrates:

  • Host-side game logic with physics
  • Controller UI with joystick and buttons
  • Player spawning/despawning on join/leave
  • Haptic feedback on collisions
  • Multiple game modes (CTF, survival)

Core Lane Model

Air Jam uses three lanes and they should stay separate:

  1. Input lane for high-frequency transient control data via useInputWriter and getInput / useGetInput
  2. Replicated state lane for replayable shared state via createAirJamStore
  3. Signal / command lane for coarse UX/runtime messages such as haptics, toasts, pause, and exit

This is the same model in standalone games and in Arcade-embedded games.

Run Modes

The SDK automatically detects and adapts to different deployment scenarios:

Standalone Mode

Your game runs independently with direct WebSocket connection.

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│  Your Game   │◀───▶│  AirJam      │◀───▶│  Controller  │
│  (Host)      │     │  Server      │     │  (Phone)     │
└──────────────┘     └──────────────┘     └──────────────┘

Use case: Self-hosted games, development, custom deployments

Arcade Mode

Game runs inside an iframe on the Air Jam Platform.

┌─────────────────────────────────────────┐
│  Air Jam Platform (Parent)              │
│  ┌───────────────────────────────────┐  │
│  │  Your Game (iframe)               │  │
│  │  • Receives launch capability     │  │
│  │  • Controlled player routing      │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘

Use case: Featured on Air Jam arcade, game discovery

Bridge Mode

Controller runs inside platform's runtime wrapper (iframe communication).

┌─────────────────────────────────────────┐
│  Platform Controller Runtime (Parent)   │
│  ┌───────────────────────────────────┐  │
│  │  Your Controller UI (iframe)      │  │
│  │  • Input via postMessage          │  │
│  │  • Seamless game switching        │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘

Use case: Arcade mode controllers, persistent session

Data Flow

Input Path (Controller → Game)

1. Player moves joystick on phone
2. Controller publishes input via useInputWriter({ vector: {x, y}, action: false })
3. SDK sends controller:input event to server
4. Server routes to host socket
5. Host SDK's InputManager receives input
6. InputManager validates with Zod schema
7. InputManager applies configured input behavior (`pulse`, `hold`, `latest`)
8. Game calls getInput(playerId) in game loop
9. Returns typed, validated input with behavior semantics applied

Signal Path (Game → Controller)

1. Game detects collision
2. Host calls sendSignal("HAPTIC", { pattern: "heavy" }, playerId)
3. SDK sends host:signal event to server
4. Server routes to target controller(s)
5. Controller SDK receives signal
6. SDK triggers navigator.vibrate() with pattern
7. Player feels haptic feedback

Security

Host Bootstrap

  • Hosts perform one host:bootstrap verification step in production/static mode
  • In normal static mode, the server validates the browser-facing appId once, then binds host authority to the socket
  • Static apps can optionally restrict bootstrap by allowed origin per app identity
  • In optional signed mode, the SDK first fetches a short-lived host grant from hostGrantEndpoint, then bootstraps with that grant instead of raw appId
  • Later host lifecycle events rely on socket authority instead of repeated raw auth payloads

Child Launch Capability

  • Arcade game launch issues a room/game-scoped launchCapability
  • Embedded host iframes receive aj_cap / aj_cap_exp automatically
  • Game code does not parse or manage this capability directly

Room Isolation

  • Each room has unique 4-character code
  • Controllers can only join with correct code
  • Input is only routed to the designated host

Input Validation

  • Zod schemas validate all incoming input
  • Invalid input is rejected with console warning
  • Protects game logic from malformed data