Skip to main content

Overview

Hyperscape is a real-time multiplayer game using WebSocket connections for low-latency communication between clients and the authoritative server.

Network Architecture

Server Authority

The server is the single source of truth:
ResponsibilityLocation
Combat calculationsServer
Item transactionsServer
XP and levelingServer
Position validationServer
RenderingClient
Input collectionClient
Clients predict movement locally but server corrects if needed.

Entity Synchronization

// From packages/shared/src/constants/GameConstants.ts
export const NETWORK_CONSTANTS = {
  UPDATE_RATE: 20,                // 20 Hz (50ms)
  INTERPOLATION_DELAY: 100,       // milliseconds
  MAX_PACKET_SIZE: 1024,
  POSITION_SYNC_THRESHOLD: 0.1,   // meters
  ROTATION_SYNC_THRESHOLD: 0.1,   // radians
} as const;

Sync Flow

  1. Server processes game tick (600ms)
  2. Entity changes collected via markNetworkDirty()
  3. Delta updates sent to clients at 20 Hz
  4. Clients apply updates and interpolate

Entity Network Data

// From packages/shared/src/entities/Entity.ts (lines 1400-1449)
getNetworkData(): Record<string, unknown> {
  return {
    id: this.id,
    type: this.type,
    name: this.name,
    position,
    rotation,
    scale,
    visible: this.node.visible,
    networkVersion: this.networkVersion,
    properties: this.config.properties || {},
    ...dataFields, // emote, inCombat, combatTarget, health
  };
}

Sync Data

DataFrequency
PositionEvery tick (600ms), threshold 0.1m
RotationOn change, threshold 0.1 rad
HealthOn change (immediate)
EquipmentOn change
InventoryOn change
Combat stateOn change
ChatImmediate

WebSocket Protocol

Connection

const ws = new WebSocket("wss://server/game");
ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  handleMessage(message);
};

Message Types

TypeDirectionPurpose
syncServer → ClientEntity updates
actionClient → ServerPlayer commands
chatBidirectionalChat messages
eventServer → ClientGame events

Persistence

Database Schema

Player data stored in PostgreSQL using Drizzle ORM:
// From packages/server/src/database/schema.ts
// Key tables for persistence:

export const users = pgTable("users", {
  id: text("id").primaryKey(),
  name: text("name").notNull(),
  privyUserId: text("privyUserId").unique(),
  farcasterFid: text("farcasterFid"),
  wallet: text("wallet"),
});

export const characters = pgTable("characters", {
  id: text("id").primaryKey(),
  accountId: text("accountId").notNull(),
  // All skill levels and XP columns
  // Position, health, coins
  // Combat preferences
});

export const inventory = pgTable("inventory", {
  id: serial("id").primaryKey(),
  playerId: text("playerId").references(() => characters.id),
  itemId: text("itemId").notNull(),
  quantity: integer("quantity").default(1),
  slotIndex: integer("slotIndex").default(-1),
});

export const equipment = pgTable("equipment", {
  id: serial("id").primaryKey(),
  playerId: text("playerId").references(() => characters.id),
  slotType: text("slotType").notNull(),
  itemId: text("itemId"),
});
TableData
usersAccount info, Privy/Farcaster IDs
charactersFull character with all skills
inventory28-slot item storage
equipmentWorn items by slot
playerDeathsDeath locks (anti-dupe)
npcKillsKill statistics
friendshipsBidirectional friend relationships
friend_requestsPending friend requests
ignore_listsBlocked players

Save Strategy

  • Immediate: Critical changes (item transactions)
  • Periodic: Stats, position (every 30 seconds)
  • On disconnect: Full state save

Authentication

Using Privy for identity:
  1. Client authenticates with Privy
  2. JWT token sent to server
  3. Server validates token
  4. Session established
Without Privy credentials, each session creates a new anonymous identity.

LiveKit Integration

Optional voice chat via LiveKit:
  • Spatial audio based on position
  • Push-to-talk or voice activation
  • Server-managed rooms
Configure with LIVEKIT_API_KEY and LIVEKIT_API_SECRET.

Scalability

Current Architecture

  • Single server instance
  • All players in shared world
  • SQLite for development, PostgreSQL for production

Future Considerations

  • Multiple server instances
  • Zone-based sharding
  • Load balancing

Network Files

LocationPurpose
packages/shared/src/core/Networking core, World class
packages/server/src/systems/ServerNetwork/Server WebSocket handling
packages/server/src/systems/ServerNetwork/handlers/Message handlers
packages/server/src/systems/ServerNetwork/authentication.tsPrivy auth
packages/server/src/systems/ServerNetwork/character-selection.tsCharacter handling
packages/server/src/systems/ServerNetwork/movement.tsPosition sync
packages/server/src/systems/ServerNetwork/broadcast.tsMessage broadcasting
packages/client/src/lib/Client networking