Skip to main content

February 2026 Updates

This document covers all major changes pushed to the main branch during February 2026, including critical memory leak fixes, client performance optimizations, streaming infrastructure improvements, and agent system enhancements.

Critical Memory Leak Fixes

A comprehensive memory leak audit identified and fixed 20+ memory leaks across the codebase. All fixes follow the established SystemBase cleanup pattern.

Server-Side Leaks (HIGH Priority)

Issue: GPU memory accumulated during hot reload and cache invalidationFix: Added geometry disposal on clear() and remove() methodsImpact: Prevents GPU memory exhaustion in long-running sessions
// ModelCache.ts
clear() {
  for (const entry of this.cache.values()) {
    entry.geometry?.dispose(); // NEW: Dispose GPU resources
  }
  this.cache.clear();
}
Issue: World event listeners never removed, causing listener accumulation on hot reloadFix: Added destroy() method to clean up all registered listenersPattern: Track listeners in Map, iterate and remove on destroy
destroy() {
  for (const [event, handlers] of this.listeners) {
    handlers.forEach(handler => world.off(event, handler));
  }
  this.listeners.clear();
}
Issue: Event handlers not cleaned up on destroy/shutdownFix: Store bound event handlers, cleanup in destroy() methodFiles Fixed:
  • GameTickProcessor.ts - Tick event handlers
  • TradingSystem/index.ts - PLAYER_LEFT/LOGOUT/DIED handlers
  • AgentManager.ts - COMBAT_DAMAGE_DEALT listener
  • AutonomousBehaviorManager.ts - Agent lifecycle handlers
Issue: WebSocket server listeners not cleaned up on closeFix: Call removeAllListeners() before closing WebSocket servers
Issue: playerSkills, combatLevelCache, and aggro maps growing unboundedlyFix: Clean up player-specific data from Maps on disconnect
Issue: lootedByCharacters Set growing unboundedly over server lifetimeFix: Add size limit (10k) with LRU pruning

Client-Side Leaks (MEDIUM-HIGH Priority)

XPDropSystem: Object pool for CanvasTexture/SpriteMaterial reuse, warn on pool exhaustionDuelCountdownSplatSystem: Pre-render count textures once, pool sprite/material pairsHealthBars: Add destroy() to clear hideTimeout handles and dispose InstancedMesh/texture/geometryProjectileRenderer: Track pending setTimeout handles in Set, cancel all on destroy(), reference-counted geometry disposal
PlayerTokenManager: Named beforeUnloadHandler property enables proper removeEventListener on dispose()EmbeddedGameClient: Guard async state updates with cancelled flag to prevent setState on unmounted componentThreeResourceManager: Add teardown() to stop dev monitor interval and reset WeakSet on hot-reloadClientLiveKit: Properly clean up voices Map and room listeners in destroy()
Issue: world.destroy() could race world.init() mid-await during fast navigation/hot-reloadFix: Two-flag handshake (initComplete + needsCleanup) ensures destroy() runs exactly once after init() completesImpact: Prevents resource leaks from partial initialization

Memory Management Best Practices

When creating new systems or managers:
  1. Track All Resources: Store references to intervals, listeners, handlers
  2. Implement Cleanup: Add destroy(), shutdown(), or stop() methods
  3. Follow SystemBase Pattern: Use the same cleanup patterns as SystemBase
  4. Clean Up on Hot Reload: Ensure resources are released during development
  5. Test for Leaks: Monitor memory usage during long-running sessions
Example cleanup pattern:
class MySystem {
  private listeners: Array<() => void> = [];
  private intervals: NodeJS.Timeout[] = [];

  init() {
    const listener = world.on('event', this.handleEvent);
    this.listeners.push(listener);
    
    const interval = setInterval(this.tick, 1000);
    this.intervals.push(interval);
  }

  destroy() {
    // Clean up listeners
    this.listeners.forEach(remove => remove());
    this.listeners = [];
    
    // Clear intervals
    this.intervals.forEach(clearInterval);
    this.intervals = []
  }
}

Client Performance Optimizations

Movement System Overhaul

Immediate Move Processing

Bypasses ActionQueue for instant response to player clicks (eliminates 0-600ms latency)

Pathfinding Rate Limit

Raised from 5/sec to 15/sec to match tile movement limiter

BFS Iterations

Increased from 2000 to 8000 (~44 tile radius vs ~22 tile)

Path Continuation

Seamless long-distance movement with automatic re-pathfinding when BFS limit reached
Skating Fix: Server-side pre-computation + client-side path appending eliminates stop-lurch at segment boundaries Multi-Click Feel: Optimistic target pivoting + pending move queue ensures last click always reaches server Per-Frame Allocation Elimination: Pre-allocated buffers and squared distance comparisons in hot paths

Minimap Rendering Optimization

1

Async Terrain Generation

Chunked sampling (50×50 grid) runs off RAF callback via setTimeout(0) yieldsImpact: Zero RAF blocking - terrain generation happens in background macrotasks
2

Canvas Rotation Transform

Decouples terrain regeneration from camera rotation (only regenerates on player move/zoom)Terrain Overshoot: √2 × 1.1 sampling ensures corners stay filled at any rotation angle
3

Layer Synchronization

All layers (terrain, roads, buildings, pips) use same camera snapshotCached Contexts: Canvas 2D contexts cached in refs to avoid getContext() DOM queries
4

Performance Results

Reduced terrain sampling from up to 40,000 pixels to 2,500 (16× reduction)

Client Memory Optimizations

  • Machine ID Caching: Browser fingerprint cached in _cachedMachineId (avoids canvas allocation on every token operation)
  • Activity Debouncing: 500ms debounce on saveSession() localStorage writes (was synchronous on every interaction)
  • XP Drop Listener: Store bound handler so destroy() can call world.off() (eliminates leak that survived world teardown)
  • Stale Health Bar Sweep: Reverse iteration to remove bars for despawned entities

Streaming Infrastructure Improvements

WebGPU Initialization & Diagnostics

Adapter Request Timeout

30s timeout on navigator.gpu.requestAdapter() to prevent indefinite hangs

Renderer Init Timeout

60s timeout on renderer.init() to detect GPU driver issues

Preflight Testing

testWebGpuInit() runs on blank page before loading game content

GPU Diagnostics

captureGpuDiagnostics() extracts chrome://gpu info for debugging

Vast.ai Deployment Architecture

The streaming pipeline requires specific GPU setup:

1. GPU Rendering Modes (tried in order)

1

Xorg with NVIDIA

Best performance, requires DRI/DRM device access
2

Xvfb with NVIDIA Vulkan

Virtual framebuffer + GPU rendering via ANGLE/Vulkan (non-headless Chrome)
Xvfb mode uses non-headless Chrome connecting to virtual display (WebGPU requires window context)
3

Headless Vulkan

Chrome --headless=new with --use-vulkan and --use-angle=vulkan
4

Headless EGL

Direct EGL rendering without X server using --headless=new --use-gl=egl
5

Ozone Headless

Experimental mode using --ozone-platform=headless with GPU rendering
6

SwiftShader

Software Vulkan fallback (poor performance, last resort)
Deployment detects Xorg swrast software rendering and switches to alternative modes.

2. Deployment Validation

  • Checks nvidia_drm kernel module
  • Checks DRM device nodes (/dev/dri/)
  • Queries GPU display_mode via nvidia-smi to verify display driver support
  • Provides clear guidance to rent instances with gpu_display_active=true on Vast.ai
  • Checks Vulkan ICD availability at /usr/share/vulkan/icd.d/nvidia_icd.json
  • Logs actual ICD content and VK_LOADER_DEBUG output for diagnostics
  • XDG_RUNTIME_DIR: Required for Vulkan/EGL initialization (set to /tmp/runtime-root)
Runs 6 WebGPU tests with different Chrome configurations:
  1. Headless Vulkan
  2. Headless EGL
  3. Xvfb Vulkan
  4. Ozone Headless
  5. SwiftShader
  6. Playwright Xvfb
Extracts Chrome GPU info (WebGPU/Vulkan status) during deployment
Deployment FAILS if WebGPU cannot be initialized (no soft fallbacks)

3. Vast.ai CLI Provisioner

New automated provisioner script: ./scripts/vast-provision.sh
1

Search

Searches for instances with gpu_display_active=true (REQUIRED for WebGPU)
2

Filter

Filters by reliability, GPU RAM, price
3

Rent

Automatically rents best available instance
4

Wait

Waits for instance to be ready
5

Output

Outputs SSH connection details and GitHub secret commands
./scripts/vast-provision.sh
Ensures only instances with NVIDIA display driver support are rented.

4. Stream Capture Improvements

  • Chrome Executable: Set STREAM_CAPTURE_EXECUTABLE to explicit Chrome path (e.g., /usr/bin/google-chrome-unstable) for reliable WebGPU
  • Browser Restart: Automatic browser restart every 45 minutes to prevent WebGPU OOM crashes
  • Page Navigation Timeout: Increased to 180s for Vite dev mode (production build recommended)
  • Resolution Tracking: Automatic viewport recovery on resolution mismatch
  • Probe Timeout: 5s timeout on probe evaluate calls to prevent hanging

5. macOS WebGPU Support

  • Metal Backend: macOS uses Metal (not Vulkan) for WebGPU
  • System Chrome Required: Auto-detects and uses system Chrome on macOS for WebGPU support
  • Playwright Limitation: Bundled Chromium doesn’t have proper WebGPU support on macOS
  • No Vulkan ICD: Don’t set VK_ICD_FILENAMES on macOS (not applicable)
  • Chrome Flags: Remove Vulkan from feature flags on macOS (Metal is the backend)

Agent System Improvements

Dynamic Combat Progression

Monster Escalation

Agents progress from goblins → bandits → barbarians as combat level grows

Combat Style Rotation

Agents cycle attack → strength → defense (train lowest skill)

Cooking Phase

Agents cook raw food immediately instead of waiting for full inventory

Gear Upgrade Phase

Agents smith better equipment when they have materials + levels

Stability Fixes

Fixed weapon.toLowerCase is not a function crash in getEquippedWeaponTier that broke ALL agents every tickRoot Cause: Weapon could be an object instead of stringFix: Added type guard and proper string extraction
Old Behavior: Agents derailed to explore on LLM errorsNew Behavior: Idle + retry when agent has active goalImpact: Agents maintain goal focus through temporary LLM failures
Added quest goal status change detection for proper quest lifecycle transitionsAgents now properly detect when quest objectives are completed

Configuration Improvements

  • Combat Food Threshold: Increased from 5 → 10 for better survival
  • World Data Manifest Loading: Monster tiers and gear tiers loaded from world-data
  • Short-Circuit Dashboard Sync: All agents show activity logs even when skipping LLM
  • LLM Rate Limiting: Exponential backoff for API calls (5s base, max 60s)
  • Consecutive Failure Tracking: Resets on successful tick

Combat System Stability

Duel System Improvements

Combat Retry Timer

Aligned with tick system (3000ms = 5 ticks) for consistent timing

Phase Timeout

Reduced grace periods from 30s to 10s for faster failure detection

Combat Stall Nudge

Tracks last nudge timestamp instead of cycle ID to allow re-nudging when combat stalls again

Damage Event Cache

Cleanup every tick (was every 2 ticks), cap lowered from 5000 to 1000, evict 75% when exceeded

Gold Betting Demo - Mobile UI

Mobile Responsive Overhaul

Resizable Panels

Desktop layout with useResizePanel hook + ResizeHandle component

Mobile Detection

useIsMobile hook gates JS inline styles so CSS media queries control layout

Mobile Layout

16:9 aspect-ratio video, bottom-sheet sidebar, touch-friendly tab targets, dvh units

Mobile Header

Stacked HYPERSCAPE/MARKET logo, phase strip above video, SOL + EVM wallet buttons

Data Integration

  • Real Data Integration: Live SSE feed from game server (devnet mode) replaces mock data
  • Simulation Mode: Available via bun run dev:stream-ui (dev mode uses real endpoints only)
  • Tab Reordering: Trades tab moved first for better mobile UX

Console Noise Reduction

Raised .hm-chart-container min-height to 120px (eliminates width/height=0 warnings)
Close EventSource on onerror to stop browser’s built-in reconnect loop
useDuelContext switched from fixed setInterval to setTimeout with backoff (3s → 6s → 60s cap)

Testing Infrastructure

E2E Journey Tests

New comprehensive end-to-end tests in packages/client/tests/e2e/complete-journey.spec.ts:
1

Login Flow

Full authentication and character selection
2

Loading Screen

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
Real Browser Testing: Uses Playwright with actual WebGPU rendering (no mocks)

Test Stability Improvements

  • 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)

Model Cache & Rendering

Index Buffer Type Preservation

Critical Fix: Model cache now preserves original index buffer type (Uint16Array vs Uint32Array)
Issue: Silent geometry corruption and RangeError crashes on cached model restore Fix: Cache version bumped to 4 to invalidate corrupt entries Impact: Affects all GLB models loaded via ModelCache (resources, NPCs, items)

Resource Management

Activity Logger Queue

  • Max Size: 1000 entries with 25% eviction to prevent memory pressure
  • Eviction Policy: LRU-style pruning when limit exceeded

Session Timeout

  • 30-Minute Max: Via MAX_SESSION_TICKS for zombie session cleanup
  • SessionCloseReason: Added “timeout” to type for proper session termination tracking

Breaking Changes

Safari 17 Support Removed: Safari 18+ (macOS 15+) is now required for WebGPU support

API Changes

Now returns boolean:
  • true = strategy handled depletion (instanced stump)
  • false = ResourceEntity should load individual depleted model
New optional method: getHighlightMesh(ctx) for instanced entity highlighting
Added new required fields:
  • requestedDestination: TileCoord | null
  • lastPathPartial: boolean
  • nextSegmentPrecomputed: boolean
These fields are always initialized by createTileMovementState() - optional typing was removed
Added optional isContinuation?: boolean field for path continuation support

Migration Guide

For Developers

1

Update Dependencies

bun install
bun run build
2

Review Memory Cleanup

If you’ve created custom systems, ensure they implement proper cleanup:
  • Add destroy() or shutdown() methods
  • Track and cleanup all event listeners
  • Clear intervals and timeouts
3

Test Movement

Movement system changes may affect custom pathfinding logic:
  • ActionQueue no longer buffers move requests
  • Path continuation handles long-distance clicks automatically
4

Update Safari Requirements

Update browser compatibility documentation to require Safari 18+ (macOS 15+)

For Vast.ai Deployments

1

Use Provisioner Script

./scripts/vast-provision.sh
This ensures you rent instances with gpu_display_active=true
2

Update GitHub Secrets

gh secret set VAST_HOST --body 'your-host'
gh secret set VAST_PORT --body 'your-port'
3

Trigger Deployment

gh workflow run deploy-vast.yml

Performance Metrics

Before vs After

MetricBeforeAfterImprovement
Move Click Latency0-600ms~0msInstant response
Pathfinding Range~22 tiles~44 tiles2× radius
Minimap Terrain Sampling40,000 pixels2,500 pixels16× reduction
Memory Leaks Fixed20+ leaks0 known leaks100%
Browser Restart Interval60 min45 minPrevents OOM
Combat Retry Timer1500ms3000msTick-aligned
Phase Timeout30s10s3× faster detection

Files Changed

Core Engine (packages/shared/)

  • systems/shared/movement/TileSystem.ts - Path continuation, immediate processing
  • systems/client/TileInterpolator.ts - Skating fix, optimistic targeting, allocation elimination
  • systems/ServerNetwork/tile-movement.ts - Server-side pre-computation
  • systems/ServerNetwork/mob-tile-movement.ts - Mob movement state updates
  • utils/rendering/ModelCache.ts - Index buffer type preservation, geometry disposal
  • systems/client/XPDropSystem.ts - Object pooling
  • systems/client/DuelCountdownSplatSystem.ts - Texture pre-rendering
  • systems/client/HealthBars.ts - Timeout cleanup, stale bar sweep
  • systems/client/ProjectileRenderer.ts - Reference-counted geometry
  • lib/ThreeResourceManager.ts - Teardown method
  • components/ColliderComponent.ts - Collision handler cleanup
  • entities/npc/MobEntity.ts - PLAYER_SET_DEAD listener cleanup
  • platform/shared/Socket.ts - WebSocket handler cleanup
  • systems/client/ClientLiveKit.ts - Voice Map and room listener cleanup

Server (packages/server/)

  • systems/GameTickProcessor.ts - Event handler cleanup
  • systems/TradingSystem/index.ts - Player lifecycle handler cleanup
  • systems/ServerNetwork/event-bridge.ts - Listener tracking and cleanup
  • systems/ServerNetwork/action-queue.ts - PlayerQueues cleanup
  • systems/ServerNetwork/ScriptQueue.ts - Queue cleanup methods
  • systems/shared/combat/AggroSystem.ts - Player data cleanup on disconnect
  • entities/world/StarterChestEntity.ts - Bounded lootedByCharacters Set
  • startup/shutdown.ts - Rate limiter and idempotency service cleanup
  • streaming/rtmp-bridge.ts - WebSocket listener cleanup
  • streaming/stream-capture.ts - Browser restart, timeout improvements
  • systems/ServerNetwork/services/Logger.ts - Cleanup interval tracking
  • eliza/AgentManager.ts - COMBAT_DAMAGE_DEALT listener cleanup, LLM rate limiting
  • eliza/managers/autonomous-behavior-manager.ts - Event handler cleanup
  • systems/DuelSystem/index.ts - Combat retry timer, phase timeout
  • systems/StreamingDuelScheduler/managers/DuelOrchestrator.ts - Combat stall nudge

Client (packages/client/)

  • game/EmbeddedGameClient.tsx - Cancelled flag for async state
  • game/hud/Minimap.tsx - Async terrain generation, canvas rotation, layer sync
  • auth/PlayerTokenManager.ts - beforeUnloadHandler cleanup, machine ID caching, activity debouncing
  • tests/e2e/complete-journey.spec.ts - NEW: Full journey tests
  • tests/e2e/utils/visualTesting.ts - Screenshot comparison utilities

Scripts

  • scripts/vast-provision.sh - NEW: Automated Vast.ai provisioner
  • scripts/deploy-vast.sh - Enhanced WebGPU validation and diagnostics

Gold Betting Demo (packages/gold-betting-demo/)

  • app/src/App.tsx - Mobile responsive UI
  • app/src/AppRoot.tsx - Mode routing (stream-ui vs normal)
  • app/src/lib/useResizePanel.ts - NEW: Resizable panel hook
  • app/src/components/ResizeHandle.tsx - NEW: Resize handle component
  • app/src/spectator/useStreamingState.ts - EventSource auto-reconnect fix
  • app/src/spectator/useDuelContext.ts - Exponential backoff polling
  • keeper/src/db.ts - NEW: Persistence layer

Documentation Updates

This changelog itself is a new addition. Additional documentation updates needed:

AGENTS.md

Already updated with all memory leak fixes and performance optimizations

README.md

Needs update for vast-provision.sh script

CLAUDE.md

Needs update for new architecture patterns

Deployment Guides

Needs update for Vast.ai provisioner workflow

Next Steps

1

Monitor Memory

Use the new cleanup patterns in all future systems
2

Test Streaming

Validate WebGPU initialization on your target platform
3

Update Deployments

Use vast-provision.sh for new Vast.ai instances
4

Review Agent Behavior

Monitor agent combat progression and gear upgrades