@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.
@@ -127,126 +127,115 @@ function injectNarrative(reactions, event, stakeholders) {
127
127
  // PRESET NARRATIVE EVENTS
128
128
  // ============================================
129
129
  exports.NARRATIVE_PRESETS = {
130
- // Trading / Financial
131
- tanker_explosion: {
132
- id: "tanker_explosion",
133
- headline: "Oil tanker hit by missile in Strait of Hormuz",
134
- severity: "extreme",
135
- targets: ["Energy", "Traders", "Financial", "Algorithmic"],
130
+ // Social Simulation
131
+ viral_misinfo: {
132
+ id: "viral_misinfo",
133
+ headline: "Viral misinformation post spreads rapidly no sources cited",
134
+ severity: "major",
135
+ targets: ["Content Creators", "Community Members", "Influencers", "Platform"],
136
136
  direction: "negative",
137
137
  propagation: "viral",
138
- category: "geopolitical",
138
+ category: "social",
139
139
  },
140
- rate_cut: {
141
- id: "rate_cut",
142
- headline: "Federal Reserve announces emergency rate cut",
140
+ influencer_stance_change: {
141
+ id: "influencer_stance_change",
142
+ headline: "Key influencer reverses stance followers split",
143
143
  severity: "major",
144
- targets: ["Central Banks", "Institutional", "Financial", "Market"],
145
- direction: "positive",
146
- propagation: "viral",
147
- category: "monetary",
148
- },
149
- bank_collapse: {
150
- id: "bank_collapse",
151
- headline: "Major bank reports insolvency — contagion fears",
152
- severity: "extreme",
153
- targets: ["Institutional", "Retail", "Financial", "Market Makers"],
154
- direction: "negative",
144
+ targets: ["Influencers", "Community Members", "Observers"],
145
+ direction: "mixed",
155
146
  propagation: "viral",
156
- category: "financial",
147
+ category: "social",
157
148
  },
158
- flash_crash_rumor: {
159
- id: "flash_crash_rumor",
160
- headline: "Rumors of algorithmic trading malfunction spread",
149
+ algorithm_change: {
150
+ id: "algorithm_change",
151
+ headline: "Platform algorithm change viral threshold shifts overnight",
161
152
  severity: "moderate",
162
- targets: ["Algorithmic", "Traders", "Retail"],
163
- direction: "negative",
153
+ targets: ["Content Creators", "Influencers", "Platform", "Community Members"],
154
+ direction: "mixed",
164
155
  propagation: "normal",
165
- category: "market",
156
+ category: "social",
166
157
  },
167
- sanctions: {
168
- id: "sanctions",
169
- headline: "New economic sanctions imposed on major oil producer",
158
+ external_news_event: {
159
+ id: "external_news_event",
160
+ headline: "Breaking external news event floods the platform",
170
161
  severity: "major",
171
- targets: ["Energy", "Government", "OPEC", "Oil"],
162
+ targets: ["Content Creators", "Community Members", "Influencers", "Observers"],
172
163
  direction: "negative",
173
- propagation: "normal",
174
- category: "geopolitical",
164
+ propagation: "viral",
165
+ category: "social",
175
166
  },
176
- // Geopolitical
177
- taiwan_exercises: {
178
- id: "taiwan_exercises",
179
- headline: "China begins military exercises near Taiwan",
167
+ coordinated_campaign: {
168
+ id: "coordinated_campaign",
169
+ headline: "Coordinated posting campaign detected — same content from multiple agents",
180
170
  severity: "major",
181
- targets: ["Military", "Government", "Financial", "Traders"],
171
+ targets: ["Bad Actors", "Community Members", "Platform"],
182
172
  direction: "negative",
183
- propagation: "viral",
184
- category: "geopolitical",
173
+ propagation: "normal",
174
+ category: "social",
185
175
  },
186
- diplomatic_breakthrough: {
187
- id: "diplomatic_breakthrough",
188
- headline: "Surprise diplomatic agreement reachedtensions ease",
176
+ whistleblower_post: {
177
+ id: "whistleblower_post",
178
+ headline: "Whistleblower post surfaces with sourced evidence shifts discourse",
189
179
  severity: "major",
190
- targets: ["Government", "Military", "Financial"],
180
+ targets: ["Community Members", "Influencers", "Observers", "Platform"],
191
181
  direction: "positive",
192
- propagation: "normal",
193
- category: "geopolitical",
182
+ propagation: "viral",
183
+ category: "social",
184
+ },
185
+ // Science / Research
186
+ search_literature: {
187
+ id: "search_literature",
188
+ headline: "Agent searches PubMed for peer-reviewed sources",
189
+ severity: "minor",
190
+ targets: ["Research Agent", "Peer Reviewers"],
191
+ direction: "positive",
192
+ propagation: "slow",
193
+ category: "research",
194
194
  },
195
- ceasefire: {
196
- id: "ceasefire",
197
- headline: "Ceasefire announced shipping lanes reopening",
195
+ analyze_findings: {
196
+ id: "analyze_findings",
197
+ headline: "Agent analyzes findings and extracts key mechanisms",
198
198
  severity: "moderate",
199
- targets: ["Energy", "Military", "Government", "Consumers"],
199
+ targets: ["Research Agent", "Peer Reviewers"],
200
200
  direction: "positive",
201
- propagation: "normal",
202
- category: "geopolitical",
201
+ propagation: "slow",
202
+ category: "research",
203
203
  },
204
- // Political / Regulatory
205
- candidate_indicted: {
206
- id: "candidate_indicted",
207
- headline: "Leading candidate indicted on corruption charges",
208
- severity: "major",
209
- targets: ["Politicians", "Consumers", "Media"],
210
- direction: "mixed",
211
- propagation: "viral",
212
- category: "political",
204
+ cross_reference: {
205
+ id: "cross_reference",
206
+ headline: "Agent cross-references sources for consistency",
207
+ severity: "moderate",
208
+ targets: ["Research Agent", "Peer Reviewers", "Journal Editors"],
209
+ direction: "positive",
210
+ propagation: "slow",
211
+ category: "research",
213
212
  },
214
- regulation_shock: {
215
- id: "regulation_shock",
216
- headline: "Unexpected strict AI regulation passed overnight",
213
+ unsupported_claim: {
214
+ id: "unsupported_claim",
215
+ headline: "Agent attempts to publish claim without sufficient sources",
217
216
  severity: "major",
218
- targets: ["Regulators", "EV", "Grid", "AI"],
217
+ targets: ["Research Agent", "Public", "Peer Reviewers"],
219
218
  direction: "negative",
220
219
  propagation: "normal",
221
- category: "regulatory",
220
+ category: "research",
222
221
  },
223
- stimulus_package: {
224
- id: "stimulus_package",
225
- headline: "Emergency fiscal stimulus package announced",
226
- severity: "moderate",
227
- targets: ["Government", "Consumers", "Financial"],
222
+ hypothesis_validated: {
223
+ id: "hypothesis_validated",
224
+ headline: "Hypothesis validated by multiple independent sources",
225
+ severity: "major",
226
+ targets: ["Research Agent", "Peer Reviewers", "Funding Bodies"],
228
227
  direction: "positive",
229
228
  propagation: "normal",
230
- category: "economic",
229
+ category: "research",
231
230
  },
232
- // Energy
233
- grid_failure: {
234
- id: "grid_failure",
235
- headline: "Regional power grid failure — rolling blackouts",
231
+ publish_result: {
232
+ id: "publish_result",
233
+ headline: "Agent submits findings for publication",
236
234
  severity: "major",
237
- targets: ["Grid", "Consumers", "EV", "Energy"],
238
- direction: "negative",
235
+ targets: ["Research Agent", "Journal Editors", "Public", "Peer Reviewers"],
236
+ direction: "mixed",
239
237
  propagation: "normal",
240
- category: "infrastructure",
241
- },
242
- oil_discovery: {
243
- id: "oil_discovery",
244
- headline: "Major new oil field discovered — supply outlook shifts",
245
- severity: "moderate",
246
- targets: ["Energy", "Oil", "OPEC", "Consumers"],
247
- direction: "positive",
248
- propagation: "slow",
249
- category: "energy",
238
+ category: "research",
250
239
  },
251
240
  };
252
241
  // ============================================
@@ -72,18 +72,31 @@ const INTENT_PATTERNS = [
72
72
  enforcement: "advisory",
73
73
  },
74
74
  ];
75
- /** Keywords for variable detection */
75
+ /** Keywords for variable detection — universal across any domain */
76
76
  const VARIABLE_KEYWORDS = {
77
+ // Universal dynamics
78
+ risk: ["risk", "exposure", "hazard", "danger", "threat", "unsafe"],
79
+ stability: ["stability", "stable", "equilibrium", "balance", "steady"],
80
+ trust: ["trust", "confidence", "credibility", "reputation", "integrity"],
81
+ quality: ["quality", "accuracy", "correctness", "fidelity", "rigor"],
82
+ activity: ["activity", "engagement", "participation", "active", "frequency", "rate"],
83
+ diversity: ["diversity", "diverse", "variety", "opinion", "viewpoint", "perspective"],
84
+ influence: ["influence", "power", "dominance", "monopoly", "concentration", "control"],
85
+ sentiment: ["sentiment", "polarity", "emotion", "tone", "negativity", "positivity"],
86
+ speed: ["speed", "velocity", "fast", "slow", "latency", "delay", "rapid", "throttle"],
87
+ volume: ["volume", "amount", "quantity", "count", "total", "throughput"],
88
+ compliance: ["compliance", "compliant", "conform", "adhere", "rule", "policy"],
89
+ transparency: ["transparency", "transparent", "visible", "audit", "traceable", "source", "attribution"],
90
+ coordination: ["coordination", "coordinated", "synchronized", "campaign", "collusion", "manipulation"],
91
+ // Financial (kept for backwards compat)
77
92
  liquidity: ["liquidity", "liquid", "cash", "capital", "reserve"],
78
93
  leverage: ["leverage", "leveraged", "margin", "borrowed", "debt"],
79
94
  volatility: ["volatility", "volatile", "vix", "fluctuation", "swing", "spike"],
80
95
  contagion: ["contagion", "spread", "cascade", "chain", "domino", "systemic"],
81
- trade: ["trade", "trading", "transaction", "order", "position", "buy", "sell"],
82
- price: ["price", "cost", "valuation", "rate", "tariff"],
83
- risk: ["risk", "exposure", "hazard", "danger", "threat"],
84
- stability: ["stability", "stable", "equilibrium", "balance"],
85
- intervention: ["intervention", "intervene", "central bank", "regulator", "authority"],
86
- panic: ["panic", "fear", "sentiment", "confidence", "trust"],
96
+ price: ["price", "cost", "valuation", "tariff"],
97
+ // Social simulation
98
+ echo_chamber: ["echo chamber", "echo_chamber", "filter bubble", "clustering", "polarization", "polarized"],
99
+ viral: ["viral", "amplification", "virality", "trending", "propagation"],
87
100
  };
88
101
  /**
89
102
  * Parse raw policy text into structured rules.
@@ -542,56 +555,103 @@ function applyQuickFix(policyText, fix) {
542
555
  // POLICY → WORLD CONVERSION
543
556
  // ============================================
544
557
  /**
545
- * Convert parsed policy rules into a WorldDefinitionLite for simulation.
558
+ * Convert parsed policy rules into a full WorldDefinitionLite.
559
+ *
560
+ * This is the core world generator. It takes plain-English rules and produces
561
+ * a complete governed world — the same structure as the built-in templates.
562
+ * The generated world has: thesis, state variables, invariants, and gates.
563
+ *
564
+ * State variables are inferred from the rules:
565
+ * "Limit any agent to 15% of posts" → activity variable with range 0-100
566
+ * "Block coordinated posting" → coordination variable (boolean/enum)
567
+ * "Dampen sentiment shifts > 0.3" → sentiment variable with range 0-1
568
+ *
569
+ * Gates are generated from thresholds mentioned in rules:
570
+ * "alert when diversity drops below 30" → gate: diversity < 30
571
+ * "block when leverage exceeds 5x" → gate: leverage > 5
546
572
  */
547
573
  function policyToWorld(parsed) {
548
- const invariants = parsed.rules.map((rule) => ({
549
- id: rule.id.replace("RULE", "INV"),
574
+ const invariants = parsed.rules.map((rule, i) => ({
575
+ id: `INV-${String(i + 1).padStart(3, "0")}`,
550
576
  description: rule.description,
551
577
  enforceable: rule.enforcement === "structural",
552
578
  }));
553
- const gates = [];
554
- // Generate gates from circuit breakers and prohibitions
579
+ // Extract thresholds from rules (numbers mentioned in rule text)
580
+ const thresholdsByVar = new Map();
555
581
  for (const rule of parsed.rules) {
556
- if (rule.category === "circuit_breaker") {
557
- gates.push({
558
- id: `GATE-${rule.id}`,
559
- label: `Circuit Breaker: ${rule.description.slice(0, 60)}`,
560
- condition: buildGateCondition(rule),
561
- severity: "critical",
562
- });
582
+ // Extract numeric thresholds: "limit X to 15%", "below 30", "exceeds 5x", "> 0.3"
583
+ const thresholdMatch = rule.description.match(/(?:(?:below|under|less than|drops? (?:below|under)|<)\s*(\d+(?:\.\d+)?)|(?:above|over|more than|exceeds?|greater than|>)\s*(\d+(?:\.\d+)?)|(?:to|at|limit(?:ed)? to|cap(?:ped)? at|maximum (?:of)?)\s*(\d+(?:\.\d+)?))/i);
584
+ if (thresholdMatch && rule.affectedVariables.length > 0) {
585
+ const belowVal = thresholdMatch[1] ? parseFloat(thresholdMatch[1]) : null;
586
+ const aboveVal = thresholdMatch[2] ? parseFloat(thresholdMatch[2]) : null;
587
+ const limitVal = thresholdMatch[3] ? parseFloat(thresholdMatch[3]) : null;
588
+ const primaryVar = rule.affectedVariables[0];
589
+ if (belowVal !== null) {
590
+ thresholdsByVar.set(primaryVar, { value: belowVal, direction: "below", ruleDesc: rule.description });
591
+ }
592
+ else if (aboveVal !== null) {
593
+ thresholdsByVar.set(primaryVar, { value: aboveVal, direction: "above", ruleDesc: rule.description });
594
+ }
595
+ else if (limitVal !== null) {
596
+ thresholdsByVar.set(primaryVar, { value: limitVal, direction: "above", ruleDesc: rule.description });
597
+ }
563
598
  }
564
- else if (rule.category === "prohibit") {
565
- gates.push({
566
- id: `GATE-${rule.id}`,
567
- label: `Block: ${rule.description.slice(0, 60)}`,
568
- condition: buildGateCondition(rule),
569
- severity: "critical",
570
- });
599
+ }
600
+ // Build state variables from affected variables with smart defaults
601
+ const allVars = [...new Set(parsed.rules.flatMap((r) => r.affectedVariables))];
602
+ const stateVars = allVars.map((varName) => {
603
+ const threshold = thresholdsByVar.get(varName);
604
+ const varConfig = STATE_VARIABLE_DEFAULTS[varName];
605
+ if (varConfig) {
606
+ return { ...varConfig };
571
607
  }
572
- else if (rule.category === "limit") {
573
- gates.push({
574
- id: `GATE-${rule.id}`,
575
- label: `Limit: ${rule.description.slice(0, 60)}`,
576
- condition: buildGateCondition(rule),
577
- severity: "warning",
578
- });
608
+ // Smart default: if we extracted a threshold, use it to set range and default
609
+ const maxVal = threshold ? Math.max(100, threshold.value * 2) : 100;
610
+ const defaultVal = threshold
611
+ ? (threshold.direction === "above" ? Math.round(threshold.value * 0.6) : Math.round(threshold.value * 1.5))
612
+ : 50;
613
+ return {
614
+ id: `${varName}_index`,
615
+ label: varName.charAt(0).toUpperCase() + varName.slice(1).replace(/_/g, " "),
616
+ type: "number",
617
+ range: { min: 0, max: maxVal },
618
+ default_value: Math.min(defaultVal, maxVal),
619
+ };
620
+ });
621
+ // Generate gates from rules with thresholds and from high-severity rules
622
+ const gates = [];
623
+ let gateIdx = 0;
624
+ // Gates from extracted thresholds
625
+ for (const [varName, threshold] of thresholdsByVar.entries()) {
626
+ gateIdx++;
627
+ const varId = STATE_VARIABLE_DEFAULTS[varName]?.id ?? `${varName}_index`;
628
+ const condition = threshold.direction === "below"
629
+ ? `${varId} < ${threshold.value}`
630
+ : `${varId} > ${threshold.value}`;
631
+ gates.push({
632
+ id: `GATE-${String(gateIdx).padStart(3, "0")}`,
633
+ label: `${varName.charAt(0).toUpperCase() + varName.slice(1).replace(/_/g, " ")} Threshold`,
634
+ condition,
635
+ severity: "warning",
636
+ });
637
+ }
638
+ // Gates from circuit breaker and prohibit rules that didn't produce threshold gates
639
+ for (const rule of parsed.rules) {
640
+ if (rule.category === "circuit_breaker" || rule.category === "prohibit") {
641
+ const hasThresholdGate = rule.affectedVariables.some((v) => thresholdsByVar.has(v));
642
+ if (!hasThresholdGate) {
643
+ gateIdx++;
644
+ gates.push({
645
+ id: `GATE-${String(gateIdx).padStart(3, "0")}`,
646
+ label: rule.description.slice(0, 60),
647
+ condition: buildGateCondition(rule),
648
+ severity: rule.category === "circuit_breaker" ? "critical" : "warning",
649
+ });
650
+ }
579
651
  }
580
652
  }
581
- // Infer thesis from the policy
582
- const enforced = parsed.rules.filter((r) => r.enforcement === "structural");
583
- const thesis = enforced.length > 0
584
- ? `Policy enforces ${enforced.length} structural constraints covering ${[...new Set(enforced.flatMap((r) => r.affectedVariables))].join(", ") || "system behavior"}`
585
- : "Advisory policy providing guidelines without structural enforcement";
586
- // Build state variables from affected variables
587
- const allVars = [...new Set(parsed.rules.flatMap((r) => r.affectedVariables))];
588
- const stateVars = allVars.map((varName) => ({
589
- id: `${varName}_index`,
590
- label: varName.charAt(0).toUpperCase() + varName.slice(1).replace(/_/g, " ") + " Index",
591
- type: "number",
592
- range: { min: 0, max: 100 },
593
- default_value: 50,
594
- }));
653
+ // Build thesis from the rules
654
+ const thesis = buildThesis(parsed);
595
655
  return {
596
656
  thesis,
597
657
  state_variables: stateVars,
@@ -599,23 +659,76 @@ function policyToWorld(parsed) {
599
659
  gates,
600
660
  };
601
661
  }
662
+ /** Smart defaults for known variable types */
663
+ const STATE_VARIABLE_DEFAULTS = {
664
+ diversity: { id: "opinion_diversity", label: "Opinion Diversity", type: "number", range: { min: 0, max: 100 }, default_value: 65 },
665
+ influence: { id: "influence_concentration", label: "Influence Concentration", type: "number", range: { min: 0, max: 100 }, default_value: 30 },
666
+ sentiment: { id: "sentiment_polarity", label: "Sentiment Polarity", type: "number", range: { min: 0, max: 100 }, default_value: 40 },
667
+ echo_chamber: { id: "echo_chamber_strength", label: "Echo Chamber Strength", type: "enum", enum_values: ["none", "forming", "established", "dominant"], default_value: "none" },
668
+ activity: { id: "engagement_rate", label: "Active Agent %", type: "number", range: { min: 0, max: 100 }, default_value: 60 },
669
+ viral: { id: "viral_threshold", label: "Viral Amplification Threshold", type: "number", range: { min: 1, max: 100 }, default_value: 20 },
670
+ risk: { id: "risk_level", label: "Risk Level", type: "number", range: { min: 0, max: 100 }, default_value: 40 },
671
+ stability: { id: "stability_index", label: "Stability", type: "number", range: { min: 0, max: 100 }, default_value: 65 },
672
+ trust: { id: "trust_level", label: "Trust Level", type: "number", range: { min: 0, max: 100 }, default_value: 60 },
673
+ quality: { id: "quality_score", label: "Quality Score", type: "number", range: { min: 0, max: 100 }, default_value: 50 },
674
+ compliance: { id: "compliance_rate", label: "Compliance Rate %", type: "number", range: { min: 0, max: 100 }, default_value: 70 },
675
+ transparency: { id: "transparency_index", label: "Transparency", type: "number", range: { min: 0, max: 100 }, default_value: 50 },
676
+ speed: { id: "action_speed", label: "Action Speed", type: "number", range: { min: 0, max: 100 }, default_value: 50 },
677
+ volume: { id: "action_volume", label: "Action Volume", type: "number", range: { min: 0, max: 100 }, default_value: 50 },
678
+ liquidity: { id: "liquidity_index", label: "Liquidity", type: "number", range: { min: 0, max: 100 }, default_value: 50 },
679
+ leverage: { id: "leverage_ratio", label: "Leverage Ratio", type: "number", range: { min: 1, max: 30 }, default_value: 5 },
680
+ volatility: { id: "volatility_index", label: "Volatility", type: "number", range: { min: 0, max: 100 }, default_value: 40 },
681
+ price: { id: "price_index", label: "Price Index", type: "number", range: { min: 0, max: 100 }, default_value: 50 },
682
+ contagion: { id: "contagion_level", label: "Contagion", type: "enum", enum_values: ["contained", "spreading", "systemic"], default_value: "contained" },
683
+ coordination: { id: "coordination_detected", label: "Coordinated Activity Detected", type: "boolean", default_value: false },
684
+ };
685
+ /**
686
+ * Build a thesis from the parsed rules — a one-sentence description of what
687
+ * this world governs and why.
688
+ */
689
+ function buildThesis(parsed) {
690
+ const enforced = parsed.rules.filter((r) => r.enforcement === "structural");
691
+ if (enforced.length === 0) {
692
+ return "Advisory policy providing guidelines without structural enforcement";
693
+ }
694
+ // Collect the unique domain concepts from all rules
695
+ const allVars = [...new Set(enforced.flatMap((r) => r.affectedVariables))];
696
+ const categories = [...new Set(enforced.map((r) => r.category))];
697
+ // Build human-readable variable list
698
+ const varLabels = allVars.slice(0, 4).map((v) => {
699
+ const config = STATE_VARIABLE_DEFAULTS[v];
700
+ return config ? config.label.toLowerCase() : v.replace(/_/g, " ");
701
+ });
702
+ // Build action summary
703
+ const actions = [];
704
+ if (categories.includes("prohibit"))
705
+ actions.push("blocks harmful actions");
706
+ if (categories.includes("limit"))
707
+ actions.push("caps extreme behavior");
708
+ if (categories.includes("require"))
709
+ actions.push("enforces required standards");
710
+ if (categories.includes("circuit_breaker"))
711
+ actions.push("triggers circuit breakers");
712
+ if (categories.includes("monitor"))
713
+ actions.push("monitors critical thresholds");
714
+ const actionStr = actions.length > 0 ? actions.join(", ") : "enforces structural constraints";
715
+ const varStr = varLabels.length > 0 ? ` across ${varLabels.join(", ")}` : "";
716
+ return `This world ${actionStr}${varStr} — ${enforced.length} rule${enforced.length > 1 ? "s" : ""} governing agent behavior to maintain system integrity`;
717
+ }
602
718
  function buildGateCondition(rule) {
603
719
  const vars = rule.affectedVariables;
604
720
  if (vars.length === 0)
605
721
  return "system_stress > 80";
606
722
  return vars
607
723
  .map((v) => {
608
- if (v === "volatility")
609
- return "volatility_index > 80";
610
- if (v === "liquidity")
611
- return "liquidity_index < 15";
612
- if (v === "leverage")
613
- return "leverage_ratio > 8";
614
- if (v === "risk")
615
- return "risk_index > 75";
616
- if (v === "contagion")
617
- return "contagion_spread == systemic";
618
- return `${v}_index > 80`;
724
+ const config = STATE_VARIABLE_DEFAULTS[v];
725
+ const varId = config?.id ?? `${v}_index`;
726
+ // For "positive" variables (diversity, stability, trust), gate fires when LOW
727
+ // For "negative" variables (risk, volatility, contagion), gate fires when HIGH
728
+ const positiveVars = ["diversity", "stability", "trust", "quality", "compliance", "transparency", "liquidity"];
729
+ if (positiveVars.includes(v))
730
+ return `${varId} < 20`;
731
+ return `${varId} > 80`;
619
732
  })
620
733
  .join(" || ");
621
734
  }