Skip to content

How to Build: Death & Level Reset

Death is inevitable in an auto-battler — especially when you're pushing into content that's slightly too hard. The question is: how punishing should it feel? In Endless Idle, the answer is "not very." Player death is a soft reset that preserves everything meaningful while giving you a fresh map to explore. Enemy death is the engine that drives progression forward.

Player death: a fresh start, not a punishment

When the player's HP hits zero, three things happen in order:

  1. Progress is saved. Experience, gold, inventory, skill levels, checkpoint unlocks — all preserved. The player never loses progress from dying.
  2. The level regenerates. A new random seed is rolled and the entire level is rebuilt from scratch using the WFC pipeline. Same level number, completely new layout.
  3. The player restarts. Health resets to full, combat state clears, position resets to the start of the level.
typescript
function onPlayerDeath(world: World): void

The key design decision here is that the player entity is never tagged with IsDead. Death is detected by checking Health.current <= 0 and handled immediately — save, regenerate, reset. This avoids a whole class of edge cases where other systems might try to interact with a "dead" player entity during the same tick.

Notice how the auto-save fires before level regeneration. If the player closes the browser mid-regeneration, they'll still have their progress when they come back.

Enemy death: gold and a timer

When an enemy dies, the handler is simpler but just as important for game feel.

typescript
function onEnemyDeath(enemy: Entity, world: World): void

The handler does three things:

  1. Grants gold to the player, reading the pre-rolled amount from EnemyData.goldDrop.
  2. Clears combat state so the enemy stops being considered in aggro and targeting queries.
  3. Starts the respawn timer by setting RespawnTimer.ticksRemaining to the enemy's respawnTicks value.

The respawn system (covered in the enemy spawning guide) handles the countdown and resurrection.

Boss death: level complete

When the dying enemy has the IsLevelBoss tag, an additional step fires after the gold grant.

typescript
function completeLevel(world: World): void

This marks the current level as complete, unlocks the next level checkpoint in the player's progression data, and triggers whatever transition the game needs — whether that's an immediate advance to the next level or a UI prompt celebrating the victory.

The boss doesn't respawn. Once it's dead, the level is done. This gives the level a clear arc: fight through clusters, optionally tackle camps, reach the boss, win.

The save-before-reset pattern

One subtle but important detail: saveGame runs before startLevel. This matters because startLevel destroys all enemy entities, regenerates terrain, and repositions the player. If the save happened after regeneration, we'd be saving the new level state instead of the completed progress.

The save captures:

  • Current gold balance (including the gold from the fight that killed the player)
  • All skill levels and experience
  • Inventory contents
  • Checkpoint unlocks
  • Consumable settings

Where death detection lives

Death checking happens inside the combat tick system, after all attacks for the current tick have resolved. This ensures that a player and enemy can kill each other on the same tick — both deaths are detected in the same sweep. The player death handler fires after enemy deaths are processed, so the player still gets gold and experience from enemies they killed on the same tick they died.

Relevant files

src/core/systems/handle-death.ts   — Death detection and response handlers

We now have a complete life-and-death cycle. Enemies die, drop gold, and come back. The player dies, keeps everything, and gets a new world to explore. Next, let's look at exactly how those gold drops work.