@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.
- package/README.md +292 -201
- package/dist/assets/index-B64NuIXu.css +1 -0
- package/dist/assets/{index-CHmUN8s0.js → index-BMkPevVr.js} +105 -105
- package/dist/assets/{reportEngine-BVdQ2_nW.js → reportEngine-D2ZrMny8.js} +1 -1
- package/dist/engine/chaosEngine.js +3 -9
- package/dist/engine/cli.js +34 -104
- package/dist/engine/index.js +2 -3
- package/dist/engine/liveVisualizer.js +965 -223
- package/dist/engine/narrativeInjection.js +78 -89
- package/dist/engine/policyEngine.js +171 -58
- package/dist/engine/scenarioCapsule.js +73 -129
- package/dist/engine/scenarioLibrary.js +52 -131
- package/dist/engine/worldComparison.js +12 -25
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/dist/assets/index-DWgMnB7I.css +0 -1
|
@@ -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: "
|
|
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 = ["
|
|
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 ?? "
|
|
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:
|
|
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:
|
|
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 || "
|
|
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 ?? "
|
|
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
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
.
|
|
1643
|
-
|
|
1644
|
-
.
|
|
1645
|
-
.
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
.
|
|
1649
|
-
.
|
|
1650
|
-
.
|
|
1651
|
-
.
|
|
1652
|
-
.
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
.
|
|
1658
|
-
.
|
|
1659
|
-
.
|
|
1660
|
-
|
|
1661
|
-
.
|
|
1662
|
-
.
|
|
1663
|
-
.
|
|
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
|
-
/*
|
|
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>
|
|
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>
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
<div class="
|
|
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">"
|
|
2083
|
-
action
|
|
2084
|
-
world=<span class="str">"
|
|
2345
|
+
actor=<span class="str">"Harry"</span>,
|
|
2346
|
+
action=action,
|
|
2347
|
+
world=<span class="str">"research"</span>
|
|
2085
2348
|
)
|
|
2086
2349
|
|
|
2087
|
-
<span class="
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
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
|
-
|
|
2093
|
-
|
|
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
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
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
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
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="
|
|
2135
|
-
<
|
|
2136
|
-
|
|
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
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
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="
|
|
2148
|
-
<
|
|
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
|
-
<!--
|
|
2154
|
-
<div
|
|
2155
|
-
<
|
|
2156
|
-
|
|
2157
|
-
<
|
|
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
|
-
|
|
2196
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
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
|
|
3410
|
+
// Track system shifts (data collection, not displayed)
|
|
2713
3411
|
trackShift(event);
|
|
2714
3412
|
|
|
2715
|
-
//
|
|
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
|
-
|
|
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')
|
|
2809
|
-
|
|
2810
|
-
|
|
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 : '
|
|
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
|
-
|
|
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
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
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) {
|