Duel Stack (bun run duel)
bun run duel now boots the end-to-end agent duel arena stack:
- Game server + client (streaming duel scheduler enabled)
- Duel matchmaker bots (
dev:duel:skip-dev) - RTMP bridge fanout to public platforms (YouTube/Twitch/etc.)
- Betting app (testnet mode)
- Keeper bot (testnet automation)
Run
bun run duel now bootstraps streaming prerequisites automatically on first run:
- uses system FFmpeg by default (resolution order:
/usr/bin/ffmpeg→/usr/local/bin/ffmpeg→ PATH →ffmpeg-static) - auto-installs Playwright Chromium if the bundled browser is missing
- installs Chrome Beta on Linux for WebGPU streaming (via
deploy-vast.sh)
Streaming Capture Configuration
Capture Modes
CDP Mode (default, recommended as of March 10, 2026):- Uses Chrome DevTools Protocol
Page.startScreencast - Most reliable for production streaming
- Works well with Chrome Beta + default ANGLE backend
- Lower overhead than MediaRecorder mode
- Uses native browser
canvas.captureStream()API - WebSocket transport to FFmpeg
- Requires
internalCapture=1URL parameter - May have compatibility issues with some GPU configurations
Chrome Channel Selection
- chrome-beta (recommended): Better stability than unstable/canary, reliable WebGPU support
- chrome (macOS): Standard Chrome on macOS
- chrome-dev: Development channel (less stable than beta)
- chromium: Playwright bundled Chromium (fallback)
ANGLE Backend Selection
- default (recommended): Auto-selects best backend (Vulkan, OpenGL, or D3D11) for the system
- metal (macOS): Metal backend for macOS
- vulkan (Linux NVIDIA): Vulkan ANGLE backend (use if default fails on NVIDIA GPUs)
- gl: OpenGL backend (fallback for older GPUs)
- Automatically selects the best backend for your GPU and driver configuration
- Better cross-platform compatibility
- Reduces rendering artifacts and crashes
- Simpler configuration - no platform-specific logic needed
FFmpeg Configuration
FFmpeg Resolution Order (March 10, 2026):- Avoids segfaults that occur with ffmpeg-static on some systems
- Better performance with native system libraries
- More reliable for long-running streams
Playwright Configuration
Critical: Block Playwright’s--enable-unsafe-swiftshader injection to prevent CPU software rendering:
--enable-unsafe-swiftshader by default, forcing Chrome to use CPU-based software rendering instead of GPU acceleration. This blocks the WebGPU compositor pipeline and causes rendering failures.
Streaming Outputs
Configure the following env vars (root.env or packages/server/.env):
RTMP_MULTIPLEXER_URL(+ optionalRTMP_MULTIPLEXER_STREAM_KEY,RTMP_MULTIPLEXER_NAME)TWITCH_STREAM_KEY(orTWITCH_RTMP_STREAM_KEY) Optional ingest override:TWITCH_STREAM_URL/TWITCH_RTMP_URL/TWITCH_RTMP_SERVERYOUTUBE_STREAM_KEY(orYOUTUBE_RTMP_STREAM_KEY) Optional ingest override:YOUTUBE_STREAM_URL/YOUTUBE_RTMP_URLKICK_STREAM_KEY(+ optionalKICK_RTMP_URL)PUMPFUN_RTMP_URL(+ optionalPUMPFUN_STREAM_KEY)X_RTMP_URL(+ optionalX_STREAM_KEY)RTMP_DESTINATIONS_JSONfor additional/custom fanout destinationsSTREAMING_VIEWER_ACCESS_TOKENoptional gate for live WebSocket stream/spectator viewers
STREAM_ENABLED_DESTINATIONS to override (e.g., twitch,kick,youtube).
Default anti-cheat timing policy (no env required):
- Canonical platform:
youtube - Default public delay:
15000ms - Optional:
STREAMING_CANONICAL_PLATFORM(youtube|twitch) - Optional override:
STREAMING_PUBLIC_DELAY_MS
0 if server delay is enabled):
VITE_UI_SYNC_DELAY_MS
NEXT_PUBLIC_ARENA_STREAM_EMBED_URL(inpackages/website/.env.local)VITE_STREAM_EMBED_URL(in the Hyperbet app.env*files if you boot the sibling repo locally)
STREAMING_PUBLIC_DELAY_MS > 0, live mode=streaming WebSocket viewers are restricted to:
- loopback/local capture clients, or
- clients presenting
streamToken=<STREAMING_VIEWER_ACCESS_TOKEN>
stream-to-rtmp automatically appends streamToken to capture URLs when STREAMING_VIEWER_ACCESS_TOKEN is set.
Spectator + Betting URLs
- Game stream view:
http://localhost:3333/?page=stream - Embedded spectator:
http://localhost:3333/?embedded=true&mode=spectator - Betting app:
http://localhost:4179(see HyperscapeAI/hyperbet) - Betting video source:
VITE_STREAM_EMBED_URL(YouTube/Twitch embed URL)
Open APIs (duel telemetry + monologues)
GET /api/streaming/stateGET /api/streaming/duel-contextGET /api/streaming/agent/:characterId/inventoryGET /api/streaming/agent/:characterId/monologues?limit=20
Verification
Run the full startup verifier against a running stack:--require-destinations.
Troubleshooting
Stream Freezing or Stalling
Problem: Stream freezes or stalls under Xvfb + WebGPU on Vast instances. Solution: Use MediaRecorder mode (default since March 2026):canvas.captureStream() which is more reliable.
WebGPU Initialization Failed
Problem: “WebGPU not available” or rendering artifacts. Solution: Verify GPU display driver and Chrome Beta configuration:- GPU instance with
gpu_display_active=true - NVIDIA GPU with display driver support (not just compute)
- Xvfb virtual display running before PM2 starts
Stream Destinations Not Detected
Problem: RTMP streams not starting despite stream keys being set. Solution: Verify stream key environment variables:STREAM_ENABLED_DESTINATIONS is not set, it’s auto-detected from:
TWITCH_STREAM_KEYorTWITCH_RTMP_STREAM_KEY→ addstwitchKICK_STREAM_KEY→ addskickYOUTUBE_STREAM_KEYorYOUTUBE_RTMP_STREAM_KEY→ addsyoutube
Database Connection Errors
Problem: “timeout exceeded when trying to connect” or FATAL database errors. Solution: Connection pool increased to 20 (March 2026). Verify configuration:ecosystem.config.cjs explicitly forwards DATABASE_URL through PM2 environment.
CSRF 403 Errors
Problem: Account creation fails with “CSRF validation failed” when running client on localhost against deployed server. Solution: Fixed in March 2026 (commit 0b1a0bd). Ensure:- Client includes Privy auth token in Authorization header
- Server CSRF middleware allows localhost/private IP origins
- Both
{ token }and{ csrfToken }response formats are supported
CDN Asset Loading Issues
Problem: Assets fail to load (404 errors) in production streaming deployments. Solution: Verify CDN URL configuration:Recent Changes (March 2026)
MediaRecorder Streaming Capture (March 10, 2026)
Change: Switched from CDP screencast to MediaRecorder mode for streaming capture. Rationale: CDP screencast stalls under Xvfb + WebGPU on Vast instances. MediaRecorder usescanvas.captureStream() → WebSocket → FFmpeg which is more reliable for headed Linux environments.
Configuration:
Chrome Beta for Streaming (March 9, 2026)
Change: Switched from Chrome Unstable to Chrome Beta for better stability. Configuration:Database Auto-Detection (March 9-10, 2026)
Change: Database mode now auto-detected fromDATABASE_URL hostname.
Logic:
- localhost/127.0.0.1/0.0.0.0/::1 → local mode
- All other hostnames → remote mode
- Manual override via
DUEL_DATABASE_MODE=remote
PostgreSQL Connection Pool Increase (March 10, 2026)
Change: Increased connection pool from 10 to 20 connections. Configuration:PM2 Secrets Loading (March 9, 2026)
Change:ecosystem.config.cjs now reads /tmp/hyperscape-secrets.env directly at config load time.
Rationale: bunx pm2 doesn’t reliably inherit exported environment variables from deploy shell scripts.
Impact: Ensures DATABASE_URL and stream keys are always available to PM2-managed processes.
Xvfb Display Environment (March 9, 2026)
Change:ecosystem.config.cjs explicitly sets DISPLAY=:99 in PM2 environment.
Impact: Ensures streaming processes can access Xvfb virtual display for WebGPU rendering.
Stream Destination Auto-Detection (March 9, 2026)
Change: Stream destinations now auto-detected from available stream keys. Logic:deploy-vast.sh detects enabled destinations using || logic:
Streaming Entry Points (March 10, 2026)
Change: Added dedicated streaming entry points for optimized capture. New Files:packages/client/src/stream.html- Dedicated HTML entry for streaming capturepackages/client/src/stream.tsx- React entry point for streaming modepackages/shared/src/runtime/clientViewportMode.ts- Viewport mode detection utility
- Main game:
index.html→dist/index.html - Streaming:
stream.html→dist/stream.html - Separate bundles optimize for different use cases
- Optimized streaming capture with minimal UI overhead
- Clear separation between game and streaming entry points
- Automatic viewport mode detection for conditional rendering (e.g., skip PhysX for streaming)