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.

960 lines
44 KiB

<!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>PF2e Hit Points, Healing &amp; Dying</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-1);border-color:var(--c-dying-1-hi)"></div> Dying 1</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--c-dying-2);border-color:var(--c-dying-2-hi)"></div> Dying 2</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--c-dying-3);border-color:var(--c-dying-3-hi)"></div> Dying 3</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 d1 fill:#3e2e06,stroke:#a07a10,color:#f0e060
classDef d2 fill:#3e1a06,stroke:#a04010,color:#f0a868
classDef d3 fill:#300c0c,stroke:#8a1010,color:#f07070
classDef dead fill:#1a0404,stroke:#600808,color:#c06060
classDef unc fill:#0e2234,stroke:#2060a0,color:#90c8e8
ALIVE(["ALIVE · ≥1 HP"]):::alive
D1(["DYING 1 · Recovery DC 11"]):::d1
D2(["DYING 2 · Recovery DC 12"]):::d2
D3(["DYING 3 · Recovery DC 13"]):::d3
DEAD(["DEAD · dying 4"]):::dead
UNC(["UNCONSCIOUS · 0 HP"]):::unc
ALIVE -->|"0 HP lethal · normal hit (+W)"| D1
ALIVE -->|"0 HP lethal · crit hit/fail (+W)"| D2
ALIVE -->|"0 HP nonlethal"| UNC
ALIVE -->|"Massive dmg or Death Effect"| DEAD
D1 -->|"Crit Success · dying-2"| UNC
D1 -->|"Success · dying-1"| UNC
D1 -->|"Failure · dying+1+W"| D2
D1 -->|"Crit Fail · dying+2+W"| D3
D1 -->|"Take dmg · +1+W"| D2
D1 -->|"Healing 1+ HP"| ALIVE
D1 -->|"Hero Points"| UNC
D2 -->|"Crit Success · dying-2"| UNC
D2 -->|"Success · dying-1"| D1
D2 -->|"Failure · dying+1+W"| D3
D2 -->|"Crit Fail · dying+2+W"| DEAD
D2 -->|"Take dmg · +1+W"| D3
D2 -->|"Healing 1+ HP"| ALIVE
D2 -->|"Hero Points"| UNC
D3 -->|"Crit Success · dying-2"| D1
D3 -->|"Success · dying-1"| D2
D3 -->|"Failure · dying+1+W"| DEAD
D3 -->|"Crit Fail · dying+2+W"| DEAD
D3 -->|"Take dmg · +1+W"| DEAD
D3 -->|"Healing 1+ HP"| ALIVE
D3 -->|"Hero Points"| UNC
UNC -->|"Healing or natural recovery"| ALIVE
UNC -->|"Take lethal dmg to 0 HP"| D1
</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>