@neuroverseos/nv-sim 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -180,12 +180,30 @@ function startInteractiveServer(port, onReady) {
180
180
  let currentSession = {
181
181
  id: `session_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
182
182
  startedAt: new Date().toISOString(),
183
- world: "trading",
183
+ world: "social_simulation",
184
184
  guardCount: 0,
185
185
  evaluations: [],
186
186
  };
187
187
  // Session snapshots for multi-run comparison
188
188
  const sessionHistory = [];
189
+ // ── Bridge Metrics Tracker ──
190
+ // Computes meaningful live metrics from external /api/evaluate calls
191
+ let bridgeEvalCount = 0;
192
+ let bridgeBlockCount = 0;
193
+ let bridgeModifyCount = 0;
194
+ function computeBridgeMetrics(decision) {
195
+ bridgeEvalCount++;
196
+ if (decision === "BLOCK")
197
+ bridgeBlockCount++;
198
+ if (decision === "MODIFY")
199
+ bridgeModifyCount++;
200
+ const totalInterventions = bridgeBlockCount + bridgeModifyCount;
201
+ // Stability = ratio of non-blocked actions (higher = more stable)
202
+ const stability = bridgeEvalCount > 0 ? (bridgeEvalCount - bridgeBlockCount) / bridgeEvalCount : 1;
203
+ // Volatility = ratio of interventions (blocks + modifies) — more interventions = more volatile
204
+ const volatility = bridgeEvalCount > 0 ? totalInterventions / bridgeEvalCount : 0;
205
+ return { stability, volatility, totalInterventions, evalCount: bridgeEvalCount };
206
+ }
189
207
  function synthesizeSessionReport() {
190
208
  // Gather all sessions to report on (history + current if it has data)
191
209
  const allSessions = [
@@ -499,7 +517,7 @@ function startInteractiveServer(port, onReady) {
499
517
  }
500
518
  if (req.url === "/api/worlds" && req.method === "GET") {
501
519
  const { resolveWorld } = await Promise.resolve().then(() => __importStar(require("./worldComparison")));
502
- const worldIds = ["trading", "strait_of_hormuz", "gas_price_spike", "ai_regulation_crisis"];
520
+ const worldIds = ["social_simulation", "science_research"];
503
521
  const worlds = worldIds.map(id => {
504
522
  try {
505
523
  const r = resolveWorld(id);
@@ -602,7 +620,7 @@ function startInteractiveServer(port, onReady) {
602
620
  return;
603
621
  }
604
622
  // Resolve world for evaluation
605
- const worldId = payload.world ?? "trading";
623
+ const worldId = payload.world ?? "social_simulation";
606
624
  let nvWorld;
607
625
  try {
608
626
  const { resolveWorld } = await Promise.resolve().then(() => __importStar(require("./worldComparison")));
@@ -834,25 +852,35 @@ function startInteractiveServer(port, onReady) {
834
852
  const decision = verdict.status === "BLOCK" ? "BLOCK"
835
853
  : verdict.status === "PAUSE" ? "MODIFY"
836
854
  : "ALLOW";
855
+ // Compute live metrics from cumulative bridge evaluations
856
+ const bridgeMetrics = computeBridgeMetrics(decision);
837
857
  // Broadcast governance event to connected SSE clients
838
858
  broadcast({
839
859
  type: "round",
840
- round: 0,
860
+ round: bridgeMetrics.evalCount,
841
861
  totalRounds: 0,
842
862
  phase: "governed",
843
863
  reactions: [{
844
864
  stakeholder_id: payload.actor,
845
865
  reaction: payload.action,
846
- impact: 0,
866
+ impact: decision === "BLOCK" ? -0.8 : decision === "MODIFY" ? -0.3 : 0.1,
847
867
  confidence: 0.5,
848
868
  trigger: "bridge",
849
869
  verdict: { status: verdict.status, reason: verdict.reason, ruleId: verdict.ruleId },
850
870
  }],
851
- avgImpact: 0,
852
- maxVolatility: 0,
871
+ avgImpact: decision === "BLOCK" ? -0.8 : decision === "MODIFY" ? -0.3 : 0.1,
872
+ maxVolatility: bridgeMetrics.volatility,
853
873
  dynamics: [],
854
874
  interventionCount: decision !== "ALLOW" ? 1 : 0,
855
875
  });
876
+ // Also broadcast a stability update so the metric refreshes live
877
+ broadcast({
878
+ type: "bridge_metrics",
879
+ stability: bridgeMetrics.stability,
880
+ volatility: bridgeMetrics.volatility,
881
+ evalCount: bridgeMetrics.evalCount,
882
+ totalInterventions: bridgeMetrics.totalInterventions,
883
+ });
856
884
  // Record in session
857
885
  currentSession.evaluations.push({
858
886
  actor: payload.actor, action: payload.action,
@@ -923,6 +951,59 @@ function startInteractiveServer(port, onReady) {
923
951
  }
924
952
  return;
925
953
  }
954
+ // Generate a full governed world from plain-English rules
955
+ // Returns a complete world: thesis, state variables, invariants, gates
956
+ if (req.url === "/api/generate-world" && req.method === "POST") {
957
+ try {
958
+ const body = await readBody(req);
959
+ const payload = JSON.parse(body);
960
+ if (!payload.text) {
961
+ jsonResponse(res, 400, { error: "text is required" });
962
+ return;
963
+ }
964
+ const { parseRulesFromText, policyToWorld } = await Promise.resolve().then(() => __importStar(require("./policyEngine")));
965
+ const parsed = parseRulesFromText(payload.text);
966
+ const worldDef = policyToWorld(parsed);
967
+ const worldName = payload.name || "Custom World";
968
+ const worldId = "custom-" + worldName.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
969
+ // Apply as the active world
970
+ customGuards.length = 0;
971
+ for (let i = 0; i < parsed.rules.length; i++) {
972
+ const rule = parsed.rules[i];
973
+ customGuards.push({
974
+ id: worldDef.invariants[i]?.id ?? `custom-rule-${i}`,
975
+ label: rule.description,
976
+ description: rule.description,
977
+ category: "custom",
978
+ enforcement: (rule.category === "prohibit" || rule.category === "circuit_breaker") ? "block" : rule.category === "limit" ? "block" : "allow",
979
+ immutable: false,
980
+ intent_patterns: [...rule.keywords],
981
+ default_enabled: true,
982
+ });
983
+ }
984
+ jsonResponse(res, 200, {
985
+ status: "generated",
986
+ world: {
987
+ id: worldId,
988
+ title: worldName,
989
+ thesis: worldDef.thesis,
990
+ stateVariables: worldDef.state_variables,
991
+ invariants: worldDef.invariants,
992
+ gates: worldDef.gates ?? [],
993
+ },
994
+ parsed: {
995
+ total: parsed.summary.total,
996
+ enforced: parsed.summary.enforced,
997
+ advisory: parsed.summary.advisory,
998
+ },
999
+ rulesApplied: customGuards.length,
1000
+ });
1001
+ }
1002
+ catch (err) {
1003
+ jsonResponse(res, 400, { error: err?.message ?? "Invalid request" });
1004
+ }
1005
+ return;
1006
+ }
926
1007
  // Apply parsed rules to the active governance context
927
1008
  if (req.url === "/api/apply-rules" && req.method === "POST") {
928
1009
  try {
@@ -1050,7 +1131,7 @@ function startInteractiveServer(port, onReady) {
1050
1131
  // Exports the current world configuration (base world + custom rules + overrides) as a world file
1051
1132
  if (req.url === "/api/export-world" && req.method === "GET") {
1052
1133
  const { resolveWorld } = await Promise.resolve().then(() => __importStar(require("./worldComparison")));
1053
- const worldId = currentSession.world || "trading";
1134
+ const worldId = currentSession.world || "social_simulation";
1054
1135
  let baseWorld;
1055
1136
  try {
1056
1137
  baseWorld = resolveWorld(worldId);
@@ -1243,6 +1324,10 @@ function startInteractiveServer(port, onReady) {
1243
1324
  guardCount: customGuards.length,
1244
1325
  evaluations: [],
1245
1326
  };
1327
+ // Reset bridge metrics counters
1328
+ bridgeEvalCount = 0;
1329
+ bridgeBlockCount = 0;
1330
+ bridgeModifyCount = 0;
1246
1331
  jsonResponse(res, 200, {
1247
1332
  status: "reset",
1248
1333
  newSessionId: currentSession.id,
@@ -1321,7 +1406,7 @@ function startInteractiveServer(port, onReady) {
1321
1406
  jsonResponse(res, 200, { status: "started", adapter: payload.adapterId });
1322
1407
  // Resolve world for governance evaluation
1323
1408
  const { resolveWorld } = await Promise.resolve().then(() => __importStar(require("./worldComparison")));
1324
- const resolved = resolveWorld(payload.worldId ?? "trading");
1409
+ const resolved = resolveWorld(payload.worldId ?? "social_simulation");
1325
1410
  const world = { ...resolved.world };
1326
1411
  if (payload.stateOverrides) {
1327
1412
  const updatedVars = world.state_variables.map(sv => {
@@ -1636,42 +1721,80 @@ const INTERACTIVE_DASHBOARD_HTML = `<!DOCTYPE html>
1636
1721
  .variant-card .vbase { display: inline-block; font-size: 9px; padding: 1px 5px; background: var(--accent-bg); color: var(--accent); border-radius: 3px; margin-top: 3px; }
1637
1722
 
1638
1723
  /* RIGHT PANEL — Simulation viewer */
1639
- .viewer { display: grid; grid-template-rows: auto 1fr auto; overflow: hidden; }
1640
- .viewer-top { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: var(--border); }
1641
- .viewer-mid { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: var(--border); overflow: hidden; }
1642
- .viewer-bottom { background: var(--bg-primary); border-top: 1px solid var(--border); padding: 12px 16px; max-height: 180px; overflow-y: auto; }
1643
-
1644
- .vpanel { background: var(--bg-primary); padding: 14px; overflow-y: auto; }
1645
- .vpanel h2 { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 10px; }
1646
-
1647
- /* Metrics */
1648
- .metric-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
1649
- .metric-box { background: var(--bg-surface); border: 1px solid var(--bg-elevated); border-radius: 6px; padding: 10px; text-align: center; }
1650
- .metric-box .value { font-size: 20px; font-weight: 700; color: var(--text-primary); }
1651
- .metric-box .label { font-size: 10px; color: var(--text-muted); margin-top: 2px; }
1652
- .metric-box.good .value { color: var(--green); }
1653
- .metric-box.bad .value { color: var(--red); }
1654
- .metric-box.warn .value { color: var(--yellow); }
1655
-
1656
- /* Agent bars */
1657
- .agent-row { display: flex; align-items: center; gap: 6px; padding: 3px 0; font-size: 11px; }
1658
- .agent-name { width: 130px; color: var(--text-secondary); flex-shrink: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1659
- .impact-bar-bg { flex: 1; height: 14px; background: var(--bg-surface); border-radius: 3px; position: relative; overflow: hidden; }
1660
- .impact-bar { height: 100%; border-radius: 3px; transition: width 0.4s ease; position: absolute; top: 0; }
1661
- .impact-bar.positive { background: var(--green); right: 50%; }
1662
- .impact-bar.negative { background: var(--red); left: 50%; }
1663
- .impact-val { width: 44px; text-align: right; color: var(--text-secondary); font-size: 10px; }
1724
+ .viewer { display: grid; grid-template-rows: auto auto auto auto auto; overflow-y: auto; height: 100%; gap: 0; }
1725
+
1726
+ /* Outcome panel */
1727
+ .outcome-panel { padding: 20px 24px 16px; border-bottom: 1px solid var(--border); }
1728
+ .outcome-statement { font-size: 16px; font-weight: 600; color: var(--text-primary); line-height: 1.4; margin-bottom: 12px; }
1729
+ .outcome-empty { font-size: 13px; color: var(--text-faint); font-style: italic; }
1730
+ .confidence-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 8px; }
1731
+ .confidence-card { background: var(--bg-surface); border: 1px solid var(--bg-elevated); border-radius: 6px; padding: 8px 10px; }
1732
+ .confidence-card .cc-label { font-size: 9px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px; }
1733
+ .confidence-card .cc-value { font-size: 13px; font-weight: 600; }
1734
+ .confidence-card .cc-value.good { color: var(--green); }
1735
+ .confidence-card .cc-value.warn { color: var(--yellow); }
1736
+ .confidence-card .cc-value.bad { color: var(--red); }
1737
+ .outcome-context { font-size: 10px; color: var(--text-faint); margin-top: 6px; }
1738
+
1739
+ /* Behavior panel */
1740
+ .behavior-panel { padding: 16px 24px; border-bottom: 1px solid var(--border); }
1741
+ .behavior-panel h2 { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 10px; }
1742
+ .behavior-shifts { margin-bottom: 10px; }
1743
+ .behavior-shift-item { font-size: 12px; color: var(--text-secondary); padding: 4px 0 4px 12px; border-left: 2px solid var(--accent); line-height: 1.5; margin-bottom: 4px; }
1744
+ .behavior-empty { font-size: 12px; color: var(--text-faint); font-style: italic; }
1745
+
1746
+ .activity-toggle { font-size: 10px; color: var(--text-muted); cursor: pointer; padding: 6px 0; margin-top: 4px; }
1747
+ .activity-toggle:hover { color: var(--text-secondary); }
1748
+ .activity-timeline { display: none; max-height: 180px; overflow-y: auto; margin-top: 6px; }
1749
+ .activity-timeline.open { display: block; }
1750
+ .activity-item { font-size: 11px; color: var(--text-secondary); padding: 3px 0; border-bottom: 1px solid var(--border); }
1751
+ .activity-item:last-child { border-bottom: none; }
1752
+ .activity-agent { color: var(--blue); font-weight: 500; }
1753
+
1754
+ /* Why panel */
1755
+ .why-panel { padding: 16px 24px; border-bottom: 1px solid var(--border); }
1756
+ .why-panel h2 { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 10px; }
1757
+ .why-item { font-size: 12px; color: var(--text-secondary); padding: 4px 0 4px 12px; border-left: 2px solid var(--green); line-height: 1.5; margin-bottom: 4px; }
1758
+ .why-empty { font-size: 12px; color: var(--text-faint); font-style: italic; }
1759
+
1760
+ /* Export bar */
1761
+ .export-bar { display: flex; gap: 8px; padding: 12px 24px; border-bottom: 1px solid var(--border); background: var(--bg-secondary); }
1762
+ .export-btn { padding: 6px 14px; font-size: 11px; font-weight: 600; border: 1px solid var(--border); border-radius: 4px; background: var(--bg-surface); color: var(--text-secondary); cursor: pointer; font-family: inherit; transition: all 0.2s; }
1763
+ .export-btn:hover { background: var(--bg-elevated); color: var(--text-primary); }
1764
+ .audit-btn { margin-left: auto; color: var(--text-faint); }
1765
+
1766
+ /* Audit overlay */
1767
+ .audit-overlay { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 1000; background: rgba(0,0,0,0.7); }
1768
+ .audit-overlay.open { display: flex; align-items: center; justify-content: center; }
1769
+ .audit-modal { background: var(--bg-primary); border: 1px solid var(--border); border-radius: 8px; width: 90%; max-width: 900px; max-height: 85vh; overflow-y: auto; padding: 0; }
1770
+ .audit-header { display: flex; align-items: center; padding: 16px 20px; border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--bg-primary); z-index: 1; }
1771
+ .audit-header h2 { font-size: 14px; font-weight: 700; color: var(--text-primary); margin: 0; }
1772
+ .audit-close { margin-left: auto; padding: 4px 12px; font-size: 12px; background: none; border: 1px solid var(--border); border-radius: 4px; color: var(--text-muted); cursor: pointer; font-family: inherit; }
1773
+ .audit-close:hover { color: var(--text-primary); }
1774
+ .audit-section { padding: 16px 20px; border-bottom: 1px solid var(--border); }
1775
+ .audit-section:last-child { border-bottom: none; }
1776
+ .audit-section h3 { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 8px; }
1777
+ .audit-section .rule-card { margin-bottom: 6px; }
1778
+
1779
+ /* Print styles for PDF export */
1780
+ @media print {
1781
+ body { background: #fff !important; color: #111 !important; }
1782
+ .controls, .export-bar, .audit-overlay, .activity-toggle { display: none !important; }
1783
+ .viewer { overflow: visible !important; height: auto !important; }
1784
+ .outcome-panel, .behavior-panel, .why-panel { border: 1px solid #ddd !important; margin-bottom: 8px; border-radius: 6px; }
1785
+ .confidence-card { border: 1px solid #ddd !important; }
1786
+ * { color: #111 !important; background: #fff !important; border-color: #ddd !important; }
1787
+ .cc-value.good { color: #16a34a !important; }
1788
+ .cc-value.warn { color: #ca8a04 !important; }
1789
+ .cc-value.bad { color: #dc2626 !important; }
1790
+ }
1664
1791
  .center-line { position: absolute; left: 50%; top: 0; bottom: 0; width: 1px; background: var(--border-subtle); }
1665
1792
  .verdict { display: inline-block; padding: 1px 5px; border-radius: 3px; font-size: 9px; font-weight: 600; margin-left: 4px; }
1666
1793
  .verdict.ALLOW { background: var(--green-bg); color: var(--green); }
1667
1794
  .verdict.BLOCK { background: var(--red-bg); color: var(--red); }
1668
1795
  .verdict.PAUSE { background: var(--yellow-bg); color: var(--yellow); }
1669
1796
 
1670
- /* Chart */
1671
- .chart-container { position: relative; height: 100%; min-height: 150px; }
1672
- canvas { width: 100% !important; height: 100% !important; }
1673
-
1674
- /* Simulation Trace */
1797
+ /* Simulation Trace (audit only) */
1675
1798
  .trace-round { margin-bottom: 10px; border: 1px solid var(--border); border-radius: 4px; overflow: hidden; }
1676
1799
  .trace-round-header { display: flex; align-items: center; gap: 8px; padding: 6px 10px; background: var(--bg-surface); cursor: pointer; user-select: none; }
1677
1800
  .trace-round-header:hover { background: var(--bg-elevated); }
@@ -1781,6 +1904,60 @@ const INTERACTIVE_DASHBOARD_HTML = `<!DOCTYPE html>
1781
1904
  .integrate-endpoint { font-size: 11px; color: var(--text-secondary); margin-top: 6px; }
1782
1905
  .integrate-endpoint code { color: var(--green); background: var(--bg-surface); padding: 1px 5px; border-radius: 3px; }
1783
1906
 
1907
+ /* Language tabs for integration code */
1908
+ .lang-tabs { display: flex; gap: 2px; margin-bottom: 6px; }
1909
+ .lang-tab { padding: 4px 10px; font-size: 10px; font-weight: 600; color: var(--text-muted); background: transparent; border: 1px solid transparent; border-bottom: none; border-radius: 4px 4px 0 0; cursor: pointer; font-family: inherit; transition: all 0.2s; }
1910
+ .lang-tab:hover { color: var(--text-secondary); }
1911
+ .lang-tab.active { color: var(--accent); background: var(--bg-surface); border-color: var(--bg-elevated); }
1912
+ .lang-code-panel { display: none; }
1913
+ .lang-code-panel.active { display: block; }
1914
+ .integrate-hint { font-size: 10px; color: var(--text-muted); margin-top: 6px; line-height: 1.5; }
1915
+ .integrate-hint strong { color: var(--text-secondary); }
1916
+ .integrate-works { font-size: 10px; color: var(--text-faint); margin-top: 8px; line-height: 1.6; }
1917
+ .integrate-works strong { color: var(--text-muted); }
1918
+
1919
+ /* Framework selector tabs */
1920
+ .fw-tabs { display: flex; gap: 4px; margin-bottom: 10px; }
1921
+ .fw-tab { flex: 1; padding: 8px 6px; font-size: 10px; font-weight: 700; color: var(--text-muted); background: var(--bg-surface); border: 1px solid var(--border); border-radius: 6px; cursor: pointer; font-family: inherit; transition: all 0.2s; text-align: center; line-height: 1.3; }
1922
+ .fw-tab:hover { border-color: var(--text-muted); color: var(--text-secondary); }
1923
+ .fw-tab.active { border-color: var(--accent); color: var(--accent); background: var(--accent-bg); }
1924
+ .fw-tab .fw-sub { display: block; font-size: 8px; font-weight: 400; color: var(--text-faint); margin-top: 2px; }
1925
+ .fw-tab.active .fw-sub { color: var(--accent); opacity: 0.7; }
1926
+ .fw-panel { display: none; }
1927
+ .fw-panel.active { display: block; }
1928
+
1929
+ /* Architecture callout */
1930
+ .arch-callout { background: var(--bg-surface); border: 1px solid var(--border); border-radius: 6px; padding: 8px 10px; margin-bottom: 8px; }
1931
+ .arch-callout .arch-title { font-size: 10px; font-weight: 700; color: var(--text-secondary); margin-bottom: 4px; }
1932
+ .arch-flow { font-size: 10px; color: var(--text-muted); font-family: monospace; line-height: 1.6; }
1933
+ .arch-flow .arch-yes { color: var(--green); }
1934
+ .arch-flow .arch-no { color: var(--red); }
1935
+ .arch-warn { font-size: 9px; color: var(--red); margin-top: 6px; padding: 4px 8px; background: #2d0606; border-radius: 4px; line-height: 1.5; }
1936
+
1937
+ /* Responsive / Adaptive Panels */
1938
+ @media (max-width: 1200px) {
1939
+ .layout { grid-template-columns: 300px 1fr; }
1940
+ .controls { padding: 12px; }
1941
+ }
1942
+ @media (max-width: 960px) {
1943
+ .layout { grid-template-columns: 1fr; grid-template-rows: auto 1fr; height: auto; min-height: 100vh; }
1944
+ .controls { border-right: none; border-bottom: 1px solid var(--border); max-height: 50vh; overflow-y: auto; }
1945
+ .viewer { min-height: 60vh; }
1946
+ .viewer-top { grid-template-columns: 1fr; }
1947
+ .viewer-mid { grid-template-columns: 1fr; }
1948
+ .metric-grid { grid-template-columns: repeat(2, 1fr); }
1949
+ }
1950
+ @media (max-width: 640px) {
1951
+ .header h1 { font-size: 14px; }
1952
+ .header .sub { font-size: 10px; }
1953
+ .controls { padding: 10px; }
1954
+ .ctrl-section h3 { font-size: 10px; }
1955
+ .confidence-grid { grid-template-columns: 1fr; }
1956
+ .agent-name { width: 80px; }
1957
+ .integrate-code { font-size: 9px; padding: 6px; }
1958
+ .scenario-btn { padding: 6px; }
1959
+ }
1960
+
1784
1961
  /* Rule editor */
1785
1962
  .rule-editor { margin-top: 8px; }
1786
1963
  .rule-input { width: 100%; min-height: 60px; background: var(--bg-surface); color: var(--text-primary); border: 1px solid var(--border-subtle); padding: 8px; border-radius: 4px; font-family: inherit; font-size: 12px; resize: vertical; line-height: 1.5; }
@@ -1928,10 +2105,10 @@ const INTERACTIVE_DASHBOARD_HTML = `<!DOCTYPE html>
1928
2105
  <div id="state-vars"></div>
1929
2106
  </div>
1930
2107
 
1931
- <!-- Scenario presets -->
2108
+ <!-- Scenario presets (collapsed by default) -->
1932
2109
  <div class="ctrl-section">
1933
- <h3>Scenarios</h3>
1934
- <div id="scenario-list"></div>
2110
+ <h3 style="cursor:pointer" onclick="var el=document.getElementById('scenario-list');var a=this.querySelector('.arrow');if(el.style.display==='none'){el.style.display='';a.textContent='▼'}else{el.style.display='none';a.textContent='▶'}"><span class="arrow" style="font-size:9px;margin-right:4px">▶</span>Scenarios</h3>
2111
+ <div id="scenario-list" style="display:none"></div>
1935
2112
  </div>
1936
2113
  </div>
1937
2114
 
@@ -2067,37 +2244,207 @@ const INTERACTIVE_DASHBOARD_HTML = `<!DOCTYPE html>
2067
2244
 
2068
2245
  <!-- Saved variants -->
2069
2246
  <div class="ctrl-section" style="margin-top:16px">
2070
- <h3>Saved Variants</h3>
2247
+ <h3>Your Worlds</h3>
2071
2248
  <div id="variant-list"><div style="font-size:11px;color:#333">No saved variants yet</div></div>
2072
2249
  </div>
2073
2250
 
2074
2251
  <!-- Integration Quick-Start -->
2075
2252
  <div class="ctrl-section" style="margin-top:16px">
2076
- <h3>Integrate Your Simulator</h3>
2077
- <div class="integrate-section">
2078
- <h4>Connect in 3 lines</h4>
2079
- <div class="integrate-code"><span class="kw">from</span> neuroverse_bridge <span class="kw">import</span> evaluate
2253
+ <h3>Govern Any Agent System</h3>
2254
+
2255
+ <div class="arch-callout">
2256
+ <div class="arch-title">Pre-Execution Enforcement</div>
2257
+ <div class="arch-flow">
2258
+ Agent decides → <span class="arch-yes">Governance evaluates</span> → <span class="arch-yes">THEN executes</span>
2259
+ </div>
2260
+ <div class="arch-warn">Governance MUST happen BEFORE execution. Post-execution = audit only, not control.</div>
2261
+ </div>
2262
+
2263
+ <div class="fw-tabs">
2264
+ <button class="fw-tab active" onclick="switchFw('generic',this)">Generic<span class="fw-sub">any agent</span></button>
2265
+ <button class="fw-tab" onclick="switchFw('scienceclaw',this)">ScienceClaw<span class="fw-sub">research</span></button>
2266
+ <button class="fw-tab" onclick="switchFw('mirofish',this)">MiroFish<span class="fw-sub">OASIS</span></button>
2267
+ <button class="fw-tab" onclick="switchFw('langchain',this)">LangChain<span class="fw-sub">tools</span></button>
2268
+ </div>
2080
2269
 
2270
+ <div class="fw-panel active" id="fw-generic">
2271
+ <div class="integrate-section">
2272
+ <h4>Insert before your agent executes</h4>
2273
+ <div class="lang-tabs">
2274
+ <button class="lang-tab active" onclick="switchLang('generic-py',this)">Python</button>
2275
+ <button class="lang-tab" onclick="switchLang('generic-js',this)">JavaScript</button>
2276
+ <button class="lang-tab" onclick="switchLang('generic-curl',this)">cURL</button>
2277
+ </div>
2278
+ <div class="lang-code-panel active" id="lang-generic-py"><div class="integrate-code"><span class="kw">import</span> requests
2279
+
2280
+ <span class="kw">for</span> agent <span class="kw">in</span> agents:
2281
+ raw_action = agent.decide()
2282
+
2283
+ <span class="kw">try</span>:
2284
+ verdict = requests.post(
2285
+ <span class="str">"http://localhost:3456/api/evaluate"</span>,
2286
+ json={<span class="str">"actor"</span>: agent.id,
2287
+ <span class="str">"action"</span>: raw_action,
2288
+ <span class="str">"world"</span>: <span class="str">"trading"</span>},
2289
+ timeout=0.5
2290
+ ).json()
2291
+ <span class="kw">except</span>:
2292
+ verdict = {<span class="str">"decision"</span>: <span class="str">"ALLOW"</span>}
2293
+
2294
+ <span class="kw">if</span> verdict[<span class="str">"decision"</span>] == <span class="str">"BLOCK"</span>:
2295
+ <span class="kw">continue</span> <span class="comment"># skip this action entirely</span>
2296
+ <span class="kw">elif</span> verdict[<span class="str">"decision"</span>] == <span class="str">"MODIFY"</span>:
2297
+ raw_action = verdict[<span class="str">"modified_action"</span>]
2298
+
2299
+ environment.apply(agent, raw_action)</div></div>
2300
+ <div class="lang-code-panel" id="lang-generic-js"><div class="integrate-code"><span class="kw">for</span> (<span class="kw">const</span> agent <span class="kw">of</span> agents) {
2301
+ <span class="kw">let</span> action = agent.decide()
2302
+
2303
+ <span class="kw">const</span> verdict = <span class="kw">await</span> fetch(
2304
+ <span class="str">"http://localhost:3456/api/evaluate"</span>, {
2305
+ method: <span class="str">"POST"</span>,
2306
+ headers: {<span class="str">"Content-Type"</span>: <span class="str">"application/json"</span>},
2307
+ body: JSON.stringify({
2308
+ actor: agent.id, action, world: <span class="str">"trading"</span>
2309
+ })
2310
+ }).then(r => r.json())
2311
+
2312
+ <span class="kw">if</span> (verdict.decision === <span class="str">"BLOCK"</span>) <span class="kw">continue</span>
2313
+ <span class="kw">if</span> (verdict.decision === <span class="str">"MODIFY"</span>)
2314
+ action = verdict.modified_action
2315
+
2316
+ environment.apply(agent, action)
2317
+ }</div></div>
2318
+ <div class="lang-code-panel" id="lang-generic-curl"><div class="integrate-code">curl -X POST http://localhost:3456/api/evaluate \
2319
+ -H <span class="str">"Content-Type: application/json"</span> \
2320
+ -d <span class="str">'{"actor":"agent_1","action":"panic_sell"}'</span>
2321
+
2322
+ <span class="comment"># Returns: {"decision":"BLOCK","reason":"..."}</span>
2323
+ <span class="comment"># Watch it appear in the dashboard →</span></div></div>
2324
+ </div>
2325
+ </div>
2326
+
2327
+ <div class="fw-panel" id="fw-scienceclaw">
2328
+ <div class="integrate-section">
2329
+ <h4>Detect action type, then evaluate</h4>
2330
+ <div style="font-size:10px;color:var(--yellow);margin-bottom:6px;line-height:1.4">
2331
+ Don't hardcode <code style="background:var(--bg-surface);padding:1px 4px;border-radius:2px">action = "post"</code> — detect intent from agent output so rules can differentiate.
2332
+ </div>
2333
+ <div class="integrate-code"><span class="kw">from</span> neuroverse_bridge <span class="kw">import</span> evaluate, detect_action_type
2334
+
2335
+ <span class="comment"># Step 1: Agent generates content</span>
2336
+ results = generator.run_pubmed_search(topic)
2337
+ output = generator.generate_content(topic, results)
2338
+
2339
+ <span class="comment"># Step 2: Detect what kind of action this is</span>
2340
+ action = detect_action_type(output[<span class="str">"content"</span>])
2341
+ <span class="comment"># → "analyze", "publish", "cite", "recommend"</span>
2342
+
2343
+ <span class="comment"># Step 3: Evaluate BEFORE executing</span>
2081
2344
  verdict = evaluate(
2082
- actor=<span class="str">"agent_1"</span>,
2083
- action=<span class="str">"panic_sell"</span>,
2084
- world=<span class="str">"trading"</span>
2345
+ actor=<span class="str">"Harry"</span>,
2346
+ action=action,
2347
+ world=<span class="str">"research"</span>
2085
2348
  )
2086
2349
 
2087
- <span class="kw">if</span> verdict[<span class="str">"decision"</span>] == <span class="str">"BLOCK"</span>:
2088
- action = <span class="str">"hold"</span> <span class="comment"># adapted</span></div>
2089
- <div class="integrate-endpoint">
2090
- Endpoint: <code id="integrate-url">POST /api/evaluate</code>
2350
+ <span class="comment"># Step 4: Only execute if allowed</span>
2351
+ <span class="kw">if</span> verdict[<span class="str">"decision"</span>] != <span class="str">"BLOCK"</span>:
2352
+ generator.post_to_infinite(output)</div>
2353
+ <div style="font-size:9px;color:var(--text-muted);margin-top:6px;line-height:1.5">
2354
+ See <code style="background:var(--bg-surface);padding:1px 4px;border-radius:2px">bridge/scienceclaw_governed.py</code> for full example
2355
+ </div>
2091
2356
  </div>
2092
- <div style="font-size:10px;color:#444;margin-top:6px">
2093
- Fail-open · 500ms timeout · Stateless
2357
+ </div>
2358
+
2359
+ <div class="fw-panel" id="fw-mirofish">
2360
+ <div class="integrate-section">
2361
+ <h4>Replace the dict comprehension</h4>
2362
+ <div style="font-size:10px;color:var(--red);margin-bottom:6px;line-height:1.4">
2363
+ MiroFish executes inside <code style="background:var(--bg-surface);padding:1px 4px;border-radius:2px">env.step()</code> — governance MUST happen before that call.
2364
+ </div>
2365
+ <div class="integrate-code" style="margin-bottom:6px;border-left:3px solid var(--red);opacity:0.6"><span class="comment"># ❌ BEFORE (opaque — no governance possible)</span>
2366
+ actions = {agent: LLMAction()
2367
+ <span class="kw">for</span> _, agent <span class="kw">in</span> active_agents}
2368
+ <span class="kw">await</span> result.env.step(actions)</div>
2369
+ <div class="integrate-code" style="border-left:3px solid var(--green)"><span class="comment"># ✅ AFTER (expand the loop, insert governance)</span>
2370
+ <span class="kw">import</span> requests
2371
+
2372
+ actions = {}
2373
+ <span class="kw">for</span> _, agent <span class="kw">in</span> active_agents:
2374
+ raw_action = LLMAction()
2375
+
2376
+ <span class="kw">try</span>:
2377
+ verdict = requests.post(
2378
+ <span class="str">"http://localhost:3456/api/evaluate"</span>,
2379
+ json={
2380
+ <span class="str">"actor"</span>: getattr(agent, <span class="str">"id"</span>, str(agent)),
2381
+ <span class="str">"action"</span>: str(raw_action),
2382
+ <span class="str">"world"</span>: <span class="str">"social_media"</span>
2383
+ },
2384
+ timeout=0.5
2385
+ ).json()
2386
+ <span class="kw">except</span>:
2387
+ verdict = {<span class="str">"decision"</span>: <span class="str">"ALLOW"</span>}
2388
+
2389
+ <span class="kw">if</span> verdict[<span class="str">"decision"</span>] == <span class="str">"BLOCK"</span>:
2390
+ <span class="kw">continue</span>
2391
+ <span class="kw">elif</span> verdict[<span class="str">"decision"</span>] == <span class="str">"MODIFY"</span>:
2392
+ raw_action = verdict.get(<span class="str">"modified_action"</span>, raw_action)
2393
+
2394
+ actions[agent] = raw_action
2395
+
2396
+ <span class="kw">await</span> result.env.step(actions)</div>
2397
+ <div style="font-size:9px;color:var(--text-muted);margin-top:6px;line-height:1.5">
2398
+ Apply to all 3 scripts: <code style="background:var(--bg-surface);padding:1px 4px;border-radius:2px">run_twitter_simulation.py</code>
2399
+ <code style="background:var(--bg-surface);padding:1px 4px;border-radius:2px">run_reddit_simulation.py</code>
2400
+ <code style="background:var(--bg-surface);padding:1px 4px;border-radius:2px">run_parallel_simulation.py</code>
2401
+ </div>
2402
+ </div>
2403
+ </div>
2404
+
2405
+ <div class="fw-panel" id="fw-langchain">
2406
+ <div class="integrate-section">
2407
+ <h4>Wrap your tool or agent executor</h4>
2408
+ <div class="integrate-code"><span class="kw">import</span> requests
2409
+ <span class="kw">from</span> langchain.tools <span class="kw">import</span> tool
2410
+
2411
+ <span class="kw">def</span> governed(fn, world=<span class="str">"trading"</span>):
2412
+ <span class="kw">def</span> wrapper(*args, **kwargs):
2413
+ action = fn.__name__
2414
+ <span class="kw">try</span>:
2415
+ v = requests.post(
2416
+ <span class="str">"http://localhost:3456/api/evaluate"</span>,
2417
+ json={<span class="str">"actor"</span>: <span class="str">"langchain"</span>,
2418
+ <span class="str">"action"</span>: action,
2419
+ <span class="str">"world"</span>: world},
2420
+ timeout=0.5
2421
+ ).json()
2422
+ <span class="kw">except</span>:
2423
+ v = {<span class="str">"decision"</span>: <span class="str">"ALLOW"</span>}
2424
+ <span class="kw">if</span> v[<span class="str">"decision"</span>] == <span class="str">"BLOCK"</span>:
2425
+ <span class="kw">return</span> f<span class="str">"Blocked: {v['reason']}"</span>
2426
+ <span class="kw">return</span> fn(*args, **kwargs)
2427
+ <span class="kw">return</span> wrapper
2428
+
2429
+ <span class="comment"># Usage: wrap any tool</span>
2430
+ @tool
2431
+ @governed
2432
+ <span class="kw">def</span> execute_trade(ticker, amount):
2433
+ ...</div>
2094
2434
  </div>
2095
- <div style="margin-top:8px;font-size:10px">
2096
- <span style="display:inline-block;padding:2px 6px;background:#2d0606;color:#f87171;border-radius:3px;margin-right:3px">BLOCK</span> replaced
2097
- <span style="display:inline-block;padding:2px 6px;background:#2d2006;color:#fbbf24;border-radius:3px;margin-right:3px;margin-left:4px">MODIFY</span> constrained
2435
+ </div>
2436
+
2437
+ <div style="margin-top:8px">
2438
+ <div class="integrate-hint">
2439
+ <span style="display:inline-block;padding:2px 6px;background:#2d0606;color:#f87171;border-radius:3px;margin-right:3px">BLOCK</span> stopped
2440
+ <span style="display:inline-block;padding:2px 6px;background:#2d2006;color:#fbbf24;border-radius:3px;margin-right:3px;margin-left:4px">MODIFY</span> adjusted
2098
2441
  <span style="display:inline-block;padding:2px 6px;background:#052e16;color:#4ade80;border-radius:3px;margin-left:4px">ALLOW</span> proceeds
2099
2442
  </div>
2443
+ <div style="font-size:10px;color:var(--text-faint);margin-top:8px;border-top:1px solid var(--border);padding-top:6px">
2444
+ The world file controls everything. Same rules govern simulated and live systems.
2445
+ </div>
2100
2446
  </div>
2447
+ </div>
2101
2448
 
2102
2449
  <!-- Session Report Panel -->
2103
2450
  <div class="ctrl-section" id="session-panel">
@@ -2121,86 +2468,308 @@ verdict = evaluate(
2121
2468
 
2122
2469
  <!-- RIGHT: VIEWER -->
2123
2470
  <div class="viewer">
2124
- <div class="viewer-top">
2125
- <div class="vpanel">
2126
- <h2>Live Metrics</h2>
2127
- <div class="metric-grid">
2128
- <div class="metric-box"><div class="value" id="m-stability">--</div><div class="label">Stability</div></div>
2129
- <div class="metric-box"><div class="value" id="m-volatility">--</div><div class="label">Volatility</div></div>
2130
- <div class="metric-box"><div class="value" id="m-round">--</div><div class="label">Round</div></div>
2131
- <div class="metric-box"><div class="value" id="m-interventions">0</div><div class="label">Interventions</div></div>
2132
- </div>
2471
+
2472
+ <!-- LAYER 1: OUTCOME -->
2473
+ <div class="outcome-panel" id="outcome-panel">
2474
+ <div class="outcome-statement" id="outcome-statement">
2475
+ <span class="outcome-empty">Run a simulation or connect an agent to see outcomes</span>
2133
2476
  </div>
2134
- <div class="vpanel">
2135
- <h2>World Rules Active</h2>
2136
- <div id="active-invariants"></div>
2477
+ <div class="confidence-grid" id="confidence-grid" style="display:none">
2478
+ <div class="confidence-card">
2479
+ <div class="cc-label">Conclusion Strength</div>
2480
+ <div class="cc-value" id="cc-strength">--</div>
2481
+ </div>
2482
+ <div class="confidence-card">
2483
+ <div class="cc-label">Evidence Quality</div>
2484
+ <div class="cc-value" id="cc-evidence">--</div>
2485
+ </div>
2486
+ <div class="confidence-card">
2487
+ <div class="cc-label">Risk of Error</div>
2488
+ <div class="cc-value" id="cc-risk">--</div>
2489
+ </div>
2137
2490
  </div>
2491
+ <div class="outcome-context" id="outcome-context"></div>
2138
2492
  </div>
2139
2493
 
2140
- <div class="viewer-mid">
2141
- <div class="vpanel" id="agents-panel">
2142
- <h2>Agent Impacts</h2>
2143
- <div id="agents">
2144
- <div class="empty-state"><div class="icon">&gt;_</div><div class="msg">Configure world and run simulation</div><div class="hint">Adjust rules on the left, then press Run</div></div>
2145
- </div>
2494
+ <!-- LAYER 2: BEHAVIOR -->
2495
+ <div class="behavior-panel" id="behavior-panel-v2">
2496
+ <h2>What Agents Did</h2>
2497
+ <div class="behavior-shifts" id="behavior-shifts">
2498
+ <div class="behavior-empty">Waiting for agent actions</div>
2146
2499
  </div>
2147
- <div class="vpanel">
2148
- <h2>Impact Timeline</h2>
2149
- <div class="chart-container"><canvas id="chart"></canvas></div>
2500
+ <div class="activity-toggle" id="activity-toggle" onclick="document.getElementById('activity-timeline').classList.toggle('open');this.querySelector('.arrow').textContent=document.getElementById('activity-timeline').classList.contains('open')?'▼':'▶'" style="display:none">
2501
+ <span class="arrow">▶</span> See activity timeline
2150
2502
  </div>
2503
+ <div class="activity-timeline" id="activity-timeline"></div>
2151
2504
  </div>
2152
2505
 
2153
- <!-- System Shift Card — the demo moment -->
2154
- <div id="system-shift" class="system-shift">
2155
- <div class="ss-header">
2156
- <div class="ss-icon"></div>
2157
- <span class="ss-title">System Shift</span>
2158
- </div>
2159
- <div class="ss-rule" id="ss-rule"></div>
2160
- <div class="ss-scale" id="ss-scale"></div>
2161
- <div class="ss-flow">
2162
- <span>Rule</span><span class="ss-flow-arrow">→</span>
2163
- <span>Behavioral Shift</span><span class="ss-flow-arrow">→</span>
2164
- <span>Emergent Pattern</span><span class="ss-flow-arrow">→</span>
2165
- <span>System Outcome</span>
2166
- </div>
2167
- <div class="ss-body">
2168
- <div class="ss-section">
2169
- <div class="ss-section-label">Behavioral Shift</div>
2170
- <div class="ss-adapt-rate" id="ss-adapt-rate"></div>
2171
- <div class="ss-adapt-desc" id="ss-adapt-desc"></div>
2172
- <div id="ss-shifts"></div>
2173
- </div>
2174
- <div class="ss-section">
2175
- <div class="ss-section-label">What Emerged</div>
2176
- <div id="ss-patterns"></div>
2177
- </div>
2178
- <div class="ss-section">
2179
- <div class="ss-section-label">System Outcome</div>
2180
- <div id="ss-impacts"></div>
2181
- </div>
2182
- <div class="ss-section">
2183
- <div class="ss-section-label">What Actually Happened</div>
2184
- <div class="ss-narrative" id="ss-narrative"></div>
2185
- </div>
2186
- </div>
2187
- <button class="ss-raw-toggle" id="ss-raw-toggle">
2188
- <span class="arrow">▶</span> View raw detail
2189
- </button>
2190
- <div class="ss-raw-detail" id="ss-raw-detail">
2191
- <div class="ss-raw-list" id="ss-raw-list"></div>
2506
+ <!-- LAYER 3: WHY -->
2507
+ <div class="why-panel" id="why-panel">
2508
+ <h2>Why This Happened</h2>
2509
+ <div id="why-content">
2510
+ <div class="why-empty">Causation analysis appears after agent actions are evaluated</div>
2192
2511
  </div>
2193
2512
  </div>
2194
2513
 
2195
- <div class="viewer-bottom">
2196
- <h2 style="font-size:11px;color:#555;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px">Simulation Trace <span id="trace-source" style="color:#4ade80;font-weight:600;margin-left:6px"></span> <span style="color:#333;font-weight:400">— Events → Agents → Rules → Outcomes</span></h2>
2514
+ <!-- LAYER 4: EXPORT -->
2515
+ <div class="export-bar">
2516
+ <button class="export-btn" onclick="exportPDF()">Download PDF</button>
2517
+ <button class="export-btn" onclick="exportCSV()">Export CSV</button>
2518
+ <button class="export-btn" onclick="copyShareSummary()">Copy Summary</button>
2519
+ <button class="export-btn audit-btn" onclick="openAudit()">Audit Trail</button>
2520
+ </div>
2521
+
2522
+ <!-- Hidden elements for data tracking (not displayed) -->
2523
+ <div style="display:none">
2524
+ <span id="m-stability">--</span>
2525
+ <span id="m-volatility">--</span>
2526
+ <span id="m-round">--</span>
2527
+ <span id="m-interventions">0</span>
2528
+ <span id="trace-source"></span>
2529
+ <div id="agents"></div>
2530
+ <div id="active-invariants"></div>
2197
2531
  <div id="log"></div>
2198
2532
  </div>
2199
2533
  </div>
2534
+
2535
+ <!-- LAYER 5: AUDIT OVERLAY (separate from dashboard) -->
2536
+ <div class="audit-overlay" id="audit-overlay" onclick="if(event.target===this)closeAudit()">
2537
+ <div class="audit-modal">
2538
+ <div class="audit-header">
2539
+ <h2>Audit Trail</h2>
2540
+ <button class="audit-close" onclick="closeAudit()">Close</button>
2541
+ </div>
2542
+ <div class="audit-section" id="audit-rules">
2543
+ <h3>Active Rules</h3>
2544
+ <div id="audit-rules-content"></div>
2545
+ </div>
2546
+ <div class="audit-section" id="audit-verdicts">
2547
+ <h3>Verdict Log</h3>
2548
+ <div id="audit-verdicts-content"></div>
2549
+ </div>
2550
+ <div class="audit-section" id="audit-trace">
2551
+ <h3>Detailed Trace</h3>
2552
+ <div id="audit-trace-content"></div>
2553
+ </div>
2554
+ </div>
2555
+ </div>
2200
2556
  </div>
2201
2557
 
2202
- <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js"><\/script>
2203
2558
  <script>
2559
+ // ============================================
2560
+ // LANGUAGE & FRAMEWORK TABS
2561
+ // ============================================
2562
+ function switchLang(lang, btn) {
2563
+ const container = btn.closest('.integrate-section') || document;
2564
+ container.querySelectorAll('.lang-tab').forEach(t => t.classList.remove('active'));
2565
+ container.querySelectorAll('.lang-code-panel').forEach(p => p.classList.remove('active'));
2566
+ btn.classList.add('active');
2567
+ const panel = document.getElementById('lang-' + lang);
2568
+ if (panel) panel.classList.add('active');
2569
+ }
2570
+
2571
+ function switchFw(fw, btn) {
2572
+ document.querySelectorAll('.fw-tab').forEach(t => t.classList.remove('active'));
2573
+ document.querySelectorAll('.fw-panel').forEach(p => p.classList.remove('active'));
2574
+ btn.classList.add('active');
2575
+ const panel = document.getElementById('fw-' + fw);
2576
+ if (panel) panel.classList.add('active');
2577
+ }
2578
+
2579
+ // ============================================
2580
+ // OUTCOME INTELLIGENCE LAYER
2581
+ // ============================================
2582
+
2583
+ function generateOutcome(stability, volatility, shiftData, worldThesis) {
2584
+ var st = stability || 0;
2585
+ var vol = volatility || 0;
2586
+ var blockRatio = shiftData.total > 0 ? shiftData.blocks / shiftData.total : 0;
2587
+ var thesis = worldThesis || '';
2588
+
2589
+ // Determine outcome direction + dominant behavior (state + what agents did)
2590
+ var direction = '';
2591
+ var behavior = '';
2592
+ if (st > 0.7 && vol < 0.3) {
2593
+ direction = 'stabilized';
2594
+ behavior = 'agents shifted toward safer positions';
2595
+ } else if (st > 0.7 && vol >= 0.3) {
2596
+ direction = 'held under pressure';
2597
+ behavior = 'agents maintained cautious strategies despite volatility';
2598
+ } else if (st > 0.4 && vol < 0.5) {
2599
+ direction = 'partially converged';
2600
+ behavior = 'agents split between aggressive and conservative approaches';
2601
+ } else if (st <= 0.4 && vol >= 0.5) {
2602
+ direction = 'fragmented';
2603
+ behavior = 'agents competed with conflicting strategies';
2604
+ } else if (st <= 0.4) {
2605
+ direction = 'remains uncertain';
2606
+ behavior = 'agents failed to find a dominant strategy';
2607
+ } else {
2608
+ direction = 'showed mixed results';
2609
+ behavior = 'agents oscillated between risk-taking and caution';
2610
+ }
2611
+
2612
+ // Enrich with world context if available
2613
+ if (thesis) {
2614
+ var nouns = thesis.match(/\b(market|research|supply|trade|financial|climate|regulatory|investment|innovation|security)\b/i);
2615
+ if (nouns) {
2616
+ var subject = nouns[1].charAt(0).toUpperCase() + nouns[1].slice(1).toLowerCase();
2617
+ return subject + ' ' + direction + ' as ' + behavior;
2618
+ }
2619
+ }
2620
+
2621
+ // Build behavior-enriched statement — always state + dominant behavior
2622
+ if (blockRatio > 0.4 && st > 0.6) return 'System ' + direction + ' after agents abandoned high-risk strategies';
2623
+ if (blockRatio > 0.2) return 'Outcome ' + direction + ' as ' + behavior;
2624
+ if (shiftData.patterns && shiftData.patterns.length > 0) return 'Outcome ' + direction + ' — ' + behavior;
2625
+ return 'Outcome ' + direction + ' as ' + behavior;
2626
+ }
2627
+
2628
+ function computeConfidence(stability, volatility, interventions, total) {
2629
+ var interventionRate = total > 0 ? interventions / total : 0;
2630
+ var score = stability * 0.5 + (1 - volatility) * 0.3 + (1 - interventionRate) * 0.2;
2631
+
2632
+ var strength, evidence, risk, strengthCls, evidenceCls, riskCls;
2633
+
2634
+ if (score > 0.75) {
2635
+ strength = 'Strong'; strengthCls = 'good';
2636
+ evidence = 'Solid'; evidenceCls = 'good';
2637
+ risk = 'Low'; riskCls = 'good';
2638
+ } else if (score > 0.55) {
2639
+ strength = 'Moderate'; strengthCls = 'warn';
2640
+ evidence = stability > 0.6 ? 'Solid' : 'Mixed'; evidenceCls = stability > 0.6 ? 'good' : 'warn';
2641
+ risk = volatility > 0.4 ? 'Elevated' : 'Moderate'; riskCls = volatility > 0.4 ? 'warn' : 'warn';
2642
+ } else if (score > 0.35) {
2643
+ strength = 'Moderate'; strengthCls = 'warn';
2644
+ evidence = 'Mixed'; evidenceCls = 'warn';
2645
+ risk = 'Elevated'; riskCls = 'bad';
2646
+ } else {
2647
+ strength = 'Weak'; strengthCls = 'bad';
2648
+ evidence = 'Thin'; evidenceCls = 'bad';
2649
+ risk = 'High'; riskCls = 'bad';
2650
+ }
2651
+
2652
+ return { score: score, strength: strength, strengthCls: strengthCls, evidence: evidence, evidenceCls: evidenceCls, risk: risk, riskCls: riskCls };
2653
+ }
2654
+
2655
+ function translateBehaviorNarrative(reaction, verdict) {
2656
+ // Convert verdict+action into before → after narrative — no system words
2657
+ var status = verdict ? verdict.status : 'ALLOW';
2658
+ var action = reaction || 'acted';
2659
+ if (status === 'BLOCK') {
2660
+ return 'abandoned ' + action + ' and switched to a safer strategy';
2661
+ }
2662
+ if (status === 'MODIFY' || status === 'PAUSE') {
2663
+ return 'scaled back ' + action + ' after early resistance';
2664
+ }
2665
+ return action;
2666
+ }
2667
+
2668
+ function generateBehaviorShifts(shiftData, bLog) {
2669
+ // Quantified behavioral shifts in human language
2670
+ var sentences = [];
2671
+ var total = shiftData.total || bLog.length || 0;
2672
+ if (total === 0) return sentences;
2673
+
2674
+ // Group by what agents actually did — always anchor in before → after
2675
+ var keys = Object.keys(shiftData.shifts || {}).sort(function(a, b) { return shiftData.shifts[b] - shiftData.shifts[a]; });
2676
+ if (keys.length > 0 && total > 0) {
2677
+ keys.slice(0, 3).forEach(function(k) {
2678
+ var parts = k.split(': ');
2679
+ var action = parts[1] || parts[0];
2680
+ var count = shiftData.shifts[k];
2681
+ var pct = Math.round((count / total) * 100);
2682
+ // before → after: what they tried → what they did instead
2683
+ if (parts[0] === 'BLOCK') {
2684
+ sentences.push(pct + '% of agents (' + count + ') shifted from ' + action + ' to conservative strategies');
2685
+ } else {
2686
+ sentences.push(pct + '% of agents (' + count + ') reduced ' + action + ' after initial attempts failed');
2687
+ }
2688
+ });
2689
+ }
2690
+
2691
+ // Fallback from behavioral log — still before → after
2692
+ if (sentences.length === 0 && bLog.length > 0) {
2693
+ var blocked = bLog.filter(function(e) { return e.status === 'BLOCK'; }).length;
2694
+ var modified = bLog.filter(function(e) { return e.status === 'MODIFY' || e.status === 'PAUSE'; }).length;
2695
+ var proceeded = bLog.length - blocked - modified;
2696
+
2697
+ if (blocked > 0) {
2698
+ var bPct = Math.round((blocked / bLog.length) * 100);
2699
+ sentences.push(bPct + '% of agents (' + blocked + ') shifted from aggressive to conservative strategies');
2700
+ }
2701
+ if (modified > 0) {
2702
+ var mPct = Math.round((modified / bLog.length) * 100);
2703
+ sentences.push(mPct + '% of agents (' + modified + ') reduced position size after initial attempts failed');
2704
+ }
2705
+ if (proceeded > 0 && (blocked > 0 || modified > 0)) {
2706
+ var pPct = Math.round((proceeded / bLog.length) * 100);
2707
+ sentences.push(pPct + '% of agents (' + proceeded + ') maintained their original strategy throughout');
2708
+ }
2709
+ }
2710
+
2711
+ // Volatility insight — before → after framing
2712
+ if (shiftData.governedVol < shiftData.baselineVol && shiftData.baselineVol > 0) {
2713
+ var volDrop = Math.round((1 - shiftData.governedVol / shiftData.baselineVol) * 100);
2714
+ sentences.push('Uncertainty dropped ' + volDrop + '% as agents moved from exploration to caution');
2715
+ }
2716
+
2717
+ // Add emergent pattern insights
2718
+ if (shiftData.patterns && shiftData.patterns.length > 0) {
2719
+ shiftData.patterns.slice(0, 2).forEach(function(p) {
2720
+ sentences.push(p);
2721
+ });
2722
+ }
2723
+
2724
+ return sentences;
2725
+ }
2726
+
2727
+ function generateCausation(shiftData, bLog) {
2728
+ // Pure causation — why behavior changed — no governance terminology
2729
+ var causes = [];
2730
+ var total = shiftData.total || bLog.length || 0;
2731
+ if (total === 0) return causes;
2732
+
2733
+ var blockRatio = shiftData.blocks / total;
2734
+
2735
+ // No system words (feedback, evaluation, validation, review)
2736
+ // Only agent experience (failed attempts, uncertainty, risk, delay)
2737
+ if (blockRatio > 0.4) {
2738
+ causes.push('Early aggressive attempts failed, forcing agents to rethink their strategy');
2739
+ } else if (blockRatio > 0.1) {
2740
+ causes.push('Some agents hit unexpected resistance and pulled back');
2741
+ }
2742
+
2743
+ if (shiftData.governedVol < shiftData.baselineVol) {
2744
+ causes.push('Uncertainty dropped as agents stopped experimenting and committed to safer positions');
2745
+ }
2746
+
2747
+ if (shiftData.patterns && shiftData.patterns.length > 0) {
2748
+ if (shiftData.patterns.some(function(p) { return p.toLowerCase().includes('hold') || p.toLowerCase().includes('caution'); })) {
2749
+ causes.push('Risk became too visible — agents chose to wait rather than act');
2750
+ }
2751
+ if (shiftData.patterns.some(function(p) { return p.toLowerCase().includes('coordination') || p.toLowerCase().includes('coordinated'); })) {
2752
+ causes.push('Agents independently converged on similar strategies under shared pressure');
2753
+ }
2754
+ }
2755
+
2756
+ // Detect adaptation pattern from behavior log
2757
+ if (bLog.length >= 3) {
2758
+ var recent = bLog.slice(-6);
2759
+ var earlyBlocks = recent.slice(0, 3).filter(function(e) { return e.status === 'BLOCK'; }).length;
2760
+ var lateAllows = recent.slice(-3).filter(function(e) { return e.status === 'ALLOW'; }).length;
2761
+ if (earlyBlocks >= 2 && lateAllows >= 2) {
2762
+ causes.push('Agents became more cautious after early attempts failed');
2763
+ }
2764
+ }
2765
+
2766
+ if (causes.length === 0) {
2767
+ causes.push('Agents held steady — no major shifts in strategy throughout the simulation');
2768
+ }
2769
+
2770
+ return causes;
2771
+ }
2772
+
2204
2773
  // ============================================
2205
2774
  // STATE
2206
2775
  // ============================================
@@ -2210,12 +2779,12 @@ let narratives = {};
2210
2779
  let currentWorld = null;
2211
2780
  let injectedEvents = [];
2212
2781
  let totalInterventions = 0;
2213
- let baselineImpacts = [];
2214
- let governedImpacts = [];
2215
- let chartLabels = [];
2216
- let chart = null;
2782
+ let totalActions = 0;
2217
2783
  let narrativeEventsByRound = {}; // { round: [{ id, headline, severity }] }
2218
2784
  let ruleImpactTracker = {}; // { ruleId: { blocks: N, label: string } }
2785
+ let behaviorLog = []; // { agent, action, status, reason, ts }
2786
+ let latestStability = 0;
2787
+ let latestVolatility = 0;
2219
2788
 
2220
2789
  const statusEl = document.getElementById('status');
2221
2790
  const worldSelect = document.getElementById('world-select');
@@ -2234,6 +2803,79 @@ const activeInvEl = document.getElementById('active-invariants');
2234
2803
  const engineSelect = document.getElementById('engine-select');
2235
2804
  const engineStatusEl = document.getElementById('engine-status');
2236
2805
  const traceSourceEl = document.getElementById('trace-source');
2806
+ const outcomeStatementEl = document.getElementById('outcome-statement');
2807
+ const confidenceGridEl = document.getElementById('confidence-grid');
2808
+ const outcomeContextEl = document.getElementById('outcome-context');
2809
+ const behaviorShiftsEl = document.getElementById('behavior-shifts');
2810
+ const activityTimelineEl = document.getElementById('activity-timeline');
2811
+ const activityToggleEl = document.getElementById('activity-toggle');
2812
+ const whyContentEl = document.getElementById('why-content');
2813
+
2814
+ // Audit + Export
2815
+ function openAudit() {
2816
+ // Populate audit content from current data
2817
+ var rulesEl = document.getElementById('audit-rules-content');
2818
+ rulesEl.innerHTML = activeInvEl.innerHTML || '<div style="font-size:11px;color:#666">No rules loaded</div>';
2819
+
2820
+ var verdictsEl = document.getElementById('audit-verdicts-content');
2821
+ var vHtml = '';
2822
+ behaviorLog.forEach(function(e) {
2823
+ vHtml += '<div style="font-size:10px;padding:2px 0;display:flex;gap:8px;border-bottom:1px solid var(--border)">' +
2824
+ '<span style="color:var(--blue);min-width:100px">' + e.agent + '</span>' +
2825
+ '<span style="flex:1;color:var(--text-secondary)">' + e.action + '</span>' +
2826
+ '<span style="font-size:9px;font-weight:600;padding:0 4px;border-radius:2px;' +
2827
+ (e.status === 'BLOCK' ? 'background:var(--red-bg);color:var(--red)' : e.status === 'ALLOW' ? 'background:var(--green-bg);color:var(--green)' : 'background:var(--yellow-bg);color:var(--yellow)') + '">' + e.status + '</span>' +
2828
+ (e.reason ? '<span style="font-size:9px;color:var(--text-faint)">' + e.reason + '</span>' : '') +
2829
+ '</div>';
2830
+ });
2831
+ verdictsEl.innerHTML = vHtml || '<div style="font-size:11px;color:#666">No verdict data</div>';
2832
+
2833
+ var traceEl = document.getElementById('audit-trace-content');
2834
+ traceEl.innerHTML = logEl.innerHTML || '<div style="font-size:11px;color:#666">No trace data</div>';
2835
+
2836
+ document.getElementById('audit-overlay').classList.add('open');
2837
+ }
2838
+
2839
+ function closeAudit() {
2840
+ document.getElementById('audit-overlay').classList.remove('open');
2841
+ }
2842
+
2843
+ function exportPDF() {
2844
+ window.print();
2845
+ }
2846
+
2847
+ function exportCSV() {
2848
+ if (behaviorLog.length === 0) { alert('No data to export yet'); return; }
2849
+ var csv = 'Agent,Action,Behavior,Round\\n';
2850
+ behaviorLog.forEach(function(e) {
2851
+ var behavior = translateBehaviorNarrative(e.action, { status: e.status });
2852
+ csv += '"' + e.agent + '","' + e.action + '","' + behavior + '","' + (e.round || '') + '"\\n';
2853
+ });
2854
+ var blob = new Blob([csv], { type: 'text/csv' });
2855
+ var url = URL.createObjectURL(blob);
2856
+ var a = document.createElement('a');
2857
+ a.href = url;
2858
+ a.download = 'simulation-behavior-' + new Date().toISOString().slice(0, 10) + '.csv';
2859
+ a.click();
2860
+ URL.revokeObjectURL(url);
2861
+ }
2862
+
2863
+ function copyShareSummary() {
2864
+ var outcome = outcomeStatementEl.textContent;
2865
+ var shifts = [];
2866
+ behaviorShiftsEl.querySelectorAll('.behavior-shift-item').forEach(function(el) { shifts.push('- ' + el.textContent); });
2867
+ var causes = [];
2868
+ whyContentEl.querySelectorAll('.why-item').forEach(function(el) { causes.push('- ' + el.textContent); });
2869
+
2870
+ var text = 'OUTCOME: ' + outcome + '\\n\\n';
2871
+ if (shifts.length) text += 'BEHAVIOR:\\n' + shifts.join('\\n') + '\\n\\n';
2872
+ if (causes.length) text += 'WHY:\\n' + causes.join('\\n') + '\\n';
2873
+ text += '\\nGenerated by NeuroVerse Simulations';
2874
+
2875
+ navigator.clipboard.writeText(text).then(function() {
2876
+ alert('Summary copied to clipboard');
2877
+ });
2878
+ }
2237
2879
 
2238
2880
  // ============================================
2239
2881
  // INIT — Load worlds, scenarios, narratives, adapters
@@ -2404,10 +3046,6 @@ runBtn.addEventListener('click', async () => {
2404
3046
 
2405
3047
  // Reset viewer state
2406
3048
  totalInterventions = 0;
2407
- baselineImpacts = [];
2408
- governedImpacts = [];
2409
- chartLabels = [];
2410
- if (chart) { chart.destroy(); chart = null; }
2411
3049
  agentsEl.innerHTML = '';
2412
3050
  logEl.innerHTML = '';
2413
3051
  document.getElementById('m-stability').textContent = '--';
@@ -2475,28 +3113,7 @@ function connectSSE() {
2475
3113
  }
2476
3114
 
2477
3115
  function initChart() {
2478
- if (typeof Chart === 'undefined') return;
2479
- const ctx = document.getElementById('chart');
2480
- chart = new Chart(ctx, {
2481
- type: 'line',
2482
- data: {
2483
- labels: chartLabels,
2484
- datasets: [
2485
- { label: 'Baseline', data: baselineImpacts, borderColor: '#ef4444', backgroundColor: 'rgba(239,68,68,0.1)', fill: true, tension: 0.3, pointRadius: 3 },
2486
- { label: 'Governed', data: governedImpacts, borderColor: '#4ade80', backgroundColor: 'rgba(74,222,128,0.1)', fill: true, tension: 0.3, pointRadius: 3 },
2487
- ]
2488
- },
2489
- options: {
2490
- animation: { duration: 400 },
2491
- responsive: true,
2492
- maintainAspectRatio: false,
2493
- plugins: { legend: { labels: { color: getComputedStyle(document.body).getPropertyValue('--text-muted').trim() || '#888', font: { family: 'monospace', size: 10 } } } },
2494
- scales: {
2495
- x: { ticks: { color: getComputedStyle(document.body).getPropertyValue('--text-muted').trim() || '#888' }, grid: { color: getComputedStyle(document.body).getPropertyValue('--border').trim() || '#2a2a2a' } },
2496
- y: { ticks: { color: getComputedStyle(document.body).getPropertyValue('--text-muted').trim() || '#888' }, grid: { color: getComputedStyle(document.body).getPropertyValue('--border').trim() || '#2a2a2a' }, min: -1, max: 1 }
2497
- }
2498
- }
2499
- });
3116
+ // Chart removed no longer needed in outcome-first view
2500
3117
  }
2501
3118
 
2502
3119
  function addLog(msg, cls) {
@@ -2607,6 +3224,84 @@ function renderAgents(reactions) {
2607
3224
  agentsEl.innerHTML = html;
2608
3225
  }
2609
3226
 
3227
+ // ============================================
3228
+ // PANEL RENDERING — Outcome / Behavior / Why
3229
+ // ============================================
3230
+
3231
+ function recordBehavior(reactions, round) {
3232
+ if (!reactions || !reactions.length) return;
3233
+ reactions.forEach(function(r) {
3234
+ var status = r.verdict ? r.verdict.status : 'ALLOW';
3235
+ var reason = r.verdict ? (r.verdict.reason || '') : '';
3236
+ behaviorLog.push({
3237
+ agent: r.stakeholder_id,
3238
+ action: r.reaction || 'acted',
3239
+ status: status,
3240
+ reason: reason,
3241
+ round: round || 0,
3242
+ ts: Date.now(),
3243
+ });
3244
+ });
3245
+ }
3246
+
3247
+ function updateOutcomePanel() {
3248
+ var worldThesis = currentWorld ? (currentWorld.thesis || currentWorld.description || '') : '';
3249
+ var outcome = generateOutcome(latestStability, latestVolatility, shiftTracker, worldThesis);
3250
+ outcomeStatementEl.textContent = outcome;
3251
+
3252
+ // Structured confidence card
3253
+ var conf = computeConfidence(latestStability, latestVolatility, totalInterventions, totalActions);
3254
+ confidenceGridEl.style.display = 'grid';
3255
+ document.getElementById('cc-strength').textContent = conf.strength;
3256
+ document.getElementById('cc-strength').className = 'cc-value ' + conf.strengthCls;
3257
+ document.getElementById('cc-evidence').textContent = conf.evidence;
3258
+ document.getElementById('cc-evidence').className = 'cc-value ' + conf.evidenceCls;
3259
+ document.getElementById('cc-risk').textContent = conf.risk;
3260
+ document.getElementById('cc-risk').className = 'cc-value ' + conf.riskCls;
3261
+
3262
+ // Context line
3263
+ outcomeContextEl.textContent = 'Based on ' + totalActions + ' agent action' + (totalActions !== 1 ? 's' : '') +
3264
+ (shiftTracker.total > 0 ? ' across ' + (document.getElementById('m-round').textContent || '') : '');
3265
+ }
3266
+
3267
+ function updateBehaviorPanel() {
3268
+ // Quantified behavioral shifts in human language
3269
+ var shifts = generateBehaviorShifts(shiftTracker, behaviorLog);
3270
+ if (shifts.length > 0) {
3271
+ var html = shifts.map(function(s) {
3272
+ return '<div class="behavior-shift-item">' + s + '</div>';
3273
+ }).join('');
3274
+ behaviorShiftsEl.innerHTML = html;
3275
+ }
3276
+
3277
+ // Activity timeline (narrative micro-events, expandable)
3278
+ if (behaviorLog.length > 0) {
3279
+ activityToggleEl.style.display = 'block';
3280
+ var recent = behaviorLog.slice(-30).reverse();
3281
+ var tHtml = recent.map(function(e) {
3282
+ var narrative = translateBehaviorNarrative(e.action, { status: e.status });
3283
+ return '<div class="activity-item"><span class="activity-agent">' + e.agent + '</span> ' + narrative + '</div>';
3284
+ }).join('');
3285
+ activityTimelineEl.innerHTML = tHtml;
3286
+ }
3287
+ }
3288
+
3289
+ function updateWhyPanel() {
3290
+ var causes = generateCausation(shiftTracker, behaviorLog);
3291
+ if (causes.length > 0) {
3292
+ var html = causes.map(function(c) {
3293
+ return '<div class="why-item">' + c + '</div>';
3294
+ }).join('');
3295
+ whyContentEl.innerHTML = html;
3296
+ }
3297
+ }
3298
+
3299
+ function updateAllPanels() {
3300
+ updateOutcomePanel();
3301
+ updateBehaviorPanel();
3302
+ updateWhyPanel();
3303
+ }
3304
+
2610
3305
  function handleEvent(event) {
2611
3306
  if (event.type === 'meta') {
2612
3307
  statusEl.className = 'status live';
@@ -2689,44 +3384,59 @@ function handleEvent(event) {
2689
3384
  '<div class="rule-impact" data-impact-id="' + g.id + '"></div>' +
2690
3385
  '</div>';
2691
3386
  }).join('');
2692
- initChart();
2693
3387
  }
2694
3388
 
2695
3389
  if (event.type === 'round') {
2696
- if (event.phase === 'baseline') {
2697
- chartLabels.push('R' + event.round);
2698
- baselineImpacts.push(event.avgImpact);
2699
- } else {
2700
- governedImpacts.push(event.avgImpact);
2701
- }
2702
- if (chart) chart.update();
3390
+ const isBridge = event.reactions && event.reactions[0] && event.reactions[0].trigger === 'bridge';
2703
3391
 
2704
- renderAgents(event.reactions);
2705
- document.getElementById('m-round').textContent = event.round + '/' + event.totalRounds;
2706
- document.getElementById('m-volatility').textContent = (event.maxVolatility * 100).toFixed(0) + '%';
2707
- document.getElementById('m-volatility').parentElement.className = 'metric-box ' + (event.maxVolatility > 0.6 ? 'bad' : event.maxVolatility > 0.4 ? 'warn' : 'good');
3392
+ // Record behavior data (no governance display)
3393
+ recordBehavior(event.reactions, event.round);
3394
+ renderAgents(event.reactions); // hidden, kept for audit
2708
3395
 
3396
+ // Track totals
3397
+ totalActions += event.reactions ? event.reactions.length : 0;
2709
3398
  totalInterventions += event.interventionCount;
3399
+ latestVolatility = event.maxVolatility || 0;
3400
+
3401
+ // Update hidden data elements for audit
3402
+ if (isBridge) {
3403
+ document.getElementById('m-round').textContent = event.round + ' evals';
3404
+ } else {
3405
+ document.getElementById('m-round').textContent = event.round + '/' + event.totalRounds;
3406
+ }
3407
+ document.getElementById('m-volatility').textContent = (event.maxVolatility * 100).toFixed(0) + '%';
2710
3408
  document.getElementById('m-interventions').textContent = totalInterventions;
2711
3409
 
2712
- // Track system shifts for the card
3410
+ // Track system shifts (data collection, not displayed)
2713
3411
  trackShift(event);
2714
3412
 
2715
- // Render structured trace entry instead of flat log line
3413
+ // Update all visible panels
3414
+ updateAllPanels();
3415
+
3416
+ // Add trace entry for audit
2716
3417
  addTraceRound(event);
2717
3418
  }
2718
3419
 
3420
+ // Bridge metrics — updates stability from external /api/evaluate calls
3421
+ if (event.type === 'bridge_metrics') {
3422
+ latestStability = event.stability;
3423
+ document.getElementById('m-stability').textContent = (event.stability * 100).toFixed(0) + '%';
3424
+ updateAllPanels();
3425
+ }
3426
+
2719
3427
  if (event.type === 'complete') {
2720
3428
  statusEl.className = 'status complete';
2721
3429
  statusEl.textContent = 'COMPLETE';
2722
3430
  const r = event.result;
2723
3431
  if (r.governed) {
3432
+ latestStability = r.governed.metrics.stabilityScore;
3433
+ latestVolatility = r.governed.metrics.maxVolatility || latestVolatility;
2724
3434
  document.getElementById('m-stability').textContent = (r.governed.metrics.stabilityScore * 100).toFixed(0) + '%';
2725
- document.getElementById('m-stability').parentElement.className = 'metric-box ' + (r.governed.metrics.stabilityScore > 0.7 ? 'good' : 'warn');
2726
- addLog('Complete. Governance effectiveness: ' + (r.comparison.governanceEffectiveness * 100).toFixed(0) + '%');
3435
+ addLog('Simulation complete');
2727
3436
  renderSystemShift(r);
2728
3437
  renderRuleImpacts(r);
2729
3438
  renderEnforcementClassification(r.enforcementClassification || []);
3439
+ updateAllPanels();
2730
3440
  lastSimResult = {
2731
3441
  stability: r.governed.metrics.stabilityScore,
2732
3442
  volatility: r.governed.metrics.maxVolatility,
@@ -2805,20 +3515,37 @@ const ssImpactsEl = document.getElementById('ss-impacts');
2805
3515
  const ssNarrativeEl = document.getElementById('ss-narrative');
2806
3516
 
2807
3517
  // Raw detail toggle
2808
- document.getElementById('ss-raw-toggle').addEventListener('click', function() {
2809
- this.classList.toggle('open');
2810
- document.getElementById('ss-raw-detail').classList.toggle('open');
2811
- });
3518
+ var ssRawToggle = document.getElementById('ss-raw-toggle');
3519
+ if (ssRawToggle) {
3520
+ ssRawToggle.addEventListener('click', function() {
3521
+ this.classList.toggle('open');
3522
+ document.getElementById('ss-raw-detail').classList.toggle('open');
3523
+ });
3524
+ }
2812
3525
 
2813
3526
  let shiftTracker = { blocks: 0, total: 0, shifts: {}, patterns: [], baselineVol: 0, governedVol: 0, narrative: '', rawGoverned: [] };
2814
3527
 
2815
3528
  function resetShiftTracker() {
2816
3529
  shiftTracker = { blocks: 0, total: 0, shifts: {}, patterns: [], baselineVol: 0, governedVol: 0, narrative: '', rawGoverned: [] };
2817
- ssCard.classList.remove('visible');
3530
+ if (ssCard) ssCard.classList.remove('visible');
2818
3531
  var rawToggle = document.getElementById('ss-raw-toggle');
2819
3532
  var rawDetail = document.getElementById('ss-raw-detail');
2820
3533
  if (rawToggle) rawToggle.classList.remove('open');
2821
3534
  if (rawDetail) rawDetail.classList.remove('open');
3535
+ // Reset behavior tracking
3536
+ behaviorLog = [];
3537
+ totalActions = 0;
3538
+ totalInterventions = 0;
3539
+ latestStability = 0;
3540
+ latestVolatility = 0;
3541
+ // Reset visible panels
3542
+ if (outcomeStatementEl) outcomeStatementEl.innerHTML = '<span class="outcome-empty">Run a simulation or connect an agent to see outcomes</span>';
3543
+ if (confidenceGridEl) confidenceGridEl.style.display = 'none';
3544
+ if (outcomeContextEl) outcomeContextEl.textContent = '';
3545
+ if (behaviorShiftsEl) behaviorShiftsEl.innerHTML = '<div class="behavior-empty">Waiting for agent actions</div>';
3546
+ if (activityToggleEl) activityToggleEl.style.display = 'none';
3547
+ if (activityTimelineEl) { activityTimelineEl.innerHTML = ''; activityTimelineEl.classList.remove('open'); }
3548
+ if (whyContentEl) whyContentEl.innerHTML = '<div class="why-empty">Causation analysis appears after agent actions are evaluated</div>';
2822
3549
  }
2823
3550
 
2824
3551
  function trackShift(event) {
@@ -2861,6 +3588,7 @@ function trackShift(event) {
2861
3588
 
2862
3589
  function renderSystemShift(result) {
2863
3590
  if (shiftTracker.blocks === 0) return;
3591
+ if (!ssCard) return; // System shift card removed from visible UI
2864
3592
 
2865
3593
  var adaptRate = shiftTracker.total > 0 ? Math.round((shiftTracker.blocks / shiftTracker.total) * 100) : 0;
2866
3594
 
@@ -3151,18 +3879,6 @@ function applyTheme(theme) {
3151
3879
  themeToggleBtn.textContent = 'Light Mode';
3152
3880
  }
3153
3881
  localStorage.setItem('nv-theme', theme);
3154
- // Update chart colors if chart exists
3155
- if (chart && chart.options) {
3156
- const gridColor = theme === 'light' ? '#d4d4d4' : '#2a2a2a';
3157
- const tickColor = theme === 'light' ? '#666' : '#888';
3158
- const legendColor = theme === 'light' ? '#444' : '#888';
3159
- chart.options.scales.x.ticks.color = tickColor;
3160
- chart.options.scales.x.grid.color = gridColor;
3161
- chart.options.scales.y.ticks.color = tickColor;
3162
- chart.options.scales.y.grid.color = gridColor;
3163
- chart.options.plugins.legend.labels.color = legendColor;
3164
- chart.update();
3165
- }
3166
3882
  }
3167
3883
  themeToggleBtn.addEventListener('click', () => {
3168
3884
  const current = document.body.classList.contains('light') ? 'light' : 'dark';
@@ -3445,7 +4161,7 @@ parseRulesBtn.addEventListener('click', async () => {
3445
4161
  const resp = await fetch('/api/parse-rules', {
3446
4162
  method: 'POST',
3447
4163
  headers: { 'Content-Type': 'application/json' },
3448
- body: JSON.stringify({ text, worldId: currentWorld ? currentWorld.id : 'trading' }),
4164
+ body: JSON.stringify({ text, worldId: currentWorld ? currentWorld.id : 'social_simulation' }),
3449
4165
  });
3450
4166
  const data = await resp.json();
3451
4167
 
@@ -3476,46 +4192,72 @@ parseRulesBtn.addEventListener('click', async () => {
3476
4192
  parsedRulesEl.innerHTML += '<button class="btn btn-apply-rules" id="apply-rules-btn">' + btnLabel + '</button>';
3477
4193
  document.getElementById('apply-rules-btn').addEventListener('click', async () => {
3478
4194
  try {
3479
- // If in custom rules mode, use base world if selected
3480
- let worldId = currentWorld ? currentWorld.id : 'trading';
3481
4195
  if (currentWorldSource === 'custom') {
3482
- const baseWorld = document.getElementById('custom-base-world').value;
3483
- if (baseWorld) worldId = baseWorld;
3484
- }
3485
-
3486
- const applyResp = await fetch('/api/apply-rules', {
3487
- method: 'POST',
3488
- headers: { 'Content-Type': 'application/json' },
3489
- body: JSON.stringify({ rules: parsedRuleData, worldId }),
3490
- });
3491
- const applyData = await applyResp.json();
3492
- if (applyData.status === 'applied') {
3493
- ruleStatusEl.textContent = applyData.applied + ' rule(s) active. Run a simulation to see the effect.';
3494
- ruleStatusEl.className = 'rule-status success';
3495
-
3496
- // Update right panel invariants with custom rules
4196
+ // CUSTOM MODE: Generate a full governed world (state vars, gates, thesis)
3497
4197
  const customName = document.getElementById('custom-world-name');
3498
4198
  const worldName = (customName && customName.value) ? customName.value : 'Custom World';
3499
- const customThesis = document.getElementById('custom-world-thesis');
3500
- const thesis = (customThesis && customThesis.value) ? customThesis.value : 'User-defined governance rules';
3501
-
3502
- // Show rules in active invariants panel
3503
- activeInvEl.innerHTML = parsedRuleData.map(r => {
3504
- const enfType = r.enforcement || 'block';
3505
- const colorMap = { block: 'var(--red)', allow: 'var(--green)', modify: 'var(--blue)', warn: 'var(--yellow)', pause: 'var(--yellow)' };
3506
- const color = colorMap[enfType] || 'var(--text-secondary)';
3507
- return '<div class="inv-item" style="color:' + color + '">[' + r.id + '] ' + r.description + '</div>';
3508
- }).join('');
3509
-
3510
- // In custom mode, set a custom world reference
3511
- if (currentWorldSource === 'custom') {
3512
- const baseWorld = document.getElementById('custom-base-world').value;
3513
- if (baseWorld) {
3514
- selectWorld(baseWorld);
3515
- } else {
3516
- currentWorld = { id: 'custom-world', title: worldName, thesis, stateVariables: [], invariants: [], gates: [] };
3517
- }
3518
- document.getElementById('world-thesis').textContent = '"' + thesis + '"';
4199
+ const ruleText = document.getElementById('rule-input').value;
4200
+
4201
+ const genResp = await fetch('/api/generate-world', {
4202
+ method: 'POST',
4203
+ headers: { 'Content-Type': 'application/json' },
4204
+ body: JSON.stringify({ text: ruleText, name: worldName }),
4205
+ });
4206
+ const genData = await genResp.json();
4207
+
4208
+ if (genData.status === 'generated') {
4209
+ ruleStatusEl.textContent = genData.parsed.total + ' rules parsed. Full world generated: ' + genData.world.stateVariables.length + ' state variables, ' + genData.world.gates.length + ' gates, ' + genData.world.invariants.length + ' invariants.';
4210
+ ruleStatusEl.className = 'rule-status success';
4211
+
4212
+ // Show invariants in active panel
4213
+ activeInvEl.innerHTML = genData.world.invariants.map(function(inv) {
4214
+ const color = inv.enforceable ? 'var(--red)' : 'var(--green)';
4215
+ return '<div class="inv-item" style="color:' + color + '">[' + inv.id + '] ' + inv.description + '</div>';
4216
+ }).join('');
4217
+
4218
+ // Set the generated world as the current world with real state variables and gates
4219
+ currentWorld = {
4220
+ id: genData.world.id,
4221
+ title: genData.world.title,
4222
+ thesis: genData.world.thesis,
4223
+ stateVariables: genData.world.stateVariables,
4224
+ invariants: genData.world.invariants,
4225
+ gates: genData.world.gates,
4226
+ };
4227
+ worlds.push(currentWorld);
4228
+
4229
+ // Update world selector dropdown
4230
+ var opt = document.createElement('option');
4231
+ opt.value = currentWorld.id;
4232
+ opt.textContent = currentWorld.title;
4233
+ worldSelect.appendChild(opt);
4234
+ worldSelect.value = currentWorld.id;
4235
+
4236
+ // Render the state variable sliders
4237
+ selectWorld(currentWorld.id);
4238
+ document.getElementById('world-thesis').textContent = '"' + genData.world.thesis + '"';
4239
+ }
4240
+ } else {
4241
+ // PRESET MODE: Just apply rules on top of existing world
4242
+ let worldId = currentWorld ? currentWorld.id : 'social_simulation';
4243
+
4244
+ const applyResp = await fetch('/api/apply-rules', {
4245
+ method: 'POST',
4246
+ headers: { 'Content-Type': 'application/json' },
4247
+ body: JSON.stringify({ rules: parsedRuleData, worldId }),
4248
+ });
4249
+ const applyData = await applyResp.json();
4250
+ if (applyData.status === 'applied') {
4251
+ ruleStatusEl.textContent = applyData.applied + ' rule(s) active. Run a simulation to see the effect.';
4252
+ ruleStatusEl.className = 'rule-status success';
4253
+
4254
+ // Show rules in active invariants panel
4255
+ activeInvEl.innerHTML = parsedRuleData.map(function(r) {
4256
+ var enfType = r.enforcement || 'block';
4257
+ var colorMap = { block: 'var(--red)', allow: 'var(--green)', modify: 'var(--blue)', warn: 'var(--yellow)', pause: 'var(--yellow)' };
4258
+ var color = colorMap[enfType] || 'var(--text-secondary)';
4259
+ return '<div class="inv-item" style="color:' + color + '">[' + r.id + '] ' + r.description + '</div>';
4260
+ }).join('');
3519
4261
  }
3520
4262
  }
3521
4263
  } catch (err) {