import { resetEnvironment, stepEnvironment } from './api.js'; import { ACTION_KINDS, AUTHORITIES, DATA_TYPES, RESOURCE_TYPES, SEVERITIES, buildAction, formatAction, labelize, normalizeRegionSelection, resourceValue, } from './actions.js'; import { narrateCouncil } from './council-narration.js'; import { goToFrame, setReplayTrace, stopReplay, toggleReplayPlayback } from './replay.js'; import { SAMPLE_TRACE } from './samples.js'; import { AppState, addTraceFrame, setState, showToast, subscribe } from './state.js'; const app = document.getElementById('app'); let liveAutoplayTimer = null; function init() { const first = SAMPLE_TRACE[0]; setState({ replayTrace: SAMPLE_TRACE, replayIndex: 0, observation: first.observation, reward: first.reward, done: first.done, council: narrateCouncil(first.observation, first.reward), }); subscribe(render); render(); } function render() { const regionPatch = normalizeRegionSelection(AppState); if (Object.keys(regionPatch).length) { setState(regionPatch); return; } const council = narrateCouncil(AppState.observation, AppState.reward || 0); AppState.council = council; app.innerHTML = `
${renderTopbar()} ${renderCommandbar()}
${renderPhasePanel(council)} ${renderWorldPanel()} ${renderResourcesPanel()}
${renderActionPanel(council)} ${renderTimelinePanel()}
${renderCouncilPanel(council)} ${renderReplayPanel()}
`; bindEvents(); } function renderTopbar() { return `
CW

CrisisWorld Cortex

Outbreak response dashboard for live OpenEnv episodes and replay traces.

${AppState.mode === 'live' ? 'Live env' : 'Sample replay'} ${AppState.done ? 'Terminal' : ''} ${AppState.connection}
`; } function renderCommandbar() { return `
${AppState.replaySpeedMs} ms
`; } function renderPhasePanel(council) { const obs = AppState.observation; return `

Episode State

${AppState.statusMessage}
${metric('Tick', obs?.tick ?? '-', `${obs?.ticks_remaining ?? '-'} left`)} ${metric('Phase', council.phase, `round ${council.round}`)} ${metric('Budget', council.budget || '-', 'cognition')} ${metric('Reward', formatReward(AppState.reward), `total ${formatReward(AppState.totalReward)}`)}
`; } function renderWorldPanel() { const obs = AppState.observation; if (!obs) return panel('World State', '
No observation loaded.
'); const regions = obs.regions || []; const maxScore = Math.max(...regions.map((region) => pressure(region)), 1); const html = `
${regions.map((region) => renderRegion(region, pressure(region) / maxScore)).join('')}
`; return panel('World State', html); } function renderRegion(region, heat) { const hot = heat > 0.78 ? 'hot' : ''; return `
${region.region} ${Math.round(heat * 100)} pressure
${statline('Cases', clamp(region.reported_cases_d_ago / 180, 0, 1), region.reported_cases_d_ago)} ${statline('Load', region.hospital_load, pct(region.hospital_load), 'load')} ${statline('Comply', region.compliance_proxy, pct(region.compliance_proxy), 'comp')}
`; } function renderResourcesPanel() { const obs = AppState.observation; const resources = obs?.resources || {}; const constraints = obs?.legal_constraints || []; const restrictions = obs?.active_restrictions || []; return panel('Resources and Constraints', `
${RESOURCE_TYPES.map((type) => `
${labelize(type)} ${resourceValue(resources, type)}
`).join('')}
Active restrictions
${restrictions.length ? restrictions.map((item) => `
${item.region} ${labelize(item.severity)} movement limits ${item.ticks_remaining} ticks
`).join('') : '
None active.
'}
Legal constraints
${constraints.length ? constraints.map((item) => `
${item.rule_id} Blocks ${item.blocked_action} ${item.unlock_via}
`).join('') : '
No active legal blockers.
'}
`); } function renderActionPanel(council) { const selected = AppState.selectedActionKind; return panel('Task Controls', `
${ACTION_KINDS.map((kind) => ` `).join('')}
${renderActionInputs(selected)}
Recommendation: ${formatAction(council.decision)}
`); } function renderActionInputs(kind) { const regions = AppState.observation?.regions || [{ region: AppState.selectedRegion || 'R1' }]; const regionSelect = ` `; const resourceSelect = (bindName, label) => ` `; const quantity = ` `; if (kind === 'deploy_resource') { return `
${regionSelect}${resourceSelect('selectedResource', 'Resource')}${quantity}
`; } if (kind === 'request_data') { return `
${regionSelect}
`; } if (kind === 'restrict_movement') { return `
${regionSelect}
`; } if (kind === 'escalate') { return `
`; } if (kind === 'reallocate_budget') { return `
${resourceSelect('selectedResource', 'From')}${resourceSelect('selectedToResource', 'To')}${quantity}
`; } return '
No parameters required.
'; } function renderCouncilPanel(council) { return panel('Narrated Council', `
Frontend visualization derived from observations. It is not a live Cortex runtime or LLM council.

Converged action

${formatAction(council.decision)}

${council.rationale}

${council.recommendations.map((report) => renderBrain(report)).join('')}
Preserved dissent
${council.preservedDissent.length ? council.preservedDissent.map((item, index) => `
D${index + 1} ${item}
`).join('') : '
No dissent preserved on this frame.
'}
`); } function renderBrain(report) { return `

${report.name}

${Math.round(report.confidence * 100)} conf

${formatAction(report.action)}

${report.summary}

Challenge
${report.challenge}

Minority
${formatAction(report.minority)}

`; } function renderTimelinePanel() { const log = AppState.observation?.recent_action_log || []; const liveTrace = AppState.liveTrace || []; return panel('Final Action Timeline', `
${log.length ? log.map((entry) => `
T${entry.tick} ${formatAction(entry.action)} ${entry.accepted ? 'accepted' : 'rejected'}
`).join('') : '
No actions have been submitted yet.
'}
Live trace frames captured this session: ${liveTrace.length}
`); } function renderReplayPanel() { const max = Math.max(0, AppState.replayTrace.length - 1); return panel('Replay', `
${AppState.replayIndex + 1}/${AppState.replayTrace.length || 1}

Replay changes the displayed frame only. It does not step the environment.

`); } function panel(title, body) { return `

${title}

${body}
`; } function metric(label, value, hint) { return `
${label} ${value} ${hint}
`; } function statline(label, value, display, extraClass = '') { return `
${label}
${display}
`; } function bindEvents() { document.querySelectorAll('[data-bind]').forEach((element) => { element.addEventListener('change', () => { const key = element.dataset.bind; const value = element.type === 'number' || element.type === 'range' ? Number.parseInt(element.value, 10) : element.value; setState({ [key]: value }); }); if (element.type === 'range') { element.addEventListener('input', () => { const key = element.dataset.bind; setState({ [key]: Number.parseInt(element.value, 10) }); }); } }); document.querySelectorAll('[data-kind]').forEach((button) => { button.addEventListener('click', () => setState({ selectedActionKind: button.dataset.kind })); }); document.querySelectorAll('[data-action]').forEach((element) => { element.addEventListener('click', () => handleAction(element.dataset.action, element)); if (element.dataset.action === 'scrub-replay') { element.addEventListener('input', () => { stopReplay(); goToFrame(Number.parseInt(element.value, 10)); }); } }); } async function handleAction(actionName, element) { if (actionName === 'open-web') { window.location.href = '/web/'; return; } if (actionName === 'load-sample') { stopLiveAutoplay(); setReplayTrace(SAMPLE_TRACE); setState({ mode: 'sample', statusMessage: 'Sample trace loaded', council: narrateCouncil(SAMPLE_TRACE[0].observation, SAMPLE_TRACE[0].reward), }); showToast('Sample trace loaded', 'success'); return; } if (actionName === 'use-live-trace') { if (!AppState.liveTrace.length) return; setReplayTrace(AppState.liveTrace); setState({ mode: 'replay', statusMessage: 'Live trace replay' }); return; } if (actionName === 'toggle-replay') { toggleReplayPlayback(); return; } if (actionName === 'scrub-replay') { stopReplay(); goToFrame(Number.parseInt(element.value, 10)); return; } if (actionName === 'reset-live') { await resetLive(); return; } if (actionName === 'submit-action') { await submitAction(buildAction(AppState)); return; } if (actionName === 'apply-recommendation') { await submitAction(AppState.council?.decision || { kind: 'no_op' }); return; } if (actionName === 'toggle-live-autoplay') { toggleLiveAutoplay(); } } async function resetLive() { stopReplay(); stopLiveAutoplay(); setState({ connection: 'resetting', statusMessage: 'Resetting live episode' }); try { const data = await resetEnvironment({ taskName: AppState.taskName, seed: AppState.seed, maxTicks: AppState.maxTicks, }); const frame = { label: `Live reset: ${AppState.taskName}`, action: null, observation: data.observation, reward: data.reward ?? 0, done: Boolean(data.done), }; setState({ mode: 'live', connection: 'connected', observation: data.observation, reward: data.reward ?? 0, done: Boolean(data.done), totalReward: 0, liveTrace: [frame], replayTrace: [frame], replayIndex: 0, statusMessage: 'Live episode ready', }); showToast('Live episode reset', 'success'); } catch (error) { setState({ connection: 'error', statusMessage: 'Reset failed' }); showToast(`Reset failed: ${error.message}`, 'error'); } } async function submitAction(payload) { if (AppState.mode !== 'live') { showToast('Reset a live episode before submitting actions.', 'error'); return; } if (AppState.done) { showToast('Episode is terminal. Reset to continue.', 'error'); return; } setState({ connection: 'stepping', statusMessage: `Submitting ${payload.kind}` }); try { const data = await stepEnvironment(payload); const reward = Number(data.reward ?? 0); const frame = { label: `Submitted ${formatAction(payload)}`, action: payload, observation: data.observation, reward, done: Boolean(data.done), }; addTraceFrame(frame); setState({ mode: 'live', connection: 'connected', observation: data.observation, reward, done: Boolean(data.done), totalReward: AppState.totalReward + reward, statusMessage: data.done ? 'Episode complete' : 'Action accepted', }); if (data.done) { stopLiveAutoplay(); showToast('Episode complete', 'success'); } } catch (error) { stopLiveAutoplay(); setState({ connection: 'error', statusMessage: 'Step failed' }); showToast(`Step failed: ${error.message}`, 'error'); } } function toggleLiveAutoplay() { if (AppState.autoplayLive) { stopLiveAutoplay(); return; } if (AppState.mode !== 'live' || AppState.done) return; setState({ autoplayLive: true }); liveAutoplayTimer = window.setInterval(async () => { if (AppState.done || AppState.mode !== 'live') { stopLiveAutoplay(); return; } await submitAction(AppState.council?.decision || { kind: 'no_op' }); }, AppState.replaySpeedMs); } function stopLiveAutoplay() { if (liveAutoplayTimer) { window.clearInterval(liveAutoplayTimer); liveAutoplayTimer = null; } if (AppState.autoplayLive) setState({ autoplayLive: false }); } function option(value, label, selected) { return ``; } function pressure(region) { return region.reported_cases_d_ago / 1000 + region.hospital_load * 1.55 + (1 - region.compliance_proxy) * 0.75; } function pct(value) { return `${Math.round((value || 0) * 100)}%`; } function formatReward(value) { return typeof value === 'number' ? value.toFixed(2) : '-'; } function clamp(value, min, max) { return Math.max(min, Math.min(max, value)); } init();