@neuroverseos/nv-sim 0.1.2 → 0.1.6

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.
Files changed (52) hide show
  1. package/README.md +376 -66
  2. package/dist/adapters/mirofish.js +461 -0
  3. package/dist/adapters/scienceclaw.js +750 -0
  4. package/dist/assets/index-CHmUN8s0.js +532 -0
  5. package/dist/assets/index-DWgMnB7I.css +1 -0
  6. package/dist/assets/mirotir-logo-DUexumBH.svg +185 -0
  7. package/dist/assets/reportEngine-BVdQ2_nW.js +1 -0
  8. package/dist/components/ConstraintsPanel.js +11 -0
  9. package/dist/components/StakeholderBuilder.js +32 -0
  10. package/dist/components/ui/badge.js +24 -0
  11. package/dist/components/ui/button.js +70 -0
  12. package/dist/components/ui/card.js +57 -0
  13. package/dist/components/ui/input.js +44 -0
  14. package/dist/components/ui/label.js +45 -0
  15. package/dist/components/ui/select.js +70 -0
  16. package/dist/engine/aiProvider.js +681 -0
  17. package/dist/engine/auditTrace.js +352 -0
  18. package/dist/engine/behavioralAnalysis.js +605 -0
  19. package/dist/engine/cli.js +1408 -299
  20. package/dist/engine/dynamicsGovernance.js +588 -0
  21. package/dist/engine/fullGovernedLoop.js +367 -0
  22. package/dist/engine/governance.js +8 -3
  23. package/dist/engine/governedSimulation.js +114 -17
  24. package/dist/engine/index.js +56 -1
  25. package/dist/engine/liveAdapter.js +342 -0
  26. package/dist/engine/liveVisualizer.js +3063 -0
  27. package/dist/engine/metrics/science.metrics.js +335 -0
  28. package/dist/engine/narrativeInjection.js +305 -0
  29. package/dist/engine/policyEnforcement.js +1611 -0
  30. package/dist/engine/policyEngine.js +799 -0
  31. package/dist/engine/primeRadiant.js +540 -0
  32. package/dist/engine/reasoningEngine.js +57 -3
  33. package/dist/engine/reportEngine.js +97 -0
  34. package/dist/engine/scenarioComparison.js +463 -0
  35. package/dist/engine/scenarioLibrary.js +231 -0
  36. package/dist/engine/swarmSimulation.js +54 -1
  37. package/dist/engine/worldComparison.js +358 -0
  38. package/dist/engine/worldStorage.js +232 -0
  39. package/dist/favicon.ico +0 -0
  40. package/dist/index.html +23 -0
  41. package/dist/lib/reasoningEngine.js +290 -0
  42. package/dist/lib/simulationAdapter.js +686 -0
  43. package/dist/lib/swarmParser.js +291 -0
  44. package/dist/lib/types.js +2 -0
  45. package/dist/lib/utils.js +8 -0
  46. package/dist/placeholder.svg +1 -0
  47. package/dist/robots.txt +14 -0
  48. package/dist/runtime/govern.js +473 -0
  49. package/dist/runtime/index.js +75 -0
  50. package/dist/runtime/types.js +11 -0
  51. package/package.json +17 -12
  52. package/variants/.gitkeep +0 -0
@@ -0,0 +1,231 @@
1
+ "use strict";
2
+ /**
3
+ * Scenario Library — Named Multi-Event Stress Scenarios
4
+ *
5
+ * The layer that turns NV-SIM from a simulation engine into
6
+ * a scenario modeling platform.
7
+ *
8
+ * Instead of manually wiring events:
9
+ * nv-sim compare --inject tanker_explosion@3,sanctions@5
10
+ *
11
+ * Users run named scenarios:
12
+ * nv-sim scenario taiwan_crisis
13
+ *
14
+ * Each scenario bundles a world preset + a sequence of narrative events
15
+ * that model a realistic multi-phase crisis.
16
+ *
17
+ * Usage:
18
+ * nv-sim scenario taiwan_crisis
19
+ * nv-sim scenario bank_run
20
+ * nv-sim scenario oil_shock --compare
21
+ * nv-sim scenarios # List all
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.SCENARIO_LIBRARY = void 0;
25
+ exports.resolveScenarioEvents = resolveScenarioEvents;
26
+ exports.getScenariosByCategory = getScenariosByCategory;
27
+ const narrativeInjection_1 = require("./narrativeInjection");
28
+ // ============================================
29
+ // BUILT-IN SCENARIOS
30
+ // ============================================
31
+ exports.SCENARIO_LIBRARY = {
32
+ // --- Geopolitical ---
33
+ taiwan_crisis: {
34
+ id: "taiwan_crisis",
35
+ title: "Taiwan Strait Crisis",
36
+ description: "Military escalation near Taiwan triggers cascading economic and diplomatic shocks",
37
+ world: "strait_of_hormuz",
38
+ events: [
39
+ "taiwan_exercises@2",
40
+ "sanctions@4",
41
+ "flash_crash_rumor@5",
42
+ "rate_cut@7",
43
+ ],
44
+ compareWorlds: ["trading"],
45
+ category: "geopolitical",
46
+ rounds: 8,
47
+ },
48
+ hormuz_blockade: {
49
+ id: "hormuz_blockade",
50
+ title: "Strait of Hormuz Blockade",
51
+ description: "Tanker attack escalates to full shipping disruption with energy market cascade",
52
+ world: "strait_of_hormuz",
53
+ events: [
54
+ "tanker_explosion@2",
55
+ "sanctions@4",
56
+ "oil_discovery@6",
57
+ ],
58
+ compareWorlds: ["gas_price_spike"],
59
+ category: "geopolitical",
60
+ rounds: 7,
61
+ },
62
+ // --- Financial ---
63
+ bank_run: {
64
+ id: "bank_run",
65
+ title: "Banking Crisis — Contagion Cascade",
66
+ description: "Major bank insolvency triggers contagion, liquidity crisis, and emergency intervention",
67
+ world: "trading",
68
+ events: [
69
+ "bank_collapse@2",
70
+ "flash_crash_rumor@3",
71
+ "rate_cut@4",
72
+ "stimulus_package@6",
73
+ ],
74
+ compareWorlds: ["gas_price_spike"],
75
+ category: "financial",
76
+ rounds: 7,
77
+ },
78
+ flash_cascade: {
79
+ id: "flash_cascade",
80
+ title: "Flash Crash — Multi-Phase Cascade",
81
+ description: "Algorithmic failure triggers chain reaction across interconnected markets",
82
+ world: "trading",
83
+ events: [
84
+ "flash_crash_rumor@1",
85
+ "bank_collapse@3",
86
+ "rate_cut@5",
87
+ ],
88
+ category: "financial",
89
+ rounds: 6,
90
+ },
91
+ // --- Energy ---
92
+ oil_shock: {
93
+ id: "oil_shock",
94
+ title: "Oil Supply Shock",
95
+ description: "Tanker attack + sanctions create compound energy crisis",
96
+ world: "gas_price_spike",
97
+ events: [
98
+ "tanker_explosion@2",
99
+ "sanctions@4",
100
+ "grid_failure@5",
101
+ "oil_discovery@7",
102
+ ],
103
+ compareWorlds: ["strait_of_hormuz"],
104
+ category: "energy",
105
+ rounds: 8,
106
+ },
107
+ energy_transition_shock: {
108
+ id: "energy_transition_shock",
109
+ title: "Energy Transition Disruption",
110
+ description: "Grid failure during rapid transition triggers regulatory and market cascade",
111
+ world: "gas_price_spike",
112
+ events: [
113
+ "grid_failure@2",
114
+ "regulation_shock@3",
115
+ "stimulus_package@5",
116
+ ],
117
+ category: "energy",
118
+ rounds: 6,
119
+ },
120
+ // --- Political ---
121
+ election_shock: {
122
+ id: "election_shock",
123
+ title: "Election Crisis",
124
+ description: "Political shock cascades into market and regulatory disruption",
125
+ world: "trading",
126
+ events: [
127
+ "candidate_indicted@2",
128
+ "flash_crash_rumor@3",
129
+ "regulation_shock@5",
130
+ "stimulus_package@6",
131
+ ],
132
+ category: "political",
133
+ rounds: 7,
134
+ },
135
+ ai_crackdown: {
136
+ id: "ai_crackdown",
137
+ title: "AI Regulatory Crackdown",
138
+ description: "Overnight AI regulation triggers market panic and sector realignment",
139
+ world: "ai_regulation_crisis",
140
+ events: [
141
+ "regulation_shock@2",
142
+ "flash_crash_rumor@3",
143
+ "diplomatic_breakthrough@5",
144
+ ],
145
+ compareWorlds: ["trading"],
146
+ category: "political",
147
+ rounds: 6,
148
+ },
149
+ // --- Compound ---
150
+ perfect_storm: {
151
+ id: "perfect_storm",
152
+ title: "Perfect Storm — Multi-Domain Crisis",
153
+ description: "Geopolitical, financial, and energy shocks converge simultaneously",
154
+ world: "trading",
155
+ events: [
156
+ "tanker_explosion@1",
157
+ "bank_collapse@2",
158
+ "sanctions@3",
159
+ "grid_failure@4",
160
+ "rate_cut@5",
161
+ "ceasefire@7",
162
+ ],
163
+ compareWorlds: ["strait_of_hormuz", "gas_price_spike"],
164
+ category: "compound",
165
+ rounds: 8,
166
+ },
167
+ black_swan: {
168
+ id: "black_swan",
169
+ title: "Black Swan Cascade",
170
+ description: "Extreme low-probability events in rapid succession",
171
+ world: "trading",
172
+ events: [
173
+ "bank_collapse@1",
174
+ "tanker_explosion@2",
175
+ "taiwan_exercises@3",
176
+ "grid_failure@4",
177
+ "regulation_shock@5",
178
+ ],
179
+ compareWorlds: ["strait_of_hormuz"],
180
+ category: "compound",
181
+ rounds: 6,
182
+ },
183
+ };
184
+ // ============================================
185
+ // SCENARIO RESOLUTION
186
+ // ============================================
187
+ /**
188
+ * Parse a scenario's event list into NarrativeEvent objects.
189
+ */
190
+ function resolveScenarioEvents(scenario) {
191
+ const events = [];
192
+ for (const spec of scenario.events) {
193
+ const atIdx = spec.lastIndexOf("@");
194
+ if (atIdx === -1)
195
+ continue;
196
+ const eventId = spec.slice(0, atIdx).trim();
197
+ const round = parseInt(spec.slice(atIdx + 1), 10);
198
+ if (isNaN(round))
199
+ continue;
200
+ const preset = narrativeInjection_1.NARRATIVE_PRESETS[eventId];
201
+ if (preset) {
202
+ events.push({ ...preset, round });
203
+ }
204
+ else {
205
+ events.push({
206
+ id: `custom_${round}`,
207
+ headline: eventId,
208
+ round,
209
+ severity: "major",
210
+ targets: [],
211
+ direction: "mixed",
212
+ propagation: "normal",
213
+ category: "custom",
214
+ });
215
+ }
216
+ }
217
+ return events;
218
+ }
219
+ /**
220
+ * Get all scenarios grouped by category.
221
+ */
222
+ function getScenariosByCategory() {
223
+ const grouped = {};
224
+ for (const scenario of Object.values(exports.SCENARIO_LIBRARY)) {
225
+ if (!grouped[scenario.category]) {
226
+ grouped[scenario.category] = [];
227
+ }
228
+ grouped[scenario.category].push(scenario);
229
+ }
230
+ return grouped;
231
+ }
@@ -19,6 +19,7 @@
19
19
  */
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
21
  exports.runSwarmSimulation = runSwarmSimulation;
22
+ const aiProvider_1 = require("./aiProvider");
22
23
  // ============================================
23
24
  // REACTION MODELS
24
25
  // ============================================
@@ -205,10 +206,16 @@ function findInflectionPoints(rounds) {
205
206
  * This is Echelon's native reaction model — NOT MiroFish.
206
207
  * It simulates how stakeholders react to different reasoning paths
207
208
  * over multiple rounds, detecting emergent dynamics.
209
+ *
210
+ * When ANTHROPIC_API_KEY is set, agents use Claude for real reasoning.
211
+ * Otherwise, falls back to the deterministic Math.random() model.
208
212
  */
209
213
  async function runSwarmSimulation(scenario, stakeholders, paths, config) {
210
214
  const roundCount = config.rounds ?? 3;
211
215
  const model = config.reaction_model ?? "mixed";
216
+ // Detect if AI reasoning is available and requested
217
+ const defaultProvider = (0, aiProvider_1.getDefaultProviderName)();
218
+ const useAI = config.ai_reasoning !== false && defaultProvider !== "deterministic";
212
219
  // Filter stakeholders if specific ones requested
213
220
  const activeStakeholders = config.simulate_stakeholders
214
221
  ? stakeholders.filter((s) => config.simulate_stakeholders.includes(s.id))
@@ -224,8 +231,54 @@ async function runSwarmSimulation(scenario, stakeholders, paths, config) {
224
231
  }
225
232
  // Run simulation rounds
226
233
  const rounds = [];
234
+ let aiProvider;
235
+ if (useAI) {
236
+ try {
237
+ aiProvider = (0, aiProvider_1.getAIProvider)(defaultProvider);
238
+ }
239
+ catch {
240
+ // No API key or provider unavailable — fall back silently
241
+ }
242
+ }
227
243
  for (let round = 0; round < roundCount; round++) {
228
- const reactions = activeStakeholders.map((stakeholder) => modelReaction(stakeholder, primaryPath, model, round));
244
+ let reactions;
245
+ if (aiProvider) {
246
+ // AI-POWERED REASONING: Each agent gets a Claude call
247
+ const previousReactions = round > 0
248
+ ? rounds[round - 1].reactions.map(r => `${r.stakeholder_id}: ${r.reaction}`)
249
+ : [];
250
+ reactions = await Promise.all(activeStakeholders.map(async (stakeholder) => {
251
+ try {
252
+ const aiResult = await (0, aiProvider_1.generateAIReaction)({
253
+ stakeholderId: stakeholder.id,
254
+ stakeholderDescription: stakeholder.description,
255
+ stakeholderDisposition: stakeholder.disposition,
256
+ stakeholderPriorities: stakeholder.priorities,
257
+ scenario,
258
+ pathDescription: primaryPath.description,
259
+ pathRisk: primaryPath.risk,
260
+ round,
261
+ previousReactions,
262
+ provider: aiProvider,
263
+ });
264
+ return {
265
+ stakeholder_id: stakeholder.id,
266
+ reaction: aiResult.reaction,
267
+ confidence: aiResult.confidence,
268
+ impact: aiResult.impact,
269
+ trigger: aiResult.trigger,
270
+ };
271
+ }
272
+ catch {
273
+ // Individual agent fallback to deterministic
274
+ return modelReaction(stakeholder, primaryPath, model, round);
275
+ }
276
+ }));
277
+ }
278
+ else {
279
+ // DETERMINISTIC: Original Math.random() model
280
+ reactions = activeStakeholders.map((stakeholder) => modelReaction(stakeholder, primaryPath, model, round));
281
+ }
229
282
  const emergentDynamics = detectEmergentDynamics(reactions);
230
283
  rounds.push({
231
284
  round,
@@ -0,0 +1,358 @@
1
+ "use strict";
2
+ /**
3
+ * World Comparison Engine — Same Swarm, Different Rule Environments
4
+ *
5
+ * "NV-SIM lets you run the same swarm through different rule environments
6
+ * to see how governance changes emergent behavior."
7
+ *
8
+ * Unlike `compare` (baseline vs governed), this runs TWO governed simulations
9
+ * with different world rules. Same agents, same scenario — only the rules change.
10
+ *
11
+ * Usage:
12
+ * nv-sim worlds strait_of_hormuz gas_price_spike
13
+ * nv-sim worlds trading strait_of_hormuz
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.resolveWorld = resolveWorld;
17
+ exports.runWorldComparison = runWorldComparison;
18
+ exports.getAvailableWorlds = getAvailableWorlds;
19
+ exports.explainWorldGovernance = explainWorldGovernance;
20
+ exports.generateComparisonImpact = generateComparisonImpact;
21
+ const scenarioCapsule_1 = require("./scenarioCapsule");
22
+ const governedSimulation_1 = require("./governedSimulation");
23
+ const worldStorage_1 = require("./worldStorage");
24
+ function resolveWorld(presetId) {
25
+ // Check saved worlds first (browser localStorage)
26
+ const saved = resolveSavedWorld(presetId);
27
+ if (saved)
28
+ return saved;
29
+ if (presetId === "trading" || presetId === "flash_crash") {
30
+ return {
31
+ id: "trading-flash-crash",
32
+ title: "Flash Crash — Algorithmic Cascade",
33
+ scenario: governedSimulation_1.TRADING_DEMO.scenario.scenario,
34
+ stakeholders: governedSimulation_1.TRADING_DEMO.scenario.stakeholders,
35
+ assumptions: governedSimulation_1.TRADING_DEMO.scenario.assumptions,
36
+ constraints: governedSimulation_1.TRADING_DEMO.scenario.constraints,
37
+ swarm: governedSimulation_1.TRADING_DEMO.scenario.swarm,
38
+ depth: governedSimulation_1.TRADING_DEMO.scenario.depth,
39
+ world: governedSimulation_1.TRADING_DEMO.world,
40
+ paths: governedSimulation_1.TRADING_DEMO.paths,
41
+ };
42
+ }
43
+ const template = scenarioCapsule_1.SCENARIO_TEMPLATES[presetId];
44
+ if (!template) {
45
+ throw new Error(`Unknown preset: ${presetId}. Available: trading, ${Object.keys(scenarioCapsule_1.SCENARIO_TEMPLATES).join(", ")}`);
46
+ }
47
+ if (!template.world?.inline_definition) {
48
+ throw new Error(`Preset "${presetId}" has no world definition.`);
49
+ }
50
+ return {
51
+ id: template.world.world_id,
52
+ title: template.title,
53
+ scenario: template.scenario,
54
+ stakeholders: template.stakeholders,
55
+ assumptions: template.assumptions,
56
+ constraints: template.constraints,
57
+ swarm: (template.swarm ?? { enabled: true, rounds: 5, reaction_model: "mixed" }),
58
+ depth: template.depth,
59
+ world: template.world.inline_definition,
60
+ paths: [
61
+ {
62
+ id: "path_primary",
63
+ label: "Primary Response",
64
+ description: "Most likely course of action",
65
+ projected_outcome: "Moderate disruption with cascading effects",
66
+ probability: 0.6,
67
+ risk: "high",
68
+ tradeoffs: ["Speed vs. thoroughness", "Short-term vs. long-term"],
69
+ benefits_stakeholders: template.stakeholders.filter(s => s.disposition === "supportive").map(s => s.id),
70
+ harms_stakeholders: template.stakeholders.filter(s => s.disposition === "hostile").map(s => s.id),
71
+ },
72
+ {
73
+ id: "path_defensive",
74
+ label: "Defensive Posture",
75
+ description: "Conservative approach prioritizing stability",
76
+ projected_outcome: "Reduced damage but slower recovery",
77
+ probability: 0.7,
78
+ risk: "moderate",
79
+ tradeoffs: ["Safety vs. opportunity cost"],
80
+ benefits_stakeholders: template.stakeholders.filter(s => s.disposition !== "hostile").map(s => s.id),
81
+ harms_stakeholders: [],
82
+ },
83
+ ],
84
+ };
85
+ }
86
+ /**
87
+ * Resolve a saved world (from localStorage) into a ResolvedWorld.
88
+ * Saved worlds get merged with the base preset's scenario/stakeholders
89
+ * so they work in the comparison engine.
90
+ */
91
+ function resolveSavedWorld(id) {
92
+ try {
93
+ const saved = (0, worldStorage_1.loadWorldFromStorage)(id);
94
+ if (!saved)
95
+ return null;
96
+ // Try to get scenario context from the base preset
97
+ let baseScenario = `Simulation with custom world: ${saved.world.thesis}`;
98
+ let baseStakeholders = [
99
+ { id: "System", disposition: "neutral", priorities: ["stability"] },
100
+ { id: "Agents", disposition: "unknown", priorities: ["execution"] },
101
+ ];
102
+ let baseAssumptions = { severity: "high" };
103
+ let baseConstraints = {};
104
+ try {
105
+ const base = resolveWorld(saved.basePreset);
106
+ baseScenario = base.scenario;
107
+ baseStakeholders = base.stakeholders;
108
+ baseAssumptions = base.assumptions;
109
+ baseConstraints = base.constraints;
110
+ }
111
+ catch {
112
+ // Base preset not found — use defaults
113
+ }
114
+ return {
115
+ id: saved.id,
116
+ title: saved.name,
117
+ scenario: baseScenario,
118
+ stakeholders: baseStakeholders,
119
+ assumptions: baseAssumptions,
120
+ constraints: baseConstraints,
121
+ swarm: { enabled: true, rounds: 5, reaction_model: "mixed" },
122
+ depth: "full",
123
+ world: saved.world,
124
+ paths: [
125
+ {
126
+ id: "path_primary",
127
+ label: "Primary Response",
128
+ description: "Most likely course of action",
129
+ projected_outcome: "Moderate disruption with cascading effects",
130
+ probability: 0.6,
131
+ risk: "high",
132
+ tradeoffs: ["Speed vs. thoroughness", "Short-term vs. long-term"],
133
+ benefits_stakeholders: baseStakeholders.filter(s => s.disposition === "supportive").map(s => s.id),
134
+ harms_stakeholders: baseStakeholders.filter(s => s.disposition === "hostile").map(s => s.id),
135
+ },
136
+ ],
137
+ };
138
+ }
139
+ catch {
140
+ return null;
141
+ }
142
+ }
143
+ async function runWorldComparison(worldAId, worldBId, options) {
144
+ const worldADef = resolveWorld(worldAId);
145
+ const worldBDef = resolveWorld(worldBId);
146
+ // Use the scenario from world A as the shared scenario,
147
+ // but merge stakeholders from both worlds for richer simulation
148
+ const sharedScenario = worldADef.scenario + " " + worldBDef.scenario;
149
+ const mergedStakeholders = mergeStakeholders(worldADef.stakeholders, worldBDef.stakeholders);
150
+ // Build paths that work for both worlds
151
+ const sharedPaths = worldADef.paths;
152
+ options?.onProgress?.("World A", "simulating");
153
+ // Run World A governed comparison
154
+ const resultA = await (0, governedSimulation_1.runGovernedComparison)({
155
+ scenario: sharedScenario,
156
+ stakeholders: mergedStakeholders,
157
+ assumptions: worldADef.assumptions,
158
+ constraints: worldADef.constraints,
159
+ depth: worldADef.depth,
160
+ swarm: worldADef.swarm,
161
+ }, worldADef.world, sharedPaths);
162
+ options?.onProgress?.("World B", "simulating");
163
+ // Run World B governed comparison
164
+ const resultB = await (0, governedSimulation_1.runGovernedComparison)({
165
+ scenario: sharedScenario,
166
+ stakeholders: mergedStakeholders,
167
+ assumptions: worldBDef.assumptions,
168
+ constraints: worldBDef.constraints,
169
+ depth: worldBDef.depth,
170
+ swarm: worldBDef.swarm,
171
+ }, worldBDef.world, sharedPaths);
172
+ // Extract round snapshots
173
+ const roundsA = extractRoundSnapshots(resultA);
174
+ const roundsB = extractRoundSnapshots(resultB);
175
+ // Build delta
176
+ const metricsA = resultA.governed.metrics;
177
+ const metricsB = resultB.governed.metrics;
178
+ const stabilityDiff = metricsA.stabilityScore - metricsB.stabilityScore;
179
+ const volatilityDiff = metricsA.maxVolatility - metricsB.maxVolatility;
180
+ const coalitionRiskDiff = metricsA.coalitionRisks - metricsB.coalitionRisks;
181
+ const collapseDiff = metricsA.collapseProbability - metricsB.collapseProbability;
182
+ const moreStable = Math.abs(stabilityDiff) < 0.02 ? "equal"
183
+ : stabilityDiff > 0 ? "A" : "B";
184
+ const narrative = buildWorldNarrative(worldADef, worldBDef, metricsA, metricsB, moreStable);
185
+ return {
186
+ scenario: sharedScenario,
187
+ worldA: {
188
+ id: worldAId,
189
+ thesis: worldADef.world.thesis,
190
+ invariantCount: worldADef.world.invariants.length,
191
+ gateCount: (worldADef.world.gates ?? []).length,
192
+ metrics: metricsA,
193
+ trajectory: resultA.governed.swarm.trajectory,
194
+ governanceStats: resultA.governanceStats,
195
+ rounds: roundsA,
196
+ },
197
+ worldB: {
198
+ id: worldBId,
199
+ thesis: worldBDef.world.thesis,
200
+ invariantCount: worldBDef.world.invariants.length,
201
+ gateCount: (worldBDef.world.gates ?? []).length,
202
+ metrics: metricsB,
203
+ trajectory: resultB.governed.swarm.trajectory,
204
+ governanceStats: resultB.governanceStats,
205
+ rounds: roundsB,
206
+ },
207
+ delta: {
208
+ stabilityDiff: Number((stabilityDiff * 100).toFixed(1)),
209
+ volatilityDiff: Number((volatilityDiff * 100).toFixed(1)),
210
+ coalitionRiskDiff,
211
+ collapseDiff: Number((collapseDiff * 100).toFixed(1)),
212
+ moreStable,
213
+ narrative,
214
+ },
215
+ };
216
+ }
217
+ function mergeStakeholders(a, b) {
218
+ const seen = new Set();
219
+ const merged = [];
220
+ for (const s of [...a, ...b]) {
221
+ if (!seen.has(s.id)) {
222
+ seen.add(s.id);
223
+ merged.push(s);
224
+ }
225
+ }
226
+ return merged;
227
+ }
228
+ function extractRoundSnapshots(result) {
229
+ return result.governed.swarm.rounds.map((r) => ({
230
+ round: r.round,
231
+ avgImpact: r.reactions.reduce((s, rx) => s + rx.impact, 0) / r.reactions.length,
232
+ maxVolatility: Math.max(...r.reactions.map((rx) => Math.abs(rx.impact))),
233
+ agentImpacts: r.reactions.map((rx) => ({
234
+ id: rx.stakeholder_id,
235
+ impact: rx.impact,
236
+ confidence: rx.confidence,
237
+ })),
238
+ interventions: r.emergent_dynamics ?? [],
239
+ }));
240
+ }
241
+ function buildWorldNarrative(worldA, worldB, metricsA, metricsB, moreStable) {
242
+ const parts = [];
243
+ parts.push(`World A ("${worldA.world.thesis.slice(0, 60)}...") vs World B ("${worldB.world.thesis.slice(0, 60)}...").`);
244
+ if (moreStable === "A") {
245
+ parts.push(`World A produced a more stable system (${(metricsA.stabilityScore * 100).toFixed(0)}% vs ${(metricsB.stabilityScore * 100).toFixed(0)}%).`);
246
+ }
247
+ else if (moreStable === "B") {
248
+ parts.push(`World B produced a more stable system (${(metricsB.stabilityScore * 100).toFixed(0)}% vs ${(metricsA.stabilityScore * 100).toFixed(0)}%).`);
249
+ }
250
+ else {
251
+ parts.push(`Both worlds produced similar stability levels.`);
252
+ }
253
+ const invDiff = worldA.world.invariants.length - worldB.world.invariants.length;
254
+ if (invDiff !== 0) {
255
+ parts.push(`World ${invDiff > 0 ? "A" : "B"} has ${Math.abs(invDiff)} more invariant${Math.abs(invDiff) > 1 ? "s" : ""}, providing tighter structural constraints.`);
256
+ }
257
+ parts.push("Same agents, different rules — the world shapes the outcome.");
258
+ return parts.join(" ");
259
+ }
260
+ /**
261
+ * Get all available world presets for the comparison picker.
262
+ */
263
+ function getAvailableWorlds() {
264
+ const worlds = [
265
+ {
266
+ id: "trading",
267
+ title: "Flash Crash — Algorithmic Cascade",
268
+ thesis: governedSimulation_1.TRADING_DEMO.world.thesis,
269
+ invariantCount: governedSimulation_1.TRADING_DEMO.world.invariants.length,
270
+ gateCount: (governedSimulation_1.TRADING_DEMO.world.gates ?? []).length,
271
+ },
272
+ ];
273
+ for (const [key, template] of Object.entries(scenarioCapsule_1.SCENARIO_TEMPLATES)) {
274
+ if (template.world?.inline_definition) {
275
+ worlds.push({
276
+ id: key,
277
+ title: template.title,
278
+ thesis: template.world.inline_definition.thesis,
279
+ invariantCount: template.world.inline_definition.invariants.length,
280
+ gateCount: (template.world.inline_definition.gates ?? []).length,
281
+ });
282
+ }
283
+ }
284
+ return worlds;
285
+ }
286
+ /**
287
+ * Generate a plain-English explanation of a world's governance posture.
288
+ * Uses the governance engine's own data — no LLM needed.
289
+ */
290
+ function explainWorldGovernance(presetId) {
291
+ const world = resolveWorld(presetId);
292
+ const invariants = world.world.invariants;
293
+ const gates = world.world.gates ?? [];
294
+ const enforceableCount = invariants.filter(i => i.enforceable).length;
295
+ const advisoryCount = invariants.length - enforceableCount;
296
+ const invariantSummary = invariants.length === 0
297
+ ? "No invariants defined — ungoverned."
298
+ : `${invariants.length} rules (${enforceableCount} enforced, ${advisoryCount} advisory): ${invariants.map(i => i.description).join("; ")}`;
299
+ const criticalGates = gates.filter(g => g.severity === "critical");
300
+ const warningGates = gates.filter(g => g.severity === "warning");
301
+ const gateSummary = gates.length === 0
302
+ ? "No viability gates — no collapse detection."
303
+ : `${gates.length} gates (${criticalGates.length} critical, ${warningGates.length} warning): ${gates.map(g => `${g.label} [${g.severity}]`).join(", ")}`;
304
+ const riskProfile = criticalGates.length >= 2 ? "high"
305
+ : criticalGates.length >= 1 || warningGates.length >= 2 ? "moderate"
306
+ : "low";
307
+ return {
308
+ worldId: presetId,
309
+ title: world.title,
310
+ thesis: world.world.thesis,
311
+ invariantSummary,
312
+ gateSummary,
313
+ riskProfile,
314
+ };
315
+ }
316
+ /**
317
+ * Generate a counterfactual impact summary from comparison results.
318
+ * "What would have happened without these rules?"
319
+ *
320
+ * Powered by @neuroverseos/governance engine data.
321
+ */
322
+ function generateComparisonImpact(result) {
323
+ const { worldA, worldB, delta } = result;
324
+ const winner = delta.moreStable;
325
+ const winnerSide = winner === "A" ? worldA : winner === "B" ? worldB : worldA;
326
+ const loserSide = winner === "A" ? worldB : winner === "B" ? worldA : worldB;
327
+ const interventionDiff = winnerSide.governanceStats.totalEvaluations - loserSide.governanceStats.totalEvaluations;
328
+ const blockDiff = winnerSide.governanceStats.verdicts.block - loserSide.governanceStats.verdicts.block;
329
+ const keyFindings = [];
330
+ if (Math.abs(delta.stabilityDiff) >= 5) {
331
+ keyFindings.push(`${Math.abs(delta.stabilityDiff).toFixed(0)}% stability ${delta.stabilityDiff > 0 ? "advantage for World A" : "advantage for World B"}`);
332
+ }
333
+ if (Math.abs(delta.collapseDiff) >= 5) {
334
+ keyFindings.push(`${Math.abs(delta.collapseDiff).toFixed(0)}% ${delta.collapseDiff > 0 ? "higher" : "lower"} cascade risk in World A`);
335
+ }
336
+ if (blockDiff !== 0) {
337
+ keyFindings.push(`${Math.abs(blockDiff)} more governance interventions in ${blockDiff > 0 ? "World A" : "World B"}`);
338
+ }
339
+ if (Math.abs(delta.volatilityDiff) >= 5) {
340
+ keyFindings.push(`${Math.abs(delta.volatilityDiff).toFixed(0)}% volatility ${delta.volatilityDiff > 0 ? "higher in A" : "higher in B"}`);
341
+ }
342
+ if (keyFindings.length === 0) {
343
+ keyFindings.push("Both worlds produced similar outcomes — rules had minimal differential effect.");
344
+ }
345
+ return {
346
+ winner,
347
+ stabilityDelta: `${delta.stabilityDiff > 0 ? "+" : ""}${delta.stabilityDiff.toFixed(1)}%`,
348
+ cascadeRiskDelta: `${delta.collapseDiff > 0 ? "+" : ""}${delta.collapseDiff.toFixed(1)}%`,
349
+ volatilityDelta: `${delta.volatilityDiff > 0 ? "+" : ""}${delta.volatilityDiff.toFixed(1)}%`,
350
+ totalInterventionsA: worldA.governanceStats.totalEvaluations,
351
+ totalInterventionsB: worldB.governanceStats.totalEvaluations,
352
+ blockedActionsA: worldA.governanceStats.verdicts.block,
353
+ blockedActionsB: worldB.governanceStats.verdicts.block,
354
+ keyFindings,
355
+ verdict: delta.narrative,
356
+ poweredBy: "@neuroverseos/governance — simulateWorld() + evaluateGuard()",
357
+ };
358
+ }