Skip to main content

Development Commands

Core Commands

CommandDescription
bun run devStart game (client + server + shared)
bun run dev:elizaosGame + ElizaOS AI agents
bun run dev:allEverything: game + AI + AssetForge
bun run buildBuild all packages
bun startStart production server

Package-Specific

# From package.json scripts
bun run dev:client    # Client only (port 3333)
bun run dev:server    # Server only (port 5555)
bun run dev:shared    # Shared package with watch mode
bun run dev:turbo     # Turbo-based dev (excludes asset-forge)
bun run dev:reset     # Clean + rebuild + dev (nuclear option)

# Server build tasks
cd packages/server
bun run extract-bounds  # Generate model-bounds.json from GLB files
bun run build           # Build server (runs extract-bounds automatically)

# Asset Forge (in packages/asset-forge/)
cd packages/asset-forge
bun run dev           # Frontend (Vite) + Backend (Elysia) concurrently
bun run dev:frontend  # Vite only
bun run dev:backend   # Elysia API only

Build Pipeline

The server package includes automated tasks for collision footprint extraction:
# Extract model bounds (auto-runs during build)
bun run extract-bounds
This scans world/assets/models/**/*.glb and generates world/assets/manifests/model-bounds.json with bounding box data for automatic collision footprint calculation. Turbo Caching:
  • Only re-runs when GLB files or script changes
  • Outputs cached between builds
  • Configured in packages/server/turbo.json

Circular Dependency Handling

The build system handles circular dependencies between packages using resilient build patterns:
// From packages/procgen/package.json and packages/plugin-hyperscape/package.json
// Use 'tsc || echo' pattern so build exits 0 even with circular dep errors
"build": "tsc || echo 'Build completed with warnings'"
Circular Dependencies:
  • @hyperscape/shared@hyperscape/procgen (shared imports from procgen, procgen peer-depends on shared)
  • @hyperscape/shared@hyperscape/plugin-hyperscape (similar circular dependency)
Build Strategy:
  • When turbo runs a clean build, tsc fails because the other package’s dist/ doesn’t exist yet
  • The || echo pattern allows the build to exit 0 even with circular dep errors
  • Packages still produce partial output which is sufficient for downstream consumers
  • Shared package always uses --skipLibCheck in declaration generation to handle circular dependencies
This is a known limitation of the current architecture. The packages produce working output despite TypeScript errors during clean builds.

Port Allocation

PortServiceStarted By
3333Game Clientbun run dev
5555Game Serverbun run dev
8080Asset CDNbun run dev
5432PostgreSQLDocker (auto)
4001ElizaOS APIbun run dev:elizaos
3400AssetForge UIbun run dev:forge
3401AssetForge APIbun run dev:forge

Hot Reload

The dev server provides:
  • Client: Vite HMR for instant updates
  • Server: Auto-restart on file changes
  • Shared: Watch mode with rebuild

Asset Management

Hyperscape uses a separate assets repository to keep the main codebase lightweight and prevent manifest divergence.

Asset Repository Structure

  • Main repo (HyperscapeAI/hyperscape): Code only, no asset files
  • Assets repo (HyperscapeAI/assets): All game content (manifests, models, textures, audio) (~200MB with Git LFS)
Change (Feb 2026): Manifests are now sourced exclusively from the assets repo. The main repo no longer tracks any asset files, including JSON manifests.

Local Development

Assets are automatically cloned during bun install:
# Automatic (runs during bun install)
# See scripts/ensure-assets.mjs

# Manual sync (if needed)
bun run assets:sync
The ensure-assets.mjs script:
  1. Checks if packages/server/world/assets/ exists
  2. If missing, clones HyperscapeAI/assets repository
  3. Local dev: Full clone with LFS for models/textures/audio (~200MB)
  4. CI/Production: Shallow clone without LFS (manifests only, ~1MB)

CI/CD Asset Handling

In CI environments, assets are cloned without LFS (manifests only):
# From scripts/ensure-assets.mjs
if (process.env.CI === 'true') {
  // Shallow clone without LFS (manifests only)
  git clone --depth 1 https://github.com/HyperscapeAI/assets.git
  # Manifests are in assets/manifests/ directory
} else {
  // Full clone with LFS for local development
  git clone https://github.com/HyperscapeAI/assets.git
  # Includes models/, textures/, audio/ with Git LFS
}
The entire packages/server/world/assets/ directory is gitignored. Never commit asset files to the main repository. All game content lives in the HyperscapeAI/assets repository.

Asset Updates

When assets are updated in the assets repository:
  1. Pull latest assets: bun run assets:sync
  2. Manifests and models are automatically updated
  3. Restart server to reload manifests
Benefits of Separate Assets Repo:
  • Single source of truth for all game content
  • Prevents manifest divergence between repos
  • Reduces main repo size (no binary commits)
  • Cleaner git history
  • Easier asset updates (PR to assets repo, not main repo)
  • CI gets manifests without downloading large binary files

Manifest Files

All manifest JSON files are sourced from the assets repo:
HyperscapeAI/assets/
├── manifests/
│   ├── items/
│   │   ├── weapons.json
│   │   ├── tools.json
│   │   ├── resources.json
│   │   ├── food.json
│   │   ├── armor.json
│   │   ├── ammunition.json
│   │   └── runes.json
│   ├── gathering/
│   │   ├── woodcutting.json
│   │   ├── mining.json
│   │   └── fishing.json
│   ├── recipes/
│   │   ├── smelting.json
│   │   ├── smithing.json
│   │   ├── cooking.json
│   │   └── firemaking.json
│   ├── npcs.json
│   ├── stores.json
│   ├── world-areas.json
│   ├── biomes.json
│   ├── vegetation.json
│   ├── combat-spells.json
│   ├── duel-arenas.json
│   ├── tier-requirements.json
│   └── skill-unlocks.json
├── models/          # 3D models (GLB/VRM)
├── textures/        # Texture files
└── audio/           # Music and sound effects
Manifest Loading: The server’s DataManager loads manifests from packages/server/world/assets/manifests/ which is populated by cloning the assets repo.

Docker Services

bun run cdn:up      # Start CDN container
bun run cdn:down    # Stop CDN container
The CDN starts automatically with bun run dev. Only run manually if using services separately.

Testing

npm test                              # Run all tests
npm test --workspace=packages/server  # Test specific package
npm test --workspace=packages/client  # Client E2E tests
Tests use real Hyperscape instances with Playwright—no mocks allowed. The server must not be running before tests.

E2E Journey Tests (NEW - February 2026)

Comprehensive end-to-end tests validate the complete player journey from login to gameplay: Test File: packages/client/tests/e2e/complete-journey.spec.ts
1

Login Flow

Full authentication and character selection
2

Loading Screen

Uses waitForLoadingScreenHidden helper for reliable test synchronization
3

Spawn

Character spawns in world with proper initialization
4

Walk

Movement and pathfinding validation
5

Screenshot Comparison

Utilities to verify game is rendering correctly
Key Features:
  • Real Browser Testing: Uses Playwright with actual WebGPU rendering (no mocks)
  • Screenshot Comparison: Visual regression testing to verify rendering
  • Loading Screen Detection: Reliable synchronization helpers
  • Full Gameplay Flow: Tests complete user journey, not isolated features
Test Utilities:
// From packages/client/tests/e2e/utils/visualTesting.ts

// Wait for loading screen to disappear
await waitForLoadingScreenHidden(page);

// Take screenshot for comparison
await page.screenshot({ path: 'test-output/gameplay.png' });

// Verify game is rendering (not black screen)
const isRendering = await verifyGameRendering(page);
expect(isRendering).toBe(true);
Running Journey Tests:
cd packages/client
npm test -- complete-journey.spec.ts

Test Stability Improvements

Recent commits improved test reliability:
  • GoldClob Fuzz Tests: 120s timeout for randomized invariant tests (4 seeds × 140 operations)
  • Precision Fixes: Use larger amounts (10000n) to avoid gas cost precision issues
  • Dynamic Import Timeout: 60s timeout for EmbeddedHyperscapeService beforeEach hooks
  • Anchor Test Configuration: Use localnet instead of devnet for free SOL in anchor test
  • CI Build Order: Build impostors/procgen before shared (dependency fix)

Linting

npm run lint        # Lint codebase
npm run typecheck   # TypeScript type checking

GitHub Actions

The repository includes automated workflows for code quality and documentation:

Claude Code Integration

  • .github/workflows/claude.yml - Responds to @claude mentions in issues and PRs for automated assistance
  • .github/workflows/claude-code-review.yml - Automated code review on pull requests
  • .github/workflows/update-docs.yml - Automatically updates documentation when manifest files change
To use Claude Code:
  1. Comment @claude in any issue or PR
  2. Claude will analyze the context and provide assistance
  3. For code reviews, Claude automatically reviews new PRs
Requires CLAUDE_CODE_OAUTH_TOKEN and MINTLIFY_API_KEY secrets to be configured in repository settings.

Code Quality Improvements (February 2026)

Type Safety Audit (commit d9113595)

Eliminated explicit any types in core game logic: Files Updated:
  • tile-movement.ts - Removed 13 any casts by properly typing BuildingCollisionService
  • proxy-routes.ts - Replaced any with proper types (unknown, Buffer | string, Error)
  • ClientGraphics.ts - Added safe cast after WebGPU verification
Remaining any types:
  • TSL shader code (ProceduralGrass.ts) - @types/three limitation
  • Browser polyfills (polyfills.ts) - intentional mock implementations
  • Test files - acceptable for test fixtures

Memory Leak Fix (commit 3bc59db)

Fixed memory leak in InventoryInteractionSystem using AbortController:
// Before: 9 event listeners never removed
world.on('inventory:add', handler);

// After: Proper cleanup with AbortController
const abortController = new AbortController();
world.on('inventory:add', handler, { signal: abortController.signal });

// Cleanup on system destroy
abortController.abort();

Dead Code Removal (commit 7c3dc985)

Removed 3,098 lines of dead code:
  • Deleted PacketHandlers.ts (never imported, completely unused)
  • Updated audit TODOs to reflect actual codebase state
  • ServerNetwork is already decomposed into 30+ modules (not 116K lines)
  • ClientNetwork handlers are intentional thin wrappers (not bloated)

Build System Fixes

TypeScript Override Conflict (commit 113a85a): Removed conflicting TypeScript overrides from root package.json. The build system now relies on workspace protocol and Turbo’s dependency resolution. Windows Environment Variables (commit 3b7665d): Fixed native app builds on Windows by conditionally supplying secrets:
env:
  TAURI_SIGNING_PRIVATE_KEY: ${{ needs.prepare.outputs.is_release == 'true' && secrets.TAURI_SIGNING_PRIVATE_KEY || '' }}
Linux/Windows Desktop Builds (commit f19a7042): Fixed unsigned builds for Linux and Windows:
# Before: --bundles app (macOS-only, caused Linux/Windows to fail)
# After: --no-bundle (works on all platforms)
tauri build --no-bundle
CI Workflow Matrix Reference (commit a095ba1): Removed invalid matrix reference from job-level condition. Matrix variables are only available during job execution, not at job scheduling time.

Common Workflows

Adding a New Feature

  1. Make changes in packages/shared/src/
  2. Hot reload applies automatically
  3. Test in browser at localhost:3333

Updating Game Content

  1. Edit manifest files in world/assets/manifests/
  2. Restart server to reload manifests
  3. Documentation updates automatically via GitHub Actions

Debugging Server

  1. Check terminal output for errors
  2. Server logs show WebSocket activity
  3. Database queries logged in dev mode

Clean Rebuild

bun run clean
rm -rf node_modules packages/*/node_modules
bun install
bun run build