Skip to main content

PLAYER_DIED Event Migration Guide

Deprecation Date: March 26, 2026
Removal Date: TBD (next major version)
Related PR: #1094

Overview

The PLAYER_DIED event has been deprecated in favor of PLAYER_SET_DEAD. This migration guide explains why the change was made, how to update your code, and what to watch out for.

Why the Change?

Problem with PLAYER_DIED

The old PLAYER_DIED event was emitted multiple times during the death flow:
  1. First emission: PlayerSystem.handleDeath() when health reaches 0
  2. Second emission: PlayerDeathSystem.postDeathCleanup() after death processing
This caused several issues:
  • Subscribers received duplicate events
  • Timing was unpredictable (before or after death processing?)
  • Race conditions when multiple systems reacted to the same death
  • Difficult to reason about event ordering

Solution: PLAYER_SET_DEAD

The new PLAYER_SET_DEAD event is emitted exactly once, after all death processing completes:
  • Emitted in PlayerDeathSystem.postDeathCleanup()
  • Fires after inventory/equipment cleared
  • Fires after gravestone created
  • Fires after death lock created
  • Fires after player state set to DYING
Guarantee: When PLAYER_SET_DEAD fires, the player is fully dead and all death processing is complete.

Migration Steps

Step 1: Find All Usages

Search your codebase for PLAYER_DIED:

Step 2: Update Event Listeners

Replace PLAYER_DIED with PLAYER_SET_DEAD: Before:
After:
Payload Compatibility: Both events have the same payload structure:

Step 3: Update Event Emissions (if any)

You should NOT be emitting PLAYER_DIED directly. This event is internal to the death system. If you find code emitting PLAYER_DIED:
  1. Remove the emission
  2. Let PlayerSystem.handleDeath() handle it
  3. Subscribe to PLAYER_SET_DEAD instead
Anti-pattern (remove this):
Correct pattern:

Step 4: Test Your Changes

After migration, verify:
  1. Death events fire correctly:
    • Kill a player
    • Check that PLAYER_SET_DEAD fires exactly once
    • Check that your subscriber receives the event
  2. No duplicate handling:
    • Ensure your code doesn’t run twice for the same death
    • Check logs for duplicate messages
  3. Timing is correct:
    • Verify death processing completes before your subscriber runs
    • Check that gravestone exists when your code runs
    • Check that player is in DYING state when your code runs

Event Timing Comparison

Old Flow (PLAYER_DIED)

Problem: Subscribers run twice, and first run happens before death processing completes.

New Flow (PLAYER_SET_DEAD)

Guarantee: Subscribers run exactly once, after all death processing completes.

Common Migration Patterns

Pattern 1: Death Logging

Before:
After:

Pattern 2: Death Statistics

Before:
After:
Note: No logic change needed, just event name.

Pattern 3: Death Notifications

Before:
After:
Note: Player entity still exists when event fires (state is DYING, not removed).

Pattern 4: Achievement Tracking

Before:
After:
Note: killedBy is now sanitized (XSS protection), but still usable for logic.

Pattern 5: Conditional Logic Based on Death State

Before (fragile):
After (reliable):

Breaking Changes

Event Payload

No breaking changes - payload structure is identical:

Event Timing

Breaking change - event fires at different time:
  • PLAYER_DIED: Fired before death processing (unreliable)
  • PLAYER_SET_DEAD: Fired after death processing (reliable)
Impact: If your code assumed death processing hadn’t completed yet, it will break. Update your code to assume death processing is complete.

Event Frequency

Breaking change - event fires once instead of twice:
  • PLAYER_DIED: Fired 2 times per death
  • PLAYER_SET_DEAD: Fired 1 time per death
Impact: If your code relied on duplicate events (e.g., incrementing a counter twice), it will break. Update your code to handle single emission.

Deprecation Timeline

Phase 1: Deprecation (March 26, 2026)

  • PLAYER_DIED marked @deprecated in JSDoc
  • PLAYER_SET_DEAD is the recommended event
  • Both events work (backward compatibility)
Action Required: Migrate to PLAYER_SET_DEAD at your convenience.

Phase 2: Removal (TBD - next major version)

  • PLAYER_DIED event removed entirely
  • Code using PLAYER_DIED will break
  • No backward compatibility
Action Required: Complete migration before next major version.

Testing Your Migration

Unit Tests

Update test assertions: Before:
After:

Integration Tests

Verify death flow end-to-end:

Troubleshooting

Issue: Event not firing

Symptoms: PLAYER_SET_DEAD never fires when player dies. Diagnosis:
  1. Check that player health actually reaches 0
  2. Check server logs for death processing errors
  3. Verify PlayerDeathSystem is registered in world
Solution: Update to latest version (March 26, 2026+). Ensure PlayerDeathSystem is in your world’s system list.

Issue: Event fires multiple times

Symptoms: PLAYER_SET_DEAD fires more than once for a single death. Diagnosis:
  1. Check for duplicate PlayerDeathSystem instances
  2. Check for manual PLAYER_SET_DEAD emissions (anti-pattern)
Solution: Remove duplicate system registrations. Never emit PLAYER_SET_DEAD manually.

Issue: Gravestone doesn’t exist when event fires

Symptoms: PLAYER_SET_DEAD fires but gravestone entity is undefined. Diagnosis:
  1. Check server logs for gravestone creation errors
  2. Verify position is valid (not NaN/Infinity)
  3. Check entity manager for gravestone entity
Solution: Update to latest version. Position validation was added in PR #1094.

FAQ

Q: Can I use both PLAYER_DIED and PLAYER_SET_DEAD during migration?

A: Yes, both events work during the deprecation phase. However, you should migrate to PLAYER_SET_DEAD as soon as possible to avoid breaking changes in the next major version.

Q: What’s the difference between ENTITY_DEATH and PLAYER_SET_DEAD?

A:
  • ENTITY_DEATH is a generic event for any entity death (players, mobs, NPCs)
  • PLAYER_SET_DEAD is player-specific and fires after death processing completes
  • Use PLAYER_SET_DEAD for player-specific logic (respawn, gravestones, etc.)
  • Use ENTITY_DEATH for generic death logic (kill tracking, loot drops, etc.)

Q: Does PLAYER_SET_DEAD fire for mob deaths?

A: No, PLAYER_SET_DEAD is player-only. For mob deaths, use ENTITY_DEATH or mob-specific events.

Q: What if I need to run code BEFORE death processing?

A: Subscribe to ENTITY_DEATH instead. This fires immediately when health reaches 0, before death processing starts. Example:

Q: Can I still access player inventory when PLAYER_SET_DEAD fires?

A: No, inventory and equipment are cleared before PLAYER_SET_DEAD fires. If you need pre-death inventory, subscribe to ENTITY_DEATH instead. Example:

Q: What about ElizaOS agents?

A: The plugin-hyperscape package has been updated to use PLAYER_SET_DEAD. If you’re using a custom ElizaOS plugin, update your event listeners. File: packages/plugin-hyperscape/src/types.ts Before:
After:

Automated Migration

Search and Replace

Use your editor’s search-and-replace to migrate: Find: PLAYER_DIED
Replace: PLAYER_SET_DEAD
Regex (for event listener patterns):
Regex (for event emission patterns - should find none):

Codemod Script

For large codebases, use a codemod:
Run with:

Rollback Plan

If you need to rollback during migration:

Option 1: Listen to Both Events

Option 2: Feature Flag

Support

If you encounter issues during migration:
  1. Check the logs: Look for death processing errors
  2. Review the PR: See PR #1094 for implementation details
  3. Read the docs: See death-system-architecture.md for complete system documentation
  4. Ask for help: Open an issue on GitHub with your migration question

References

  • PR #1094: Player death system overhaul
  • DeathUtils.ts: Pure utility functions
  • PlayerDeathSystem.ts: Main death orchestration
  • death-system-architecture.md: Complete system documentation
  • CLAUDE.md: Recent changes section