Skip to main content

Overview

The @hyperscape/shared package is the core Hyperscape engine, providing:
  • Entity Component System (ECS)
  • Three.js WebGPU rendering (WebGPU required - no WebGL fallback)
  • PhysX physics simulation
  • Real-time networking
  • React UI components
  • Game data manifests
WebGPU Required: All rendering uses TSL (Three Shading Language) which only works with WebGPU. There is no WebGL fallback. Requires Chrome 113+, Edge 113+, or Safari 18+ (macOS 15+).

Package Location

Terrain Generation System

The shared package includes a sophisticated procedural terrain generation system with multi-threaded performance and cached height lookups for optimal performance.

Terrain Height Cache System

The terrain system uses a tile-based cache for fast height queries without re-evaluating noise functions. Critical bugs in the cache indexing were fixed in commit 21e0860 (Feb 25, 2026). Cache Structure:
Critical Bug Fixes (commit 21e0860):
  1. Tile Index Calculation Error:
    • Issue: getHeightAtCached used Math.floor(worldX/TILE_SIZE) which doesn’t account for PlaneGeometry’s centered coordinates
    • Impact: Terrain tiles were indexed incorrectly, causing ~50m vertical offset in height queries
    • Fix: Added worldToTerrainTileIndex() canonical helper
    • Formula:
    • Result: Terrain height queries now return correct values for all world positions
  2. Grid Index Formula Error:
    • Issue: Grid index formula omitted the halfSize offset from PlaneGeometry’s [-50,+50] range
    • Impact: Local coordinates within tiles were mapped incorrectly to height data arrays
    • Fix: Added localToGridIndex() canonical helper
    • Formula:
    • Result: Height data lookups within tiles now use correct array indices
  3. Color Cache Key Typo:
    • Issue: getTerrainColorAt() used comma separator (tileKey_x,z) instead of underscore (tileKey_x_z)
    • Impact: Color cache lookups always failed, forcing expensive recomputation
    • Fix: Corrected key format to match height cache convention
    • Result: Color cache now works correctly, improving terrain rendering performance
Usage Example:
Impact of Fixes:
  • Eliminates 50m vertical offset in player spawning and entity placement
  • Fixes vegetation placement using incorrect terrain heights
  • Prevents height query inconsistencies between cached and real-time lookups
  • Improves terrain color rendering performance via working color cache

TerrainHeightParams.ts

Single source of truth for all terrain generation parameters. Both TerrainSystem (main thread) and TerrainWorker (web worker) consume these values to ensure identical terrain generation:
Key Features:
  • Prevents parameter drift between main thread and workers
  • TypeScript constants injected into worker code at runtime
  • Changing a constant automatically updates both threads
  • Includes noise layers, island mask, pond, coastline, and mountain boost

Web Worker Terrain Generation

Terrain generation offloaded to web workers for parallel processing:
  • Worker Height Computation: Full height calculation including shoreline adjustment
  • Worker Normal Computation: Normals computed via (resolution+2)² overflow grid
  • Performance: 63x reduction in main-thread noise evaluations
  • Conditional Fallback: Main-thread recomputation only for tiles with flat zones (buildings/stations)
  • Parallel Processing: Utilizes multiple CPU cores while keeping main thread free

GPU-Instanced Rendering Systems

Tree Dissolve Transparency System (March 2026)

New Feature (PR #1101): Depleted trees use screen-door dithering to become ~70% transparent instantly on depletion, then animate back to full opacity over 0.3s on respawn.
Location: packages/shared/src/systems/shared/world/DissolveAnimation.ts The dissolve system provides visual feedback for resource depletion and respawn using GPU-driven animations:
Key Features:
  • Dual Encoding: InstancedMesh uses instanceDissolve attribute, BatchedMesh uses batch color blue channel
  • Screen-Door Dithering: Uses Bayer 4×4 pattern in alphaTestNode to keep trees in opaque render pass
  • LOD Preservation: Dissolve state carries over during LOD transitions (no visual pops)
  • Interrupt Handling: Continues from current progress on direction reversal (no pops)
  • Zero Allocation: Reuses module-level _completed array to avoid per-frame allocation
Configuration (GPU_VEG_CONFIG):
Performance:
  • No alpha blending overhead (opaque render pass)
  • Full early-Z rejection
  • No transparency sorting required
  • Consistent performance regardless of dissolve state
See Visual Effects for complete documentation.

GLBTreeInstancer (commit 0871acb)

InstancedMesh-based tree rendering system that replaces per-tree scene.clone(true) with shared InstancedMesh pools per LOD level. 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
Implementation:
Integration:
  • Initialized in createClientWorld.ts when stage scene is ready
  • ResourceEntity routes GLB trees through instancer instead of scene.clone()
  • Handles depleted/stump/destroy lifecycle via no-op-safe calls

ResourceEntity Visual Strategy Pattern (commit bc60264)

Refactored ResourceEntity (~1700 lines removed) into delegated visual strategies using the Strategy Pattern. Visual Strategies:
  1. TreeGLBVisualStrategy: GLB tree models with LOD support
    • Uses GLBTreeInstancer for instanced rendering
    • Handles LOD transitions based on camera distance
    • Manages stump state transitions
  2. TreeProcgenVisualStrategy: Procedurally generated trees
    • Uses ProcgenTreeInstancer for procedural geometry
    • Supports runtime tree generation with L-system parameters
    • Handles leaf/branch color variations
  3. StandardModelVisualStrategy: Generic 3D models (rocks, ores)
    • Loads GLB models via ModelCache
    • Handles scale and rotation from manifest
    • Supports depleted model swapping
  4. FishingSpotVisualStrategy: Fishing spot particles
    • Registers with ParticleManager for GPU-instanced water effects
    • Handles ripple/splash/bubble animations
    • Manages particle lifecycle on depletion
  5. PlaceholderVisualStrategy: Fallback for missing models
    • Uses PlaceholderInstancer for instanced colored cubes
    • Color-coded by resource type (green=tree, brown=ore, blue=fishing)
    • Automatically used when modelPath is null or “null” string
Factory Pattern:
Benefits:
  • 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
PlaceholderInstancer:
  • Manages InstancedMesh pools for placeholder resources (trees/ores with missing models)
  • Color-coded: green (trees), brown (ores), blue (fishing spots)
  • Max 1000 instances per resource type
  • Initialized in createClientWorld.ts alongside GLBTreeInstancer
Bug Fixes:
  • Fixed fishing spot particles persisting after depletion (guard re-registration when depleted, zero ripple phase offset on unregister)
  • Fixed placeholder trees not rendering due to “null” string in modelPath (sanitize in ResourceSystem + createVisualStrategy factory)

Particle System

The shared package includes a centralized GPU-instanced particle system for high-performance visual effects. This system was introduced in PR #877 and provides a 97% reduction in draw calls with an 80% FPS improvement.

ParticleManager Architecture

Unified particle routing system that dispatches particle events to specialized sub-managers:
Core Components:
  • ParticleManager: Central router with ownership tracking and event dispatching
  • WaterParticleManager: GPU-instanced fishing spot effects (splash, bubble, shimmer, ripple)
Performance Benefits:
  • Reduces ~150 draw calls to 4 per fishing spot (97% reduction)
  • FPS improvement: 65-70 → 120 on reference hardware (80% increase)
  • ~450 lines of CPU animation code eliminated
  • Zero CPU overhead for particle updates (all on GPU via TSL shaders)
  • GPU-driven particle animations (parabolic arcs, wobble, twinkle, ring expansion)

WaterParticleManager Implementation

GPU-instanced water particle effects for fishing spots using InstancedMesh and TSL shaders: Technical Details:
  • 4 InstancedMeshes: splash, bubble, shimmer, ripple layers
  • TSL NodeMaterials: GPU-computed animations using Three.js Shading Language
  • InstancedBufferAttributes: Per-particle data storage
    • spotPos (vec3): fishing spot world center
    • ageLifetime (vec2): current age (x), total lifetime (y)
    • angleRadius (vec2): polar angle (x), radial distance (y)
    • dynamics (vec4): peakHeight (x), size (y), speed (z), direction (w)
  • 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)
  • Ripple Layer: 5 of 8 max attributes
    • position(1) + uv(1) + instanceMatrix(1) + spotPos(1) + rippleParams(1)
  • Pool Sizes: MAX_SPLASH=96, MAX_BUBBLE=72, MAX_SHIMMER=72, MAX_RIPPLE=24
Particle Animations:
  • Splash: Parabolic arc trajectories with pop-in fade
    • arcY = peakHeight * 4 * t * (1-t) for parabolic motion
    • Snappy fade-in (12x rate), smooth fade-out (power 1.2)
    • Lifetime: 0.6-1.2 seconds
  • Bubble: Rising with lateral wobble
    • Wobble frequency: direction * 4.0
    • Drift pattern: sin(angle + t * wobbleFreq) * radius
    • Lifetime: 1.2-2.5 seconds
  • Shimmer: Surface sparkle with twinkle effect
    • Fast twinkle using global time: sin(time8 + angle5) * sin(time13 + angle3)
    • Circular wander pattern on water surface
    • Lifetime: 1.5-3.0 seconds
  • Ripple: Expanding ring with fade
    • Phase-based scale: 0.15 + phase * 1.3
    • Early fade (0-15%): linear ramp to 0.55 opacity
    • Late fade (15-100%): power 1.5 decay from 0.55 to 0
    • Continuous expansion driven by time uniform
Fishing Spot Variants:
  • Net: Calm, gentle ripples (4 splash, 3 bubble, 3 shimmer, 2 ripples, burst every 5-10s)
  • Bait: Medium activity (5 splash, 4 bubble, 4 shimmer, 2 ripples, burst every 3-7s)
  • Fly: Active river fishing (8 splash, 5 bubble, 5 shimmer, 2 ripples, burst every 2-5s)
Burst System:
  • Fish activity bursts fire 2-4 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
  • Burst timer resets to random interval after each burst

Usage Example

Integration with ResourceSystem

The ParticleManager is created and managed by ResourceSystem on the client:

Lazy Registration Pattern

ResourceEntity uses a lazy registration pattern to handle timing issues where entities may be created before ResourceSystem starts:

Extensibility Guide

To add new particle types (fire, magic, dust, blood):
  1. Create Sub-Manager Class: Create new file in packages/shared/src/entities/managers/particleManager/
  2. Instantiate in ParticleManager: Add to constructor
  3. Add Routing Logic: Update register/unregister/move/handleEvent methods
  4. Call update() and dispose(): Add to ParticleManager lifecycle methods

Rendering System

WebGPU-Only Architecture

As of February 2026, all rendering uses WebGPU exclusively:
Breaking Change (Commit 47782ed, Feb 2026):
  • Removed all WebGL detection and fallback code
  • Removed isWebGLAvailable(), isWebGLForced(), canTransferCanvas() functions
  • RendererBackend type is now only "webgpu" (removed "webgl")
  • All shaders use TSL (Three Shading Language) which only compiles to WebGPU

Key Exports

Entity Hierarchy

The entity system follows a clear inheritance pattern:

Systems (Shared)

Located in src/systems/shared/:

Collision System

The movement system includes a unified collision matrix for OSRS-accurate tile blocking:
Key Exports: Usage:

Data Manifests

Game content is defined in TypeScript files in src/data/:
NPC data is loaded from JSON manifests at runtime by DataManager. Add new NPCs in world/assets/manifests/npcs.json.

Entry Points

Server (index.ts)

Exports for server-side usage:

Client (index.client.ts)

Exports for client-side usage:

Dependencies

Procgen Dependency: The @hyperscape/procgen package is listed as a devDependency (not a regular dependency) to break the circular dependency cycle. This allows TypeScript to find module declarations during type checking without creating a build cycle in Turbo. The import still resolves at runtime since both packages are always installed together in the workspace.Commits: f355276, 3b9c0f2, 05c2892 (Feb 25-26, 2026)

Building

Shared must be built before other packages that depend on it. Turbo handles this automatically.

Key Patterns

ECS Architecture

All game logic uses Entity Component System. See ECS Concepts.

Manifest-Driven Data

Game content defined in src/data/. See Manifests.

Type Safety

Strong TypeScript typing throughout—no any types allowed. ESLint enforces this rule.

Dual Entry Points

  • index.ts: Server-side exports (includes Fastify, database utilities)
  • index.client.ts: Client-side exports (browser-compatible)