@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.
- package/README.md +376 -66
- package/dist/adapters/mirofish.js +461 -0
- package/dist/adapters/scienceclaw.js +750 -0
- package/dist/assets/index-CHmUN8s0.js +532 -0
- package/dist/assets/index-DWgMnB7I.css +1 -0
- package/dist/assets/mirotir-logo-DUexumBH.svg +185 -0
- package/dist/assets/reportEngine-BVdQ2_nW.js +1 -0
- package/dist/components/ConstraintsPanel.js +11 -0
- package/dist/components/StakeholderBuilder.js +32 -0
- package/dist/components/ui/badge.js +24 -0
- package/dist/components/ui/button.js +70 -0
- package/dist/components/ui/card.js +57 -0
- package/dist/components/ui/input.js +44 -0
- package/dist/components/ui/label.js +45 -0
- package/dist/components/ui/select.js +70 -0
- package/dist/engine/aiProvider.js +681 -0
- package/dist/engine/auditTrace.js +352 -0
- package/dist/engine/behavioralAnalysis.js +605 -0
- package/dist/engine/cli.js +1408 -299
- package/dist/engine/dynamicsGovernance.js +588 -0
- package/dist/engine/fullGovernedLoop.js +367 -0
- package/dist/engine/governance.js +8 -3
- package/dist/engine/governedSimulation.js +114 -17
- package/dist/engine/index.js +56 -1
- package/dist/engine/liveAdapter.js +342 -0
- package/dist/engine/liveVisualizer.js +3063 -0
- package/dist/engine/metrics/science.metrics.js +335 -0
- package/dist/engine/narrativeInjection.js +305 -0
- package/dist/engine/policyEnforcement.js +1611 -0
- package/dist/engine/policyEngine.js +799 -0
- package/dist/engine/primeRadiant.js +540 -0
- package/dist/engine/reasoningEngine.js +57 -3
- package/dist/engine/reportEngine.js +97 -0
- package/dist/engine/scenarioComparison.js +463 -0
- package/dist/engine/scenarioLibrary.js +231 -0
- package/dist/engine/swarmSimulation.js +54 -1
- package/dist/engine/worldComparison.js +358 -0
- package/dist/engine/worldStorage.js +232 -0
- package/dist/favicon.ico +0 -0
- package/dist/index.html +23 -0
- package/dist/lib/reasoningEngine.js +290 -0
- package/dist/lib/simulationAdapter.js +686 -0
- package/dist/lib/swarmParser.js +291 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/utils.js +8 -0
- package/dist/placeholder.svg +1 -0
- package/dist/robots.txt +14 -0
- package/dist/runtime/govern.js +473 -0
- package/dist/runtime/index.js +75 -0
- package/dist/runtime/types.js +11 -0
- package/package.json +17 -12
- package/variants/.gitkeep +0 -0
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Dynamics Governance Engine — System-Level Per-Round Control
|
|
4
|
+
*
|
|
5
|
+
* This is the second govern() call.
|
|
6
|
+
*
|
|
7
|
+
* Layer A: govern(action) — controls what agents DO (per-action, already built)
|
|
8
|
+
* Layer B: governDynamics() — controls how actions COMPOUND (per-round, this file)
|
|
9
|
+
*
|
|
10
|
+
* "We don't just control what agents do.
|
|
11
|
+
* We control how their actions shape the system over time."
|
|
12
|
+
*
|
|
13
|
+
* Three dynamics controllers:
|
|
14
|
+
* 1. Propagation Controller — governs how far actions spread
|
|
15
|
+
* 2. Amplification Engine — governs media/influence weight modulation
|
|
16
|
+
* 3. Cascade Circuit Breaker — governs feedback loops and system-level states
|
|
17
|
+
* 4. Trust-Responsive Visibility — governs the information environment
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* const dynamics = createDynamicsGovernor(policyText)
|
|
21
|
+
* for (const round of simulation) {
|
|
22
|
+
* // ... agents act, govern() filters actions ...
|
|
23
|
+
* const result = dynamics.governRound(reactions, systemState, round)
|
|
24
|
+
* // result.reactions — modified by dynamics rules
|
|
25
|
+
* // result.systemState — updated state after dynamics
|
|
26
|
+
* // result.interventions — what dynamics did this round
|
|
27
|
+
* }
|
|
28
|
+
*/
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.createDynamicsGovernor = createDynamicsGovernor;
|
|
31
|
+
exports.governDynamics = governDynamics;
|
|
32
|
+
const policyEngine_1 = require("./policyEngine");
|
|
33
|
+
// ============================================
|
|
34
|
+
// SYSTEM STATE COMPUTATION
|
|
35
|
+
// ============================================
|
|
36
|
+
function computeSystemMetrics(reactions, prevState) {
|
|
37
|
+
const impacts = reactions.map((r) => r.impact);
|
|
38
|
+
const avgImpact = impacts.reduce((s, i) => s + i, 0) / impacts.length;
|
|
39
|
+
const variance = impacts.reduce((s, i) => s + (i - avgImpact) ** 2, 0) / impacts.length;
|
|
40
|
+
// Polarization: high when agents are split into opposing camps
|
|
41
|
+
const positive = impacts.filter((i) => i > 0.2).length;
|
|
42
|
+
const negative = impacts.filter((i) => i < -0.2).length;
|
|
43
|
+
const total = impacts.length;
|
|
44
|
+
const polarization = total > 0
|
|
45
|
+
? Math.min(1, (Math.min(positive, negative) / (total * 0.5)) * (variance * 4))
|
|
46
|
+
: 0;
|
|
47
|
+
// Outrage: high when there's strong negative sentiment
|
|
48
|
+
const negativeIntensity = impacts
|
|
49
|
+
.filter((i) => i < 0)
|
|
50
|
+
.reduce((s, i) => s + Math.abs(i), 0) / Math.max(1, impacts.length);
|
|
51
|
+
const outrage = Math.min(1, negativeIntensity * 1.5);
|
|
52
|
+
return { polarization, outrage, avgImpact, variance };
|
|
53
|
+
}
|
|
54
|
+
function detectTrend(history, metric, lookback = 3) {
|
|
55
|
+
if (history.length < 2)
|
|
56
|
+
return "stable";
|
|
57
|
+
const recent = history.slice(-lookback);
|
|
58
|
+
if (recent.length < 2)
|
|
59
|
+
return "stable";
|
|
60
|
+
const first = recent[0][metric];
|
|
61
|
+
const last = recent[recent.length - 1][metric];
|
|
62
|
+
const delta = last - first;
|
|
63
|
+
if (delta > 0.1)
|
|
64
|
+
return "rising";
|
|
65
|
+
if (delta < -0.1)
|
|
66
|
+
return "falling";
|
|
67
|
+
return "stable";
|
|
68
|
+
}
|
|
69
|
+
// ============================================
|
|
70
|
+
// DYNAMICS RULE PARSING
|
|
71
|
+
// ============================================
|
|
72
|
+
function parseDynamicsRules(policyText) {
|
|
73
|
+
const parsed = (0, policyEngine_1.parseRulesFromText)(policyText);
|
|
74
|
+
const rules = [];
|
|
75
|
+
let ruleIdx = 0;
|
|
76
|
+
for (const rule of parsed.rules) {
|
|
77
|
+
const desc = rule.description.toLowerCase();
|
|
78
|
+
// Propagation rules
|
|
79
|
+
if (desc.includes("spread") || desc.includes("propagat") ||
|
|
80
|
+
desc.includes("viral") || desc.includes("limit reach") ||
|
|
81
|
+
desc.includes("contain") || desc.includes("quarantine")) {
|
|
82
|
+
rules.push({
|
|
83
|
+
id: `DYN-PROP-${++ruleIdx}`,
|
|
84
|
+
description: rule.description,
|
|
85
|
+
type: "propagation",
|
|
86
|
+
threshold: 0.6,
|
|
87
|
+
strength: rule.category === "prohibit" ? 0.8 : rule.category === "limit" ? 0.5 : 0.3,
|
|
88
|
+
monitors: "informationVelocity",
|
|
89
|
+
active: true,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
// Amplification rules
|
|
93
|
+
if (desc.includes("amplif") || desc.includes("media") ||
|
|
94
|
+
desc.includes("influence") || desc.includes("boost") ||
|
|
95
|
+
desc.includes("visibil") || desc.includes("platform")) {
|
|
96
|
+
rules.push({
|
|
97
|
+
id: `DYN-AMP-${++ruleIdx}`,
|
|
98
|
+
description: rule.description,
|
|
99
|
+
type: "amplification",
|
|
100
|
+
threshold: 0.5,
|
|
101
|
+
strength: rule.category === "prohibit" ? 0.7 : 0.4,
|
|
102
|
+
monitors: "amplification",
|
|
103
|
+
active: true,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
// Cascade / circuit breaker rules
|
|
107
|
+
if (desc.includes("cascade") || desc.includes("circuit") ||
|
|
108
|
+
desc.includes("runaway") || desc.includes("feedback") ||
|
|
109
|
+
desc.includes("spiral") || desc.includes("chain reaction") ||
|
|
110
|
+
desc.includes("contagion") || desc.includes("panic")) {
|
|
111
|
+
rules.push({
|
|
112
|
+
id: `DYN-CASC-${++ruleIdx}`,
|
|
113
|
+
description: rule.description,
|
|
114
|
+
type: "cascade",
|
|
115
|
+
threshold: 0.7,
|
|
116
|
+
strength: 0.9,
|
|
117
|
+
monitors: "cascadeRisk",
|
|
118
|
+
active: true,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
// Trust rules
|
|
122
|
+
if (desc.includes("trust") || desc.includes("legitim") ||
|
|
123
|
+
desc.includes("credib") || desc.includes("transparen") ||
|
|
124
|
+
desc.includes("accountab") || desc.includes("institution")) {
|
|
125
|
+
rules.push({
|
|
126
|
+
id: `DYN-TRUST-${++ruleIdx}`,
|
|
127
|
+
description: rule.description,
|
|
128
|
+
type: "trust",
|
|
129
|
+
threshold: 0.4,
|
|
130
|
+
strength: 0.5,
|
|
131
|
+
monitors: "trust",
|
|
132
|
+
active: true,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// Feedback dampening rules
|
|
136
|
+
if (desc.includes("dampen") || desc.includes("cool") ||
|
|
137
|
+
desc.includes("de-escalat") || desc.includes("moderate") ||
|
|
138
|
+
desc.includes("calm") || desc.includes("stabiliz")) {
|
|
139
|
+
rules.push({
|
|
140
|
+
id: `DYN-FEED-${++ruleIdx}`,
|
|
141
|
+
description: rule.description,
|
|
142
|
+
type: "feedback",
|
|
143
|
+
threshold: 0.5,
|
|
144
|
+
strength: 0.6,
|
|
145
|
+
monitors: "outrage",
|
|
146
|
+
active: true,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
// Cooling period rules
|
|
150
|
+
if (desc.includes("pause") || desc.includes("halt") ||
|
|
151
|
+
desc.includes("freeze") || desc.includes("cooling") ||
|
|
152
|
+
desc.includes("moratorium") || desc.includes("waiting period")) {
|
|
153
|
+
rules.push({
|
|
154
|
+
id: `DYN-COOL-${++ruleIdx}`,
|
|
155
|
+
description: rule.description,
|
|
156
|
+
type: "cooling",
|
|
157
|
+
threshold: 0.7,
|
|
158
|
+
strength: 0.8,
|
|
159
|
+
monitors: "outrage",
|
|
160
|
+
active: true,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Always add baseline dynamics rules if none were parsed
|
|
165
|
+
if (rules.length === 0) {
|
|
166
|
+
rules.push({
|
|
167
|
+
id: "DYN-BASE-1",
|
|
168
|
+
description: "Default propagation control — limit information spread during high outrage",
|
|
169
|
+
type: "propagation",
|
|
170
|
+
threshold: 0.7,
|
|
171
|
+
strength: 0.4,
|
|
172
|
+
monitors: "informationVelocity",
|
|
173
|
+
active: true,
|
|
174
|
+
}, {
|
|
175
|
+
id: "DYN-BASE-2",
|
|
176
|
+
description: "Default cascade protection — activate circuit breaker during crisis",
|
|
177
|
+
type: "cascade",
|
|
178
|
+
threshold: 0.8,
|
|
179
|
+
strength: 0.7,
|
|
180
|
+
monitors: "cascadeRisk",
|
|
181
|
+
active: true,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return rules;
|
|
185
|
+
}
|
|
186
|
+
// ============================================
|
|
187
|
+
// CORE: createDynamicsGovernor()
|
|
188
|
+
// ============================================
|
|
189
|
+
function createDynamicsGovernor(policyText, initialState, options) {
|
|
190
|
+
const dynamicsRules = options?.skipRules ? [] : parseDynamicsRules(policyText);
|
|
191
|
+
let state = {
|
|
192
|
+
polarization: 0.1,
|
|
193
|
+
outrage: 0.1,
|
|
194
|
+
trust: 0.7,
|
|
195
|
+
informationVelocity: 0.3,
|
|
196
|
+
amplification: 1.0,
|
|
197
|
+
cascadeRisk: 0.1,
|
|
198
|
+
contagionDepth: 2,
|
|
199
|
+
coolingPeriod: 0,
|
|
200
|
+
history: [],
|
|
201
|
+
...initialState,
|
|
202
|
+
};
|
|
203
|
+
const stats = {
|
|
204
|
+
totalRounds: 0,
|
|
205
|
+
totalInterventions: 0,
|
|
206
|
+
propagationLimits: 0,
|
|
207
|
+
amplificationDampens: 0,
|
|
208
|
+
cascadeBreakers: 0,
|
|
209
|
+
coolingPeriods: 0,
|
|
210
|
+
trustBoosts: 0,
|
|
211
|
+
peakOutrage: 0,
|
|
212
|
+
peakPolarization: 0,
|
|
213
|
+
peakCascadeRisk: 0,
|
|
214
|
+
trustSum: 0,
|
|
215
|
+
};
|
|
216
|
+
function governRound(reactions, round, agentTypes) {
|
|
217
|
+
stats.totalRounds++;
|
|
218
|
+
const interventions = [];
|
|
219
|
+
const rulesFired = [];
|
|
220
|
+
const governed = reactions.map((r) => ({ ...r }));
|
|
221
|
+
// --- Step 1: Compute current system metrics from reactions ---
|
|
222
|
+
const metrics = computeSystemMetrics(reactions, state);
|
|
223
|
+
// Update state from observed metrics
|
|
224
|
+
state.polarization = state.polarization * 0.6 + metrics.polarization * 0.4;
|
|
225
|
+
state.outrage = state.outrage * 0.5 + metrics.outrage * 0.5;
|
|
226
|
+
state.informationVelocity = Math.min(1, 0.3 + metrics.variance * 2);
|
|
227
|
+
state.cascadeRisk = Math.min(1, state.outrage * 0.3 +
|
|
228
|
+
state.polarization * 0.2 +
|
|
229
|
+
state.informationVelocity * 0.2 +
|
|
230
|
+
(1 - state.trust) * 0.3);
|
|
231
|
+
// Trust erodes when outrage is high, recovers when calm
|
|
232
|
+
if (state.outrage > 0.5) {
|
|
233
|
+
state.trust = Math.max(0.05, state.trust - 0.05 * state.outrage);
|
|
234
|
+
}
|
|
235
|
+
else if (state.outrage < 0.2) {
|
|
236
|
+
state.trust = Math.min(1, state.trust + 0.02);
|
|
237
|
+
}
|
|
238
|
+
// Track peaks
|
|
239
|
+
stats.peakOutrage = Math.max(stats.peakOutrage, state.outrage);
|
|
240
|
+
stats.peakPolarization = Math.max(stats.peakPolarization, state.polarization);
|
|
241
|
+
stats.peakCascadeRisk = Math.max(stats.peakCascadeRisk, state.cascadeRisk);
|
|
242
|
+
stats.trustSum += state.trust;
|
|
243
|
+
// --- Step 2: Apply cooling period (from previous round's cascade breaker) ---
|
|
244
|
+
if (state.coolingPeriod > 0) {
|
|
245
|
+
const coolingStrength = 0.4 + (state.coolingPeriod * 0.1);
|
|
246
|
+
for (const agent of governed) {
|
|
247
|
+
if (agent.impact < -0.1) {
|
|
248
|
+
const before = agent.impact;
|
|
249
|
+
agent.impact = agent.impact * (1 - coolingStrength);
|
|
250
|
+
agent.confidence = Math.max(0.1, agent.confidence * 0.7);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
interventions.push({
|
|
254
|
+
type: "COOLING_PERIOD",
|
|
255
|
+
description: `Cooling period active (${state.coolingPeriod} rounds remaining) — reactive posting dampened by ${(coolingStrength * 100).toFixed(0)}%`,
|
|
256
|
+
triggeredBy: "COOLING_PERIOD",
|
|
257
|
+
agentsAffected: governed.filter((a) => a.impact < -0.1).length,
|
|
258
|
+
magnitude: coolingStrength,
|
|
259
|
+
effect: { metric: "outrage", before: state.outrage, after: state.outrage * (1 - coolingStrength * 0.3) },
|
|
260
|
+
});
|
|
261
|
+
state.coolingPeriod--;
|
|
262
|
+
stats.coolingPeriods++;
|
|
263
|
+
}
|
|
264
|
+
// --- Step 3: Apply each dynamics rule ---
|
|
265
|
+
for (const rule of dynamicsRules) {
|
|
266
|
+
if (!rule.active)
|
|
267
|
+
continue;
|
|
268
|
+
const currentValue = state[rule.monitors];
|
|
269
|
+
switch (rule.type) {
|
|
270
|
+
// ========== PROPAGATION CONTROL ==========
|
|
271
|
+
case "propagation": {
|
|
272
|
+
if (state.informationVelocity > rule.threshold || state.outrage > 0.6) {
|
|
273
|
+
const dampFactor = 1 - (rule.strength * 0.5);
|
|
274
|
+
let affected = 0;
|
|
275
|
+
// Limit propagation depth
|
|
276
|
+
const prevDepth = state.contagionDepth;
|
|
277
|
+
state.contagionDepth = Math.max(1, Math.floor(state.contagionDepth * dampFactor));
|
|
278
|
+
// Reduce the impact of agents whose reactions are extreme
|
|
279
|
+
// (simulates limiting how far their content spreads)
|
|
280
|
+
for (const agent of governed) {
|
|
281
|
+
if (Math.abs(agent.impact) > 0.4) {
|
|
282
|
+
const before = agent.impact;
|
|
283
|
+
agent.impact = agent.impact * dampFactor;
|
|
284
|
+
affected++;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (affected > 0) {
|
|
288
|
+
const beforeVelocity = state.informationVelocity;
|
|
289
|
+
state.informationVelocity *= dampFactor;
|
|
290
|
+
interventions.push({
|
|
291
|
+
type: "PROPAGATION_LIMIT",
|
|
292
|
+
description: `Propagation limited: spread depth ${prevDepth} → ${state.contagionDepth}, ${affected} extreme agents dampened (${rule.description})`,
|
|
293
|
+
triggeredBy: rule.id,
|
|
294
|
+
agentsAffected: affected,
|
|
295
|
+
magnitude: rule.strength,
|
|
296
|
+
effect: { metric: "informationVelocity", before: beforeVelocity, after: state.informationVelocity },
|
|
297
|
+
});
|
|
298
|
+
rulesFired.push(rule.id);
|
|
299
|
+
stats.propagationLimits++;
|
|
300
|
+
stats.totalInterventions++;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
// ========== AMPLIFICATION DAMPENING ==========
|
|
306
|
+
case "amplification": {
|
|
307
|
+
const polarizationTrend = detectTrend(state.history, "polarization");
|
|
308
|
+
if (state.polarization > rule.threshold || polarizationTrend === "rising") {
|
|
309
|
+
const dampFactor = 1 - (rule.strength * 0.4);
|
|
310
|
+
let affected = 0;
|
|
311
|
+
// Reduce amplification for media/influencer type agents
|
|
312
|
+
for (const agent of governed) {
|
|
313
|
+
const agentType = agentTypes?.get(agent.stakeholder_id) ?? "";
|
|
314
|
+
const isAmplifier = agentType.toLowerCase().includes("media") ||
|
|
315
|
+
agentType.toLowerCase().includes("influencer") ||
|
|
316
|
+
agent.stakeholder_id.toLowerCase().includes("media") ||
|
|
317
|
+
agent.stakeholder_id.toLowerCase().includes("记者") || // journalist (Chinese)
|
|
318
|
+
agent.stakeholder_id.toLowerCase().includes("媒") || // media (Chinese)
|
|
319
|
+
Math.abs(agent.impact) > 0.6; // or any agent with extreme impact
|
|
320
|
+
if (isAmplifier) {
|
|
321
|
+
const before = agent.impact;
|
|
322
|
+
agent.impact = agent.impact * dampFactor;
|
|
323
|
+
agent.confidence = Math.max(0.15, agent.confidence * 0.8);
|
|
324
|
+
affected++;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (affected > 0) {
|
|
328
|
+
const beforeAmp = state.amplification;
|
|
329
|
+
state.amplification *= dampFactor;
|
|
330
|
+
interventions.push({
|
|
331
|
+
type: "AMPLIFICATION_DAMPEN",
|
|
332
|
+
description: `Amplification dampened: ${affected} high-influence agents reduced by ${((1 - dampFactor) * 100).toFixed(0)}% (polarization ${state.polarization.toFixed(2)}, trend: ${polarizationTrend}) — ${rule.description}`,
|
|
333
|
+
triggeredBy: rule.id,
|
|
334
|
+
agentsAffected: affected,
|
|
335
|
+
magnitude: rule.strength,
|
|
336
|
+
effect: { metric: "amplification", before: beforeAmp, after: state.amplification },
|
|
337
|
+
});
|
|
338
|
+
rulesFired.push(rule.id);
|
|
339
|
+
stats.amplificationDampens++;
|
|
340
|
+
stats.totalInterventions++;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
// ========== CASCADE CIRCUIT BREAKER ==========
|
|
346
|
+
case "cascade": {
|
|
347
|
+
if (state.cascadeRisk > rule.threshold) {
|
|
348
|
+
const outrageRising = detectTrend(state.history, "outrage") === "rising";
|
|
349
|
+
const breakerStrength = outrageRising ? rule.strength : rule.strength * 0.6;
|
|
350
|
+
let affected = 0;
|
|
351
|
+
// Compress ALL negative reactions toward zero
|
|
352
|
+
for (const agent of governed) {
|
|
353
|
+
if (agent.impact < -0.15) {
|
|
354
|
+
const before = agent.impact;
|
|
355
|
+
agent.impact = agent.impact * (1 - breakerStrength);
|
|
356
|
+
agent.confidence = Math.max(0.1, agent.confidence * (1 - breakerStrength * 0.5));
|
|
357
|
+
affected++;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// Activate cooling period
|
|
361
|
+
if (state.coolingPeriod === 0 && outrageRising) {
|
|
362
|
+
state.coolingPeriod = 2; // 2 rounds of cooling
|
|
363
|
+
}
|
|
364
|
+
const beforeCascade = state.cascadeRisk;
|
|
365
|
+
state.cascadeRisk *= (1 - breakerStrength * 0.4);
|
|
366
|
+
interventions.push({
|
|
367
|
+
type: "CASCADE_BREAKER",
|
|
368
|
+
description: `CASCADE CIRCUIT BREAKER: ${affected} agents compressed, cascade risk ${beforeCascade.toFixed(2)} → ${state.cascadeRisk.toFixed(2)}${outrageRising ? " + cooling period activated" : ""} — ${rule.description}`,
|
|
369
|
+
triggeredBy: rule.id,
|
|
370
|
+
agentsAffected: affected,
|
|
371
|
+
magnitude: breakerStrength,
|
|
372
|
+
effect: { metric: "cascadeRisk", before: beforeCascade, after: state.cascadeRisk },
|
|
373
|
+
});
|
|
374
|
+
rulesFired.push(rule.id);
|
|
375
|
+
stats.cascadeBreakers++;
|
|
376
|
+
stats.totalInterventions++;
|
|
377
|
+
}
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
// ========== TRUST-RESPONSIVE VISIBILITY ==========
|
|
381
|
+
case "trust": {
|
|
382
|
+
if (state.trust < rule.threshold) {
|
|
383
|
+
let affected = 0;
|
|
384
|
+
// Boost official/institutional agents, dampen hostile/unknown ones
|
|
385
|
+
for (const agent of governed) {
|
|
386
|
+
const agentType = agentTypes?.get(agent.stakeholder_id) ?? "";
|
|
387
|
+
const isOfficial = agentType.toLowerCase().includes("official") ||
|
|
388
|
+
agentType.toLowerCase().includes("government") ||
|
|
389
|
+
agentType.toLowerCase().includes("institution") ||
|
|
390
|
+
agent.stakeholder_id.toLowerCase().includes("regulator") ||
|
|
391
|
+
agent.stakeholder_id.toLowerCase().includes("central") ||
|
|
392
|
+
agent.stakeholder_id.toLowerCase().includes("official") ||
|
|
393
|
+
agent.stakeholder_id.toLowerCase().includes("管理") || // management (Chinese)
|
|
394
|
+
agent.stakeholder_id.toLowerCase().includes("官方"); // official (Chinese)
|
|
395
|
+
if (isOfficial && agent.impact > -0.2) {
|
|
396
|
+
// Boost visibility of official sources
|
|
397
|
+
const before = agent.impact;
|
|
398
|
+
agent.impact = Math.min(1, agent.impact * 1.3 + 0.1);
|
|
399
|
+
agent.confidence = Math.min(1, agent.confidence * 1.2);
|
|
400
|
+
affected++;
|
|
401
|
+
}
|
|
402
|
+
else if (!isOfficial && agent.impact < -0.3) {
|
|
403
|
+
// Reduce visibility of unverified negative claims
|
|
404
|
+
agent.impact = agent.impact * 0.7;
|
|
405
|
+
affected++;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (affected > 0) {
|
|
409
|
+
const beforeTrust = state.trust;
|
|
410
|
+
state.trust = Math.min(1, state.trust + 0.03 * rule.strength);
|
|
411
|
+
interventions.push({
|
|
412
|
+
type: "TRUST_BOOST",
|
|
413
|
+
description: `Trust-responsive visibility: official sources boosted, unverified claims dampened (trust ${beforeTrust.toFixed(2)} → ${state.trust.toFixed(2)}) — ${rule.description}`,
|
|
414
|
+
triggeredBy: rule.id,
|
|
415
|
+
agentsAffected: affected,
|
|
416
|
+
magnitude: rule.strength,
|
|
417
|
+
effect: { metric: "trust", before: beforeTrust, after: state.trust },
|
|
418
|
+
});
|
|
419
|
+
rulesFired.push(rule.id);
|
|
420
|
+
stats.trustBoosts++;
|
|
421
|
+
stats.totalInterventions++;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
// ========== FEEDBACK DAMPENING ==========
|
|
427
|
+
case "feedback": {
|
|
428
|
+
const outrageTrend = detectTrend(state.history, "outrage");
|
|
429
|
+
if (state.outrage > rule.threshold && outrageTrend === "rising") {
|
|
430
|
+
const dampFactor = 1 - (rule.strength * 0.35);
|
|
431
|
+
let affected = 0;
|
|
432
|
+
// Reduce ALL reactive behavior — pull everything toward zero
|
|
433
|
+
for (const agent of governed) {
|
|
434
|
+
if (Math.abs(agent.impact) > 0.2) {
|
|
435
|
+
const before = agent.impact;
|
|
436
|
+
agent.impact = agent.impact * dampFactor;
|
|
437
|
+
affected++;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (affected > 0) {
|
|
441
|
+
const beforeOutrage = state.outrage;
|
|
442
|
+
state.outrage *= dampFactor;
|
|
443
|
+
interventions.push({
|
|
444
|
+
type: "FEEDBACK_DAMPEN",
|
|
445
|
+
description: `Feedback loop dampened: ${affected} reactive agents moderated, outrage ${beforeOutrage.toFixed(2)} → ${state.outrage.toFixed(2)} (trend: ${outrageTrend}) — ${rule.description}`,
|
|
446
|
+
triggeredBy: rule.id,
|
|
447
|
+
agentsAffected: affected,
|
|
448
|
+
magnitude: rule.strength,
|
|
449
|
+
effect: { metric: "outrage", before: beforeOutrage, after: state.outrage },
|
|
450
|
+
});
|
|
451
|
+
rulesFired.push(rule.id);
|
|
452
|
+
stats.totalInterventions++;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
// ========== COOLING PERIOD TRIGGER ==========
|
|
458
|
+
case "cooling": {
|
|
459
|
+
if (state.outrage > rule.threshold && state.coolingPeriod === 0) {
|
|
460
|
+
state.coolingPeriod = Math.ceil(rule.strength * 3); // 1-3 rounds
|
|
461
|
+
interventions.push({
|
|
462
|
+
type: "COOLING_PERIOD",
|
|
463
|
+
description: `Cooling period triggered: ${state.coolingPeriod} round(s) of dampened reactive posting (outrage: ${state.outrage.toFixed(2)}) — ${rule.description}`,
|
|
464
|
+
triggeredBy: rule.id,
|
|
465
|
+
agentsAffected: 0,
|
|
466
|
+
magnitude: rule.strength,
|
|
467
|
+
effect: { metric: "outrage", before: state.outrage, after: state.outrage },
|
|
468
|
+
});
|
|
469
|
+
rulesFired.push(rule.id);
|
|
470
|
+
stats.coolingPeriods++;
|
|
471
|
+
stats.totalInterventions++;
|
|
472
|
+
}
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// --- Step 4: Reclamp all impacts ---
|
|
478
|
+
for (const agent of governed) {
|
|
479
|
+
agent.impact = Math.max(-1, Math.min(1, agent.impact));
|
|
480
|
+
agent.impact = Number(agent.impact.toFixed(3));
|
|
481
|
+
agent.confidence = Math.max(0.05, Math.min(1, agent.confidence));
|
|
482
|
+
agent.confidence = Number(agent.confidence.toFixed(3));
|
|
483
|
+
}
|
|
484
|
+
// --- Step 5: Record state snapshot ---
|
|
485
|
+
const postMetrics = computeSystemMetrics(governed, state);
|
|
486
|
+
state.history.push({
|
|
487
|
+
round,
|
|
488
|
+
polarization: Number(state.polarization.toFixed(3)),
|
|
489
|
+
outrage: Number(state.outrage.toFixed(3)),
|
|
490
|
+
trust: Number(state.trust.toFixed(3)),
|
|
491
|
+
amplification: Number(state.amplification.toFixed(3)),
|
|
492
|
+
cascadeRisk: Number(state.cascadeRisk.toFixed(3)),
|
|
493
|
+
avgImpact: Number(postMetrics.avgImpact.toFixed(3)),
|
|
494
|
+
agentCount: governed.length,
|
|
495
|
+
});
|
|
496
|
+
// --- Step 6: Determine trajectory ---
|
|
497
|
+
let trajectory;
|
|
498
|
+
if (state.coolingPeriod > 0) {
|
|
499
|
+
trajectory = "cooling";
|
|
500
|
+
}
|
|
501
|
+
else if (state.cascadeRisk > 0.7) {
|
|
502
|
+
trajectory = "critical";
|
|
503
|
+
}
|
|
504
|
+
else if (detectTrend(state.history, "outrage") === "rising" || detectTrend(state.history, "polarization") === "rising") {
|
|
505
|
+
trajectory = "escalating";
|
|
506
|
+
}
|
|
507
|
+
else if (detectTrend(state.history, "outrage") === "falling" && detectTrend(state.history, "polarization") === "falling") {
|
|
508
|
+
trajectory = "de-escalating";
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
trajectory = "stable";
|
|
512
|
+
}
|
|
513
|
+
// Predict next-round risk
|
|
514
|
+
const predictedRisk = Math.min(1, state.cascadeRisk * 0.4 +
|
|
515
|
+
state.outrage * 0.3 +
|
|
516
|
+
state.polarization * 0.2 +
|
|
517
|
+
state.informationVelocity * 0.1);
|
|
518
|
+
return {
|
|
519
|
+
reactions: governed,
|
|
520
|
+
systemState: { ...state, history: [...state.history] },
|
|
521
|
+
interventions,
|
|
522
|
+
rulesFired,
|
|
523
|
+
trajectory,
|
|
524
|
+
predictedRisk: Number(predictedRisk.toFixed(3)),
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function reset() {
|
|
528
|
+
state = {
|
|
529
|
+
polarization: 0.1,
|
|
530
|
+
outrage: 0.1,
|
|
531
|
+
trust: 0.7,
|
|
532
|
+
informationVelocity: 0.3,
|
|
533
|
+
amplification: 1.0,
|
|
534
|
+
cascadeRisk: 0.1,
|
|
535
|
+
contagionDepth: 2,
|
|
536
|
+
coolingPeriod: 0,
|
|
537
|
+
history: [],
|
|
538
|
+
...initialState,
|
|
539
|
+
};
|
|
540
|
+
stats.totalRounds = 0;
|
|
541
|
+
stats.totalInterventions = 0;
|
|
542
|
+
stats.propagationLimits = 0;
|
|
543
|
+
stats.amplificationDampens = 0;
|
|
544
|
+
stats.cascadeBreakers = 0;
|
|
545
|
+
stats.coolingPeriods = 0;
|
|
546
|
+
stats.trustBoosts = 0;
|
|
547
|
+
stats.peakOutrage = 0;
|
|
548
|
+
stats.peakPolarization = 0;
|
|
549
|
+
stats.peakCascadeRisk = 0;
|
|
550
|
+
stats.trustSum = 0;
|
|
551
|
+
}
|
|
552
|
+
return {
|
|
553
|
+
governRound,
|
|
554
|
+
get state() { return { ...state, history: [...state.history] }; },
|
|
555
|
+
/** Mutate the internal state directly (used by adapters for governance feedback) */
|
|
556
|
+
mutateState(fn) { fn(state); },
|
|
557
|
+
get rules() { return [...dynamicsRules]; },
|
|
558
|
+
get stats() {
|
|
559
|
+
return {
|
|
560
|
+
totalRounds: stats.totalRounds,
|
|
561
|
+
totalInterventions: stats.totalInterventions,
|
|
562
|
+
propagationLimits: stats.propagationLimits,
|
|
563
|
+
amplificationDampens: stats.amplificationDampens,
|
|
564
|
+
cascadeBreakers: stats.cascadeBreakers,
|
|
565
|
+
coolingPeriods: stats.coolingPeriods,
|
|
566
|
+
trustBoosts: stats.trustBoosts,
|
|
567
|
+
peakOutrage: stats.peakOutrage,
|
|
568
|
+
peakPolarization: stats.peakPolarization,
|
|
569
|
+
peakCascadeRisk: stats.peakCascadeRisk,
|
|
570
|
+
averageTrust: stats.totalRounds > 0 ? Number((stats.trustSum / stats.totalRounds).toFixed(3)) : 0.7,
|
|
571
|
+
};
|
|
572
|
+
},
|
|
573
|
+
reset,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
// ============================================
|
|
577
|
+
// CONVENIENCE: governDynamics() one-shot
|
|
578
|
+
// ============================================
|
|
579
|
+
/**
|
|
580
|
+
* One-shot dynamics governance for a single round.
|
|
581
|
+
*
|
|
582
|
+
* For repeated calls, use createDynamicsGovernor() instead.
|
|
583
|
+
* This is mainly useful for testing/playground.
|
|
584
|
+
*/
|
|
585
|
+
function governDynamics(reactions, policyText, systemState) {
|
|
586
|
+
const governor = createDynamicsGovernor(policyText, systemState);
|
|
587
|
+
return governor.governRound(reactions, 0);
|
|
588
|
+
}
|