Skip to main content

Death System API Reference

Complete API documentation for the player death system, including utilities, types, and event handling.

Overview

The death system was completely rewritten in March 2026 (PR #1094) to fix SQLite deadlock issues, prevent equipment duplication, and implement OSRS-style “keep 3 most valuable items” mechanics for safe zone deaths.

Core Modules

DeathUtils (packages/shared/src/systems/shared/combat/DeathUtils.ts)

Pure utility functions for the player death pipeline. All functions are stateless and side-effect-free.

Constants

GRAVESTONE_ID_PREFIX
Prefix for gravestone entity IDs. Used in ID generation and filtering to distinguish gravestones from player entities. Usage:
ITEMS_KEPT_ON_DEATH
OSRS-style constant for number of most valuable items kept on death in safe zones. Reference: OSRS Wiki - Items Kept on Death
POSITION_VALIDATION
Position validation constants for world bounds checking.

Functions

sanitizeKilledBy()
Sanitize killedBy string to prevent injection attacks. Security Features:
  • Normalizes Unicode to prevent homograph attacks (Cyrillic ‘а’ vs Latin ‘a’)
  • Removes zero-width characters and BiDi overrides that could manipulate display
  • Removes control characters and dangerous HTML characters
  • Limits length to prevent buffer overflow attacks
  • Defaults to “unknown” for invalid inputs
Parameters:
  • killedBy - Raw killer identifier (string, entity ID, or unknown type)
Returns: Sanitized string (max 64 characters) or “unknown” Example:
getItemValue()
Get the value of an item from manifest data. Parameters:
  • itemId - Item identifier string
Returns: Item value from manifest, or 0 for unknown items (they sort to bottom and get dropped first) Example:
splitItemsForSafeDeath()
Split items into “kept” and “dropped” lists for safe zone deaths (OSRS-style). Keeps the N most valuable individual items. For stacked items (quantity > 1), each unit counts as one item but only the top N units across all stacks are kept. Algorithm: O(n log n) on unique items — does NOT expand stacks into individual entries, avoiding memory explosion for large quantities (e.g. 10,000 arrows). Parameters:
  • allItems - Combined inventory + equipment items
  • keepCount - Number of items to keep (typically ITEMS_KEPT_ON_DEATH = 3)
Returns: Object with kept (items retained by player) and dropped (items for gravestone) Example:
Stack Handling:
  • Stacks are NOT expanded into individual entries (prevents OOM)
  • Greedy quantity assignment: keeps top N units across all stacks
  • Deterministic tiebreaking: original index used when values are equal
validatePosition()
Validate and clamp a position to world bounds. Parameters:
  • position - Position to validate
Returns: Validated and clamped position, or null if completely invalid (NaN, Infinity) Example:
isPositionInBounds()
Check if position is within world bounds without clamping. Parameters:
  • position - Position to check
Returns: true if within bounds, false otherwise Example:
isValidPositionNumber()
Check if a number is valid for position use (finite, not NaN). Parameters:
  • n - Number to validate
Returns: true if finite, false for NaN/Infinity Example:

DeathTypes (packages/shared/src/systems/shared/combat/DeathTypes.ts)

Shared type definitions for the player death pipeline. Extracted from PlayerDeathSystem to reduce file size and allow reuse across death-related modules.

Interfaces

PlayerSystemLike
Duck-typed interface for PlayerSystem.
DatabaseSystemLike
Duck-typed interface for DatabaseSystem with transaction support.
EquipmentSystemLike
Duck-typed interface for EquipmentSystem.
Key Method: clearEquipmentAndReturn() - Atomic read-and-clear operation that prevents item duplication if server crashes between read and clear.
PlayerEntityLike
Duck-typed interface for player entities in the death pipeline.
DeathLocationDataWithHeadstone
Extended death location data with headstone tracking.

Events

Deprecated Events

PLAYER_DIED

Status: DEPRECATED (as of March 26, 2026) Replacement: Use PLAYER_SET_DEAD for client death UI, or ENTITY_DEATH for server-side death processing. Migration:

Current Events

ENTITY_DEATH

Unified death event for all entity types (players, mobs, NPCs). Payload:
Usage:

PLAYER_SET_DEAD

Client-side death state event for UI updates. Payload:
Usage:

CORPSE_EMPTY

Fired when a gravestone is fully looted. Payload:
Usage:

Death Lock System

Schema

Death locks prevent item duplication during the death-to-respawn window. Database Schema (death_locks table):
DeathItemData:

Crash Recovery

If the server crashes between death transaction commit and post-transaction DB persist:
  1. On Restart: DeathStateManager.recoverUnrecoveredDeaths() finds active death locks
  2. On Reconnect: onPlayerReconnect() blocks inventory load from DB (prevents stale items)
  3. Kept Items: Restored from keptItems field in death lock if in-memory map is lost

Two-Phase Persist Pattern

The death system uses a two-phase pattern to avoid SQLite nested transaction deadlocks:

Phase 1: Transaction (Atomic)

Inside executeInTransaction():
  1. Clear inventory in-memory (skipPersist=true)
  2. Clear equipment in-memory (via clearEquipmentAndReturn() with tx parameter)
  3. Create death lock with dropped items and kept items
  4. Transaction commits

Phase 2: Persist (After Transaction)

After transaction completes:
  1. Persist equipment clear to DB (clearEquipmentImmediate())
  2. Persist inventory clear to DB (clearInventoryImmediate(skipPersist=false))
  3. If persist fails, add to retry queue
Retry Queue:
  • Bounded to 100 entries (prevents unbounded growth)
  • Single retry attempt per failure
  • Drained once per tick in processPendingRespawns()
  • Emits AUDIT_LOG event if retry also fails

OSRS Keep-3 System

How It Works

In safe zones (non-wilderness), players keep their 3 most valuable items on death:
  1. Value Calculation: Items sorted by manifest value (descending)
  2. Stack Handling: Each unit in a stack counts as one item
  3. Greedy Assignment: Top N units across all stacks are kept
  4. Deterministic Tiebreaking: Original index used when values are equal

Example

Wilderness Deaths

In wilderness zones, ALL items are dropped (no keep-3 protection).

Gravestone System

Privacy Protection

Gravestone loot items are hidden from network broadcast to prevent information leakage: Network Data (broadcast to all clients):
Full Loot Data (sent only to interacting player):

Gravestone Lifecycle

  1. Creation: SafeAreaDeathHandler.spawnGravestone()
  2. Interaction: Player clicks gravestone → server sends corpseLoot packet
  3. Looting: HeadstoneEntity.removeItem() → updates lootItemCount
  4. Empty: When lootItemCount reaches 0, emits CORPSE_EMPTY event
  5. Destruction: PlayerDeathSystem.handleCorpseEmpty() destroys entity via EntityManager

TTL Fallback

If CORPSE_EMPTY event is lost, SafeAreaDeathHandler.processTick() still destroys the gravestone when its tick-based TTL expires (fallback cleanup).

Error Handling

Death Processing Guard

Prevents respawn race while death transaction is in progress:

Duel Respawn Guard

Blocks respawn during active duels to prevent escape exploit:

Persist Retry Queue

Handles transient DB failures during post-transaction persist:

Audit Events

The death system emits AUDIT_LOG events for operational monitoring:

DEATH_LOCK_RECONNECT_BLOCK

Player reconnected with active death lock (potential crash-window scenario).

DEATH_PERSIST_DESYNC

Post-transaction persist failed (equipment or inventory).

DEATH_PERSIST_RETRY_QUEUE_FULL

Retry queue reached max capacity (DB may be persistently unavailable).

Testing

Unit Tests

DeathUtils (packages/shared/src/systems/shared/combat/__tests__/DeathUtils.test.ts):
  • 51 tests covering sanitization, stack splitting, position validation
  • Edge cases: Unicode attacks, stack explosion (10k arrows), boundary values
PlayerDeathFlow (packages/shared/src/systems/shared/combat/__tests__/PlayerDeathFlow.test.ts):
  • 10 tests covering death-to-respawn flow, guards, retry queue
  • Duel guard, processing guard, tick-based respawn, persist retry, event migration

Integration Tests

Use Playwright with real Hyperscape instances (per project testing philosophy):
  • Full death → respawn → items-returned flow
  • Gravestone interaction and looting
  • Crash recovery scenarios
  • Duel escape prevention

Performance Considerations

Stack Handling

splitItemsForSafeDeath() uses O(n log n) on unique item slots, NOT on total quantity:

Gravestone Network Sync

lootItems are included in network data but only sent when dirty:
  • markNetworkDirty() called after removeItem()/restoreItem()
  • Gravestones have few items and rarely change
  • Bandwidth impact is minimal

See Also