> ## Documentation Index
> Fetch the complete documentation index at: https://hyperscape-ai.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Networking Architecture

> WebSocket protocol, packet format, and client-server sync

# Networking Architecture

Hyperscape uses a **server-authoritative architecture** with WebSocket-based real-time communication. The server runs at 600ms ticks while clients render at 60 FPS using prediction and interpolation.

<Info>
  Network code lives in `packages/shared/src/systems/client/ClientNetwork.ts` (2640+ lines) and `packages/server/src/systems/ServerNetwork/`.
</Info>

## Architecture Overview

```mermaid theme={"theme":{"light":"github-light","dark":"css-variables"}}
flowchart LR
    A[Client Browser] -->|WSS| B[Railway Server]
    A -->|HTTPS| C[Cloudflare Pages]
    A -->|HTTPS| D[Cloudflare R2]
    B -->|Fetches| D
    
    subgraph "Client 60 FPS"
        A
    end
    
    subgraph "Server 600ms Ticks"
        B
    end
    
    subgraph "Assets"
        D[CDN<br/>Manifests + Models]
    end
```

### Connection URLs

| Environment     | WebSocket URL                          | HTTP API URL                          |
| --------------- | -------------------------------------- | ------------------------------------- |
| **Development** | `ws://localhost:5555/ws`               | `http://localhost:5555`               |
| **Production**  | `wss://api.hyperscape.club/ws`         | `https://api.hyperscape.club`         |
| **Staging**     | `wss://staging-api.hyperscape.club/ws` | `https://staging-api.hyperscape.club` |

### CORS Configuration

The server automatically allows connections from:

* Production: `https://hyperscape.club`, `https://www.hyperscape.club`
* Staging: `https://staging.hyperscape.club`
* Preview: `https://*.hyperscape.pages.dev`
* Development: `http://localhost:*` (any port)

See [Configuration](/devops/configuration#cors-configuration) for full CORS details.

| Layer             | Rate            | Purpose                  |
| ----------------- | --------------- | ------------------------ |
| **Server Tick**   | 600ms (1.67 Hz) | Authoritative game logic |
| **Network Sync**  | 125ms (8 Hz)    | Entity state broadcasts  |
| **Client Render** | 16.7ms (60 FPS) | Visual interpolation     |
| **Client Input**  | 33ms (30 Hz)    | Movement/action requests |

***

## Binary Protocol

Communication uses **msgpackr** binary serialization for efficiency.

### Packet Format

```typescript theme={"theme":{"light":"github-light","dark":"css-variables"}}
// From platform/shared/packets.ts
export function writePacket(name: string, data: unknown): ArrayBuffer {
  const id = PACKET_ID_MAP[name];
  const payload = pack(data);
  const buffer = new ArrayBuffer(1 + payload.byteLength);
  new Uint8Array(buffer)[0] = id;
  new Uint8Array(buffer).set(new Uint8Array(payload), 1);
  return buffer;
}

export function readPacket(buffer: ArrayBuffer): { name: string; data: unknown } {
  const id = new Uint8Array(buffer)[0];
  const name = PACKET_NAMES[id];
  const data = unpack(new Uint8Array(buffer.slice(1)));
  return { name, data };
}
```

### Entity Update Optimization

Entity updates use **abbreviated keys** to minimize bandwidth:

| Key | Full Name  | Type               | Description       |
| --- | ---------- | ------------------ | ----------------- |
| `p` | position   | `[x, y, z]`        | World coordinates |
| `q` | quaternion | `[x, y, z, w]`     | Rotation          |
| `v` | velocity   | `[x, y, z]`        | Movement vector   |
| `e` | emote      | `string`           | Current emote     |
| `h` | health     | `{ current, max }` | HP state          |
| `i` | id         | `string`           | Entity ID         |
| `t` | type       | `string`           | Entity type       |

### Example Packet

```typescript theme={"theme":{"light":"github-light","dark":"css-variables"}}
// entityModified packet payload
{
  i: "player_abc123",
  t: "player",
  p: [125.5, 10.0, -42.3],
  q: [0, 0.707, 0, 0.707],
  h: { current: 45, max: 99 },
  e: "wave"
}
```

***

## Packet Types

### Client → Server

| Packet              | Purpose             | Payload                               |
| ------------------- | ------------------- | ------------------------------------- |
| `moveRequest`       | Request movement    | `{ x, z, running }`                   |
| `attackEntity`      | Attack target       | `{ targetId }`                        |
| `changeCombatStyle` | Change style        | `{ style }`                           |
| `pickupItem`        | Pick up ground item | `{ itemId }`                          |
| `useItem`           | Use inventory item  | `{ itemId, slot }`                    |
| `equipItem`         | Equip item          | `{ itemId }`                          |
| `dropItem`          | Drop item           | `{ itemId, quantity }`                |
| `bankDeposit`       | Deposit to bank     | `{ itemId, quantity }`                |
| `bankWithdraw`      | Withdraw from bank  | `{ itemId, quantity }`                |
| `chatMessage`       | Send chat           | `{ message }`                         |
| `chopTree`          | Start woodcutting   | `{ treeId }`                          |
| `catchFish`         | Start fishing       | `{ spotId }`                          |
| `lightFire`         | Light fire          | `{ logId }`                           |
| `cookFood`          | Cook food           | `{ foodId, fireId }`                  |
| `getQuestList`      | Fetch quest list    | `{}`                                  |
| `getQuestDetail`    | Fetch quest details | `{ questId }`                         |
| `questAccept`       | Accept quest        | `{ questId }`                         |
| `xpLampUse`         | Use XP lamp         | `{ itemId, slot, skillId, xpAmount }` |
| `dialogueContinue`  | Continue dialogue   | `{ npcId }`                           |

### Server → Client

| Packet              | Purpose               | Payload                                         |
| ------------------- | --------------------- | ----------------------------------------------- |
| `init`              | Connection setup      | `{ playerId, worldState }`                      |
| `snapshot`          | Full world state      | `{ entities[], tick }`                          |
| `entityAdded`       | New entity            | `EntityData`                                    |
| `entityModified`    | Entity update         | `{ id, ...changes }`                            |
| `entityRemoved`     | Entity despawn        | `{ id }`                                        |
| `inventoryUpdated`  | Inventory change      | `{ items[], coins }`                            |
| `equipmentUpdated`  | Equipment change      | `{ slots }`                                     |
| `skillsUpdated`     | XP/level change       | `{ skills }`                                    |
| `chatMessage`       | Incoming chat         | `{ sender, message }`                           |
| `damageDealt`       | Combat damage         | `{ targetId, damage, didHit }`                  |
| `deathNotification` | Entity died           | `{ entityId, killerId }`                        |
| `questList`         | Quest list response   | `{ quests[], questPoints }`                     |
| `questDetail`       | Quest detail response | `{ id, name, status, stages[], ... }`           |
| `questStartConfirm` | Quest accept screen   | `{ questId, questName, requirements, rewards }` |
| `questProgressed`   | Quest progress update | `{ questId, stage, progress }`                  |
| `questCompleted`    | Quest completion      | `{ questId, questName, rewards }`               |
| `xpDrop`            | XP gain notification  | `{ skill, xpGained, newXp, newLevel }`          |

***

## Client-Side Prediction

The client predicts movement locally for responsive controls, then reconciles with server authority.

### Prediction Flow

```typescript theme={"theme":{"light":"github-light","dark":"css-variables"}}
// From PlayerLocal.ts
class PlayerLocal {
  private pendingMoves: MoveRequest[] = [];
  private lastServerPosition: Position3D;

  fixedUpdate(delta: number): void {
    // 1. Run local physics simulation
    this.physics.step(delta);

    // 2. Store pending move for reconciliation
    this.pendingMoves.push({
      tick: this.world.currentTick,
      position: this.position.clone(),
      input: this.currentInput,
    });

    // 3. Send to server
    this.network.send('moveRequest', {
      x: this.targetPosition.x,
      z: this.targetPosition.z,
      running: this.isRunning,
    });
  }

  updateServerPosition(serverPos: Position3D, serverTick: number): void {
    // 4. Remove acknowledged moves
    this.pendingMoves = this.pendingMoves.filter(m => m.tick > serverTick);

    // 5. Check prediction error
    const error = this.position.distanceTo(serverPos);

    if (error > 0.5) {
      // 6. Snap to server position if too far off
      this.position.copy(serverPos);

      // 7. Replay pending moves
      for (const move of this.pendingMoves) {
        this.applyMove(move.input);
      }
    }
  }
}
```

### Interpolation for Remote Entities

```typescript theme={"theme":{"light":"github-light","dark":"css-variables"}}
// From TileInterpolator.ts
class TileInterpolator {
  private snapshots: EntitySnapshot[] = []; // Buffer of last 3 positions
  private snapshotIndex = 0;

  addSnapshot(position: Position3D, rotation: Quaternion, timestamp: number): void {
    this.snapshots[this.snapshotIndex] = { position, rotation, timestamp };
    this.snapshotIndex = (this.snapshotIndex + 1) % 3;
  }

  interpolate(alpha: number): { position: Position3D; rotation: Quaternion } {
    const prev = this.snapshots[this.snapshotIndex];
    const next = this.snapshots[(this.snapshotIndex + 1) % 3];

    return {
      position: prev.position.clone().lerp(next.position, alpha),
      rotation: prev.rotation.clone().slerp(next.rotation, alpha),
    };
  }
}
```

***

## Server Network System

The server handles all authoritative game logic.

### Connection Flow

```typescript theme={"theme":{"light":"github-light","dark":"css-variables"}}
// From ServerNetwork/index.ts
class ServerNetwork extends SystemBase {
  private sockets: Map<string, WebSocket> = new Map();

  onConnection(socket: WebSocket, query: { token: string }): void {
    // 1. Authenticate via Privy JWT
    const userId = await this.auth.verify(query.token);

    // 2. Load or create character
    const character = await this.db.characters.findOrCreate(userId);

    // 3. Spawn player entity
    const player = this.world.spawnEntity({
      type: 'player',
      id: character.id,
      position: character.position,
      stats: character.stats,
    });

    // 4. Send init packet
    socket.send(writePacket('init', {
      playerId: player.id,
      worldState: this.world.serialize(),
    }));

    // 5. Register socket
    this.sockets.set(player.id, socket);

    // 6. Emit event for other systems
    this.world.emit(EventType.PLAYER_CONNECTED, { playerId: player.id });
  }

  onDisconnect(socket: WebSocket): void {
    const playerId = this.getPlayerIdBySocket(socket);

    // 1. Save character to database
    this.saveManager.savePlayer(playerId);

    // 2. Despawn player entity
    this.world.removeEntity(playerId);

    // 3. Notify other players
    this.broadcast('entityRemoved', { id: playerId });

    // 4. Cleanup
    this.sockets.delete(playerId);
  }
}
```

### Packet Handlers

```typescript theme={"theme":{"light":"github-light","dark":"css-variables"}}
// From ServerNetwork/index.ts
private registerHandlers(): void {
  this.on('moveRequest', this.handleMoveRequest.bind(this));
  this.on('attackEntity', this.handleAttackEntity.bind(this));
  this.on('pickupItem', this.handlePickupItem.bind(this));
  this.on('useItem', this.handleUseItem.bind(this));
  this.on('equipItem', this.handleEquipItem.bind(this));
  this.on('dropItem', this.handleDropItem.bind(this));
  this.on('bankDeposit', this.handleBankDeposit.bind(this));
  this.on('bankWithdraw', this.handleBankWithdraw.bind(this));
  this.on('chatMessage', this.handleChatMessage.bind(this));
  this.on('chopTree', this.handleChopTree.bind(this));
  this.on('catchFish', this.handleCatchFish.bind(this));
  // ... more handlers
}

private handleMoveRequest(playerId: string, data: { x: number; z: number; running: boolean }): void {
  const player = this.world.entities.get(playerId);
  if (!player) return;

  // Validate and set path
  const movementSystem = this.world.getSystem('movement');
  movementSystem.setPlayerDestination(playerId, data.x, data.z, data.running);
}
```

***

## Event Bridge

The `EventBridge` converts game events to network packets automatically.

```typescript theme={"theme":{"light":"github-light","dark":"css-variables"}}
// From ServerNetwork/event-bridge.ts
class EventBridge {
  constructor(world: World, network: ServerNetwork) {
    // Inventory changes → inventoryUpdated packet
    world.on(EventType.INVENTORY_UPDATED, (data) => {
      network.sendTo(data.playerId, 'inventoryUpdated', {
        items: data.items,
        coins: data.coins,
      });
    });

    // Equipment changes → equipmentUpdated packet
    world.on(EventType.EQUIPMENT_UPDATED, (data) => {
      network.sendTo(data.playerId, 'equipmentUpdated', {
        slots: data.slots,
      });
    });

    // Combat damage → damageDealt packet
    world.on(EventType.COMBAT_DAMAGE, (data) => {
      network.broadcast('damageDealt', {
        attackerId: data.attackerId,
        targetId: data.targetId,
        damage: data.damage,
        didHit: data.didHit,
      });
    });

    // ... 50+ more event mappings
  }
}
```

***

## Network Constants

```typescript theme={"theme":{"light":"github-light","dark":"css-variables"}}
// Network configuration
export const NETWORK_CONSTANTS = {
  // Tick and sync rates
  TICK_DURATION_MS: 600,          // Server tick interval
  NETWORK_RATE: 125,              // 8 Hz entity sync
  INPUT_RATE: 33,                 // 30 Hz client input

  // Interpolation
  SNAPSHOT_BUFFER_SIZE: 3,        // Snapshots to buffer
  INTERPOLATION_DELAY_MS: 100,    // Delay for smooth interpolation

  // Prediction
  MAX_PREDICTION_ERROR: 0.5,      // Units before snap correction
  MAX_PENDING_MOVES: 10,          // Moves to buffer for reconciliation

  // Connection
  PING_INTERVAL_MS: 5000,         // Latency measurement interval
  RECONNECT_DELAY_MS: 1000,       // Initial reconnect delay
  MAX_RECONNECT_DELAY_MS: 30000,  // Max backoff delay
  RECONNECT_MULTIPLIER: 2,        // Exponential backoff factor

  // Timeouts
  CONNECTION_TIMEOUT_MS: 10000,   // Max time to connect
  IDLE_TIMEOUT_MS: 300000,        // Disconnect after 5 min idle
};
```

***

## Rate Limiting

Network handlers use **sliding window rate limiters** to prevent abuse:

```typescript theme={"theme":{"light":"github-light","dark":"css-variables"}}
// From ServerNetwork/services/SlidingWindowRateLimiter.ts

// Quest handlers
getQuestListRateLimiter()    // 5 requests/sec
getQuestDetailRateLimiter()  // 10 requests/sec
getQuestAcceptRateLimiter()  // 3 requests/sec

// Inventory handlers
getPickupRateLimiter()       // 5 requests/sec
getConsumeRateLimiter()      // 3 requests/sec
getCoinPouchRateLimiter()    // 5 requests/sec

// Combat handlers
getAttackRateLimiter()       // 10 requests/sec
getPrayerRateLimiter()       // 5 requests/sec

// Movement handlers
getMoveRateLimiter()         // 30 requests/sec
getFollowRateLimiter()       // 5 requests/sec
```

**Features:**

* Per-player tracking
* Sliding window algorithm (more accurate than fixed window)
* Automatic stale entry cleanup
* Configurable cleanup interval (60s default)

**Example:**

```typescript theme={"theme":{"light":"github-light","dark":"css-variables"}}
// Handler with rate limiting
export function handleQuestAccept(socket: ServerSocket, data: { questId: string }, world: World): void {
  const playerId = getPlayerId(socket);
  
  // Rate limit check
  if (!getQuestAcceptRateLimiter().check(playerId)) {
    logger.debug(`Rate limit exceeded for ${playerId} on questAccept`);
    return;
  }
  
  // Process request...
}
```

***

## Related Documentation

* [Entity Component System](/wiki/engine/ecs)
* [Combat System](/wiki/game-systems/combat)
* [Tick System](/wiki/engine/tick)
