Skip to main content

Resource Respawn System

Updated: March 27, 2026 (PR #1099)
Location: packages/shared/src/systems/shared/entities/ResourceSystem.ts

Overview

The resource respawn system provides deterministic, tick-based respawn mechanics for gathering resources (trees, rocks, fishing spots). It eliminates non-deterministic setTimeout-based respawn in favor of OSRS-accurate tick counting.

Key Changes (March 2026)

Before (Non-Deterministic)

Resources used setTimeout for respawn timing:
Problems:
  • Respawn timing varied based on server load and event loop congestion
  • Not OSRS-accurate (OSRS uses deterministic tick-based respawn)
  • Difficult to test and reproduce timing issues

After (Tick-Based)

Resources use tick counting for deterministic respawn:
Benefits:
  • ✅ Deterministic respawn timing (exact tick count)
  • ✅ OSRS-accurate mechanics
  • ✅ Testable and reproducible
  • ✅ No event loop dependency

Architecture

ResourceSystem

File: packages/shared/src/systems/shared/entities/ResourceSystem.ts

processRespawns()

Processes pending resource respawns based on tick count. Behavior:
  • Called every tick by the tick system
  • Iterates all depleted resources
  • Checks if currentTick - depletedAtTick >= respawnTicks
  • Calls resource.respawn() when ready
Implementation:

ResourceEntity

deplete(): Promise<void>

Marks resource as depleted and records depletion tick. Behavior:
  • Sets depleted = true
  • Records depletedAtTick = world.tickNumber
  • Calls visualStrategy.onDepleted() for visual feedback
  • Does NOT schedule respawn (handled by ResourceSystem.processRespawns())
Example:

respawn(): void

Respawns the resource and resets depletion state. Behavior:
  • Sets depleted = false
  • Resets depletedAtTick = null
  • Calls visualStrategy.onRespawn() for visual feedback
  • Emits RESOURCE_RESPAWNED event
Example:

Depletion Chance System

Manifest Configuration

Resources can specify a depleteChance in their manifest to control depletion probability:
depleteChance Values:
  • 1.0 - Always depletes on successful gather (default for most resources)
  • 0.5 - 50% chance to deplete on successful gather
  • 0.0 - Never depletes (e.g., rune essence rocks in OSRS)

Mining Integration

File: packages/server/src/systems/ServerNetwork/handlers/resources.ts Mining now reads depleteChance from manifest instead of using hardcoded constants:
Removed Constants:
  • MINING_DEPLETE_CHANCE - No longer used
  • MINING_REDWOOD_DEPLETE_CHANCE - No longer used
Impact:
  • ✅ Rune essence rocks work correctly (never deplete with depleteChance: 0)
  • ✅ Consistent depletion behavior across all gathering skills
  • ✅ Manifest-driven design (no code changes for new resources)

Tick Calculation

Respawn Timing

Respawn time is converted from milliseconds to ticks:
Example (600ms tick duration):
  • respawnTime: 2400ms4 ticks
  • respawnTime: 3000ms5 ticks
  • respawnTime: 1800ms3 ticks

Tick Counting

Depletion tick is recorded when resource is depleted:
Respawn check compares current tick to depletion tick:
Precision: Respawn timing is accurate to ±1 tick (±600ms with default tick rate).

OSRS Accuracy

Tick-Based Mechanics

OSRS uses a 600ms game tick for all timing:
  • Combat attacks
  • Resource respawns
  • Skill actions
  • Movement
Hyperscape matches this with TICK_DURATION_MS = 600.

Depletion Mechanics

OSRS Behavior:
  • Most resources deplete on every successful gather
  • Some resources (rune essence) never deplete
  • Some resources (redwood trees) have low depletion chance
Hyperscape Implementation:
Examples:
  • Copper rock: depleteChance: 1.0 (always depletes)
  • Rune essence: depleteChance: 0 (never depletes)
  • Redwood tree: depleteChance: 0.1 (10% chance to deplete)

API Reference

ResourceEntity

Properties

Methods

deplete(): Promise<void>
Depletes the resource and records depletion tick. Behavior:
  • Sets depleted = true
  • Records depletedAtTick = world.tickNumber
  • Calls visualStrategy.onDepleted() for visual feedback
  • Falls back to loadDepletedModel() if visual strategy doesn’t handle depletion
Example:
respawn(): void
Respawns the resource and resets depletion state. Behavior:
  • Sets depleted = false
  • Resets depletedAtTick = null
  • Calls visualStrategy.onRespawn() for visual feedback
  • Emits RESOURCE_RESPAWNED event
Example:

ResourceSystem

Methods

processRespawns(): void
Processes pending resource respawns based on tick count. Behavior:
  • Iterates all entities in world
  • Filters for depleted ResourceEntity instances
  • Checks if respawn time has elapsed (tick-based)
  • Calls resource.respawn() when ready
Called By: Tick system (every tick) Example:

Configuration

Manifest Schema

File: packages/server/world/assets/manifests/resources.json
Fields:
  • respawnTime - Respawn time in milliseconds (converted to ticks)
  • depleteChance - Probability of depletion on successful gather (0.0-1.0)
    • 1.0 - Always depletes (default)
    • 0.0 - Never depletes (rune essence rocks)
    • 0.1 - 10% chance (redwood trees)

Constants

File: packages/shared/src/constants/GameConstants.ts

Testing

Unit Tests

File: packages/shared/src/systems/shared/entities/gathering/__tests__/ToolUtils.test.ts

Integration Tests

File: packages/shared/src/systems/shared/entities/__tests__/ResourceSystem.integration.test.ts

Migration Guide

Updating from setTimeout-Based Respawn

Before:
After:

Updating Depletion Chance Logic

Before (hardcoded constants):
After (manifest-based):

Manifest Examples

Standard Tree (Always Depletes)

Rune Essence Rock (Never Depletes)

Note: respawnTime: 0 is ignored since depleteChance: 0 means the resource never depletes.

Redwood Tree (Low Depletion Chance)

Behavior: 10% chance to deplete on each successful gather. On average, depletes after 10 gathers.

Tick Priority

Resource respawn processing runs at TickPriority.RESOURCE_RESPAWN priority:
Tick Order (from TickPriority enum):
  1. MOVEMENT - Player/mob movement
  2. COMBAT - Combat processing
  3. RESOURCE_RESPAWN - Resource respawns
  4. CLEANUP - Entity cleanup
This ensures resources respawn after movement and combat, but before cleanup.

Performance Characteristics

CPU

  • O(resources): Iterates all resources every tick
  • Early-out: Skips non-depleted resources immediately
  • Typical Cost: ~0.01-0.1ms for 100-1000 resources

Memory

  • No Timers: No setTimeout handles to track
  • Minimal State: Only depletedAtTick number per resource
  • No Leaks: Tick-based approach has no timer cleanup issues

Troubleshooting

Resources not respawning

Symptoms: Depleted resources never respawn. Causes:
  1. ResourceSystem.processRespawns() not being called
  2. depletedAtTick not being set on depletion
  3. respawnTime set to 0 or invalid value
Debug:

Resources respawning too fast/slow

Symptoms: Respawn timing doesn’t match manifest respawnTime. Cause: Tick duration mismatch or incorrect respawn time calculation. Fix: Verify TICK_DURATION_MS = 600 and respawn time is in milliseconds:

Rune essence rocks depleting

Symptoms: Rune essence rocks deplete when they shouldn’t. Cause: depleteChance not set to 0 in manifest, or depletion logic not reading from manifest. Fix: Verify manifest has depleteChance: 0 and gathering handler reads from manifest:

Code Examples

Basic Resource Depletion

Tick-Based Respawn Processing

Registering Respawn Tick Handler


  • TickSystem (packages/server/src/systems/TickSystem.ts) - Manages game tick loop
  • ResourceEntity (packages/shared/src/entities/world/ResourceEntity.ts) - Resource entity implementation
  • GatheringSystem (packages/server/src/systems/ServerNetwork/handlers/resources.ts) - Handles gathering actions
  • TreeGLBVisualStrategy (packages/shared/src/entities/world/visuals/TreeGLBVisualStrategy.ts) - Tree visual feedback