February 2026 Technical Updates
Comprehensive documentation of all major technical improvements, bug fixes, and new features deployed to Hyperscape in February 2026.Table of Contents
- Performance & Rendering
- Deployment & Operations
- Streaming & Capture
- CI/CD & Build System
- Bug Fixes
- Asset Forge
- Breaking Changes
- Migration Guide
Performance & Rendering
Arena Performance Optimization (97% Draw Call Reduction)
Problem: Duel arena rendering was a major bottleneck with 28 dynamicPointLights forcing expensive per-pixel lighting calculations and ~846 individual THREE.Mesh draw calls causing excessive GPU state changes.
Solution: Complete rewrite using InstancedMesh and GPU-driven TSL emissive materials.
Changes:
- Removed all 28 dynamic
PointLights - Primary FPS killer eliminated - Replaced with TSL emissive material on brazier bowls - Animated flicker runs entirely on GPU via
emissiveNodewith per-instance phase offset - Converted ~846 meshes → ~20
InstancedMeshdraw calls - 97% reduction in draw calls - Instanced geometry: Fence posts, caps, rails, pillar components, brazier bowls, border strips, banner poles
- Draw calls: ~846 → ~22 (97% reduction)
- CPU cost per frame: Near zero (GPU-driven animation)
- FPS improvement: Significant gains on mid-range GPUs
- File:
packages/shared/src/systems/client/DuelArenaVisualsSystem.ts - TSL time uniform drives all brazier glow animation
- Per-instance phase offset derived from quantized world position
- Multi-frequency sine flicker + high-freq noise matches old PointLight behavior
- Only top face (fire opening) glows; outer shell stays dark
Enhanced Fire Particle Shader
Problem: Old fire particles used simple radial falloff, creating hard edges and unrealistic flame appearance. Solution: Complete rewrite with smooth value noise, soft radial falloff, and per-particle turbulent motion. Changes:- Removed
"torch"preset - Unified all fire emitters on enhanced"fire"preset - Smooth value noise - Bilinear interpolated hash lattice for organic flame shapes
- Soft radial falloff - Designed for additive blending, overlapping particles merge into cohesive flame body
- Per-particle turbulent vertex motion - Natural flickering via time-based sine/cosine offsets
- Height-based color gradient - White-yellow core → orange-red tips
- Increased particle count - 18 → 28 particles per emitter for fuller flames
- File:
packages/shared/src/entities/managers/particleManager/GlowParticleManager.ts - Fragment shader uses scrolling noise for organic edges and upward motion feel
- Noise modulates mask - wispy edges but keeps 70%+ base intensity
- Age-based fade: hold brightness then drop at end of life
- Core color blending: bright core fading to particle color at edges/top
- Before: Hard-edged particles, visible individual sprites, static appearance
- After: Cohesive flame body, organic flickering, natural upward motion
Model Cache Fixes
Problem 1 - Missing Objects: Models with duplicate mesh names (common: "", “Cube”, “Cube”) had only the last reference survive deserialization. Three.jsadd() auto-removes from previous parent, so hierarchy nodes all resolved to the same index.
Solution: Use Map<Object3D, number> identity map built during traversal instead of findIndex-by-name.
Problem 2 - Lost Textures: Textures were serialized as ephemeral blob: URLs but never reloaded during deserialization, causing white/grey materials after browser restart.
Solution: Extract raw RGBA pixels via canvas getImageData (synchronous) and restore as THREE.DataTexture - no async loading race conditions.
Additional Fixes:
- Grey tree materials:
createDissolveMaterialusedinstanceof MeshStandardMaterialwhich fails forMeshStandardNodeMaterialin WebGPU build - replaced with duck-type property check - Cache bypass:
localStorage.setItem('disable-model-cache', 'true')to bypass cache for debugging - Error logging on IndexedDB put/transaction failures
- Bumped
PROCESSED_CACHE_VERSIONto 3 to invalidate broken entries
- Commit:
c98f1cce4240b5d4d7a459f60f47a927fe606d2b - Files: Model serialization/deserialization in shared package
- No migration needed - cache rebuilds automatically on first load
Terrain Height Cache Fix
Problem:getHeightAtCached had two bugs causing a consistent 50m offset in height lookups:
- Tile index used
Math.floor(worldX/TILE_SIZE)which doesn’t account for centered geometry - Grid index formula omitted the
halfSizeoffset fromPlaneGeometry’s[-50,+50]range
worldToTerrainTileIndex() and localToGridIndex() and fixed getHeightAtCached + getTerrainColorAt (which also had a comma-vs-underscore key typo preventing it from ever finding tiles).
Impact:
- Players no longer float 50m above ground
- Pathfinding works correctly
- Resources spawn at correct heights
- No migration needed - fix is automatic on update
- Commit:
21e0860993131928edf3cd6e90265b0d2ba1b2a7 - Files: Terrain system height cache
- Co-authored with Cursor
Deployment & Operations
Maintenance Mode API
Purpose: Graceful deployment coordination for streaming duel system. Prevents data loss and market inconsistency during deployments. How It Works:- Pauses new duel cycles (current cycle completes)
- Locks betting markets (no new bets accepted)
- Waits for current market to resolve (up to configurable timeout)
- Reports “safe to deploy” status
- Resumes operations after deployment
ADMIN_CODE authentication):
safeToDeploy: true- No active duel phases (FIGHTING, COUNTDOWN, ANNOUNCEMENT)
- All betting markets resolved
.github/workflows/deploy-vast.yml automatically enters/exits maintenance mode during deployments.
Implementation: packages/server/src/startup/maintenance-mode.ts
Commit: 30b52bd90a64de8fe34d89b80213b7fa08e618bb
Vast.ai Health Checks & Auto-Recovery
New Features:- Auto-detect unhealthy instances via
/healthendpoint polling - Destroy and reprovision when failures exceed threshold
- Configurable health check intervals
- Better logging with timestamps
- CI triggers on successful main branch builds
- System enters maintenance mode (pauses new duel cycles)
- Waits for active markets to resolve (up to 5 minutes)
- Deploys latest code via SSH
- Waits for server health check (up to 5 minutes, 30 attempts)
- Exits maintenance mode and resumes operations
VAST_HOST- Vast.ai instance IPVAST_PORT- SSH portVAST_SSH_KEY- SSH private keyVAST_SERVER_URL- Public server URL (e.g., https://hyperscape.gg)ADMIN_CODE- Admin authentication code
- Vulkan driver installation for GPU rendering
- Post-deploy health check with retry logic
- Better error handling and logging
.github/workflows/deploy-vast.ymlscripts/deploy-vast.shpackages/vast-keeper/src/index.ts
Streaming & Capture
RTMP Streaming Stability Improvements
Problems:- CDP stall threshold too aggressive (2 intervals = 60s)
- FFmpeg crashes causing stream gaps
- WebGPU initialization failures in headless environments (Docker, vast.ai)
- Restarts screencast without browser/FFmpeg teardown
- No stream gap during recovery
- Resets restart attempt counter on successful recovery
- Tries
maxTextureArrayLayers: 2048first - Retries with default limits if GPU rejects
- Always WebGPU, never WebGL (unless explicitly disabled)
ecosystem.config.cjsuses swiftshader backend for reliable software rendering- Works in Docker/vast.ai where WebGPU often fails
- Commit:
14a1e1bbe558c0626a78f3d6e93197eb2e5d1a96 - Files:
packages/server/src/streaming/stream-capture.tspackages/server/src/streaming/browser-capture.tspackages/shared/src/utils/rendering/RendererFactory.tsecosystem.config.cjs
Teleport VFX Improvements
Changes:- Fixed duplicate teleport VFX from race condition in
clearDuelFlagsForCycle() - Forward
suppressEffectthrough ServerNetwork → ClientNetwork → VFX system - Mid-fight proximity corrections suppressed, arena exit effects visible
- Scaled down teleport beam/ring/particle geometry to fit avatar size
- Removed duplicate
PLAYER_TELEPORTEDemit fromPlayerRemote.modify()
- Commit:
7bf0e14357be0022a4fa728c6bf9d6f0287b5b14 - Flags now stay true until
cleanupAfterDuel()completes teleports via microtask suppressEffectparameter properly propagated through network layers
Victory Emote Timing Fix
Problem: Winning agent’s wave emote was immediately overwritten by stale “idle” resets from combat animation system. Solution: Delay victory emote by 600ms so all death/combat cleanup finishes first. Also reset emote to idle instopCombat so wave stops when agents teleport out.
Technical Details:
- Commit:
645137386212208a7472ffd04aef9391394b5f65 - File:
packages/server/src/systems/StreamingDuelScheduler/managers/DuelOrchestrator.ts - Sets
entity.data.emote = "victory"on server entity for future sync - Broadcasts
entityModifiedwith victory emote after 600ms delay
CI/CD & Build System
npm Retry Logic for Rate Limiting
Problem: npm rate-limits GitHub Actions IP ranges, causing intermittent 403 Forbidden errors duringbun install.
Solution: Automatic retry with exponential backoff (15s, 30s, 45s, 60s, 75s) - up to 5 attempts.
Implementation:
7c9ff6c1086737d462998ee0507be3fedbcad118- Initial retry logic08aa151393ea0eb5b25dace6eb9e328946bf2e2f- Frozen lockfile enforcement
Frozen Lockfile Enforcement
Problem:bun install without --frozen-lockfile tries to resolve packages fresh from npm, triggering rate-limiting under CI load.
Solution: All workflows now use bun install --frozen-lockfile to ensure bun uses only the committed lockfile for resolution.
Impact: Eliminates npm resolution attempts that trigger 403 errors.
Files Updated:
.github/workflows/ci.yml.github/workflows/build-app.yml.github/workflows/deploy-vast.yml- All other workflow files
Tauri Build Fixes
Problem 1 - macOS Unsigned Builds: Using--bundles app (macOS-only bundle type) caused Linux/Windows builds to fail.
Solution: Use --no-bundle for unsigned builds to produce raw binaries on all platforms.
Problem 2 - iOS Builds: Unsigned iOS builds always fail with “Signing requires a development team”.
Solution: Make iOS build job release-only - skip unsigned builds entirely.
Problem 3 - Windows Install Failures: Transient NPM registry 403 errors on Windows runners.
Solution: Add retry logic (3 attempts) to bun install step on Windows.
Problem 4 - Signing Env Vars: tauri-bundler attempts macOS code signing whenever APPLE_CERTIFICATE env var exists, even if empty.
Solution: Split Desktop, iOS, and Android build steps into separate Unsigned and Release variants so signing env vars are only present during actual releases.
Technical Details:
- Commit:
f19a7042571d84e741a3a57cb6e9d8b93eb8a094 - Commit:
8ce4819a0bdb5a99551ef9ba423fd96262a4f22c - Commit:
15250d266042f43c6faa7f640fc77af1b9a83e03 - File:
.github/workflows/build-app.yml
Dependency Cycle Resolution
Problem: Turbo detected cyclic dependency:shared → procgen → shared. Turbo treats peerDependencies as graph edges in workspace monorepos.
Solution:
procgenis an optional peerDependency inshared/package.jsonsharedis a devDependency inprocgen/package.json
devDependenciesare not followed by Turbo’s^buildtopological ordering (no cycle)- The devDependency in procgen ensures bun links the package so TypeScript can find
@hyperscape/procgenmodule declarations during type checking - Both packages are always installed together in the workspace, so imports resolve at runtime
- Commit:
f355276637d36e3ebe1914d900acc36eeb3d42fa - Commit:
3b9c0f2a671db13bff08655e64c71352a50b3a18 - Commit:
05c2892e373d05b679c9cdde9ead7f1ad85105d7
Bug Fixes
Duel Combat Fixes
Problem: Mage staff and 2H sword combat broken in streaming duels due to:- Agents idling when combat state times out (2H sword issue)
- Weapon type not propagated through
DuelOrchestratorintostartCombat - Rune inventory not ready for mage attacks
- State starvation from repeated
startCombatresets on slow weapons
- Add keep-alive re-engagement in
DuelCombatAIto prevent agents idling - Propagate weapon type (mage/ranged/melee) through
DuelOrchestratorintostartCombat - Add rune inventory readiness polling and rune validation bypass for duel bot agents
- Guard against state starvation from repeated
startCombatresets - Refresh combat timeout after ranged/magic attacks in both
CombatSystemandCombatTickProcessor - Bypass PvP zone checks for streaming duel combatants
- Block aggro and chase on players in safe zones via
AggroSystem
- Commit:
029456255c5af67e95ca1137d11977fb2c1f5ff9 - Files:
packages/server/src/arena/DuelCombatAI.tspackages/server/src/systems/StreamingDuelScheduler/managers/DuelOrchestrator.tspackages/shared/src/systems/shared/combat/CombatSystem.tspackages/shared/src/systems/shared/combat/CombatTickProcessor.tspackages/shared/src/systems/shared/combat/AggroSystem.ts
Cloudflare Deployment Fixes
Problem 1: Rootwrangler.toml named “hyperscape-betting” conflicted with “hyperscape” Pages project.
Solution: Remove root wrangler.toml. Correct Pages configuration is in packages/client/wrangler.toml.
Problem 2: pages_build_output_dir directive not working with wrangler deploy.
Solution: Switch to [assets] directive which works better with static asset hosting.
Problem 3: Cloudflare origin lock preventing direct frontend API access.
Solution: Disable origin lock to allow direct access.
Technical Details:
- Commits:
1af02ce112d00e0780cb8511a271c82bc5d61ac8,42a1a0e45c4617b101e1d08681ed1ef0715e7508,3ec98268de142ebafbbdf93c2d6299e73e2762da - File:
packages/client/wrangler.toml
Asset Forge
VFX Catalog Browser
New Feature: VFX page in Asset Forge with sidebar catalog of all game effects and live Three.js previews. Includes:- Spells (magic attacks, teleport, combat HUD)
- Arrows (ranged projectiles)
- Glow particles (fire, altar, healing)
- Fishing effects
- Teleport VFX
- Combat HUD elements
- Color swatches for all effect colors
- Parameter tables (lifetime, speed, scale, etc.)
- Layer breakdowns (pillar, wisp, spark, base, riseSpread)
- Phase timelines for multi-phase effects
- Commit:
69105229a905d1621820c119877e982ea328ddb6 - Files:
packages/asset-forge/src/pages/VFXPage.tsxpackages/asset-forge/src/components/VFX/VFXPreview.tsxpackages/asset-forge/src/components/VFX/EffectDetailPanel.tsxpackages/asset-forge/src/data/vfx-catalog.ts
Asset Forge Build Fix
Problem: Vast.ai deployment container only has bun installed, not npm/npx. Usingnpx tsc in build-services.mjs failed.
Solution: Use bunx tsc instead of npx tsc.
Technical Details:
- Commit:
c80ad7a273cf54a3cc3ab454aa28f57df5474fc7 - File:
packages/asset-forge/scripts/build-services.mjs
ESLint Fixes
Problem 1:eslint . --ext .ts,.tsx uses deprecated --ext flag and lints entire directory, causing eslint-plugin-import’s import/order rule to crash.
Solution: Use eslint src instead, matching other packages in monorepo.
Problem 2: eslint-plugin-import@2.32.0 incompatible with ESLint 10 - import/order rule uses removed sourceCode.getTokenOrCommentBefore API.
Solution: Disable cascaded rule in asset-forge’s flat config.
Technical Details:
- Commits:
cadd3d50430df478540f070a591b9d9fedd35a29,b5c762c986a98e15371e3776842a6bbc7d9b55f5 - File:
packages/asset-forge/eslint.config.mjs
Breaking Changes
Removed Features
-
"torch"particle preset - Removed in favor of unified"fire"preset- Migration: Change all
preset: "torch"topreset: "fire"in particle registrations - Impact: Minimal - fire preset now handles all flame effects with better quality
- Migration: Change all
-
Dead code removal - Removed unused functions:
createArenaMarker()- Arena number markers (unused)createAmbientDust()- Ambient dust particles (unused)createLobbyBenches()- Lobby bench geometry (unused)
API Changes
Maintenance Mode Endpoints (New):POST /admin/maintenance/enterGET /admin/maintenance/statusPOST /admin/maintenance/exit
- Now includes
maintenanceModefield in response
Migration Guide
Updating from Pre-February 2026
1. Model Cache Issues:- No action needed - update to latest main branch
- Fix is automatic, no migration required
- No action needed if using standard workflows
- Custom workflows should add
--frozen-lockfiletobun install - Tauri builds should split unsigned/release jobs
- No action needed - already fixed in package.json files
- If you see Turbo cycle errors, verify package.json configurations match the pattern above
Environment Variables
New Variables
Streaming Stability (packages/server/.env):
- Controlled via API endpoints, not environment variables
- Requires
ADMIN_CODEto be set in production
Updated Defaults
Build Order (turbo.json):
physx-js-webidl- PhysX WASMprocgen- Procedural generation library (new position)shared- Core engine (depends on physx-js-webidl and procgen)- All other packages - Depend on shared
Performance Benchmarks
Arena Rendering
Before:- Draw calls: ~846
- PointLights: 28 (CPU-animated at 60fps)
- FPS: Variable, drops on mid-range GPUs
- Draw calls: ~22 (97% reduction)
- PointLights: 0 (GPU-driven TSL emissive)
- FPS: Stable, significant improvement
Fire Particles
Before:- Particle count: 18 per emitter
- Shader: Simple radial falloff
- Appearance: Hard edges, visible individual sprites
- Particle count: 28 per emitter
- Shader: Smooth value noise + soft radial falloff + turbulent motion
- Appearance: Cohesive flame body, organic flickering
Known Issues & Workarounds
Model Cache
Issue: Corrupted cache from pre-February 2026 builds causes missing objects or white textures. Workaround:Streaming in Headless Environments
Issue: WebGPU often fails in Docker/vast.ai containers. Workaround:?page=stream&forceWebGL=1
CI npm 403 Errors
Issue: Transient npm rate limiting on GitHub Actions. Workaround: Retry logic is automatic (up to 5 attempts). If persistent, check GitHub Actions logs and wait for rate limit window to reset.Testing
New Test Coverage
Arena Performance:- Visual regression tests for instanced rendering
- Draw call count verification
- FPS benchmarks
- Duplicate mesh name serialization tests
- Texture persistence tests
- Cache version migration tests
- Height cache offset tests
- Canonical helper function tests
- Integration tests for player positioning
- CDP recovery tests
- WebGPU fallback tests
- FFmpeg restart tests
Documentation Updates
Files Updated
-
README.md:
- Added maintenance mode API documentation
- Added Vast.ai deployment flow
- Added troubleshooting for model cache, terrain heights, streaming, CI builds
- Added “Recent Updates (February 2026)” section
-
CLAUDE.md:
- Added model cache troubleshooting
- Added terrain height troubleshooting
- Added streaming issues troubleshooting
- Added CI build failures troubleshooting
- Added dependency cycle troubleshooting
- Added maintenance mode documentation
- Updated build dependency graph (added procgen)
-
packages/server/.env.example:
- Already comprehensive, no updates needed
-
packages/client/.env.example:
- Already comprehensive, no updates needed
-
docs/railway-dev-prod.md:
- Already comprehensive, no updates needed
-
This file (
docs/february-2026-updates.md):- New comprehensive technical documentation
Related Pull Requests
- #940 - Victory emote timing fix
- #938 - Arena performance optimization (instancing + TSL)
- #935 - Model cache fixes (missing objects + lost textures)
Contributors
- Shaw (@lalalune) - Deployment, CI/CD, streaming, dependency fixes
- Ting Chien Meng (@tcm390) - Arena performance optimization, terrain height fix
- Lucid (@dreaminglucid) - VFX improvements, teleport fixes, victory emote timing