Skip to main content

Streaming & Betting Integration Guide

Last Updated: March 23, 2026
PR: #1065 - Internal bet sync feed and renderer health

Overview

Hyperscape provides an authenticated internal betting feed that makes the streaming duel lifecycle authoritative for betting market synchronization. This guide covers the betting feed API, renderer health monitoring, and integration patterns for betting consumers.

Architecture

Source of Truth

Hyperscape is the authoritative source for:
  • Duel lifecycle events (announcement, countdown, fight start, resolution)
  • Agent state (HP, equipment, position)
  • Arena state (positions, phase transitions)
  • Renderer health (ready vs degraded states)
Betting consumers (e.g., Hyperbet) subscribe to Hyperscape’s internal feed rather than polling public spectator endpoints.

Components

  1. DuelBettingBridge (packages/server/src/systems/DuelScheduler/DuelBettingBridge.ts)
    • Listens to streaming duel events
    • Creates/syncs/resolves markets with Solana operator
    • Publishes sequence-aware SSE feed
  2. Betting Feed Routes (packages/server/src/routes/streaming-betting-routes.ts)
    • Bootstrap endpoint: GET /api/internal/bet-sync/state
    • SSE feed: GET /api/internal/bet-sync/events
    • Authentication, rate limiting, CORS
  3. Renderer Health (packages/server/src/routes/streaming-betting-health.ts)
    • Derives health from streaming guardrails + external RTMP status
    • Detects degraded states (loading, invalid positions, etc.)
  4. Streaming Guardrails (packages/shared/src/utils/rendering/streamingGuardrails.ts)
    • Shared validation logic (client + server)
    • Agent snapshot validation
    • Arena position sanity checks

API Reference

Bootstrap Endpoint

Endpoint: GET /api/internal/bet-sync/state Authentication: Required
Response:
Example:
Rate Limit: 240 requests/minute per IP

SSE Events Feed

Endpoint: GET /api/internal/bet-sync/events Authentication: Required (Bearer header or ?streamToken= query param)
Query Parameters:
  • since=<sequence> - Resume from specific sequence number (optional)
  • limit=<number> - Max frames in initial replay (default: 100, max: 2048)
Response: Server-Sent Events stream
Replay Delivery Modes:
  • "bootstrap" - Full replay buffer (client is behind or first connection)
  • "incremental" - Frames since since sequence (client is caught up)
  • "reset" - Client sequence is ahead of server (server restarted)
Example:
Rate Limit: 60 requests/minute per IP
Max Clients: 32 concurrent connections (configurable)
Heartbeat: Every 15 seconds

Payload Structure

Renderer Health

Health States

Healthy (ready: true, degradedReason: null):
  • All agents present with valid HP
  • Arena positions are sane (no overlaps)
  • Loading overlay dismissed
  • Camera locked to target (if required)
  • Active phase (ANNOUNCEMENT, COUNTDOWN, FIGHTING)
Degraded (ready: false, degradedReason: <string>): Surface-Level Reasons (client/server not ready):
  • "socket_disconnected" - WebSocket connection lost
  • "world_not_ready" - 3D world not initialized
  • "terrain_not_ready" - Terrain system not loaded
  • "camera_target_unresolved" - Camera hasn’t locked to target
  • "initialization_failed" - World init error
  • "renderer_unavailable" - WebGPU not available
Streaming Guardrail Reasons (duel state invalid):
  • "agent1_invalid" - Agent 1 missing or invalid HP
  • "agent2_invalid" - Agent 2 missing or invalid HP
  • "arena_positions_invalid" - Positions overlapping or missing
Loading/Transition Reasons:
  • "loading_overlay_active" - Loading screen still visible
  • "initializing" - Waiting for duel data (IDLE phase)
  • "waiting_for_duel_data" - No streaming state yet

Client-Side Health Monitoring

Window Globals (exposed for capture pipeline):
Derivation (packages/client/src/screens/StreamingMode.tsx):

Server-Side Health Monitoring

Derivation (packages/server/src/routes/streaming-betting-health.ts):
External RTMP Status (packages/server/src/routes/streaming-external-status.ts):

DuelBettingBridge Lifecycle

State Machine

Event Handlers

Announcement (handleStreamingAnnouncement):
Fight Start (handleStreamingFightStart):
Resolution (handleStreamingResolution):
Abort (handleStreamingAbort):

Reconciliation Loop

Runs every 1 second to ensure market state stays aligned with streaming lifecycle:
Configuration:

Security

Authentication

Timing-Safe Token Comparison (packages/server/src/routes/streaming-betting-auth.ts):
Token Extraction:
Development Bypass:

CORS Configuration

Token Handling Best Practices

Server-Side:
  • Store BETTING_FEED_ACCESS_TOKEN in environment variables or secret manager
  • Use strong random tokens: openssl rand -base64 32
  • Rotate tokens periodically
  • Never log tokens in access logs (use redactStreamingSecretsFromUrl)
Client-Side (for embedded streaming):
  • Pass tokens in URL hash fragments (not query params): #streamToken=...
  • Scrub tokens immediately via history.replaceState
  • Use getStreamingAccessToken() to retrieve cached token
  • Never send tokens in Referer headers (use <meta name="referrer" content="same-origin">)

Renderer Health Monitoring

Health Derivation

Client (packages/client/src/screens/StreamingMode.tsx):
Server (packages/server/src/routes/streaming-betting-health.ts):

Capture Pipeline Integration

Renderer Health Probe (packages/server/scripts/stream-to-rtmp.ts):
Readiness Acceptance (packages/server/src/streaming/captureBrowserPolicy.ts):

Configuration

Environment Variables

Server (packages/server/.env):
Client (packages/client/.env):

Rate Limits

Bootstrap Endpoint:
  • 240 requests/minute per IP
  • No concurrent connection limit (stateless)
SSE Events Endpoint:
  • 60 requests/minute per IP
  • Max 32 concurrent connections (configurable via BETTING_SSE_MAX_CLIENTS)
  • Slow clients evicted when writableLength > STREAMING_SSE_MAX_PENDING_BYTES

Integration Patterns

Betting Consumer (Hyperbet)

Bootstrap on Startup:
Frame Processing:
Reconnection Handling:

Troubleshooting

Authentication Failures

Symptom: 401 Unauthorized on betting feed endpoints Solutions:
  1. Verify BETTING_FEED_ACCESS_TOKEN is set in server .env
  2. Check token is passed correctly (Bearer header or ?streamToken=)
  3. Ensure token matches exactly (no extra whitespace)
  4. Check server logs for “Betting feed auth failed” warnings
Test:

Renderer Health Always Degraded

Symptom: rendererHealth.ready is always false Solutions:
  1. Check degradedReason for specific issue
  2. Verify streaming state is present (hasStreamingState: true)
  3. Check agent snapshots have valid HP (agent.hp <= agent.maxHp)
  4. Verify arena positions are not overlapping
  5. Check loading overlay has dismissed (loadingDismissed: true)
  6. Verify camera has locked to target (if needsCameraLock: true)
Debug:

SSE Connection Drops

Symptom: EventSource closes unexpectedly Solutions:
  1. Check server logs for “Slow client evicted” warnings
  2. Verify client is consuming frames fast enough
  3. Increase STREAMING_SSE_MAX_PENDING_BYTES if needed
  4. Check network stability (SSE requires persistent connection)
  5. Implement reconnection logic with ?since= parameter
Monitoring:

Sequence Gaps

Symptom: Missing sequence numbers in SSE feed Solutions:
  1. Check sourceEpoch - if changed, server restarted (sequence reset)
  2. Use bootstrap endpoint to get full replay buffer
  3. Implement gap detection and re-bootstrap logic
  4. Check mode field in SSE delivery:
    • "bootstrap" - Full buffer (client is behind)
    • "incremental" - Frames since since (client is caught up)
    • "reset" - Client is ahead (server restarted)
Example:

Testing

Unit Tests

Betting Feed Auth (packages/server/src/routes/__tests__/streaming-betting-auth.test.ts):
  • Token extraction from Bearer header and query params
  • Timing-safe comparison
  • Development bypass logic
  • Production fail-closed behavior
Betting Feed Payload (packages/server/src/routes/__tests__/streaming-betting-feed.test.ts):
  • Payload construction
  • Replay delivery modes (bootstrap, incremental, reset)
  • Frame ordering and deduplication
DuelBettingBridge (packages/server/src/systems/DuelScheduler/__tests__/DuelBettingBridge.test.ts):
  • Lifecycle transitions (announcement → fight → resolution)
  • Reconciliation loop
  • Abort handling
  • Error recovery
Streaming Guardrails (packages/shared/src/utils/rendering/__tests__/streamingGuardrails.test.ts):
  • Agent snapshot validation
  • Arena position validation
  • Phase-specific requirements

Integration Tests

StreamingMode Component (packages/client/tests/unit/screens/StreamingMode.component.test.tsx):
  • Loading overlay dismissal
  • Renderer health globals
  • Token passing to WebSocket
  • Init error handling
Renderer Health Derivation (packages/client/tests/unit/screens/StreamingMode.test.ts):
  • Surface-level blocking reasons
  • Streaming guardrail reasons
  • Loading overlay state
  • Arena position validation

Migration Guide

From Public Spectator Polling to Internal Feed

Old Pattern (polling public endpoint):
New Pattern (SSE feed):
Benefits:
  • Real-time updates (no polling delay)
  • Renderer health signals (prevent betting on degraded frames)
  • Sequence-aware (idempotent deduplication via phaseVersion)
  • Replay buffer (reconnection support)
  • Lower server load (push vs pull)

Breaking Changes

Authentication Required:
  • All internal betting endpoints now require BETTING_FEED_ACCESS_TOKEN
  • Public spectator endpoints remain unauthenticated
  • Set BETTING_FEED_ACCESS_TOKEN in server .env before deploying
CORS Restrictions:
  • Internal endpoints restrict CORS to INTERNAL_BET_SYNC_ALLOWED_ORIGIN
  • Public endpoints remain open (wildcard CORS)
  • Set INTERNAL_BET_SYNC_ALLOWED_ORIGIN to your betting frontend domain
Sequence Continuity:
  • Sequence numbers reset on server restart (new sourceEpoch)
  • Clients must detect sourceEpoch changes and re-bootstrap
  • Replay buffer limited to 2048 frames (older frames are trimmed)

References

  • PR #1065: Internal bet sync feed and renderer health
  • Hyperbet Consumer PR: HyperscapeAI/hyperbet#28
  • Streaming Guardrails: packages/shared/src/utils/rendering/streamingGuardrails.ts
  • DuelBettingBridge: packages/server/src/systems/DuelScheduler/DuelBettingBridge.ts
  • Betting Feed Routes: packages/server/src/routes/streaming-betting-routes.ts