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

# server

> Game server with Fastify, WebSockets, and database

## Overview

The `@hyperscape/server` package runs the authoritative game server:

* Fastify 5 HTTP API with rate limiting
* WebSocket real-time game state
* PostgreSQL (production) / SQLite (local) database
* LiveKit voice chat integration

## Package Location

```
packages/server/
├── src/
│   ├── database/       # Database schema and queries
│   ├── infrastructure/ # Docker, CDN configuration
│   ├── scripts/        # Utility scripts
│   ├── shared/         # Shared server utilities
│   ├── startup/        # Server initialization
│   ├── systems/        # Server-specific systems
│   ├── types/          # TypeScript types
│   ├── types.ts        # Core type definitions
│   └── index.ts        # Entry point
├── scripts/
│   ├── extract-model-bounds.ts  # Build-time model bounds extraction
│   └── inject-model-collision.ts # GLB collision data injection
├── world/
│   └── assets/         # Game assets (Git LFS)
│       ├── models/     # 3D models (GLB files)
│       └── manifests/  # Generated manifests
│           ├── model-bounds.json  # Auto-generated model bounds
│           └── stations.json      # Station configurations
├── docker-compose.yml  # Docker services
├── turbo.json          # Turbo build configuration
└── .env.example        # Environment template
```

## Entry Point

`src/index.ts` initializes:

1. Database connection (PostgreSQL or SQLite)
2. Asset CDN (Docker nginx, optional)
3. Fastify HTTP server with plugins
4. WebSocket handlers
5. Game world and systems

## API Routes

| Route     | Method | Purpose                   |
| --------- | ------ | ------------------------- |
| `/health` | GET    | Health check              |
| `/auth`   | POST   | Authentication            |
| `/ws`     | WS     | Game WebSocket connection |

## Database

The server supports both PostgreSQL (production via Neon) and SQLite (local development). Schema is defined with Drizzle ORM.

### Key Tables

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// From packages/server/src/database/schema.ts
export const characters = pgTable("characters", {
  id: text("id").primaryKey(),
  accountId: text("accountId").notNull(),
  name: text("name").notNull(),
  // Combat stats
  combatLevel: integer("combatLevel").default(3),
  attackLevel: integer("attackLevel").default(1),
  strengthLevel: integer("strengthLevel").default(1),
  defenseLevel: integer("defenseLevel").default(1),
  constitutionLevel: integer("constitutionLevel").default(10),
  rangedLevel: integer("rangedLevel").default(1),
  // Gathering skills
  woodcuttingLevel: integer("woodcuttingLevel").default(1),
  fishingLevel: integer("fishingLevel").default(1),
  firemakingLevel: integer("firemakingLevel").default(1),
  cookingLevel: integer("cookingLevel").default(1),
  // XP for all skills...
  health: integer("health").default(100),
  coins: integer("coins").default(0),
  positionX: real("positionX").default(0),
  positionY: real("positionY").default(10),
  positionZ: real("positionZ").default(0),
});
```

| Table            | Purpose                                         |
| ---------------- | ----------------------------------------------- |
| `users`          | Account authentication with Privy/Farcaster IDs |
| `characters`     | Full character data with all skills and XP      |
| `inventory`      | Player items (28 slots with quantities)         |
| `equipment`      | Worn items by slot type                         |
| `items`          | Item definitions and stats                      |
| `worldChunks`    | Persistent world modifications                  |
| `playerSessions` | Login/logout tracking                           |
| `playerDeaths`   | Death locks to prevent duplication              |
| `npcKills`       | Kill statistics per player                      |

### Database Commands

```bash theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
cd packages/server

# Using Drizzle Kit
bunx drizzle-kit push      # Apply schema to database
bunx drizzle-kit generate  # Generate migrations
bunx drizzle-kit studio    # Open Drizzle Studio GUI
```

## Environment Variables

```bash theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
# Database (choose one)
DATABASE_URL=postgresql://user:pass@host/db    # PostgreSQL (production)
# or SQLite is used automatically for local development

# PostgreSQL Connection Pool (increased March 2026)
POSTGRES_POOL_MAX=20                           # Up from 10 (commit 24fa8a5)
POSTGRES_POOL_MIN=2                            # Minimum idle connections

# Database Mode (auto-detected from DATABASE_URL hostname)
# DUEL_DATABASE_MODE=remote                    # or local (auto-detected)

# Authentication (required for persistent accounts)
PRIVY_APP_ID=your-privy-app-id
PRIVY_APP_SECRET=your-privy-app-secret
JWT_SECRET=your-jwt-secret

# Server Configuration
PORT=5555
NODE_ENV=development

# CDN (optional)
PUBLIC_CDN_URL=http://localhost:8080
DUEL_PUBLIC_CDN_URL=https://assets.hyperscape.club  # Production CDN (commit 2b3cbcb)

# Voice Chat (optional)
LIVEKIT_API_KEY=your-livekit-key
LIVEKIT_API_SECRET=your-livekit-secret
LIVEKIT_URL=wss://your-livekit-url

# ElizaCloud AI (for duel arena agents - March 2026)
ELIZAOS_CLOUD_API_KEY=your-elizacloud-api-key  # Access to 13 frontier models

# Streaming (optional - auto-detected from available keys)
TWITCH_STREAM_KEY=live_123456789_abcdefghij    # or TWITCH_RTMP_STREAM_KEY
KICK_STREAM_KEY=your-kick-stream-key
STREAM_ENABLED_DESTINATIONS=twitch,kick        # Auto-detected if not set

# Streaming Capture (March 2026)
STREAM_CAPTURE_MODE=mediarecorder              # Changed from cdp (commit 72c667a)
STREAM_CAPTURE_CHANNEL=chrome-beta             # Changed from chrome-unstable (commit 547714e)
STREAM_CAPTURE_ANGLE=default                   # Changed from vulkan (commit 547714e)
DISPLAY=:99                                    # Xvfb virtual display

# Duel Arena Oracle (optional)
DUEL_ARENA_ORACLE_ENABLED=true
DUEL_ARENA_ORACLE_PROFILE=testnet              # or mainnet
DUEL_ARENA_ORACLE_EVM_PRIVATE_KEY=0x...
DUEL_ARENA_ORACLE_SOLANA_AUTHORITY_SECRET=...
```

## Build Scripts

### Model Bounds Extraction

The server includes build-time scripts for automatic collision footprint detection:

```bash theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
# Extract bounding boxes from GLB models
bun run extract-bounds

# Generates: world/assets/manifests/model-bounds.json
```

**How it works:**

1. Scans `world/assets/models/**/*.glb` files
2. Parses glTF position accessor min/max values
3. Calculates bounding boxes and dimensions
4. Computes tile footprints at scale 1.0
5. Writes manifest for runtime use

**Turbo Integration:**

* Runs automatically before `build` and `dev` commands
* Cached based on GLB file changes
* Only rebuilds when models are added/modified

**Example Output:**

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "generatedAt": "2026-01-15T11:25:00.000Z",
  "tileSize": 1.0,
  "models": [
    {
      "id": "furnace",
      "assetPath": "asset://models/furnace/furnace.glb",
      "bounds": {
        "min": { "x": -0.755, "y": 0.0, "z": -0.725 },
        "max": { "x": 0.755, "y": 2.1, "z": 0.725 }
      },
      "dimensions": { "x": 1.51, "y": 2.1, "z": 1.45 },
      "footprint": { "width": 2, "depth": 1 }
    }
  ]
}
```

<Info>
  Footprints are calculated from model dimensions × `modelScale` from `stations.json`. A furnace with raw dimensions 1.51×1.45 and scale 1.5 becomes 2.27×2.18 meters → 2×2 tiles.
</Info>

***

## Running

### Development

```bash theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
bun run dev:server    # With hot reload and auto-restart
```

### Production

```bash theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
bun run build:server  # Build to dist/ (runs extract-bounds automatically)
bun start             # Start production server
```

Server runs at `http://localhost:5555` by default.

## Dependencies

| Package                 | Purpose                                      |
| ----------------------- | -------------------------------------------- |
| `fastify`               | HTTP server (v5)                             |
| `@fastify/websocket`    | WebSocket support                            |
| `@fastify/cors`         | CORS handling                                |
| `@fastify/rate-limit`   | Rate limiting                                |
| `@fastify/static`       | Static file serving (v9.1.2)                 |
| `@fastify/multipart`    | Multipart upload handling (v10.0.0)          |
| `pg`                    | PostgreSQL client                            |
| `@hyperscape/shared`    | Core engine                                  |
| `@privy-io/server-auth` | Authentication                               |
| `livekit-server-sdk`    | Voice chat                                   |
| `msgpackr`              | Binary serialization (v2.0.2)                |
| `uuid`                  | UUID generation (v14.0.0)                    |
| `uWebSockets.js`        | High-performance WebSocket server (v20.68.0) |
| `commander`             | CLI argument parsing (v15.0.0)               |

## Docker Services

The server can use Docker for CDN and PostgreSQL:

```bash theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
# Asset CDN
bun run cdn:up       # Start nginx CDN container
bun run cdn:down     # Stop CDN
bun run cdn:logs     # View CDN logs
bun run cdn:verify   # Verify CDN is working

# Asset Management
bun run assets:sync   # Sync assets from Git LFS
bun run assets:deploy # Deploy to R2 (production)
```

## Deployment

### Cloudflare Workers

```bash theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
bun run deploy        # Deploy to staging
bun run deploy:prod   # Deploy to production
```

### Railway

The `railway.server.json` in the project root configures Railway deployment.

## ElizaCloud AI Integration (March 2026)

All duel arena AI agents now use `@elizaos/plugin-elizacloud` for unified model access (commit 4d1eb53).

**13 Frontier Models:**

**American Models:**

* `openai/gpt-5` - GPT-5
* `anthropic/claude-sonnet-4.6` - Claude Sonnet 4.6
* `anthropic/claude-opus-4.6` - Claude Opus 4.6
* `google/gemini-3.1-pro-preview` - Gemini 3.1 Pro
* `xai/grok-4` - Grok 4
* `meta/llama-4-maverick` - Llama 4 Maverick
* `mistral/magistral-medium` - Magistral Medium

**Chinese Models:**

* `deepseek/deepseek-v3.2` - DeepSeek V3.2
* `alibaba/qwen3-max` - Qwen 3 Max
* `minimax/minimax-m2.5` - Minimax M2.5
* `zai/glm-5` - GLM-5
* `moonshotai/kimi-k2.5` - Kimi K2.5
* `bytedance/seed-1.8` - Seed 1.8

**Configuration:**

```bash theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
# Single API key for all models
ELIZAOS_CLOUD_API_KEY=your-elizacloud-api-key
```

**Benefits:**

* Simplified configuration (one API key instead of multiple provider keys)
* Access to 13 frontier models from 13 different providers
* Consistent model routing and error handling
* Reduced dependency complexity

**Files:**

* `src/eliza/agentHelpers.ts` - Added `elizacloud` provider
* `src/eliza/ModelAgentSpawner.ts` - Updated model agent spawning
* `packages/plugin-hyperscape/src/index.ts` - Added ElizaCloud plugin types

## Duel Arena Oracle (March 2026)

The server publishes duel outcomes to EVM and Solana oracle contracts for verifiable results (commit aecab58).

**New Oracle Fields:**

* `damageA` - Total damage dealt by participant A
* `damageB` - Total damage dealt by participant B
* `winReason` - Detailed win reason (knockout, timeout, forfeit, draw)
* `seed` - Cryptographic seed for replay verification
* `replayHashHex` - Hash of replay data for integrity verification
* `resultHashHex` - Combined hash of all duel outcome data

**Configuration:**

```bash theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
# Enable oracle publishing
DUEL_ARENA_ORACLE_ENABLED=true
DUEL_ARENA_ORACLE_PROFILE=testnet  # or mainnet

# Metadata API base URL
DUEL_ARENA_ORACLE_METADATA_BASE_URL=https://api.hyperscape.gg/api/duel-arena/oracle

# EVM signer (works across Base, BSC, Avalanche)
DUEL_ARENA_ORACLE_EVM_PRIVATE_KEY=0x...

# Solana signers
DUEL_ARENA_ORACLE_SOLANA_AUTHORITY_SECRET=...
DUEL_ARENA_ORACLE_SOLANA_REPORTER_SECRET=...
```

**Database Schema:**

Oracle records are stored in the `arena_rounds` table with comprehensive outcome data for betting market settlement and replay verification.

**Files:**

* `src/oracle/DuelArenaOraclePublisher.ts` - Oracle publishing logic
* `src/oracle/config.ts` - Oracle configuration and target management
* `src/oracle/types.ts` - Oracle type definitions

## Duel System

The server includes a comprehensive duel arena system with AI combat, streaming, and betting integration.

### Combat Roles (PR #933, commit 82ff784)

Duel agents are assigned weighted random combat roles:

* **Melee** (50%): Bronze weapons (longsword, scimitar, 2h sword)
* **Ranged** (25%): Shortbow + bronze arrows (500 qty)
* **Mage** (25%): Staff of air + wind strike autocast + runes

**Implementation:**

* `DuelOrchestrator.pickCombatRole()`: Weighted random selection
* `DuelOrchestrator.ensureAgentCombatSetup()`: Role-specific gear equipping
* `DuelOrchestrator.cleanupAgentCombatSetup()`: Full gear removal after duel
* `DuelCombatAI`: Adapts style switching based on combat role

**Gear Lifecycle:**

1. Pre-duel: Role selected → gear equipped → food filled → health restored
2. Post-duel: Gear removed → food removed → health restored → teleport back

### Critical Bug Fixes (PR #933)

**Combat State Key Mismatch:**

* Issue: CombatStateService syncs abbreviated keys (`data.c`/`data.ct`) but `getGameState()` only read full keys
* Impact: DuelCombatAI always saw `inCombat=false` and flooded `executeAttack` every tick
* Fix: EmbeddedHyperscapeService now reads both abbreviated and full keys
* File: `packages/server/src/eliza/EmbeddedHyperscapeService.ts`

**Magic Attack TOCTOU Race:**

* Issue: Cooldown checked early but claimed after async `consumeRunesForSpell` call
* Impact: Duplicate magic projectiles, double rune consumption
* Fix: Moved cooldown claim and `enterCombat` before async rune consumption
* File: `packages/shared/src/systems/shared/combat/CombatSystem.ts`

### Terrain Flat Zones (commit 7a60135)

DuelArenaVisualsSystem registers flat zones programmatically for all 8 floor areas (6 arenas + lobby + hospital):

* Prevents players/agents from sinking \~0.4m into arena floors
* Terrain height queries return correct floor-level values
* Terrain mesh carved under floors to prevent grass/vegetation clipping
* File: `packages/shared/src/systems/client/DuelArenaVisualsSystem.ts`

## Key Files

| File                                  | Purpose                                              |
| ------------------------------------- | ---------------------------------------------------- |
| `src/index.ts`                        | Server entry point                                   |
| `src/database/`                       | Database schema and queries                          |
| `src/startup/`                        | Initialization logic                                 |
| `src/arena/`                          | Duel arena system (betting, combat AI, market maker) |
| `src/eliza/`                          | ElizaOS AI agent integration                         |
| `src/systems/StreamingDuelScheduler/` | Streaming duel orchestration                         |
| `docker-compose.yml`                  | Docker service configuration                         |
| `.env.example`                        | Environment template                                 |
