@neuroverseos/nv-sim 0.1.6 → 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 +374 -123
- 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 +1530 -230
- 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 {
|
|
@@ -958,6 +1039,127 @@ function startInteractiveServer(port, onReady) {
|
|
|
958
1039
|
}
|
|
959
1040
|
return;
|
|
960
1041
|
}
|
|
1042
|
+
// ── Clear Rules ──
|
|
1043
|
+
// Wipes all custom guards and resets governance to base world rules
|
|
1044
|
+
if (req.url === "/api/clear-rules" && req.method === "POST") {
|
|
1045
|
+
customGuards.length = 0;
|
|
1046
|
+
jsonResponse(res, 200, {
|
|
1047
|
+
status: "cleared",
|
|
1048
|
+
message: "All custom rules removed. Governance reset to base world rules.",
|
|
1049
|
+
});
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
// ── Load World File ──
|
|
1053
|
+
// Accept a full world definition JSON and use it as the active world
|
|
1054
|
+
if (req.url === "/api/load-world-file" && req.method === "POST") {
|
|
1055
|
+
try {
|
|
1056
|
+
const body = await readBody(req);
|
|
1057
|
+
const payload = JSON.parse(body);
|
|
1058
|
+
if (!payload.world) {
|
|
1059
|
+
jsonResponse(res, 400, { error: "world object is required" });
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
const w = payload.world;
|
|
1063
|
+
// Parse any plain-English rules in the world file into guards
|
|
1064
|
+
customGuards.length = 0;
|
|
1065
|
+
if (w.rules && Array.isArray(w.rules)) {
|
|
1066
|
+
for (let i = 0; i < w.rules.length; i++) {
|
|
1067
|
+
const rule = w.rules[i];
|
|
1068
|
+
if (rule.intent_patterns && rule.intent_patterns.length > 0) {
|
|
1069
|
+
// Already structured rule
|
|
1070
|
+
customGuards.push({
|
|
1071
|
+
id: rule.id || `world-rule-${i}`,
|
|
1072
|
+
label: rule.description,
|
|
1073
|
+
description: rule.description,
|
|
1074
|
+
category: "world-file",
|
|
1075
|
+
enforcement: rule.enforcement || "block",
|
|
1076
|
+
immutable: false,
|
|
1077
|
+
intent_patterns: rule.intent_patterns,
|
|
1078
|
+
default_enabled: true,
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
// Plain-English rule — parse it
|
|
1083
|
+
const parsed = parseNaturalLanguageRule(rule.description, i);
|
|
1084
|
+
if (parsed) {
|
|
1085
|
+
customGuards.push({
|
|
1086
|
+
id: parsed.id,
|
|
1087
|
+
label: parsed.description,
|
|
1088
|
+
description: parsed.description,
|
|
1089
|
+
category: "world-file",
|
|
1090
|
+
enforcement: parsed.enforcement,
|
|
1091
|
+
immutable: false,
|
|
1092
|
+
intent_patterns: parsed.intent_patterns,
|
|
1093
|
+
default_enabled: true,
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
// Build the response world definition for the UI
|
|
1100
|
+
const loadedWorld = {
|
|
1101
|
+
id: "custom-world",
|
|
1102
|
+
title: w.name || "Custom World",
|
|
1103
|
+
thesis: w.thesis || "User-defined world",
|
|
1104
|
+
stateVariables: w.state_variables || [],
|
|
1105
|
+
invariants: (w.invariants || []).map(inv => ({
|
|
1106
|
+
id: inv.id,
|
|
1107
|
+
description: inv.description,
|
|
1108
|
+
enforceable: inv.enforceable !== false,
|
|
1109
|
+
})),
|
|
1110
|
+
gates: (w.gates || []).map(g => ({
|
|
1111
|
+
id: g.id,
|
|
1112
|
+
label: g.label,
|
|
1113
|
+
condition: g.condition,
|
|
1114
|
+
severity: g.severity || "warning",
|
|
1115
|
+
})),
|
|
1116
|
+
rulesApplied: customGuards.length,
|
|
1117
|
+
};
|
|
1118
|
+
jsonResponse(res, 200, {
|
|
1119
|
+
status: "loaded",
|
|
1120
|
+
world: loadedWorld,
|
|
1121
|
+
rulesApplied: customGuards.length,
|
|
1122
|
+
message: `World "${loadedWorld.title}" loaded with ${loadedWorld.invariants.length} invariants, ${loadedWorld.gates.length} gates, and ${customGuards.length} rules.`,
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
catch (err) {
|
|
1126
|
+
jsonResponse(res, 400, { error: "Invalid world file JSON" });
|
|
1127
|
+
}
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
// ── Export World File ──
|
|
1131
|
+
// Exports the current world configuration (base world + custom rules + overrides) as a world file
|
|
1132
|
+
if (req.url === "/api/export-world" && req.method === "GET") {
|
|
1133
|
+
const { resolveWorld } = await Promise.resolve().then(() => __importStar(require("./worldComparison")));
|
|
1134
|
+
const worldId = currentSession.world || "social_simulation";
|
|
1135
|
+
let baseWorld;
|
|
1136
|
+
try {
|
|
1137
|
+
baseWorld = resolveWorld(worldId);
|
|
1138
|
+
}
|
|
1139
|
+
catch {
|
|
1140
|
+
baseWorld = null;
|
|
1141
|
+
}
|
|
1142
|
+
const exportedWorld = {
|
|
1143
|
+
name: baseWorld?.title || worldId,
|
|
1144
|
+
thesis: baseWorld?.world?.thesis || "Exported world",
|
|
1145
|
+
state_variables: baseWorld?.world?.state_variables || [],
|
|
1146
|
+
invariants: baseWorld?.world?.invariants || [],
|
|
1147
|
+
gates: baseWorld?.world?.gates || [],
|
|
1148
|
+
rules: customGuards.map(g => ({
|
|
1149
|
+
id: g.id,
|
|
1150
|
+
description: g.description,
|
|
1151
|
+
enforcement: g.enforcement,
|
|
1152
|
+
intent_patterns: g.intent_patterns,
|
|
1153
|
+
})),
|
|
1154
|
+
};
|
|
1155
|
+
res.writeHead(200, {
|
|
1156
|
+
"Content-Type": "application/json",
|
|
1157
|
+
"Content-Disposition": `attachment; filename="${worldId}-world.json"`,
|
|
1158
|
+
"Access-Control-Allow-Origin": "*",
|
|
1159
|
+
});
|
|
1160
|
+
res.end(JSON.stringify({ world: exportedWorld }, null, 2));
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
961
1163
|
// ── Session Reporting Endpoints ──
|
|
962
1164
|
// Connect the serve runtime to the enforce reporting pipeline.
|
|
963
1165
|
// Users can request reports, stats, and recommendations from live governance data.
|
|
@@ -1122,6 +1324,10 @@ function startInteractiveServer(port, onReady) {
|
|
|
1122
1324
|
guardCount: customGuards.length,
|
|
1123
1325
|
evaluations: [],
|
|
1124
1326
|
};
|
|
1327
|
+
// Reset bridge metrics counters
|
|
1328
|
+
bridgeEvalCount = 0;
|
|
1329
|
+
bridgeBlockCount = 0;
|
|
1330
|
+
bridgeModifyCount = 0;
|
|
1125
1331
|
jsonResponse(res, 200, {
|
|
1126
1332
|
status: "reset",
|
|
1127
1333
|
newSessionId: currentSession.id,
|
|
@@ -1200,7 +1406,7 @@ function startInteractiveServer(port, onReady) {
|
|
|
1200
1406
|
jsonResponse(res, 200, { status: "started", adapter: payload.adapterId });
|
|
1201
1407
|
// Resolve world for governance evaluation
|
|
1202
1408
|
const { resolveWorld } = await Promise.resolve().then(() => __importStar(require("./worldComparison")));
|
|
1203
|
-
const resolved = resolveWorld(payload.worldId ?? "
|
|
1409
|
+
const resolved = resolveWorld(payload.worldId ?? "social_simulation");
|
|
1204
1410
|
const world = { ...resolved.world };
|
|
1205
1411
|
if (payload.stateOverrides) {
|
|
1206
1412
|
const updatedVars = world.state_variables.map(sv => {
|
|
@@ -1515,42 +1721,80 @@ const INTERACTIVE_DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1515
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; }
|
|
1516
1722
|
|
|
1517
1723
|
/* RIGHT PANEL — Simulation viewer */
|
|
1518
|
-
.viewer { display: grid; grid-template-rows: auto
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
.
|
|
1522
|
-
|
|
1523
|
-
.
|
|
1524
|
-
.
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
.
|
|
1528
|
-
.
|
|
1529
|
-
.
|
|
1530
|
-
.
|
|
1531
|
-
.
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
.
|
|
1537
|
-
.
|
|
1538
|
-
.
|
|
1539
|
-
|
|
1540
|
-
.
|
|
1541
|
-
.
|
|
1542
|
-
.
|
|
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
|
+
}
|
|
1543
1791
|
.center-line { position: absolute; left: 50%; top: 0; bottom: 0; width: 1px; background: var(--border-subtle); }
|
|
1544
1792
|
.verdict { display: inline-block; padding: 1px 5px; border-radius: 3px; font-size: 9px; font-weight: 600; margin-left: 4px; }
|
|
1545
1793
|
.verdict.ALLOW { background: var(--green-bg); color: var(--green); }
|
|
1546
1794
|
.verdict.BLOCK { background: var(--red-bg); color: var(--red); }
|
|
1547
1795
|
.verdict.PAUSE { background: var(--yellow-bg); color: var(--yellow); }
|
|
1548
1796
|
|
|
1549
|
-
/*
|
|
1550
|
-
.chart-container { position: relative; height: 100%; min-height: 150px; }
|
|
1551
|
-
canvas { width: 100% !important; height: 100% !important; }
|
|
1552
|
-
|
|
1553
|
-
/* Simulation Trace */
|
|
1797
|
+
/* Simulation Trace (audit only) */
|
|
1554
1798
|
.trace-round { margin-bottom: 10px; border: 1px solid var(--border); border-radius: 4px; overflow: hidden; }
|
|
1555
1799
|
.trace-round-header { display: flex; align-items: center; gap: 8px; padding: 6px 10px; background: var(--bg-surface); cursor: pointer; user-select: none; }
|
|
1556
1800
|
.trace-round-header:hover { background: var(--bg-elevated); }
|
|
@@ -1660,6 +1904,60 @@ const INTERACTIVE_DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1660
1904
|
.integrate-endpoint { font-size: 11px; color: var(--text-secondary); margin-top: 6px; }
|
|
1661
1905
|
.integrate-endpoint code { color: var(--green); background: var(--bg-surface); padding: 1px 5px; border-radius: 3px; }
|
|
1662
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
|
+
|
|
1663
1961
|
/* Rule editor */
|
|
1664
1962
|
.rule-editor { margin-top: 8px; }
|
|
1665
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; }
|
|
@@ -1687,13 +1985,70 @@ const INTERACTIVE_DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1687
1985
|
.rule-status.error { color: var(--red); }
|
|
1688
1986
|
.rule-examples { font-size: 10px; color: var(--text-faint); margin-top: 6px; line-height: 1.6; }
|
|
1689
1987
|
.rule-examples code { background: var(--bg-surface); padding: 1px 4px; border-radius: 2px; color: var(--text-secondary); }
|
|
1988
|
+
|
|
1989
|
+
/* World Action Bar */
|
|
1990
|
+
.world-action-bar { display: flex; gap: 4px; margin-bottom: 16px; flex-wrap: wrap; }
|
|
1991
|
+
.btn-world-action { flex: 1; min-width: 0; padding: 6px 4px; font-size: 10px; background: var(--bg-surface); color: var(--text-secondary); border: 1px solid var(--border); border-radius: 4px; cursor: pointer; font-family: inherit; text-align: center; transition: all 0.2s; white-space: nowrap; }
|
|
1992
|
+
.btn-world-action:hover { border-color: var(--accent); color: var(--accent); }
|
|
1993
|
+
.btn-world-action.btn-export { color: var(--green); border-color: var(--green); opacity: 0.7; }
|
|
1994
|
+
.btn-world-action.btn-export:hover { opacity: 1; }
|
|
1995
|
+
|
|
1996
|
+
/* World Source Tabs */
|
|
1997
|
+
.world-source-tabs { display: flex; gap: 4px; }
|
|
1998
|
+
.ws-tab { flex: 1; display: flex; flex-direction: column; align-items: center; padding: 10px 6px; background: var(--bg-surface); border: 2px solid var(--border); border-radius: 6px; cursor: pointer; transition: all 0.2s; text-align: center; }
|
|
1999
|
+
.ws-tab:hover { border-color: var(--text-muted); }
|
|
2000
|
+
.ws-tab.active { border-color: var(--accent); background: var(--accent-bg); }
|
|
2001
|
+
.ws-tab input[type="radio"] { display: none; }
|
|
2002
|
+
.ws-label { font-size: 11px; font-weight: 700; color: var(--text-primary); }
|
|
2003
|
+
.ws-hint { font-size: 9px; color: var(--text-muted); margin-top: 2px; }
|
|
2004
|
+
.ws-tab.active .ws-label { color: var(--accent); }
|
|
2005
|
+
|
|
2006
|
+
/* World Source Panels */
|
|
2007
|
+
.world-source-panel { animation: fadeIn 0.2s ease; }
|
|
2008
|
+
@keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
|
|
2009
|
+
|
|
2010
|
+
/* Custom World Header */
|
|
2011
|
+
.custom-world-header { margin-bottom: 12px; }
|
|
2012
|
+
.world-name-input { width: 100%; background: var(--bg-surface); color: var(--text-primary); border: 1px solid var(--border-subtle); padding: 8px; border-radius: 4px; font-family: inherit; font-size: 13px; font-weight: 600; margin-bottom: 6px; }
|
|
2013
|
+
.world-name-input:focus { border-color: var(--accent); outline: none; }
|
|
2014
|
+
.world-thesis-input { width: 100%; background: var(--bg-surface); color: var(--text-secondary); border: 1px solid var(--border-subtle); padding: 6px 8px; border-radius: 4px; font-family: inherit; font-size: 11px; resize: none; height: 36px; }
|
|
2015
|
+
.world-thesis-input:focus { border-color: var(--accent); outline: none; }
|
|
2016
|
+
|
|
2017
|
+
/* Rule editor enhancements */
|
|
2018
|
+
.rule-editor-label { font-size: 11px; color: var(--text-secondary); margin-bottom: 6px; font-weight: 600; }
|
|
2019
|
+
.rule-input-large { min-height: 120px; }
|
|
2020
|
+
.btn-generate-world { background: var(--accent); color: #fff; margin-top: 8px; padding: 10px; font-size: 12px; font-weight: 700; width: 100%; border: none; border-radius: 4px; cursor: pointer; font-family: inherit; transition: filter 0.2s; }
|
|
2021
|
+
.btn-generate-world:hover { filter: brightness(1.1); }
|
|
2022
|
+
|
|
2023
|
+
/* Upload Zone */
|
|
2024
|
+
.upload-zone { border: 2px dashed var(--border-subtle); border-radius: 8px; padding: 24px; text-align: center; cursor: pointer; transition: all 0.2s; margin-bottom: 12px; }
|
|
2025
|
+
.upload-zone:hover, .upload-zone.dragover { border-color: var(--accent); background: var(--accent-bg); }
|
|
2026
|
+
.upload-icon { font-size: 28px; margin-bottom: 8px; }
|
|
2027
|
+
.upload-label { font-size: 12px; color: var(--text-secondary); }
|
|
2028
|
+
.upload-or { font-size: 10px; color: var(--text-faint); margin: 8px 0; }
|
|
2029
|
+
.btn-upload-browse { background: var(--bg-elevated); color: var(--text-primary); border: 1px solid var(--border); padding: 6px 16px; border-radius: 4px; cursor: pointer; font-family: inherit; font-size: 11px; }
|
|
2030
|
+
.upload-paste-section { margin-bottom: 12px; }
|
|
2031
|
+
.btn-load-world { background: var(--green); color: #0a0a0a; width: 100%; padding: 10px; font-size: 12px; font-weight: 700; border: none; border-radius: 4px; cursor: pointer; font-family: inherit; transition: filter 0.2s; }
|
|
2032
|
+
.btn-load-world:hover { filter: brightness(0.9); }
|
|
2033
|
+
|
|
2034
|
+
/* Loaded World Card */
|
|
2035
|
+
.loaded-world-card { background: var(--green-bg); border: 1px solid var(--green); border-radius: 6px; padding: 12px; margin-top: 10px; }
|
|
2036
|
+
.lw-name { font-size: 13px; font-weight: 700; color: var(--green); }
|
|
2037
|
+
.lw-thesis { font-size: 11px; color: var(--text-secondary); margin-top: 4px; font-style: italic; }
|
|
2038
|
+
.lw-stats { font-size: 10px; color: var(--text-muted); margin-top: 6px; }
|
|
2039
|
+
|
|
2040
|
+
/* Schema Reference */
|
|
2041
|
+
.schema-ref { font-size: 10px; color: var(--text-muted); }
|
|
2042
|
+
.schema-item { padding: 3px 0; border-bottom: 1px solid var(--border); }
|
|
2043
|
+
.schema-item:last-child { border-bottom: none; }
|
|
2044
|
+
.schema-item code { color: var(--accent); background: var(--bg-surface); padding: 1px 4px; border-radius: 2px; }
|
|
1690
2045
|
</style>
|
|
1691
2046
|
</head>
|
|
1692
2047
|
<body>
|
|
1693
2048
|
<div class="header">
|
|
1694
2049
|
<div style="display:flex;align-items:center">
|
|
1695
2050
|
<h1>NV-SIM</h1>
|
|
1696
|
-
<span class="sub">
|
|
2051
|
+
<span class="sub">Governance Runtime</span>
|
|
1697
2052
|
</div>
|
|
1698
2053
|
<div class="header-right">
|
|
1699
2054
|
<button class="theme-toggle" id="theme-toggle" title="Toggle light/dark mode">Light Mode</button>
|
|
@@ -1704,55 +2059,148 @@ const INTERACTIVE_DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1704
2059
|
<div class="layout">
|
|
1705
2060
|
<!-- LEFT: CONTROLS -->
|
|
1706
2061
|
<div class="controls" id="controls-panel">
|
|
1707
|
-
<!--
|
|
2062
|
+
<!-- World Action Bar -->
|
|
2063
|
+
<div class="world-action-bar">
|
|
2064
|
+
<button class="btn btn-world-action" id="new-world-btn" title="Clear everything and start fresh">+ New World</button>
|
|
2065
|
+
<button class="btn btn-world-action" id="load-file-btn" title="Load a .json world file">Load World File</button>
|
|
2066
|
+
<button class="btn btn-world-action" id="clear-rules-btn" title="Clear custom rules only">Clear Rules</button>
|
|
2067
|
+
<button class="btn btn-world-action btn-export" id="export-world-btn" title="Export current world as JSON">Save as World File</button>
|
|
2068
|
+
</div>
|
|
2069
|
+
|
|
2070
|
+
<!-- World Source selector -->
|
|
1708
2071
|
<div class="ctrl-section">
|
|
1709
|
-
<h3>
|
|
1710
|
-
<div class="
|
|
1711
|
-
<
|
|
1712
|
-
<
|
|
1713
|
-
|
|
2072
|
+
<h3>World Source</h3>
|
|
2073
|
+
<div class="world-source-tabs">
|
|
2074
|
+
<label class="ws-tab active" data-source="preset">
|
|
2075
|
+
<input type="radio" name="world-source" value="preset" checked>
|
|
2076
|
+
<span class="ws-label">Preset</span>
|
|
2077
|
+
<span class="ws-hint">Demo scenarios</span>
|
|
2078
|
+
</label>
|
|
2079
|
+
<label class="ws-tab" data-source="custom">
|
|
2080
|
+
<input type="radio" name="world-source" value="custom">
|
|
2081
|
+
<span class="ws-label">Custom Rules</span>
|
|
2082
|
+
<span class="ws-hint">Define your world</span>
|
|
2083
|
+
</label>
|
|
2084
|
+
<label class="ws-tab" data-source="upload">
|
|
2085
|
+
<input type="radio" name="world-source" value="upload">
|
|
2086
|
+
<span class="ws-label">World File</span>
|
|
2087
|
+
<span class="ws-hint">JSON / .nv-world</span>
|
|
2088
|
+
</label>
|
|
1714
2089
|
</div>
|
|
1715
|
-
<div id="engine-status" style="font-size:10px;color:#555;margin-top:4px"></div>
|
|
1716
2090
|
</div>
|
|
1717
2091
|
|
|
1718
|
-
<!--
|
|
1719
|
-
<div class="
|
|
1720
|
-
<
|
|
1721
|
-
|
|
1722
|
-
<
|
|
2092
|
+
<!-- SOURCE: Preset -->
|
|
2093
|
+
<div class="world-source-panel" id="source-preset">
|
|
2094
|
+
<div class="ctrl-section">
|
|
2095
|
+
<h3>World</h3>
|
|
2096
|
+
<div class="ctrl-row">
|
|
2097
|
+
<select id="world-select"></select>
|
|
2098
|
+
</div>
|
|
2099
|
+
<div id="world-thesis" class="world-thesis"></div>
|
|
2100
|
+
</div>
|
|
2101
|
+
|
|
2102
|
+
<!-- State variables (dynamic sliders) -->
|
|
2103
|
+
<div class="ctrl-section" id="state-vars-section" style="display:none">
|
|
2104
|
+
<h3>World Rules</h3>
|
|
2105
|
+
<div id="state-vars"></div>
|
|
2106
|
+
</div>
|
|
2107
|
+
|
|
2108
|
+
<!-- Scenario presets (collapsed by default) -->
|
|
2109
|
+
<div class="ctrl-section">
|
|
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>
|
|
1723
2112
|
</div>
|
|
1724
|
-
<div id="world-thesis" class="world-thesis"></div>
|
|
1725
2113
|
</div>
|
|
1726
2114
|
|
|
1727
|
-
<!--
|
|
1728
|
-
<div class="
|
|
1729
|
-
<
|
|
1730
|
-
|
|
2115
|
+
<!-- SOURCE: Custom Rules (Define Your World) -->
|
|
2116
|
+
<div class="world-source-panel" id="source-custom" style="display:none">
|
|
2117
|
+
<div class="ctrl-section">
|
|
2118
|
+
<h3>Define Your World</h3>
|
|
2119
|
+
<div class="custom-world-header">
|
|
2120
|
+
<input type="text" class="world-name-input" id="custom-world-name" placeholder="World name (e.g. Marketing Governance)">
|
|
2121
|
+
<textarea class="world-thesis-input" id="custom-world-thesis" placeholder="What is this world about? (thesis)"></textarea>
|
|
2122
|
+
</div>
|
|
2123
|
+
<div class="rule-editor">
|
|
2124
|
+
<div class="rule-editor-label">Type your governance rules:</div>
|
|
2125
|
+
<textarea class="rule-input rule-input-large" id="rule-input" placeholder="No agent may spend more than $10k without approval All outbound emails must be reviewed Block deletion of production data Limit API calls to 100 per minute Require manager approval for refunds over $500"></textarea>
|
|
2126
|
+
<button class="btn btn-generate-world" id="parse-rules-btn">Generate World</button>
|
|
2127
|
+
<div id="parsed-rules" class="parsed-rules"></div>
|
|
2128
|
+
<div id="rule-status" class="rule-status"></div>
|
|
2129
|
+
<div class="rule-examples">
|
|
2130
|
+
Rule patterns:<br>
|
|
2131
|
+
<code>Block [action]</code> — hard suppression<br>
|
|
2132
|
+
<code>Limit [X] to [N]</code> — cap extremes<br>
|
|
2133
|
+
<code>Require [X] for [Y]</code> — structural constraint<br>
|
|
2134
|
+
<code>Pause [X] for review</code> — human-in-the-loop<br>
|
|
2135
|
+
<code>Allow [X]</code> — explicit permission<br>
|
|
2136
|
+
<code>Monitor [X]</code> — circuit breaker gate
|
|
2137
|
+
</div>
|
|
2138
|
+
</div>
|
|
2139
|
+
</div>
|
|
2140
|
+
|
|
2141
|
+
<!-- Base world (optional) -->
|
|
2142
|
+
<div class="ctrl-section">
|
|
2143
|
+
<h3>Base World (Optional)</h3>
|
|
2144
|
+
<div class="ctrl-row">
|
|
2145
|
+
<select id="custom-base-world">
|
|
2146
|
+
<option value="">None — start from scratch</option>
|
|
2147
|
+
</select>
|
|
2148
|
+
<div style="font-size:10px;color:var(--text-faint);margin-top:4px">Layer your rules on top of a preset world</div>
|
|
2149
|
+
</div>
|
|
2150
|
+
</div>
|
|
1731
2151
|
</div>
|
|
1732
2152
|
|
|
1733
|
-
<!--
|
|
1734
|
-
<div class="
|
|
1735
|
-
<
|
|
1736
|
-
|
|
1737
|
-
<
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
<
|
|
1746
|
-
<
|
|
1747
|
-
|
|
2153
|
+
<!-- SOURCE: Upload World File -->
|
|
2154
|
+
<div class="world-source-panel" id="source-upload" style="display:none">
|
|
2155
|
+
<div class="ctrl-section">
|
|
2156
|
+
<h3>Load World File</h3>
|
|
2157
|
+
<div class="upload-zone" id="upload-zone">
|
|
2158
|
+
<div class="upload-icon">📄</div>
|
|
2159
|
+
<div class="upload-label">Drop a .json or .nv-world file here</div>
|
|
2160
|
+
<div class="upload-or">or</div>
|
|
2161
|
+
<button class="btn btn-upload-browse" id="upload-browse-btn">Browse Files</button>
|
|
2162
|
+
<input type="file" id="upload-file-input" accept=".json,.nv-world" style="display:none">
|
|
2163
|
+
</div>
|
|
2164
|
+
<div class="upload-paste-section">
|
|
2165
|
+
<div class="rule-editor-label">Or paste world JSON:</div>
|
|
2166
|
+
<textarea class="rule-input rule-input-large" id="world-json-input" placeholder='{ "name": "Marketing Governance", "thesis": "All marketing actions are governed", "invariants": [...], "rules": [...], "gates": [...] }'></textarea>
|
|
2167
|
+
</div>
|
|
2168
|
+
<button class="btn btn-load-world" id="load-world-btn">Load into Runtime</button>
|
|
2169
|
+
<div id="upload-status" class="rule-status"></div>
|
|
2170
|
+
|
|
2171
|
+
<!-- Loaded world info -->
|
|
2172
|
+
<div id="loaded-world-info" style="display:none">
|
|
2173
|
+
<div class="loaded-world-card">
|
|
2174
|
+
<div class="lw-name" id="lw-name"></div>
|
|
2175
|
+
<div class="lw-thesis" id="lw-thesis"></div>
|
|
2176
|
+
<div class="lw-stats" id="lw-stats"></div>
|
|
2177
|
+
</div>
|
|
2178
|
+
</div>
|
|
2179
|
+
</div>
|
|
2180
|
+
|
|
2181
|
+
<!-- World file schema reference -->
|
|
2182
|
+
<div class="ctrl-section">
|
|
2183
|
+
<h3>World File Schema</h3>
|
|
2184
|
+
<div class="schema-ref">
|
|
2185
|
+
<div class="schema-item"><code>name</code> — world name</div>
|
|
2186
|
+
<div class="schema-item"><code>thesis</code> — what this world is about</div>
|
|
2187
|
+
<div class="schema-item"><code>rules[]</code> — governance rules (plain English or structured)</div>
|
|
2188
|
+
<div class="schema-item"><code>invariants[]</code> — rules that always hold <code>{id, description}</code></div>
|
|
2189
|
+
<div class="schema-item"><code>gates[]</code> — viability thresholds <code>{id, label, condition, severity}</code></div>
|
|
2190
|
+
<div class="schema-item"><code>state_variables[]</code> — sliders <code>{id, label, type, range, default_value}</code></div>
|
|
1748
2191
|
</div>
|
|
1749
2192
|
</div>
|
|
1750
2193
|
</div>
|
|
1751
2194
|
|
|
1752
|
-
<!--
|
|
1753
|
-
<div class="ctrl-section">
|
|
1754
|
-
<h3>
|
|
1755
|
-
<div
|
|
2195
|
+
<!-- Simulation Engine (demoted, below world source) -->
|
|
2196
|
+
<div class="ctrl-section" style="margin-top:8px">
|
|
2197
|
+
<h3>Engine</h3>
|
|
2198
|
+
<div class="ctrl-row">
|
|
2199
|
+
<select id="engine-select">
|
|
2200
|
+
<option value="nv-sim" selected>NV-SIM (Built-in)</option>
|
|
2201
|
+
</select>
|
|
2202
|
+
</div>
|
|
2203
|
+
<div id="engine-status" style="font-size:10px;color:var(--text-faint);margin-top:4px"></div>
|
|
1756
2204
|
</div>
|
|
1757
2205
|
|
|
1758
2206
|
<!-- Narrative injection -->
|
|
@@ -1796,37 +2244,207 @@ const INTERACTIVE_DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1796
2244
|
|
|
1797
2245
|
<!-- Saved variants -->
|
|
1798
2246
|
<div class="ctrl-section" style="margin-top:16px">
|
|
1799
|
-
<h3>
|
|
2247
|
+
<h3>Your Worlds</h3>
|
|
1800
2248
|
<div id="variant-list"><div style="font-size:11px;color:#333">No saved variants yet</div></div>
|
|
1801
2249
|
</div>
|
|
1802
2250
|
|
|
1803
2251
|
<!-- Integration Quick-Start -->
|
|
1804
2252
|
<div class="ctrl-section" style="margin-top:16px">
|
|
1805
|
-
<h3>
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
<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>
|
|
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>
|
|
1809
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>
|
|
1810
2344
|
verdict = evaluate(
|
|
1811
|
-
actor=<span class="str">"
|
|
1812
|
-
action
|
|
1813
|
-
world=<span class="str">"
|
|
2345
|
+
actor=<span class="str">"Harry"</span>,
|
|
2346
|
+
action=action,
|
|
2347
|
+
world=<span class="str">"research"</span>
|
|
1814
2348
|
)
|
|
1815
2349
|
|
|
1816
|
-
<span class="
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
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>
|
|
1820
2356
|
</div>
|
|
1821
|
-
|
|
1822
|
-
|
|
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>
|
|
1823
2402
|
</div>
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
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>
|
|
2434
|
+
</div>
|
|
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
|
|
1827
2441
|
<span style="display:inline-block;padding:2px 6px;background:#052e16;color:#4ade80;border-radius:3px;margin-left:4px">ALLOW</span> proceeds
|
|
1828
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>
|
|
1829
2446
|
</div>
|
|
2447
|
+
</div>
|
|
1830
2448
|
|
|
1831
2449
|
<!-- Session Report Panel -->
|
|
1832
2450
|
<div class="ctrl-section" id="session-panel">
|
|
@@ -1850,86 +2468,308 @@ verdict = evaluate(
|
|
|
1850
2468
|
|
|
1851
2469
|
<!-- RIGHT: VIEWER -->
|
|
1852
2470
|
<div class="viewer">
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
<div class="metric-box"><div class="value" id="m-volatility">--</div><div class="label">Volatility</div></div>
|
|
1859
|
-
<div class="metric-box"><div class="value" id="m-round">--</div><div class="label">Round</div></div>
|
|
1860
|
-
<div class="metric-box"><div class="value" id="m-interventions">0</div><div class="label">Interventions</div></div>
|
|
1861
|
-
</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>
|
|
1862
2476
|
</div>
|
|
1863
|
-
<div class="
|
|
1864
|
-
<
|
|
1865
|
-
|
|
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>
|
|
1866
2490
|
</div>
|
|
2491
|
+
<div class="outcome-context" id="outcome-context"></div>
|
|
1867
2492
|
</div>
|
|
1868
2493
|
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
</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>
|
|
1875
2499
|
</div>
|
|
1876
|
-
<div class="
|
|
1877
|
-
<
|
|
1878
|
-
<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
|
|
1879
2502
|
</div>
|
|
2503
|
+
<div class="activity-timeline" id="activity-timeline"></div>
|
|
1880
2504
|
</div>
|
|
1881
2505
|
|
|
1882
|
-
<!--
|
|
1883
|
-
<div
|
|
1884
|
-
<
|
|
1885
|
-
|
|
1886
|
-
<
|
|
1887
|
-
</div>
|
|
1888
|
-
<div class="ss-rule" id="ss-rule"></div>
|
|
1889
|
-
<div class="ss-scale" id="ss-scale"></div>
|
|
1890
|
-
<div class="ss-flow">
|
|
1891
|
-
<span>Rule</span><span class="ss-flow-arrow">→</span>
|
|
1892
|
-
<span>Behavioral Shift</span><span class="ss-flow-arrow">→</span>
|
|
1893
|
-
<span>Emergent Pattern</span><span class="ss-flow-arrow">→</span>
|
|
1894
|
-
<span>System Outcome</span>
|
|
1895
|
-
</div>
|
|
1896
|
-
<div class="ss-body">
|
|
1897
|
-
<div class="ss-section">
|
|
1898
|
-
<div class="ss-section-label">Behavioral Shift</div>
|
|
1899
|
-
<div class="ss-adapt-rate" id="ss-adapt-rate"></div>
|
|
1900
|
-
<div class="ss-adapt-desc" id="ss-adapt-desc"></div>
|
|
1901
|
-
<div id="ss-shifts"></div>
|
|
1902
|
-
</div>
|
|
1903
|
-
<div class="ss-section">
|
|
1904
|
-
<div class="ss-section-label">What Emerged</div>
|
|
1905
|
-
<div id="ss-patterns"></div>
|
|
1906
|
-
</div>
|
|
1907
|
-
<div class="ss-section">
|
|
1908
|
-
<div class="ss-section-label">System Outcome</div>
|
|
1909
|
-
<div id="ss-impacts"></div>
|
|
1910
|
-
</div>
|
|
1911
|
-
<div class="ss-section">
|
|
1912
|
-
<div class="ss-section-label">What Actually Happened</div>
|
|
1913
|
-
<div class="ss-narrative" id="ss-narrative"></div>
|
|
1914
|
-
</div>
|
|
1915
|
-
</div>
|
|
1916
|
-
<button class="ss-raw-toggle" id="ss-raw-toggle">
|
|
1917
|
-
<span class="arrow">▶</span> View raw detail
|
|
1918
|
-
</button>
|
|
1919
|
-
<div class="ss-raw-detail" id="ss-raw-detail">
|
|
1920
|
-
<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>
|
|
1921
2511
|
</div>
|
|
1922
2512
|
</div>
|
|
1923
2513
|
|
|
1924
|
-
|
|
1925
|
-
|
|
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>
|
|
1926
2531
|
<div id="log"></div>
|
|
1927
2532
|
</div>
|
|
1928
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>
|
|
1929
2556
|
</div>
|
|
1930
2557
|
|
|
1931
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js"><\/script>
|
|
1932
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
|
+
|
|
1933
2773
|
// ============================================
|
|
1934
2774
|
// STATE
|
|
1935
2775
|
// ============================================
|
|
@@ -1939,12 +2779,12 @@ let narratives = {};
|
|
|
1939
2779
|
let currentWorld = null;
|
|
1940
2780
|
let injectedEvents = [];
|
|
1941
2781
|
let totalInterventions = 0;
|
|
1942
|
-
let
|
|
1943
|
-
let governedImpacts = [];
|
|
1944
|
-
let chartLabels = [];
|
|
1945
|
-
let chart = null;
|
|
2782
|
+
let totalActions = 0;
|
|
1946
2783
|
let narrativeEventsByRound = {}; // { round: [{ id, headline, severity }] }
|
|
1947
2784
|
let ruleImpactTracker = {}; // { ruleId: { blocks: N, label: string } }
|
|
2785
|
+
let behaviorLog = []; // { agent, action, status, reason, ts }
|
|
2786
|
+
let latestStability = 0;
|
|
2787
|
+
let latestVolatility = 0;
|
|
1948
2788
|
|
|
1949
2789
|
const statusEl = document.getElementById('status');
|
|
1950
2790
|
const worldSelect = document.getElementById('world-select');
|
|
@@ -1963,6 +2803,79 @@ const activeInvEl = document.getElementById('active-invariants');
|
|
|
1963
2803
|
const engineSelect = document.getElementById('engine-select');
|
|
1964
2804
|
const engineStatusEl = document.getElementById('engine-status');
|
|
1965
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
|
+
}
|
|
1966
2879
|
|
|
1967
2880
|
// ============================================
|
|
1968
2881
|
// INIT — Load worlds, scenarios, narratives, adapters
|
|
@@ -2018,6 +2931,9 @@ async function init() {
|
|
|
2018
2931
|
// Load saved variants
|
|
2019
2932
|
await loadVariants();
|
|
2020
2933
|
|
|
2934
|
+
// Populate base world selector for custom rules mode
|
|
2935
|
+
populateBaseWorldSelect();
|
|
2936
|
+
|
|
2021
2937
|
// Connect SSE
|
|
2022
2938
|
connectSSE();
|
|
2023
2939
|
}
|
|
@@ -2130,10 +3046,6 @@ runBtn.addEventListener('click', async () => {
|
|
|
2130
3046
|
|
|
2131
3047
|
// Reset viewer state
|
|
2132
3048
|
totalInterventions = 0;
|
|
2133
|
-
baselineImpacts = [];
|
|
2134
|
-
governedImpacts = [];
|
|
2135
|
-
chartLabels = [];
|
|
2136
|
-
if (chart) { chart.destroy(); chart = null; }
|
|
2137
3049
|
agentsEl.innerHTML = '';
|
|
2138
3050
|
logEl.innerHTML = '';
|
|
2139
3051
|
document.getElementById('m-stability').textContent = '--';
|
|
@@ -2201,28 +3113,7 @@ function connectSSE() {
|
|
|
2201
3113
|
}
|
|
2202
3114
|
|
|
2203
3115
|
function initChart() {
|
|
2204
|
-
|
|
2205
|
-
const ctx = document.getElementById('chart');
|
|
2206
|
-
chart = new Chart(ctx, {
|
|
2207
|
-
type: 'line',
|
|
2208
|
-
data: {
|
|
2209
|
-
labels: chartLabels,
|
|
2210
|
-
datasets: [
|
|
2211
|
-
{ label: 'Baseline', data: baselineImpacts, borderColor: '#ef4444', backgroundColor: 'rgba(239,68,68,0.1)', fill: true, tension: 0.3, pointRadius: 3 },
|
|
2212
|
-
{ label: 'Governed', data: governedImpacts, borderColor: '#4ade80', backgroundColor: 'rgba(74,222,128,0.1)', fill: true, tension: 0.3, pointRadius: 3 },
|
|
2213
|
-
]
|
|
2214
|
-
},
|
|
2215
|
-
options: {
|
|
2216
|
-
animation: { duration: 400 },
|
|
2217
|
-
responsive: true,
|
|
2218
|
-
maintainAspectRatio: false,
|
|
2219
|
-
plugins: { legend: { labels: { color: getComputedStyle(document.body).getPropertyValue('--text-muted').trim() || '#888', font: { family: 'monospace', size: 10 } } } },
|
|
2220
|
-
scales: {
|
|
2221
|
-
x: { ticks: { color: getComputedStyle(document.body).getPropertyValue('--text-muted').trim() || '#888' }, grid: { color: getComputedStyle(document.body).getPropertyValue('--border').trim() || '#2a2a2a' } },
|
|
2222
|
-
y: { ticks: { color: getComputedStyle(document.body).getPropertyValue('--text-muted').trim() || '#888' }, grid: { color: getComputedStyle(document.body).getPropertyValue('--border').trim() || '#2a2a2a' }, min: -1, max: 1 }
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
});
|
|
3116
|
+
// Chart removed — no longer needed in outcome-first view
|
|
2226
3117
|
}
|
|
2227
3118
|
|
|
2228
3119
|
function addLog(msg, cls) {
|
|
@@ -2333,6 +3224,84 @@ function renderAgents(reactions) {
|
|
|
2333
3224
|
agentsEl.innerHTML = html;
|
|
2334
3225
|
}
|
|
2335
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
|
+
|
|
2336
3305
|
function handleEvent(event) {
|
|
2337
3306
|
if (event.type === 'meta') {
|
|
2338
3307
|
statusEl.className = 'status live';
|
|
@@ -2415,44 +3384,59 @@ function handleEvent(event) {
|
|
|
2415
3384
|
'<div class="rule-impact" data-impact-id="' + g.id + '"></div>' +
|
|
2416
3385
|
'</div>';
|
|
2417
3386
|
}).join('');
|
|
2418
|
-
initChart();
|
|
2419
3387
|
}
|
|
2420
3388
|
|
|
2421
3389
|
if (event.type === 'round') {
|
|
2422
|
-
|
|
2423
|
-
chartLabels.push('R' + event.round);
|
|
2424
|
-
baselineImpacts.push(event.avgImpact);
|
|
2425
|
-
} else {
|
|
2426
|
-
governedImpacts.push(event.avgImpact);
|
|
2427
|
-
}
|
|
2428
|
-
if (chart) chart.update();
|
|
3390
|
+
const isBridge = event.reactions && event.reactions[0] && event.reactions[0].trigger === 'bridge';
|
|
2429
3391
|
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
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
|
|
2434
3395
|
|
|
3396
|
+
// Track totals
|
|
3397
|
+
totalActions += event.reactions ? event.reactions.length : 0;
|
|
2435
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) + '%';
|
|
2436
3408
|
document.getElementById('m-interventions').textContent = totalInterventions;
|
|
2437
3409
|
|
|
2438
|
-
// Track system shifts
|
|
3410
|
+
// Track system shifts (data collection, not displayed)
|
|
2439
3411
|
trackShift(event);
|
|
2440
3412
|
|
|
2441
|
-
//
|
|
3413
|
+
// Update all visible panels
|
|
3414
|
+
updateAllPanels();
|
|
3415
|
+
|
|
3416
|
+
// Add trace entry for audit
|
|
2442
3417
|
addTraceRound(event);
|
|
2443
3418
|
}
|
|
2444
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
|
+
|
|
2445
3427
|
if (event.type === 'complete') {
|
|
2446
3428
|
statusEl.className = 'status complete';
|
|
2447
3429
|
statusEl.textContent = 'COMPLETE';
|
|
2448
3430
|
const r = event.result;
|
|
2449
3431
|
if (r.governed) {
|
|
3432
|
+
latestStability = r.governed.metrics.stabilityScore;
|
|
3433
|
+
latestVolatility = r.governed.metrics.maxVolatility || latestVolatility;
|
|
2450
3434
|
document.getElementById('m-stability').textContent = (r.governed.metrics.stabilityScore * 100).toFixed(0) + '%';
|
|
2451
|
-
|
|
2452
|
-
addLog('Complete. Governance effectiveness: ' + (r.comparison.governanceEffectiveness * 100).toFixed(0) + '%');
|
|
3435
|
+
addLog('Simulation complete');
|
|
2453
3436
|
renderSystemShift(r);
|
|
2454
3437
|
renderRuleImpacts(r);
|
|
2455
3438
|
renderEnforcementClassification(r.enforcementClassification || []);
|
|
3439
|
+
updateAllPanels();
|
|
2456
3440
|
lastSimResult = {
|
|
2457
3441
|
stability: r.governed.metrics.stabilityScore,
|
|
2458
3442
|
volatility: r.governed.metrics.maxVolatility,
|
|
@@ -2531,20 +3515,37 @@ const ssImpactsEl = document.getElementById('ss-impacts');
|
|
|
2531
3515
|
const ssNarrativeEl = document.getElementById('ss-narrative');
|
|
2532
3516
|
|
|
2533
3517
|
// Raw detail toggle
|
|
2534
|
-
document.getElementById('ss-raw-toggle')
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
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
|
+
}
|
|
2538
3525
|
|
|
2539
3526
|
let shiftTracker = { blocks: 0, total: 0, shifts: {}, patterns: [], baselineVol: 0, governedVol: 0, narrative: '', rawGoverned: [] };
|
|
2540
3527
|
|
|
2541
3528
|
function resetShiftTracker() {
|
|
2542
3529
|
shiftTracker = { blocks: 0, total: 0, shifts: {}, patterns: [], baselineVol: 0, governedVol: 0, narrative: '', rawGoverned: [] };
|
|
2543
|
-
ssCard.classList.remove('visible');
|
|
3530
|
+
if (ssCard) ssCard.classList.remove('visible');
|
|
2544
3531
|
var rawToggle = document.getElementById('ss-raw-toggle');
|
|
2545
3532
|
var rawDetail = document.getElementById('ss-raw-detail');
|
|
2546
3533
|
if (rawToggle) rawToggle.classList.remove('open');
|
|
2547
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>';
|
|
2548
3549
|
}
|
|
2549
3550
|
|
|
2550
3551
|
function trackShift(event) {
|
|
@@ -2587,6 +3588,7 @@ function trackShift(event) {
|
|
|
2587
3588
|
|
|
2588
3589
|
function renderSystemShift(result) {
|
|
2589
3590
|
if (shiftTracker.blocks === 0) return;
|
|
3591
|
+
if (!ssCard) return; // System shift card removed from visible UI
|
|
2590
3592
|
|
|
2591
3593
|
var adaptRate = shiftTracker.total > 0 ? Math.round((shiftTracker.blocks / shiftTracker.total) * 100) : 0;
|
|
2592
3594
|
|
|
@@ -2877,18 +3879,6 @@ function applyTheme(theme) {
|
|
|
2877
3879
|
themeToggleBtn.textContent = 'Light Mode';
|
|
2878
3880
|
}
|
|
2879
3881
|
localStorage.setItem('nv-theme', theme);
|
|
2880
|
-
// Update chart colors if chart exists
|
|
2881
|
-
if (chart && chart.options) {
|
|
2882
|
-
const gridColor = theme === 'light' ? '#d4d4d4' : '#2a2a2a';
|
|
2883
|
-
const tickColor = theme === 'light' ? '#666' : '#888';
|
|
2884
|
-
const legendColor = theme === 'light' ? '#444' : '#888';
|
|
2885
|
-
chart.options.scales.x.ticks.color = tickColor;
|
|
2886
|
-
chart.options.scales.x.grid.color = gridColor;
|
|
2887
|
-
chart.options.scales.y.ticks.color = tickColor;
|
|
2888
|
-
chart.options.scales.y.grid.color = gridColor;
|
|
2889
|
-
chart.options.plugins.legend.labels.color = legendColor;
|
|
2890
|
-
chart.update();
|
|
2891
|
-
}
|
|
2892
3882
|
}
|
|
2893
3883
|
themeToggleBtn.addEventListener('click', () => {
|
|
2894
3884
|
const current = document.body.classList.contains('light') ? 'light' : 'dark';
|
|
@@ -2898,6 +3888,257 @@ themeToggleBtn.addEventListener('click', () => {
|
|
|
2898
3888
|
const savedTheme = localStorage.getItem('nv-theme');
|
|
2899
3889
|
if (savedTheme) applyTheme(savedTheme);
|
|
2900
3890
|
|
|
3891
|
+
// ============================================
|
|
3892
|
+
// WORLD SOURCE SWITCHING
|
|
3893
|
+
// ============================================
|
|
3894
|
+
let currentWorldSource = 'preset';
|
|
3895
|
+
const worldSourceTabs = document.querySelectorAll('.ws-tab');
|
|
3896
|
+
const sourcePresetPanel = document.getElementById('source-preset');
|
|
3897
|
+
const sourceCustomPanel = document.getElementById('source-custom');
|
|
3898
|
+
const sourceUploadPanel = document.getElementById('source-upload');
|
|
3899
|
+
|
|
3900
|
+
worldSourceTabs.forEach(tab => {
|
|
3901
|
+
tab.addEventListener('click', () => {
|
|
3902
|
+
const source = tab.dataset.source;
|
|
3903
|
+
if (source === currentWorldSource) return;
|
|
3904
|
+
|
|
3905
|
+
currentWorldSource = source;
|
|
3906
|
+
|
|
3907
|
+
// Update tab visuals
|
|
3908
|
+
worldSourceTabs.forEach(t => t.classList.remove('active'));
|
|
3909
|
+
tab.classList.add('active');
|
|
3910
|
+
tab.querySelector('input').checked = true;
|
|
3911
|
+
|
|
3912
|
+
// Show/hide panels
|
|
3913
|
+
sourcePresetPanel.style.display = source === 'preset' ? '' : 'none';
|
|
3914
|
+
sourceCustomPanel.style.display = source === 'custom' ? '' : 'none';
|
|
3915
|
+
sourceUploadPanel.style.display = source === 'upload' ? '' : 'none';
|
|
3916
|
+
});
|
|
3917
|
+
});
|
|
3918
|
+
|
|
3919
|
+
// Populate base world selector in custom rules panel
|
|
3920
|
+
function populateBaseWorldSelect() {
|
|
3921
|
+
const select = document.getElementById('custom-base-world');
|
|
3922
|
+
if (!select) return;
|
|
3923
|
+
worlds.forEach(w => {
|
|
3924
|
+
const opt = document.createElement('option');
|
|
3925
|
+
opt.value = w.id;
|
|
3926
|
+
opt.textContent = w.title;
|
|
3927
|
+
select.appendChild(opt);
|
|
3928
|
+
});
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
// ============================================
|
|
3932
|
+
// WORLD ACTION BAR
|
|
3933
|
+
// ============================================
|
|
3934
|
+
|
|
3935
|
+
// + New World
|
|
3936
|
+
document.getElementById('new-world-btn').addEventListener('click', () => {
|
|
3937
|
+
// Switch to custom rules mode
|
|
3938
|
+
currentWorldSource = 'custom';
|
|
3939
|
+
worldSourceTabs.forEach(t => {
|
|
3940
|
+
t.classList.toggle('active', t.dataset.source === 'custom');
|
|
3941
|
+
t.querySelector('input').checked = t.dataset.source === 'custom';
|
|
3942
|
+
});
|
|
3943
|
+
sourcePresetPanel.style.display = 'none';
|
|
3944
|
+
sourceCustomPanel.style.display = '';
|
|
3945
|
+
sourceUploadPanel.style.display = 'none';
|
|
3946
|
+
|
|
3947
|
+
// Clear everything
|
|
3948
|
+
document.getElementById('custom-world-name').value = '';
|
|
3949
|
+
document.getElementById('custom-world-thesis').value = '';
|
|
3950
|
+
document.getElementById('rule-input').value = '';
|
|
3951
|
+
document.getElementById('parsed-rules').innerHTML = '';
|
|
3952
|
+
document.getElementById('rule-status').textContent = '';
|
|
3953
|
+
document.getElementById('rule-status').className = 'rule-status';
|
|
3954
|
+
document.getElementById('custom-base-world').value = '';
|
|
3955
|
+
|
|
3956
|
+
// Clear active rules server-side
|
|
3957
|
+
fetch('/api/clear-rules', { method: 'POST' });
|
|
3958
|
+
|
|
3959
|
+
// Reset right panel
|
|
3960
|
+
document.getElementById('active-invariants').innerHTML = '<div style="font-size:11px;color:var(--text-muted)">No rules loaded. Define your world.</div>';
|
|
3961
|
+
});
|
|
3962
|
+
|
|
3963
|
+
// Clear Rules
|
|
3964
|
+
document.getElementById('clear-rules-btn').addEventListener('click', async () => {
|
|
3965
|
+
await fetch('/api/clear-rules', { method: 'POST' });
|
|
3966
|
+
|
|
3967
|
+
// Clear rule editor UI
|
|
3968
|
+
const ruleInput = document.getElementById('rule-input');
|
|
3969
|
+
if (ruleInput) ruleInput.value = '';
|
|
3970
|
+
const parsed = document.getElementById('parsed-rules');
|
|
3971
|
+
if (parsed) parsed.innerHTML = '';
|
|
3972
|
+
const status = document.getElementById('rule-status');
|
|
3973
|
+
if (status) { status.textContent = 'Rules cleared.'; status.className = 'rule-status success'; }
|
|
3974
|
+
|
|
3975
|
+
// Clear upload state
|
|
3976
|
+
const uploadStatus = document.getElementById('upload-status');
|
|
3977
|
+
if (uploadStatus) { uploadStatus.textContent = 'Rules cleared.'; uploadStatus.className = 'rule-status success'; }
|
|
3978
|
+
const loadedInfo = document.getElementById('loaded-world-info');
|
|
3979
|
+
if (loadedInfo) loadedInfo.style.display = 'none';
|
|
3980
|
+
});
|
|
3981
|
+
|
|
3982
|
+
// Load World File (switch to upload tab)
|
|
3983
|
+
document.getElementById('load-file-btn').addEventListener('click', () => {
|
|
3984
|
+
currentWorldSource = 'upload';
|
|
3985
|
+
worldSourceTabs.forEach(t => {
|
|
3986
|
+
t.classList.toggle('active', t.dataset.source === 'upload');
|
|
3987
|
+
t.querySelector('input').checked = t.dataset.source === 'upload';
|
|
3988
|
+
});
|
|
3989
|
+
sourcePresetPanel.style.display = 'none';
|
|
3990
|
+
sourceCustomPanel.style.display = 'none';
|
|
3991
|
+
sourceUploadPanel.style.display = '';
|
|
3992
|
+
});
|
|
3993
|
+
|
|
3994
|
+
// Save as World File (export)
|
|
3995
|
+
document.getElementById('export-world-btn').addEventListener('click', async () => {
|
|
3996
|
+
try {
|
|
3997
|
+
const resp = await fetch('/api/export-world');
|
|
3998
|
+
const data = await resp.json();
|
|
3999
|
+
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
4000
|
+
const url = URL.createObjectURL(blob);
|
|
4001
|
+
const a = document.createElement('a');
|
|
4002
|
+
a.href = url;
|
|
4003
|
+
a.download = (currentWorld ? currentWorld.id : 'custom') + '-world.json';
|
|
4004
|
+
document.body.appendChild(a);
|
|
4005
|
+
a.click();
|
|
4006
|
+
document.body.removeChild(a);
|
|
4007
|
+
URL.revokeObjectURL(url);
|
|
4008
|
+
} catch (err) {
|
|
4009
|
+
alert('Export failed: ' + err.message);
|
|
4010
|
+
}
|
|
4011
|
+
});
|
|
4012
|
+
|
|
4013
|
+
// ============================================
|
|
4014
|
+
// WORLD FILE UPLOAD / PASTE
|
|
4015
|
+
// ============================================
|
|
4016
|
+
const uploadZone = document.getElementById('upload-zone');
|
|
4017
|
+
const uploadFileInput = document.getElementById('upload-file-input');
|
|
4018
|
+
const uploadBrowseBtn = document.getElementById('upload-browse-btn');
|
|
4019
|
+
const worldJsonInput = document.getElementById('world-json-input');
|
|
4020
|
+
const loadWorldBtn = document.getElementById('load-world-btn');
|
|
4021
|
+
const uploadStatusEl = document.getElementById('upload-status');
|
|
4022
|
+
|
|
4023
|
+
// Drag and drop
|
|
4024
|
+
uploadZone.addEventListener('dragover', (e) => { e.preventDefault(); uploadZone.classList.add('dragover'); });
|
|
4025
|
+
uploadZone.addEventListener('dragleave', () => { uploadZone.classList.remove('dragover'); });
|
|
4026
|
+
uploadZone.addEventListener('drop', (e) => {
|
|
4027
|
+
e.preventDefault();
|
|
4028
|
+
uploadZone.classList.remove('dragover');
|
|
4029
|
+
const file = e.dataTransfer.files[0];
|
|
4030
|
+
if (file) readWorldFile(file);
|
|
4031
|
+
});
|
|
4032
|
+
|
|
4033
|
+
// Browse button
|
|
4034
|
+
uploadBrowseBtn.addEventListener('click', (e) => { e.stopPropagation(); uploadFileInput.click(); });
|
|
4035
|
+
uploadFileInput.addEventListener('change', () => {
|
|
4036
|
+
if (uploadFileInput.files[0]) readWorldFile(uploadFileInput.files[0]);
|
|
4037
|
+
});
|
|
4038
|
+
|
|
4039
|
+
// Click zone to browse
|
|
4040
|
+
uploadZone.addEventListener('click', () => { uploadFileInput.click(); });
|
|
4041
|
+
|
|
4042
|
+
function readWorldFile(file) {
|
|
4043
|
+
const reader = new FileReader();
|
|
4044
|
+
reader.onload = (e) => {
|
|
4045
|
+
worldJsonInput.value = e.target.result;
|
|
4046
|
+
uploadStatusEl.textContent = 'File loaded: ' + file.name + '. Click "Load into Runtime".';
|
|
4047
|
+
uploadStatusEl.className = 'rule-status success';
|
|
4048
|
+
};
|
|
4049
|
+
reader.readAsText(file);
|
|
4050
|
+
}
|
|
4051
|
+
|
|
4052
|
+
// Load into Runtime
|
|
4053
|
+
loadWorldBtn.addEventListener('click', async () => {
|
|
4054
|
+
const jsonText = worldJsonInput.value.trim();
|
|
4055
|
+
if (!jsonText) {
|
|
4056
|
+
uploadStatusEl.textContent = 'Paste or upload a world file first.';
|
|
4057
|
+
uploadStatusEl.className = 'rule-status error';
|
|
4058
|
+
return;
|
|
4059
|
+
}
|
|
4060
|
+
|
|
4061
|
+
let worldData;
|
|
4062
|
+
try {
|
|
4063
|
+
worldData = JSON.parse(jsonText);
|
|
4064
|
+
} catch (err) {
|
|
4065
|
+
uploadStatusEl.textContent = 'Invalid JSON: ' + err.message;
|
|
4066
|
+
uploadStatusEl.className = 'rule-status error';
|
|
4067
|
+
return;
|
|
4068
|
+
}
|
|
4069
|
+
|
|
4070
|
+
// Normalize: if the JSON is { world: {...} } or just {...}
|
|
4071
|
+
const worldPayload = worldData.world || worldData;
|
|
4072
|
+
|
|
4073
|
+
loadWorldBtn.textContent = 'Loading...';
|
|
4074
|
+
loadWorldBtn.disabled = true;
|
|
4075
|
+
|
|
4076
|
+
try {
|
|
4077
|
+
const resp = await fetch('/api/load-world-file', {
|
|
4078
|
+
method: 'POST',
|
|
4079
|
+
headers: { 'Content-Type': 'application/json' },
|
|
4080
|
+
body: JSON.stringify({ world: worldPayload }),
|
|
4081
|
+
});
|
|
4082
|
+
const result = await resp.json();
|
|
4083
|
+
|
|
4084
|
+
if (result.error) {
|
|
4085
|
+
uploadStatusEl.textContent = result.error;
|
|
4086
|
+
uploadStatusEl.className = 'rule-status error';
|
|
4087
|
+
} else {
|
|
4088
|
+
uploadStatusEl.textContent = result.message;
|
|
4089
|
+
uploadStatusEl.className = 'rule-status success';
|
|
4090
|
+
|
|
4091
|
+
// Show loaded world info
|
|
4092
|
+
const infoEl = document.getElementById('loaded-world-info');
|
|
4093
|
+
infoEl.style.display = '';
|
|
4094
|
+
document.getElementById('lw-name').textContent = result.world.title;
|
|
4095
|
+
document.getElementById('lw-thesis').textContent = '"' + result.world.thesis + '"';
|
|
4096
|
+
document.getElementById('lw-stats').textContent =
|
|
4097
|
+
result.world.invariants.length + ' invariants, ' +
|
|
4098
|
+
result.world.gates.length + ' gates, ' +
|
|
4099
|
+
result.rulesApplied + ' rules';
|
|
4100
|
+
|
|
4101
|
+
// Update active invariants in right panel
|
|
4102
|
+
const invHtml = result.world.invariants.map(inv =>
|
|
4103
|
+
'<div class="inv-item">[' + inv.id + '] ' + inv.description + '</div>'
|
|
4104
|
+
).join('') + result.world.gates.map(g =>
|
|
4105
|
+
'<div class="inv-item" style="color:' + (g.severity === 'critical' ? 'var(--red)' : 'var(--yellow)') + '">[' + g.id + '] ' + g.label + '</div>'
|
|
4106
|
+
).join('');
|
|
4107
|
+
activeInvEl.innerHTML = invHtml || '<div style="font-size:11px;color:var(--text-muted)">No invariants defined</div>';
|
|
4108
|
+
|
|
4109
|
+
// Update state variables if present
|
|
4110
|
+
if (result.world.stateVariables && result.world.stateVariables.length > 0) {
|
|
4111
|
+
// Store as a pseudo-world so sliders render
|
|
4112
|
+
currentWorld = {
|
|
4113
|
+
id: 'custom-world',
|
|
4114
|
+
title: result.world.title,
|
|
4115
|
+
thesis: result.world.thesis,
|
|
4116
|
+
stateVariables: result.world.stateVariables,
|
|
4117
|
+
invariants: result.world.invariants,
|
|
4118
|
+
gates: result.world.gates,
|
|
4119
|
+
};
|
|
4120
|
+
selectWorld('custom-world');
|
|
4121
|
+
} else {
|
|
4122
|
+
// Just set current world reference
|
|
4123
|
+
currentWorld = {
|
|
4124
|
+
id: 'custom-world',
|
|
4125
|
+
title: result.world.title,
|
|
4126
|
+
thesis: result.world.thesis,
|
|
4127
|
+
stateVariables: [],
|
|
4128
|
+
invariants: result.world.invariants,
|
|
4129
|
+
gates: result.world.gates,
|
|
4130
|
+
};
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
} catch (err) {
|
|
4134
|
+
uploadStatusEl.textContent = 'Error: ' + err.message;
|
|
4135
|
+
uploadStatusEl.className = 'rule-status error';
|
|
4136
|
+
}
|
|
4137
|
+
|
|
4138
|
+
loadWorldBtn.textContent = 'Load into Runtime';
|
|
4139
|
+
loadWorldBtn.disabled = false;
|
|
4140
|
+
});
|
|
4141
|
+
|
|
2901
4142
|
// ============================================
|
|
2902
4143
|
// PLAIN-ENGLISH RULE EDITOR
|
|
2903
4144
|
// ============================================
|
|
@@ -2920,7 +4161,7 @@ parseRulesBtn.addEventListener('click', async () => {
|
|
|
2920
4161
|
const resp = await fetch('/api/parse-rules', {
|
|
2921
4162
|
method: 'POST',
|
|
2922
4163
|
headers: { 'Content-Type': 'application/json' },
|
|
2923
|
-
body: JSON.stringify({ text, worldId: currentWorld ? currentWorld.id : '
|
|
4164
|
+
body: JSON.stringify({ text, worldId: currentWorld ? currentWorld.id : 'social_simulation' }),
|
|
2924
4165
|
});
|
|
2925
4166
|
const data = await resp.json();
|
|
2926
4167
|
|
|
@@ -2947,25 +4188,84 @@ parseRulesBtn.addEventListener('click', async () => {
|
|
|
2947
4188
|
}).join('');
|
|
2948
4189
|
|
|
2949
4190
|
if (parsedRuleData.length > 0) {
|
|
2950
|
-
|
|
4191
|
+
const btnLabel = currentWorldSource === 'custom' ? 'Generate World with ' + parsedRuleData.length + ' Rule' + (parsedRuleData.length > 1 ? 's' : '') : 'Apply ' + parsedRuleData.length + ' Rule' + (parsedRuleData.length > 1 ? 's' : '') + ' to Simulation';
|
|
4192
|
+
parsedRulesEl.innerHTML += '<button class="btn btn-apply-rules" id="apply-rules-btn">' + btnLabel + '</button>';
|
|
2951
4193
|
document.getElementById('apply-rules-btn').addEventListener('click', async () => {
|
|
2952
4194
|
try {
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
4195
|
+
if (currentWorldSource === 'custom') {
|
|
4196
|
+
// CUSTOM MODE: Generate a full governed world (state vars, gates, thesis)
|
|
4197
|
+
const customName = document.getElementById('custom-world-name');
|
|
4198
|
+
const worldName = (customName && customName.value) ? customName.value : 'Custom World';
|
|
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('');
|
|
4261
|
+
}
|
|
2962
4262
|
}
|
|
2963
4263
|
} catch (err) {
|
|
2964
4264
|
ruleStatusEl.textContent = 'Error applying rules: ' + err.message;
|
|
2965
4265
|
ruleStatusEl.className = 'rule-status error';
|
|
2966
4266
|
}
|
|
2967
4267
|
});
|
|
2968
|
-
ruleStatusEl.textContent = 'Parsed ' + parsedRuleData.length + ' rule(s). Review and click Apply.';
|
|
4268
|
+
ruleStatusEl.textContent = 'Parsed ' + parsedRuleData.length + ' rule(s). Review and click ' + (currentWorldSource === 'custom' ? 'Generate.' : 'Apply.');
|
|
2969
4269
|
ruleStatusEl.className = 'rule-status success';
|
|
2970
4270
|
}
|
|
2971
4271
|
}
|