- Add status-driven death-save model: - status: conscious | dying | stable | dead - deathSaveSuccesses/deathSaveFailures as display counters - remove old death-save fields as source of truth - Add death-save actions: - success, fail, nat1, nat20 - stabilize participant - revive dead participant to 0 HP, stable, unconscious - Apply 5e HP transition rules: - characters and NPCs at 0 HP become dying - stable/dying participants gain unconscious condition - healing clears death saves and returns conscious - massive damage kills - non-NPC monsters at 0 HP become dead and inactive - Split NPCs from monsters with type="npc": - NPCs use death saves like characters - NPCs remain DM-controlled/monster-colored in UI - new NPCs no longer persist isNpc flag - Keep dead PCs/NPCs in encounter and initiative until DM removes or deactivates - Allow DM active/inactive toggle for dead participants - Hide inactive participants from player display - Improve DM and player death-state UI: - show Dying/Dead/Unconscious states consistently - add revive button for dead participants - distinguish dead visual from inactive visual in DM view - Fix initiative drag/reorder behavior: - downward drag now changes order instead of no-op - paused combat can reorder across current turn pointer - turnOrderIds stay synced to participants order - Persist campaign collapse state in localStorage - Update death-save docs and encounter builder docs - Add/expand tests for: - death saves and HP transitions - dead/active/inactive behavior - NPC death-save behavior - player display visibility - drag reorder semantics - logging expectations Tests: - npm run test:all - app: 94 passed - shared: 158 passed, 5 skipped - server: 32 passed
5.1 KiB
Death Saves (D&D 5e)
Concept and rule structure for the death-save state machine. Reference for how combat tracking should behave at 0 HP. This is the 5e death-save loop, not a code spec.
Scope: tracker tracks, DM decides
This tracker is not a virtual tabletop. It does not roll dice or interpret the table. The DM rolls at the table, decides what happened, and feeds the result into the tracker through the UI. The tracker records the result and applies the automatic pieces.
- DM does: rolls the d20, determines crits vs normal hits, declares damage amounts, decides when to stabilize or heal.
- Tracker does: increments success/failure counters on DM input, resolves the terminal states (3 successes → stable, 3 failures → dead), resets counters on heal or stabilize, records HP changes.
The transitions below describe the rules the tracker must honor once the DM hands it a result. The DM is the source of truth for what happened; the tracker is the source of truth for what state that produces.
State
A character or NPC at 0 HP enters a death-save loop. Track per death-save participant:
deathSaveSuccesses: 0 | 1 | 2 | 3
deathSaveFailures: 0 | 1 | 2 | 3
status: "conscious" | "dying" | "stable" | "dead"
hp: number
Transitions
Drop to 0 HP (damage, character/NPC):
- If damage >= currentHp + maxHp → outright dead (massive damage). Equivalently: after reducing currentHp to 0, overflow damage >= maxHp.
- Otherwise →
status = "dying", counters reset to 0.
Drop to 0 HP (monster):
- Non-NPC monsters do not use death saves.
status = "dead", counters reset,isActive = false.
Death save (d20 roll on a dying character/NPC turn):
if roll === 1: failures += 2 // natural 1 = two failures
else if roll === 20: hp = 1; successes = 0; failures = 0; status = "conscious" // nat 20 = 1 HP, conscious
else if roll >= 10: successes += 1
else: failures += 1
Then resolve immediately on the click/action that reaches 3:
if failures >= 3: status = "dead"; successes = 0; failures = 0
if successes >= 3: status = "stable"; successes = 0; failures = 0
The UI must transition immediately. A third failure shows Dead state, not three active failure boxes. A third success shows Stable state, not three active success boxes.
Damage while dying (at 0 HP):
failures += isCriticalHit ? 2 : 1
if failures >= 3: status = "dead"
Attacks from within 5 ft against an unconscious creature are auto-crits, so melee vs a dying character/NPC adds 2 failures.
Healing while dying or stable:
Any +HP received while dying or stable makes the character/NPC conscious.
This usually comes from magic, or from the stable-state time-delayed recovery
(1 HP after 1d4 hours, if the DM chooses to track it here).
hp = max(1, hp + amount)
successes = 0
failures = 0
status = "conscious"
Any HP regained from dying or stable clears all counters and ends the
death-save loop. This does not apply to dead; dead requires revive flow,
revival magic, or DM state override, not normal healing.
Stabilize (manual action, not a save):
hp = 0
successes = 0
failures = 0
status = "stable"
Stable state
- Stays at 0 HP, unconscious, no longer makes death saves.
- 3 successes = stable (unconscious), NOT conscious. NOT 1 HP. 1 HP only comes from a natural 20 or healing.
- Taking any damage ends stable → back to
dying(counters reset, then damage applies its failure per "damage while dying").
Tool-specific initiative behavior
Death is a combat state, not an automatic removal action.
When a character or NPC becomes dead:
- Do not remove them from initiative.
- Do not automatically deactivate them.
- Do not remove them from the encounter participants list.
- Leave them in turn order. Other table/tool events may still matter on that initiative count.
- DM may manually mark them inactive or remove them.
When a non-NPC monster becomes dead:
- Set
isActive = falseautomatically. - Keep it in the encounter participants list until DM removes it.
- Player display hides it because inactive participants are hidden.
Invariants
status === "dying"⟺ hp === 0 AND no terminal flag.status === "stable"⟹ hp === 0, unconscious, counters 0.status === "dead"⟹ hp === 0, no further saves, counters 0.status === "dead"⟹ participant remains in initiative and participants until DM removes them.status === "dead"does not itself mean inactive; monster death sets inactive as a separate rule.- Normal +HP applies to
dyingorstable, notdead; dead requires revive, revival, or DM override. - Successes and failures tracked independently. 2 fail + 3 success = stable. Counters do not need to be consecutive.
- A roll of 10+ is a success. 9 or lower is a failure.