AGENTS.md
Instructions for AI coding assistants (GitHub Copilot, Cursor, etc.) working with this codebase.CRITICAL: WebGPU Required (NO WebGL)
Hyperscape requires WebGPU. WebGL WILL NOT WORK. This is a hard requirement. DO NOT:- Add WebGL fallback code
- Use
--disable-webgpuflags - Use
forceWebGLparameters - Create headless Chrome configurations without GPU/WebGPU support
- Suggest WebGL as an alternative
Why WebGPU-Only?
- All materials use TSL (Three Shading Language) which ONLY works with WebGPU
- Post-processing effects use TSL-based node materials
- There is NO WebGL fallback path - the game simply won’t render
Browser Requirements
- Chrome 113+, Edge 113+, Safari 18+ (macOS 15+)
- WebGPU must be available and working
- Check: webgpureport.org
- Note: Safari 17 support was removed - Safari 18+ (macOS 15+) is now required
Server/Streaming (Vast.ai)
- NVIDIA GPU with Display Driver REQUIRED: Must have
gpu_display_active=trueon Vast.ai - Display Driver vs Compute: WebGPU requires GPU display driver support, not just compute access
- Must run non-headless with Xorg or Xvfb (WebGPU requires window context)
- Chrome Beta Channel: Use
google-chrome-betafor WebGPU streaming (better stability than Dev/Canary) - ANGLE Backend: Use Vulkan ANGLE backend (
--use-angle=vulkan) on Linux NVIDIA for WebGPU stability - Xvfb Virtual Display:
scripts/deploy-vast.shstarts Xvfb before PM2 to ensure DISPLAY is available - PM2 Environment:
ecosystem.config.cjsexplicitly forwardsDISPLAY=:99andDATABASE_URLthrough PM2 - Capture Mode: Default to
STREAM_CAPTURE_MODE=cdp(Chrome DevTools Protocol) for reliable frame capture - FFmpeg: Prefer system ffmpeg over ffmpeg-static to avoid segfaults (resolution order:
/usr/bin/ffmpeg→/usr/local/bin/ffmpeg→ PATH → ffmpeg-static) - Playwright: Block
--enable-unsafe-swiftshaderinjection to prevent CPU software rendering - If WebGPU cannot initialize, deployment MUST FAIL
Project Overview
Hyperscape is a RuneScape-style MMORPG built on Three.js WebGPURenderer with TSL shaders.CRITICAL: Secrets and Private Keys
Never put private keys, seed phrases, API keys, tokens, RPC secrets, or wallet secrets into any file that could be committed.- ALWAYS use local untracked
.envfiles for real secrets - NEVER hardcode secrets in source files, tests, docs, JSON fixtures, scripts, config files, or workflow YAML
- NEVER put real secrets in
.env.example; placeholders only - If a secret is needed in production or CI, use the platform secret store, not a tracked file
- If a task requires a new secret, document the variable name and load it from
.env,.env.local, or deployment secrets
Key Rules
- No
anytypes - ESLint will reject them - WebGPU only - No WebGL code or fallbacks
- No mocks in tests - Use real Playwright browser sessions
- Bun package manager - Use
bun install, not npm - Strong typing - Prefer classes over interfaces
- Secrets stay out of git - Real keys must only come from local
.envfiles or secret managers
Tech Stack
- Runtime: Bun v1.1.38+
- Rendering: WebGPU ONLY (Three.js WebGPURenderer + TSL)
- Engine: Three.js 0.183.2, PhysX (WASM)
- UI: React 19.2.0
- Server: Fastify, WebSockets
- Database: PostgreSQL (production, connection pool: 20), Docker (local)
- Testing: Vitest 4.x (upgraded from 2.x for Vite 6 compatibility), Playwright (WebGPU-enabled browsers only)
- AI: ElizaOS
alphatag (aligned with latest alpha releases) - Streaming: FFmpeg (system preferred over ffmpeg-static), Playwright Chromium, RTMP
- Mobile: Capacitor 8.2.0 (Android, iOS)
Common Commands
File Structure
gold-betting-demo, evm-contracts, sim-engine, market-maker-bot) has been split into a separate repository: HyperscapeAI/hyperbet
Recent Changes (March 2026)
Streaming Frame Pacing Fix (March 11, 2026)
Change (Commits 522fe37, e2c9fbf): Enforced 30fps frame pacing to eliminate stream buffering. Problem: CDP screencast was delivering frames at ~60fps while FFmpeg expected 30fps input, causing buffer buildup and viewer lag. Initial fix (522fe37) seteveryNthFrame: 2 to halve compositor delivery, but this was incorrect - Xvfb compositor runs at 30fps (no vsync), not 60fps.
Fix:
- Reverted everyNthFrame to 1 (commit e2c9fbf) - Xvfb compositor delivers at 30fps, so no frame skipping needed
- Frame Pacing Guard: Skip frames arriving faster than 85% of the 33.3ms target interval (prevents burst-feeding FFmpeg)
- Output Resolution: Default changed from 1920x1080→1280x720 to match capture viewport and eliminate unnecessary upscaling
- Xvfb runs at 30fps without vsync (game is capped at 30fps)
everyNthFrame: 2would halve 30fps delivery to 15fps, causing FFmpeg underflow- Frame pacing guard handles edge cases where compositor exceeds TARGET_FPS
- 1280x720 matches capture viewport, eliminating upscaling overhead
BankTabBar Test Updates (March 11, 2026)
Change (Commits 297539f, be2503f): Fixed BankTabBar tests to be environment-agnostic for color formats. Problem: Tests were failing due to browser differences in CSS color representation (hex vs rgb). Fix: Updated test assertions to handle both hex and rgb color formats, matching the gradient background style. Impact: More reliable tests across different browser environments.Test Infrastructure Updates (March 11, 2026)
Change (Commit cd253d5, 97b7a4e): Fixed monorepo test failures and excluded WebGPU-dependent packages from CI. Key Changes:- CI Test Exclusions: Excluded
@hyperscape/impostorfrom headless CI test runs (requires WebGPU, unavailable on GitHub Actions runners) - Test Timeouts: Increased
sim-engineguarded MEV fee sweep test timeout from 60s to 120s to prevent flaky CI failures - Cyclic Dependencies: Resolved circular dependency issues in monorepo package structure
- Port Conflicts: Fixed port allocation conflicts between test suites
- WebGPU-dependent packages (
impostor,client) require local testing with GPU-enabled browsers - Headless CI focuses on server-side logic, data processing, and non-rendering systems
- Full integration tests run locally or on GPU-enabled CI runners (not GitHub Actions)
Manifest File Loading Fix (March 10, 2026)
Change (Commit c0898fa): Fixed legacy manifest entries that 404 on CDN. Problem:DataManager was attempting to fetch items.json and resources.json as root-level files, but these never existed - items are stored as split category files (items/weapons.json, items/armor.json, etc.).
Fix:
- Removed legacy
items.jsonandresources.jsonfrom manifest fetch list - Added missing newer manifests to
MANIFEST_FILESfetch list:ammunition.jsoncombat-spells.jsonduel-arenas.jsonlod-settings.jsonquests.jsonrunes.json
Three.js 0.183.2 Upgrade (March 10, 2026)
Change (Commit 8b93772): Upgraded Three.js from 0.182.0 to 0.183.2 across all packages. Breaking Changes:- TSL API Change:
atan2renamed toatanin TSL exports - Type Compatibility: Updated TSL typed node aliases (TSLNodeFloat/Vec2/Vec3/Vec4)
Streaming Pipeline Optimization (March 10, 2026)
Change (Commits c0e7313, 796b61f): Major streaming pipeline overhaul with CDP default, default ANGLE backend, and FFmpeg improvements. Key Changes:- Default Capture Mode: CDP (Chrome DevTools Protocol) everywhere for reliability
- Chrome Beta Channel: Switched from Chrome Unstable to Chrome Beta for better stability
- ANGLE Backend: Default ANGLE backend (
--use-angle=default) for automatic best-backend selection - FFmpeg Resolution: Prefer system ffmpeg (
/usr/bin,/usr/local/bin) over ffmpeg-static to avoid segfaults - x264 Tuning: Default to
zerolatencytune for live streaming (wasfilm) - GOP Size: Set to 30 frames (1s at 30fps) to match HLS segment boundaries
- Playwright Fix: Block
--enable-unsafe-swiftshaderinjection to prevent CPU software rendering - Dead Code Removal: Deleted
dev-final.mjs(875 lines), removedSERVER_DEV_LEAN_MODEsystem
--use-angle=gl) fails with “Invalid visual ID” on NVIDIA GPUs. Native Vulkan (--use-vulkan) crashes. Only ANGLE’s Vulkan backend works reliably for WebGPU streaming on Linux NVIDIA hardware.
FFmpeg Improvements:
Physics Optimization for Streaming (March 10, 2026)
Change (Commit c0e7313): Skip client-side PhysX initialization for streaming/spectator viewports. Rationale: Streaming and spectator clients don’t need physics simulation - they only render the world state. Impact: Faster streaming client startup, reduced memory footprint for spectator views.Service Worker Cache Strategy (March 10, 2026)
Change (Commit 796b61f): Switched Workbox caching fromCacheFirst to NetworkFirst for JS/CSS.
Problem: Stale service worker serving old HTML for JS chunks after rebuild.
Solution: NetworkFirst strategy - always fetch latest, fallback to cache.
Impact: Eliminates stale module errors after rebuilds, better dev experience.
WebSocket Connection Stability (March 10, 2026)
Change (Commit 3b4dc66): Fixed WebSocket disconnects under load. Impact: More stable multiplayer connections during high-load scenarios.CDN URL Unification (Commit 2173086)
Change: ReplacedDUEL_PUBLIC_CDN_URL with unified PUBLIC_CDN_URL environment variable.
Rationale: Simplifies CDN configuration by using a single environment variable across all contexts instead of separate duel-specific and general CDN URLs.
Configuration:
- Cleaner environment variable naming
- Consistent CDN URL across client, server, and streaming contexts
- Reduces configuration complexity
Dependency Updates (March 10, 2026)
Major Updates:- Capacitor: 7.6.0 → 8.2.0 (Android, iOS, Core)
- lucide-react: → 0.577.0 (icon library)
- three-mesh-bvh: 0.8.3 → 0.9.9 (BVH acceleration)
- eslint: → 10.0.3 (linting)
- jsdom: → 28.1.0 (testing)
- @ai-sdk/openai: → 3.0.41 (AI SDK)
- hardhat: → 3.1.11 (smart contracts)
- @nomicfoundation/hardhat-chai-matchers: → 3.0.0 (testing)
- globals: → 17.4.0 (TypeScript globals)
- Latest mobile platform features (Capacitor 8.2.0)
- Improved icon library with new icons
- Better BVH performance for collision detection
- Latest linting rules and TypeScript support
- Bug fixes and security updates
Deployment Fixes (March 11, 2026)
Change (Commits a65a308, 9e6f5bb): Fixed SSH session timeout and orphaned process deadlocks during Vast.ai deployments. Problem 1 - SSH Timeout: Background processes (Xvfb, socat) were keeping SSH session file descriptors open, causingappleboy/ssh-action to hang for 30 minutes until command_timeout killed it - even though deployment completed in ~1 minute.
Fix 1: Added disown after each background process in scripts/deploy-vast.sh to detach them from the shell’s job table, allowing SSH to exit cleanly.
Problem 2 - Orphaned Bun Processes: PM2 kill command was failing to terminate orphaned bun child processes (game server instances), causing them to hold database connections and deadlock subsequent deployments.
Fix 2: Added explicit pkill commands in scripts/deploy-vast.sh to kill orphaned bun server processes before starting new deployment:
- Deployment completes in ~1 minute instead of hanging for 30 minutes
- Eliminates database connection deadlocks from ghost game servers
- CI/CD pipeline runs faster and more reliably
- No more false timeout failures or deployment hangs