> ## Documentation Index
> Fetch the complete documentation index at: https://hyperscape-ai-mintlify-docs-update.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# shared

> Core 3D engine with ECS, Three.js, PhysX, and networking

## 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

<Warning>
  **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+).
</Warning>

## Package Location

```
packages/shared/
├── src/
│   ├── components/     # ECS components
│   ├── constants/      # Game constants
│   ├── core/           # Core engine classes
│   ├── data/           # Game manifests (npcs, items, etc.)
│   ├── entities/       # Entity definitions
│   │   ├── player/     # PlayerEntity, PlayerLocal, PlayerRemote
│   │   ├── npc/        # MobEntity, NPCEntity
│   │   ├── world/      # World entities
│   │   └── managers/   # Entity managers
│   ├── extras/         # Additional utilities
│   ├── libs/           # External integrations
│   ├── nodes/          # Scene graph nodes
│   ├── physics/        # PhysX wrapper
│   ├── platform/       # Platform detection
│   ├── runtime/        # Game loop
│   ├── systems/        # ECS systems
│   │   ├── client/     # Client-only systems
│   │   ├── server/     # Server-only systems
│   │   └── shared/     # Shared systems (combat, economy, etc.)
│   ├── types/          # TypeScript types
│   └── utils/          # Helper functions
├── index.ts            # Server exports
└── index.client.ts     # Client exports
```

## 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:**

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// From TerrainSystem.ts
private heightCache = new Map<string, Float32Array>();  // tileKey → height grid
private colorCache = new Map<string, Uint8Array>();     // tileKey → color grid

// Tile key format: "tileKey_x_z" (e.g., "tileKey_0_0", "tileKey_-1_2")
```

**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**:
     ```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
     export function worldToTerrainTileIndex(worldCoord: number): number {
       return Math.floor((worldCoord + HALF_TILE_SIZE) / TILE_SIZE);
     }
     ```
   * **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**:
     ```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
     export function localToGridIndex(localCoord: number, halfSize: number): number {
       return Math.floor(localCoord + halfSize);
     }
     ```
   * **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:**

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// Get height at world position (uses cache if available)
const height = terrainSystem.getHeightAt(worldX, worldZ);

// Get terrain color at world position (uses cache if available)
const color = terrainSystem.getTerrainColorAt(worldX, worldZ);

// Both methods now use canonical helpers internally:
const tileX = worldToTerrainTileIndex(worldX);
const tileZ = worldToTerrainTileIndex(worldZ);
const tileKey = `tileKey_${tileX}_${tileZ}`;
const heightGrid = this.heightCache.get(tileKey);
if (heightGrid) {
  const localX = worldX - tileX * TILE_SIZE;
  const localZ = worldZ - tileZ * TILE_SIZE;
  const gridX = localToGridIndex(localX, HALF_TILE_SIZE);
  const gridZ = localToGridIndex(localZ, HALF_TILE_SIZE);
  const index = gridZ * resolution + gridX;
  return heightGrid[index];
}
```

**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:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// Noise layer definitions
export const CONTINENT_LAYER: NoiseLayerDef = {
  scale: 0.0008,
  octaves: 5,
  persistence: 0.7,
  lacunarity: 2.0,
  weight: 0.35,
};

export const HILL_LAYER: NoiseLayerDef = {
  scale: 0.02,
  octaves: 4,
  persistence: 0.6,
  lacunarity: 2.2,
  weight: 0.25,
};

// Island configuration
export const ISLAND_RADIUS = 350;
export const ISLAND_FALLOFF = 100;
export const BASE_ELEVATION = 0.42;

// Worker code generation
export function buildGetBaseHeightAtJS(): string {
  // Injects constants into worker string
}
```

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

<Info>
  **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.
</Info>

**Location**: `packages/shared/src/systems/shared/world/DissolveAnimation.ts`

The dissolve system provides visual feedback for resource depletion and respawn using GPU-driven animations:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
import { startDissolve, tickDissolveAnims, type DissolveAnim } from './DissolveAnimation';

// Animation state
interface DissolveAnim {
  direction: 1 | -1;  // 1 = dissolving out, -1 = appearing in
  progress: number;   // 0.0 to DISSOLVE_MAX (1.0)
}

// Start dissolve animation
startDissolve(
  dissolveAnims,      // Map<string, DissolveAnim>
  entityId,           // Entity to animate
  direction,          // 1 = dissolve out, -1 = appear in
  instant,            // true = jump to target, false = animate
  applyDissolveValue  // Callback to write value to GPU
);

// Tick animations each frame
tickDissolveAnims(
  dissolveAnims,      // Map<string, DissolveAnim>
  deltaTime,          // Frame delta in seconds
  applyDissolveValue  // Callback to write value to GPU
);
```

**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`):

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  DISSOLVE_DURATION: 0.3,  // Animation duration (seconds)
  DISSOLVE_MAX: 1.0,       // Max dissolve progress
  DISSOLVE_ALPHA_SCALE: 0.7,  // Fraction of fragments discarded (70%)
}
```

**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](/wiki/engine/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:**

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// From packages/shared/src/systems/shared/world/GLBTreeInstancer.ts
export class GLBTreeInstancer {
  private pools: Map<string, LODPool> = new Map();
  
  allocate(modelId: string, position: Vector3, rotation: number): InstanceHandle | null {
    // Allocates instance slot from pre-sized pool (max 1000 per LOD)
  }
  
  free(handle: InstanceHandle): void {
    // Returns instance slot to pool for reuse
  }
  
  setDepleted(handle: InstanceHandle, isDepleted: boolean): void {
    // Handles stump state transitions
  }
}
```

**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:**

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// From packages/shared/src/entities/world/visuals/createVisualStrategy.ts
export function createVisualStrategy(
  config: ResourceConfig,
  entity: ResourceEntity
): ResourceVisualStrategy {
  // Selects strategy based on resource type, model path, and manifest config
}
```

**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:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// From packages/shared/src/entities/managers/particleManager/
export { ParticleManager } from './ParticleManager';
export { WaterParticleManager } from './WaterParticleManager';
```

**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(time*8 + angle*5) \* sin(time*13 + angle*3)
  * 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

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
import { ParticleManager } from '@hyperscape/shared';

// ResourceSystem creates ParticleManager on client startup
const particleManager = new ParticleManager(scene);

// Register water particles (fishing spot)
particleManager.registerSpot({
  entityId: 'entity-123',
  position: { x: 10, y: 0, z: 20 },
  resourceType: 'fishing_spot',
  resourceId: 'fishing_spot_net'
});

// Move existing spot (when fishing spot relocates)
particleManager.moveSpot('entity-123', 'fishing_spot', { x: 12, y: 0, z: 22 });

// Per-frame update (called by ResourceSystem)
particleManager.update(deltaTime, camera);

// Cleanup on entity destroy
particleManager.unregisterSpot('entity-123', 'fishing_spot');

// Dispose all particle managers on system shutdown
particleManager.dispose();
```

### Integration with ResourceSystem

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

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// From packages/shared/src/systems/shared/entities/ResourceSystem.ts

// CLIENT: Create centralized particle hub for all particle effects
if (!this.world.isServer) {
  const scene = this.world.stage?.scene;
  if (scene) {
    this.particleManager = new ParticleManager(scene as any);

    // Retroactively register any fishing spot entities created before this system started
    const existingEntities = this.world.entities?.getByType?.("resource") || [];
    for (const entity of existingEntities) {
      if (
        entity instanceof ResourceEntity &&
        entity.config?.resourceType === "fishing_spot"
      ) {
        entity.tryRegisterWithParticleManager();
      }
    }
  }

  // Listen for resource events and route them through the particle hub
  this.subscribe(
    EventType.RESOURCE_SPAWNED,
    (data: { id?: string; type?: string; position?: { x: number; y: number; z: number } }) => {
      this.particleManager?.handleResourceEvent(data);
    }
  );
}

// Per-frame update: drives all particle managers on the client
update(dt: number): void {
  if (this.particleManager) {
    const camera = this.world.camera;
    if (camera) {
      this.particleManager.update(dt, camera);
    }
  }
}
```

### Lazy Registration Pattern

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

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// From packages/shared/src/entities/world/ResourceEntity.ts

private _registeredWithParticleManager = false;

public tryRegisterWithParticleManager(): boolean {
  if (this._registeredWithParticleManager) return true;

  const pm = this.getParticleManager();
  if (!pm) return false;

  const pos = this.getPosition();
  pm.registerSpot({
    entityId: this.id,
    position: { x: pos.x, y: pos.y, z: pos.z },
    resourceType: this.config.resourceType || "",
    resourceId: this.config.resourceId || "",
  });
  this._registeredWithParticleManager = true;
  return true;
}

protected clientUpdate(_deltaTime: number): void {
  super.clientUpdate(_deltaTime);

  // Lazy registration: retry if particle manager wasn't ready during createFishingSpotVisual
  if (
    !this._registeredWithParticleManager &&
    this.config.resourceType === "fishing_spot"
  ) {
    if (this.tryRegisterWithParticleManager()) {
      console.log(`[FishingSpot] Late registration succeeded for ${this.id}`);
    }
  }

  // Organic glow pulse — two frequencies layered for natural breathing
  if (this.glowMesh) {
    const now = Date.now();
    const slow = Math.sin(now * 0.0015) * 0.04;
    const fast = Math.sin(now * 0.004 + 1.3) * 0.02;
    const pulse = 0.18 + slow + fast;
    (this.glowMesh.material as THREE.MeshBasicMaterial).opacity = pulse;
  }
}
```

### 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/`
   ```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
   // Example: MagicParticleManager.ts
   export class MagicParticleManager {
     constructor(scene: THREE.Scene) { /* ... */ }
     registerSpot(config: MagicSpotConfig): void { /* ... */ }
     unregisterSpot(entityId: string): void { /* ... */ }
     update(dt: number, camera: THREE.Camera): void { /* ... */ }
     dispose(): void { /* ... */ }
   }
   ```

2. **Instantiate in ParticleManager**: Add to constructor
   ```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
   export class ParticleManager {
     private waterManager: WaterParticleManager;
     private magicManager: MagicParticleManager; // Add new manager

     constructor(scene: THREE.Scene) {
       this.waterManager = new WaterParticleManager(scene);
       this.magicManager = new MagicParticleManager(scene); // Instantiate
     }
   }
   ```

3. **Add Routing Logic**: Update register/unregister/move/handleEvent methods
   ```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
   registerSpot(config: ParticleSpotConfig): void {
     if (this.isWaterType(config.resourceType)) {
       this.waterManager.registerSpot({ /* ... */ });
       return;
     }
     if (this.isMagicType(config.resourceType)) {
       this.magicManager.registerSpot({ /* ... */ });
       return;
     }
   }
   ```

4. **Call update() and dispose()**: Add to ParticleManager lifecycle methods
   ```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
   update(dt: number, camera: THREE.Camera): void {
     this.waterManager.update(dt, camera);
     this.magicManager.update(dt, camera); // Add update call
   }

   dispose(): void {
     this.waterManager.dispose();
     this.magicManager.dispose(); // Add dispose call
   }
   ```

## Rendering System

### WebGPU-Only Architecture

As of February 2026, all rendering uses WebGPU exclusively:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// From utils/rendering/RendererFactory.ts
export type RendererBackend = "webgpu";  // WebGL removed
export type UniversalRenderer = WebGPURenderer;

// Create WebGPU renderer (no fallback)
export async function createRenderer(options?: RendererOptions): Promise<WebGPURenderer> {
  if (!navigator.gpu) {
    throw new Error('WebGPU not supported. Please use Chrome 113+, Edge 113+, or Safari 18+');
  }
  
  const renderer = new THREE.WebGPURenderer({
    antialias: options?.antialias ?? true,
    powerPreference: options?.powerPreference ?? "high-performance",
  });
  
  await renderer.init();
  return renderer;
}
```

**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:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// From packages/shared/src/entities/Entity.ts (lines 26-38)
// Entity (base class)
// ├── InteractableEntity (can be interacted with)
// │   ├── ResourceEntity (trees, rocks, fishing spots)
// │   ├── ItemEntity (ground items)
// │   └── NPCEntity (dialogue, shops)
// ├── CombatantEntity (can fight)
// │   ├── PlayerEntity (base player)
// │   │   ├── PlayerLocal (client-side local player)
// │   │   └── PlayerRemote (client-side remote players)
// │   └── MobEntity (enemies)
// └── HeadstoneEntity (player death markers)
```

| Export               | Purpose                                                |
| -------------------- | ------------------------------------------------------ |
| `Entity`             | Base entity class with components, physics, networking |
| `CombatantEntity`    | Combat-capable entities with health, damage            |
| `InteractableEntity` | Objects players can interact with                      |
| `PlayerEntity`       | Server-side player with full stats, inventory          |
| `PlayerLocal`        | Client-side local player with input handling           |
| `PlayerRemote`       | Client-side representation of other players            |
| `MobEntity`          | NPCs and hostile mobs with AI states                   |
| `HeadstoneEntity`    | Death marker for item retrieval                        |

### Systems (Shared)

Located in `src/systems/shared/`:

| Category        | Systems                                     |
| --------------- | ------------------------------------------- |
| `character/`    | Character stats, equipment, skills          |
| `combat/`       | Combat mechanics, damage, death             |
| `death/`        | Death handling, respawn                     |
| `economy/`      | Banks, shops, trading                       |
| `entities/`     | Entity lifecycle management                 |
| `interaction/`  | Player-world interactions                   |
| `movement/`     | Tile-based movement, collision, pathfinding |
| `presentation/` | Visual rendering                            |
| `world/`        | World management                            |

### Collision System

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

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// From packages/shared/src/systems/shared/movement/
export { CollisionMatrix } from './CollisionMatrix';
export { CollisionFlag, CollisionMask } from './CollisionFlags';
export { EntityOccupancyMap } from './EntityOccupancyMap';
export { TileSystem } from './TileSystem';
```

**Key Exports:**

| Export                        | Purpose                                         |
| ----------------------------- | ----------------------------------------------- |
| `CollisionMatrix`             | Zone-based collision storage (8×8 tile zones)   |
| `CollisionFlag`               | Bitmask flags (BLOCKED, WATER, OCCUPIED, walls) |
| `CollisionMask`               | Combined masks (BLOCKS\_WALK, BLOCKS\_MOVEMENT) |
| `EntityOccupancyMap`          | Entity tracking facade over CollisionMatrix     |
| `tilesWithinRangeOfFootprint` | Multi-tile interaction range checks             |

**Usage:**

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
import { CollisionMatrix, CollisionMask } from '@hyperscape/shared';

const collision = new CollisionMatrix();

// Check if tile is walkable
if (!collision.hasFlags(x, z, CollisionMask.BLOCKS_WALK)) {
  // Safe to move
}

// Add blocking for a tree
collision.addFlags(x, z, CollisionFlag.BLOCKED);
```

### Data Manifests

Game content is defined in TypeScript files in `src/data/`:

| File                          | Purpose                                   |
| ----------------------------- | ----------------------------------------- |
| `DataManager.ts`              | Runtime loader for manifests              |
| `npcs.ts`                     | NPC/mob definitions with drops, stats     |
| `items.ts`                    | Item definitions with stats, requirements |
| `world-areas.ts`              | Zone configuration                        |
| `banks-stores.ts`             | Shop inventories and bank locations       |
| `avatars.ts`                  | VRM avatar options                        |
| `playerEmotes.ts`             | Emote animation URLs                      |
| `world-structure.ts`          | World layout and spawn points             |
| `equipment-stats.json`        | Equipment bonuses                         |
| `equipment-requirements.json` | Level requirements                        |

<Info>
  NPC data is loaded from JSON manifests at runtime by DataManager.
  Add new NPCs in `world/assets/manifests/npcs.json`.
</Info>

## Entry Points

### Server (index.ts)

Exports for server-side usage:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
import { 
  Entity, 
  DataManager, 
  PlayerEntity,
  MobEntity 
} from '@hyperscape/shared';
```

### Client (index.client.ts)

Exports for client-side usage:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
import { 
  Entity, 
  PlayerLocal,
  PlayerRemote 
} from '@hyperscape/shared/client';
```

## Dependencies

| Package                       | Version            | Purpose                                      |
| ----------------------------- | ------------------ | -------------------------------------------- |
| `three`                       | 0.184.0            | 3D rendering                                 |
| `three-mesh-bvh`              | 0.9.8              | BVH raycasting optimization                  |
| `@pixiv/three-vrm`            | 3.5.3              | VRM avatar support                           |
| `@hyperscape/physx-js-webidl` | workspace          | Physics                                      |
| `@hyperscape/procgen`         | workspace (devDep) | Procedural generation (type resolution only) |
| `react`                       | 19.2.7             | UI components                                |
| `styled-components`           | ^6.1.19            | CSS-in-JS                                    |
| `fastify`                     | ^5.0.0             | HTTP server (server builds)                  |
| `livekit-client`              | ^2.9.9             | Voice chat                                   |
| `msgpackr`                    | 2.0.2              | Binary serialization for networking          |
| `lucide-react`                | 1.8.0              | Icon library                                 |
| `dotenv`                      | 17.4.2             | Environment variables                        |
| `commander`                   | 15.0.0             | CLI argument parsing                         |

<Note>
  **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)
</Note>

## Building

```bash theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
cd packages/shared
bun run build    # Production build
bun run dev      # Watch mode with auto-rebuild
```

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

## Key Patterns

### ECS Architecture

All game logic uses Entity Component System. See [ECS Concepts](/concepts/ecs).

### Manifest-Driven Data

Game content defined in `src/data/`. See [Manifests](/concepts/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)
