const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"phase": "TFC",
"severity": "CRITICAL",
"forcedStep": "AUTO",
"scheme": "NIGHT",
"showPerf": true,
"playing": true,
"simT": 30,
"scenario": 0,
"holo": "CYAN",
"focus": "BOTH",
"scrollSpeed": 25,
"speed": 8,
"audio": false
}/*EDITMODE-END*/;
const TWEAK_FALLBACK = { phase: 'TFC', severity: 'CRITICAL', forcedStep: 'AUTO', scheme: 'NIGHT', showPerf: true, playing: true, simT: 30, scenario: 0, holo: 'CYAN', focus: 'BOTH', scrollSpeed: 25, speed: 8, audio: false };
const INIT_TWEAKS = { ...TWEAK_FALLBACK, ...TWEAK_DEFAULTS };
INIT_TWEAKS.simT = Number(INIT_TWEAKS.simT) || 0;
INIT_TWEAKS.scenario = Math.max(0, Math.min(SCENARIOS.length - 1, Number(INIT_TWEAKS.scenario) || 0));
INIT_TWEAKS.audio = false; // audio always starts muted (browser gesture policy)
const HOLO_SWATCH = { CYAN: '#8df0ff', GREEN: '#6df0a3', AMBER: '#ffd27a', VIOLET: '#c8a8ff' };
function HUDFrame({ children, label }) {
const wrapRef = React.useRef(null);
const scaleRef = React.useRef(null);
React.useLayoutEffect(() => {
const wrap = wrapRef.current, inner = scaleRef.current;
if (!wrap || !inner) return;
const apply = () => { if (wrap.clientWidth) inner.style.transform = `scale(${wrap.clientWidth / 960})`; };
apply();
const ro = new ResizeObserver(apply);
ro.observe(wrap);
return () => ro.disconnect();
}, []);
return (
<div className="hud-wrap" ref={wrapRef}>
{label && <div className="hud-label">{label}</div>}
<div className="hud-scale" ref={scaleRef}>{children}</div>
</div>
);
}
const NAR_RATIONALE = {
hemostasis: 'Direct hemorrhage control — the single largest preventable cause of battlefield death.',
binder: 'Closes the pelvic ring to tamponade venous bleeding.',
airway: 'Secures oxygenation before respiratory decline.',
seal: 'Occludes the chest-wall defect to prevent tension physiology.',
needle: 'Decompresses the pleural space — watch for re-tension.',
o2: 'High-flow oxygen ahead of possible airway edema.',
io: 'Vascular access for resuscitation and analgesia.',
txa: 'Antifibrinolytic — must run inside the 3-hour window.',
blood: 'Whole blood replaces what was lost; crystalloid only dilutes it.',
fluids: 'Calculated burn resuscitation — avoid over-infusion.',
hpmk: 'Hypothermia is one corner of the lethal triad.',
neuro: 'Serial neuro exams track an evolving TBI.',
pain: 'Analgesia chosen to protect pressure and airway.',
abx: 'Early antibiotics cut wound-infection mortality.',
dress: 'Clean, documented wounds for the receiving surgical team.',
splint: 'Stabilizes the fracture — less bleeding, less pain.',
};
function narrationHTML(scen) {
const steps = scen.script.map(e => {
const tt = `T+${Math.floor(e.t / 60)}:${String(e.t % 60).padStart(2, '0')}`;
if (e.kind === 'comp') return `<p class="nar-comp"><b>${tt} ⚠ COMPLICATION</b> — ${e.action}.</p>`;
if (e.kind === 'fix') return `<p class="nar-fix"><b>${tt} ✓ CORRECTIVE</b> — ${e.action}.` + (e.teach ? `<span class="nar-teach">TEACHING POINT — ${e.teach}</span>` : '') + `</p>`;
if (e.kind === 'teach') return `<p class="nar-lane"><b>${tt} ✚</b> — ${e.action}.` + (e.teach ? `<span class="nar-teach">TEACHING POINT — ${e.teach}</span>` : '') + `</p>`;
return `<p><b>${tt} · ${e.step.replace('2','')}</b> — ${e.action}. <span class="nar-dim">${NAR_RATIONALE[e.effect] || ''}</span></p>`;
}).join('');
return `<h3>${scen.casualty.name} · ${scen.mech}</h3>` +
`<p>${scen.casualty.unit} · ${scen.casualty.bloodType} · ${scen.casualty.weightKg} kg · allergies ${scen.casualty.allergies}. ` +
`A ${Math.round(scen.duration / 60)}-minute trauma lane under TCCC: interventions, scripted complications, corrective actions, and teaching points. This narration is editable — click and type.</p>` +
steps +
`<p><b>ENDEX</b> — casualty packaged, 9-line transmitted, DUSTOFF inbound. <span class="nar-dim">Debrief against the performance panel: time-to-TQ, benchmark deltas, order score.</span></p>`;
}
function NarrationPanel({ scenIdx, speed }) {
const boxRef = React.useRef(null);
const posRef = React.useRef(0);
React.useEffect(() => {
if (!boxRef.current) return;
boxRef.current.innerHTML = narrationHTML(SCENARIOS[scenIdx]);
boxRef.current.scrollTop = 0;
posRef.current = 0;
}, [scenIdx]);
React.useEffect(() => {
let raf, last = performance.now();
const tick = (now) => {
raf = requestAnimationFrame(tick);
const dt = (now - last) / 1000; last = now;
const el = boxRef.current;
if (!el || !speed || document.activeElement === el) return; // pause while editing
if (Math.abs(el.scrollTop - posRef.current) > 2) posRef.current = el.scrollTop; // user scrolled
posRef.current += speed * dt;
const max = el.scrollHeight - el.clientHeight;
if (speed > 0 && posRef.current >= max - 1) posRef.current = 0;
if (speed < 0 && posRef.current <= 0) posRef.current = max;
el.scrollTop = posRef.current;
};
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
}, [speed]);
return (
<div>
<div className="narration" ref={boxRef} contentEditable suppressContentEditableWarning spellCheck={false}></div>
<div className="nar-hint">Editable narration — click into the panel to type (auto-scroll pauses while editing). Roll speed &amp; direction in the top-right controls.</div>
</div>
);
}
function Controls({ tweaks, setTweaks }) {
const set = (k, v) => setTweaks(prev => ({ ...prev, [k]: v }));
const mm = Math.floor(tweaks.simT / 60).toString().padStart(2,'0');
const ss = Math.floor(tweaks.simT % 60).toString().padStart(2,'0');
const dur = SCENARIOS[tweaks.scenario].duration;
const focused = tweaks.focus !== 'BOTH';
return (
<div className="controls">
<div className="row">
<select value={tweaks.scenario} onChange={e => set('scenario', +e.target.value)}>
{SCENARIOS.map((s, i) => <option key={i} value={i}>{s.label}</option>)}
</select>
<button onClick={() => set('playing', !tweaks.playing)}>{tweaks.playing ? '⏸' : '▶'}</button>
<button onClick={() => { set('simT', 0); set('playing', true); }}>↺</button>
<input type="range" min="0" max={dur} step="1" value={tweaks.simT}
onChange={e => { set('simT', +e.target.value); set('playing', false); }}/>
<span className="cue">T+{mm}:{ss}</span>
<span className="lbl">SIM</span>
{[1, 8, 20].map(x => (
<button key={x} className={tweaks.speed === x ? 'on' : ''} onClick={() => set('speed', x)}>×{x}</button>
))}
</div>
<div className="row">
<span className="lbl">FOCUS</span>
{['BOTH','TACMED','AEGIS'].map(f => (
<button key={f} className={tweaks.focus === f ? 'on' : ''} onClick={() => set('focus', f)}>{f}</button>
))}
<span className="lbl" style={{ marginLeft: 8 }}>HOLO</span>
{Object.entries(HOLO_SWATCH).map(([name, c]) => (
<button key={name} title={name} className={'swatch' + (tweaks.holo === name ? ' on' : '')}
style={{ background: c, color: c }} onClick={() => set('holo', name)}></button>
))}
<button style={{ marginLeft: 8 }} className={tweaks.showPerf ? 'on' : ''} onClick={() => set('showPerf', !tweaks.showPerf)}>PERF</button>
<button className={tweaks.audio ? 'on' : ''}
onClick={() => { const next = !tweaks.audio; if (next && window.TacAudio) TacAudio.start(); if (!next && window.TacAudio) TacAudio.stop(); set('audio', next); }}>
{tweaks.audio ? '🔊 AUDIO' : '🔇 AUDIO'}
</button>
<div className="scheme-tgl" role="group" aria-label="Color scheme">
<span className={'st-thumb' + (tweaks.scheme === 'DAY' ? ' day' : '')} aria-hidden="true"></span>
<button className={tweaks.scheme === 'NIGHT' ? 'on' : ''} title="Night scheme — dark suite look"
onClick={() => set('scheme', 'NIGHT')}>☾ NIGHT</button>
<button className={tweaks.scheme === 'DAY' ? 'on' : ''} title="Day scheme — the original paper brief"
onClick={() => set('scheme', 'DAY')}>☀ DAY</button>
</div>
</div>
{focused && (
<div className="row">
<span className="lbl">ROLL</span>
<input type="range" min="-90" max="90" step="5" value={tweaks.scrollSpeed}
onChange={e => set('scrollSpeed', +e.target.value)}/>
<span className="cue">{tweaks.scrollSpeed > 0 ? '▼' : tweaks.scrollSpeed < 0 ? '▲' : '⏸'} {Math.abs(tweaks.scrollSpeed)}px/s</span>
</div>
)}
</div>
);
}
function App() {
const [tweaks, setTweaks] = React.useState(INIT_TWEAKS);
React.useEffect(() => {
document.body.classList.toggle('spm-day', tweaks.scheme === 'DAY');
}, [tweaks.scheme]);
React.useEffect(() => {
setScenario(tweaks.scenario);
setTweaks(prev => ({ ...prev, simT: 0, playing: true }));
}, [tweaks.scenario]);
React.useEffect(() => {
if (!tweaks.playing) return;
let raf;
let last = performance.now();
const tick = (now) => {
const dt = (now - last) / 1000;
last = now;
setTweaks(prev => {
if (!prev.playing) return prev;
let next = prev.simT + dt * (prev.speed || 8);
if (next > SCENARIOS[prev.scenario].duration) next = 30;
return { ...prev, simT: next };
});
raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
}, [tweaks.playing]);
const t = Math.floor(tweaks.simT);
const forced = tweaks.forcedStep === 'AUTO' ? null : tweaks.forcedStep;
const vit = vitalsAt(t);
const alertOn = vit.spo2 < 90 || vit.sbp < 85;
React.useEffect(() => { if (window.TacAudio) TacAudio.hr = vit.hr; });
React.useEffect(() => {
if (!tweaks.audio || !alertOn || !window.TacAudio) return;
TacAudio.alert();
const iv = setInterval(() => TacAudio.alert(), 3200);
return () => clearInterval(iv);
}, [alertOn, tweaks.audio]);
React.useEffect(() => () => { if (window.TacAudio) TacAudio.stop(); }, []);
const focused = tweaks.focus !== 'BOTH';
const showA = tweaks.focus !== 'AEGIS';
const showB = tweaks.focus !== 'TACMED';
return (
<>
<Controls tweaks={tweaks} setTweaks={setTweaks}/>
<div className="page">
<div className="kicker">TCCC DASHBOARD CONCEPT · MARCH·PAWS</div>
<h1 className="h1">Casualty Simulator</h1>
{!focused && (
<p className="lede">
Two HUD directions for a holographic combat-medic display supporting the full
MARCH·PAWS protocol. A library of <b>50 scripted casualty scenarios</b>, each paced
as a <b>20–25 minute trauma lane</b> — interventions, well-known complications, corrective
actions, and teaching points — built for classroom walk-through before hands-on trauma runs.
Vitals respond to every event; performance is scored live against lane benchmarks.
Pick a scenario and sim speed from the top-right controls; use FOCUS for the teaching teleprompter.
</p>
)}
{showA && (
<section id="variant-a">
<div className="section-head">
<div>
<h2 className="section-title">Variant A — TACMED-19</h2>
{!focused && (
<p className="section-sub">
Grounded, near-term rugged tactical. Dense instrumentation,
amber/red/green semantics. Feels like issued kit.
</p>
)}
</div>
</div>
<div className={'hud-row' + (focused ? ' solo' : '')}>
<HUDFrame label="Holographic projection · 960 × 640">
<VariantA t={t} phase={tweaks.phase} forcedStep={forced} showPerf={tweaks.showPerf} holo={tweaks.holo}/>
</HUDFrame>
{!focused && (
<div className="annotation-card">
<Annotations items={[
{ t: 'CASUALTY HEADER', d: 'Callsign, unit, blood type, allergy, weight, mechanism — the 9-line essentials auto-pulled from IFAK RFID + roster.' },
{ t: 'VITALS GRID', d: 'HR · BP · SpO₂ · RR · EtCO₂ · Temp · GCS · Shock Index. Each carries a sparkline so the medic sees trajectory, not just spot-values.' },
{ t: 'HEMODYNAMICS', d: 'Derived values: EBL, PI, MAP, pulse pressure — the triad of occult shock.' },
{ t: '3D INJURY HOLOGRAM', d: 'Rotating anatomical model. Injuries pulse by severity; interventions drop a ✓ ring at the treatment site. Hologram tint switchable in controls.' },
{ t: 'MARCH·PAWS TREE', d: 'All 9 steps in doctrinal order. Time-completed vs JTS benchmark; late steps flag with +delta.' },
{ t: 'PARALLEL TIMELINE', d: 'One track per MARCH step. Green ticks are interventions; amber is benchmark; red is NOW.' },
{ t: 'INTERVENTION LOG', d: 'Latest actions with T+ timestamp. Auto-feeds the TCCC Card for Role 2 handoff.' },
{ t: 'PERFORMANCE PANEL', d: 'Adherence %, order score, intervention count, per-step bar vs benchmark.' },
{ t: 'ALERT BAR', d: 'High-contrast escalation: shock trip, SpO₂ collapse, predicted pneumothorax — one imperative line.' },
]}/>
</div>
)}
</div>
{focused && showA && <NarrationPanel scenIdx={tweaks.scenario} speed={tweaks.scrollSpeed}/>}
</section>
)}
{showB && (
<section id="variant-b">
<div className="section-head">
<div>
<h2 className="section-title">Variant B — AEGIS Predictive</h2>
{!focused && (
<p className="section-sub">
Speculative near-future AI-augmented hologram. Translucent cyan glass,
predictive vitals, auto-populated 9-line, P(survival) ring.
</p>
)}
</div>
</div>
<div className={'hud-row' + (focused ? ' solo' : '')}>
<HUDFrame label="Holographic projection · 960 × 640">
<VariantB t={t} phase={tweaks.phase} forcedStep={forced} showPerf={tweaks.showPerf} holo={tweaks.holo}/>
</HUDFrame>
{!focused && (
<div className="annotation-card">
<Annotations items={[
{ t: 'P(SURVIVE) RING', d: 'Bayesian survival model fed by vitals trajectory, injury pattern, and resuscitation applied. A single-glance outcome signal.' },
{ t: 'TREND CARDS w/ PROJECTION', d: 'Large-numeral vitals with history + 30s AI projection (dashed). Medic sees where the patient is heading.' },
{ t: 'ANATOMICAL HOLOGRAM', d: '3D volumetric body model, slowly rotating. Injury markers pulse by severity and track the anatomy; interventions drop ✓ rings at the treatment site.' },
{ t: 'MARCH·PAWS STREAM', d: 'Horizontal pill row of all 9 steps. Active step expands with AI-tuned clinical suggestion.' },
{ t: 'NEXT ACTION', d: 'The single most important instruction right now. The whole HUD supports this one cell.' },
{ t: 'PERFORMANCE TELEMETRY', d: 'Adherence, order, TQ time, trajectory — annotated with cohort position vs n=4,218 JTS records.' },
{ t: '9-LINE AUTO', d: 'Populated from vitals, GPS, kit. Transmitted to Role 2 the moment URGENT-SURG fires.' },
{ t: 'AI TRIAGE TAG', d: 'Bayesian triage class. Elevates PRIORITY → URGENT-SURGICAL as shock index crosses threshold.' },
{ t: 'INTERVENTION LEDGER', d: 'Tamper-evident, cryptographically signed log of every action, ready for AAR.' },
]}/>
</div>
)}
</div>
{focused && showB && <NarrationPanel scenIdx={tweaks.scenario} speed={tweaks.scrollSpeed}/>}
</section>
)}
{!focused && (
<section id="data-points">
<div className="section-head">
<div>
<h2 className="section-title">Data points the medic sees — and why</h2>
<p className="section-sub">
What's on the glass is a deliberate edit. Everything here answers
"what do I do next?" or "how am I doing?"
</p>
</div>
</div>
<div className="data-grid">
<div className="data-card">
<div style={{ fontSize: 10, letterSpacing: 2, color: '#8df0ff', marginBottom: 12 }}>LIVE PHYSIOLOGY</div>
<DataList items={[
['HR, SBP/DBP, SpO₂, RR', 'Trauma primaries. Sparklines, not spot-values.'],
['EtCO₂', 'Best early proxy for perfusion + ROSC quality.'],
['Core temp', 'Trauma triad — coagulopathy predictor.'],
['GCS', 'Neuro baseline; flags TBI trajectory.'],
['Shock index (HR/SBP)', 'Early occult-shock tripwire at >0.9.'],
['Perfusion index', 'Pulse-ox-derived vasotone signal.'],
['EBL estimate', 'Integrated bleed rate + TQ/seal effects.'],
['MAP, pulse pressure', 'Derived. Drives resuscitation dosing.'],
]}/>
</div>
<div className="data-card">
<div style={{ fontSize: 10, letterSpacing: 2, color: '#8df0ff', marginBottom: 12 }}>PROTOCOL & INTERVENTION</div>
<DataList items={[
['MARCH·PAWS priority tree', 'Doctrine-locked order. Auto-escalates on vitals.'],
['Active step', 'One cell, loud. Medic never guesses.'],
['Completed steps + T+', 'Timestamped against JTS benchmark.'],
['Parallel timeline', 'Shows temporal overlap of interventions.'],
['Body-map markers', 'Injury + intervention locations.'],
['Intervention log', 'Feeds TCCC Card + AAR automatically.'],
['Medication ledger', 'TXA, ketamine, antibiotics — dose + route.'],
['Next-action suggestion', 'AI-tuned clinical prompt for current step.'],
]}/>
</div>
<div className="data-card">
<div style={{ fontSize: 10, letterSpacing: 2, color: '#8df0ff', marginBottom: 12 }}>PERFORMANCE FEEDBACK</div>
<DataList items={[
['Time-to-TQ', 'TCCC gold standard: ≤60s from contact.'],
['Time-to-airway', '≤120s benchmark; escalates if delayed.'],
['Time-to-needle-D', '≤180s for suspected tension pneumo.'],
['Adherence %', 'Composite: benchmarks hit vs total.'],
['Order score', 'Steps done in canonical MARCH order.'],
['Trajectory', 'Improving / stable / deteriorating (SBP slope).'],
['Cohort percentile', 'Your time vs JTS cohort (n=4,218).'],
['Deviation log', 'Any out-of-order or missed steps for AAR.'],
]}/>
</div>
<div className="data-card">
<div style={{ fontSize: 10, letterSpacing: 2, color: '#8df0ff', marginBottom: 12 }}>CONTEXT & HANDOFF</div>
<DataList items={[
['TCCC phase', 'CUF / TFC / TACEVAC — UI density adapts.'],
['Elapsed since contact', 'The only clock that matters.'],
['9-line EVAC auto-fill', 'GPS, precedence, special equipment, pax.'],
['Role 2 uplink status', 'Latency + handshake with surgical team.'],
['Mesh net status', 'For multi-casualty coordination.'],
['Kit inventory burn', 'TQs, seals, blood units remaining.'],
['IR strobe / LZ marker', 'Activation state for inbound Dustoff.'],
['Battery + GPS lock', 'The kit itself has to survive too.'],
]}/>
</div>
</div>
</section>
)}
</div>
</>
);
}
function Annotations({ items }) {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
{items.map((it, i) => (
<div key={i}>
<div style={{ fontSize: 10, letterSpacing: 1.8, color: '#b86b3a', fontWeight: 700, marginBottom: 3 }}>{it.t}</div>
<div className="ann-d" style={{ fontSize: 12, lineHeight: 1.5 }}>{it.d}</div>
</div>
))}
</div>
);
}
function DataList({ items }) {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, fontFamily: 'JetBrains Mono, monospace' }}>
{items.map((row, i) => (
<div key={i}>
<div style={{ fontSize: 11, color: '#8df0ff', letterSpacing: 0.5, fontWeight: 600 }}>{row[0]}</div>
<div style={{ fontSize: 10, color: '#7f98b3', lineHeight: 1.5, marginTop: 2 }}>{row[1]}</div>
</div>
))}
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App/>);