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
ITEMS_KEPT_ON_DEATH
POSITION_VALIDATION
Functions
sanitizeKilledBy()
- 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
killedBy- Raw killer identifier (string, entity ID, or unknown type)
getItemValue()
itemId- Item identifier string
splitItemsForSafeDeath()
allItems- Combined inventory + equipment itemskeepCount- Number of items to keep (typicallyITEMS_KEPT_ON_DEATH = 3)
kept (items retained by player) and dropped (items for gravestone)
Example:
- 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()
position- Position to validate
null if completely invalid (NaN, Infinity)
Example:
isPositionInBounds()
position- Position to check
true if within bounds, false otherwise
Example:
isValidPositionNumber()
n- Number to validate
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.
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:
PLAYER_SET_DEAD
Client-side death state event for UI updates.
Payload:
CORPSE_EMPTY
Fired when a gravestone is fully looted.
Payload:
Death Lock System
Schema
Death locks prevent item duplication during the death-to-respawn window. Database Schema (death_locks table):
Crash Recovery
If the server crashes between death transaction commit and post-transaction DB persist:- On Restart:
DeathStateManager.recoverUnrecoveredDeaths()finds active death locks - On Reconnect:
onPlayerReconnect()blocks inventory load from DB (prevents stale items) - Kept Items: Restored from
keptItemsfield 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)
InsideexecuteInTransaction():
- Clear inventory in-memory (
skipPersist=true) - Clear equipment in-memory (via
clearEquipmentAndReturn()withtxparameter) - Create death lock with dropped items and kept items
- Transaction commits
Phase 2: Persist (After Transaction)
After transaction completes:- Persist equipment clear to DB (
clearEquipmentImmediate()) - Persist inventory clear to DB (
clearInventoryImmediate(skipPersist=false)) - If persist fails, add to retry queue
- Bounded to 100 entries (prevents unbounded growth)
- Single retry attempt per failure
- Drained once per tick in
processPendingRespawns() - Emits
AUDIT_LOGevent 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:- Value Calculation: Items sorted by manifest value (descending)
- Stack Handling: Each unit in a stack counts as one item
- Greedy Assignment: Top N units across all stacks are kept
- 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):Gravestone Lifecycle
- Creation:
SafeAreaDeathHandler.spawnGravestone() - Interaction: Player clicks gravestone → server sends
corpseLootpacket - Looting:
HeadstoneEntity.removeItem()→ updateslootItemCount - Empty: When
lootItemCountreaches 0, emitsCORPSE_EMPTYevent - Destruction:
PlayerDeathSystem.handleCorpseEmpty()destroys entity viaEntityManager
TTL Fallback
IfCORPSE_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 emitsAUDIT_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
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 afterremoveItem()/restoreItem()- Gravestones have few items and rarely change
- Bandwidth impact is minimal
See Also
- Player Death System - Main death orchestration
- Safe Area Death Handler - Gravestone spawning and TTL
- Death State Manager - Death lock persistence
- Headstone Entity - Gravestone entity implementation