Monorepo Structure
Hyperscape is a Turbo monorepo with core packages:gold-betting-demo, evm-contracts, sim-engine, market-maker-bot) has been split into a separate repository: HyperscapeAI/hyperbet
Build Dependency Graph
Packages must build in this order due to dependencies:The
turbo.json configuration handles build order automatically via dependsOn: ["^build"].Package Overview
shared (@hyperscape/shared)
The core Hyperscape 3D engine containing:
- Entity Component System (ECS): Game object architecture
- Three.js integration: WebGPU rendering (v0.182.0, TSL shaders only)
- React 19.2.0: UI framework (unified across monorepo)
- PhysX bindings: Physics simulation via WASM
- Networking: Real-time multiplayer sync via WebSockets
- React UI components: In-game interface with styled-components
- Game data: Manifests for NPCs, items, world areas
- Procedural terrain: Multi-threaded terrain generation with web workers
- GPU Instancing: InstancedMesh-based rendering for trees, particles, placeholders
- Strategy Pattern: Delegated visual strategies for resource rendering
server (@hyperscape/server)
Game server built with:
- Fastify 5: HTTP API with rate limiting
- WebSockets: Real-time game state via
@fastify/websocket - PostgreSQL/SQLite: Database persistence
- LiveKit: Voice chat integration
client (@hyperscape/client)
Web client featuring:
- Vite: Fast development builds with HMR
- React 19: UI framework
- Three.js: 3D rendering
- Capacitor: iOS/Android mobile builds
- Privy: Authentication
- Farcaster: Miniapp SDK integration
plugin-hyperscape (@hyperscape/plugin-hyperscape)
ElizaOS plugin enabling AI agents to:
- Perform actions (combat, skills, movement, banking)
- Query world state via 7 providers (goal, gameState, inventory, nearbyEntities, skills, equipment, availableActions)
- Make LLM-driven decisions with Anthropic, OpenAI, or OpenRouter
- Play autonomously as real players with full WebSocket connectivity
- All duel arena agents now route through
@elizaos/plugin-elizacloud - Access to 13 frontier models via single API key
- Individual provider plugins retained for backward compatibility
asset-forge (3d-asset-forge)
AI-powered asset generation using:
- Elysia: API server (with CORS, rate limiting, Swagger)
- MeshyAI: 3D model generation
- GPT-4: Design and lore generation
- React Three Fiber: 3D preview with
@react-three/fiberand@react-three/drei - Drizzle ORM: PostgreSQL database for asset tracking
- TensorFlow.js: Hand pose detection for VR/AR interactions
website (@hyperscape/website)
Marketing website built with:
- Next.js 15: Static site generation with App Router
- React 19: UI framework
- Tailwind CSS 4: Utility-first styling
- Framer Motion: Scroll animations and transitions
- GSAP: Advanced animations
- Lenis: Smooth scroll library
- React Three Fiber: 3D background effects
/- Landing page with hero, features, and CTA/gold- $GOLD token page with tokenomics
Recent Dependency Updates (February 2026)
The following major dependencies were updated in late February 2026:Core Dependencies
ElizaOS Ecosystem
Development Tools
UI Libraries
Breaking Changes
React 19.2.4 Unification (commit 3322e78):- All packages now use React 19.2.4 (previously mixed 19.2.2 and 19.2.4)
- Fixes client exception caused by version mismatch between packages
- Unified via root package.json overrides
- Removed npm override for
playwright >= 1.55.1 - Conflicted with direct dependency
^1.55.1in Cloudflare build environment - Caused EOVERRIDE error during deployment
- Breaking changes in configuration format
- All packages updated simultaneously to maintain consistency
- Breaking changes in schema validation API
- Updated across all packages using Zod schemas
Key Patterns
Entity Component System
All game logic runs through ECS:- Entities: Game objects (players, mobs, items)
- Components: Data containers (position, health, inventory)
- Systems: Logic processors (combat, skills, movement)
GPU-Instanced Rendering Systems
GLBTreeInstancer (commit 0871acb) - InstancedMesh-based tree rendering Replaces per-treescene.clone(true) with shared InstancedMesh pools per LOD level. Trees from woodcutting.json now render via shared geometry references instead of deep-cloning all buffers on each spawn.
Performance Impact:
- Eliminated per-tree geometry cloning (saves ~2-5ms per tree spawn)
- Reduced draw calls from N trees to 3 (one per LOD level)
- FPS improvement: ~15-20% in dense forest areas
- Memory savings: ~80% reduction in geometry buffer allocations
- Location:
packages/shared/src/systems/shared/world/GLBTreeInstancer.ts - Manages LOD0/LOD1/LOD2 InstancedMesh pools per model
- Allocates instance slots from pre-sized pools (max 1000 instances per LOD)
- Handles depleted/stump/destroy lifecycle via no-op-safe calls
- Initialized in
createClientWorld.tswhen stage scene is ready
- TreeGLBVisualStrategy: GLB tree models with LOD support via GLBTreeInstancer
- TreeProcgenVisualStrategy: Procedurally generated trees via ProcgenTreeInstancer
- StandardModelVisualStrategy: Generic 3D models (rocks, ores) via ModelCache
- FishingSpotVisualStrategy: Fishing spot particles via ParticleManager
- PlaceholderVisualStrategy: Fallback colored cubes via PlaceholderInstancer
- Single Responsibility: Each strategy handles one rendering approach
- Open/Closed: Add new strategies without modifying ResourceEntity
- Testability: Strategies can be tested in isolation
- Maintainability: ~1700 lines of conditional logic replaced with clean delegation
- Bug Prevention: Centralized model path sanitization prevents “null” string bugs
- Color-coded: green (trees), brown (ores), blue (fishing spots)
- Lifecycle:
allocate()on spawn,free()on destroy - Max 1000 instances per resource type
- Initialized in
createClientWorld.tsalongside GLBTreeInstancer - Location:
packages/shared/src/systems/shared/world/PlaceholderInstancer.ts
- Fixed fishing spot particles persisting after depletion (guard re-registration when depleted, zero ripple phase offset on unregister for full transparency)
- Fixed placeholder trees not rendering due to “null” string in modelPath (sanitize in ResourceSystem + createVisualStrategy factory, fix woodcutting.json)
- Removed dead ParticleSystem.move() method and leftover hack fixes
Model Cache System
The shared package includes an IndexedDB-based model cache that stores processed 3D models for instant loading on subsequent page visits. This system was significantly improved in February 2026 (PR #935) to fix critical bugs affecting object visibility and texture persistence. Location:packages/shared/src/utils/rendering/ModelCache.ts
Purpose: Cache processed GLTF/GLB models in IndexedDB to skip expensive parsing on subsequent loads. The cache stores:
- Serialized mesh geometry (positions, normals, UVs, skinning data)
- Material properties (colors, roughness, metalness, textures)
- Scene hierarchy (parent-child relationships, transforms)
- Animation clips (keyframes, tracks)
- Collision data (optional PhysX mesh data)
-
Missing Objects Bug (Identity Map Fix):
- Issue: Models with duplicate mesh names (e.g., "", “Cube”, “Cube”) lost objects after deserialization.
serializeNodeusedfindIndex-by-name which caused all duplicate names to resolve to the same index. Three.jsadd()auto-removes from previous parent, so only the last reference survived. - Impact: Altars and other multi-mesh models appeared incomplete or invisible after page refresh
- Fix: Replaced name-based lookup with
Map<Object3D, number>identity map built during traversal - Code:
- Result: All objects in models now render correctly after cache restore
- Issue: Models with duplicate mesh names (e.g., "", “Cube”, “Cube”) lost objects after deserialization.
-
Lost Textures Bug (Pixel Extraction Fix):
- Issue: Textures were serialized as ephemeral
blob:URLs but never reloaded during deserialization - Impact: Models appeared white or with wrong colors after page refresh
- Fix: Extract raw RGBA pixels via canvas
getImageData()(synchronous) and restore asTHREE.DataTexture - Code:
- Result: Textures persist correctly across page refreshes with no async loading race conditions
- Issue: Textures were serialized as ephemeral
-
Grey Trees in WebGPU (Duck-Type Fix):
- Issue:
createDissolveMaterialusedinstanceof MeshStandardMaterialwhich fails forMeshStandardNodeMaterialin WebGPU build (separate classes) - Impact: Tree materials rendered grey instead of textured in WebGPU mode
- Fix: Replaced with duck-type property check
- Code:
- Result: Tree materials render correctly in both WebGL and WebGPU builds
- Issue:
localStorage.setItem('disable-model-cache', 'true')bypass option for debugging- Error logging on IndexedDB
put()and transaction failures - Cache version bumped to 3 to invalidate broken entries
- All texture types supported: map, normalMap, emissiveMap, roughnessMap, metalnessMap, aoMap
- Proper color space handling (sRGB for diffuse, Linear for data textures)
GPU-Instanced Particle System (PR #877)
The shared package includes a centralized particle system that uses GPU instancing for high-performance visual effects. Introduced in February 2026, this system provides dramatic performance improvements for fishing spots and other particle-heavy entities. Architecture:- ParticleManager: Central router that dispatches particle events to specialized sub-managers
- Single entry point for all particle systems
- Type-based routing to appropriate sub-manager
- Ownership tracking for efficient lifecycle management
- Extensible design for future particle types
- WaterParticleManager: GPU-instanced fishing spot effects
- 4 InstancedMeshes (splash, bubble, shimmer, ripple) with TSL shaders
- GPU-computed animations (parabolic arcs, wobble, twinkle, ring expansion)
- Per-instance data via InstancedBufferAttributes
- Fishing spot variant system (net, bait, fly)
- Draw Call Reduction: ~150 → 4 per fishing spot (97% reduction)
- FPS Improvement: 65-70 → 120 on reference hardware (80% increase)
- CPU Savings: ~450 lines of per-entity animation code eliminated
- GPU-Driven: All particle updates computed on GPU via TSL shaders
- Zero CPU Overhead: No per-frame CPU particle animation
- InstancedBufferAttributes: Per-particle data storage
- spotPos (vec3): fishing spot world center
- ageLifetime (vec2): current age, total lifetime
- angleRadius (vec2): polar angle, radial distance
- dynamics (vec4): peakHeight, size, speed, direction
- TSL NodeMaterials: GPU-computed particle animations using Three.js Shading Language
- Billboard orientation via camera right/up vectors
- Parabolic arc trajectories for splash particles
- Wobble and drift patterns for bubbles
- Twinkle effects for shimmer particles
- Ring expansion and fade for ripples
- Vertex Buffer Budget: 7 of 8 max attributes per particle layer
- position(1) + uv(1) + instanceMatrix(1) + spotPos(1) + ageLifetime(1) + angleRadius(1) + dynamics(1)
- Pool Sizes: MAX_SPLASH=96, MAX_BUBBLE=72, MAX_SHIMMER=72, MAX_RIPPLE=24
- Fishing Variants: Net (calm), Bait (medium), Fly (active)
- Variant-specific colors, ripple speeds, particle counts
- Burst intervals: Net 5-10s, Bait 3-7s, Fly 2-5s
- Burst splash counts: Net 2, Bait 3, Fly 4
- Burst System: Fish activity bursts fire splash particles simultaneously
- Burst center randomized within 0.05-0.15 radius
- Burst particles have higher peak heights (0.25-0.6 vs 0.12-0.32)
- Burst particles cluster around burst center with 0.06 spread
- ResourceSystem: Creates ParticleManager on client startup
- Retroactive registration for entities created before system start
- Listens to RESOURCE_SPAWNED events for particle routing
- Per-frame update drives all particle managers via update(dt, camera)
- Proper disposal on system shutdown
- ResourceEntity: Delegates particle lifecycle to ParticleManager
- Lazy registration pattern (retries if manager not ready during entity init)
- Retains only lightweight glow mesh for interaction detection
- Proper cleanup via unregister on entity destroy
- Removed ~450 lines of CPU particle animation code
- Create new sub-manager class in
particleManager/folder - Instantiate in ParticleManager constructor
- Add routing logic in register/unregister/move/handleEvent methods
- Call update() and dispose() from ParticleManager
Manifest-Driven Content
Game content is defined in JSON manifest files inpackages/server/world/assets/manifests/:
Add new content by editing these JSON files—no code changes required.
Code Quality & Architecture
Code Audit Fixes (February 2026)
A comprehensive code audit was performed in commit 3bc59db, addressing critical issues: 1. Memory Leak Fix:- Issue: InventoryInteractionSystem registered 9 event listeners that were never removed
- Impact: Memory leak on every player connection
- Fix: Use AbortController for proper event listener cleanup
- Issue: JWT_SECRET optional in production, allowing unsigned tokens
- Impact: Security vulnerability in production deployments
- Fix: Now throws error in production/staging if JWT_SECRET not set
- Issue: WebGL fallback attempted but all shaders use TSL (WebGPU-only)
- Impact: Broken rendering on WebGL-only browsers
- Fix: Enforce WebGPU-only rendering with user-friendly error screen
- Deleted
PacketHandlers.ts(3,098 lines of dead code, never imported) - Updated audit TODOs to reflect actual codebase state:
- AUDIT-002: ServerNetwork already decomposed into 30+ modules (not 116K lines)
- AUDIT-003: ClientNetwork handlers are intentional thin wrappers (not bloated)
- AUDIT-005:
anytypes reduced from 142 to ~46 after cleanup
- Eliminated explicit
anytypes in core game logic tile-movement.ts: Removed 13 any casts by properly typing BuildingCollisionServiceproxy-routes.ts: Replaced any with proper types (unknown, Buffer | string, Error)ClientGraphics.ts: Added safe cast after WebGPU verification
any types (acceptable):
- TSL shader code (ProceduralGrass.ts) - @types/three limitation
- Browser polyfills (polyfills.ts) - intentional mock implementations
- Test files - acceptable for test fixtures
Architectural TODOs
The codebase includes TODO comments tracking architectural refactoring opportunities: AUDIT-001: Entity.ts Decomposition- Current: Entity.ts is large but manageable
- Recommendation: Extract visual/physics/networking concerns to separate classes
- Priority: Low (current structure works well)
- Status: ✅ Already decomposed into 30+ modules
- Location:
packages/server/src/systems/ServerNetwork/handlers/ - Actual size: ~3K lines (not 116K as originally estimated)
- Status: ✅ Handlers are intentional thin wrappers
- Purpose: Emit events for other systems to handle
- Actual size: ~5K lines (not 165K as originally estimated)
- Extraction not needed - current design is correct
- Status: ✅ Fixed in commits 3b9c0f2, 05c2892 (Feb 26, 2026)
- Solution: Removed cross-references between shared ↔ procgen
- Uses devDependencies for TypeScript type resolution without build cycle
- Status: ✅ Reduced from 142 to ~46 (commit d9113595)
- Remaining any types are in acceptable locations (shaders, polyfills, tests)
Build Pipeline
Circular Dependency Handling
The build system handles circular dependencies between packages gracefully: procgen ↔ shared circular dependency (FIXED):- Previous Issue:
@hyperscape/sharedimports from@hyperscape/procgen, procgen imports from shared - Solution (commits 3b9c0f2, 05c2892):
- Removed cross-references entirely from both package.json files
- Added procgen as devDependency in shared for TypeScript type resolution
- devDependencies not followed by turbo’s
^buildtopological ordering (no cycle) - Imports still resolve at runtime via bun workspace resolution
- Result: Clean builds work without circular dependency errors
- Similar pattern with
--skipLibCheckin shared’s declaration generation - Variable shadowing fixes (e.g., PlayerMovementSystem.ts tile redeclaration)
Automated Model Bounds Extraction
The server package includes a build-time task that automatically extracts collision footprints from 3D models:- Scans
world/assets/models/**/*.glbfiles - Parses glTF position accessor min/max values
- Calculates bounding boxes and tile footprints
- Generates
world/assets/manifests/model-bounds.json
packages/server/turbo.json):
The bounds extraction is cached - it only re-runs when GLB files or the script changes. This keeps builds fast while ensuring collision data stays in sync with models.