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

# ParticleManager

> GPU-instanced particle system API reference

## ParticleManager

Central particle routing system that dispatches particle events to specialized sub-managers.

### Constructor

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
constructor(scene: THREE.Scene)
```

**Parameters:**

* `scene`: Three.js scene to add particle meshes to

**Example:**

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

const particleManager = new ParticleManager(scene);
```

### Methods

#### register

Register a particle emitter with discriminated union config.

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
register(id: string, config: ParticleConfig): void
```

**Parameters:**

* `id`: Unique identifier for the emitter
* `config`: Particle configuration (WaterParticleConfig | GlowParticleConfig)

**Example:**

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// Water particles (fishing spot)
particleManager.register('fishing-spot-1', {
  type: 'water',
  position: { x: 10, y: 0, z: 20 },
  resourceId: 'fishing_spot_net'
});

// Glow particles (altar)
particleManager.register('altar-1', {
  type: 'glow',
  preset: 'altar',
  position: { x: 15, y: 1, z: 25 },
  color: 0x00ffff,
  meshRoot: altarMesh,
  modelScale: 1.0
});
```

#### unregister

Unregister a particle emitter. No type hint required (ownership map resolves automatically).

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
unregister(id: string): void
```

**Parameters:**

* `id`: Emitter identifier to remove

**Example:**

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
particleManager.unregister('fishing-spot-1');
```

#### move

Move an existing emitter to a new position. No type hint required.

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
move(id: string, newPos: { x: number; y: number; z: number }): void
```

**Parameters:**

* `id`: Emitter identifier to move
* `newPos`: New world position

**Example:**

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
particleManager.move('fishing-spot-1', { x: 12, y: 0, z: 22 });
```

#### handleResourceEvent

Handle a resource event (e.g. RESOURCE\_SPAWNED) and route to appropriate manager.

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
handleResourceEvent(data: ParticleResourceEvent): void
```

**Parameters:**

* `data`: Event data with id, type, position

**Example:**

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// Called by ResourceSystem
this.subscribe(EventType.RESOURCE_SPAWNED, (data) => {
  this.particleManager?.handleResourceEvent(data);
});
```

#### update

Drive all particle managers. Called once per frame.

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
update(dt: number, camera: THREE.Camera): void
```

**Parameters:**

* `dt`: Delta time in seconds
* `camera`: Three.js camera for billboard orientation

**Example:**

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// In ResourceSystem.update()
update(dt: number): void {
  if (this.particleManager) {
    const camera = this.world.camera;
    if (camera) {
      this.particleManager.update(dt, camera);
    }
  }
}
```

#### dispose

Dispose all particle managers and clean up GPU resources.

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
dispose(): void
```

**Example:**

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// In ResourceSystem.destroy()
if (this.particleManager) {
  this.particleManager.dispose();
  this.particleManager = undefined;
}
```

## Types

### ParticleConfig

Discriminated union of all particle configuration types.

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
type ParticleConfig = WaterParticleConfig | GlowParticleConfig;
```

### WaterParticleConfig

Configuration for water-type particles (fishing spots).

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
interface WaterParticleConfig {
  type: 'water';
  position: { x: number; y: number; z: number };
  resourceId: string;  // e.g., 'fishing_spot_net', 'fishing_spot_fly'
}
```

**Fishing Spot Variants:**

* `fishing_spot_net`: Calm, gentle ripples (4 splash, 3 bubble, 3 shimmer)
* `fishing_spot_bait`: Medium activity (5 splash, 4 bubble, 4 shimmer)
* `fishing_spot_fly`: Active, moving water (8 splash, 5 bubble, 5 shimmer)

### GlowParticleConfig

Configuration for glow-type particles (altars, fires, torches).

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
interface GlowParticleConfig {
  type: 'glow';
  preset: GlowPreset;
  position: { x: number; y: number; z: number };
  color?: number | { core: number; mid: number; outer: number };
  meshRoot?: THREE.Object3D;
  modelScale?: number;
  modelYOffset?: number;
}
```

**Parameters:**

* `preset`: 'altar' | 'fire' | 'torch'
* `color`: Single hex (0x00ffff) or three-tone palette
* `meshRoot`: Mesh for geometry-aware spark placement (altar preset)
* `modelScale`: Scale of loaded model (default 1.0)
* `modelYOffset`: Vertical offset applied to model (default 0)

**Preset Particle Counts:**

* `altar`: 12 particles (samples mesh geometry)
* `fire`: 8 particles (rising with spread)
* `torch`: 6 particles (tight 0.08 spread)

### ParticleResourceEvent

Lightweight event shape emitted by ResourceSystem.

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
interface ParticleResourceEvent {
  id?: string;
  type?: string;
  position?: { x: number; y: number; z: number };
}
```

## WaterParticleManager

GPU-instanced fishing spot effects using 4 InstancedMeshes.

### Methods

#### registerSpot

Register a fishing spot for particle rendering.

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
registerSpot(config: {
  entityId: string;
  position: { x: number; y: number; z: number };
  resourceId: string;
}): void
```

#### unregisterSpot

Unregister a fishing spot and free particle slots.

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
unregisterSpot(entityId: string): void
```

#### moveSpot

Move a fishing spot to a new position.

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
moveSpot(
  entityId: string,
  newPos: { x: number; y: number; z: number }
): void
```

#### update

Per-frame particle animation update.

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
update(dt: number, camera: THREE.Camera): void
```

#### dispose

Dispose all GPU resources.

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
dispose(): void
```

### Particle Layers

**Splash Layer:**

* Parabolic arc trajectories
* 0.6-1.2s lifetime
* 0.05-0.35m radial spread
* 0.12-0.32m peak height

**Bubble Layer:**

* Rising with lateral wobble
* 1.2-2.5s lifetime
* 0.04-0.24m radial spread
* 0.3-0.55m rise height

**Shimmer Layer:**

* Surface glints with twinkle
* 1.5-3.0s lifetime
* 0.15-0.6m radial spread
* GPU-computed twinkle animation

**Ripple Layer:**

* Expanding ring waves
* Continuous loop (no lifetime)
* 0.15-1.45m scale range
* Phase-based opacity fade

## GlowParticleManager

Instanced glow billboards for altars, fires, and torches.

### Methods

#### registerGlow

Register a glow emitter.

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
registerGlow(id: string, config: {
  preset: GlowPreset;
  position: { x: number; y: number; z: number };
  color?: number | { core: number; mid: number; outer: number };
  meshRoot?: THREE.Object3D;
  modelScale?: number;
  modelYOffset?: number;
}): void
```

#### unregisterGlow

Unregister a glow emitter.

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
unregisterGlow(id: string): void
```

#### moveGlow

Move a glow emitter to a new position.

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
moveGlow(id: string, newPos: { x: number; y: number; z: number }): void
```

#### update

Per-frame particle animation update.

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
update(dt: number, camera: THREE.Camera): void
```

#### dispose

Dispose all GPU resources.

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
dispose(): void
```

### Presets

**Altar Preset:**

* 12 particles sampling mesh geometry vertices
* Mesh-aware placement (requires meshRoot)
* Upward rise with spread
* Color: Cyan (0x00ffff) or custom

**Fire Preset:**

* 8 particles rising with spread
* Random radial distribution
* Parabolic rise trajectories
* Color: Orange-red gradient or custom

**Torch Preset:**

* 6 particles with tight spread (0.08)
* Concentrated flame effect
* Minimal lateral movement
* Color: Orange (0xff8800) or custom

## Performance

### Draw Call Reduction

**Before:**

```
10 fishing spots × 15 particles each = 150 individual meshes
→ 150 draw calls
```

**After:**

```
4 InstancedMeshes (splash, bubble, shimmer, ripple)
→ 4 draw calls (shared across all fishing spots)
```

### Memory Usage

**Per Fishing Spot:**

* Before: \~500KB (150 meshes × \~3.3KB each)
* After: \~5KB (particle slot data in shared buffers)

**100 Fishing Spots:**

* Before: \~50MB
* After: \~2MB (98% reduction)

### FPS Impact

**Reference Hardware (RTX 3060 Ti):**

* Before: 65-70 FPS with 10 fishing spots
* After: 120 FPS with 10 fishing spots

## Integration Example

Complete integration in ResourceSystem:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// packages/shared/src/systems/shared/entities/ResourceSystem.ts
export class ResourceSystem extends SystemBase {
  public particleManager?: ParticleManager;

  start(): void {
    if (!this.world.isServer) {
      const scene = this.world.stage?.scene;
      if (scene) {
        // Create particle manager
        this.particleManager = new ParticleManager(scene);
        
        // Retroactive registration
        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
      this.subscribe(EventType.RESOURCE_SPAWNED, (data) => {
        this.particleManager?.handleResourceEvent(data);
      });
    }
  }

  update(dt: number): void {
    if (this.particleManager) {
      const camera = this.world.camera;
      if (camera) {
        this.particleManager.update(dt, camera);
      }
    }
  }

  destroy(): void {
    if (this.particleManager) {
      this.particleManager.dispose();
      this.particleManager = undefined;
    }
    super.destroy();
  }
}
```

## Vertex Buffer Budget

InstancedMesh has a maximum of 8 vertex attributes:

**Particle Layers (7 of 8):**

1. `position` (built-in)
2. `uv` (built-in)
3. `instanceMatrix` (built-in)
4. `spotPos` (custom, vec3)
5. `ageLifetime` (custom, vec2)
6. `angleRadius` (custom, vec2)
7. `dynamics` (custom, vec4)

**Ripple Layer (5 of 8):**

1. `position` (built-in)
2. `uv` (built-in)
3. `instanceMatrix` (built-in)
4. `spotPos` (custom, vec3)
5. `rippleParams` (custom, vec2)

<Warning>
  Adding more custom attributes requires removing existing ones or splitting into multiple InstancedMeshes.
</Warning>

## See Also

* [Particle System Guide](/wiki/engine/particles) - Detailed guide with TSL shader examples
* [ResourceEntity](/api-reference/resource-entity) - Entity that uses ParticleManager
* [ResourceSystem](/api-reference/resource-system) - System that creates ParticleManager
