@agent-e/core 1.5.0 → 1.5.2

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 CHANGED
@@ -36,7 +36,7 @@ __export(index_exports, {
36
36
  OPEN_ECONOMY_PRINCIPLES: () => OPEN_ECONOMY_PRINCIPLES,
37
37
  OPERATIONS_PRINCIPLES: () => OPERATIONS_PRINCIPLES,
38
38
  Observer: () => Observer,
39
- P10_SpawnWeightingUsesInversePopulation: () => P10_SpawnWeightingUsesInversePopulation,
39
+ P10_EntryWeightingUsesInversePopulation: () => P10_EntryWeightingUsesInversePopulation,
40
40
  P11_TwoTierPressure: () => P11_TwoTierPressure,
41
41
  P12_OnePrimaryFaucet: () => P12_OnePrimaryFaucet,
42
42
  P13_PotsAreZeroSumAndSelfRegulate: () => P13_PotsAreZeroSumAndSelfRegulate,
@@ -56,9 +56,9 @@ __export(index_exports, {
56
56
  P26_ContinuousPressureBeatsThresholdCuts: () => P26_ContinuousPressureBeatsThresholdCuts,
57
57
  P27_AdjustmentsNeedCooldowns: () => P27_AdjustmentsNeedCooldowns,
58
58
  P28_StructuralDominanceIsNotPathological: () => P28_StructuralDominanceIsNotPathological,
59
- P29_PinchPoint: () => P29_PinchPoint,
59
+ P29_BottleneckDetection: () => P29_BottleneckDetection,
60
60
  P2_ClosedLoopsNeedDirectHandoff: () => P2_ClosedLoopsNeedDirectHandoff,
61
- P30_MovingPinchPoint: () => P30_MovingPinchPoint,
61
+ P30_DynamicBottleneckRotation: () => P30_DynamicBottleneckRotation,
62
62
  P31_AnchorValueTracking: () => P31_AnchorValueTracking,
63
63
  P32_VelocityAboveSupply: () => P32_VelocityAboveSupply,
64
64
  P33_FairNotEqual: () => P33_FairNotEqual,
@@ -81,16 +81,16 @@ __export(index_exports, {
81
81
  P49_IdleAssetTax: () => P49_IdleAssetTax,
82
82
  P4_MaterialsFlowFasterThanCooldown: () => P4_MaterialsFlowFasterThanCooldown,
83
83
  P50_PayPowerRatio: () => P50_PayPowerRatio,
84
- P51_SharkTooth: () => P51_SharkTooth,
84
+ P51_CyclicalEngagement: () => P51_CyclicalEngagement,
85
85
  P52_EndowmentEffect: () => P52_EndowmentEffect,
86
86
  P53_EventCompletionRate: () => P53_EventCompletionRate,
87
- P54_LiveOpsCadence: () => P54_LiveOpsCadence,
87
+ P54_OperationalCadence: () => P54_OperationalCadence,
88
88
  P55_ArbitrageThermometer: () => P55_ArbitrageThermometer,
89
- P56_ContentDropShock: () => P56_ContentDropShock,
89
+ P56_SupplyShockAbsorption: () => P56_SupplyShockAbsorption,
90
90
  P57_CombinatorialPriceSpace: () => P57_CombinatorialPriceSpace,
91
91
  P58_NoNaturalNumeraire: () => P58_NoNaturalNumeraire,
92
92
  P59_GiftEconomyNoise: () => P59_GiftEconomyNoise,
93
- P5_ProfitabilityIsCompetitive: () => P5_ProfitabilityIsCompetitive,
93
+ P5_ProfitabilityIsRelative: () => P5_ProfitabilityIsRelative,
94
94
  P60_SurplusDisposalAsymmetry: () => P60_SurplusDisposalAsymmetry,
95
95
  P6_CrowdingMultiplierOnAllRoles: () => P6_CrowdingMultiplierOnAllRoles,
96
96
  P7_NonSpecialistsSubsidiseSpecialists: () => P7_NonSpecialistsSubsidiseSpecialists,
@@ -109,6 +109,7 @@ __export(index_exports, {
109
109
  SYSTEM_DYNAMICS_PRINCIPLES: () => SYSTEM_DYNAMICS_PRINCIPLES,
110
110
  Simulator: () => Simulator,
111
111
  emptyMetrics: () => emptyMetrics,
112
+ findWorstSystem: () => findWorstSystem,
112
113
  validateEconomyState: () => validateEconomyState
113
114
  });
114
115
  module.exports = __toCommonJS(index_exports);
@@ -147,7 +148,7 @@ var DEFAULT_THRESHOLDS = {
147
148
  cooldownTicks: 15,
148
149
  // Currency (P13)
149
150
  poolWinRate: 0.65,
150
- poolHouseCut: 0.1,
151
+ poolOperatorShare: 0.1,
151
152
  // Population balance (P9)
152
153
  roleSwitchFrictionMax: 0.05,
153
154
  // >5% of population switching in one period = herd
@@ -208,6 +209,12 @@ var Observer = class {
208
209
  this.customMetricFns[name] = fn;
209
210
  }
210
211
  compute(state, recentEvents) {
212
+ if (!state.currencies || state.currencies.length === 0) {
213
+ console.warn("[AgentE] Warning: state.currencies is empty. Metrics will be zeroed.");
214
+ }
215
+ if (!state.agentBalances || Object.keys(state.agentBalances).length === 0) {
216
+ console.warn("[AgentE] Warning: state.agentBalances is empty.");
217
+ }
211
218
  const tick = state.tick;
212
219
  const roles = Object.values(state.agentRoles);
213
220
  const totalAgents = Object.keys(state.agentBalances).length;
@@ -255,7 +262,7 @@ var Observer = class {
255
262
  if (!actorsBySystem[e.system]) actorsBySystem[e.system] = /* @__PURE__ */ new Set();
256
263
  actorsBySystem[e.system].add(e.actor);
257
264
  const amt = e.amount ?? 0;
258
- if (e.type === "mint" || e.type === "enter") {
265
+ if (e.type === "mint") {
259
266
  flowBySystem[e.system] = (flowBySystem[e.system] ?? 0) + amt;
260
267
  } else if (e.type === "burn" || e.type === "consume") {
261
268
  flowBySystem[e.system] = (flowBySystem[e.system] ?? 0) - amt;
@@ -263,7 +270,7 @@ var Observer = class {
263
270
  }
264
271
  if (e.sourceOrSink) {
265
272
  const amt = e.amount ?? 0;
266
- if (e.type === "mint" || e.type === "enter") {
273
+ if (e.type === "mint") {
267
274
  flowBySource[e.sourceOrSink] = (flowBySource[e.sourceOrSink] ?? 0) + amt;
268
275
  } else if (e.type === "burn" || e.type === "consume") {
269
276
  flowBySink[e.sourceOrSink] = (flowBySink[e.sourceOrSink] ?? 0) + amt;
@@ -302,7 +309,7 @@ var Observer = class {
302
309
  const faucet = faucetVolumeByCurrency[curr] ?? 0;
303
310
  const sink = sinkVolumeByCurrency[curr] ?? 0;
304
311
  netFlowByCurrency[curr] = faucet - sink;
305
- tapSinkRatioByCurrency[curr] = sink > 0 ? faucet / sink : faucet > 0 ? Infinity : 1;
312
+ tapSinkRatioByCurrency[curr] = sink > 0 ? Math.min(faucet / sink, 100) : faucet > 0 ? 100 : 1;
306
313
  const prevSupply = this.previousMetrics?.totalSupplyByCurrency?.[curr] ?? totalSupplyByCurrency[curr] ?? 0;
307
314
  const currSupply = totalSupplyByCurrency[curr] ?? 0;
308
315
  inflationRateByCurrency[curr] = prevSupply > 0 ? (currSupply - prevSupply) / prevSupply : 0;
@@ -337,7 +344,7 @@ var Observer = class {
337
344
  const faucetVolume = Object.values(faucetVolumeByCurrency).reduce((s, v) => s + v, 0);
338
345
  const sinkVolume = Object.values(sinkVolumeByCurrency).reduce((s, v) => s + v, 0);
339
346
  const netFlow = faucetVolume - sinkVolume;
340
- const tapSinkRatio = sinkVolume > 0 ? faucetVolume / sinkVolume : faucetVolume > 0 ? Infinity : 1;
347
+ const tapSinkRatio = sinkVolume > 0 ? Math.min(faucetVolume / sinkVolume, 100) : faucetVolume > 0 ? 100 : 1;
341
348
  const velocity = totalSupply > 0 ? tradeEvents.length / totalSupply : 0;
342
349
  const prevTotalSupply = this.previousMetrics?.totalSupply ?? totalSupply;
343
350
  const inflationRate = prevTotalSupply > 0 ? (totalSupply - prevTotalSupply) / prevTotalSupply : 0;
@@ -375,7 +382,9 @@ var Observer = class {
375
382
  const pVals = Object.values(resourcePrices);
376
383
  priceIndexByCurrency[curr] = pVals.length > 0 ? pVals.reduce((s, p) => s + p, 0) / pVals.length : 0;
377
384
  }
378
- this.previousPricesByCurrency = JSON.parse(JSON.stringify(pricesByCurrency));
385
+ this.previousPricesByCurrency = Object.fromEntries(
386
+ Object.entries(pricesByCurrency).map(([c, p]) => [c, { ...p }])
387
+ );
379
388
  const prices = pricesByCurrency[defaultCurrency] ?? {};
380
389
  const priceVolatility = priceVolatilityByCurrency[defaultCurrency] ?? {};
381
390
  const priceIndex = priceIndexByCurrency[defaultCurrency] ?? 0;
@@ -395,9 +404,9 @@ var Observer = class {
395
404
  for (const resource of /* @__PURE__ */ new Set([...Object.keys(supplyByResource), ...Object.keys(demandSignals)])) {
396
405
  const s = supplyByResource[resource] ?? 0;
397
406
  const d = demandSignals[resource] ?? 0;
398
- if (d > 2 && s / d < 0.5) {
407
+ if (d > 0 && d > 2 && s / d < 0.5) {
399
408
  pinchPoints[resource] = "scarce";
400
- } else if (s > 3 && d > 0 && s / d > 3) {
409
+ } else if (d > 0 && s > 3 && s / d > 3) {
401
410
  pinchPoints[resource] = "oversupplied";
402
411
  } else {
403
412
  pinchPoints[resource] = "optimal";
@@ -443,21 +452,11 @@ var Observer = class {
443
452
  const arbitrageIndexByCurrency = {};
444
453
  for (const curr of currencies) {
445
454
  const cPrices = pricesByCurrency[curr] ?? {};
446
- const priceKeys = Object.keys(cPrices).filter((k) => cPrices[k] > 0);
447
- if (priceKeys.length >= 2) {
448
- let pairCount = 0;
449
- let totalDivergence = 0;
450
- for (let i = 0; i < priceKeys.length; i++) {
451
- for (let j = i + 1; j < priceKeys.length; j++) {
452
- const pA = cPrices[priceKeys[i]];
453
- const pB = cPrices[priceKeys[j]];
454
- let ratio = pA / pB;
455
- ratio = Math.max(1e-3, Math.min(1e3, ratio));
456
- totalDivergence += Math.abs(Math.log(ratio));
457
- pairCount++;
458
- }
459
- }
460
- arbitrageIndexByCurrency[curr] = pairCount > 0 ? Math.min(1, totalDivergence / pairCount) : 0;
455
+ const logPrices = Object.values(cPrices).filter((p) => p > 0).map((p) => Math.log(p));
456
+ if (logPrices.length >= 2) {
457
+ const mean = logPrices.reduce((s, v) => s + v, 0) / logPrices.length;
458
+ const variance = logPrices.reduce((s, v) => s + (v - mean) ** 2, 0) / logPrices.length;
459
+ arbitrageIndexByCurrency[curr] = Math.min(1, Math.sqrt(variance));
461
460
  } else {
462
461
  arbitrageIndexByCurrency[curr] = 0;
463
462
  }
@@ -544,10 +543,10 @@ var Observer = class {
544
543
  prices,
545
544
  priceVolatility,
546
545
  poolSizes: poolSizesAggregate,
547
- extractionRatio: NaN,
548
- newUserDependency: NaN,
549
- smokeTestRatio: NaN,
550
- currencyInsulation: NaN,
546
+ extractionRatio: 0,
547
+ newUserDependency: 0,
548
+ smokeTestRatio: 0,
549
+ currencyInsulation: 0,
551
550
  arbitrageIndex,
552
551
  giftTradeRatio,
553
552
  disposalTradeRatio,
@@ -569,8 +568,11 @@ var Observer = class {
569
568
  timeToValue,
570
569
  sharkToothPeaks: this.previousMetrics?.sharkToothPeaks ?? [],
571
570
  sharkToothValleys: this.previousMetrics?.sharkToothValleys ?? [],
572
- eventCompletionRate: NaN,
571
+ eventCompletionRate: 0,
573
572
  contentDropAge,
573
+ systems: state.systems ?? [],
574
+ sources: state.sources ?? [],
575
+ sinks: state.sinks ?? [],
574
576
  flowBySystem,
575
577
  activityBySystem,
576
578
  participantsBySystem,
@@ -598,7 +600,7 @@ function computeGini(sorted) {
598
600
  for (let i = 0; i < n; i++) {
599
601
  numerator += (2 * (i + 1) - n - 1) * (sorted[i] ?? 0);
600
602
  }
601
- return Math.abs(numerator) / (n * sum);
603
+ return Math.min(1, Math.abs(numerator) / (n * sum));
602
604
  }
603
605
 
604
606
  // src/Diagnoser.ts
@@ -694,10 +696,10 @@ function emptyMetrics(tick = 0) {
694
696
  prices: {},
695
697
  priceVolatility: {},
696
698
  poolSizes: {},
697
- extractionRatio: NaN,
698
- newUserDependency: NaN,
699
- smokeTestRatio: NaN,
700
- currencyInsulation: NaN,
699
+ extractionRatio: 0,
700
+ newUserDependency: 0,
701
+ smokeTestRatio: 0,
702
+ currencyInsulation: 0,
701
703
  arbitrageIndex: 0,
702
704
  giftTradeRatio: 0,
703
705
  disposalTradeRatio: 0,
@@ -718,8 +720,11 @@ function emptyMetrics(tick = 0) {
718
720
  timeToValue: 0,
719
721
  sharkToothPeaks: [],
720
722
  sharkToothValleys: [],
721
- eventCompletionRate: NaN,
723
+ eventCompletionRate: 0,
722
724
  contentDropAge: 0,
725
+ systems: [],
726
+ sources: [],
727
+ sinks: [],
723
728
  flowBySystem: {},
724
729
  activityBySystem: {},
725
730
  participantsBySystem: {},
@@ -920,9 +925,9 @@ var SUPPLY_CHAIN_PRINCIPLES = [
920
925
  ];
921
926
 
922
927
  // src/principles/incentives.ts
923
- var P5_ProfitabilityIsCompetitive = {
928
+ var P5_ProfitabilityIsRelative = {
924
929
  id: "P5",
925
- name: "Profitability Is Competitive, Not Absolute",
930
+ name: "Profitability Is Relative, Not Absolute",
926
931
  category: "incentive",
927
932
  description: "Any profitability formula that returns the same number regardless of how many agents are already in that role will cause stampedes. 97 intermediaries happened because profit = transactions \xD7 10 with no competition denominator.",
928
933
  check(metrics, thresholds) {
@@ -986,7 +991,7 @@ var P7_NonSpecialistsSubsidiseSpecialists = {
986
991
  id: "P7",
987
992
  name: "Non-Specialists Subsidise Specialists in Zero-Sum Games",
988
993
  category: "incentive",
989
- description: "In zero-sum pools (competitive pool, 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.",
994
+ description: "In zero-sum pools (staking, prize pools, etc.), 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.",
990
995
  check(metrics, _thresholds) {
991
996
  const { poolSizes } = metrics;
992
997
  for (const [poolName, poolSize] of Object.entries(poolSizes)) {
@@ -1022,7 +1027,7 @@ var P8_RegulatorCannotFightDesign = {
1022
1027
  id: "P8",
1023
1028
  name: "Regulator Cannot Fight the Design",
1024
1029
  category: "incentive",
1025
- description: "If the economy is designed to have a majority role (e.g. dominant role exceeds 55%), the regulator must know this and exempt that role from population suppression. AgentE at tick 1 seeing dominant role exceeds 55% and slashing competitive pool rewards is overreach.",
1030
+ description: "If the economy is designed to have a majority role (e.g. dominant role exceeds 55%), the regulator must know this and exempt that role from population suppression. AgentE at tick 1 seeing dominant role exceeds 55% and slashing pool rewards is overreach.",
1026
1031
  check(metrics, _thresholds) {
1027
1032
  const { roleShares, avgSatisfaction } = metrics;
1028
1033
  if (avgSatisfaction < 45) {
@@ -1047,7 +1052,7 @@ var P8_RegulatorCannotFightDesign = {
1047
1052
  }
1048
1053
  };
1049
1054
  var INCENTIVE_PRINCIPLES = [
1050
- P5_ProfitabilityIsCompetitive,
1055
+ P5_ProfitabilityIsRelative,
1051
1056
  P6_CrowdingMultiplierOnAllRoles,
1052
1057
  P7_NonSpecialistsSubsidiseSpecialists,
1053
1058
  P8_RegulatorCannotFightDesign
@@ -1058,7 +1063,7 @@ var P9_RoleSwitchingNeedsFriction = {
1058
1063
  id: "P9",
1059
1064
  name: "Role Switching Needs Friction",
1060
1065
  category: "population",
1061
- 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.",
1066
+ 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, minimum interval), one good tick causes mass migration.",
1062
1067
  check(metrics, thresholds) {
1063
1068
  const { churnByRole, roleShares } = metrics;
1064
1069
  const totalChurn = Object.values(churnByRole).reduce((s, v) => s + v, 0);
@@ -1081,7 +1086,7 @@ var P9_RoleSwitchingNeedsFriction = {
1081
1086
  return { violated: false };
1082
1087
  }
1083
1088
  };
1084
- var P10_SpawnWeightingUsesInversePopulation = {
1089
+ var P10_EntryWeightingUsesInversePopulation = {
1085
1090
  id: "P10",
1086
1091
  name: "Entry Weighting Uses Inverse Population",
1087
1092
  category: "population",
@@ -1187,7 +1192,7 @@ var P46_PersonaDiversity = {
1187
1192
  };
1188
1193
  var POPULATION_PRINCIPLES = [
1189
1194
  P9_RoleSwitchingNeedsFriction,
1190
- P10_SpawnWeightingUsesInversePopulation,
1195
+ P10_EntryWeightingUsesInversePopulation,
1191
1196
  P11_TwoTierPressure,
1192
1197
  P46_PersonaDiversity
1193
1198
  ];
@@ -1243,7 +1248,7 @@ var P13_PotsAreZeroSumAndSelfRegulate = {
1243
1248
  id: "P13",
1244
1249
  name: "Pots Self-Regulate with Correct Multiplier",
1245
1250
  category: "currency",
1246
- description: "Competitive 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.",
1251
+ description: "Pool math: winRate \xD7 multiplier > (1 - operatorShare) drains the pot. At 65% win rate, multiplier must be \u2264 1.38. We use 1.5 for slight surplus buffer.",
1247
1252
  check(metrics, thresholds) {
1248
1253
  const { populationByRole } = metrics;
1249
1254
  const roleEntries = Object.entries(populationByRole).sort((a, b) => b[1] - a[1]);
@@ -1252,8 +1257,8 @@ var P13_PotsAreZeroSumAndSelfRegulate = {
1252
1257
  for (const curr of metrics.currencies) {
1253
1258
  const poolSize = currencyAmounts[curr] ?? 0;
1254
1259
  if (dominantCount > 5 && poolSize < 50) {
1255
- const { poolWinRate, poolHouseCut } = thresholds;
1256
- const maxSustainableMultiplier = (1 - poolHouseCut) / poolWinRate;
1260
+ const { poolWinRate, poolOperatorShare } = thresholds;
1261
+ const maxSustainableMultiplier = (1 - poolOperatorShare) / poolWinRate;
1257
1262
  return {
1258
1263
  violated: true,
1259
1264
  severity: 7,
@@ -1278,7 +1283,7 @@ var P14_TrackActualInjection = {
1278
1283
  id: "P14",
1279
1284
  name: "Track Actual Currency Injection, Not Value Creation",
1280
1285
  category: "currency",
1281
- description: 'Counting resource gathering as "currency injected" is misleading. Currency enters through faucet mechanisms (entering, rewards). Fake metrics break every downstream decision.',
1286
+ description: 'Counting resource extraction as "currency injected" is misleading. Currency enters through faucet mechanisms (entering, rewards). Fake metrics break every downstream decision.',
1282
1287
  check(metrics, _thresholds) {
1283
1288
  for (const curr of metrics.currencies) {
1284
1289
  const faucetVolume = metrics.faucetVolumeByCurrency[curr] ?? 0;
@@ -1536,7 +1541,7 @@ var P19_StartingSupplyExceedsDemand = {
1536
1541
  parameterType: "reward",
1537
1542
  direction: "increase",
1538
1543
  magnitude: 0.2,
1539
- reasoning: `${mostPopulatedRole} (${population} agents) has insufficient resources (${resourcesPerAgent.toFixed(2)} per agent). Cold-start scarcity. Boost competitive pool reward to attract participation despite scarcity.`
1544
+ reasoning: `${mostPopulatedRole} (${population} agents) has insufficient resources (${resourcesPerAgent.toFixed(2)} per agent). Cold-start scarcity. Boost pool reward to attract participation despite scarcity.`
1540
1545
  },
1541
1546
  confidence: 0.75,
1542
1547
  estimatedLag: 5
@@ -1866,7 +1871,7 @@ var REGULATOR_PRINCIPLES = [
1866
1871
  ];
1867
1872
 
1868
1873
  // src/principles/market-dynamics.ts
1869
- var P29_PinchPoint = {
1874
+ var P29_BottleneckDetection = {
1870
1875
  id: "P29",
1871
1876
  name: "Bottleneck Detection",
1872
1877
  category: "market_dynamics",
@@ -1911,7 +1916,7 @@ var P29_PinchPoint = {
1911
1916
  return { violated: false };
1912
1917
  }
1913
1918
  };
1914
- var P30_MovingPinchPoint = {
1919
+ var P30_DynamicBottleneckRotation = {
1915
1920
  id: "P30",
1916
1921
  name: "Dynamic Bottleneck Rotation",
1917
1922
  category: "market_dynamics",
@@ -1986,8 +1991,8 @@ var P57_CombinatorialPriceSpace = {
1986
1991
  }
1987
1992
  };
1988
1993
  var MARKET_DYNAMICS_PRINCIPLES = [
1989
- P29_PinchPoint,
1990
- P30_MovingPinchPoint,
1994
+ P29_BottleneckDetection,
1995
+ P30_DynamicBottleneckRotation,
1991
1996
  P57_CombinatorialPriceSpace
1992
1997
  ];
1993
1998
 
@@ -2281,7 +2286,7 @@ var P35_DestructionCreatesValue = {
2281
2286
  scope: { tags: ["entry"] },
2282
2287
  direction: "decrease",
2283
2288
  magnitude: 0.1,
2284
- reasoning: `${resource} supply at ${supply} units with low destruction (sink ${sinkVolume}/t). Resources not being consumed. Lower competitive pool entry to increase resource usage.`
2289
+ reasoning: `${resource} supply at ${supply} units with low destruction (sink ${sinkVolume}/t). Resources not being consumed. Lower pool entry to increase resource usage.`
2285
2290
  },
2286
2291
  confidence: 0.7,
2287
2292
  estimatedLag: 5
@@ -2295,7 +2300,7 @@ var P40_ReplacementRate = {
2295
2300
  id: "P40",
2296
2301
  name: "Replacement Rate \u2265 2\xD7 Consumption",
2297
2302
  category: "resource",
2298
- 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.",
2303
+ description: "Replacement/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.",
2299
2304
  check(metrics, thresholds) {
2300
2305
  const { productionIndex, sinkVolume } = metrics;
2301
2306
  if (sinkVolume > 0 && productionIndex > 0) {
@@ -2662,7 +2667,7 @@ var OPEN_ECONOMY_PRINCIPLES = [
2662
2667
  ];
2663
2668
 
2664
2669
  // src/principles/operations.ts
2665
- var P51_SharkTooth = {
2670
+ var P51_CyclicalEngagement = {
2666
2671
  id: "P51",
2667
2672
  name: "Cyclical Engagement Pattern",
2668
2673
  category: "operations",
@@ -2718,7 +2723,7 @@ var P52_EndowmentEffect = {
2718
2723
  id: "P52",
2719
2724
  name: "Endowment Effect",
2720
2725
  category: "operations",
2721
- description: "Participants who never owned premium items do not value them. Free trial activities that let participants experience premium items drive conversions because ownership creates perceived value (endowment effect).",
2726
+ description: "Participants who never owned premium assets do not value them. Free trial activities that let participants experience premium assets drive conversions because ownership creates perceived value (endowment effect).",
2722
2727
  check(metrics, _thresholds) {
2723
2728
  const { avgSatisfaction, churnRate } = metrics;
2724
2729
  const { eventCompletionRate } = metrics;
@@ -2787,11 +2792,11 @@ var P53_EventCompletionRate = {
2787
2792
  return { violated: false };
2788
2793
  }
2789
2794
  };
2790
- var P54_LiveOpsCadence = {
2795
+ var P54_OperationalCadence = {
2791
2796
  id: "P54",
2792
2797
  name: "Operational Cadence",
2793
2798
  category: "operations",
2794
- description: ">50% of activities 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.",
2799
+ description: ">50% of activities that are re-wrapped existing supply \u2192 stagnation. The cadence must include genuinely new supply at regular intervals. This is an advisory principle \u2014 AgentE can flag but cannot fix supply.",
2795
2800
  check(metrics, _thresholds) {
2796
2801
  const { velocity, avgSatisfaction } = metrics;
2797
2802
  if (velocity < 2 && avgSatisfaction < 55 && metrics.tick > 100) {
@@ -2803,7 +2808,7 @@ var P54_LiveOpsCadence = {
2803
2808
  parameterType: "reward",
2804
2809
  direction: "increase",
2805
2810
  magnitude: 0.1,
2806
- reasoning: "Low velocity and satisfaction after long runtime. Possible content staleness. Increase rewards as bridge while new content is developed (developer action required)."
2811
+ reasoning: "Low velocity and satisfaction after long runtime. Possible supply stagnation. Increase rewards as bridge while new supply is developed (developer action required)."
2807
2812
  },
2808
2813
  confidence: 0.4,
2809
2814
  estimatedLag: 30
@@ -2812,11 +2817,11 @@ var P54_LiveOpsCadence = {
2812
2817
  return { violated: false };
2813
2818
  }
2814
2819
  };
2815
- var P56_ContentDropShock = {
2820
+ var P56_SupplyShockAbsorption = {
2816
2821
  id: "P56",
2817
2822
  name: "Supply Shock Absorption",
2818
2823
  category: "operations",
2819
- 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.",
2824
+ description: "Every new-item injection shatters existing price equilibria \u2014 arbitrage spikes as participants re-price. Build stabilization windows for price discovery before measuring post-injection economic health.",
2820
2825
  check(metrics, thresholds) {
2821
2826
  const { contentDropAge, arbitrageIndex } = metrics;
2822
2827
  if (contentDropAge > 0 && contentDropAge <= thresholds.contentDropCooldownTicks) {
@@ -2835,7 +2840,7 @@ var P56_ContentDropShock = {
2835
2840
  scope: { tags: ["transaction"] },
2836
2841
  direction: "decrease",
2837
2842
  magnitude: 0.1,
2838
- 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.`
2843
+ reasoning: `Supply injection ${contentDropAge} ticks ago \u2014 arbitrage at ${arbitrageIndex.toFixed(2)} exceeds post-injection max (${thresholds.postDropArbitrageMax}). Price discovery struggling. Lower trading friction temporarily.`
2839
2844
  },
2840
2845
  confidence: 0.6,
2841
2846
  estimatedLag: 5
@@ -2846,11 +2851,11 @@ var P56_ContentDropShock = {
2846
2851
  }
2847
2852
  };
2848
2853
  var OPERATIONS_PRINCIPLES = [
2849
- P51_SharkTooth,
2854
+ P51_CyclicalEngagement,
2850
2855
  P52_EndowmentEffect,
2851
2856
  P53_EventCompletionRate,
2852
- P54_LiveOpsCadence,
2853
- P56_ContentDropShock
2857
+ P54_OperationalCadence,
2858
+ P56_SupplyShockAbsorption
2854
2859
  ];
2855
2860
 
2856
2861
  // src/principles/index.ts
@@ -2888,15 +2893,24 @@ var ALL_PRINCIPLES = [
2888
2893
  ];
2889
2894
 
2890
2895
  // src/Simulator.ts
2896
+ var DEFAULT_SIM_CONFIG = {
2897
+ sinkMultiplier: 0.2,
2898
+ faucetMultiplier: 0.15,
2899
+ frictionMultiplier: 0.1,
2900
+ frictionVelocityScale: 10,
2901
+ redistributionMultiplier: 0.3,
2902
+ neutralMultiplier: 0.05,
2903
+ minIterations: 100,
2904
+ maxProjectionTicks: 20
2905
+ };
2891
2906
  var Simulator = class {
2892
- constructor(registry) {
2907
+ constructor(registry, simConfig) {
2893
2908
  this.diagnoser = new Diagnoser(ALL_PRINCIPLES);
2894
- // Cache beforeViolations for the *current* tick only (one entry max).
2895
- // Using a Map here is intentional but the cache must be bounded — we only
2896
- // care about the tick that is currently being evaluated, so we evict any
2897
- // entries whose key differs from the incoming tick.
2898
- this.beforeViolationsCache = /* @__PURE__ */ new Map();
2909
+ // Cache beforeViolations for the *current* tick only.
2910
+ this.cachedViolationsTick = -1;
2911
+ this.cachedViolations = /* @__PURE__ */ new Set();
2899
2912
  this.registry = registry;
2913
+ this.simConfig = { ...DEFAULT_SIM_CONFIG, ...simConfig };
2900
2914
  }
2901
2915
  /**
2902
2916
  * Simulate the effect of applying `action` to the current economy forward `forwardTicks`.
@@ -2920,16 +2934,13 @@ var Simulator = class {
2920
2934
  const mean = this.averageMetrics(outcomes);
2921
2935
  const netImprovement = this.checkImprovement(currentMetrics, p50, action);
2922
2936
  const tick = currentMetrics.tick;
2923
- if (this.beforeViolationsCache.size > 0 && !this.beforeViolationsCache.has(tick)) {
2924
- this.beforeViolationsCache.clear();
2925
- }
2926
- let beforeViolations = this.beforeViolationsCache.get(tick);
2927
- if (!beforeViolations) {
2928
- beforeViolations = new Set(
2937
+ if (this.cachedViolationsTick !== tick) {
2938
+ this.cachedViolations = new Set(
2929
2939
  this.diagnoser.diagnose(currentMetrics, thresholds).map((d) => d.principle.id)
2930
2940
  );
2931
- this.beforeViolationsCache.set(tick, beforeViolations);
2941
+ this.cachedViolationsTick = tick;
2932
2942
  }
2943
+ const beforeViolations = this.cachedViolations;
2933
2944
  const afterViolations = new Set(
2934
2945
  this.diagnoser.diagnose(p50, thresholds).map((d) => d.principle.id)
2935
2946
  );
@@ -3009,25 +3020,32 @@ var Simulator = class {
3009
3020
  const sign = direction === "increase" ? -1 : 1;
3010
3021
  const roleEntries = Object.entries(metrics.populationByRole).sort((a, b) => b[1] - a[1]);
3011
3022
  const dominantRoleCount = roleEntries[0]?.[1] ?? 0;
3012
- const resolvedKey = action.resolvedParameter;
3013
3023
  let impact;
3014
- if (resolvedKey && this.registry) {
3015
- impact = this.registry.getFlowImpact(resolvedKey);
3024
+ if (this.registry) {
3025
+ const resolved = this.registry.resolve(action.parameterType, action.scope);
3026
+ if (resolved) {
3027
+ impact = resolved.flowImpact;
3028
+ }
3016
3029
  }
3017
3030
  if (!impact) {
3018
3031
  impact = this.inferFlowImpact(action.parameterType);
3019
3032
  }
3033
+ const cfg = this.simConfig;
3020
3034
  switch (impact) {
3021
3035
  case "sink":
3022
- return sign * (metrics.netFlowByCurrency[currency] ?? 0) * 0.2;
3036
+ return sign * (metrics.netFlowByCurrency[currency] ?? 0) * cfg.sinkMultiplier;
3023
3037
  case "faucet":
3024
- return -sign * dominantRoleCount * 0.3;
3038
+ return -sign * dominantRoleCount * cfg.redistributionMultiplier;
3025
3039
  case "neutral":
3026
- return sign * dominantRoleCount * 0.5;
3040
+ return sign * dominantRoleCount * cfg.neutralMultiplier;
3027
3041
  case "mixed":
3028
- return sign * (metrics.faucetVolumeByCurrency[currency] ?? 0) * 0.15;
3042
+ return sign * (metrics.faucetVolumeByCurrency[currency] ?? 0) * cfg.faucetMultiplier;
3043
+ case "friction":
3044
+ return sign * (metrics.netFlowByCurrency[currency] ?? 0) * cfg.frictionMultiplier;
3045
+ case "redistribution":
3046
+ return sign * dominantRoleCount * cfg.neutralMultiplier;
3029
3047
  default:
3030
- return sign * (metrics.netFlowByCurrency[currency] ?? 0) * 0.1;
3048
+ return sign * (metrics.netFlowByCurrency[currency] ?? 0) * cfg.frictionMultiplier;
3031
3049
  }
3032
3050
  }
3033
3051
  /** Infer flow impact from parameter type when registry is unavailable */
@@ -3048,7 +3066,7 @@ var Simulator = class {
3048
3066
  return "mixed";
3049
3067
  }
3050
3068
  }
3051
- checkImprovement(before, after, action) {
3069
+ checkImprovement(before, after, _action) {
3052
3070
  const satisfactionImproved = after.avgSatisfaction >= before.avgSatisfaction - 2;
3053
3071
  const flowMoreBalanced = before.currencies.every((curr) => {
3054
3072
  const afterFlow = Math.abs(after.netFlowByCurrency[curr] ?? 0);
@@ -3060,7 +3078,6 @@ var Simulator = class {
3060
3078
  const beforeGini = before.giniCoefficientByCurrency[curr] ?? 0;
3061
3079
  return afterGini <= beforeGini + 0.05;
3062
3080
  });
3063
- void action;
3064
3081
  return satisfactionImproved && flowMoreBalanced && notWorseGini;
3065
3082
  }
3066
3083
  averageMetrics(outcomes) {
@@ -3108,6 +3125,8 @@ var Planner = class {
3108
3125
  this.constraints = /* @__PURE__ */ new Map();
3109
3126
  this.cooldowns = /* @__PURE__ */ new Map();
3110
3127
  // param → last-applied-tick
3128
+ this.typeCooldowns = /* @__PURE__ */ new Map();
3129
+ // type+scope key → last-applied-tick
3111
3130
  this.activePlanCount = 0;
3112
3131
  }
3113
3132
  lock(param) {
@@ -3130,6 +3149,8 @@ var Planner = class {
3130
3149
  */
3131
3150
  plan(diagnosis, metrics, simulationResult, currentParams, thresholds, registry) {
3132
3151
  const action = diagnosis.violation.suggestedAction;
3152
+ const typeKey = this.typeCooldownKey(action.parameterType, action.scope);
3153
+ if (this.isTypeCooldown(typeKey, metrics.tick, thresholds.cooldownTicks)) return null;
3133
3154
  let param;
3134
3155
  let resolvedBaseline;
3135
3156
  let scope;
@@ -3187,6 +3208,9 @@ var Planner = class {
3187
3208
  }
3188
3209
  recordApplied(plan, tick) {
3189
3210
  this.cooldowns.set(plan.parameter, tick);
3211
+ const action = plan.diagnosis.violation.suggestedAction;
3212
+ const typeKey = this.typeCooldownKey(action.parameterType, action.scope);
3213
+ this.typeCooldowns.set(typeKey, tick);
3190
3214
  this.activePlanCount++;
3191
3215
  }
3192
3216
  recordRolledBack(_plan) {
@@ -3203,13 +3227,35 @@ var Planner = class {
3203
3227
  /** Reset all cooldowns (useful for testing) */
3204
3228
  resetCooldowns() {
3205
3229
  this.cooldowns.clear();
3230
+ this.typeCooldowns.clear();
3231
+ }
3232
+ /** V1.5.2: Reset active plan count (e.g., on system restart) */
3233
+ resetActivePlans() {
3234
+ this.activePlanCount = 0;
3235
+ }
3236
+ /** V1.5.2: Current active plan count (for diagnostics) */
3237
+ getActivePlanCount() {
3238
+ return this.activePlanCount;
3239
+ }
3240
+ typeCooldownKey(type, scope) {
3241
+ const parts = [type];
3242
+ if (scope?.system) parts.push(`sys:${scope.system}`);
3243
+ if (scope?.currency) parts.push(`cur:${scope.currency}`);
3244
+ if (scope?.tags?.length) parts.push(`tags:${scope.tags.sort().join(",")}`);
3245
+ return parts.join("|");
3246
+ }
3247
+ isTypeCooldown(typeKey, currentTick, cooldownTicks) {
3248
+ const lastApplied = this.typeCooldowns.get(typeKey);
3249
+ if (lastApplied === void 0) return false;
3250
+ return currentTick - lastApplied < cooldownTicks;
3206
3251
  }
3207
3252
  };
3208
3253
 
3209
3254
  // src/Executor.ts
3210
3255
  var Executor = class {
3211
- constructor() {
3256
+ constructor(settlementWindowTicks = 200) {
3212
3257
  this.activePlans = [];
3258
+ this.maxActiveTicks = settlementWindowTicks;
3213
3259
  }
3214
3260
  async apply(plan, adapter, currentParams) {
3215
3261
  const originalValue = currentParams[plan.parameter] ?? plan.currentValue;
@@ -3228,8 +3274,7 @@ var Executor = class {
3228
3274
  for (const active of this.activePlans) {
3229
3275
  const { plan, originalValue } = active;
3230
3276
  const rc = plan.rollbackCondition;
3231
- const maxActiveTicks = 200;
3232
- if (plan.appliedAt !== void 0 && metrics.tick - plan.appliedAt > maxActiveTicks) {
3277
+ if (plan.appliedAt !== void 0 && metrics.tick - plan.appliedAt > this.maxActiveTicks) {
3233
3278
  settled.push(plan);
3234
3279
  continue;
3235
3280
  }
@@ -3296,9 +3341,9 @@ var DecisionLog = class {
3296
3341
  reasoning: this.buildReasoning(diagnosis, plan, result),
3297
3342
  metricsSnapshot: metrics
3298
3343
  };
3299
- this.entries.unshift(entry);
3300
- if (this.entries.length > this.maxEntries) {
3301
- this.entries.pop();
3344
+ this.entries.push(entry);
3345
+ if (this.entries.length > this.maxEntries * 1.5) {
3346
+ this.entries = this.entries.slice(-this.maxEntries);
3302
3347
  }
3303
3348
  return entry;
3304
3349
  }
@@ -3313,8 +3358,10 @@ var DecisionLog = class {
3313
3358
  reasoning: reason,
3314
3359
  metricsSnapshot: metrics
3315
3360
  };
3316
- this.entries.unshift(entry);
3317
- if (this.entries.length > this.maxEntries) this.entries.pop();
3361
+ this.entries.push(entry);
3362
+ if (this.entries.length > this.maxEntries * 1.5) {
3363
+ this.entries = this.entries.slice(-this.maxEntries);
3364
+ }
3318
3365
  }
3319
3366
  query(filter) {
3320
3367
  return this.entries.filter((e) => {
@@ -3327,7 +3374,7 @@ var DecisionLog = class {
3327
3374
  });
3328
3375
  }
3329
3376
  latest(n = 30) {
3330
- return this.entries.slice(0, n);
3377
+ return this.entries.slice(-n).reverse();
3331
3378
  }
3332
3379
  export(format = "json") {
3333
3380
  if (format === "text") {
@@ -3556,10 +3603,13 @@ var MetricStore = class {
3556
3603
  var PersonaTracker = class {
3557
3604
  constructor() {
3558
3605
  this.agentHistory = /* @__PURE__ */ new Map();
3606
+ this.lastSeen = /* @__PURE__ */ new Map();
3559
3607
  }
3560
3608
  /** Ingest a state snapshot and update agent signal history */
3561
3609
  update(state) {
3610
+ const tick = state.tick;
3562
3611
  for (const agentId of Object.keys(state.agentBalances)) {
3612
+ this.lastSeen.set(agentId, tick);
3563
3613
  const history = this.agentHistory.get(agentId) ?? [];
3564
3614
  const inv = state.agentInventories[agentId] ?? {};
3565
3615
  const uniqueItems = Object.values(inv).filter((q) => q > 0).length;
@@ -3577,6 +3627,14 @@ var PersonaTracker = class {
3577
3627
  if (history.length > 50) history.shift();
3578
3628
  this.agentHistory.set(agentId, history);
3579
3629
  }
3630
+ if (tick % 50 === 0) {
3631
+ for (const [id, lastTick] of this.lastSeen) {
3632
+ if (tick - lastTick > 100) {
3633
+ this.agentHistory.delete(id);
3634
+ this.lastSeen.delete(id);
3635
+ }
3636
+ }
3637
+ }
3580
3638
  }
3581
3639
  /** Classify all agents and return persona distribution */
3582
3640
  getDistribution() {
@@ -3640,25 +3698,30 @@ var ParameterRegistry = class {
3640
3698
  * Resolve a parameterType + scope to a concrete RegisteredParameter.
3641
3699
  * Returns the best match, or undefined if no match.
3642
3700
  *
3643
- * Matching rules (in priority order):
3644
- * 1. Exact type match + all scope fields match
3645
- * 2. Exact type match + partial scope match (tags overlap)
3646
- * 3. Exact type match + no scope constraints
3647
- * 4. undefined (no match)
3701
+ * Matching rules:
3702
+ * 1. Filter candidates by type
3703
+ * 2. Score each by scope specificity (system +10, currency +5, tags +3 each)
3704
+ * 3. Mismatched scope fields disqualify (score = -Infinity)
3705
+ * 4. Ties broken by `priority` (higher wins), then registration order
3706
+ * 5. All disqualified → undefined
3648
3707
  */
3649
3708
  resolve(type, scope) {
3650
3709
  const candidates = this.findByType(type);
3651
3710
  if (candidates.length === 0) return void 0;
3652
3711
  if (candidates.length === 1) return candidates[0];
3653
- let bestScore = -1;
3712
+ let bestScore = -Infinity;
3713
+ let bestPriority = -Infinity;
3654
3714
  let best;
3655
3715
  for (const candidate of candidates) {
3656
- const score = this.scopeMatchScore(candidate.scope, scope);
3657
- if (score > bestScore) {
3716
+ const score = this.scopeSpecificity(candidate.scope, scope);
3717
+ const prio = candidate.priority ?? 0;
3718
+ if (score > bestScore || score === bestScore && prio > bestPriority) {
3658
3719
  bestScore = score;
3720
+ bestPriority = prio;
3659
3721
  best = candidate;
3660
3722
  }
3661
3723
  }
3724
+ if (bestScore === -Infinity) return void 0;
3662
3725
  return best;
3663
3726
  }
3664
3727
  /** Find all parameters of a given type. */
@@ -3700,28 +3763,60 @@ var ParameterRegistry = class {
3700
3763
  get size() {
3701
3764
  return this.parameters.size;
3702
3765
  }
3766
+ /**
3767
+ * Validate the registry for common misconfigurations.
3768
+ * Returns warnings (non-fatal) and errors (likely broken).
3769
+ */
3770
+ validate() {
3771
+ const warnings = [];
3772
+ const errors = [];
3773
+ const typeMap = /* @__PURE__ */ new Map();
3774
+ for (const param of this.parameters.values()) {
3775
+ const list = typeMap.get(param.type) ?? [];
3776
+ list.push(param);
3777
+ typeMap.set(param.type, list);
3778
+ }
3779
+ for (const [type, params] of typeMap) {
3780
+ if (params.length > 1) {
3781
+ const unscopedCount = params.filter((p) => !p.scope).length;
3782
+ if (unscopedCount > 1) {
3783
+ errors.push(
3784
+ `Type '${type}' has ${unscopedCount} unscoped parameters \u2014 resolve() cannot distinguish them`
3785
+ );
3786
+ }
3787
+ }
3788
+ }
3789
+ for (const param of this.parameters.values()) {
3790
+ if (!param.flowImpact) {
3791
+ warnings.push(`Parameter '${param.key}' has no flowImpact \u2014 Simulator will use inference`);
3792
+ }
3793
+ }
3794
+ return {
3795
+ valid: errors.length === 0,
3796
+ warnings,
3797
+ errors
3798
+ };
3799
+ }
3703
3800
  // ── Private ─────────────────────────────────────────────────────────────
3704
- scopeMatchScore(paramScope, queryScope) {
3801
+ scopeSpecificity(paramScope, queryScope) {
3705
3802
  if (!queryScope) return 0;
3706
3803
  if (!paramScope) return 0;
3707
3804
  let score = 0;
3708
3805
  if (queryScope.system && paramScope.system) {
3709
3806
  if (queryScope.system === paramScope.system) score += 10;
3710
- else return -1;
3807
+ else return -Infinity;
3711
3808
  }
3712
3809
  if (queryScope.currency && paramScope.currency) {
3713
3810
  if (queryScope.currency === paramScope.currency) score += 5;
3714
- else return -1;
3811
+ else return -Infinity;
3715
3812
  }
3716
3813
  if (queryScope.tags && queryScope.tags.length > 0 && paramScope.tags && paramScope.tags.length > 0) {
3717
3814
  const overlap = queryScope.tags.filter((t) => paramScope.tags.includes(t)).length;
3718
3815
  if (overlap > 0) {
3719
3816
  score += overlap * 3;
3720
3817
  } else {
3721
- return -1;
3818
+ return -Infinity;
3722
3819
  }
3723
- } else if (queryScope.tags && queryScope.tags.length > 0 && paramScope.tags && paramScope.tags.length > 0) {
3724
- return -1;
3725
3820
  }
3726
3821
  return score;
3727
3822
  }
@@ -3731,7 +3826,6 @@ var ParameterRegistry = class {
3731
3826
  var AgentE = class {
3732
3827
  constructor(config) {
3733
3828
  this.planner = new Planner();
3734
- this.executor = new Executor();
3735
3829
  this.registry = new ParameterRegistry();
3736
3830
  // ── State ──
3737
3831
  this.log = new DecisionLog();
@@ -3753,6 +3847,9 @@ var AgentE = class {
3753
3847
  mode: this.mode,
3754
3848
  dominantRoles: config.dominantRoles ?? [],
3755
3849
  idealDistribution: config.idealDistribution ?? {},
3850
+ validateRegistry: config.validateRegistry ?? true,
3851
+ simulation: config.simulation ?? {},
3852
+ settlementWindowTicks: config.settlementWindowTicks ?? 200,
3756
3853
  tickConfig: config.tickConfig ?? { duration: 1, unit: "tick" },
3757
3854
  gracePeriod: config.gracePeriod ?? 50,
3758
3855
  checkInterval: config.checkInterval ?? 5,
@@ -3773,7 +3870,13 @@ var AgentE = class {
3773
3870
  if (config.parameters) {
3774
3871
  this.registry.registerAll(config.parameters);
3775
3872
  }
3776
- this.simulator = new Simulator(this.registry);
3873
+ if (config.validateRegistry !== false && this.registry.size > 0) {
3874
+ const validation = this.registry.validate();
3875
+ for (const w of validation.warnings) console.warn(`[AgentE] Registry warning: ${w}`);
3876
+ for (const e of validation.errors) console.error(`[AgentE] Registry error: ${e}`);
3877
+ }
3878
+ this.executor = new Executor(config.settlementWindowTicks);
3879
+ this.simulator = new Simulator(this.registry, config.simulation);
3777
3880
  if (config.onDecision) this.on("decision", config.onDecision);
3778
3881
  if (config.onAlert) this.on("alert", config.onAlert);
3779
3882
  if (config.onRollback) this.on("rollback", config.onRollback);
@@ -3809,7 +3912,7 @@ var AgentE = class {
3809
3912
  if (!this.isRunning || this.isPaused) return;
3810
3913
  const currentState = state ?? await Promise.resolve(this.adapter.getState());
3811
3914
  this.currentTick = currentState.tick;
3812
- const events = [...this.eventBuffer];
3915
+ const events = this.eventBuffer;
3813
3916
  this.eventBuffer = [];
3814
3917
  let metrics;
3815
3918
  try {
@@ -3927,7 +4030,9 @@ var AgentE = class {
3927
4030
  // ── Events ──────────────────────────────────────────────────────────────────
3928
4031
  on(event, handler) {
3929
4032
  const list = this.handlers.get(event) ?? [];
3930
- list.push(handler);
4033
+ if (!list.includes(handler)) {
4034
+ list.push(handler);
4035
+ }
3931
4036
  this.handlers.set(event, list);
3932
4037
  return this;
3933
4038
  }
@@ -3940,8 +4045,12 @@ var AgentE = class {
3940
4045
  const list = this.handlers.get(event) ?? [];
3941
4046
  let result;
3942
4047
  for (const handler of list) {
3943
- result = handler(...args);
3944
- if (result === false) return false;
4048
+ try {
4049
+ result = handler(...args);
4050
+ if (result === false) return false;
4051
+ } catch (err) {
4052
+ console.error(`[AgentE] Handler error on '${event}':`, err);
4053
+ }
3945
4054
  }
3946
4055
  return result;
3947
4056
  }
@@ -3969,6 +4078,31 @@ var AgentE = class {
3969
4078
  }
3970
4079
  };
3971
4080
 
4081
+ // src/utils.ts
4082
+ function findWorstSystem(metrics, check, tolerancePercent = 0) {
4083
+ const systems = metrics.systems;
4084
+ if (systems.length === 0) return void 0;
4085
+ let worstSystem;
4086
+ let worstScore = -Infinity;
4087
+ let totalScore = 0;
4088
+ for (const sys of systems) {
4089
+ const score = check(sys, metrics);
4090
+ totalScore += score;
4091
+ if (score > worstScore) {
4092
+ worstScore = score;
4093
+ worstSystem = sys;
4094
+ }
4095
+ }
4096
+ if (!worstSystem) return void 0;
4097
+ if (tolerancePercent > 0 && systems.length > 1) {
4098
+ const avg = totalScore / systems.length;
4099
+ if (avg === 0) return { system: worstSystem, score: worstScore };
4100
+ const excessPercent = (worstScore - avg) / Math.abs(avg) * 100;
4101
+ if (excessPercent < tolerancePercent) return void 0;
4102
+ }
4103
+ return { system: worstSystem, score: worstScore };
4104
+ }
4105
+
3972
4106
  // src/StateValidator.ts
3973
4107
  function validateEconomyState(state) {
3974
4108
  const errors = [];
@@ -4320,7 +4454,7 @@ function describeValue(value) {
4320
4454
  OPEN_ECONOMY_PRINCIPLES,
4321
4455
  OPERATIONS_PRINCIPLES,
4322
4456
  Observer,
4323
- P10_SpawnWeightingUsesInversePopulation,
4457
+ P10_EntryWeightingUsesInversePopulation,
4324
4458
  P11_TwoTierPressure,
4325
4459
  P12_OnePrimaryFaucet,
4326
4460
  P13_PotsAreZeroSumAndSelfRegulate,
@@ -4340,9 +4474,9 @@ function describeValue(value) {
4340
4474
  P26_ContinuousPressureBeatsThresholdCuts,
4341
4475
  P27_AdjustmentsNeedCooldowns,
4342
4476
  P28_StructuralDominanceIsNotPathological,
4343
- P29_PinchPoint,
4477
+ P29_BottleneckDetection,
4344
4478
  P2_ClosedLoopsNeedDirectHandoff,
4345
- P30_MovingPinchPoint,
4479
+ P30_DynamicBottleneckRotation,
4346
4480
  P31_AnchorValueTracking,
4347
4481
  P32_VelocityAboveSupply,
4348
4482
  P33_FairNotEqual,
@@ -4365,16 +4499,16 @@ function describeValue(value) {
4365
4499
  P49_IdleAssetTax,
4366
4500
  P4_MaterialsFlowFasterThanCooldown,
4367
4501
  P50_PayPowerRatio,
4368
- P51_SharkTooth,
4502
+ P51_CyclicalEngagement,
4369
4503
  P52_EndowmentEffect,
4370
4504
  P53_EventCompletionRate,
4371
- P54_LiveOpsCadence,
4505
+ P54_OperationalCadence,
4372
4506
  P55_ArbitrageThermometer,
4373
- P56_ContentDropShock,
4507
+ P56_SupplyShockAbsorption,
4374
4508
  P57_CombinatorialPriceSpace,
4375
4509
  P58_NoNaturalNumeraire,
4376
4510
  P59_GiftEconomyNoise,
4377
- P5_ProfitabilityIsCompetitive,
4511
+ P5_ProfitabilityIsRelative,
4378
4512
  P60_SurplusDisposalAsymmetry,
4379
4513
  P6_CrowdingMultiplierOnAllRoles,
4380
4514
  P7_NonSpecialistsSubsidiseSpecialists,
@@ -4393,6 +4527,7 @@ function describeValue(value) {
4393
4527
  SYSTEM_DYNAMICS_PRINCIPLES,
4394
4528
  Simulator,
4395
4529
  emptyMetrics,
4530
+ findWorstSystem,
4396
4531
  validateEconomyState
4397
4532
  });
4398
4533
  //# sourceMappingURL=index.js.map