@agent-e/core 1.1.0

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/dist/index.js ADDED
@@ -0,0 +1,3442 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ALL_PRINCIPLES: () => ALL_PRINCIPLES,
24
+ AgentE: () => AgentE,
25
+ BOOTSTRAP_PRINCIPLES: () => BOOTSTRAP_PRINCIPLES,
26
+ CURRENCY_FLOW_PRINCIPLES: () => CURRENCY_FLOW_PRINCIPLES,
27
+ DEFAULT_THRESHOLDS: () => DEFAULT_THRESHOLDS,
28
+ DecisionLog: () => DecisionLog,
29
+ Diagnoser: () => Diagnoser,
30
+ Executor: () => Executor,
31
+ FEEDBACK_LOOP_PRINCIPLES: () => FEEDBACK_LOOP_PRINCIPLES,
32
+ INCENTIVE_PRINCIPLES: () => INCENTIVE_PRINCIPLES,
33
+ LIVEOPS_PRINCIPLES: () => LIVEOPS_PRINCIPLES,
34
+ MARKET_DYNAMICS_PRINCIPLES: () => MARKET_DYNAMICS_PRINCIPLES,
35
+ MEASUREMENT_PRINCIPLES: () => MEASUREMENT_PRINCIPLES,
36
+ MetricStore: () => MetricStore,
37
+ OPEN_ECONOMY_PRINCIPLES: () => OPEN_ECONOMY_PRINCIPLES,
38
+ Observer: () => Observer,
39
+ P10_SpawnWeightingUsesInversePopulation: () => P10_SpawnWeightingUsesInversePopulation,
40
+ P11_TwoTierPressure: () => P11_TwoTierPressure,
41
+ P12_OnePrimaryFaucet: () => P12_OnePrimaryFaucet,
42
+ P13_PotsAreZeroSumAndSelfRegulate: () => P13_PotsAreZeroSumAndSelfRegulate,
43
+ P14_TrackActualInjection: () => P14_TrackActualInjection,
44
+ P15_PoolsNeedCapAndDecay: () => P15_PoolsNeedCapAndDecay,
45
+ P16_WithdrawalPenaltyScales: () => P16_WithdrawalPenaltyScales,
46
+ P17_GracePeriodBeforeIntervention: () => P17_GracePeriodBeforeIntervention,
47
+ P18_FirstProducerNeedsStartingInventory: () => P18_FirstProducerNeedsStartingInventory,
48
+ P19_StartingSupplyExceedsDemand: () => P19_StartingSupplyExceedsDemand,
49
+ P1_ProductionMatchesConsumption: () => P1_ProductionMatchesConsumption,
50
+ P20_DecayPreventsAccumulation: () => P20_DecayPreventsAccumulation,
51
+ P21_PriceFromGlobalSupply: () => P21_PriceFromGlobalSupply,
52
+ P22_MarketAwarenessPreventsSurplus: () => P22_MarketAwarenessPreventsSurplus,
53
+ P23_ProfitabilityFactorsFeasibility: () => P23_ProfitabilityFactorsFeasibility,
54
+ P24_BlockedAgentsDecayFaster: () => P24_BlockedAgentsDecayFaster,
55
+ P25_CorrectLeversForCorrectProblems: () => P25_CorrectLeversForCorrectProblems,
56
+ P26_ContinuousPressureBeatsThresholdCuts: () => P26_ContinuousPressureBeatsThresholdCuts,
57
+ P27_AdjustmentsNeedCooldowns: () => P27_AdjustmentsNeedCooldowns,
58
+ P28_StructuralDominanceIsNotPathological: () => P28_StructuralDominanceIsNotPathological,
59
+ P29_PinchPoint: () => P29_PinchPoint,
60
+ P2_ClosedLoopsNeedDirectHandoff: () => P2_ClosedLoopsNeedDirectHandoff,
61
+ P30_MovingPinchPoint: () => P30_MovingPinchPoint,
62
+ P31_AnchorValueTracking: () => P31_AnchorValueTracking,
63
+ P32_VelocityAboveSupply: () => P32_VelocityAboveSupply,
64
+ P33_FairNotEqual: () => P33_FairNotEqual,
65
+ P34_ExtractionRatio: () => P34_ExtractionRatio,
66
+ P35_DestructionCreatesValue: () => P35_DestructionCreatesValue,
67
+ P36_MechanicFrictionDetector: () => P36_MechanicFrictionDetector,
68
+ P37_LatecommerProblem: () => P37_LatecommerProblem,
69
+ P38_CommunicationPreventsRevolt: () => P38_CommunicationPreventsRevolt,
70
+ P39_TheLagPrinciple: () => P39_TheLagPrinciple,
71
+ P3_BootstrapCapitalCoversFirstTransaction: () => P3_BootstrapCapitalCoversFirstTransaction,
72
+ P40_ReplacementRate: () => P40_ReplacementRate,
73
+ P41_MultiResolutionMonitoring: () => P41_MultiResolutionMonitoring,
74
+ P42_TheMedianPrinciple: () => P42_TheMedianPrinciple,
75
+ P43_SimulationMinimum: () => P43_SimulationMinimum,
76
+ P44_ComplexityBudget: () => P44_ComplexityBudget,
77
+ P45_TimeBudget: () => P45_TimeBudget,
78
+ P46_PersonaDiversity: () => P46_PersonaDiversity,
79
+ P47_SmokeTest: () => P47_SmokeTest,
80
+ P48_CurrencyInsulation: () => P48_CurrencyInsulation,
81
+ P49_IdleAssetTax: () => P49_IdleAssetTax,
82
+ P4_MaterialsFlowFasterThanCooldown: () => P4_MaterialsFlowFasterThanCooldown,
83
+ P50_PayPowerRatio: () => P50_PayPowerRatio,
84
+ P51_SharkTooth: () => P51_SharkTooth,
85
+ P52_EndowmentEffect: () => P52_EndowmentEffect,
86
+ P53_EventCompletionRate: () => P53_EventCompletionRate,
87
+ P54_LiveOpsCadence: () => P54_LiveOpsCadence,
88
+ P55_ArbitrageThermometer: () => P55_ArbitrageThermometer,
89
+ P56_ContentDropShock: () => P56_ContentDropShock,
90
+ P57_CombinatorialPriceSpace: () => P57_CombinatorialPriceSpace,
91
+ P58_NoNaturalNumeraire: () => P58_NoNaturalNumeraire,
92
+ P59_GiftEconomyNoise: () => P59_GiftEconomyNoise,
93
+ P5_ProfitabilityIsCompetitive: () => P5_ProfitabilityIsCompetitive,
94
+ P60_SurplusDisposalAsymmetry: () => P60_SurplusDisposalAsymmetry,
95
+ P6_CrowdingMultiplierOnAllRoles: () => P6_CrowdingMultiplierOnAllRoles,
96
+ P7_NonSpecialistsSubsidiseSpecialists: () => P7_NonSpecialistsSubsidiseSpecialists,
97
+ P8_RegulatorCannotFightDesign: () => P8_RegulatorCannotFightDesign,
98
+ P9_RoleSwitchingNeedsFriction: () => P9_RoleSwitchingNeedsFriction,
99
+ PERSONA_HEALTHY_RANGES: () => PERSONA_HEALTHY_RANGES,
100
+ PLAYER_EXPERIENCE_PRINCIPLES: () => PLAYER_EXPERIENCE_PRINCIPLES,
101
+ POPULATION_PRINCIPLES: () => POPULATION_PRINCIPLES,
102
+ PersonaTracker: () => PersonaTracker,
103
+ Planner: () => Planner,
104
+ REGULATOR_PRINCIPLES: () => REGULATOR_PRINCIPLES,
105
+ RESOURCE_MGMT_PRINCIPLES: () => RESOURCE_MGMT_PRINCIPLES,
106
+ STATISTICAL_PRINCIPLES: () => STATISTICAL_PRINCIPLES,
107
+ SUPPLY_CHAIN_PRINCIPLES: () => SUPPLY_CHAIN_PRINCIPLES,
108
+ SYSTEM_DYNAMICS_PRINCIPLES: () => SYSTEM_DYNAMICS_PRINCIPLES,
109
+ Simulator: () => Simulator,
110
+ emptyMetrics: () => emptyMetrics
111
+ });
112
+ module.exports = __toCommonJS(index_exports);
113
+
114
+ // src/defaults.ts
115
+ var DEFAULT_THRESHOLDS = {
116
+ // Statistical (P42-P43)
117
+ meanMedianDivergenceMax: 0.3,
118
+ simulationMinIterations: 100,
119
+ // Population (P46)
120
+ personaMonocultureMax: 0.4,
121
+ personaMinClusters: 3,
122
+ // Open Economy (P34, P47-P48)
123
+ extractionRatioYellow: 0.5,
124
+ extractionRatioRed: 0.65,
125
+ smokeTestWarning: 0.3,
126
+ smokeTestCritical: 0.1,
127
+ currencyInsulationMax: 0.5,
128
+ // Player Experience (P45, P50)
129
+ timeBudgetRatio: 0.8,
130
+ payPowerRatioMax: 2,
131
+ payPowerRatioTarget: 1.5,
132
+ // LiveOps (P51, P53)
133
+ sharkToothPeakDecay: 0.95,
134
+ sharkToothValleyDecay: 0.9,
135
+ eventCompletionMin: 0.4,
136
+ eventCompletionMax: 0.8,
137
+ // System Dynamics (P39, P44)
138
+ lagMultiplierMin: 3,
139
+ lagMultiplierMax: 5,
140
+ complexityBudgetMax: 20,
141
+ // Resource (P40)
142
+ replacementRateMultiplier: 2,
143
+ // Regulator (P26-P27)
144
+ maxAdjustmentPercent: 0.15,
145
+ cooldownTicks: 15,
146
+ // Currency (P13)
147
+ arenaWinRate: 0.65,
148
+ arenaHouseCut: 0.1,
149
+ // Population balance (P9)
150
+ roleSwitchFrictionMax: 0.05,
151
+ // >5% of population switching in one period = herd
152
+ // Pool limits (P15)
153
+ poolCapPercent: 0.05,
154
+ // no pool > 5% of total currency
155
+ poolDecayRate: 0.02,
156
+ // Profitability (P5)
157
+ stampedeProfitRatio: 3,
158
+ // one role's profit > 3× median → stampede risk
159
+ // Satisfaction (P24)
160
+ blockedAgentMaxFraction: 0.15,
161
+ // Gini (P33)
162
+ giniWarnThreshold: 0.45,
163
+ giniRedThreshold: 0.6,
164
+ // Churn (P9)
165
+ churnWarnRate: 0.05,
166
+ // Net flow (P12)
167
+ netFlowWarnThreshold: 10,
168
+ // V1.1 (P55-P60)
169
+ arbitrageIndexWarning: 0.35,
170
+ arbitrageIndexCritical: 0.55,
171
+ contentDropCooldownTicks: 30,
172
+ postDropArbitrageMax: 0.45,
173
+ relativePriceConvergenceTarget: 0.85,
174
+ priceDiscoveryWindowTicks: 20,
175
+ giftTradeFilterRatio: 0.15,
176
+ disposalTradeWeightDiscount: 0.5
177
+ };
178
+ var PERSONA_HEALTHY_RANGES = {
179
+ Gamer: { min: 0.2, max: 0.4 },
180
+ Trader: { min: 0.05, max: 0.15 },
181
+ Collector: { min: 0.05, max: 0.15 },
182
+ Speculator: { min: 0, max: 0.1 },
183
+ Earner: { min: 0, max: 0.15 },
184
+ Builder: { min: 0.05, max: 0.15 },
185
+ Social: { min: 0.1, max: 0.2 },
186
+ Whale: { min: 0, max: 0.05 },
187
+ Influencer: { min: 0, max: 0.05 }
188
+ };
189
+
190
+ // src/Observer.ts
191
+ var Observer = class {
192
+ constructor() {
193
+ this.previousMetrics = null;
194
+ this.previousPrices = {};
195
+ this.customMetricFns = {};
196
+ this.anchorBaseline = null;
197
+ }
198
+ registerCustomMetric(name, fn) {
199
+ this.customMetricFns[name] = fn;
200
+ }
201
+ compute(state, recentEvents) {
202
+ const tick = state.tick;
203
+ const balances = Object.values(state.agentBalances);
204
+ const roles = Object.values(state.agentRoles);
205
+ const totalAgents = balances.length;
206
+ const totalSupply = balances.reduce((s, b) => s + b, 0);
207
+ const faucetVolume = recentEvents.filter((e) => e.type === "mint" || e.type === "spawn").reduce((s, e) => s + (e.amount ?? 0), 0);
208
+ const sinkVolume = recentEvents.filter((e) => e.type === "burn" || e.type === "consume").reduce((s, e) => s + (e.amount ?? 0), 0);
209
+ const netFlow = faucetVolume - sinkVolume;
210
+ const tapSinkRatio = sinkVolume > 0 ? faucetVolume / sinkVolume : faucetVolume > 0 ? Infinity : 1;
211
+ const prevSupply = this.previousMetrics?.totalSupply ?? totalSupply;
212
+ const inflationRate = prevSupply > 0 ? (totalSupply - prevSupply) / prevSupply : 0;
213
+ const tradeEvents = recentEvents.filter((e) => e.type === "trade");
214
+ const velocity = totalSupply > 0 ? tradeEvents.length / totalSupply : 0;
215
+ const sortedBalances = [...balances].sort((a, b) => a - b);
216
+ const meanBalance = totalAgents > 0 ? totalSupply / totalAgents : 0;
217
+ const medianBalance = computeMedian(sortedBalances);
218
+ const top10Idx = Math.floor(totalAgents * 0.9);
219
+ const top10Sum = sortedBalances.slice(top10Idx).reduce((s, b) => s + b, 0);
220
+ const top10PctShare = totalSupply > 0 ? top10Sum / totalSupply : 0;
221
+ const giniCoefficient = computeGini(sortedBalances);
222
+ const meanMedianDivergence = medianBalance > 0 ? Math.abs(meanBalance - medianBalance) / medianBalance : 0;
223
+ const populationByRole = {};
224
+ const roleShares = {};
225
+ for (const role of roles) {
226
+ populationByRole[role] = (populationByRole[role] ?? 0) + 1;
227
+ }
228
+ for (const [role, count] of Object.entries(populationByRole)) {
229
+ roleShares[role] = count / Math.max(1, totalAgents);
230
+ }
231
+ const churnByRole = {};
232
+ const roleChanges = recentEvents.filter((e) => e.type === "churn" || e.type === "role_change");
233
+ for (const e of roleChanges) {
234
+ const role = e.role ?? "unknown";
235
+ churnByRole[role] = (churnByRole[role] ?? 0) + 1;
236
+ }
237
+ const churnCount = recentEvents.filter((e) => e.type === "churn").length;
238
+ const churnRate = churnCount / Math.max(1, totalAgents);
239
+ const prices = { ...state.marketPrices };
240
+ const priceVolatility = {};
241
+ for (const [resource, price] of Object.entries(prices)) {
242
+ const prev = this.previousPrices[resource] ?? price;
243
+ priceVolatility[resource] = prev > 0 ? Math.abs(price - prev) / prev : 0;
244
+ }
245
+ this.previousPrices = { ...prices };
246
+ const priceValues = Object.values(prices);
247
+ const priceIndex = priceValues.length > 0 ? priceValues.reduce((s, p) => s + p, 0) / priceValues.length : 0;
248
+ const supplyByResource = {};
249
+ for (const inv of Object.values(state.agentInventories)) {
250
+ for (const [resource, qty] of Object.entries(inv)) {
251
+ supplyByResource[resource] = (supplyByResource[resource] ?? 0) + qty;
252
+ }
253
+ }
254
+ const demandSignals = {};
255
+ for (const e of tradeEvents) {
256
+ if (e.resource) {
257
+ demandSignals[e.resource] = (demandSignals[e.resource] ?? 0) + (e.amount ?? 1);
258
+ }
259
+ }
260
+ const pinchPoints = {};
261
+ for (const resource of /* @__PURE__ */ new Set([...Object.keys(supplyByResource), ...Object.keys(demandSignals)])) {
262
+ const s = supplyByResource[resource] ?? 0;
263
+ const d = demandSignals[resource] ?? 0;
264
+ if (d > 2 && s / d < 0.5) {
265
+ pinchPoints[resource] = "scarce";
266
+ } else if (s > 3 && d > 0 && s / d > 3) {
267
+ pinchPoints[resource] = "oversupplied";
268
+ } else {
269
+ pinchPoints[resource] = "optimal";
270
+ }
271
+ }
272
+ const productionIndex = recentEvents.filter((e) => e.type === "produce").reduce((s, e) => s + (e.amount ?? 1), 0);
273
+ const maxPossibleProduction = productionIndex + sinkVolume;
274
+ const capacityUsage = maxPossibleProduction > 0 ? productionIndex / maxPossibleProduction : 0;
275
+ const satisfactions = Object.values(state.agentSatisfaction ?? {});
276
+ const avgSatisfaction = satisfactions.length > 0 ? satisfactions.reduce((s, v) => s + v, 0) / satisfactions.length : 80;
277
+ const blockedAgentCount = satisfactions.filter((s) => s < 20).length;
278
+ const timeToValue = tick > 0 ? Math.max(0, 20 - tick * 0.1) : 20;
279
+ const poolSizes = { ...state.poolSizes ?? {} };
280
+ if (!this.anchorBaseline && tick === 1 && totalSupply > 0) {
281
+ this.anchorBaseline = {
282
+ goldPerHour: totalSupply / Math.max(1, totalAgents),
283
+ itemsPerGold: priceIndex > 0 ? 1 / priceIndex : 0
284
+ };
285
+ }
286
+ let anchorRatioDrift = 0;
287
+ if (this.anchorBaseline && totalAgents > 0) {
288
+ const currentGoldPerHour = totalSupply / totalAgents;
289
+ anchorRatioDrift = this.anchorBaseline.goldPerHour > 0 ? (currentGoldPerHour - this.anchorBaseline.goldPerHour) / this.anchorBaseline.goldPerHour : 0;
290
+ }
291
+ let arbitrageIndex = 0;
292
+ const priceKeys = Object.keys(prices).filter((k) => prices[k] > 0);
293
+ if (priceKeys.length >= 2) {
294
+ let pairCount = 0;
295
+ let totalDivergence = 0;
296
+ for (let i = 0; i < priceKeys.length; i++) {
297
+ for (let j = i + 1; j < priceKeys.length; j++) {
298
+ const pA = prices[priceKeys[i]];
299
+ const pB = prices[priceKeys[j]];
300
+ const ratio = pA / pB;
301
+ totalDivergence += Math.abs(Math.log(ratio));
302
+ pairCount++;
303
+ }
304
+ }
305
+ arbitrageIndex = pairCount > 0 ? Math.min(1, totalDivergence / pairCount) : 0;
306
+ }
307
+ const contentDropEvents = recentEvents.filter(
308
+ (e) => e.metadata?.["contentDrop"] === true
309
+ );
310
+ const contentDropAge = contentDropEvents.length > 0 ? tick - Math.max(...contentDropEvents.map((e) => e.timestamp)) : (this.previousMetrics?.contentDropAge ?? 0) + 1;
311
+ let giftTrades = 0;
312
+ if (tradeEvents.length > 0) {
313
+ for (const e of tradeEvents) {
314
+ const marketPrice = prices[e.resource ?? ""] ?? 0;
315
+ const tradePrice = e.price ?? 0;
316
+ if (tradePrice === 0 || marketPrice > 0 && tradePrice < marketPrice * 0.3) {
317
+ giftTrades++;
318
+ }
319
+ }
320
+ }
321
+ const giftTradeRatio = tradeEvents.length > 0 ? giftTrades / tradeEvents.length : 0;
322
+ let disposalTrades = 0;
323
+ if (tradeEvents.length > 0) {
324
+ for (const e of tradeEvents) {
325
+ if (e.from && e.resource) {
326
+ const sellerInv = state.agentInventories[e.from]?.[e.resource] ?? 0;
327
+ const avgInv = (supplyByResource[e.resource] ?? 0) / Math.max(1, totalAgents);
328
+ if (sellerInv > avgInv * 3) {
329
+ disposalTrades++;
330
+ }
331
+ }
332
+ }
333
+ }
334
+ const disposalTradeRatio = tradeEvents.length > 0 ? disposalTrades / tradeEvents.length : 0;
335
+ const custom = {};
336
+ for (const [name, fn] of Object.entries(this.customMetricFns)) {
337
+ try {
338
+ custom[name] = fn(state);
339
+ } catch {
340
+ custom[name] = NaN;
341
+ }
342
+ }
343
+ const metrics = {
344
+ tick,
345
+ timestamp: Date.now(),
346
+ totalSupply,
347
+ netFlow,
348
+ velocity,
349
+ inflationRate,
350
+ populationByRole,
351
+ roleShares,
352
+ totalAgents,
353
+ churnRate,
354
+ churnByRole,
355
+ personaDistribution: {},
356
+ // populated by PersonaTracker
357
+ giniCoefficient,
358
+ medianBalance,
359
+ meanBalance,
360
+ top10PctShare,
361
+ meanMedianDivergence,
362
+ priceIndex,
363
+ productionIndex,
364
+ capacityUsage,
365
+ prices,
366
+ priceVolatility,
367
+ supplyByResource,
368
+ demandSignals,
369
+ pinchPoints,
370
+ avgSatisfaction,
371
+ blockedAgentCount,
372
+ timeToValue,
373
+ faucetVolume,
374
+ sinkVolume,
375
+ tapSinkRatio,
376
+ poolSizes,
377
+ anchorRatioDrift,
378
+ extractionRatio: NaN,
379
+ newUserDependency: NaN,
380
+ smokeTestRatio: NaN,
381
+ currencyInsulation: NaN,
382
+ sharkToothPeaks: this.previousMetrics?.sharkToothPeaks ?? [],
383
+ sharkToothValleys: this.previousMetrics?.sharkToothValleys ?? [],
384
+ eventCompletionRate: NaN,
385
+ arbitrageIndex,
386
+ contentDropAge,
387
+ giftTradeRatio,
388
+ disposalTradeRatio,
389
+ custom
390
+ };
391
+ this.previousMetrics = metrics;
392
+ return metrics;
393
+ }
394
+ };
395
+ function computeMedian(sorted) {
396
+ if (sorted.length === 0) return 0;
397
+ const mid = Math.floor(sorted.length / 2);
398
+ return sorted.length % 2 === 0 ? ((sorted[mid - 1] ?? 0) + (sorted[mid] ?? 0)) / 2 : sorted[mid] ?? 0;
399
+ }
400
+ function computeGini(sorted) {
401
+ const n = sorted.length;
402
+ if (n === 0) return 0;
403
+ const sum = sorted.reduce((a, b) => a + b, 0);
404
+ if (sum === 0) return 0;
405
+ let numerator = 0;
406
+ for (let i = 0; i < n; i++) {
407
+ numerator += (2 * (i + 1) - n - 1) * (sorted[i] ?? 0);
408
+ }
409
+ return Math.abs(numerator) / (n * sum);
410
+ }
411
+
412
+ // src/Diagnoser.ts
413
+ var Diagnoser = class {
414
+ constructor(principles) {
415
+ this.principles = [];
416
+ this.principles = [...principles];
417
+ }
418
+ addPrinciple(principle) {
419
+ this.principles.push(principle);
420
+ }
421
+ removePrinciple(id) {
422
+ this.principles = this.principles.filter((p) => p.id !== id);
423
+ }
424
+ /**
425
+ * Run all principles against current metrics.
426
+ * Returns violations sorted by severity (highest first).
427
+ * Only one action is taken per cycle — the highest severity violation.
428
+ */
429
+ diagnose(metrics, thresholds) {
430
+ const diagnoses = [];
431
+ for (const principle of this.principles) {
432
+ try {
433
+ const result = principle.check(metrics, thresholds);
434
+ if (result.violated) {
435
+ diagnoses.push({
436
+ principle,
437
+ violation: result,
438
+ tick: metrics.tick
439
+ });
440
+ }
441
+ } catch (err) {
442
+ console.warn(`[AgentE] Principle ${principle.id} threw an error:`, err);
443
+ }
444
+ }
445
+ diagnoses.sort((a, b) => {
446
+ const severityDiff = b.violation.severity - a.violation.severity;
447
+ if (severityDiff !== 0) return severityDiff;
448
+ return b.violation.confidence - a.violation.confidence;
449
+ });
450
+ return diagnoses;
451
+ }
452
+ getPrinciples() {
453
+ return [...this.principles];
454
+ }
455
+ };
456
+
457
+ // src/types.ts
458
+ function emptyMetrics(tick = 0) {
459
+ return {
460
+ tick,
461
+ timestamp: Date.now(),
462
+ totalSupply: 0,
463
+ netFlow: 0,
464
+ velocity: 0,
465
+ inflationRate: 0,
466
+ populationByRole: {},
467
+ roleShares: {},
468
+ totalAgents: 0,
469
+ churnRate: 0,
470
+ churnByRole: {},
471
+ personaDistribution: {},
472
+ giniCoefficient: 0,
473
+ medianBalance: 0,
474
+ meanBalance: 0,
475
+ top10PctShare: 0,
476
+ meanMedianDivergence: 0,
477
+ priceIndex: 0,
478
+ productionIndex: 0,
479
+ capacityUsage: 0,
480
+ prices: {},
481
+ priceVolatility: {},
482
+ supplyByResource: {},
483
+ demandSignals: {},
484
+ pinchPoints: {},
485
+ avgSatisfaction: 100,
486
+ blockedAgentCount: 0,
487
+ timeToValue: 0,
488
+ faucetVolume: 0,
489
+ sinkVolume: 0,
490
+ tapSinkRatio: 1,
491
+ poolSizes: {},
492
+ anchorRatioDrift: 0,
493
+ extractionRatio: NaN,
494
+ newUserDependency: NaN,
495
+ smokeTestRatio: NaN,
496
+ currencyInsulation: NaN,
497
+ sharkToothPeaks: [],
498
+ sharkToothValleys: [],
499
+ eventCompletionRate: NaN,
500
+ arbitrageIndex: 0,
501
+ contentDropAge: 0,
502
+ giftTradeRatio: 0,
503
+ disposalTradeRatio: 0,
504
+ custom: {}
505
+ };
506
+ }
507
+
508
+ // src/principles/supply-chain.ts
509
+ var P1_ProductionMatchesConsumption = {
510
+ id: "P1",
511
+ name: "Production Must Match Consumption",
512
+ category: "supply_chain",
513
+ description: "If producer rate < consumer rate, supply deficit kills the economy. 105 ore rotting at Forge (V0.4.6) happened because this was out of balance.",
514
+ check(metrics, _thresholds) {
515
+ const { supplyByResource, demandSignals, populationByRole } = metrics;
516
+ const violations = [];
517
+ for (const resource of Object.keys(demandSignals)) {
518
+ const demand = demandSignals[resource] ?? 0;
519
+ const supply = supplyByResource[resource] ?? 0;
520
+ if (demand > 5 && supply / Math.max(1, demand) < 0.5) {
521
+ violations.push(resource);
522
+ }
523
+ }
524
+ const crafters = (populationByRole["Crafter"] ?? 0) + (populationByRole["Alchemist"] ?? 0);
525
+ const consumers = populationByRole["Fighter"] ?? 0;
526
+ const productionDeficit = consumers > 0 && crafters / consumers < 0.1;
527
+ if (violations.length > 0 || productionDeficit) {
528
+ return {
529
+ violated: true,
530
+ severity: 7,
531
+ evidence: { scarceResources: violations, crafters, consumers },
532
+ suggestedAction: {
533
+ parameter: "craftingCost",
534
+ direction: "decrease",
535
+ magnitude: 0.15,
536
+ reasoning: "Lower crafting cost to incentivise more production."
537
+ },
538
+ confidence: violations.length > 0 ? 0.85 : 0.6,
539
+ estimatedLag: 10
540
+ };
541
+ }
542
+ return { violated: false };
543
+ }
544
+ };
545
+ var P2_ClosedLoopsNeedDirectHandoff = {
546
+ id: "P2",
547
+ name: "Closed Loops Need Direct Handoff",
548
+ category: "supply_chain",
549
+ description: "Raw materials listed on an open market create noise and liquidity problems. Gatherers delivering ore directly to Crafters at the Forge is faster and cleaner.",
550
+ check(metrics, _thresholds) {
551
+ const { supplyByResource, prices, velocity } = metrics;
552
+ const ore = supplyByResource["ore"] ?? 0;
553
+ const wood = supplyByResource["wood"] ?? 0;
554
+ const orePrice = prices["ore"] ?? 0;
555
+ const woodPrice = prices["wood"] ?? 0;
556
+ const oreBacklog = ore > 50 && orePrice > 0;
557
+ const woodBacklog = wood > 50 && woodPrice > 0;
558
+ const stagnant = velocity < 3;
559
+ if ((oreBacklog || woodBacklog) && stagnant) {
560
+ return {
561
+ violated: true,
562
+ severity: 5,
563
+ evidence: { ore, wood, velocity },
564
+ suggestedAction: {
565
+ parameter: "auctionFee",
566
+ direction: "increase",
567
+ magnitude: 0.2,
568
+ reasoning: "Raise AH fees to discourage raw material listings. Direct hand-off at production zones is the correct channel."
569
+ },
570
+ confidence: 0.7,
571
+ estimatedLag: 5
572
+ };
573
+ }
574
+ return { violated: false };
575
+ }
576
+ };
577
+ var P3_BootstrapCapitalCoversFirstTransaction = {
578
+ id: "P3",
579
+ name: "Bootstrap Capital Covers First Transaction",
580
+ category: "supply_chain",
581
+ description: "A new producer must be able to afford their first transaction without selling anything first. Crafter starting with 15g but needing 30g to accept ore hand-off blocks the entire supply chain from tick 1.",
582
+ check(metrics, _thresholds) {
583
+ const { populationByRole, supplyByResource, prices } = metrics;
584
+ const crafters = populationByRole["Crafter"] ?? 0;
585
+ const alchemists = populationByRole["Alchemist"] ?? 0;
586
+ const weapons = supplyByResource["weapons"] ?? 0;
587
+ const potions = supplyByResource["potions"] ?? 0;
588
+ const crafterBootstrapFail = crafters > 0 && weapons === 0 && (prices["ore"] ?? 0) > 0;
589
+ const alchemistBootstrapFail = alchemists > 0 && potions === 0 && (prices["wood"] ?? 0) > 0;
590
+ if (crafterBootstrapFail || alchemistBootstrapFail) {
591
+ return {
592
+ violated: true,
593
+ severity: 8,
594
+ evidence: { crafters, alchemists, weapons, potions },
595
+ suggestedAction: {
596
+ parameter: "craftingCost",
597
+ direction: "decrease",
598
+ magnitude: 0.3,
599
+ reasoning: "Producers cannot complete first transaction. Lower production cost to unblock bootstrap."
600
+ },
601
+ confidence: 0.8,
602
+ estimatedLag: 3
603
+ };
604
+ }
605
+ return { violated: false };
606
+ }
607
+ };
608
+ var P4_MaterialsFlowFasterThanCooldown = {
609
+ id: "P4",
610
+ name: "Materials Flow Faster Than Cooldown",
611
+ category: "supply_chain",
612
+ description: "Input delivery rate must exceed or match production cooldown rate. If Crafters craft every 5 ticks but only receive ore every 10 ticks, they starve regardless of supply levels.",
613
+ check(metrics, _thresholds) {
614
+ const { supplyByResource, populationByRole, velocity } = metrics;
615
+ const gatherers = populationByRole["Gatherer"] ?? 0;
616
+ const crafters = populationByRole["Crafter"] ?? 0;
617
+ const alchemists = populationByRole["Alchemist"] ?? 0;
618
+ const producers = crafters + alchemists;
619
+ const gathererToProcuderRatio = gatherers / Math.max(1, producers);
620
+ if (producers > 0 && gathererToProcuderRatio < 0.5 && velocity < 5) {
621
+ return {
622
+ violated: true,
623
+ severity: 5,
624
+ evidence: { gatherers, crafters, alchemists, gathererToProcuderRatio },
625
+ suggestedAction: {
626
+ parameter: "miningYield",
627
+ direction: "increase",
628
+ magnitude: 0.15,
629
+ reasoning: "Too few gatherers relative to producers. Increase yield to compensate."
630
+ },
631
+ confidence: 0.65,
632
+ estimatedLag: 8
633
+ };
634
+ }
635
+ const ore = supplyByResource["ore"] ?? 0;
636
+ const wood = supplyByResource["wood"] ?? 0;
637
+ if (ore > 80 || wood > 80) {
638
+ return {
639
+ violated: true,
640
+ severity: 4,
641
+ evidence: { ore, wood, gatherers, producers },
642
+ suggestedAction: {
643
+ parameter: "miningYield",
644
+ direction: "decrease",
645
+ magnitude: 0.2,
646
+ reasoning: "Raw materials piling up. Gatherers outpacing producers."
647
+ },
648
+ confidence: 0.8,
649
+ estimatedLag: 5
650
+ };
651
+ }
652
+ return { violated: false };
653
+ }
654
+ };
655
+ var P60_SurplusDisposalAsymmetry = {
656
+ id: "P60",
657
+ name: "Surplus Disposal Asymmetry",
658
+ category: "supply_chain",
659
+ description: "Most trades liquidate unwanted surplus, not deliberate production. Price signals from disposal trades are weaker demand indicators than production-for-sale trades \u2014 weight them accordingly.",
660
+ check(metrics, thresholds) {
661
+ const { disposalTradeRatio } = metrics;
662
+ if (disposalTradeRatio > 0.6) {
663
+ return {
664
+ violated: true,
665
+ severity: 5,
666
+ evidence: {
667
+ disposalTradeRatio,
668
+ discount: thresholds.disposalTradeWeightDiscount
669
+ },
670
+ suggestedAction: {
671
+ parameter: "craftingCost",
672
+ direction: "decrease",
673
+ magnitude: 0.1,
674
+ reasoning: `${(disposalTradeRatio * 100).toFixed(0)}% of trades are surplus disposal. Price signals unreliable as demand indicators. Lower production costs to shift balance toward deliberate production-for-sale. ADVISORY: Weight disposal-trade prices at ${thresholds.disposalTradeWeightDiscount}\xD7 in index calculations.`
675
+ },
676
+ confidence: 0.65,
677
+ estimatedLag: 15
678
+ };
679
+ }
680
+ return { violated: false };
681
+ }
682
+ };
683
+ var SUPPLY_CHAIN_PRINCIPLES = [
684
+ P1_ProductionMatchesConsumption,
685
+ P2_ClosedLoopsNeedDirectHandoff,
686
+ P3_BootstrapCapitalCoversFirstTransaction,
687
+ P4_MaterialsFlowFasterThanCooldown,
688
+ P60_SurplusDisposalAsymmetry
689
+ ];
690
+
691
+ // src/principles/incentives.ts
692
+ var P5_ProfitabilityIsCompetitive = {
693
+ id: "P5",
694
+ name: "Profitability Is Competitive, Not Absolute",
695
+ category: "incentive",
696
+ description: "Any profitability formula that returns the same number regardless of how many agents are already in that role will cause stampedes. 97 Traders happened because profit = transactions \xD7 10 with no competition denominator.",
697
+ check(metrics, thresholds) {
698
+ const { roleShares, populationByRole } = metrics;
699
+ const highShareRoles = [];
700
+ for (const [role, share] of Object.entries(roleShares)) {
701
+ if (share > 0.45) highShareRoles.push(role);
702
+ }
703
+ if (highShareRoles.length > 0) {
704
+ const dominantRole = highShareRoles[0];
705
+ return {
706
+ violated: true,
707
+ severity: 6,
708
+ evidence: {
709
+ dominantRole,
710
+ share: roleShares[dominantRole],
711
+ population: populationByRole[dominantRole]
712
+ },
713
+ suggestedAction: {
714
+ parameter: "auctionFee",
715
+ direction: "increase",
716
+ magnitude: thresholds.maxAdjustmentPercent,
717
+ reasoning: `${dominantRole} share at ${((roleShares[dominantRole] ?? 0) * 100).toFixed(0)}%. Likely stampede from non-competitive profitability formula. Raise market friction to slow role accumulation.`
718
+ },
719
+ confidence: 0.75,
720
+ estimatedLag: 15
721
+ };
722
+ }
723
+ return { violated: false };
724
+ }
725
+ };
726
+ var P6_CrowdingMultiplierOnAllRoles = {
727
+ id: "P6",
728
+ name: "Crowding Multiplier Applies to ALL Roles",
729
+ category: "incentive",
730
+ description: "Every role needs an inverse-population profitability scaling. A role without crowding pressure is a stampede waiting to happen.",
731
+ check(metrics, _thresholds) {
732
+ const { roleShares } = metrics;
733
+ for (const [role, share] of Object.entries(roleShares)) {
734
+ if (share > 0.35) {
735
+ return {
736
+ violated: true,
737
+ severity: 5,
738
+ evidence: { role, share },
739
+ suggestedAction: {
740
+ parameter: "craftingCost",
741
+ direction: "increase",
742
+ magnitude: 0.1,
743
+ reasoning: `${role} at ${(share * 100).toFixed(0)}% \u2014 no crowding pressure detected. Apply role-specific cost increase to simulate saturation.`
744
+ },
745
+ confidence: 0.7,
746
+ estimatedLag: 10
747
+ };
748
+ }
749
+ }
750
+ return { violated: false };
751
+ }
752
+ };
753
+ var P7_NonSpecialistsSubsidiseSpecialists = {
754
+ id: "P7",
755
+ name: "Non-Specialists Subsidise Specialists in Zero-Sum Games",
756
+ category: "incentive",
757
+ description: "In zero-sum pools (arena, staking), the math only works if non-specialists overpay relative to specialists. If the pool is >70% specialists, there is no one left to subsidise and the pot drains.",
758
+ check(metrics, _thresholds) {
759
+ const { populationByRole, poolSizes } = metrics;
760
+ const arenaPot = poolSizes["arena"] ?? poolSizes["arenaPot"] ?? 0;
761
+ if (arenaPot <= 0) return { violated: false };
762
+ const fighters = populationByRole["Fighter"] ?? 0;
763
+ const total = metrics.totalAgents;
764
+ const fighterShare = fighters / Math.max(1, total);
765
+ if (fighterShare > 0.7 && arenaPot < 100) {
766
+ return {
767
+ violated: true,
768
+ severity: 6,
769
+ evidence: { fighterShare, arenaPot },
770
+ suggestedAction: {
771
+ parameter: "arenaEntryFee",
772
+ direction: "decrease",
773
+ magnitude: 0.1,
774
+ reasoning: "Arena pot draining \u2014 too many specialists, not enough subsidising non-specialists. Lower entry fee to attract diverse participants."
775
+ },
776
+ confidence: 0.75,
777
+ estimatedLag: 5
778
+ };
779
+ }
780
+ return { violated: false };
781
+ }
782
+ };
783
+ var P8_RegulatorCannotFightDesign = {
784
+ id: "P8",
785
+ name: "Regulator Cannot Fight the Design",
786
+ category: "incentive",
787
+ description: "If the economy is designed to have a majority role (e.g. 55% Fighters), the regulator must know this and exempt that role from population suppression. AgentE at tick 1 seeing 55% Fighters and slashing arena rewards is overreach.",
788
+ check(metrics, _thresholds) {
789
+ const { roleShares, avgSatisfaction } = metrics;
790
+ if (avgSatisfaction < 45) {
791
+ const dominantRole = Object.entries(roleShares).sort((a, b) => b[1] - a[1])[0];
792
+ if (dominantRole && dominantRole[1] > 0.3) {
793
+ return {
794
+ violated: true,
795
+ severity: 4,
796
+ evidence: { dominantRole: dominantRole[0], share: dominantRole[1], avgSatisfaction },
797
+ suggestedAction: {
798
+ parameter: "arenaReward",
799
+ direction: "increase",
800
+ magnitude: 0.1,
801
+ reasoning: `Low satisfaction with ${dominantRole[0]} dominant. Regulator may be suppressing a structurally necessary role. Ease pressure on dominant role rewards.`
802
+ },
803
+ confidence: 0.55,
804
+ estimatedLag: 8
805
+ };
806
+ }
807
+ }
808
+ return { violated: false };
809
+ }
810
+ };
811
+ var INCENTIVE_PRINCIPLES = [
812
+ P5_ProfitabilityIsCompetitive,
813
+ P6_CrowdingMultiplierOnAllRoles,
814
+ P7_NonSpecialistsSubsidiseSpecialists,
815
+ P8_RegulatorCannotFightDesign
816
+ ];
817
+
818
+ // src/principles/population.ts
819
+ var P9_RoleSwitchingNeedsFriction = {
820
+ id: "P9",
821
+ name: "Role Switching Needs Friction",
822
+ category: "population",
823
+ description: "If >5% of the population switches roles in a single evaluation period, it is a herd movement, not rational rebalancing. Without friction (satisfaction cost, cooldown), one good tick causes mass migration.",
824
+ check(metrics, thresholds) {
825
+ const { churnByRole, roleShares } = metrics;
826
+ const totalChurn = Object.values(churnByRole).reduce((s, v) => s + v, 0);
827
+ if (totalChurn > thresholds.roleSwitchFrictionMax) {
828
+ return {
829
+ violated: true,
830
+ severity: 5,
831
+ evidence: { totalChurnRate: totalChurn, churnByRole },
832
+ suggestedAction: {
833
+ parameter: "craftingCost",
834
+ direction: "increase",
835
+ magnitude: 0.05,
836
+ reasoning: `Role switch rate ${(totalChurn * 100).toFixed(1)}% exceeds friction threshold. Increase production costs to slow herd movement.`
837
+ },
838
+ confidence: 0.65,
839
+ estimatedLag: 20
840
+ };
841
+ }
842
+ void roleShares;
843
+ return { violated: false };
844
+ }
845
+ };
846
+ var P10_SpawnWeightingUsesInversePopulation = {
847
+ id: "P10",
848
+ name: "Spawn Weighting Uses Inverse Population",
849
+ category: "population",
850
+ description: "New agents should preferentially fill the least-populated roles. Flat spawn probability causes initial imbalances to compound.",
851
+ check(metrics, _thresholds) {
852
+ const { roleShares } = metrics;
853
+ if (Object.keys(roleShares).length === 0) return { violated: false };
854
+ const shares = Object.values(roleShares);
855
+ const mean = shares.reduce((s, v) => s + v, 0) / shares.length;
856
+ const variance = shares.reduce((s, v) => s + (v - mean) ** 2, 0) / shares.length;
857
+ const stdDev = Math.sqrt(variance);
858
+ if (stdDev > 0.2) {
859
+ const minRole = Object.entries(roleShares).sort((a, b) => a[1] - b[1])[0];
860
+ return {
861
+ violated: true,
862
+ severity: 4,
863
+ evidence: { roleShares, stdDev, leastPopulatedRole: minRole?.[0] },
864
+ suggestedAction: {
865
+ parameter: "miningYield",
866
+ direction: "increase",
867
+ magnitude: 0.05,
868
+ reasoning: `High role share variance (\u03C3=${stdDev.toFixed(2)}). Spawn weighting may not be filling under-populated roles. Increasing yield makes under-populated producer roles more attractive.`
869
+ },
870
+ confidence: 0.6,
871
+ estimatedLag: 20
872
+ };
873
+ }
874
+ return { violated: false };
875
+ }
876
+ };
877
+ var P11_TwoTierPressure = {
878
+ id: "P11",
879
+ name: "Two-Tier Pressure (Continuous + Hard)",
880
+ category: "population",
881
+ description: "Corrections only at thresholds create delayed response. Continuous gentle pressure (1% per tick toward ideal) plus hard cuts for extreme cases catches imbalances early.",
882
+ check(metrics, _thresholds) {
883
+ const { roleShares } = metrics;
884
+ for (const [role, share] of Object.entries(roleShares)) {
885
+ if (share > 0.45) {
886
+ return {
887
+ violated: true,
888
+ severity: 6,
889
+ evidence: { role, share },
890
+ suggestedAction: {
891
+ parameter: "auctionFee",
892
+ direction: "increase",
893
+ magnitude: 0.15,
894
+ reasoning: `${role} at ${(share * 100).toFixed(0)}% \u2014 continuous pressure was insufficient. Hard intervention needed alongside resumed continuous pressure.`
895
+ },
896
+ confidence: 0.8,
897
+ estimatedLag: 10
898
+ };
899
+ }
900
+ }
901
+ return { violated: false };
902
+ }
903
+ };
904
+ var P46_PersonaDiversity = {
905
+ id: "P46",
906
+ name: "Persona Diversity",
907
+ category: "population",
908
+ description: "Any single behavioral persona above 40% = monoculture. Need at least 3 distinct persona clusters each above 15%.",
909
+ check(metrics, thresholds) {
910
+ const { personaDistribution } = metrics;
911
+ if (Object.keys(personaDistribution).length === 0) return { violated: false };
912
+ for (const [persona, share] of Object.entries(personaDistribution)) {
913
+ if (share > thresholds.personaMonocultureMax) {
914
+ return {
915
+ violated: true,
916
+ severity: 5,
917
+ evidence: { dominantPersona: persona, share, personaDistribution },
918
+ suggestedAction: {
919
+ parameter: "arenaReward",
920
+ direction: "increase",
921
+ magnitude: 0.1,
922
+ reasoning: `${persona} persona at ${(share * 100).toFixed(0)}% \u2014 behavioral monoculture. Diversify reward structures to attract other persona types.`
923
+ },
924
+ confidence: 0.7,
925
+ estimatedLag: 30
926
+ };
927
+ }
928
+ }
929
+ const significantClusters = Object.values(personaDistribution).filter((s) => s >= 0.15).length;
930
+ if (significantClusters < thresholds.personaMinClusters) {
931
+ return {
932
+ violated: true,
933
+ severity: 3,
934
+ evidence: { significantClusters, required: thresholds.personaMinClusters },
935
+ suggestedAction: {
936
+ parameter: "auctionFee",
937
+ direction: "decrease",
938
+ magnitude: 0.05,
939
+ reasoning: `Only ${significantClusters} significant persona clusters (need ${thresholds.personaMinClusters}). Lower trade barriers to attract non-dominant persona types.`
940
+ },
941
+ confidence: 0.55,
942
+ estimatedLag: 40
943
+ };
944
+ }
945
+ return { violated: false };
946
+ }
947
+ };
948
+ var POPULATION_PRINCIPLES = [
949
+ P9_RoleSwitchingNeedsFriction,
950
+ P10_SpawnWeightingUsesInversePopulation,
951
+ P11_TwoTierPressure,
952
+ P46_PersonaDiversity
953
+ ];
954
+
955
+ // src/principles/currency-flow.ts
956
+ var P12_OnePrimaryFaucet = {
957
+ id: "P12",
958
+ name: "One Primary Faucet",
959
+ category: "currency",
960
+ description: "Multiple independent currency sources (gathering + crafting + quests) each creating gold causes uncontrolled inflation. One clear primary faucet makes the economy predictable and auditable.",
961
+ check(metrics, thresholds) {
962
+ const { netFlow, faucetVolume, sinkVolume } = metrics;
963
+ if (netFlow > thresholds.netFlowWarnThreshold) {
964
+ return {
965
+ violated: true,
966
+ severity: 5,
967
+ evidence: { netFlow, faucetVolume, sinkVolume },
968
+ suggestedAction: {
969
+ parameter: "craftingCost",
970
+ direction: "increase",
971
+ magnitude: 0.15,
972
+ reasoning: `Net flow +${netFlow.toFixed(1)} g/t. Inflationary. Increase crafting cost (primary sink) to balance faucet output.`
973
+ },
974
+ confidence: 0.8,
975
+ estimatedLag: 8
976
+ };
977
+ }
978
+ if (netFlow < -thresholds.netFlowWarnThreshold) {
979
+ return {
980
+ violated: true,
981
+ severity: 4,
982
+ evidence: { netFlow, faucetVolume, sinkVolume },
983
+ suggestedAction: {
984
+ parameter: "craftingCost",
985
+ direction: "decrease",
986
+ magnitude: 0.15,
987
+ reasoning: `Net flow ${netFlow.toFixed(1)} g/t. Deflationary. Decrease crafting cost to ease sink pressure.`
988
+ },
989
+ confidence: 0.8,
990
+ estimatedLag: 8
991
+ };
992
+ }
993
+ return { violated: false };
994
+ }
995
+ };
996
+ var P13_PotsAreZeroSumAndSelfRegulate = {
997
+ id: "P13",
998
+ name: "Pots Self-Regulate with Correct Multiplier",
999
+ category: "currency",
1000
+ description: "Arena pot math: winRate \xD7 multiplier > (1 - houseCut) drains the pot. At 65% win rate, multiplier must be \u2264 1.38. We use 1.5 for slight surplus buffer.",
1001
+ check(metrics, thresholds) {
1002
+ const { poolSizes } = metrics;
1003
+ const arenaPot = poolSizes["arena"] ?? poolSizes["arenaPot"] ?? 0;
1004
+ const fighters = metrics.populationByRole["Fighter"] ?? 0;
1005
+ if (fighters > 5 && arenaPot < 50) {
1006
+ const { arenaWinRate, arenaHouseCut } = thresholds;
1007
+ const maxSustainableMultiplier = (1 - arenaHouseCut) / arenaWinRate;
1008
+ return {
1009
+ violated: true,
1010
+ severity: 7,
1011
+ evidence: { arenaPot, fighters, maxSustainableMultiplier },
1012
+ suggestedAction: {
1013
+ parameter: "arenaReward",
1014
+ direction: "decrease",
1015
+ magnitude: 0.15,
1016
+ reasoning: `Arena pot at ${arenaPot.toFixed(0)}g with ${fighters} fighters. Sustainable multiplier \u2264 ${maxSustainableMultiplier.toFixed(2)}. Reduce reward multiplier to prevent pot drain.`
1017
+ },
1018
+ confidence: 0.85,
1019
+ estimatedLag: 3
1020
+ };
1021
+ }
1022
+ return { violated: false };
1023
+ }
1024
+ };
1025
+ var P14_TrackActualInjection = {
1026
+ id: "P14",
1027
+ name: "Track Actual Gold Injection, Not Value Creation",
1028
+ category: "currency",
1029
+ description: 'Counting resource gathering as "gold injected" is a lie. Gold only enters when Fighters spawn (100-150g each). Fake metrics break every downstream decision.',
1030
+ check(metrics, _thresholds) {
1031
+ const { faucetVolume, netFlow, totalSupply } = metrics;
1032
+ const supplyGrowthRate = Math.abs(netFlow) / Math.max(1, totalSupply);
1033
+ if (supplyGrowthRate > 0.1) {
1034
+ return {
1035
+ violated: true,
1036
+ severity: 4,
1037
+ evidence: { faucetVolume, netFlow, supplyGrowthRate },
1038
+ suggestedAction: {
1039
+ parameter: "miningYield",
1040
+ direction: "decrease",
1041
+ magnitude: 0.1,
1042
+ reasoning: `Supply growing at ${(supplyGrowthRate * 100).toFixed(1)}%/tick. Verify gold injection tracking. Resources should not create gold directly.`
1043
+ },
1044
+ confidence: 0.55,
1045
+ estimatedLag: 5
1046
+ };
1047
+ }
1048
+ return { violated: false };
1049
+ }
1050
+ };
1051
+ var P15_PoolsNeedCapAndDecay = {
1052
+ id: "P15",
1053
+ name: "Pools Need Cap + Decay",
1054
+ category: "currency",
1055
+ description: "Any pool (bank, reward pool) without a cap accumulates infinitely. Bank pool at 42% of gold supply means 42% of the economy is frozen. Cap at 5%, decay at 2%/tick.",
1056
+ check(metrics, thresholds) {
1057
+ const { poolSizes, totalSupply } = metrics;
1058
+ const { poolCapPercent } = thresholds;
1059
+ for (const [pool, size] of Object.entries(poolSizes)) {
1060
+ if (pool === "arena" || pool === "arenaPot") continue;
1061
+ const shareOfSupply = size / Math.max(1, totalSupply);
1062
+ if (shareOfSupply > poolCapPercent * 2) {
1063
+ return {
1064
+ violated: true,
1065
+ severity: 6,
1066
+ evidence: { pool, size, shareOfSupply, cap: poolCapPercent },
1067
+ suggestedAction: {
1068
+ parameter: "auctionFee",
1069
+ direction: "decrease",
1070
+ magnitude: 0.1,
1071
+ reasoning: `${pool} pool at ${(shareOfSupply * 100).toFixed(1)}% of supply (cap: ${(poolCapPercent * 100).toFixed(0)}%). Gold frozen. Lower fees to encourage circulation over accumulation.`
1072
+ },
1073
+ confidence: 0.85,
1074
+ estimatedLag: 5
1075
+ };
1076
+ }
1077
+ }
1078
+ return { violated: false };
1079
+ }
1080
+ };
1081
+ var P16_WithdrawalPenaltyScales = {
1082
+ id: "P16",
1083
+ name: "Withdrawal Penalty Scales with Lock Duration",
1084
+ category: "currency",
1085
+ description: "A 50-tick lock period with a penalty calculated as /100 means agents can exit after 1 tick and keep 99% of accrued yield. Penalty must scale linearly: (1 - ticksStaked/lockDuration) \xD7 yield.",
1086
+ check(metrics, _thresholds) {
1087
+ const { poolSizes, totalSupply } = metrics;
1088
+ const bankPool = poolSizes["bank"] ?? poolSizes["bankPool"] ?? 0;
1089
+ const stakedEstimate = totalSupply * 0.15;
1090
+ if (bankPool < 10 && stakedEstimate > 100) {
1091
+ return {
1092
+ violated: true,
1093
+ severity: 3,
1094
+ evidence: { bankPool, estimatedStaked: stakedEstimate },
1095
+ suggestedAction: {
1096
+ parameter: "auctionFee",
1097
+ direction: "increase",
1098
+ magnitude: 0.05,
1099
+ reasoning: "Bank pool depleted while significant gold should be staked. Early withdrawals may be draining yield pool. Ensure withdrawal penalty scales with lock duration."
1100
+ },
1101
+ confidence: 0.45,
1102
+ estimatedLag: 10
1103
+ };
1104
+ }
1105
+ return { violated: false };
1106
+ }
1107
+ };
1108
+ var P32_VelocityAboveSupply = {
1109
+ id: "P32",
1110
+ name: "Velocity > Supply for Liquidity",
1111
+ category: "currency",
1112
+ description: "Low transactions despite adequate supply means liquidity is trapped. High supply with low velocity = stagnation, not abundance.",
1113
+ check(metrics, _thresholds) {
1114
+ const { velocity, totalSupply, supplyByResource } = metrics;
1115
+ const totalResources = Object.values(supplyByResource).reduce((s, v) => s + v, 0);
1116
+ if (velocity < 3 && totalSupply > 100 && totalResources > 20) {
1117
+ return {
1118
+ violated: true,
1119
+ severity: 4,
1120
+ evidence: { velocity, totalSupply, totalResources },
1121
+ suggestedAction: {
1122
+ parameter: "auctionFee",
1123
+ direction: "decrease",
1124
+ magnitude: 0.2,
1125
+ reasoning: `Velocity ${velocity}/t with ${totalResources} resources in system. Economy stagnant despite available supply. Lower trading friction.`
1126
+ },
1127
+ confidence: 0.75,
1128
+ estimatedLag: 5
1129
+ };
1130
+ }
1131
+ return { violated: false };
1132
+ }
1133
+ };
1134
+ var P58_NoNaturalNumeraire = {
1135
+ id: "P58",
1136
+ name: "No Natural Num\xE9raire",
1137
+ category: "currency",
1138
+ description: "No single commodity naturally stabilizes as currency in barter-heavy economies. Multiple items rotate as de facto units of account, but none locks in. If a num\xE9raire is needed, design and enforce it \u2014 emergence alone will not produce one.",
1139
+ check(metrics, _thresholds) {
1140
+ const { prices, velocity, totalSupply } = metrics;
1141
+ const priceValues = Object.values(prices).filter((p) => p > 0);
1142
+ if (priceValues.length < 3) return { violated: false };
1143
+ const mean = priceValues.reduce((s, p) => s + p, 0) / priceValues.length;
1144
+ const coeffOfVariation = mean > 0 ? Math.sqrt(
1145
+ priceValues.reduce((s, p) => s + (p - mean) ** 2, 0) / priceValues.length
1146
+ ) / mean : 0;
1147
+ if (coeffOfVariation < 0.25 && velocity > 5 && totalSupply > 100) {
1148
+ return {
1149
+ violated: true,
1150
+ severity: 3,
1151
+ evidence: {
1152
+ coeffOfVariation,
1153
+ velocity,
1154
+ numResources: priceValues.length,
1155
+ meanPrice: mean
1156
+ },
1157
+ suggestedAction: {
1158
+ parameter: "craftingCost",
1159
+ direction: "increase",
1160
+ magnitude: 0.1,
1161
+ reasoning: `Price coefficient of variation ${coeffOfVariation.toFixed(2)} with velocity ${velocity.toFixed(1)}. All items priced similarly in an active economy \u2014 no natural num\xE9raire emerging. If a designated currency exists, increase its sink demand to differentiate it.`
1162
+ },
1163
+ confidence: 0.5,
1164
+ estimatedLag: 20
1165
+ };
1166
+ }
1167
+ return { violated: false };
1168
+ }
1169
+ };
1170
+ var CURRENCY_FLOW_PRINCIPLES = [
1171
+ P12_OnePrimaryFaucet,
1172
+ P13_PotsAreZeroSumAndSelfRegulate,
1173
+ P14_TrackActualInjection,
1174
+ P15_PoolsNeedCapAndDecay,
1175
+ P16_WithdrawalPenaltyScales,
1176
+ P32_VelocityAboveSupply,
1177
+ P58_NoNaturalNumeraire
1178
+ ];
1179
+
1180
+ // src/principles/bootstrap.ts
1181
+ var P17_GracePeriodBeforeIntervention = {
1182
+ id: "P17",
1183
+ name: "Grace Period Before Intervention",
1184
+ category: "bootstrap",
1185
+ description: "Any intervention before tick 50 is premature. The economy needs time to bootstrap with designed distributions. AgentE intervening at tick 1 against 55% Fighters (designed) killed the economy instantly.",
1186
+ check(metrics, _thresholds) {
1187
+ if (metrics.tick < 30 && metrics.avgSatisfaction < 40) {
1188
+ return {
1189
+ violated: true,
1190
+ severity: 7,
1191
+ evidence: { tick: metrics.tick, avgSatisfaction: metrics.avgSatisfaction },
1192
+ suggestedAction: {
1193
+ parameter: "arenaEntryFee",
1194
+ direction: "decrease",
1195
+ magnitude: 0.2,
1196
+ reasoning: `Very low satisfaction at tick ${metrics.tick}. Intervention may have fired during grace period. Ease all costs to let economy bootstrap.`
1197
+ },
1198
+ confidence: 0.7,
1199
+ estimatedLag: 10
1200
+ };
1201
+ }
1202
+ return { violated: false };
1203
+ }
1204
+ };
1205
+ var P18_FirstProducerNeedsStartingInventory = {
1206
+ id: "P18",
1207
+ name: "First Producer Needs Starting Inventory + Capital",
1208
+ category: "bootstrap",
1209
+ description: "A Crafter with 0 weapons and 0 gold must sell nothing to get gold before they can buy ore. This creates a chicken-and-egg freeze. Starting inventory (2 weapons + 4 ore + 40g) breaks the deadlock.",
1210
+ check(metrics, _thresholds) {
1211
+ if (metrics.tick > 20) return { violated: false };
1212
+ const weapons = metrics.supplyByResource["weapons"] ?? 0;
1213
+ const potions = metrics.supplyByResource["potions"] ?? 0;
1214
+ const crafters = metrics.populationByRole["Crafter"] ?? 0;
1215
+ const alchemists = metrics.populationByRole["Alchemist"] ?? 0;
1216
+ if (crafters > 0 && weapons === 0 || alchemists > 0 && potions === 0) {
1217
+ return {
1218
+ violated: true,
1219
+ severity: 8,
1220
+ evidence: { tick: metrics.tick, weapons, potions, crafters, alchemists },
1221
+ suggestedAction: {
1222
+ parameter: "craftingCost",
1223
+ direction: "decrease",
1224
+ magnitude: 0.5,
1225
+ reasoning: "Bootstrap failure: producers have no products on tick 1-20. Drastically reduce production cost to allow immediate output."
1226
+ },
1227
+ confidence: 0.9,
1228
+ estimatedLag: 2
1229
+ };
1230
+ }
1231
+ return { violated: false };
1232
+ }
1233
+ };
1234
+ var P19_StartingSupplyExceedsDemand = {
1235
+ id: "P19",
1236
+ name: "Starting Supply Exceeds Initial Demand",
1237
+ category: "bootstrap",
1238
+ description: "Launch with more consumables than you think you need. Early scarcity creates an AH gridlock where everyone wants to buy and nobody has anything to sell.",
1239
+ check(metrics, _thresholds) {
1240
+ if (metrics.tick > 30) return { violated: false };
1241
+ const fighters = metrics.populationByRole["Fighter"] ?? 0;
1242
+ const weapons = metrics.supplyByResource["weapons"] ?? 0;
1243
+ const potions = metrics.supplyByResource["potions"] ?? 0;
1244
+ if (fighters > 5 && weapons < fighters * 0.5) {
1245
+ return {
1246
+ violated: true,
1247
+ severity: 6,
1248
+ evidence: { fighters, weapons, potions, weaponsPerFighter: weapons / Math.max(1, fighters) },
1249
+ suggestedAction: {
1250
+ parameter: "arenaReward",
1251
+ direction: "increase",
1252
+ magnitude: 0.2,
1253
+ reasoning: `${fighters} fighters but only ${weapons} weapons. Cold-start scarcity. Boost arena reward to attract fighters even without weapons.`
1254
+ },
1255
+ confidence: 0.75,
1256
+ estimatedLag: 5
1257
+ };
1258
+ }
1259
+ return { violated: false };
1260
+ }
1261
+ };
1262
+ var BOOTSTRAP_PRINCIPLES = [
1263
+ P17_GracePeriodBeforeIntervention,
1264
+ P18_FirstProducerNeedsStartingInventory,
1265
+ P19_StartingSupplyExceedsDemand
1266
+ ];
1267
+
1268
+ // src/principles/feedback-loops.ts
1269
+ var P20_DecayPreventsAccumulation = {
1270
+ id: "P20",
1271
+ name: "Decay Prevents Accumulation",
1272
+ category: "feedback",
1273
+ description: "Resources without decay create infinite hoarding. A Gatherer who never sells has 500 ore rotting in their pocket while Crafters starve. 2-10% decay per period forces circulation.",
1274
+ check(metrics, _thresholds) {
1275
+ const { supplyByResource, velocity, totalAgents } = metrics;
1276
+ const totalResources = Object.values(supplyByResource).reduce((s, v) => s + v, 0);
1277
+ const resourcesPerAgent = totalResources / Math.max(1, totalAgents);
1278
+ if (resourcesPerAgent > 20 && velocity < 3) {
1279
+ return {
1280
+ violated: true,
1281
+ severity: 4,
1282
+ evidence: { totalResources, resourcesPerAgent, velocity },
1283
+ suggestedAction: {
1284
+ parameter: "miningYield",
1285
+ direction: "decrease",
1286
+ magnitude: 0.1,
1287
+ reasoning: `${totalResources.toFixed(0)} resources with velocity ${velocity}/t. Likely hoarding. Reduce yield to increase scarcity and force circulation.`
1288
+ },
1289
+ confidence: 0.65,
1290
+ estimatedLag: 15
1291
+ };
1292
+ }
1293
+ return { violated: false };
1294
+ }
1295
+ };
1296
+ var P21_PriceFromGlobalSupply = {
1297
+ id: "P21",
1298
+ name: "Price Reflects Global Supply, Not Just AH Listings",
1299
+ category: "feedback",
1300
+ description: "If prices only update from Auction House activity, agents with hoarded inventory see artificially high prices and keep gathering when they should stop.",
1301
+ check(metrics, _thresholds) {
1302
+ const { priceVolatility, supplyByResource, prices } = metrics;
1303
+ for (const resource of Object.keys(prices)) {
1304
+ const volatility = priceVolatility[resource] ?? 0;
1305
+ const supply = supplyByResource[resource] ?? 0;
1306
+ if (volatility > 0.3 && supply > 30) {
1307
+ return {
1308
+ violated: true,
1309
+ severity: 3,
1310
+ evidence: { resource, volatility, supply, price: prices[resource] },
1311
+ suggestedAction: {
1312
+ parameter: "auctionFee",
1313
+ direction: "increase",
1314
+ magnitude: 0.05,
1315
+ reasoning: `${resource} price volatile (${(volatility * 100).toFixed(0)}%) despite supply ${supply}. Price may not reflect global inventory. Increase trading friction to stabilise.`
1316
+ },
1317
+ confidence: 0.55,
1318
+ estimatedLag: 10
1319
+ };
1320
+ }
1321
+ }
1322
+ return { violated: false };
1323
+ }
1324
+ };
1325
+ var P22_MarketAwarenessPreventsSurplus = {
1326
+ id: "P22",
1327
+ name: "Market Awareness Prevents Overproduction",
1328
+ category: "feedback",
1329
+ description: "Producers who craft without checking market prices will create surpluses that crash prices. Agents need to see prices before deciding to produce.",
1330
+ check(metrics, _thresholds) {
1331
+ const { supplyByResource, prices, productionIndex } = metrics;
1332
+ const weapons = supplyByResource["weapons"] ?? 0;
1333
+ const weaponPrice = prices["weapons"] ?? 0;
1334
+ const healthyWeaponPrice = 30;
1335
+ if (weapons > 100 && weaponPrice < healthyWeaponPrice * 0.5 && productionIndex > 0) {
1336
+ return {
1337
+ violated: true,
1338
+ severity: 4,
1339
+ evidence: { weapons, weaponPrice, productionIndex },
1340
+ suggestedAction: {
1341
+ parameter: "craftingCost",
1342
+ direction: "increase",
1343
+ magnitude: 0.1,
1344
+ reasoning: `${weapons} weapons with price ${weaponPrice.toFixed(0)}g but still producing. Producers appear unaware of market. Raise production cost to slow output.`
1345
+ },
1346
+ confidence: 0.7,
1347
+ estimatedLag: 8
1348
+ };
1349
+ }
1350
+ return { violated: false };
1351
+ }
1352
+ };
1353
+ var P23_ProfitabilityFactorsFeasibility = {
1354
+ id: "P23",
1355
+ name: "Profitability Factors Execution Feasibility",
1356
+ category: "feedback",
1357
+ description: "An agent who calculates profit = weapon_price - ore_cost but has no gold to buy ore is chasing phantom profit. Feasibility (can I afford the inputs?) must be part of the profitability calc.",
1358
+ check(metrics, _thresholds) {
1359
+ const { avgSatisfaction, blockedAgentCount, totalAgents } = metrics;
1360
+ const blockedFraction = blockedAgentCount / Math.max(1, totalAgents);
1361
+ if (blockedFraction > 0.2 && avgSatisfaction < 60) {
1362
+ return {
1363
+ violated: true,
1364
+ severity: 5,
1365
+ evidence: { blockedFraction, blockedAgentCount, avgSatisfaction },
1366
+ suggestedAction: {
1367
+ parameter: "craftingCost",
1368
+ direction: "decrease",
1369
+ magnitude: 0.15,
1370
+ reasoning: `${(blockedFraction * 100).toFixed(0)}% of agents blocked with low satisfaction. Agents may have roles they cannot afford to execute. Lower production costs to restore feasibility.`
1371
+ },
1372
+ confidence: 0.7,
1373
+ estimatedLag: 5
1374
+ };
1375
+ }
1376
+ return { violated: false };
1377
+ }
1378
+ };
1379
+ var P24_BlockedAgentsDecayFaster = {
1380
+ id: "P24",
1381
+ name: "Blocked Agents Decay Faster",
1382
+ category: "feedback",
1383
+ description: "An agent who cannot perform their preferred activity loses satisfaction faster and churns sooner. Blocked agents must be identified and unblocked, or they become silent bottlenecks that skew churn data.",
1384
+ check(metrics, thresholds) {
1385
+ const { blockedAgentCount, totalAgents, churnRate } = metrics;
1386
+ const blockedFraction = blockedAgentCount / Math.max(1, totalAgents);
1387
+ if (blockedFraction > thresholds.blockedAgentMaxFraction) {
1388
+ return {
1389
+ violated: true,
1390
+ severity: 5,
1391
+ evidence: { blockedFraction, blockedAgentCount, churnRate },
1392
+ suggestedAction: {
1393
+ parameter: "auctionFee",
1394
+ direction: "decrease",
1395
+ magnitude: 0.15,
1396
+ reasoning: `${(blockedFraction * 100).toFixed(0)}% of agents blocked. Blocked agents churn silently, skewing metrics. Lower fees to unblock market participation.`
1397
+ },
1398
+ confidence: 0.75,
1399
+ estimatedLag: 5
1400
+ };
1401
+ }
1402
+ return { violated: false };
1403
+ }
1404
+ };
1405
+ var FEEDBACK_LOOP_PRINCIPLES = [
1406
+ P20_DecayPreventsAccumulation,
1407
+ P21_PriceFromGlobalSupply,
1408
+ P22_MarketAwarenessPreventsSurplus,
1409
+ P23_ProfitabilityFactorsFeasibility,
1410
+ P24_BlockedAgentsDecayFaster
1411
+ ];
1412
+
1413
+ // src/principles/regulator.ts
1414
+ var P25_CorrectLeversForCorrectProblems = {
1415
+ id: "P25",
1416
+ name: "Target the Correct Lever",
1417
+ category: "regulator",
1418
+ description: "Adjusting sinks for supply-side inflation is wrong. Inflation from too much gathering \u2192 reduce mining yield. Inflation from pot payout \u2192 reduce reward multiplier. Matching lever to cause prevents oscillation.",
1419
+ check(metrics, thresholds) {
1420
+ const { netFlow, supplyByResource } = metrics;
1421
+ const ore = supplyByResource["ore"] ?? 0;
1422
+ const wood = supplyByResource["wood"] ?? 0;
1423
+ const resourceFlood = ore + wood > 100;
1424
+ if (netFlow > thresholds.netFlowWarnThreshold && resourceFlood) {
1425
+ return {
1426
+ violated: true,
1427
+ severity: 4,
1428
+ evidence: { netFlow, ore, wood },
1429
+ suggestedAction: {
1430
+ parameter: "miningYield",
1431
+ direction: "decrease",
1432
+ magnitude: 0.15,
1433
+ reasoning: `Inflation with raw material backlog (ore ${ore}, wood ${wood}). Root cause is gathering. Correct lever: miningYield, not fees.`
1434
+ },
1435
+ confidence: 0.75,
1436
+ estimatedLag: 8
1437
+ };
1438
+ }
1439
+ return { violated: false };
1440
+ }
1441
+ };
1442
+ var P26_ContinuousPressureBeatsThresholdCuts = {
1443
+ id: "P26",
1444
+ name: "Continuous 1%/tick > One-Time 10% Cut",
1445
+ category: "regulator",
1446
+ description: "Large one-time adjustments cause overshoot and oscillation. 1% per tick for 10 ticks reaches the same destination with far less disruption. This principle is enforced by maxAdjustmentPercent in the Planner.",
1447
+ check(metrics, thresholds) {
1448
+ const { inflationRate } = metrics;
1449
+ if (Math.abs(inflationRate) > 0.2) {
1450
+ return {
1451
+ violated: true,
1452
+ severity: 4,
1453
+ evidence: { inflationRate },
1454
+ suggestedAction: {
1455
+ parameter: "craftingCost",
1456
+ direction: inflationRate > 0 ? "increase" : "decrease",
1457
+ magnitude: Math.min(thresholds.maxAdjustmentPercent, 0.05),
1458
+ // force smaller step
1459
+ reasoning: `Inflation rate ${(inflationRate * 100).toFixed(1)}% \u2014 possible oscillation. Apply smaller correction to avoid overshoot.`
1460
+ },
1461
+ confidence: 0.6,
1462
+ estimatedLag: 5
1463
+ };
1464
+ }
1465
+ return { violated: false };
1466
+ }
1467
+ };
1468
+ var P27_AdjustmentsNeedCooldowns = {
1469
+ id: "P27",
1470
+ name: "Adjustments Need Cooldowns",
1471
+ category: "regulator",
1472
+ description: "Adjusting the same parameter twice in a window causes oscillation. Minimum 15 ticks between same-parameter adjustments. This is enforced in the Planner but checked here as a diagnostic.",
1473
+ check(metrics, _thresholds) {
1474
+ const { churnRate, avgSatisfaction } = metrics;
1475
+ if (churnRate > 0.08 && avgSatisfaction < 50) {
1476
+ return {
1477
+ violated: true,
1478
+ severity: 4,
1479
+ evidence: { churnRate, avgSatisfaction },
1480
+ suggestedAction: {
1481
+ parameter: "arenaEntryFee",
1482
+ direction: "decrease",
1483
+ magnitude: 0.05,
1484
+ reasoning: `High churn (${(churnRate * 100).toFixed(1)}%) with low satisfaction. Possible oscillation from rapid adjustments. Apply small correction only.`
1485
+ },
1486
+ confidence: 0.5,
1487
+ estimatedLag: 10
1488
+ };
1489
+ }
1490
+ return { violated: false };
1491
+ }
1492
+ };
1493
+ var P28_StructuralDominanceIsNotPathological = {
1494
+ id: "P28",
1495
+ name: "Structural Dominance \u2260 Pathological Monopoly",
1496
+ category: "regulator",
1497
+ description: 'A designed Fighter majority (55%) should not trigger population suppression. AgentE must distinguish between "this role is dominant BY DESIGN" (configured via dominantRoles) and "this role took over unexpectedly".',
1498
+ check(metrics, _thresholds) {
1499
+ const { roleShares, avgSatisfaction } = metrics;
1500
+ const dominant = Object.entries(roleShares).sort((a, b) => b[1] - a[1])[0];
1501
+ if (!dominant) return { violated: false };
1502
+ const [dominantRole, dominantShare] = dominant;
1503
+ if (dominantShare > 0.4 && avgSatisfaction > 70) {
1504
+ return { violated: false };
1505
+ }
1506
+ if (dominantShare > 0.4 && avgSatisfaction < 50) {
1507
+ return {
1508
+ violated: true,
1509
+ severity: 5,
1510
+ evidence: { dominantRole, dominantShare, avgSatisfaction },
1511
+ suggestedAction: {
1512
+ parameter: "craftingCost",
1513
+ direction: "decrease",
1514
+ magnitude: 0.1,
1515
+ reasoning: `${dominantRole} dominant (${(dominantShare * 100).toFixed(0)}%) with low satisfaction. Pathological dominance \u2014 agents trapped, not thriving. Ease costs to allow role switching.`
1516
+ },
1517
+ confidence: 0.65,
1518
+ estimatedLag: 15
1519
+ };
1520
+ }
1521
+ return { violated: false };
1522
+ }
1523
+ };
1524
+ var P38_CommunicationPreventsRevolt = {
1525
+ id: "P38",
1526
+ name: "Communication Prevents Revolt",
1527
+ category: "regulator",
1528
+ description: "Every adjustment must be logged with reasoning. An adjustment made without explanation to players causes revolt. AgentE logs every decision \u2014 this principle checks that logging is active.",
1529
+ check(metrics, _thresholds) {
1530
+ const { churnRate } = metrics;
1531
+ if (churnRate > 0.1) {
1532
+ return {
1533
+ violated: true,
1534
+ severity: 3,
1535
+ evidence: { churnRate },
1536
+ suggestedAction: {
1537
+ parameter: "arenaReward",
1538
+ direction: "increase",
1539
+ magnitude: 0.1,
1540
+ reasoning: `High churn (${(churnRate * 100).toFixed(1)}%) \u2014 agents leaving. Ensure all recent adjustments are logged with reasoning to diagnose cause.`
1541
+ },
1542
+ confidence: 0.5,
1543
+ estimatedLag: 10
1544
+ };
1545
+ }
1546
+ return { violated: false };
1547
+ }
1548
+ };
1549
+ var REGULATOR_PRINCIPLES = [
1550
+ P25_CorrectLeversForCorrectProblems,
1551
+ P26_ContinuousPressureBeatsThresholdCuts,
1552
+ P27_AdjustmentsNeedCooldowns,
1553
+ P28_StructuralDominanceIsNotPathological,
1554
+ P38_CommunicationPreventsRevolt
1555
+ ];
1556
+
1557
+ // src/principles/market-dynamics.ts
1558
+ var P29_PinchPoint = {
1559
+ id: "P29",
1560
+ name: "Pinch Point",
1561
+ category: "market_dynamics",
1562
+ description: "Every economy has a resource that constrains all downstream activity. In AgentE v0: weapons are the pinch point (Fighters need them, Crafters make them). If demand drops \u2192 oversupply. If frustration rises \u2192 undersupply.",
1563
+ check(metrics, _thresholds) {
1564
+ const { pinchPoints, supplyByResource, demandSignals } = metrics;
1565
+ for (const [resource, status] of Object.entries(pinchPoints)) {
1566
+ if (status === "scarce") {
1567
+ const supply = supplyByResource[resource] ?? 0;
1568
+ const demand = demandSignals[resource] ?? 0;
1569
+ return {
1570
+ violated: true,
1571
+ severity: 7,
1572
+ evidence: { resource, supply, demand, status },
1573
+ suggestedAction: {
1574
+ parameter: "craftingCost",
1575
+ direction: "decrease",
1576
+ magnitude: 0.15,
1577
+ reasoning: `${resource} is a pinch point and currently SCARCE (supply ${supply}, demand ${demand}). Reduce production cost to increase throughput.`
1578
+ },
1579
+ confidence: 0.8,
1580
+ estimatedLag: 5
1581
+ };
1582
+ }
1583
+ if (status === "oversupplied") {
1584
+ const supply = supplyByResource[resource] ?? 0;
1585
+ return {
1586
+ violated: true,
1587
+ severity: 4,
1588
+ evidence: { resource, supply, status },
1589
+ suggestedAction: {
1590
+ parameter: "craftingCost",
1591
+ direction: "increase",
1592
+ magnitude: 0.1,
1593
+ reasoning: `${resource} is a pinch point and OVERSUPPLIED (supply ${supply}). Raise production cost to reduce surplus.`
1594
+ },
1595
+ confidence: 0.7,
1596
+ estimatedLag: 8
1597
+ };
1598
+ }
1599
+ }
1600
+ return { violated: false };
1601
+ }
1602
+ };
1603
+ var P30_MovingPinchPoint = {
1604
+ id: "P30",
1605
+ name: "Moving Pinch Point",
1606
+ category: "market_dynamics",
1607
+ description: "Player progression shifts the demand curve. A static pinch point that works at level 1 will be cleared at level 10. The pinch point must move with the player to maintain ongoing scarcity and engagement.",
1608
+ check(metrics, _thresholds) {
1609
+ const { capacityUsage, supplyByResource, avgSatisfaction } = metrics;
1610
+ const totalResources = Object.values(supplyByResource).reduce((s, v) => s + v, 0);
1611
+ const resourcesPerAgent = totalResources / Math.max(1, metrics.totalAgents);
1612
+ if (capacityUsage > 0.9 && resourcesPerAgent > 15 && avgSatisfaction > 75) {
1613
+ return {
1614
+ violated: true,
1615
+ severity: 3,
1616
+ evidence: { capacityUsage, resourcesPerAgent, avgSatisfaction },
1617
+ suggestedAction: {
1618
+ parameter: "craftingCost",
1619
+ direction: "increase",
1620
+ magnitude: 0.1,
1621
+ reasoning: "Economy operating at full capacity with abundant resources and high satisfaction. Pinch point may have been cleared. Increase production cost to restore scarcity."
1622
+ },
1623
+ confidence: 0.55,
1624
+ estimatedLag: 20
1625
+ };
1626
+ }
1627
+ return { violated: false };
1628
+ }
1629
+ };
1630
+ var P57_CombinatorialPriceSpace = {
1631
+ id: "P57",
1632
+ name: "Combinatorial Price Space",
1633
+ category: "market_dynamics",
1634
+ description: "N tradeable items generate (N\u22121)N/2 relative prices. With thousands of items no single agent can track them all. Design for distributed self-organization, not centralized pricing.",
1635
+ check(metrics, thresholds) {
1636
+ const { prices, priceVolatility } = metrics;
1637
+ const priceKeys = Object.keys(prices);
1638
+ const n = priceKeys.length;
1639
+ const relativePriceCount = n * (n - 1) / 2;
1640
+ if (n < 2) return { violated: false };
1641
+ let convergedPairs = 0;
1642
+ for (let i = 0; i < priceKeys.length; i++) {
1643
+ for (let j = i + 1; j < priceKeys.length; j++) {
1644
+ const volA = priceVolatility[priceKeys[i]] ?? 0;
1645
+ const volB = priceVolatility[priceKeys[j]] ?? 0;
1646
+ if (volA < 0.2 && volB < 0.2) {
1647
+ convergedPairs++;
1648
+ }
1649
+ }
1650
+ }
1651
+ const convergenceRate = convergedPairs / Math.max(1, relativePriceCount);
1652
+ if (convergenceRate < thresholds.relativePriceConvergenceTarget && n >= 4) {
1653
+ return {
1654
+ violated: true,
1655
+ severity: 4,
1656
+ evidence: {
1657
+ totalItems: n,
1658
+ relativePriceCount,
1659
+ convergedPairs,
1660
+ convergenceRate,
1661
+ target: thresholds.relativePriceConvergenceTarget
1662
+ },
1663
+ suggestedAction: {
1664
+ parameter: "auctionFee",
1665
+ direction: "decrease",
1666
+ magnitude: 0.1,
1667
+ reasoning: `Only ${(convergenceRate * 100).toFixed(0)}% of ${relativePriceCount} relative prices have converged (target: ${(thresholds.relativePriceConvergenceTarget * 100).toFixed(0)}%). Price space too complex for distributed discovery. Lower friction to help.`
1668
+ },
1669
+ confidence: 0.6,
1670
+ estimatedLag: thresholds.priceDiscoveryWindowTicks
1671
+ };
1672
+ }
1673
+ return { violated: false };
1674
+ }
1675
+ };
1676
+ var MARKET_DYNAMICS_PRINCIPLES = [
1677
+ P29_PinchPoint,
1678
+ P30_MovingPinchPoint,
1679
+ P57_CombinatorialPriceSpace
1680
+ ];
1681
+
1682
+ // src/principles/measurement.ts
1683
+ var P31_AnchorValueTracking = {
1684
+ id: "P31",
1685
+ name: "Anchor Value Tracking",
1686
+ category: "measurement",
1687
+ description: "1 hour of play = X gold = Y items. If this ratio drifts, the economy is inflating or deflating in ways that players feel before metrics catch it. Track the ratio constantly.",
1688
+ check(metrics, _thresholds) {
1689
+ const { anchorRatioDrift, inflationRate } = metrics;
1690
+ if (Math.abs(anchorRatioDrift) > 0.25) {
1691
+ return {
1692
+ violated: true,
1693
+ severity: 5,
1694
+ evidence: { anchorRatioDrift, inflationRate },
1695
+ suggestedAction: {
1696
+ parameter: "craftingCost",
1697
+ direction: anchorRatioDrift > 0 ? "increase" : "decrease",
1698
+ magnitude: 0.1,
1699
+ reasoning: `Anchor ratio has drifted ${(anchorRatioDrift * 100).toFixed(0)}% from baseline. Time-to-value for players is changing. Adjust production costs to restore.`
1700
+ },
1701
+ confidence: 0.65,
1702
+ estimatedLag: 10
1703
+ };
1704
+ }
1705
+ return { violated: false };
1706
+ }
1707
+ };
1708
+ var P41_MultiResolutionMonitoring = {
1709
+ id: "P41",
1710
+ name: "Multi-Resolution Monitoring",
1711
+ category: "measurement",
1712
+ description: "Single-resolution monitoring misses crises that develop slowly (coarse only) or explode suddenly (fine only). Monitor at fine (per-tick), medium (per-10-ticks), and coarse (per-100-ticks) simultaneously.",
1713
+ check(metrics, _thresholds) {
1714
+ const { giniCoefficient, avgSatisfaction } = metrics;
1715
+ if (giniCoefficient > 0.5 && avgSatisfaction > 65) {
1716
+ return {
1717
+ violated: true,
1718
+ severity: 4,
1719
+ evidence: { giniCoefficient, avgSatisfaction },
1720
+ suggestedAction: {
1721
+ parameter: "auctionFee",
1722
+ direction: "increase",
1723
+ magnitude: 0.1,
1724
+ reasoning: `Gini ${giniCoefficient.toFixed(2)} rising despite okay satisfaction. Early warning from fine-resolution monitoring. Raise trading fees to slow wealth concentration before it hurts satisfaction.`
1725
+ },
1726
+ confidence: 0.7,
1727
+ estimatedLag: 20
1728
+ };
1729
+ }
1730
+ return { violated: false };
1731
+ }
1732
+ };
1733
+ var P55_ArbitrageThermometer = {
1734
+ id: "P55",
1735
+ name: "Arbitrage Thermometer",
1736
+ category: "measurement",
1737
+ description: "A virtual economy is never in true equilibrium \u2014 it oscillates around it. The aggregate arbitrage window across relative prices is a live health metric: rising arbitrage signals destabilization, falling signals recovery.",
1738
+ check(metrics, thresholds) {
1739
+ const { arbitrageIndex } = metrics;
1740
+ if (arbitrageIndex > thresholds.arbitrageIndexCritical) {
1741
+ return {
1742
+ violated: true,
1743
+ severity: 7,
1744
+ evidence: {
1745
+ arbitrageIndex,
1746
+ warning: thresholds.arbitrageIndexWarning,
1747
+ critical: thresholds.arbitrageIndexCritical
1748
+ },
1749
+ suggestedAction: {
1750
+ parameter: "auctionFee",
1751
+ direction: "decrease",
1752
+ magnitude: 0.15,
1753
+ reasoning: `Arbitrage index ${arbitrageIndex.toFixed(2)} exceeds critical threshold (${thresholds.arbitrageIndexCritical}). Relative prices are diverging \u2014 economy destabilizing. Lower trading friction to accelerate price convergence.`
1754
+ },
1755
+ confidence: 0.8,
1756
+ estimatedLag: 8
1757
+ };
1758
+ }
1759
+ if (arbitrageIndex > thresholds.arbitrageIndexWarning) {
1760
+ return {
1761
+ violated: true,
1762
+ severity: 4,
1763
+ evidence: {
1764
+ arbitrageIndex,
1765
+ warning: thresholds.arbitrageIndexWarning
1766
+ },
1767
+ suggestedAction: {
1768
+ parameter: "auctionFee",
1769
+ direction: "decrease",
1770
+ magnitude: 0.08,
1771
+ reasoning: `Arbitrage index ${arbitrageIndex.toFixed(2)} above warning threshold (${thresholds.arbitrageIndexWarning}). Early sign of price divergence. Gently reduce friction to support self-correction.`
1772
+ },
1773
+ confidence: 0.65,
1774
+ estimatedLag: 12
1775
+ };
1776
+ }
1777
+ return { violated: false };
1778
+ }
1779
+ };
1780
+ var P59_GiftEconomyNoise = {
1781
+ id: "P59",
1782
+ name: "Gift-Economy Noise",
1783
+ category: "measurement",
1784
+ description: "Non-market exchanges \u2014 gifts, charity trades, social signaling \u2014 contaminate price signals. Filter gift-like and below-market transactions before computing economic indicators.",
1785
+ check(metrics, thresholds) {
1786
+ const { giftTradeRatio } = metrics;
1787
+ if (giftTradeRatio > thresholds.giftTradeFilterRatio) {
1788
+ return {
1789
+ violated: true,
1790
+ severity: 4,
1791
+ evidence: {
1792
+ giftTradeRatio,
1793
+ threshold: thresholds.giftTradeFilterRatio
1794
+ },
1795
+ suggestedAction: {
1796
+ parameter: "auctionFee",
1797
+ direction: "increase",
1798
+ magnitude: 0.05,
1799
+ reasoning: `${(giftTradeRatio * 100).toFixed(0)}% of trades are gift-like (price = 0 or <30% market). Exceeds filter threshold (${(thresholds.giftTradeFilterRatio * 100).toFixed(0)}%). Price signals contaminated. Slightly raise trading fees to discourage zero-value listings. ADVISORY: Consider filtering sub-market trades from price index computation.`
1800
+ },
1801
+ confidence: 0.7,
1802
+ estimatedLag: 5
1803
+ };
1804
+ }
1805
+ return { violated: false };
1806
+ }
1807
+ };
1808
+ var MEASUREMENT_PRINCIPLES = [
1809
+ P31_AnchorValueTracking,
1810
+ P41_MultiResolutionMonitoring,
1811
+ P55_ArbitrageThermometer,
1812
+ P59_GiftEconomyNoise
1813
+ ];
1814
+
1815
+ // src/principles/statistical.ts
1816
+ var P42_TheMedianPrinciple = {
1817
+ id: "P42",
1818
+ name: "The Median Principle",
1819
+ category: "statistical",
1820
+ description: "When (mean - median) / median > 0.3, mean is a lie. A few whales with 10,000g raise the mean while most agents have 50g. Always balance to median when divergence exceeds 30%.",
1821
+ check(metrics, thresholds) {
1822
+ const { meanMedianDivergence, giniCoefficient } = metrics;
1823
+ if (meanMedianDivergence > thresholds.meanMedianDivergenceMax) {
1824
+ return {
1825
+ violated: true,
1826
+ severity: 5,
1827
+ evidence: {
1828
+ meanMedianDivergence,
1829
+ giniCoefficient,
1830
+ meanBalance: metrics.meanBalance,
1831
+ medianBalance: metrics.medianBalance
1832
+ },
1833
+ suggestedAction: {
1834
+ parameter: "auctionFee",
1835
+ direction: "increase",
1836
+ magnitude: 0.15,
1837
+ reasoning: `Mean/median divergence ${(meanMedianDivergence * 100).toFixed(0)}% (threshold: ${(thresholds.meanMedianDivergenceMax * 100).toFixed(0)}%). Economy has outliers skewing metrics. Use median for decisions. Raise auction fees to redistribute wealth.`
1838
+ },
1839
+ confidence: 0.85,
1840
+ estimatedLag: 15
1841
+ };
1842
+ }
1843
+ return { violated: false };
1844
+ }
1845
+ };
1846
+ var P43_SimulationMinimum = {
1847
+ id: "P43",
1848
+ name: "Simulation Minimum (100 Iterations)",
1849
+ category: "statistical",
1850
+ description: "Fewer than 100 Monte Carlo iterations produces unreliable predictions. The variance of a 10-iteration simulation is so high that you might as well be guessing. This principle enforces the minimum in the Simulator.",
1851
+ check(metrics, thresholds) {
1852
+ const { inflationRate } = metrics;
1853
+ if (Math.abs(inflationRate) > 0.3) {
1854
+ return {
1855
+ violated: true,
1856
+ severity: 3,
1857
+ evidence: { inflationRate, minIterations: thresholds.simulationMinIterations },
1858
+ suggestedAction: {
1859
+ parameter: "craftingCost",
1860
+ direction: inflationRate > 0 ? "increase" : "decrease",
1861
+ magnitude: 0.05,
1862
+ reasoning: `Large inflation rate swing (${(inflationRate * 100).toFixed(0)}%). Ensure all decisions use \u2265${thresholds.simulationMinIterations} simulation iterations. Apply conservative correction.`
1863
+ },
1864
+ confidence: 0.5,
1865
+ estimatedLag: 5
1866
+ };
1867
+ }
1868
+ return { violated: false };
1869
+ }
1870
+ };
1871
+ var STATISTICAL_PRINCIPLES = [
1872
+ P42_TheMedianPrinciple,
1873
+ P43_SimulationMinimum
1874
+ ];
1875
+
1876
+ // src/principles/system-dynamics.ts
1877
+ var P39_TheLagPrinciple = {
1878
+ id: "P39",
1879
+ name: "The Lag Principle",
1880
+ category: "system_dynamics",
1881
+ description: "Total lag = 3-5\xD7 observation interval. If you observe every 5 ticks, expect effects after 15-25 ticks. Adjusting again before lag expires = overshoot. This is enforced by the Planner but diagnosed here.",
1882
+ check(metrics, thresholds) {
1883
+ const { inflationRate, netFlow } = metrics;
1884
+ const inflationPositive = inflationRate > 0.05;
1885
+ const netFlowNegative = netFlow < -5;
1886
+ const inflationNegative = inflationRate < -0.05;
1887
+ const netFlowPositive = netFlow > 5;
1888
+ const oscillating = inflationPositive && netFlowNegative || inflationNegative && netFlowPositive;
1889
+ if (oscillating) {
1890
+ const lagMin = thresholds.lagMultiplierMin;
1891
+ const lagMax = thresholds.lagMultiplierMax;
1892
+ return {
1893
+ violated: true,
1894
+ severity: 5,
1895
+ evidence: { inflationRate, netFlow, lagRange: [lagMin, lagMax] },
1896
+ suggestedAction: {
1897
+ parameter: "craftingCost",
1898
+ direction: "increase",
1899
+ magnitude: 0.03,
1900
+ // very small — oscillation means over-adjusting
1901
+ reasoning: `Inflation and net flow moving in opposite directions \u2014 overshoot pattern. Wait for lag to resolve (${lagMin}-${lagMax}\xD7 observation interval). Apply minimal correction only.`
1902
+ },
1903
+ confidence: 0.65,
1904
+ estimatedLag: thresholds.lagMultiplierMax * 5
1905
+ // conservative
1906
+ };
1907
+ }
1908
+ return { violated: false };
1909
+ }
1910
+ };
1911
+ var P44_ComplexityBudget = {
1912
+ id: "P44",
1913
+ name: "Complexity Budget",
1914
+ category: "system_dynamics",
1915
+ description: "More than 20 active adjustable parameters \u2192 exponential debugging cost. Each parameter that affects <5% of a core metric should be pruned. AgentE tracks active parameters and flags when budget exceeded.",
1916
+ check(metrics, thresholds) {
1917
+ const customMetricCount = Object.keys(metrics.custom).length;
1918
+ if (customMetricCount > thresholds.complexityBudgetMax) {
1919
+ return {
1920
+ violated: true,
1921
+ severity: 3,
1922
+ evidence: { customMetricCount, budgetMax: thresholds.complexityBudgetMax },
1923
+ suggestedAction: {
1924
+ parameter: "auctionFee",
1925
+ direction: "decrease",
1926
+ magnitude: 0.01,
1927
+ reasoning: `${customMetricCount} custom metrics tracked (budget: ${thresholds.complexityBudgetMax}). Consider pruning low-impact parameters. Applying minimal correction to avoid adding complexity.`
1928
+ },
1929
+ confidence: 0.4,
1930
+ estimatedLag: 0
1931
+ };
1932
+ }
1933
+ return { violated: false };
1934
+ }
1935
+ };
1936
+ var SYSTEM_DYNAMICS_PRINCIPLES = [
1937
+ P39_TheLagPrinciple,
1938
+ P44_ComplexityBudget
1939
+ ];
1940
+
1941
+ // src/principles/resource-mgmt.ts
1942
+ var P35_DestructionCreatesValue = {
1943
+ id: "P35",
1944
+ name: "Destruction Creates Value",
1945
+ category: "resource",
1946
+ description: "If nothing is ever permanently lost, inflation is inevitable. Weapon durability (breaks after 3 fights), potion consumption on use, and ore costs for crafting are all destruction mechanisms. Without them, supply grows without bound.",
1947
+ check(metrics, _thresholds) {
1948
+ const { supplyByResource, sinkVolume, netFlow } = metrics;
1949
+ const weapons = supplyByResource["weapons"] ?? 0;
1950
+ const potions = supplyByResource["potions"] ?? 0;
1951
+ if ((weapons > 200 || potions > 200) && sinkVolume < 5 && netFlow > 0) {
1952
+ return {
1953
+ violated: true,
1954
+ severity: 6,
1955
+ evidence: { weapons, potions, sinkVolume, netFlow },
1956
+ suggestedAction: {
1957
+ parameter: "arenaEntryFee",
1958
+ direction: "decrease",
1959
+ magnitude: 0.1,
1960
+ reasoning: `${weapons} weapons + ${potions} potions with low destruction (sink ${sinkVolume}/t). Consumables not being consumed. Lower arena entry to increase weapon/potion usage.`
1961
+ },
1962
+ confidence: 0.7,
1963
+ estimatedLag: 5
1964
+ };
1965
+ }
1966
+ return { violated: false };
1967
+ }
1968
+ };
1969
+ var P40_ReplacementRate = {
1970
+ id: "P40",
1971
+ name: "Replacement Rate \u2265 2\xD7 Consumption",
1972
+ category: "resource",
1973
+ description: "Respawn/production rate must be at least 2\xD7 consumption rate for equilibrium. At 1\xD7 you drift toward depletion. At 2\xD7 you have a buffer for demand spikes.",
1974
+ check(metrics, thresholds) {
1975
+ const { productionIndex, sinkVolume } = metrics;
1976
+ if (sinkVolume > 0 && productionIndex > 0) {
1977
+ const replacementRatio = productionIndex / sinkVolume;
1978
+ if (replacementRatio < 1) {
1979
+ return {
1980
+ violated: true,
1981
+ severity: 6,
1982
+ evidence: { productionIndex, sinkVolume, replacementRatio },
1983
+ suggestedAction: {
1984
+ parameter: "miningYield",
1985
+ direction: "increase",
1986
+ magnitude: 0.15,
1987
+ reasoning: `Replacement rate ${replacementRatio.toFixed(2)} (need \u2265${thresholds.replacementRateMultiplier}). Production below consumption. Resources will deplete. Increase yield.`
1988
+ },
1989
+ confidence: 0.8,
1990
+ estimatedLag: 5
1991
+ };
1992
+ } else if (replacementRatio > thresholds.replacementRateMultiplier * 3) {
1993
+ return {
1994
+ violated: true,
1995
+ severity: 3,
1996
+ evidence: { productionIndex, sinkVolume, replacementRatio },
1997
+ suggestedAction: {
1998
+ parameter: "miningYield",
1999
+ direction: "decrease",
2000
+ magnitude: 0.1,
2001
+ reasoning: `Replacement rate ${replacementRatio.toFixed(2)} \u2014 overproducing. Production far exceeds consumption. Reduce yield to prevent glut.`
2002
+ },
2003
+ confidence: 0.7,
2004
+ estimatedLag: 8
2005
+ };
2006
+ }
2007
+ }
2008
+ return { violated: false };
2009
+ }
2010
+ };
2011
+ var P49_IdleAssetTax = {
2012
+ id: "P49",
2013
+ name: "Idle Asset Tax",
2014
+ category: "resource",
2015
+ description: 'Appreciating assets without holding cost \u2192 wealth concentration. If hoarding an asset makes you richer just by holding it, everyone hoards. Decay rates, storage costs, or expiry are "idle asset taxes" that force circulation.',
2016
+ check(metrics, _thresholds) {
2017
+ const { giniCoefficient, top10PctShare, velocity } = metrics;
2018
+ if (giniCoefficient > 0.55 && top10PctShare > 0.6 && velocity < 5) {
2019
+ return {
2020
+ violated: true,
2021
+ severity: 5,
2022
+ evidence: { giniCoefficient, top10PctShare, velocity },
2023
+ suggestedAction: {
2024
+ parameter: "auctionFee",
2025
+ direction: "increase",
2026
+ magnitude: 0.15,
2027
+ reasoning: `Gini ${giniCoefficient.toFixed(2)}, top 10% hold ${(top10PctShare * 100).toFixed(0)}%, velocity ${velocity}. Wealth concentrated in idle assets. Raise trading costs to simulate holding tax.`
2028
+ },
2029
+ confidence: 0.7,
2030
+ estimatedLag: 15
2031
+ };
2032
+ }
2033
+ return { violated: false };
2034
+ }
2035
+ };
2036
+ var RESOURCE_MGMT_PRINCIPLES = [
2037
+ P35_DestructionCreatesValue,
2038
+ P40_ReplacementRate,
2039
+ P49_IdleAssetTax
2040
+ ];
2041
+
2042
+ // src/principles/player-experience.ts
2043
+ var P33_FairNotEqual = {
2044
+ id: "P33",
2045
+ name: "Fair \u2260 Equal",
2046
+ category: "player_experience",
2047
+ description: "Gini = 0 is boring \u2014 everyone has the same and there is nothing to strive for. Healthy inequality from skill/effort is fine. Inequality from money (pay-to-win) is toxic. Target Gini 0.3-0.45: meaningful spread, not oligarchy.",
2048
+ check(metrics, thresholds) {
2049
+ const { giniCoefficient } = metrics;
2050
+ if (giniCoefficient < 0.1) {
2051
+ return {
2052
+ violated: true,
2053
+ severity: 3,
2054
+ evidence: { giniCoefficient },
2055
+ suggestedAction: {
2056
+ parameter: "arenaReward",
2057
+ direction: "increase",
2058
+ magnitude: 0.1,
2059
+ reasoning: `Gini ${giniCoefficient.toFixed(2)} \u2014 near-perfect equality. Economy lacks stakes. Increase winner rewards to create meaningful spread.`
2060
+ },
2061
+ confidence: 0.6,
2062
+ estimatedLag: 20
2063
+ };
2064
+ }
2065
+ if (giniCoefficient > thresholds.giniRedThreshold) {
2066
+ return {
2067
+ violated: true,
2068
+ severity: 7,
2069
+ evidence: { giniCoefficient },
2070
+ suggestedAction: {
2071
+ parameter: "auctionFee",
2072
+ direction: "increase",
2073
+ magnitude: 0.2,
2074
+ reasoning: `Gini ${giniCoefficient.toFixed(2)} \u2014 oligarchy level. Toxic inequality. Raise transaction fees to redistribute wealth from rich to pool.`
2075
+ },
2076
+ confidence: 0.85,
2077
+ estimatedLag: 10
2078
+ };
2079
+ }
2080
+ if (giniCoefficient > thresholds.giniWarnThreshold) {
2081
+ return {
2082
+ violated: true,
2083
+ severity: 4,
2084
+ evidence: { giniCoefficient },
2085
+ suggestedAction: {
2086
+ parameter: "auctionFee",
2087
+ direction: "increase",
2088
+ magnitude: 0.1,
2089
+ reasoning: `Gini ${giniCoefficient.toFixed(2)} \u2014 high inequality warning. Gently raise fees to slow wealth concentration.`
2090
+ },
2091
+ confidence: 0.75,
2092
+ estimatedLag: 15
2093
+ };
2094
+ }
2095
+ return { violated: false };
2096
+ }
2097
+ };
2098
+ var P36_MechanicFrictionDetector = {
2099
+ id: "P36",
2100
+ name: "Mechanic Friction Detector",
2101
+ category: "player_experience",
2102
+ description: "Deterministic + probabilistic systems \u2192 expectation mismatch. When crafting is guaranteed but combat is random, players feel betrayed by the random side. Mix mechanics carefully or segregate them entirely.",
2103
+ check(metrics, _thresholds) {
2104
+ const { avgSatisfaction, churnRate, velocity } = metrics;
2105
+ if (churnRate > 0.1 && avgSatisfaction < 50 && velocity > 3) {
2106
+ return {
2107
+ violated: true,
2108
+ severity: 5,
2109
+ evidence: { churnRate, avgSatisfaction, velocity },
2110
+ suggestedAction: {
2111
+ parameter: "arenaReward",
2112
+ direction: "increase",
2113
+ magnitude: 0.15,
2114
+ reasoning: `Churn ${(churnRate * 100).toFixed(1)}% with satisfaction ${avgSatisfaction.toFixed(0)} despite active economy (velocity ` + velocity.toFixed(1) + "). Suggests mechanic friction (deterministic vs random systems). Increase rewards to compensate for perceived unfairness. ADVISORY: Review if mixing guaranteed and probabilistic mechanics."
2115
+ },
2116
+ confidence: 0.55,
2117
+ estimatedLag: 15
2118
+ };
2119
+ }
2120
+ return { violated: false };
2121
+ }
2122
+ };
2123
+ var P37_LatecommerProblem = {
2124
+ id: "P37",
2125
+ name: "Latecomer Problem",
2126
+ category: "player_experience",
2127
+ description: "A new participant must reach viability in reasonable time. If all the good roles are saturated and prices are high, new agents cannot contribute and churn immediately.",
2128
+ check(metrics, _thresholds) {
2129
+ const { timeToValue, avgSatisfaction, churnRate } = metrics;
2130
+ if (churnRate > 0.08 && avgSatisfaction < 55 && timeToValue > 20) {
2131
+ return {
2132
+ violated: true,
2133
+ severity: 6,
2134
+ evidence: { timeToValue, avgSatisfaction, churnRate },
2135
+ suggestedAction: {
2136
+ parameter: "craftingCost",
2137
+ direction: "decrease",
2138
+ magnitude: 0.15,
2139
+ reasoning: `New agents taking ${timeToValue} ticks to reach viability. Churn ${(churnRate * 100).toFixed(1)}%, satisfaction ${avgSatisfaction.toFixed(0)}. Lower production costs to help new participants contribute faster.`
2140
+ },
2141
+ confidence: 0.7,
2142
+ estimatedLag: 10
2143
+ };
2144
+ }
2145
+ return { violated: false };
2146
+ }
2147
+ };
2148
+ var P45_TimeBudget = {
2149
+ id: "P45",
2150
+ name: "Time Budget",
2151
+ category: "player_experience",
2152
+ description: "required_time \u2264 available_time \xD7 0.8. If the economy requires more engagement than participants can realistically give, it is a disguised paywall. The 0.8 buffer accounts for real life.",
2153
+ check(metrics, thresholds) {
2154
+ const { timeToValue, avgSatisfaction } = metrics;
2155
+ const timePressure = timeToValue > 30;
2156
+ const dissatisfied = avgSatisfaction < 55;
2157
+ if (timePressure && dissatisfied) {
2158
+ return {
2159
+ violated: true,
2160
+ severity: 5,
2161
+ evidence: { timeToValue, avgSatisfaction, timeBudgetRatio: thresholds.timeBudgetRatio },
2162
+ suggestedAction: {
2163
+ parameter: "arenaEntryFee",
2164
+ direction: "decrease",
2165
+ magnitude: 0.15,
2166
+ reasoning: `Time-to-value ${timeToValue} ticks with ${avgSatisfaction.toFixed(0)} satisfaction. Economy requires too much time investment. Lower barriers to participation.`
2167
+ },
2168
+ confidence: 0.65,
2169
+ estimatedLag: 10
2170
+ };
2171
+ }
2172
+ return { violated: false };
2173
+ }
2174
+ };
2175
+ var P50_PayPowerRatio = {
2176
+ id: "P50",
2177
+ name: "Pay-Power Ratio",
2178
+ category: "player_experience",
2179
+ description: "spender / non-spender power ratio > 2.0 = pay-to-win territory. Target 1.5 (meaningful advantage without shutting out non-payers). Above 2.0, non-paying participants start leaving.",
2180
+ check(metrics, thresholds) {
2181
+ const { top10PctShare, giniCoefficient } = metrics;
2182
+ const wealthToTopFraction = top10PctShare;
2183
+ if (wealthToTopFraction > 0.7 && giniCoefficient > 0.55) {
2184
+ return {
2185
+ violated: true,
2186
+ severity: 6,
2187
+ evidence: {
2188
+ top10PctShare,
2189
+ giniCoefficient,
2190
+ threshold: thresholds.payPowerRatioMax
2191
+ },
2192
+ suggestedAction: {
2193
+ parameter: "auctionFee",
2194
+ direction: "increase",
2195
+ magnitude: 0.2,
2196
+ reasoning: `Top 10% hold ${(top10PctShare * 100).toFixed(0)}% of wealth (Gini ${giniCoefficient.toFixed(2)}). Wealth advantage may exceed pay-power ratio threshold. Redistribute via higher trading fees.`
2197
+ },
2198
+ confidence: 0.65,
2199
+ estimatedLag: 15
2200
+ };
2201
+ }
2202
+ return { violated: false };
2203
+ }
2204
+ };
2205
+ var PLAYER_EXPERIENCE_PRINCIPLES = [
2206
+ P33_FairNotEqual,
2207
+ P36_MechanicFrictionDetector,
2208
+ P37_LatecommerProblem,
2209
+ P45_TimeBudget,
2210
+ P50_PayPowerRatio
2211
+ ];
2212
+
2213
+ // src/principles/open-economy.ts
2214
+ var P34_ExtractionRatio = {
2215
+ id: "P34",
2216
+ name: "Extraction Ratio",
2217
+ category: "open_economy",
2218
+ description: "If >65% of participants are net extractors (taking value out without putting it in), the economy needs external subsidy (new user influx) to survive. Above 65%, any slowdown in new users collapses the economy.",
2219
+ check(metrics, thresholds) {
2220
+ const { extractionRatio } = metrics;
2221
+ if (isNaN(extractionRatio)) return { violated: false };
2222
+ if (extractionRatio > thresholds.extractionRatioRed) {
2223
+ return {
2224
+ violated: true,
2225
+ severity: 8,
2226
+ evidence: { extractionRatio, threshold: thresholds.extractionRatioRed },
2227
+ suggestedAction: {
2228
+ parameter: "auctionFee",
2229
+ direction: "increase",
2230
+ magnitude: 0.25,
2231
+ reasoning: `Extraction ratio ${(extractionRatio * 100).toFixed(0)}% (critical: ${(thresholds.extractionRatioRed * 100).toFixed(0)}%). Economy is extraction-heavy and subsidy-dependent. Raise fees to increase the cost of extraction.`
2232
+ },
2233
+ confidence: 0.85,
2234
+ estimatedLag: 10
2235
+ };
2236
+ }
2237
+ if (extractionRatio > thresholds.extractionRatioYellow) {
2238
+ return {
2239
+ violated: true,
2240
+ severity: 5,
2241
+ evidence: { extractionRatio, threshold: thresholds.extractionRatioYellow },
2242
+ suggestedAction: {
2243
+ parameter: "auctionFee",
2244
+ direction: "increase",
2245
+ magnitude: 0.1,
2246
+ reasoning: `Extraction ratio ${(extractionRatio * 100).toFixed(0)}% (warning: ${(thresholds.extractionRatioYellow * 100).toFixed(0)}%). Economy trending toward extraction-heavy. Apply early pressure.`
2247
+ },
2248
+ confidence: 0.75,
2249
+ estimatedLag: 15
2250
+ };
2251
+ }
2252
+ return { violated: false };
2253
+ }
2254
+ };
2255
+ var P47_SmokeTest = {
2256
+ id: "P47",
2257
+ name: "Smoke Test",
2258
+ category: "open_economy",
2259
+ description: "intrinsic_utility_value / total_market_value < 0.3 = economy is >70% speculation. If utility value drops below 10%, a single bad week can collapse the entire market. Real utility (weapons that help you fight, potions that heal) must anchor value.",
2260
+ check(metrics, thresholds) {
2261
+ const { smokeTestRatio } = metrics;
2262
+ if (isNaN(smokeTestRatio)) return { violated: false };
2263
+ if (smokeTestRatio < thresholds.smokeTestCritical) {
2264
+ return {
2265
+ violated: true,
2266
+ severity: 9,
2267
+ evidence: { smokeTestRatio, threshold: thresholds.smokeTestCritical },
2268
+ suggestedAction: {
2269
+ parameter: "arenaReward",
2270
+ direction: "increase",
2271
+ magnitude: 0.2,
2272
+ reasoning: `Utility/market ratio ${(smokeTestRatio * 100).toFixed(0)}% (critical). Economy is >90% speculative. Collapse risk is extreme. Increase utility rewards to anchor real value.`
2273
+ },
2274
+ confidence: 0.9,
2275
+ estimatedLag: 20
2276
+ };
2277
+ }
2278
+ if (smokeTestRatio < thresholds.smokeTestWarning) {
2279
+ return {
2280
+ violated: true,
2281
+ severity: 6,
2282
+ evidence: { smokeTestRatio, threshold: thresholds.smokeTestWarning },
2283
+ suggestedAction: {
2284
+ parameter: "arenaReward",
2285
+ direction: "increase",
2286
+ magnitude: 0.1,
2287
+ reasoning: `Utility/market ratio ${(smokeTestRatio * 100).toFixed(0)}% (warning). Economy is >70% speculative. Boost utility rewards to restore intrinsic value anchor.`
2288
+ },
2289
+ confidence: 0.75,
2290
+ estimatedLag: 20
2291
+ };
2292
+ }
2293
+ return { violated: false };
2294
+ }
2295
+ };
2296
+ var P48_CurrencyInsulation = {
2297
+ id: "P48",
2298
+ name: "Currency Insulation",
2299
+ category: "open_economy",
2300
+ description: "Gameplay economy correlation with external markets > 0.5 = insulation failure. When your in-game gold price tracks ETH/USD, external market crashes destroy in-game economies. Good design insulates the two.",
2301
+ check(metrics, thresholds) {
2302
+ const { currencyInsulation } = metrics;
2303
+ if (isNaN(currencyInsulation)) return { violated: false };
2304
+ if (currencyInsulation > thresholds.currencyInsulationMax) {
2305
+ return {
2306
+ violated: true,
2307
+ severity: 6,
2308
+ evidence: { currencyInsulation, threshold: thresholds.currencyInsulationMax },
2309
+ suggestedAction: {
2310
+ parameter: "auctionFee",
2311
+ direction: "increase",
2312
+ magnitude: 0.1,
2313
+ reasoning: `Currency correlation with external market: ${(currencyInsulation * 100).toFixed(0)}% (max: ${(thresholds.currencyInsulationMax * 100).toFixed(0)}%). Economy is exposed to external market shocks. Increase internal friction to reduce external correlation.`
2314
+ },
2315
+ confidence: 0.7,
2316
+ estimatedLag: 30
2317
+ };
2318
+ }
2319
+ return { violated: false };
2320
+ }
2321
+ };
2322
+ var OPEN_ECONOMY_PRINCIPLES = [
2323
+ P34_ExtractionRatio,
2324
+ P47_SmokeTest,
2325
+ P48_CurrencyInsulation
2326
+ ];
2327
+
2328
+ // src/principles/liveops.ts
2329
+ var P51_SharkTooth = {
2330
+ id: "P51",
2331
+ name: "Shark Tooth Pattern",
2332
+ category: "liveops",
2333
+ description: "Each event peak should be \u226595% of the previous peak. If peaks are shrinking (shark tooth becoming flat), event fatigue is setting in. If valleys are deepening, the off-event economy is failing to sustain engagement.",
2334
+ check(metrics, thresholds) {
2335
+ const { sharkToothPeaks, sharkToothValleys } = metrics;
2336
+ if (sharkToothPeaks.length < 2) return { violated: false };
2337
+ const lastPeak = sharkToothPeaks[sharkToothPeaks.length - 1] ?? 0;
2338
+ const prevPeak = sharkToothPeaks[sharkToothPeaks.length - 2] ?? 0;
2339
+ if (prevPeak > 0 && lastPeak / prevPeak < thresholds.sharkToothPeakDecay) {
2340
+ return {
2341
+ violated: true,
2342
+ severity: 5,
2343
+ evidence: {
2344
+ lastPeak,
2345
+ prevPeak,
2346
+ ratio: lastPeak / prevPeak,
2347
+ threshold: thresholds.sharkToothPeakDecay
2348
+ },
2349
+ suggestedAction: {
2350
+ parameter: "arenaReward",
2351
+ direction: "increase",
2352
+ magnitude: 0.1,
2353
+ reasoning: `Peak engagement dropped to ${(lastPeak / prevPeak * 100).toFixed(0)}% of previous peak (threshold: ${(thresholds.sharkToothPeakDecay * 100).toFixed(0)}%). Event fatigue detected. Boost event rewards to restore peak engagement.`
2354
+ },
2355
+ confidence: 0.75,
2356
+ estimatedLag: 30
2357
+ };
2358
+ }
2359
+ if (sharkToothValleys.length >= 2) {
2360
+ const lastValley = sharkToothValleys[sharkToothValleys.length - 1] ?? 0;
2361
+ const prevValley = sharkToothValleys[sharkToothValleys.length - 2] ?? 0;
2362
+ if (prevValley > 0 && lastValley / prevValley < thresholds.sharkToothValleyDecay) {
2363
+ return {
2364
+ violated: true,
2365
+ severity: 4,
2366
+ evidence: { lastValley, prevValley, ratio: lastValley / prevValley },
2367
+ suggestedAction: {
2368
+ parameter: "craftingCost",
2369
+ direction: "decrease",
2370
+ magnitude: 0.1,
2371
+ reasoning: "Between-event engagement declining (deepening valleys). Base economy not sustaining participants between events. Lower production costs to improve off-event value."
2372
+ },
2373
+ confidence: 0.65,
2374
+ estimatedLag: 20
2375
+ };
2376
+ }
2377
+ }
2378
+ return { violated: false };
2379
+ }
2380
+ };
2381
+ var P52_EndowmentEffect = {
2382
+ id: "P52",
2383
+ name: "Endowment Effect",
2384
+ category: "liveops",
2385
+ description: "Players who never owned premium items do not value them. Free trial events that let players experience premium items drive conversions because ownership creates perceived value (endowment effect).",
2386
+ check(metrics, _thresholds) {
2387
+ const { avgSatisfaction, churnRate } = metrics;
2388
+ const { eventCompletionRate } = metrics;
2389
+ if (isNaN(eventCompletionRate)) return { violated: false };
2390
+ if (eventCompletionRate > 0.9 && avgSatisfaction < 60) {
2391
+ return {
2392
+ violated: true,
2393
+ severity: 4,
2394
+ evidence: { eventCompletionRate, avgSatisfaction, churnRate },
2395
+ suggestedAction: {
2396
+ parameter: "arenaReward",
2397
+ direction: "increase",
2398
+ magnitude: 0.15,
2399
+ reasoning: `${(eventCompletionRate * 100).toFixed(0)}% event completion but satisfaction only ${avgSatisfaction.toFixed(0)}. Events not creating perceived value. Increase reward quality/quantity.`
2400
+ },
2401
+ confidence: 0.6,
2402
+ estimatedLag: 20
2403
+ };
2404
+ }
2405
+ return { violated: false };
2406
+ }
2407
+ };
2408
+ var P53_EventCompletionRate = {
2409
+ id: "P53",
2410
+ name: "Event Completion Rate Sweet Spot",
2411
+ category: "liveops",
2412
+ description: "Free completion at 60-80% is the sweet spot. <40% = predatory design. >80% = no monetization pressure. 100% free = zero reason to ever spend.",
2413
+ check(metrics, thresholds) {
2414
+ const { eventCompletionRate } = metrics;
2415
+ if (isNaN(eventCompletionRate)) return { violated: false };
2416
+ if (eventCompletionRate < thresholds.eventCompletionMin) {
2417
+ return {
2418
+ violated: true,
2419
+ severity: 6,
2420
+ evidence: {
2421
+ eventCompletionRate,
2422
+ min: thresholds.eventCompletionMin,
2423
+ max: thresholds.eventCompletionMax
2424
+ },
2425
+ suggestedAction: {
2426
+ parameter: "craftingCost",
2427
+ direction: "decrease",
2428
+ magnitude: 0.15,
2429
+ reasoning: `Event completion rate ${(eventCompletionRate * 100).toFixed(0)}% \u2014 predatory territory (min: ${(thresholds.eventCompletionMin * 100).toFixed(0)}%). Too hard for free players. Lower barriers to participation.`
2430
+ },
2431
+ confidence: 0.8,
2432
+ estimatedLag: 10
2433
+ };
2434
+ }
2435
+ if (eventCompletionRate > thresholds.eventCompletionMax) {
2436
+ return {
2437
+ violated: true,
2438
+ severity: 3,
2439
+ evidence: { eventCompletionRate, max: thresholds.eventCompletionMax },
2440
+ suggestedAction: {
2441
+ parameter: "arenaEntryFee",
2442
+ direction: "increase",
2443
+ magnitude: 0.05,
2444
+ reasoning: `Event completion rate ${(eventCompletionRate * 100).toFixed(0)}% \u2014 no monetization pressure (max: ${(thresholds.eventCompletionMax * 100).toFixed(0)}%). Slightly raise costs to create meaningful premium differentiation.`
2445
+ },
2446
+ confidence: 0.55,
2447
+ estimatedLag: 10
2448
+ };
2449
+ }
2450
+ return { violated: false };
2451
+ }
2452
+ };
2453
+ var P54_LiveOpsCadence = {
2454
+ id: "P54",
2455
+ name: "LiveOps Cadence",
2456
+ category: "liveops",
2457
+ description: ">50% of events that are re-wrapped existing content \u2192 staleness. The cadence must include genuinely new content at regular intervals. This is an advisory principle \u2014 AgentE can flag but cannot fix content.",
2458
+ check(metrics, _thresholds) {
2459
+ const { velocity, avgSatisfaction } = metrics;
2460
+ if (velocity < 2 && avgSatisfaction < 55 && metrics.tick > 100) {
2461
+ return {
2462
+ violated: true,
2463
+ severity: 3,
2464
+ evidence: { velocity, avgSatisfaction, tick: metrics.tick },
2465
+ suggestedAction: {
2466
+ parameter: "arenaReward",
2467
+ direction: "increase",
2468
+ magnitude: 0.1,
2469
+ reasoning: "Low velocity and satisfaction after long runtime. Possible content staleness. Increase rewards as bridge while new content is developed (developer action required)."
2470
+ },
2471
+ confidence: 0.4,
2472
+ estimatedLag: 30
2473
+ };
2474
+ }
2475
+ return { violated: false };
2476
+ }
2477
+ };
2478
+ var P56_ContentDropShock = {
2479
+ id: "P56",
2480
+ name: "Content-Drop Shock",
2481
+ category: "liveops",
2482
+ description: "Every new-item injection shatters existing price equilibria \u2014 arbitrage spikes as participants re-price. Build cooldown windows for price discovery before measuring post-drop economic health.",
2483
+ check(metrics, thresholds) {
2484
+ const { contentDropAge, arbitrageIndex } = metrics;
2485
+ if (contentDropAge > 0 && contentDropAge <= thresholds.contentDropCooldownTicks) {
2486
+ if (arbitrageIndex > thresholds.postDropArbitrageMax) {
2487
+ return {
2488
+ violated: true,
2489
+ severity: 5,
2490
+ evidence: {
2491
+ contentDropAge,
2492
+ arbitrageIndex,
2493
+ cooldownTicks: thresholds.contentDropCooldownTicks,
2494
+ postDropMax: thresholds.postDropArbitrageMax
2495
+ },
2496
+ suggestedAction: {
2497
+ parameter: "auctionFee",
2498
+ direction: "decrease",
2499
+ magnitude: 0.1,
2500
+ reasoning: `Content drop ${contentDropAge} ticks ago \u2014 arbitrage at ${arbitrageIndex.toFixed(2)} exceeds post-drop max (${thresholds.postDropArbitrageMax}). Price discovery struggling. Lower trading friction temporarily.`
2501
+ },
2502
+ confidence: 0.7,
2503
+ estimatedLag: 5
2504
+ };
2505
+ }
2506
+ }
2507
+ return { violated: false };
2508
+ }
2509
+ };
2510
+ var LIVEOPS_PRINCIPLES = [
2511
+ P51_SharkTooth,
2512
+ P52_EndowmentEffect,
2513
+ P53_EventCompletionRate,
2514
+ P54_LiveOpsCadence,
2515
+ P56_ContentDropShock
2516
+ ];
2517
+
2518
+ // src/principles/index.ts
2519
+ var ALL_PRINCIPLES = [
2520
+ ...SUPPLY_CHAIN_PRINCIPLES,
2521
+ // P1-P4, P60
2522
+ ...INCENTIVE_PRINCIPLES,
2523
+ // P5-P8
2524
+ ...POPULATION_PRINCIPLES,
2525
+ // P9-P11, P46
2526
+ ...CURRENCY_FLOW_PRINCIPLES,
2527
+ // P12-P16, P32, P58
2528
+ ...BOOTSTRAP_PRINCIPLES,
2529
+ // P17-P19
2530
+ ...FEEDBACK_LOOP_PRINCIPLES,
2531
+ // P20-P24
2532
+ ...REGULATOR_PRINCIPLES,
2533
+ // P25-P28, P38
2534
+ ...MARKET_DYNAMICS_PRINCIPLES,
2535
+ // P29-P30, P57
2536
+ ...MEASUREMENT_PRINCIPLES,
2537
+ // P31, P41, P55, P59
2538
+ ...STATISTICAL_PRINCIPLES,
2539
+ // P42-P43
2540
+ ...SYSTEM_DYNAMICS_PRINCIPLES,
2541
+ // P39, P44
2542
+ ...RESOURCE_MGMT_PRINCIPLES,
2543
+ // P35, P40, P49
2544
+ ...PLAYER_EXPERIENCE_PRINCIPLES,
2545
+ // P33, P36, P37, P45, P50
2546
+ ...OPEN_ECONOMY_PRINCIPLES,
2547
+ // P34, P47-P48
2548
+ ...LIVEOPS_PRINCIPLES
2549
+ // P51-P54, P56
2550
+ ];
2551
+
2552
+ // src/Simulator.ts
2553
+ var Simulator = class {
2554
+ constructor() {
2555
+ this.diagnoser = new Diagnoser(ALL_PRINCIPLES);
2556
+ }
2557
+ /**
2558
+ * Simulate the effect of applying `action` to the current economy forward `forwardTicks`.
2559
+ * Runs `iterations` Monte Carlo trials and returns the outcome distribution.
2560
+ *
2561
+ * The inner model is intentionally lightweight — it models how key metrics evolve
2562
+ * under a parameter change using simplified dynamics. This is not a full agent simulation;
2563
+ * it is a fast inner model that catches obvious over/under-corrections.
2564
+ */
2565
+ simulate(action, currentMetrics, thresholds, iterations = 100, forwardTicks = 20) {
2566
+ const actualIterations = Math.max(thresholds.simulationMinIterations, iterations);
2567
+ const outcomes = [];
2568
+ for (let i = 0; i < actualIterations; i++) {
2569
+ const projected = this.runForward(currentMetrics, action, forwardTicks, thresholds);
2570
+ outcomes.push(projected);
2571
+ }
2572
+ const sorted = [...outcomes].sort((a, b) => a.avgSatisfaction - b.avgSatisfaction);
2573
+ const p10 = sorted[Math.floor(actualIterations * 0.1)] ?? emptyMetrics();
2574
+ const p50 = sorted[Math.floor(actualIterations * 0.5)] ?? emptyMetrics();
2575
+ const p90 = sorted[Math.floor(actualIterations * 0.9)] ?? emptyMetrics();
2576
+ const mean = this.averageMetrics(outcomes);
2577
+ const netImprovement = this.checkImprovement(currentMetrics, p50, action);
2578
+ const beforeViolations = new Set(
2579
+ this.diagnoser.diagnose(currentMetrics, thresholds).map((d) => d.principle.id)
2580
+ );
2581
+ const afterViolations = new Set(
2582
+ this.diagnoser.diagnose(p50, thresholds).map((d) => d.principle.id)
2583
+ );
2584
+ const newViolations = [...afterViolations].filter((id) => !beforeViolations.has(id));
2585
+ const noNewProblems = newViolations.length === 0;
2586
+ const satisfactions = outcomes.map((o) => o.avgSatisfaction);
2587
+ const meanSat = satisfactions.reduce((s, v) => s + v, 0) / satisfactions.length;
2588
+ const stdDev = Math.sqrt(
2589
+ satisfactions.reduce((s, v) => s + (v - meanSat) ** 2, 0) / satisfactions.length
2590
+ );
2591
+ const ci = [meanSat - 1.96 * stdDev, meanSat + 1.96 * stdDev];
2592
+ const estimatedEffectTick = currentMetrics.tick + thresholds.lagMultiplierMin * 5;
2593
+ const overshootRisk = sorted.slice(Math.floor(actualIterations * 0.8)).filter((m) => Math.abs(m.netFlow) > Math.abs(currentMetrics.netFlow) * 2).length / (actualIterations * 0.2);
2594
+ return {
2595
+ proposedAction: action,
2596
+ iterations: actualIterations,
2597
+ forwardTicks,
2598
+ outcomes: { p10, p50, p90, mean },
2599
+ netImprovement,
2600
+ noNewProblems,
2601
+ confidenceInterval: ci,
2602
+ estimatedEffectTick,
2603
+ overshootRisk: Math.min(1, overshootRisk)
2604
+ };
2605
+ }
2606
+ /**
2607
+ * Lightweight forward model: apply action then project key metrics forward.
2608
+ * Uses simplified dynamics — not a full agent replay.
2609
+ */
2610
+ runForward(metrics, action, ticks, _thresholds) {
2611
+ const multiplier = this.actionMultiplier(action);
2612
+ const noise = () => 1 + (Math.random() - 0.5) * 0.1;
2613
+ let supply = metrics.totalSupply;
2614
+ let satisfaction = metrics.avgSatisfaction;
2615
+ let gini = metrics.giniCoefficient;
2616
+ let velocity = metrics.velocity;
2617
+ let netFlow = metrics.netFlow;
2618
+ const churnRate = metrics.churnRate;
2619
+ for (let t = 0; t < ticks; t++) {
2620
+ const effectOnFlow = this.flowEffect(action, metrics) * multiplier * noise();
2621
+ netFlow = netFlow * 0.9 + effectOnFlow * 0.1;
2622
+ supply += netFlow * noise();
2623
+ supply = Math.max(0, supply);
2624
+ const satDelta = netFlow > 0 && netFlow < 20 ? 0.5 : netFlow < 0 ? -1 : 0;
2625
+ satisfaction = Math.min(100, Math.max(0, satisfaction + satDelta * noise()));
2626
+ gini = gini * 0.99 + 0.35 * 0.01 * noise();
2627
+ velocity = supply / Math.max(1, metrics.totalAgents) * 0.01 * noise();
2628
+ const agentLoss = metrics.totalAgents * churnRate * noise();
2629
+ void agentLoss;
2630
+ }
2631
+ const projected = {
2632
+ ...metrics,
2633
+ tick: metrics.tick + ticks,
2634
+ totalSupply: supply,
2635
+ netFlow,
2636
+ velocity,
2637
+ giniCoefficient: Math.max(0, Math.min(1, gini)),
2638
+ avgSatisfaction: satisfaction,
2639
+ inflationRate: metrics.totalSupply > 0 ? (supply - metrics.totalSupply) / metrics.totalSupply : 0
2640
+ };
2641
+ return projected;
2642
+ }
2643
+ actionMultiplier(action) {
2644
+ const base = action.magnitude ?? 0.1;
2645
+ return action.direction === "increase" ? 1 + base : 1 - base;
2646
+ }
2647
+ flowEffect(action, metrics) {
2648
+ const { parameter, direction } = action;
2649
+ const sign = direction === "increase" ? -1 : 1;
2650
+ if (parameter === "craftingCost" || parameter === "alchemyCost") {
2651
+ return sign * metrics.netFlow * 0.2;
2652
+ }
2653
+ if (parameter === "auctionFee") {
2654
+ return sign * metrics.velocity * 10 * 0.1;
2655
+ }
2656
+ if (parameter === "arenaEntryFee") {
2657
+ return sign * (metrics.populationByRole["Fighter"] ?? 0) * 0.5;
2658
+ }
2659
+ if (parameter === "arenaReward") {
2660
+ return -sign * (metrics.populationByRole["Fighter"] ?? 0) * 0.3;
2661
+ }
2662
+ if (parameter === "miningYield" || parameter === "lumberYield") {
2663
+ return sign * metrics.faucetVolume * 0.15;
2664
+ }
2665
+ return sign * metrics.netFlow * 0.1;
2666
+ }
2667
+ checkImprovement(before, after, action) {
2668
+ const satisfactionImproved = after.avgSatisfaction >= before.avgSatisfaction - 2;
2669
+ const flowMoreBalanced = Math.abs(after.netFlow) <= Math.abs(before.netFlow) * 1.2;
2670
+ const notWorseGini = after.giniCoefficient <= before.giniCoefficient + 0.05;
2671
+ void action;
2672
+ return satisfactionImproved && flowMoreBalanced && notWorseGini;
2673
+ }
2674
+ averageMetrics(outcomes) {
2675
+ if (outcomes.length === 0) return emptyMetrics();
2676
+ const base = { ...outcomes[0] };
2677
+ const avg = (key) => {
2678
+ const vals = outcomes.map((o) => o[key]).filter((v) => typeof v === "number" && !isNaN(v));
2679
+ return vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : 0;
2680
+ };
2681
+ return {
2682
+ ...base,
2683
+ totalSupply: avg("totalSupply"),
2684
+ netFlow: avg("netFlow"),
2685
+ velocity: avg("velocity"),
2686
+ giniCoefficient: avg("giniCoefficient"),
2687
+ avgSatisfaction: avg("avgSatisfaction"),
2688
+ inflationRate: avg("inflationRate")
2689
+ };
2690
+ }
2691
+ };
2692
+
2693
+ // src/Planner.ts
2694
+ var Planner = class {
2695
+ constructor() {
2696
+ this.lockedParams = /* @__PURE__ */ new Set();
2697
+ this.constraints = /* @__PURE__ */ new Map();
2698
+ this.cooldowns = /* @__PURE__ */ new Map();
2699
+ // param → last-applied-tick
2700
+ this.activePlanCount = 0;
2701
+ }
2702
+ lock(param) {
2703
+ this.lockedParams.add(param);
2704
+ }
2705
+ unlock(param) {
2706
+ this.lockedParams.delete(param);
2707
+ }
2708
+ constrain(param, constraint) {
2709
+ this.constraints.set(param, constraint);
2710
+ }
2711
+ /**
2712
+ * Convert a diagnosis into an ActionPlan.
2713
+ * Returns null if:
2714
+ * - parameter is locked
2715
+ * - parameter is still in cooldown
2716
+ * - simulation result failed
2717
+ * - complexity budget exceeded
2718
+ */
2719
+ plan(diagnosis, metrics, simulationResult, currentParams, thresholds) {
2720
+ const action = diagnosis.violation.suggestedAction;
2721
+ const param = action.parameter;
2722
+ if (this.lockedParams.has(param)) return null;
2723
+ if (this.isOnCooldown(param, metrics.tick, thresholds.cooldownTicks)) return null;
2724
+ if (!simulationResult.netImprovement) return null;
2725
+ if (!simulationResult.noNewProblems) return null;
2726
+ if (this.activePlanCount >= thresholds.complexityBudgetMax) return null;
2727
+ const currentValue = currentParams[param] ?? 1;
2728
+ const magnitude = Math.min(action.magnitude ?? 0.1, thresholds.maxAdjustmentPercent);
2729
+ let targetValue;
2730
+ if (action.direction === "set" && action.absoluteValue !== void 0) {
2731
+ targetValue = action.absoluteValue;
2732
+ } else if (action.direction === "increase") {
2733
+ targetValue = currentValue * (1 + magnitude);
2734
+ } else {
2735
+ targetValue = currentValue * (1 - magnitude);
2736
+ }
2737
+ const constraint = this.constraints.get(param);
2738
+ if (constraint) {
2739
+ targetValue = Math.max(constraint.min, Math.min(constraint.max, targetValue));
2740
+ }
2741
+ if (Math.abs(targetValue - currentValue) < 1e-3) return null;
2742
+ const estimatedLag = diagnosis.violation.estimatedLag ?? simulationResult.estimatedEffectTick - metrics.tick;
2743
+ const plan = {
2744
+ id: `plan_${metrics.tick}_${param}`,
2745
+ diagnosis,
2746
+ parameter: param,
2747
+ currentValue,
2748
+ targetValue,
2749
+ maxChangePercent: thresholds.maxAdjustmentPercent,
2750
+ cooldownTicks: thresholds.cooldownTicks,
2751
+ rollbackCondition: {
2752
+ metric: "avgSatisfaction",
2753
+ direction: "below",
2754
+ threshold: Math.max(20, metrics.avgSatisfaction - 10),
2755
+ // rollback if sat drops >10 pts
2756
+ checkAfterTick: metrics.tick + estimatedLag + 3
2757
+ },
2758
+ simulationResult,
2759
+ estimatedLag
2760
+ };
2761
+ return plan;
2762
+ }
2763
+ recordApplied(plan, tick) {
2764
+ this.cooldowns.set(plan.parameter, tick);
2765
+ this.activePlanCount++;
2766
+ }
2767
+ recordRolledBack(_plan) {
2768
+ this.activePlanCount = Math.max(0, this.activePlanCount - 1);
2769
+ }
2770
+ isOnCooldown(param, currentTick, cooldownTicks) {
2771
+ const lastApplied = this.cooldowns.get(param);
2772
+ if (lastApplied === void 0) return false;
2773
+ return currentTick - lastApplied < cooldownTicks;
2774
+ }
2775
+ /** Reset all cooldowns (useful for testing) */
2776
+ resetCooldowns() {
2777
+ this.cooldowns.clear();
2778
+ }
2779
+ };
2780
+
2781
+ // src/Executor.ts
2782
+ var Executor = class {
2783
+ constructor() {
2784
+ this.activePlans = [];
2785
+ }
2786
+ async apply(plan, adapter, currentParams) {
2787
+ const originalValue = currentParams[plan.parameter] ?? plan.currentValue;
2788
+ await adapter.setParam(plan.parameter, plan.targetValue);
2789
+ plan.appliedAt = plan.diagnosis.tick;
2790
+ this.activePlans.push({ plan, originalValue });
2791
+ }
2792
+ /**
2793
+ * Check all active plans for rollback conditions.
2794
+ * Called every tick after metrics are computed.
2795
+ * Returns list of plans that were rolled back.
2796
+ */
2797
+ async checkRollbacks(metrics, adapter) {
2798
+ const rolledBack = [];
2799
+ const remaining = [];
2800
+ for (const active of this.activePlans) {
2801
+ const { plan, originalValue } = active;
2802
+ const rc = plan.rollbackCondition;
2803
+ if (metrics.tick < rc.checkAfterTick) {
2804
+ remaining.push(active);
2805
+ continue;
2806
+ }
2807
+ const metricValue = this.getMetricValue(metrics, rc.metric);
2808
+ const shouldRollback = rc.direction === "below" ? metricValue < rc.threshold : metricValue > rc.threshold;
2809
+ if (shouldRollback) {
2810
+ await adapter.setParam(plan.parameter, originalValue);
2811
+ rolledBack.push(plan);
2812
+ } else {
2813
+ const settledTick = rc.checkAfterTick + 10;
2814
+ if (metrics.tick > settledTick) {
2815
+ } else {
2816
+ remaining.push(active);
2817
+ }
2818
+ }
2819
+ }
2820
+ this.activePlans = remaining;
2821
+ return rolledBack;
2822
+ }
2823
+ getMetricValue(metrics, metricPath) {
2824
+ const parts = metricPath.split(".");
2825
+ let value = metrics;
2826
+ for (const part of parts) {
2827
+ if (value !== null && typeof value === "object") {
2828
+ value = value[part];
2829
+ } else {
2830
+ return NaN;
2831
+ }
2832
+ }
2833
+ return typeof value === "number" ? value : NaN;
2834
+ }
2835
+ getActivePlans() {
2836
+ return this.activePlans.map((a) => a.plan);
2837
+ }
2838
+ };
2839
+
2840
+ // src/DecisionLog.ts
2841
+ var DecisionLog = class {
2842
+ constructor(maxEntries = 1e3) {
2843
+ this.entries = [];
2844
+ this.maxEntries = maxEntries;
2845
+ }
2846
+ record(diagnosis, plan, result, metrics) {
2847
+ const entry = {
2848
+ id: `decision_${metrics.tick}_${plan.parameter}`,
2849
+ tick: metrics.tick,
2850
+ timestamp: Date.now(),
2851
+ diagnosis,
2852
+ plan,
2853
+ result,
2854
+ reasoning: this.buildReasoning(diagnosis, plan, result),
2855
+ metricsSnapshot: metrics
2856
+ };
2857
+ this.entries.unshift(entry);
2858
+ if (this.entries.length > this.maxEntries) {
2859
+ this.entries.pop();
2860
+ }
2861
+ return entry;
2862
+ }
2863
+ recordSkip(diagnosis, result, metrics, reason) {
2864
+ const entry = {
2865
+ id: `skip_${metrics.tick}_${diagnosis.principle.id}`,
2866
+ tick: metrics.tick,
2867
+ timestamp: Date.now(),
2868
+ diagnosis,
2869
+ plan: this.stubPlan(diagnosis, metrics),
2870
+ result,
2871
+ reasoning: reason,
2872
+ metricsSnapshot: metrics
2873
+ };
2874
+ this.entries.unshift(entry);
2875
+ if (this.entries.length > this.maxEntries) this.entries.pop();
2876
+ }
2877
+ query(filter) {
2878
+ return this.entries.filter((e) => {
2879
+ if (filter?.since !== void 0 && e.tick < filter.since) return false;
2880
+ if (filter?.until !== void 0 && e.tick > filter.until) return false;
2881
+ if (filter?.issue && e.diagnosis.principle.id !== filter.issue) return false;
2882
+ if (filter?.parameter && e.plan.parameter !== filter.parameter) return false;
2883
+ if (filter?.result && e.result !== filter.result) return false;
2884
+ return true;
2885
+ });
2886
+ }
2887
+ latest(n = 30) {
2888
+ return this.entries.slice(0, n);
2889
+ }
2890
+ export(format = "json") {
2891
+ if (format === "text") {
2892
+ return this.entries.map((e) => `[Tick ${e.tick}] ${e.result.toUpperCase()} \u2014 ${e.reasoning}`).join("\n");
2893
+ }
2894
+ return JSON.stringify(this.entries, null, 2);
2895
+ }
2896
+ buildReasoning(diagnosis, plan, result) {
2897
+ const sim = plan.simulationResult;
2898
+ const simSummary = `Simulation (${sim.iterations} iterations, ${sim.forwardTicks} ticks forward): p50 satisfaction ${sim.outcomes.p50.avgSatisfaction.toFixed(0)}, net improvement ${sim.netImprovement}, no new problems ${sim.noNewProblems}, overshoot risk ${(sim.overshootRisk * 100).toFixed(0)}%.`;
2899
+ const actionSummary = `[${diagnosis.principle.id}] ${diagnosis.principle.name}: ${plan.parameter} ${plan.currentValue.toFixed(3)} \u2192 ${plan.targetValue.toFixed(3)}. Severity ${diagnosis.violation.severity}/10, confidence ${(diagnosis.violation.confidence * 100).toFixed(0)}%.`;
2900
+ if (result === "applied") {
2901
+ return `${actionSummary} ${simSummary} Expected effect in ${plan.estimatedLag} ticks.`;
2902
+ }
2903
+ return `${actionSummary} Skipped (${result}). ${simSummary}`;
2904
+ }
2905
+ stubPlan(diagnosis, metrics) {
2906
+ const action = diagnosis.violation.suggestedAction;
2907
+ return {
2908
+ id: `stub_${metrics.tick}`,
2909
+ diagnosis,
2910
+ parameter: action.parameter,
2911
+ currentValue: 1,
2912
+ targetValue: 1,
2913
+ maxChangePercent: 0,
2914
+ cooldownTicks: 0,
2915
+ rollbackCondition: {
2916
+ metric: "avgSatisfaction",
2917
+ direction: "below",
2918
+ threshold: 0,
2919
+ checkAfterTick: 0
2920
+ },
2921
+ simulationResult: {
2922
+ proposedAction: action,
2923
+ iterations: 0,
2924
+ forwardTicks: 0,
2925
+ outcomes: {
2926
+ p10: metrics,
2927
+ p50: metrics,
2928
+ p90: metrics,
2929
+ mean: metrics
2930
+ },
2931
+ netImprovement: false,
2932
+ noNewProblems: true,
2933
+ confidenceInterval: [0, 0],
2934
+ estimatedEffectTick: 0,
2935
+ overshootRisk: 0
2936
+ },
2937
+ estimatedLag: 0
2938
+ };
2939
+ }
2940
+ };
2941
+
2942
+ // src/MetricStore.ts
2943
+ var RingBuffer = class {
2944
+ constructor(capacity) {
2945
+ this.capacity = capacity;
2946
+ this.head = 0;
2947
+ this.count = 0;
2948
+ this.buf = new Array(capacity);
2949
+ }
2950
+ push(item) {
2951
+ this.buf[this.head] = item;
2952
+ this.head = (this.head + 1) % this.capacity;
2953
+ if (this.count < this.capacity) this.count++;
2954
+ }
2955
+ /** All items in chronological order (oldest first) */
2956
+ toArray() {
2957
+ if (this.count === 0) return [];
2958
+ const result = [];
2959
+ const start = this.count < this.capacity ? 0 : this.head;
2960
+ for (let i = 0; i < this.count; i++) {
2961
+ result.push(this.buf[(start + i) % this.capacity]);
2962
+ }
2963
+ return result;
2964
+ }
2965
+ last() {
2966
+ if (this.count === 0) return void 0;
2967
+ const idx = (this.head - 1 + this.capacity) % this.capacity;
2968
+ return this.buf[idx];
2969
+ }
2970
+ get length() {
2971
+ return this.count;
2972
+ }
2973
+ };
2974
+ var MetricStore = class {
2975
+ constructor() {
2976
+ /** Fine: last 200 ticks, one entry per tick */
2977
+ this.fine = new RingBuffer(200);
2978
+ /** Medium: last 200 windows of 10 ticks */
2979
+ this.medium = new RingBuffer(200);
2980
+ /** Coarse: last 200 epochs of 100 ticks */
2981
+ this.coarse = new RingBuffer(200);
2982
+ this.ticksSinceLastMedium = 0;
2983
+ this.ticksSinceLastCoarse = 0;
2984
+ this.mediumAccumulator = [];
2985
+ this.coarseAccumulator = [];
2986
+ }
2987
+ record(metrics) {
2988
+ this.fine.push(metrics);
2989
+ this.mediumAccumulator.push(metrics);
2990
+ this.ticksSinceLastMedium++;
2991
+ if (this.ticksSinceLastMedium >= 10) {
2992
+ this.medium.push(this.aggregate(this.mediumAccumulator));
2993
+ this.mediumAccumulator = [];
2994
+ this.ticksSinceLastMedium = 0;
2995
+ }
2996
+ this.coarseAccumulator.push(metrics);
2997
+ this.ticksSinceLastCoarse++;
2998
+ if (this.ticksSinceLastCoarse >= 100) {
2999
+ this.coarse.push(this.aggregate(this.coarseAccumulator));
3000
+ this.coarseAccumulator = [];
3001
+ this.ticksSinceLastCoarse = 0;
3002
+ }
3003
+ }
3004
+ latest(resolution = "fine") {
3005
+ const buf = this.bufferFor(resolution);
3006
+ return buf.last() ?? emptyMetrics();
3007
+ }
3008
+ query(q) {
3009
+ const resolution = q.resolution ?? "fine";
3010
+ const buf = this.bufferFor(resolution);
3011
+ const all = buf.toArray();
3012
+ const filtered = all.filter((m) => {
3013
+ if (q.from !== void 0 && m.tick < q.from) return false;
3014
+ if (q.to !== void 0 && m.tick > q.to) return false;
3015
+ return true;
3016
+ });
3017
+ const metricKey = q.metric;
3018
+ const points = filtered.map((m) => ({
3019
+ tick: m.tick,
3020
+ value: typeof m[metricKey] === "number" ? m[metricKey] : NaN
3021
+ }));
3022
+ return { metric: q.metric, resolution, points };
3023
+ }
3024
+ /** Check if fine and coarse resolution metrics diverge significantly */
3025
+ divergenceDetected() {
3026
+ const f = this.fine.last();
3027
+ const c = this.coarse.last();
3028
+ if (!f || !c) return false;
3029
+ const fineSat = f.avgSatisfaction;
3030
+ const coarseSat = c.avgSatisfaction;
3031
+ return Math.abs(fineSat - coarseSat) > 20;
3032
+ }
3033
+ bufferFor(resolution) {
3034
+ if (resolution === "medium") return this.medium;
3035
+ if (resolution === "coarse") return this.coarse;
3036
+ return this.fine;
3037
+ }
3038
+ aggregate(snapshots) {
3039
+ if (snapshots.length === 0) return emptyMetrics();
3040
+ const last = snapshots[snapshots.length - 1];
3041
+ const avg = (key) => {
3042
+ const vals = snapshots.map((s) => s[key]).filter((v) => !isNaN(v));
3043
+ return vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : 0;
3044
+ };
3045
+ return {
3046
+ ...last,
3047
+ totalSupply: avg("totalSupply"),
3048
+ netFlow: avg("netFlow"),
3049
+ velocity: avg("velocity"),
3050
+ inflationRate: avg("inflationRate"),
3051
+ giniCoefficient: avg("giniCoefficient"),
3052
+ medianBalance: avg("medianBalance"),
3053
+ meanBalance: avg("meanBalance"),
3054
+ top10PctShare: avg("top10PctShare"),
3055
+ meanMedianDivergence: avg("meanMedianDivergence"),
3056
+ avgSatisfaction: avg("avgSatisfaction"),
3057
+ churnRate: avg("churnRate"),
3058
+ blockedAgentCount: avg("blockedAgentCount"),
3059
+ faucetVolume: avg("faucetVolume"),
3060
+ sinkVolume: avg("sinkVolume"),
3061
+ tapSinkRatio: avg("tapSinkRatio"),
3062
+ productionIndex: avg("productionIndex"),
3063
+ capacityUsage: avg("capacityUsage"),
3064
+ anchorRatioDrift: avg("anchorRatioDrift")
3065
+ };
3066
+ }
3067
+ };
3068
+
3069
+ // src/PersonaTracker.ts
3070
+ var PersonaTracker = class {
3071
+ constructor() {
3072
+ this.agentHistory = /* @__PURE__ */ new Map();
3073
+ }
3074
+ /** Ingest a state snapshot and update agent signal history */
3075
+ update(state) {
3076
+ for (const agentId of Object.keys(state.agentBalances)) {
3077
+ const history = this.agentHistory.get(agentId) ?? [];
3078
+ const inv = state.agentInventories[agentId] ?? {};
3079
+ const uniqueItems = Object.values(inv).filter((q) => q > 0).length;
3080
+ history.push({
3081
+ transactionCount: 0,
3082
+ // would be computed from events in full impl
3083
+ netExtraction: 0,
3084
+ // gold out vs in
3085
+ uniqueItemsHeld: uniqueItems,
3086
+ holdingDuration: 1,
3087
+ spendAmount: 0,
3088
+ sessionActivity: 1,
3089
+ socialInteractions: 0
3090
+ });
3091
+ if (history.length > 50) history.shift();
3092
+ this.agentHistory.set(agentId, history);
3093
+ }
3094
+ }
3095
+ /** Classify all agents and return persona distribution */
3096
+ getDistribution() {
3097
+ const counts = {
3098
+ Gamer: 0,
3099
+ Trader: 0,
3100
+ Collector: 0,
3101
+ Speculator: 0,
3102
+ Earner: 0,
3103
+ Builder: 0,
3104
+ Social: 0,
3105
+ Whale: 0,
3106
+ Influencer: 0
3107
+ };
3108
+ let total = 0;
3109
+ for (const [, history] of this.agentHistory) {
3110
+ const persona = this.classify(history);
3111
+ counts[persona]++;
3112
+ total++;
3113
+ }
3114
+ if (total === 0) return {};
3115
+ const distribution = {};
3116
+ for (const [persona, count] of Object.entries(counts)) {
3117
+ distribution[persona] = count / total;
3118
+ }
3119
+ return distribution;
3120
+ }
3121
+ classify(history) {
3122
+ if (history.length === 0) return "Gamer";
3123
+ const avg = (key) => {
3124
+ const vals = history.map((h) => h[key]);
3125
+ return vals.reduce((s, v) => s + v, 0) / vals.length;
3126
+ };
3127
+ const txRate = avg("transactionCount");
3128
+ const extraction = avg("netExtraction");
3129
+ const uniqueItems = avg("uniqueItemsHeld");
3130
+ const spend = avg("spendAmount");
3131
+ if (spend > 1e3) return "Whale";
3132
+ if (txRate > 10) return "Trader";
3133
+ if (uniqueItems > 5 && extraction < 0) return "Collector";
3134
+ if (extraction > 100) return "Earner";
3135
+ if (extraction > 50) return "Speculator";
3136
+ return "Gamer";
3137
+ }
3138
+ };
3139
+
3140
+ // src/AgentE.ts
3141
+ var AgentE = class {
3142
+ constructor(config) {
3143
+ // ── Pipeline ──
3144
+ this.observer = new Observer();
3145
+ this.simulator = new Simulator();
3146
+ this.planner = new Planner();
3147
+ this.executor = new Executor();
3148
+ // ── State ──
3149
+ this.log = new DecisionLog();
3150
+ this.store = new MetricStore();
3151
+ this.personaTracker = new PersonaTracker();
3152
+ this.params = {};
3153
+ this.eventBuffer = [];
3154
+ this.isRunning = false;
3155
+ this.isPaused = false;
3156
+ this.currentTick = 0;
3157
+ // ── Event handlers ──
3158
+ this.handlers = /* @__PURE__ */ new Map();
3159
+ /** Access to the metric time-series store */
3160
+ this.metrics = {
3161
+ query: (q) => this.store.query(q),
3162
+ latest: (resolution) => this.store.latest(resolution)
3163
+ };
3164
+ this.mode = config.mode ?? "autonomous";
3165
+ this.config = {
3166
+ mode: this.mode,
3167
+ dominantRoles: config.dominantRoles ?? [],
3168
+ idealDistribution: config.idealDistribution ?? {},
3169
+ gracePeriod: config.gracePeriod ?? 50,
3170
+ checkInterval: config.checkInterval ?? 5,
3171
+ maxAdjustmentPercent: config.maxAdjustmentPercent ?? 0.15,
3172
+ cooldownTicks: config.cooldownTicks ?? 15
3173
+ };
3174
+ this.thresholds = {
3175
+ ...DEFAULT_THRESHOLDS,
3176
+ ...config.thresholds ?? {},
3177
+ maxAdjustmentPercent: config.maxAdjustmentPercent ?? DEFAULT_THRESHOLDS.maxAdjustmentPercent,
3178
+ cooldownTicks: config.cooldownTicks ?? DEFAULT_THRESHOLDS.cooldownTicks
3179
+ };
3180
+ this.diagnoser = new Diagnoser(ALL_PRINCIPLES);
3181
+ if (config.onDecision) this.on("decision", config.onDecision);
3182
+ if (config.onAlert) this.on("alert", config.onAlert);
3183
+ if (config.onRollback) this.on("rollback", config.onRollback);
3184
+ if (config.dominantRoles && config.dominantRoles.length > 0) {
3185
+ }
3186
+ }
3187
+ // ── Connection ──────────────────────────────────────────────────────────────
3188
+ connect(adapter) {
3189
+ this.adapter = adapter;
3190
+ if (adapter.onEvent) {
3191
+ adapter.onEvent((event) => this.eventBuffer.push(event));
3192
+ }
3193
+ return this;
3194
+ }
3195
+ start() {
3196
+ if (!this.adapter) throw new Error("[AgentE] Call .connect(adapter) before .start()");
3197
+ this.isRunning = true;
3198
+ this.isPaused = false;
3199
+ return this;
3200
+ }
3201
+ pause() {
3202
+ this.isPaused = true;
3203
+ }
3204
+ resume() {
3205
+ this.isPaused = false;
3206
+ }
3207
+ stop() {
3208
+ this.isRunning = false;
3209
+ this.isPaused = false;
3210
+ }
3211
+ // ── Main cycle (call once per tick from your game loop) ────────────────────
3212
+ async tick(state) {
3213
+ if (!this.isRunning || this.isPaused) return;
3214
+ const currentState = state ?? await Promise.resolve(this.adapter.getState());
3215
+ this.currentTick = currentState.tick;
3216
+ const events = [...this.eventBuffer];
3217
+ this.eventBuffer = [];
3218
+ const metrics = this.observer.compute(currentState, events);
3219
+ this.store.record(metrics);
3220
+ this.personaTracker.update(currentState);
3221
+ metrics.personaDistribution = this.personaTracker.getDistribution();
3222
+ const rolledBack = await this.executor.checkRollbacks(metrics, this.adapter);
3223
+ for (const plan2 of rolledBack) {
3224
+ this.planner.recordRolledBack(plan2);
3225
+ this.emit("rollback", plan2, "rollback condition triggered");
3226
+ }
3227
+ if (metrics.tick < this.config.gracePeriod) return;
3228
+ if (metrics.tick % this.config.checkInterval !== 0) return;
3229
+ const diagnoses = this.diagnoser.diagnose(metrics, this.thresholds);
3230
+ for (const diagnosis of diagnoses) {
3231
+ this.emit("alert", diagnosis);
3232
+ }
3233
+ const topDiagnosis = diagnoses[0];
3234
+ if (!topDiagnosis) return;
3235
+ const simulationResult = this.simulator.simulate(
3236
+ topDiagnosis.violation.suggestedAction,
3237
+ metrics,
3238
+ this.thresholds,
3239
+ 100,
3240
+ // always >= minimum (P43)
3241
+ 20
3242
+ );
3243
+ const plan = this.planner.plan(
3244
+ topDiagnosis,
3245
+ metrics,
3246
+ simulationResult,
3247
+ this.params,
3248
+ this.thresholds
3249
+ );
3250
+ if (!plan) {
3251
+ let reason = "skipped_cooldown";
3252
+ if (!simulationResult.netImprovement) reason = "skipped_simulation_failed";
3253
+ this.log.recordSkip(topDiagnosis, reason, metrics, `Skipped: ${reason}`);
3254
+ return;
3255
+ }
3256
+ if (this.mode === "advisor") {
3257
+ const entry2 = this.log.record(topDiagnosis, plan, "skipped_override", metrics);
3258
+ this.emit("decision", entry2);
3259
+ return;
3260
+ }
3261
+ const vetoed = this.emit("beforeAction", plan);
3262
+ if (vetoed === false) {
3263
+ this.log.recordSkip(topDiagnosis, "skipped_override", metrics, "vetoed by beforeAction hook");
3264
+ return;
3265
+ }
3266
+ await this.executor.apply(plan, this.adapter, this.params);
3267
+ this.params[plan.parameter] = plan.targetValue;
3268
+ this.planner.recordApplied(plan, metrics.tick);
3269
+ const entry = this.log.record(topDiagnosis, plan, "applied", metrics);
3270
+ this.emit("decision", entry);
3271
+ this.emit("afterAction", entry);
3272
+ }
3273
+ /** Apply a plan manually (for advisor mode) */
3274
+ async apply(plan) {
3275
+ await this.executor.apply(plan, this.adapter, this.params);
3276
+ this.params[plan.parameter] = plan.targetValue;
3277
+ this.planner.recordApplied(plan, this.currentTick);
3278
+ }
3279
+ // ── Developer API ───────────────────────────────────────────────────────────
3280
+ lock(param) {
3281
+ this.planner.lock(param);
3282
+ }
3283
+ unlock(param) {
3284
+ this.planner.unlock(param);
3285
+ }
3286
+ constrain(param, bounds) {
3287
+ this.planner.constrain(param, bounds);
3288
+ }
3289
+ addPrinciple(principle) {
3290
+ this.diagnoser.addPrinciple(principle);
3291
+ }
3292
+ removePrinciple(id) {
3293
+ this.diagnoser.removePrinciple(id);
3294
+ }
3295
+ registerCustomMetric(name, fn) {
3296
+ this.observer.registerCustomMetric(name, fn);
3297
+ }
3298
+ getDecisions(filter) {
3299
+ return this.log.query(filter);
3300
+ }
3301
+ getPrinciples() {
3302
+ return this.diagnoser.getPrinciples();
3303
+ }
3304
+ getActivePlans() {
3305
+ return this.executor.getActivePlans();
3306
+ }
3307
+ // ── Events ──────────────────────────────────────────────────────────────────
3308
+ on(event, handler) {
3309
+ const list = this.handlers.get(event) ?? [];
3310
+ list.push(handler);
3311
+ this.handlers.set(event, list);
3312
+ return this;
3313
+ }
3314
+ off(event, handler) {
3315
+ const list = this.handlers.get(event) ?? [];
3316
+ this.handlers.set(event, list.filter((h) => h !== handler));
3317
+ return this;
3318
+ }
3319
+ emit(event, ...args) {
3320
+ const list = this.handlers.get(event) ?? [];
3321
+ let result;
3322
+ for (const handler of list) {
3323
+ result = handler(...args);
3324
+ if (result === false) return false;
3325
+ }
3326
+ return result;
3327
+ }
3328
+ // ── Diagnostics ─────────────────────────────────────────────────────────────
3329
+ diagnoseNow() {
3330
+ const metrics = this.store.latest();
3331
+ return this.diagnoser.diagnose(metrics, this.thresholds);
3332
+ }
3333
+ getHealth() {
3334
+ const m = this.store.latest();
3335
+ if (m.tick === 0) return 100;
3336
+ let health = 100;
3337
+ if (m.avgSatisfaction < 65) health -= 15;
3338
+ if (m.avgSatisfaction < 50) health -= 10;
3339
+ if (m.giniCoefficient > 0.45) health -= 15;
3340
+ if (m.giniCoefficient > 0.6) health -= 10;
3341
+ if (Math.abs(m.netFlow) > 10) health -= 15;
3342
+ if (Math.abs(m.netFlow) > 20) health -= 10;
3343
+ if (m.churnRate > 0.05) health -= 15;
3344
+ return Math.max(0, Math.min(100, health));
3345
+ }
3346
+ // ── Ingest events directly (event-driven mode) ───────────────────────────
3347
+ ingest(event) {
3348
+ this.eventBuffer.push(event);
3349
+ }
3350
+ };
3351
+ // Annotate the CommonJS export names for ESM import in node:
3352
+ 0 && (module.exports = {
3353
+ ALL_PRINCIPLES,
3354
+ AgentE,
3355
+ BOOTSTRAP_PRINCIPLES,
3356
+ CURRENCY_FLOW_PRINCIPLES,
3357
+ DEFAULT_THRESHOLDS,
3358
+ DecisionLog,
3359
+ Diagnoser,
3360
+ Executor,
3361
+ FEEDBACK_LOOP_PRINCIPLES,
3362
+ INCENTIVE_PRINCIPLES,
3363
+ LIVEOPS_PRINCIPLES,
3364
+ MARKET_DYNAMICS_PRINCIPLES,
3365
+ MEASUREMENT_PRINCIPLES,
3366
+ MetricStore,
3367
+ OPEN_ECONOMY_PRINCIPLES,
3368
+ Observer,
3369
+ P10_SpawnWeightingUsesInversePopulation,
3370
+ P11_TwoTierPressure,
3371
+ P12_OnePrimaryFaucet,
3372
+ P13_PotsAreZeroSumAndSelfRegulate,
3373
+ P14_TrackActualInjection,
3374
+ P15_PoolsNeedCapAndDecay,
3375
+ P16_WithdrawalPenaltyScales,
3376
+ P17_GracePeriodBeforeIntervention,
3377
+ P18_FirstProducerNeedsStartingInventory,
3378
+ P19_StartingSupplyExceedsDemand,
3379
+ P1_ProductionMatchesConsumption,
3380
+ P20_DecayPreventsAccumulation,
3381
+ P21_PriceFromGlobalSupply,
3382
+ P22_MarketAwarenessPreventsSurplus,
3383
+ P23_ProfitabilityFactorsFeasibility,
3384
+ P24_BlockedAgentsDecayFaster,
3385
+ P25_CorrectLeversForCorrectProblems,
3386
+ P26_ContinuousPressureBeatsThresholdCuts,
3387
+ P27_AdjustmentsNeedCooldowns,
3388
+ P28_StructuralDominanceIsNotPathological,
3389
+ P29_PinchPoint,
3390
+ P2_ClosedLoopsNeedDirectHandoff,
3391
+ P30_MovingPinchPoint,
3392
+ P31_AnchorValueTracking,
3393
+ P32_VelocityAboveSupply,
3394
+ P33_FairNotEqual,
3395
+ P34_ExtractionRatio,
3396
+ P35_DestructionCreatesValue,
3397
+ P36_MechanicFrictionDetector,
3398
+ P37_LatecommerProblem,
3399
+ P38_CommunicationPreventsRevolt,
3400
+ P39_TheLagPrinciple,
3401
+ P3_BootstrapCapitalCoversFirstTransaction,
3402
+ P40_ReplacementRate,
3403
+ P41_MultiResolutionMonitoring,
3404
+ P42_TheMedianPrinciple,
3405
+ P43_SimulationMinimum,
3406
+ P44_ComplexityBudget,
3407
+ P45_TimeBudget,
3408
+ P46_PersonaDiversity,
3409
+ P47_SmokeTest,
3410
+ P48_CurrencyInsulation,
3411
+ P49_IdleAssetTax,
3412
+ P4_MaterialsFlowFasterThanCooldown,
3413
+ P50_PayPowerRatio,
3414
+ P51_SharkTooth,
3415
+ P52_EndowmentEffect,
3416
+ P53_EventCompletionRate,
3417
+ P54_LiveOpsCadence,
3418
+ P55_ArbitrageThermometer,
3419
+ P56_ContentDropShock,
3420
+ P57_CombinatorialPriceSpace,
3421
+ P58_NoNaturalNumeraire,
3422
+ P59_GiftEconomyNoise,
3423
+ P5_ProfitabilityIsCompetitive,
3424
+ P60_SurplusDisposalAsymmetry,
3425
+ P6_CrowdingMultiplierOnAllRoles,
3426
+ P7_NonSpecialistsSubsidiseSpecialists,
3427
+ P8_RegulatorCannotFightDesign,
3428
+ P9_RoleSwitchingNeedsFriction,
3429
+ PERSONA_HEALTHY_RANGES,
3430
+ PLAYER_EXPERIENCE_PRINCIPLES,
3431
+ POPULATION_PRINCIPLES,
3432
+ PersonaTracker,
3433
+ Planner,
3434
+ REGULATOR_PRINCIPLES,
3435
+ RESOURCE_MGMT_PRINCIPLES,
3436
+ STATISTICAL_PRINCIPLES,
3437
+ SUPPLY_CHAIN_PRINCIPLES,
3438
+ SYSTEM_DYNAMICS_PRINCIPLES,
3439
+ Simulator,
3440
+ emptyMetrics
3441
+ });
3442
+ //# sourceMappingURL=index.js.map