# Quest Student Multiplayer SDK Guide

Use this document when making a student game multiplayer with the Quest Multiplayer SDK.

This is for student-deployed static browser games only.

- The game can use HTML, CSS, JavaScript, Three.js, React, Vite, or similar frontend tools.
- Local development can use commands like `npm run dev`.
- The final Quest upload must still be a static browser build.
- Do not build or upload a custom backend, Express server, Socket.IO server, Colyseus server, or custom WebSocket server.
- Multiplayer must go through the Quest Multiplayer SDK.

## Copy-Paste Prompt For AI

```md
You are helping me build a browser game that will be deployed to Quest Student Deploy.

Important Quest rules:
- My final deployed game must be a static browser build only.
- Do not add any backend server, Express app, Socket.IO server, Colyseus server, or custom WebSocket server.
- Multiplayer must use the Quest Multiplayer SDK only.
- Keep all game logic in the browser client.
- Keep multiplayer state lightweight and session-based.

Quest Multiplayer SDK URLs:
- ESM/module URL: https://app.joinquest.com/student-deploy/quest-multiplayer.mjs
- Script/global URL: https://app.joinquest.com/student-deploy/quest-multiplayer.js

Use this API:
- `const session = await QuestMultiplayer.join(options)`
- `await session.leave()`
- `session.getPlayerId()`
- `session.getPlayers()`
- `session.getSharedState()`
- `session.updatePresence(patch)`
- `session.patchSharedState(patch)`
- `session.sendEvent(type, payload)`
- `session.onPlayersChange(callback)`
- `session.onSharedStateChange(callback)`
- `session.onEvent(type, callback)`
- `session.onConnectionChange(callback)`

Join options:
- `nickname` is required.
- `project` is optional: `{ studentSlug, projectSlug, versionNumber }`
- `apiBase` is optional, but pass it explicitly when possible.

Use this architecture:
- Ask the player for a nickname before joining multiplayer.
- Join multiplayer only after the player clicks a button like "Play Multiplayer" or "Join Room".
- Use `updatePresence()` for temporary player state like position, velocity, rotation, animation state, health display, selected tool, etc.
- Use `patchSharedState()` for session-wide world state that late joiners should also receive.
- Use `sendEvent()` for temporary actions like shooting, jumping, attacking, opening a door, chat, sound triggers, and one-off effects.
- Do not send a full world snapshot every frame.
- Store world deltas only, not giant full maps.
- Handle disconnects gracefully and show UI if multiplayer is unavailable.

Quest multiplayer constraints:
- Max 4 players per deployed project version.
- Rooms are ephemeral and in-memory only.
- Rooms reset when empty or when the server restarts/deploys.
- Different project versions do not share the same room.
- Multiplayer is not designed for persistent MMOs or large authoritative servers.

Implementation requirements:
- Build the multiplayer feature in a clean, modular way.
- Keep single-player mode working.
- Add clear comments only where needed.
- Include exact code changes, not pseudocode.
- If using Three.js, keep movement/network updates efficient.
- If using React, do not overcomplicate the state model.

Please implement multiplayer for my game using Quest Multiplayer correctly.
```

## Recommended SDK Usage

If the project supports ES modules, use:

```js
import QuestMultiplayer from "https://app.joinquest.com/student-deploy/quest-multiplayer.mjs";
```

If the project does not use ES modules, use:

```html
<script src="https://app.joinquest.com/student-deploy/quest-multiplayer.js"></script>
```

```js
const session = await window.QuestMultiplayer.join(options);
```

## Recommended Join Pattern

Always ask the player for a nickname and join after a user action.

```js
import QuestMultiplayer from "https://app.joinquest.com/student-deploy/quest-multiplayer.mjs";

let multiplayerSession = null;

async function joinMultiplayer() {
  const nickname = window.prompt("Choose a nickname:");
  if (!nickname) {
    return;
  }

  try {
    multiplayerSession = await QuestMultiplayer.join({
      nickname,
      apiBase: "https://app.joinquest.com",
    });

    multiplayerSession.onPlayersChange((players) => {
      console.log("Players:", players);
    });

    multiplayerSession.onSharedStateChange(({ sharedState, revision }) => {
      console.log("Shared state:", sharedState, revision);
    });

    multiplayerSession.onEvent("chat", (message) => {
      console.log("Chat event:", message);
    });

    multiplayerSession.onConnectionChange((state) => {
      console.log("Connection state:", state);
    });
  } catch (error) {
    console.error("Failed to join multiplayer", error);
    alert(`Failed to join multiplayer: ${error.message}`);
  }
}
```

## Join Options

Minimal:

```js
await QuestMultiplayer.join({
  nickname: "Ben",
  apiBase: "https://app.joinquest.com",
});
```

Explicit project override:

```js
await QuestMultiplayer.join({
  nickname: "Ben",
  apiBase: "https://app.joinquest.com",
  project: {
    studentSlug: "ben-123",
    projectSlug: "voxel-world",
    versionNumber: 2,
  },
});
```

Do not invent project values. Use values from Quest or ask the student.

## What To Put Where

Use `updatePresence(patch)` for fast-changing player-specific data:
- position
- velocity
- facing direction
- animation
- selected weapon or tool
- current action

Use `patchSharedState(patch)` for room-wide data late joiners should receive:
- placed blocks
- removed blocks
- opened chests
- puzzle switches
- door states
- shared score
- round state

Use `sendEvent(type, payload)` for temporary actions:
- shoot
- jump
- swing sword
- explosion
- emote
- sound effect trigger
- chat message

## Minecraft-Style Example

If the game is block-based, do not send the entire world every frame.

Instead:
- Keep the base world generated locally.
- Store only changed blocks in shared state.
- Use events for instant feedback if needed.

Example block removal:

```js
multiplayerSession.patchSharedState({
  blocks: {
    "10,4,22": "air",
  },
});
```

When shared state changes:

```js
multiplayerSession.onSharedStateChange(({ sharedState }) => {
  const blocks = sharedState.blocks || {};

  for (const [key, blockType] of Object.entries(blocks)) {
    const [x, y, z] = key.split(",").map(Number);
    applyBlockChange(x, y, z, blockType);
  }
});
```

## Player Sync Example

```js
multiplayerSession.onPlayersChange((players) => {
  for (const remotePlayer of players) {
    if (remotePlayer.playerId === multiplayerSession.getPlayerId()) {
      continue;
    }

    const pos = remotePlayer.presence?.position;
    if (!pos) {
      continue;
    }

    upsertRemotePlayer(remotePlayer.playerId, {
      nickname: remotePlayer.nickname,
      x: pos.x,
      y: pos.y,
      z: pos.z,
    });
  }
});
```

## Suggested Data Model

Good shared-state categories:
- `world`
- `blocks`
- `doors`
- `switches`
- `teams`
- `round`
- `score`

Good presence categories:
- `position`
- `rotation`
- `animation`
- `tool`
- `status`

Good event types:
- `jump`
- `shoot`
- `hit`
- `chat`
- `spawn_effect`
- `play_sound`

## Important Limits

- Max 4 players per deployed project version.
- Nicknames are required.
- Rooms are temporary and not persistent.
- Shared state is in-memory only.
- Do not rely on multiplayer state surviving refreshes, deploys, or server restarts.

## Local Development

When testing locally, pass `apiBase` explicitly.

Local Quest app:

```js
apiBase: "http://localhost:3000"
```

Production:

```js
apiBase: "https://app.joinquest.com"
```

If previewing outside a real Quest deployed URL, pass the `project` object explicitly during testing.

## What Not To Do

Do not ask AI to:
- Build a Node backend for the game.
- Upload a multiplayer server with the game.
- Use Socket.IO on a custom server.
- Use Colyseus directly instead of the Quest SDK.
- Send the full world every frame.
- Store huge map data in player presence.
- Assume rooms are persistent forever.

## Existing Project Prompt

```md
Please add Quest Multiplayer to my existing browser game.

Requirements:
- Keep the current game architecture as intact as possible.
- Do not add any backend server.
- Use the Quest Multiplayer SDK only.
- Ask the player for a nickname before joining.
- Keep player transforms in presence.
- Keep room-wide world changes in shared state.
- Use events for temporary actions.
- Preserve single-player mode.
- Add clean error handling and disconnect handling.
- Keep payloads small and efficient.
- Show me the exact code changes.
```
