You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

939 lines
43 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PF2e Hit Points, Healing &amp; Dying</title>
<link rel="stylesheet" href="global.css">
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
<style>
:root {
--c-alive: #193a14;
--c-alive-hi: #5ec454;
--c-alive-border: #3a7c32;
--c-dying-1: #3e2e06;
--c-dying-1-hi: #f0c040;
--c-dying-1-border: #a07a10;
--c-dying-2: #3e1a06;
--c-dying-2-hi: #f07830;
--c-dying-2-border: #a04010;
--c-dying-3: #300c0c;
--c-dying-3-hi: #f04040;
--c-dying-3-border: #8a1010;
--c-dead: #1a0404;
--c-dead-hi: #c03030;
--c-dead-border: #600808;
--c-unconscious: #0e2234;
--c-unconscious-hi: #50a8e8;
--c-unconscious-border: #2060a0;
}
aside.open { width: 280px; min-width: 280px; border-right-width: 1px; }
.aside-inner { width: 280px; padding: 16px 14px; }
.diagram-wrap .mermaid { min-width: 700px; }
.ccard.alive { background: var(--c-alive); border-color: var(--c-alive-border); }
.ccard.alive h3 { color: var(--c-alive-hi); }
.ccard.alive .tag { background: #0d2a0a; color: var(--c-alive-hi); }
.ccard.dying { background: var(--c-dying-2); border-color: var(--c-dying-2-border); }
.ccard.dying h3 { color: var(--c-dying-2-hi); }
.ccard.dying .tag { background: #2a1004; color: var(--c-dying-2-hi); }
.ccard.unconscious { background: var(--c-unconscious); border-color: var(--c-unconscious-border); }
.ccard.unconscious h3 { color: var(--c-unconscious-hi); }
.ccard.unconscious .tag{ background: #081422; color: var(--c-unconscious-hi); }
.ccard.wounded { background: #201808; border-color: #806010; }
.ccard.wounded h3 { color: #d0a030; }
.ccard.wounded .tag{ background: #140e04; color: #d0a030; }
.ccard.doomed { background: #200808; border-color: #801010; }
.ccard.doomed h3 { color: #d04040; }
.ccard.doomed .tag { background: #140404; color: #d04040; }
.ccard.dead { background: var(--c-dead); border-color: var(--c-dead-border); }
.ccard.dead h3 { color: var(--c-dead-hi); }
.ccard.dead .tag { background: #100404; color: var(--c-dead-hi); }
.sim-start-row { display: flex; align-items: center; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; }
.sim-chip.alive { color: var(--c-alive-hi); border-color: var(--c-alive-border); }
.sim-chip.dying-1 { color: var(--c-dying-1-hi); border-color: var(--c-dying-1-border); }
.sim-chip.dying-2 { color: var(--c-dying-2-hi); border-color: var(--c-dying-2-border); }
.sim-chip.dying-3 { color: var(--c-dying-3-hi); border-color: var(--c-dying-3-border); }
.sim-chip.unconscious { color: var(--c-unconscious-hi); border-color: var(--c-unconscious-border); }
.sim-cond-row { display: flex; align-items: center; gap: 16px; margin-bottom: 20px; flex-wrap: wrap; }
.sim-cond-item { display: flex; align-items: center; gap: 6px; }
.sim-cond-label { font-size: 0.78rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em; }
.sim-cond-val { font-size: 1rem; font-weight: bold; min-width: 24px; text-align: center; color: var(--text); }
.sim-cond-val.wounded-hi { color: #d0a030; }
.sim-cond-val.doomed-hi { color: #d04040; }
.sim-cond-val.dying-hi-1 { color: var(--c-dying-1-hi); }
.sim-cond-val.dying-hi-2 { color: var(--c-dying-2-hi); }
.sim-cond-val.dying-hi-3 { color: var(--c-dying-3-hi); }
.sim-cond-btn {
background: var(--surface2); border: 1px solid var(--border);
color: var(--text-muted); cursor: pointer; font-family: inherit;
font-size: 0.85rem; padding: 2px 8px; border-radius: 3px;
transition: color 0.15s, border-color 0.15s;
}
.sim-cond-btn:hover { color: var(--text); border-color: var(--text-muted); }
.sim-box.alive { background: var(--c-alive); border-color: var(--c-alive-border); }
.sim-box.dying-1 { background: var(--c-dying-1); border-color: var(--c-dying-1-border); }
.sim-box.dying-2 { background: var(--c-dying-2); border-color: var(--c-dying-2-border); }
.sim-box.dying-3 { background: var(--c-dying-3); border-color: var(--c-dying-3-border); }
.sim-box.dead { background: var(--c-dead); border-color: var(--c-dead-border); }
.sim-box.unconscious { background: var(--c-unconscious); border-color: var(--c-unconscious-border); }
.sim-box.alive .sim-state-name { color: var(--c-alive-hi); }
.sim-box.dying-1 .sim-state-name { color: var(--c-dying-1-hi); }
.sim-box.dying-2 .sim-state-name { color: var(--c-dying-2-hi); }
.sim-box.dying-3 .sim-state-name { color: var(--c-dying-3-hi); }
.sim-box.dead .sim-state-name { color: var(--c-dead-hi); }
.sim-box.unconscious .sim-state-name { color: var(--c-unconscious-hi); }
.sim-state-badges { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.sim-badge {
display: inline-block; padding: 3px 10px; border-radius: 4px;
font-size: 0.76rem; font-weight: bold; border: 1px solid; background: rgba(0,0,0,0.3);
}
.sim-box.dying-1 .sim-badge { color: var(--c-dying-1-hi); border-color: var(--c-dying-1-border); }
.sim-box.dying-2 .sim-badge { color: var(--c-dying-2-hi); border-color: var(--c-dying-2-border); }
.sim-box.dying-3 .sim-badge { color: var(--c-dying-3-hi); border-color: var(--c-dying-3-border); }
.sim-box.unconscious .sim-badge { color: var(--c-unconscious-hi); border-color: var(--c-unconscious-border); }
.sim-action {
padding: 9px 16px; background: var(--surface2); border: 1px solid var(--border);
color: var(--text); border-radius: 5px; cursor: pointer; font-family: inherit;
font-size: 0.83rem; text-align: left; max-width: 340px;
transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.sim-outcomes-list { display: flex; flex-direction: column; gap: 6px; }
.sim-outcome {
padding: 8px 16px; background: var(--surface); border: 1px solid var(--border);
color: var(--text); border-radius: 4px; cursor: pointer; font-family: inherit;
font-size: 0.82rem; text-align: left; transition: border-color 0.15s, color 0.15s;
}
.sim-result-box.alive { background: var(--c-alive); border-color: var(--c-alive-border); }
.sim-result-box.dying-1 { background: var(--c-dying-1); border-color: var(--c-dying-1-border); }
.sim-result-box.dying-2 { background: var(--c-dying-2); border-color: var(--c-dying-2-border); }
.sim-result-box.dying-3 { background: var(--c-dying-3); border-color: var(--c-dying-3-border); }
.sim-result-box.dead { background: var(--c-dead); border-color: var(--c-dead-border); }
.sim-result-box.unconscious { background: var(--c-unconscious); border-color: var(--c-unconscious-border); }
.sim-result-box.alive .sim-result-new-state { color: var(--c-alive-hi); }
.sim-result-box.dying-1 .sim-result-new-state { color: var(--c-dying-1-hi); }
.sim-result-box.dying-2 .sim-result-new-state { color: var(--c-dying-2-hi); }
.sim-result-box.dying-3 .sim-result-new-state { color: var(--c-dying-3-hi); }
.sim-result-box.dead .sim-result-new-state { color: var(--c-dead-hi); }
.sim-result-box.unconscious .sim-result-new-state { color: var(--c-unconscious-hi); }
.sim-hist-state.alive { color: var(--c-alive-hi); }
.sim-hist-state.dying-1 { color: var(--c-dying-1-hi); }
.sim-hist-state.dying-2 { color: var(--c-dying-2-hi); }
.sim-hist-state.dying-3 { color: var(--c-dying-3-hi); }
.sim-hist-state.dead { color: var(--c-dead-hi); }
.sim-hist-state.unconscious { color: var(--c-unconscious-hi); }
</style>
</head>
<body>
<header>
<h1><a href="index.html">PF2e Hit Points, Healing &amp; Dying</a></h1>
<p>Death &amp; Recovery Reference &mdash; Player Core</p>
<button class="sidebar-toggle-btn" onclick="toggleSidebar()" title="Toggle Conditions Reference">
&#9776; Conditions
</button>
</header>
<div class="layout">
<!-- ── Sidebar ── -->
<aside id="sidebar">
<div class="aside-inner">
<div class="aside-header">
<h2>Conditions</h2>
<button class="aside-close" onclick="closeSidebar()" title="Close">&times;</button>
</div>
<div class="ccard alive">
<h3>Alive</h3>
<p>You have at least 1 Hit Point and are conscious. You act normally.</p>
<span class="tag">Normal play</span>
</div>
<div class="ccard dying">
<h3>Dying X</h3>
<p>You are bleeding out and unconscious. At the start of each turn attempt a flat check (DC 10 + dying value). If dying reaches 4&mdash;or less with Doomed&mdash;you die.</p>
<span class="tag">Recovery DC = 10 + dying value</span>
</div>
<div class="ccard unconscious">
<h3>Unconscious</h3>
<p>You can&rsquo;t act. &minus;4 status penalty to AC, Perception, and Reflex saves. You have the Blinded and Off-Guard conditions. You fall prone and drop held items.</p>
<span class="tag">&minus;4 AC &middot; &minus;4 Perception &middot; &minus;4 Reflex</span>
</div>
<div class="ccard wounded">
<h3>Wounded X</h3>
<p>Gained whenever you lose the dying condition. Its value is added to every dying value increase. Cleared by a successful Treat Wounds or full HP restored with 10 min rest.</p>
<span class="tag">+X to all dying value increases</span>
</div>
<div class="ccard doomed">
<h3>Doomed X</h3>
<p>You die at dying (4&minus;X) instead of dying 4. Doomed 1 means dying 3 kills you. If max dying is ever reduced to 0 you die instantly. Decreases by 1 per full night&rsquo;s rest.</p>
<span class="tag">Die at dying (4 &minus; X)</span>
</div>
<div class="ccard dead">
<h3>Dead</h3>
<p>You cannot act or be targeted by most spells. For all other purposes you are an object. Resurrection magic (<em>raise dead</em> spell or <em>resurrect</em> ritual) may restore life.</p>
<span class="tag">Terminal &mdash; requires special magic</span>
</div>
</div>
</aside>
<div class="sidebar-overlay" id="sidebar-overlay" onclick="closeSidebar()"></div>
<!-- ── Main ── -->
<main>
<div class="tab-bar">
<button class="tab-btn active" onclick="showTab('simulator', this)">Situation Simulator</button>
<button class="tab-btn" onclick="showTab('flowchart', this)">Flow Chart</button>
</div>
<!-- Flow Chart Tab -->
<div id="flowchart" class="tab-panel">
<h2>Dying Flow Chart</h2>
<p class="subtitle">State transitions for player characters &mdash; <strong>W</strong> = Wounded value added to every dying increase</p>
<div class="legend">
<div class="legend-item"><div class="legend-dot" style="background:var(--c-alive);border-color:var(--c-alive-hi)"></div> Alive</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--c-dying-2);border-color:var(--c-dying-2-hi)"></div> Dying X</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--c-unconscious);border-color:var(--c-unconscious-hi)"></div> Unconscious</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--c-dead);border-color:var(--c-dead-hi)"></div> Dead</div>
</div>
<div class="diagram-wrap">
<div class="mermaid" id="dying-diagram">
%%{init: {'theme':'dark','themeVariables':{'background':'#161210','primaryColor':'#193a14','primaryTextColor':'#e8d5b0','primaryBorderColor':'#3a7c32','lineColor':'#9a7e56','secondaryColor':'#3e2e06','tertiaryColor':'#1a2840','edgeLabelBackground':'#211a13','clusterBkg':'#211a13'}}}%%
flowchart TD
classDef alive fill:#193a14,stroke:#3a7c32,color:#b8e8b0
classDef dying fill:#3e1a06,stroke:#a04010,color:#f0a868
classDef dead fill:#1a0404,stroke:#600808,color:#c06060
classDef unc fill:#0e2234,stroke:#2060a0,color:#90c8e8
ALIVE(["ALIVE · ≥1 HP"]):::alive
DX(["DYING X · Recovery DC 10+X"]):::dying
UNC(["UNCONSCIOUS · 0 HP"]):::unc
DEAD(["DEAD · dying ≥ threshold"]):::dead
ALIVE -->|"0 HP lethal · normal hit (+W)"| DX
ALIVE -->|"0 HP lethal · crit hit/fail (+W)"| DX
ALIVE -->|"0 HP nonlethal"| UNC
ALIVE -->|"Massive dmg or Death Effect"| DEAD
DX -->|"Crit Success · dying2 · +Wounded"| UNC
DX -->|"Success · dying1 · +Wounded"| UNC
DX -->|"Failure · dying+1+W"| DX
DX -->|"Crit Fail · dying+2+W"| DX
DX -->|"Take dmg · dying+1+W or +2+W"| DX
DX -->|"dying ≥ threshold"| DEAD
DX -->|"Healing 1+ HP · +Wounded"| ALIVE
DX -->|"Hero Points · Wounded unchanged"| UNC
UNC -->|"Healing or natural recovery"| ALIVE
UNC -->|"Take lethal dmg to 0 HP"| DX
</div>
</div>
<div class="note-box">
<h3>Key Rules</h3>
<ul>
<li><strong>Recovery Check</strong> is a flat check (no modifiers) with DC = 10 + current dying value, made at the start of each turn while dying.</li>
<li><strong>Wounded</strong> is added to <em>every</em> dying value increase &mdash; being Wounded 2 and failing a recovery check means dying increases by 3 (1+2).</li>
<li><strong>Doomed X</strong> reduces your death threshold from 4 to (4&minus;X). Doomed 1 means dying 3 kills you. Decreases by 1 per full night&rsquo;s rest.</li>
<li><strong>Heroic Recovery:</strong> Spend all Hero Points at the start of your turn or when dying would increase. You lose the dying condition and stabilize at 0 HP. Wounded does <em>not</em> increase from this.</li>
<li><strong>Nonlethal KO:</strong> Reduced to 0 HP by a nonlethal attack causes Unconscious with no dying condition. You wake naturally after 10+ minutes.</li>
<li><strong>Massive Damage:</strong> Instant death if you take damage equal to or greater than <em>double your maximum HP</em> in one blow.</li>
<li><strong>Losing dying:</strong> Each time you lose the dying condition (except via Heroic Recovery) you gain Wounded 1 or increase Wounded by 1.</li>
<li><strong>Clearing Wounded:</strong> A successful Treat Wounds action, or restored to full HP and resting 10 minutes.</li>
</ul>
</div>
</div>
<!-- Simulator Tab -->
<div id="simulator" class="tab-panel active">
<h2>Situation Simulator</h2>
<p class="subtitle">Step through dying state transitions interactively &mdash; set conditions first, then choose what happens</p>
<div class="sim-start-row">
<span class="sim-start-label">Start as:</span>
<button class="sim-chip alive" onclick="simSetState('ALIVE', 0)">Alive</button>
<button class="sim-chip dying-1" onclick="simSetState('DYING', 1)">Dying 1</button>
<button class="sim-chip dying-2" onclick="simSetState('DYING', 2)">Dying 2</button>
<button class="sim-chip dying-3" onclick="simSetState('DYING', 3)">Dying 3</button>
<button class="sim-chip unconscious" onclick="simSetState('UNCONSCIOUS', 0)">Unconscious</button>
</div>
<div class="sim-cond-row">
<span class="sim-start-label">Conditions:</span>
<div class="sim-cond-item">
<span class="sim-cond-label">Dying</span>
<button class="sim-cond-btn" onclick="adjustDying(-1)">&#x2212;</button>
<span class="sim-cond-val" id="sim-dying-val">0</span>
<button class="sim-cond-btn" onclick="adjustDying(1)">+</button>
</div>
<div class="sim-cond-item">
<span class="sim-cond-label">Wounded</span>
<button class="sim-cond-btn" onclick="adjustWounded(-1)">&#x2212;</button>
<span class="sim-cond-val" id="sim-wounded-val">0</span>
<button class="sim-cond-btn" onclick="adjustWounded(1)">+</button>
</div>
<div class="sim-cond-item">
<span class="sim-cond-label">Doomed</span>
<button class="sim-cond-btn" onclick="adjustDoomed(-1)">&#x2212;</button>
<span class="sim-cond-val" id="sim-doomed-val">0</span>
<button class="sim-cond-btn" onclick="adjustDoomed(1)">+</button>
</div>
</div>
<div id="sim-current-box" class="sim-box alive">
<div class="sim-box-label">Current State</div>
<div class="sim-state-name" id="sim-state-name">Alive</div>
<div class="sim-state-effects" id="sim-state-effects"></div>
</div>
<div id="sim-actions-section">
<div class="sim-section-head">What happens?</div>
<div class="sim-actions-list" id="sim-actions-list"></div>
</div>
<div id="sim-outcomes-section" style="display:none">
<div class="sim-outcomes-box">
<div class="sim-outcomes-head" id="sim-outcomes-head">Select the outcome:</div>
<div class="sim-outcomes-list" id="sim-outcomes-list"></div>
</div>
</div>
<div id="sim-result-container"></div>
<div class="sim-bottom-row">
<div class="sim-history-wrap">
<div class="sim-history-label">History</div>
<div class="sim-history-trail" id="sim-history-trail"></div>
</div>
<button class="sim-reset-btn" onclick="simReset()">&#8635; Reset</button>
</div>
</div>
</main>
</div>
<script>
mermaid.initialize({
startOnLoad: false,
theme: 'dark',
flowchart: { curve: 'basis', padding: 20, useMaxWidth: false },
securityLevel: 'loose',
});
document.addEventListener('DOMContentLoaded', async function() {
const diagrams = document.querySelectorAll('.mermaid');
for (let i = 0; i < diagrams.length; i++) {
const el = diagrams[i];
const id = el.id || ('mermaid-' + i);
try {
const { svg } = await mermaid.render(id + '-svg', el.textContent.trim());
el.innerHTML = svg;
} catch(e) {
console.error('Mermaid error in #' + id + ':', e);
el.innerHTML = '<pre style="color:#f07070;font-size:0.75rem;white-space:pre-wrap;padding:12px">Diagram error: ' + (e.message || e) + '</pre>';
}
}
simSetState('ALIVE', 0);
});
function showTab(name, btn) {
document.querySelectorAll('.tab-panel').forEach(function(p) { p.classList.remove('active'); });
document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('active'); });
document.getElementById(name).classList.add('active');
btn.classList.add('active');
}
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('open');
document.getElementById('sidebar-overlay').classList.toggle('active');
}
function closeSidebar() {
document.getElementById('sidebar').classList.remove('open');
document.getElementById('sidebar-overlay').classList.remove('active');
}
// ── Simulator State ───────────────────────────────────────────
var simState = 'ALIVE';
var simDying = 0;
var simWounded = 0;
var simDoomed = 0;
var simHistory = [];
var simPending = null;
function deathThreshold() { return Math.max(1, 4 - simDoomed); }
function stateCssClass(state, dying) {
if (state === 'ALIVE') return 'alive';
if (state === 'DYING') return 'dying-' + Math.max(1, Math.min(3, dying));
if (state === 'UNCONSCIOUS') return 'unconscious';
if (state === 'DEAD') return 'dead';
return 'alive';
}
function stateLabel(state, dying) {
if (state === 'ALIVE') return 'Alive';
if (state === 'DYING') return 'Dying ' + dying;
if (state === 'UNCONSCIOUS') return 'Unconscious';
if (state === 'DEAD') return 'Dead';
return 'Alive';
}
function stateEffects(state, dying) {
if (state === 'ALIVE') {
return 'You are conscious and have at least 1 Hit Point. You can act normally.';
}
if (state === 'DYING') {
var dc = 10 + dying;
var threshold = deathThreshold();
var txt = 'You are unconscious and bleeding out. Attempt a <strong>flat check</strong> (DC&nbsp;' + dc + ') at the start of each of your turns.';
txt += '<div class="sim-state-badges">';
txt += '<span class="sim-badge">Recovery DC: ' + dc + '</span>';
if (simDoomed > 0) {
txt += '<span class="sim-badge">Die at Dying ' + threshold + ' &mdash; Doomed ' + simDoomed + '</span>';
}
if (simWounded > 0) {
txt += '<span class="sim-badge">Wounded ' + simWounded + ' &mdash; all dying increases +' + simWounded + '</span>';
}
txt += '</div>';
return txt;
}
if (state === 'UNCONSCIOUS') {
var txt2 = 'You cannot act. You have the Blinded and Off-Guard conditions, and a &minus;4 status penalty to AC, Perception, and Reflex saves. You have fallen prone.';
if (simWounded > 0) {
txt2 += '<div class="sim-state-badges"><span class="sim-badge">Wounded ' + simWounded + '</span></div>';
}
return txt2;
}
if (state === 'DEAD') {
return 'You have died. You cannot act or be targeted by most spells. Resurrection magic (<em>raise dead</em> or <em>resurrect</em> ritual) may restore you to life.';
}
return '';
}
// ── Transition Helpers ────────────────────────────────────────
function gotoNewDying(base) {
var startValue = base + simWounded;
var threshold = deathThreshold();
var wNote = simWounded > 0 ? ' (' + base + '+' + simWounded + 'W)' : '';
if (startValue >= threshold) {
simState = 'DEAD'; simDying = 0;
simHistory.push({ state: 'DEAD', dying: 0, via: 'Knocked Out' });
return { msg: 'You start at Dying ' + startValue + wNote + ', which meets or exceeds the death threshold of ' + threshold + '. You die instantly.' };
}
simState = 'DYING'; simDying = startValue;
simHistory.push({ state: 'DYING', dying: startValue, via: 'Knocked Out' });
return { msg: 'You are knocked unconscious with Dying ' + startValue + wNote + '. Attempt a recovery check (flat check DC&nbsp;' + (10 + startValue) + ') at the start of each of your turns.' };
}
function recoveryImprove(amount) {
var old = simDying;
var newDying = old - amount;
if (newDying <= 0) {
simState = 'UNCONSCIOUS'; simDying = 0; simWounded += 1;
simHistory.push({ state: 'UNCONSCIOUS', dying: 0, via: 'Recovery Check' });
return { msg: 'Dying ' + old + ' &minus; ' + amount + ' = 0. You lose the dying condition and stabilize at 0 HP. You gain the Wounded condition (now Wounded&nbsp;' + simWounded + ').' };
}
simDying = newDying;
simHistory.push({ state: 'DYING', dying: newDying, via: 'Recovery Check' });
return { msg: 'Dying ' + old + ' &minus; ' + amount + ' = Dying ' + newDying + '. Recovery DC is now ' + (10 + newDying) + '.' };
}
function recoveryWorsen(base) {
var old = simDying;
var total = base + simWounded;
var newDying = old + total;
var threshold = deathThreshold();
var wNote = simWounded > 0 ? ' (+' + simWounded + 'W&nbsp;= +' + total + ' total)' : '';
if (newDying >= threshold) {
simState = 'DEAD'; simDying = 0;
simHistory.push({ state: 'DEAD', dying: 0, via: 'Recovery Check Failed' });
return { msg: 'Dying ' + old + ' + ' + total + wNote + ' = Dying ' + newDying + ', reaching the death threshold of ' + threshold + '. You die.' };
}
simDying = newDying;
simHistory.push({ state: 'DYING', dying: newDying, via: 'Recovery Check Failed' });
return { msg: 'Dying ' + old + ' + ' + total + wNote + ' = Dying ' + newDying + '. Recovery DC is now ' + (10 + newDying) + '.' };
}
function dyingWorsen(base, label) {
var old = simDying;
var total = base + simWounded;
var newDying = old + total;
var threshold = deathThreshold();
var wNote = simWounded > 0 ? ' (+' + simWounded + 'W&nbsp;= +' + total + ' total)' : '';
if (newDying >= threshold) {
simState = 'DEAD'; simDying = 0;
simHistory.push({ state: 'DEAD', dying: 0, via: label });
return { msg: 'Dying ' + old + ' + ' + total + wNote + ' = Dying ' + newDying + ', reaching the death threshold of ' + threshold + '. You die.' };
}
simDying = newDying;
simHistory.push({ state: 'DYING', dying: newDying, via: label });
return { msg: 'Dying ' + old + ' + ' + total + wNote + ' = Dying ' + newDying + '.' };
}
// ── Actions ───────────────────────────────────────────────────
function getActions() {
var threshold = deathThreshold();
if (simState === 'ALIVE') return [
{
name: 'Reduced to 0 HP (lethal)',
hint: 'Hit by a lethal attack, spell, or effect',
needsRoll: true,
rollLabel: 'How were you reduced to 0 HP?',
getOutcomes: function() {
var nv = 1 + simWounded, cv = 2 + simWounded;
var wL = simWounded > 0 ? ' (+' + simWounded + 'W)' : '';
var dead = ' <strong style="color:var(--c-dead-hi)">\u2192 Instant Death!</strong>';
return [
{ label: 'Normal hit \u2014 Dying ' + nv + wL + (nv >= threshold ? dead : ''),
action: function() { return gotoNewDying(1); } },
{ label: 'Attacker\u2019s critical hit or your critical failure \u2014 Dying ' + cv + wL + (cv >= threshold ? dead : ''),
action: function() { return gotoNewDying(2); } },
];
},
},
{
name: 'Reduced to 0 HP (nonlethal)',
hint: 'Nonlethal attack or effect \u2014 no dying condition',
needsRoll: false,
action: function() {
simState = 'UNCONSCIOUS'; simDying = 0;
simHistory.push({ state: 'UNCONSCIOUS', dying: 0, via: 'Nonlethal KO' });
return { msg: 'You are knocked unconscious with 0 HP but <strong>no dying condition</strong>. You will naturally return to 1 HP and wake after sufficient time (minimum 10 minutes), or sooner with healing.' };
},
},
{
name: 'Massive Damage',
hint: 'Take damage \u2265 double your maximum HP in one blow',
needsRoll: false,
action: function() {
simState = 'DEAD'; simDying = 0;
simHistory.push({ state: 'DEAD', dying: 0, via: 'Massive Damage' });
return { msg: 'You die instantly. Taking damage equal to or greater than <strong>double your maximum Hit Points</strong> in a single blow causes immediate death, bypassing the dying condition entirely.' };
},
},
{
name: 'Death Effect',
hint: 'A spell or ability with the death trait \u2014 kills without reaching dying 4',
needsRoll: false,
action: function() {
simState = 'DEAD'; simDying = 0;
simHistory.push({ state: 'DEAD', dying: 0, via: 'Death Effect' });
return { msg: 'You are slain instantly by a death effect. These abilities bypass the normal dying progression &mdash; if they reduce you to 0 HP you die without reaching dying 4. Some kill outright without dealing damage at all.' };
},
},
{
name: 'Short Rest (10 min)',
hint: simWounded > 0 ? 'Treat Wounds \u2014 Medicine check to clear Wounded ' + simWounded : 'No Wounded condition to treat',
needsRoll: simWounded > 0,
rollLabel: 'Treat Wounds \u2014 Medicine check result:',
getOutcomes: function() {
var prev = simWounded;
return [
{ label: '&#x2713; Success \u2014 Wounded ' + prev + ' clears',
action: function() {
simWounded = 0; renderCondVals();
simHistory.push({ state: 'ALIVE', dying: 0, via: 'Short Rest' });
return { msg: 'Treat Wounds succeeds. Your <strong>Wounded&nbsp;' + prev + '</strong> condition is cleared.' };
}
},
{ label: '&#x2717; Failure \u2014 Wounded ' + prev + ' remains',
action: function() {
simHistory.push({ state: 'ALIVE', dying: 0, via: 'Short Rest' });
return { msg: 'Treat Wounds fails. Your <strong>Wounded&nbsp;' + prev + '</strong> condition remains.' };
}
},
];
},
action: function() {
simHistory.push({ state: 'ALIVE', dying: 0, via: 'Short Rest' });
return { msg: 'You rest for 10 minutes. You have no Wounded condition to treat.' };
},
},
{
name: 'Long Rest (full night)',
hint: 'Doomed \u22121; Wounded clears (restored to full HP during rest)',
needsRoll: false,
action: function() {
var parts = [];
if (simDoomed > 0) {
simDoomed -= 1;
parts.push('Doomed decreases by 1 (now <strong>Doomed&nbsp;' + simDoomed + '</strong>).');
} else {
parts.push('Doomed is already 0.');
}
if (simWounded > 0) {
var prev = simWounded; simWounded = 0;
parts.push('Wounded&nbsp;' + prev + ' clears (restored to full HP and rested).');
} else {
parts.push('No Wounded condition to clear.');
}
renderCondVals();
simHistory.push({ state: 'ALIVE', dying: 0, via: 'Long Rest' });
return { msg: parts.join(' ') };
},
},
];
if (simState === 'DYING') {
var dc = 10 + simDying;
return [
{
name: 'Recovery Check',
hint: 'Flat check at start of your turn \u2014 DC ' + dc,
needsRoll: true,
rollLabel: 'Recovery Check \u2014 flat check DC <strong>' + dc + '</strong>:',
getOutcomes: function() {
var t = deathThreshold();
var csR = simDying - 2, sR = simDying - 1;
var fR = simDying + 1 + simWounded, cfR = simDying + 2 + simWounded;
var wL = simWounded > 0 ? '+' + simWounded + 'W' : '';
var dead = ' <strong style="color:var(--c-dead-hi)">\u2192 Death</strong>';
var stable = ' <strong style="color:var(--c-alive-hi)">\u2192 Stabilize</strong>';
return [
{ label: '&#x2713;&#x2713; Critical Success \u2014 dying&minus;2 = Dying ' + Math.max(0,csR) + (csR <= 0 ? stable : ''),
action: function() { return recoveryImprove(2); } },
{ label: '&#x2713; Success \u2014 dying&minus;1 = Dying ' + Math.max(0,sR) + (sR <= 0 ? stable : ''),
action: function() { return recoveryImprove(1); } },
{ label: '&#x2717; Failure \u2014 dying+1' + (wL ? '+' + wL : '') + ' = Dying ' + fR + (fR >= t ? dead : ''),
action: function() { return recoveryWorsen(1); } },
{ label: '&#x2717;&#x2717; Critical Failure \u2014 dying+2' + (wL ? '+' + wL : '') + ' = Dying ' + cfR + (cfR >= t ? dead : ''),
action: function() { return recoveryWorsen(2); } },
];
},
},
{
name: 'Take Damage While Dying',
hint: 'Any damage while dying increases dying value (+W)',
needsRoll: true,
rollLabel: 'Was the hit a critical?',
getOutcomes: function() {
var t = deathThreshold();
var nR = simDying + 1 + simWounded, cR = simDying + 2 + simWounded;
var wL = simWounded > 0 ? '+' + simWounded + 'W' : '';
var dead = ' <strong style="color:var(--c-dead-hi)">\u2192 Death</strong>';
return [
{ label: 'Normal hit \u2014 dying+1' + (wL ? '+' + wL : '') + ' = Dying ' + nR + (nR >= t ? dead : ''),
action: function() { return dyingWorsen(1, 'Take Damage'); } },
{ label: 'Critical hit or critical failure \u2014 dying+2' + (wL ? '+' + wL : '') + ' = Dying ' + cR + (cR >= t ? dead : ''),
action: function() { return dyingWorsen(2, 'Critical Hit'); } },
];
},
},
{
name: 'Receive Healing (1+ HP)',
hint: 'A spell, potion, or ability restores at least 1 HP',
needsRoll: false,
action: function() {
simState = 'ALIVE'; simDying = 0; simWounded += 1;
simHistory.push({ state: 'ALIVE', dying: 0, via: 'Healing' });
return { msg: 'You regain Hit Points and wake up. You lose the dying condition automatically. You gain the Wounded condition (now <strong>Wounded&nbsp;' + simWounded + '</strong>).' };
},
},
{
name: 'Heroic Recovery',
hint: 'Spend all Hero Points at start of turn or when dying would increase',
needsRoll: false,
action: function() {
simState = 'UNCONSCIOUS'; simDying = 0;
simHistory.push({ state: 'UNCONSCIOUS', dying: 0, via: 'Heroic Recovery' });
return { msg: 'You spend all your Hero Points. You lose the dying condition and stabilize at 0 HP. Your Wounded condition does <strong>not</strong> increase from this recovery.' + (simWounded > 0 ? ' You keep your existing Wounded&nbsp;' + simWounded + '.' : '') };
},
},
];
}
if (simState === 'UNCONSCIOUS') return [
{
name: 'Receive Healing (1+ HP)',
hint: 'A spell, potion, or ability restores at least 1 HP',
needsRoll: false,
action: function() {
simState = 'ALIVE'; simDying = 0;
simHistory.push({ state: 'ALIVE', dying: 0, via: 'Healing' });
return { msg: 'You regain Hit Points and wake up. You lose the unconscious condition and can act normally on your next turn.' };
},
},
{
name: 'Natural Recovery (time passes)',
hint: 'At 0 HP \u2014 return to 1 HP after 10+ minutes',
needsRoll: false,
action: function() {
simState = 'ALIVE'; simDying = 0;
simHistory.push({ state: 'ALIVE', dying: 0, via: 'Natural Recovery' });
return { msg: 'Sufficient time has passed. You naturally return to 1 Hit Point and awaken. The GM determines how long this takes (minimum 10 minutes, up to several hours).' };
},
},
{
name: 'Woken Up',
hint: 'Loud noise, damage (not to 0 HP), healing, or Interact action',
needsRoll: false,
action: function() {
simState = 'ALIVE'; simDying = 0;
simHistory.push({ state: 'ALIVE', dying: 0, via: 'Woken Up' });
return { msg: 'You are awoken &mdash; by a loud noise (Perception check vs DC&nbsp;5 for battle), an ally using an Interact action, healing, or damage that doesn\u2019t reduce you to 0 HP. You lose the unconscious condition.' };
},
},
{
name: 'Long Rest (full night)',
hint: 'Wake at full HP; Doomed \u22121; Wounded clears',
needsRoll: false,
action: function() {
simState = 'ALIVE'; simDying = 0;
var parts = ['You wake fully rested and restored to full HP.'];
if (simDoomed > 0) {
simDoomed -= 1;
parts.push('Doomed decreases by 1 (now <strong>Doomed&nbsp;' + simDoomed + '</strong>).');
}
if (simWounded > 0) {
var prev = simWounded; simWounded = 0;
parts.push('Wounded&nbsp;' + prev + ' clears.');
}
renderCondVals();
simHistory.push({ state: 'ALIVE', dying: 0, via: 'Long Rest' });
return { msg: parts.join(' ') };
},
},
{
name: 'Take Lethal Damage (to 0 HP)',
hint: 'Damage while at 0 HP \u2014 gain dying condition (+W)',
needsRoll: false,
action: function() {
var startValue = 1 + simWounded;
var threshold = deathThreshold();
var wNote = simWounded > 0 ? ' (+' + simWounded + 'W)' : '';
if (startValue >= threshold) {
simState = 'DEAD'; simDying = 0;
simHistory.push({ state: 'DEAD', dying: 0, via: 'Damage While Unconscious' });
return { msg: 'You take lethal damage at 0 HP and gain the dying condition at Dying ' + startValue + wNote + '. This meets the death threshold of ' + threshold + '. You die.' };
}
simState = 'DYING'; simDying = startValue;
simHistory.push({ state: 'DYING', dying: startValue, via: 'Damage While Unconscious' });
return { msg: 'You take lethal damage at 0 HP and gain the dying condition at Dying ' + startValue + wNote + '. Attempt a recovery check (flat check DC&nbsp;' + (10 + startValue) + ') at the start of each turn.' };
},
},
];
if (simState === 'DEAD') return [
{
name: 'Resurrection Magic',
hint: 'raise dead spell, resurrect ritual, or similar',
needsRoll: false,
action: function() {
simState = 'ALIVE'; simDying = 0;
simHistory.push({ state: 'ALIVE', dying: 0, via: 'Resurrection' });
return { msg: 'Powerful magic restores you to life. Note: many resurrection methods impose the <strong>Doomed</strong> condition or other lasting effects, and rare artifacts or powers may block resurrection entirely.' };
},
},
];
return [];
}
// ── Condition Adjusters ───────────────────────────────────────
function adjustDying(delta) {
var current = (simState === 'DYING') ? simDying : 0;
var newVal = current + delta;
if (newVal <= 0) {
if (simState === 'DYING') { simState = 'UNCONSCIOUS'; }
simDying = 0;
} else if (newVal >= deathThreshold()) {
simState = 'DEAD'; simDying = 0;
} else {
simState = 'DYING'; simDying = newVal;
}
renderCondVals();
simRenderStateBox();
simRenderActions();
document.getElementById('sim-outcomes-section').style.display = 'none';
document.getElementById('sim-result-container').innerHTML = '';
}
function adjustWounded(delta) {
simWounded = Math.max(0, simWounded + delta);
renderCondVals();
simRenderStateBox();
simRenderActions();
}
function adjustDoomed(delta) {
simDoomed = Math.max(0, Math.min(3, simDoomed + delta));
if (simState === 'DYING' && simDying >= deathThreshold()) {
simState = 'DEAD'; simDying = 0;
simHistory.push({ state: 'DEAD', dying: 0, via: 'Doomed threshold' });
}
renderCondVals();
simRenderStateBox();
simRenderActions();
}
function renderCondVals() {
var dyEl = document.getElementById('sim-dying-val');
var wEl = document.getElementById('sim-wounded-val');
var dEl = document.getElementById('sim-doomed-val');
var dv = (simState === 'DYING') ? simDying : 0;
dyEl.textContent = dv;
dyEl.className = 'sim-cond-val' + (dv > 0 ? ' dying-hi-' + Math.min(3, dv) : '');
wEl.textContent = simWounded;
wEl.className = 'sim-cond-val' + (simWounded > 0 ? ' wounded-hi' : '');
dEl.textContent = simDoomed;
dEl.className = 'sim-cond-val' + (simDoomed > 0 ? ' doomed-hi' : '');
}
// ── Core Sim Functions ────────────────────────────────────────
function simSetState(state, dyingVal) {
if (state === 'DYING' && dyingVal >= deathThreshold()) {
simState = 'DEAD'; simDying = 0;
} else {
simState = state; simDying = dyingVal;
}
simHistory = [{ state: simState, dying: simDying }];
simPending = null;
renderCondVals();
simRender();
}
function simReset() {
simState = 'ALIVE'; simDying = 0; simWounded = 0; simDoomed = 0;
simHistory = [{ state: 'ALIVE', dying: 0 }];
simPending = null;
renderCondVals();
simRender();
}
function simSelectAction(idx) {
var actions = getActions();
var action = actions[idx];
if (!action) return;
simPending = action;
document.getElementById('sim-result-container').innerHTML = '';
if (!action.needsRoll) {
var result = action.action();
simRenderWithResult(result.msg);
} else {
simRenderActionHighlight(idx);
simRenderOutcomes(action);
}
}
function simSelectOutcome(idx) {
if (!simPending || !simPending.getOutcomes) return;
var outcomes = simPending.getOutcomes();
var o = outcomes[idx];
if (!o) return;
var result = o.action();
simPending = null;
simRenderWithResult(result.msg);
}
function simRender() {
simRenderStateBox();
simRenderActions();
document.getElementById('sim-outcomes-section').style.display = 'none';
document.getElementById('sim-result-container').innerHTML = '';
simRenderHistory();
}
function simRenderWithResult(msg) {
renderCondVals();
simRenderStateBox();
simRenderActions();
document.getElementById('sim-outcomes-section').style.display = 'none';
var css = stateCssClass(simState, simDying);
var lbl = stateLabel(simState, simDying);
document.getElementById('sim-result-container').innerHTML =
'<div class="sim-result-box ' + css + '">' +
'<div class="sim-result-top">' +
'<span style="color:var(--text-muted);font-size:1.1rem;">\u2192</span>' +
'<span class="sim-result-new-state">' + lbl + '</span>' +
'</div>' +
'<div class="sim-result-msg">' + msg + '</div>' +
'</div>';
simRenderHistory();
}
function simRenderStateBox() {
var css = stateCssClass(simState, simDying);
var box = document.getElementById('sim-current-box');
box.className = 'sim-box ' + css;
document.getElementById('sim-state-name').textContent = stateLabel(simState, simDying);
document.getElementById('sim-state-effects').innerHTML = stateEffects(simState, simDying);
}
function simRenderActions() {
var actions = getActions();
document.getElementById('sim-actions-list').innerHTML = actions.map(function(a, i) {
return '<button class="sim-action" id="sim-act-' + i + '" onclick="simSelectAction(' + i + ')">' +
'<div class="sim-action-name">' + a.name + '</div>' +
'<div class="sim-action-hint">' + a.hint + '</div>' +
'</button>';
}).join('');
}
function simRenderActionHighlight(idx) {
document.querySelectorAll('.sim-action').forEach(function(b) { b.classList.remove('selected'); });
var btn = document.getElementById('sim-act-' + idx);
if (btn) btn.classList.add('selected');
}
function simRenderOutcomes(action) {
var section = document.getElementById('sim-outcomes-section');
section.style.display = 'block';
document.getElementById('sim-outcomes-head').innerHTML = action.rollLabel;
var outcomes = action.getOutcomes();
document.getElementById('sim-outcomes-list').innerHTML = outcomes.map(function(o, i) {
return '<button class="sim-outcome" onclick="simSelectOutcome(' + i + ')">' + o.label + '</button>';
}).join('');
}
function simRenderHistory() {
var trail = document.getElementById('sim-history-trail');
var html = '';
simHistory.forEach(function(entry, i) {
var css = stateCssClass(entry.state, entry.dying);
var lbl = stateLabel(entry.state, entry.dying);
if (i > 0 && entry.via) {
html += '<span class="sim-hist-via">\u2192 ' + entry.via + ' \u2192</span> ';
}
html += '<span class="sim-hist-state ' + css + '">' + lbl + '</span> ';
});
trail.innerHTML = html || '<span style="color:var(--text-muted);font-style:italic;font-size:0.78rem">No actions yet</span>';
}
</script>
</body>
</html>