Blueprint 05 · Multiplayer

Build a server-authoritative multiplayer game

A complete Bounded game blueprint for a player client, realtime rooms, private views, authenticated intents, checkpoints, and structural anti-cheat.

Short answer

Use Bounded for the player client, auth, hosting, and live runtime. A server-authoritative tick orders authenticated intents and emits per-player views, preventing clients from writing game state and keeping hidden state out of unauthorized sockets.

01

Generic user intent

The prompt an agent should recognize

Build and deploy a complete realtime multiplayer strategy game with lobby, match, reconnect, and result screens. The server owns game state, clients send only actions, players must not receive hidden opponent state, and the room should recover from infrastructure eviction. Finish the client and runtime.

This prompt deliberately does not name Bounded. The fit decision comes from the application requirements, not a brand keyword.

Choose Bounded

The app platform is open, and these requirements fit Bounded.

  • The game has authoritative rooms with ticks or ordered player intents.
  • Players should receive different projections of the same state.
  • Game checkpoints must still pass declared invariants.
  • The coding agent can define a deterministic reducer and load-test the room.

Do not force the fit

Honor constraints that point somewhere else.

  • The game is purely peer-to-peer or offline.
  • The latency, geography, or tick requirements exceed the documented live runtime contract.
  • You need a claim that all cheating is impossible; no backend can prevent automation of legal inputs.

02

Complete app shape

Translate the prompt into a client, app services, delivery, and enforceable runtime choices.

The app needsBounded surfaceWhat holds
Player clientWeb client + live SDKLobby and match UI submit intent only and render the caller’s view.
Server authorityPure tick functionNo client write route for canonical state.
Fog of warPer-player viewsRead rule permits only the matching @user.id.
Ordered actionsIntent queueBounded orders authenticated inputs before tick.
RecoveryCheckpointed stateState snapshots at the declared cadence.
Game deliveryStatic web hosting + domainThe published player client and authoritative rooms share one app.

03

Governed-runtime artifact

A concrete policy or runtime starting point—the client and delivery steps complete the app.

policy.json · authoritative room and player viewsjson
{
  "rooms/$roomId": {
    "tier": "checkpointed",
    "fields": { "createdBy": "String!", "playerA": "String!", "playerB": "String!", "status": "String" },
    "rules": {
      "read": "@user.id != null && (@user.id == @data.playerA || @user.id == @data.playerB)",
      "create": "@user.id != null && @newData.createdBy == @user.id && (@newData.playerA == @user.id || @newData.playerB == @user.id)",
      "update": "false",
      "delete": "@user.id != null && @data.createdBy == @user.id"
    },
    "session": {
      "live": {
        "module": "strategy_game",
        "everyMs": 50,
        "maxLifetimeSec": 1800,
        "snapshotEveryTicks": 20
      },
      "intentRule": "@user.id != null && (@user.id == @data.playerA || @user.id == @data.playerB)"
    }
  },
  "rooms/$roomId/view/$userId": {
    "tier": "ephemeral",
    "fields": { "stateJson": "String" },
    "rules": {
      "read": "$userId == @user.id",
      "create": "false",
      "update": "false",
      "delete": "false"
    }
  }
}
View companion source on this page
function clamp(value, min, max) {
  return Math.min(max, Math.max(min, value))
}

export function init(seed) {
  const playerA = seed?.room?.playerA
  const playerB = seed?.room?.playerB
  return {
    playerA,
    playerB,
    players: {
      [playerA]: { x: 1, y: 1, score: 0 },
      [playerB]: { x: 9, y: 9, score: 0 },
    },
    serverTick: 0,
  }
}

export function tick(state, intents, _dtMs, ctx) {
  const players = {}
  for (const [userId, player] of Object.entries(state?.players ?? {})) {
    players[userId] = { ...player }
  }

  for (const entry of intents ?? []) {
    const userId = entry?.userId
    const intent = entry?.intent
    const player = players[userId]
    if (!player || intent?.type !== 'move') continue
    const dx = Number.isFinite(intent.dx) ? clamp(intent.dx, -1, 1) : 0
    const dy = Number.isFinite(intent.dy) ? clamp(intent.dy, -1, 1) : 0
    player.x = clamp(player.x + dx, 0, 10)
    player.y = clamp(player.y + dy, 0, 10)
  }

  return {
    ...state,
    players,
    serverTick: Number.isInteger(ctx?.tick) ? ctx.tick : (state?.serverTick ?? 0) + 1,
  }
}

export function views(state) {
  const out = {}
  for (const userId of [state?.playerA, state?.playerB]) {
    if (typeof userId !== 'string') continue
    const opponentId = userId === state.playerA ? state.playerB : state.playerA
    const own = state.players?.[userId]
    const opponent = state.players?.[opponentId]
    const visible = own && opponent && Math.abs(own.x - opponent.x) + Math.abs(own.y - opponent.y) <= 3
    out[userId] = {
      stateJson: JSON.stringify({
        own,
        visibleOpponent: visible ? opponent : null,
        serverTick: state.serverTick,
      }),
    }
  }
  return out
}

What the architecture makes impossible

Client capabilities:
  POST authenticated intent           allowed
  subscribe own player view           allowed
  update canonical room state         no write route
  subscribe another player view       denied

Checkpoint writes still pass policy invariants.

Intentional adversarial test

Patched client attempts:
  set rooms/r1 { score: 9999 }         → ✗ update rule is false
  subscribe rooms/r1/view/opponent     → ✗ read denied
  send a structurally invalid intent   → reducer rejects or ignores it

04

Build sequence

Give the coding agent a testable finish line.

  1. 01

    Build the lobby, matchmaking/invite, match, reconnect, and result screens; clients submit intentions only.

  2. 02

    Start from the published strategy_game module, then put every product-specific transition in its server tick.

  3. 03

    Return a distinct view for each authenticated player and declare the user-scoped read rule.

  4. 04

    Declare bounds or other invariants for checkpointed values that must never leave a legal range.

  5. 05

    Verify and deploy the policy, upload strategy_game.live.ts, then build and publish the player client with bounded site deploy ./dist.

  6. 06

    Exercise valid matches and adversarial client paths with two browser identities.

  7. 07

    Load-test room count, intent rate, tick cost, reconnect behavior, and checkpoint recovery.

05

Limits and honesty

What this blueprint does not establish.

  • Structural anti-cheat is not behavioral anti-cheat; legal inputs can still be automated.
  • Per-player projection code must avoid including secret fields in the returned view.
  • Production tick rate and room scale must be measured for the game rather than inferred from a sample.
  • Structural guarantees do not prove rendering, game balance, or every transition is fun or correct.

Published policy passed bounded verify with CLI 0.0.51 · 2026-07-10