Overview
The @hyperscape/server package runs the authoritative game server:
- Fastify 5 HTTP API with rate limiting
- WebSocket real-time game state
- PostgreSQL database with Drizzle ORM
- LiveKit voice chat integration
- CDN manifest loading at startup
- Integrated frontend serving (production mode)
Package Location
packages/server/
├── src/
│ ├── database/ # Database schema and queries
│ │ ├── repositories/ # Data access layer
│ │ ├── migrations/ # Database migrations
│ │ └── schema.ts # Drizzle schema definitions
│ ├── infrastructure/ # Docker, CDN configuration
│ ├── scripts/ # Utility scripts
│ ├── shared/ # Shared server utilities
│ ├── startup/ # Server initialization
│ │ └── routes/ # API route handlers
│ ├── systems/ # Server-specific systems
│ │ ├── ServerNetwork/ # Network handlers
│ │ │ └── handlers/ # Event handlers (bank, trade, friends, etc.)
│ │ ├── TradingSystem/ # Player trading system
│ │ ├── DatabaseSystem/ # Database persistence
│ │ └── ActivityLoggerSystem/ # Activity logging
│ ├── 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:
- Server configuration and environment loading
- Manifest fetching from CDN (production) or local cache (development)
- Database connection (PostgreSQL via Drizzle ORM)
- Fastify HTTP server with CORS, rate limiting, and static file serving
- WebSocket handlers for real-time multiplayer
- Game world and ECS systems
- Frontend serving (if client build exists in
public/)
Manifest Loading
The server fetches game manifests from CDN at startup:
// From src/startup/config.ts
const MANIFEST_FILES = [
'biomes.json',
'npcs.json',
'items/weapons.json',
'recipes/smithing.json',
// ... 25+ manifest files
];
// Fetched from PUBLIC_CDN_URL/manifests/
await fetchManifestsFromCDN(cdnUrl, manifestsDir, nodeEnv);
Behavior:
- Production/CI: Always fetches from CDN, caches locally
- Development: Skips fetch if local manifests exist
- Caching: Manifests cached in
world/assets/manifests/ with 5-minute TTL
This eliminates the need for Git LFS in production deployments.
Server Systems
TradingSystem
Located in src/systems/TradingSystem/:
Modular player-to-player trading system with:
- acceptance.ts: Accept/decline logic
- items.ts: Add/remove items from offers
- request.ts: Trade initiation with proximity checks
- swap.ts: Screen transitions (Screen 1 ↔ Screen 2)
- helpers.ts: Shared utilities and validation
- types.ts: Type definitions
Features:
- Two-screen confirmation flow (OSRS-accurate)
- Wealth transfer indicators
- Proximity validation (2-tile range)
- Interface blocking (prevents exploits)
- Audit logging for all trades
ServerNetwork Handlers
Located in src/systems/ServerNetwork/handlers/:
| Handler | Purpose |
|---|
bank/ | Banking operations (deposit, withdraw, tabs, coins, equipment) |
trade/ | Trading system handlers |
friends.ts | Friend management and private messaging |
combat.ts | Combat actions and validation |
inventory.ts | Inventory management |
player.ts | Player state updates |
chat.ts | Chat message handling |
dialogue.ts | NPC dialogue |
quest.ts | Quest progression |
resources.ts | Resource gathering |
store.ts | Shop transactions |
prayer.ts | Prayer system |
home-teleport.ts | Home teleport functionality |
API Routes
Core Endpoints
| Route | Method | Purpose |
|---|
/status | GET | Health check (used by Railway) |
/health | GET | Legacy health check |
/ws | WS | Game WebSocket connection |
/api/agent/* | Various | ElizaOS agent API endpoints |
/api/character/* | Various | Character management |
/api/player/* | Various | Player data queries |
/api/template/* | Various | Character templates |
Database
The server supports both PostgreSQL (production via Neon) and SQLite (local development). Schema is defined with Drizzle ORM.
Key Tables
// 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 |
friendships | Bidirectional friend relationships |
friend_requests | Pending friend requests |
ignore_lists | Blocked players per account |
Database Commands
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
Core Configuration
# Server
PORT=5555 # HTTP/WebSocket port
NODE_ENV=development # Environment: development, production, test
WORLD=world # World directory path
# Database
DATABASE_URL=postgresql://user:pass@host/db # PostgreSQL connection string
USE_LOCAL_POSTGRES=true # Auto-start PostgreSQL in Docker (dev only)
# Security
JWT_SECRET=your-jwt-secret # Token signing (generate with: openssl rand -base64 32)
ADMIN_CODE=your-admin-code # In-game admin access code
Authentication
# Privy (required for persistent accounts)
PUBLIC_PRIVY_APP_ID=your-privy-app-id
PRIVY_APP_SECRET=your-privy-app-secret
PUBLIC_PRIVY_APP_ID must match between client and server.
Assets & CDN
# CDN URL for game assets (models, textures, audio, manifests)
PUBLIC_CDN_URL=http://localhost:8080 # Development
# PUBLIC_CDN_URL=https://assets.hyperscape.club # Production
# WebSocket and API URLs (exposed to client)
PUBLIC_WS_URL=ws://localhost:5555/ws
PUBLIC_API_URL=http://localhost:5555
Manifest Loading:
- Server fetches manifests from
PUBLIC_CDN_URL/manifests/ at startup
- Cached locally in
world/assets/manifests/
- In development, skips fetch if local manifests exist
- In production/CI, always fetches fresh manifests
Optional Services
# ElizaOS AI Integration
ELIZAOS_API_URL=http://localhost:4001
# LiveKit Voice Chat
LIVEKIT_API_KEY=your-livekit-key
LIVEKIT_API_SECRET=your-livekit-secret
LIVEKIT_URL=wss://your-livekit-server
# Advanced
SAVE_INTERVAL=60 # Auto-save interval in seconds
DISABLE_RATE_LIMIT=false # Disable rate limiting (dev only!)
COMMIT_HASH=abc123 # Git commit hash (auto-set by CI)
CORS Origins
The server automatically allows requests from:
// Production domains
'https://hyperscape.club'
'https://www.hyperscape.club'
'https://hyperscape.pages.dev'
'https://hyperscape-production.up.railway.app'
// Dynamic patterns
/^https?:\/\/localhost:\d+$/ // Any localhost port
/^https?:\/\/.+\.hyperscape\.pages\.dev$/ // Cloudflare preview deployments
/^https:\/\/.+\.up\.railway\.app$/ // Railway preview deployments
Additional origins from environment variables:
CLIENT_URL
PUBLIC_APP_URL
ELIZAOS_URL / ELIZAOS_API_URL
Build Scripts
The server includes build-time scripts for automatic collision footprint detection:
# Extract bounding boxes from GLB models
bun run extract-bounds
# Generates: world/assets/manifests/model-bounds.json
How it works:
- Scans
world/assets/models/**/*.glb files
- Parses glTF position accessor min/max values
- Calculates bounding boxes and dimensions
- Computes tile footprints at scale 1.0
- 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:
{
"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 }
}
]
}
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.
Running
Development
bun run dev:server # With hot reload and auto-restart
Production
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 |
pg | PostgreSQL client |
@hyperscape/shared | Core engine |
@privy-io/server-auth | Authentication |
livekit-server-sdk | Voice chat |
msgpackr | Binary serialization |
Docker Services
The server can use Docker for CDN and PostgreSQL:
# 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
Railway (Production)
The server deploys to Railway using Nixpacks for automatic builds:
Configuration Files:
nixpacks.toml - Build configuration (Bun provider, build commands)
railway.server.json - Service configuration (health checks, restart policy)
Dockerfile.server - Multi-stage Docker build (alternative to Nixpacks)
Deployment Process:
- Push to
main branch triggers GitHub Actions
- Workflow calls Railway GraphQL API to trigger deployment
- Railway builds using Nixpacks configuration
- Server starts with
cd packages/server && bun dist/index.js
- Manifests fetched from Cloudflare R2 CDN at startup
Environment Variables (Railway):
NODE_ENV=production
DATABASE_URL=<auto-set-by-railway-postgres>
JWT_SECRET=<generate-with-openssl>
PRIVY_APP_ID=<from-privy-dashboard>
PRIVY_APP_SECRET=<from-privy-dashboard>
PUBLIC_CDN_URL=https://assets.hyperscape.club
CI=true
SKIP_ASSETS=true
Health Check:
Railway monitors /status endpoint every 30 seconds. Server must respond within 3 seconds or it will be restarted.
Manifest Fetching at Startup
In production, the server fetches manifests from the CDN instead of bundling them:
// packages/server/src/startup/config.ts
await fetchManifestsFromCDN(CDN_URL, manifestsDir, NODE_ENV);
Fetched Manifests:
- Root:
npcs.json, stores.json, world-areas.json, etc.
- Items:
items/weapons.json, items/tools.json, etc.
- Gathering:
gathering/woodcutting.json, gathering/mining.json, etc.
- Recipes:
recipes/cooking.json, recipes/smithing.json, etc.
Caching:
- Manifests cached locally in
packages/server/world/assets/manifests/
- Only re-fetched if content changes (HTTP ETag validation)
- 5-minute cache headers (
Cache-Control: public, max-age=300, must-revalidate)
This allows updating game content (items, NPCs, recipes) without redeploying the server—just upload new manifests to R2.
Key Files
| File | Purpose |
|---|
src/index.ts | Server entry point |
src/database/ | Database schema and queries |
src/startup/ | Initialization logic |
docker-compose.yml | Docker service configuration |
.env.example | Environment template |