@agent-e/core 1.2.1 → 1.3.1

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
@@ -197,9 +197,9 @@ var DEFAULT_TICK_CONFIG = {
197
197
  var Observer = class {
198
198
  constructor(tickConfig) {
199
199
  this.previousMetrics = null;
200
- this.previousPrices = {};
200
+ this.previousPricesByCurrency = {};
201
201
  this.customMetricFns = {};
202
- this.anchorBaseline = null;
202
+ this.anchorBaselineByCurrency = {};
203
203
  this.tickConfig = { ...DEFAULT_TICK_CONFIG, ...tickConfig };
204
204
  }
205
205
  registerCustomMetric(name, fn) {
@@ -207,24 +207,25 @@ var Observer = class {
207
207
  }
208
208
  compute(state, recentEvents) {
209
209
  const tick = state.tick;
210
- const balances = Object.values(state.agentBalances);
211
210
  const roles = Object.values(state.agentRoles);
212
- const totalAgents = balances.length;
213
- let faucetVolume = 0;
214
- let sinkVolume = 0;
211
+ const totalAgents = Object.keys(state.agentBalances).length;
212
+ let productionAmount = 0;
213
+ const faucetVolumeByCurrency = {};
214
+ const sinkVolumeByCurrency = {};
215
215
  const tradeEvents = [];
216
216
  const roleChangeEvents = [];
217
217
  let churnCount = 0;
218
- let productionAmount = 0;
218
+ const defaultCurrency = state.currencies[0] ?? "default";
219
219
  for (const e of recentEvents) {
220
+ const curr = e.currency ?? defaultCurrency;
220
221
  switch (e.type) {
221
222
  case "mint":
222
223
  case "spawn":
223
- faucetVolume += e.amount ?? 0;
224
+ faucetVolumeByCurrency[curr] = (faucetVolumeByCurrency[curr] ?? 0) + (e.amount ?? 0);
224
225
  break;
225
226
  case "burn":
226
227
  case "consume":
227
- sinkVolume += e.amount ?? 0;
228
+ sinkVolumeByCurrency[curr] = (sinkVolumeByCurrency[curr] ?? 0) + (e.amount ?? 0);
228
229
  break;
229
230
  case "produce":
230
231
  productionAmount += e.amount ?? 1;
@@ -241,20 +242,68 @@ var Observer = class {
241
242
  break;
242
243
  }
243
244
  }
244
- const totalSupply = balances.reduce((s, b) => s + b, 0);
245
+ const currencies = state.currencies;
246
+ const totalSupplyByCurrency = {};
247
+ const balancesByCurrency = {};
248
+ for (const [_agentId, balances] of Object.entries(state.agentBalances)) {
249
+ for (const [curr, bal] of Object.entries(balances)) {
250
+ totalSupplyByCurrency[curr] = (totalSupplyByCurrency[curr] ?? 0) + bal;
251
+ if (!balancesByCurrency[curr]) balancesByCurrency[curr] = [];
252
+ balancesByCurrency[curr].push(bal);
253
+ }
254
+ }
255
+ const netFlowByCurrency = {};
256
+ const tapSinkRatioByCurrency = {};
257
+ const inflationRateByCurrency = {};
258
+ const velocityByCurrency = {};
259
+ for (const curr of currencies) {
260
+ const faucet = faucetVolumeByCurrency[curr] ?? 0;
261
+ const sink = sinkVolumeByCurrency[curr] ?? 0;
262
+ netFlowByCurrency[curr] = faucet - sink;
263
+ tapSinkRatioByCurrency[curr] = sink > 0 ? faucet / sink : faucet > 0 ? Infinity : 1;
264
+ const prevSupply = this.previousMetrics?.totalSupplyByCurrency?.[curr] ?? totalSupplyByCurrency[curr] ?? 0;
265
+ const currSupply = totalSupplyByCurrency[curr] ?? 0;
266
+ inflationRateByCurrency[curr] = prevSupply > 0 ? (currSupply - prevSupply) / prevSupply : 0;
267
+ const currTrades = tradeEvents.filter((e) => (e.currency ?? defaultCurrency) === curr);
268
+ velocityByCurrency[curr] = currSupply > 0 ? currTrades.length / currSupply : 0;
269
+ }
270
+ const giniCoefficientByCurrency = {};
271
+ const medianBalanceByCurrency = {};
272
+ const meanBalanceByCurrency = {};
273
+ const top10PctShareByCurrency = {};
274
+ const meanMedianDivergenceByCurrency = {};
275
+ for (const curr of currencies) {
276
+ const bals = balancesByCurrency[curr] ?? [];
277
+ const sorted = [...bals].sort((a, b) => a - b);
278
+ const supply = totalSupplyByCurrency[curr] ?? 0;
279
+ const count = sorted.length;
280
+ const median = computeMedian(sorted);
281
+ const mean = count > 0 ? supply / count : 0;
282
+ const top10Idx = Math.floor(count * 0.9);
283
+ const top10Sum = sorted.slice(top10Idx).reduce((s, b) => s + b, 0);
284
+ giniCoefficientByCurrency[curr] = computeGini(sorted);
285
+ medianBalanceByCurrency[curr] = median;
286
+ meanBalanceByCurrency[curr] = mean;
287
+ top10PctShareByCurrency[curr] = supply > 0 ? top10Sum / supply : 0;
288
+ meanMedianDivergenceByCurrency[curr] = median > 0 ? Math.abs(mean - median) / median : 0;
289
+ }
290
+ const avgOf = (rec) => {
291
+ const vals = Object.values(rec);
292
+ return vals.length > 0 ? vals.reduce((s, v) => s + v, 0) / vals.length : 0;
293
+ };
294
+ const totalSupply = Object.values(totalSupplyByCurrency).reduce((s, v) => s + v, 0);
295
+ const faucetVolume = Object.values(faucetVolumeByCurrency).reduce((s, v) => s + v, 0);
296
+ const sinkVolume = Object.values(sinkVolumeByCurrency).reduce((s, v) => s + v, 0);
245
297
  const netFlow = faucetVolume - sinkVolume;
246
298
  const tapSinkRatio = sinkVolume > 0 ? faucetVolume / sinkVolume : faucetVolume > 0 ? Infinity : 1;
247
- const prevSupply = this.previousMetrics?.totalSupply ?? totalSupply;
248
- const inflationRate = prevSupply > 0 ? (totalSupply - prevSupply) / prevSupply : 0;
249
299
  const velocity = totalSupply > 0 ? tradeEvents.length / totalSupply : 0;
250
- const sortedBalances = [...balances].sort((a, b) => a - b);
300
+ const prevTotalSupply = this.previousMetrics?.totalSupply ?? totalSupply;
301
+ const inflationRate = prevTotalSupply > 0 ? (totalSupply - prevTotalSupply) / prevTotalSupply : 0;
302
+ const giniCoefficient = avgOf(giniCoefficientByCurrency);
303
+ const medianBalance = avgOf(medianBalanceByCurrency);
251
304
  const meanBalance = totalAgents > 0 ? totalSupply / totalAgents : 0;
252
- const medianBalance = computeMedian(sortedBalances);
253
- const top10Idx = Math.floor(totalAgents * 0.9);
254
- const top10Sum = sortedBalances.slice(top10Idx).reduce((s, b) => s + b, 0);
255
- const top10PctShare = totalSupply > 0 ? top10Sum / totalSupply : 0;
256
- const giniCoefficient = computeGini(sortedBalances);
257
- const meanMedianDivergence = medianBalance > 0 ? Math.abs(meanBalance - medianBalance) / medianBalance : 0;
305
+ const top10PctShare = avgOf(top10PctShareByCurrency);
306
+ const meanMedianDivergence = avgOf(meanMedianDivergenceByCurrency);
258
307
  const populationByRole = {};
259
308
  const roleShares = {};
260
309
  for (const role of roles) {
@@ -269,15 +318,25 @@ var Observer = class {
269
318
  churnByRole[role] = (churnByRole[role] ?? 0) + 1;
270
319
  }
271
320
  const churnRate = churnCount / Math.max(1, totalAgents);
272
- const prices = { ...state.marketPrices };
273
- const priceVolatility = {};
274
- for (const [resource, price] of Object.entries(prices)) {
275
- const prev = this.previousPrices[resource] ?? price;
276
- priceVolatility[resource] = prev > 0 ? Math.abs(price - prev) / prev : 0;
277
- }
278
- this.previousPrices = { ...prices };
279
- const priceValues = Object.values(prices);
280
- const priceIndex = priceValues.length > 0 ? priceValues.reduce((s, p) => s + p, 0) / priceValues.length : 0;
321
+ const pricesByCurrency = {};
322
+ const priceVolatilityByCurrency = {};
323
+ const priceIndexByCurrency = {};
324
+ for (const [curr, resourcePrices] of Object.entries(state.marketPrices)) {
325
+ pricesByCurrency[curr] = { ...resourcePrices };
326
+ const pricePrev = this.previousPricesByCurrency?.[curr] ?? {};
327
+ const volMap = {};
328
+ for (const [resource, price] of Object.entries(resourcePrices)) {
329
+ const prev = pricePrev[resource] ?? price;
330
+ volMap[resource] = prev > 0 ? Math.abs(price - prev) / prev : 0;
331
+ }
332
+ priceVolatilityByCurrency[curr] = volMap;
333
+ const pVals = Object.values(resourcePrices);
334
+ priceIndexByCurrency[curr] = pVals.length > 0 ? pVals.reduce((s, p) => s + p, 0) / pVals.length : 0;
335
+ }
336
+ this.previousPricesByCurrency = JSON.parse(JSON.stringify(pricesByCurrency));
337
+ const prices = pricesByCurrency[defaultCurrency] ?? {};
338
+ const priceVolatility = priceVolatilityByCurrency[defaultCurrency] ?? {};
339
+ const priceIndex = priceIndexByCurrency[defaultCurrency] ?? 0;
281
340
  const supplyByResource = {};
282
341
  for (const inv of Object.values(state.agentInventories)) {
283
342
  for (const [resource, qty] of Object.entries(inv)) {
@@ -309,63 +368,85 @@ var Observer = class {
309
368
  const avgSatisfaction = satisfactions.length > 0 ? satisfactions.reduce((s, v) => s + v, 0) / satisfactions.length : 80;
310
369
  const blockedAgentCount = satisfactions.filter((s) => s < 20).length;
311
370
  const timeToValue = totalAgents > 0 ? blockedAgentCount / totalAgents * 100 : 0;
312
- const poolSizes = { ...state.poolSizes ?? {} };
313
- if (!this.anchorBaseline && tick === 1 && totalSupply > 0) {
314
- this.anchorBaseline = {
315
- currencyPerPeriod: totalSupply / Math.max(1, totalAgents),
316
- itemsPerCurrency: priceIndex > 0 ? 1 / priceIndex : 0
317
- };
371
+ const poolSizesByCurrency = {};
372
+ const poolSizesAggregate = {};
373
+ if (state.poolSizes) {
374
+ for (const [pool, currencyAmounts] of Object.entries(state.poolSizes)) {
375
+ poolSizesByCurrency[pool] = { ...currencyAmounts };
376
+ poolSizesAggregate[pool] = Object.values(currencyAmounts).reduce((s, v) => s + v, 0);
377
+ }
378
+ }
379
+ const anchorRatioDriftByCurrency = {};
380
+ if (tick === 1) {
381
+ for (const curr of currencies) {
382
+ const supply = totalSupplyByCurrency[curr] ?? 0;
383
+ if (supply > 0) {
384
+ this.anchorBaselineByCurrency[curr] = {
385
+ currencyPerPeriod: supply / Math.max(1, totalAgents),
386
+ itemsPerCurrency: (priceIndexByCurrency[curr] ?? 0) > 0 ? 1 / priceIndexByCurrency[curr] : 0
387
+ };
388
+ }
389
+ }
390
+ }
391
+ for (const curr of currencies) {
392
+ const baseline = this.anchorBaselineByCurrency[curr];
393
+ if (baseline && totalAgents > 0) {
394
+ const currentCPP = (totalSupplyByCurrency[curr] ?? 0) / totalAgents;
395
+ anchorRatioDriftByCurrency[curr] = baseline.currencyPerPeriod > 0 ? (currentCPP - baseline.currencyPerPeriod) / baseline.currencyPerPeriod : 0;
396
+ } else {
397
+ anchorRatioDriftByCurrency[curr] = 0;
398
+ }
318
399
  }
319
- let anchorRatioDrift = 0;
320
- if (this.anchorBaseline && totalAgents > 0) {
321
- const currentCurrencyPerPeriod = totalSupply / totalAgents;
322
- anchorRatioDrift = this.anchorBaseline.currencyPerPeriod > 0 ? (currentCurrencyPerPeriod - this.anchorBaseline.currencyPerPeriod) / this.anchorBaseline.currencyPerPeriod : 0;
323
- }
324
- let arbitrageIndex = 0;
325
- const priceKeys = Object.keys(prices).filter((k) => prices[k] > 0);
326
- if (priceKeys.length >= 2) {
327
- let pairCount = 0;
328
- let totalDivergence = 0;
329
- for (let i = 0; i < priceKeys.length; i++) {
330
- for (let j = i + 1; j < priceKeys.length; j++) {
331
- const pA = prices[priceKeys[i]];
332
- const pB = prices[priceKeys[j]];
333
- let ratio = pA / pB;
334
- ratio = Math.max(1e-3, Math.min(1e3, ratio));
335
- totalDivergence += Math.abs(Math.log(ratio));
336
- pairCount++;
400
+ const anchorRatioDrift = avgOf(anchorRatioDriftByCurrency);
401
+ const arbitrageIndexByCurrency = {};
402
+ for (const curr of currencies) {
403
+ const cPrices = pricesByCurrency[curr] ?? {};
404
+ const priceKeys = Object.keys(cPrices).filter((k) => cPrices[k] > 0);
405
+ if (priceKeys.length >= 2) {
406
+ let pairCount = 0;
407
+ let totalDivergence = 0;
408
+ for (let i = 0; i < priceKeys.length; i++) {
409
+ for (let j = i + 1; j < priceKeys.length; j++) {
410
+ const pA = cPrices[priceKeys[i]];
411
+ const pB = cPrices[priceKeys[j]];
412
+ let ratio = pA / pB;
413
+ ratio = Math.max(1e-3, Math.min(1e3, ratio));
414
+ totalDivergence += Math.abs(Math.log(ratio));
415
+ pairCount++;
416
+ }
337
417
  }
418
+ arbitrageIndexByCurrency[curr] = pairCount > 0 ? Math.min(1, totalDivergence / pairCount) : 0;
419
+ } else {
420
+ arbitrageIndexByCurrency[curr] = 0;
338
421
  }
339
- arbitrageIndex = pairCount > 0 ? Math.min(1, totalDivergence / pairCount) : 0;
340
422
  }
423
+ const arbitrageIndex = avgOf(arbitrageIndexByCurrency);
341
424
  const contentDropEvents = recentEvents.filter(
342
425
  (e) => e.metadata?.["contentDrop"] === true
343
426
  );
344
427
  const contentDropAge = contentDropEvents.length > 0 ? tick - Math.max(...contentDropEvents.map((e) => e.timestamp)) : (this.previousMetrics?.contentDropAge ?? 0) + 1;
345
- let giftTrades = 0;
346
- if (tradeEvents.length > 0) {
347
- for (const e of tradeEvents) {
348
- const marketPrice = prices[e.resource ?? ""] ?? 0;
428
+ const giftTradeRatioByCurrency = {};
429
+ const disposalTradeRatioByCurrency = {};
430
+ for (const curr of currencies) {
431
+ const currTrades = tradeEvents.filter((e) => (e.currency ?? defaultCurrency) === curr);
432
+ const cPrices = pricesByCurrency[curr] ?? {};
433
+ let gifts = 0;
434
+ let disposals = 0;
435
+ for (const e of currTrades) {
436
+ const marketPrice = cPrices[e.resource ?? ""] ?? 0;
349
437
  const tradePrice = e.price ?? 0;
350
- if (tradePrice === 0 || marketPrice > 0 && tradePrice < marketPrice * 0.3) {
351
- giftTrades++;
352
- }
353
- }
354
- }
355
- const giftTradeRatio = tradeEvents.length > 0 ? giftTrades / tradeEvents.length : 0;
356
- let disposalTrades = 0;
357
- if (tradeEvents.length > 0) {
358
- for (const e of tradeEvents) {
438
+ if (tradePrice === 0 || marketPrice > 0 && tradePrice < marketPrice * 0.3) gifts++;
359
439
  if (e.from && e.resource) {
360
440
  const sellerInv = state.agentInventories[e.from]?.[e.resource] ?? 0;
361
441
  const avgInv = (supplyByResource[e.resource] ?? 0) / Math.max(1, totalAgents);
362
- if (sellerInv > avgInv * 3) {
363
- disposalTrades++;
364
- }
442
+ if (sellerInv > avgInv * 3) disposals++;
365
443
  }
366
444
  }
445
+ giftTradeRatioByCurrency[curr] = currTrades.length > 0 ? gifts / currTrades.length : 0;
446
+ disposalTradeRatioByCurrency[curr] = currTrades.length > 0 ? disposals / currTrades.length : 0;
367
447
  }
368
- const disposalTradeRatio = tradeEvents.length > 0 ? disposalTrades / tradeEvents.length : 0;
448
+ const giftTradeRatio = avgOf(giftTradeRatioByCurrency);
449
+ const disposalTradeRatio = avgOf(disposalTradeRatioByCurrency);
369
450
  const custom = {};
370
451
  for (const [name, fn] of Object.entries(this.customMetricFns)) {
371
452
  try {
@@ -377,10 +458,57 @@ var Observer = class {
377
458
  const metrics = {
378
459
  tick,
379
460
  timestamp: Date.now(),
461
+ currencies,
462
+ // Per-currency
463
+ totalSupplyByCurrency,
464
+ netFlowByCurrency,
465
+ velocityByCurrency,
466
+ inflationRateByCurrency,
467
+ faucetVolumeByCurrency,
468
+ sinkVolumeByCurrency,
469
+ tapSinkRatioByCurrency,
470
+ anchorRatioDriftByCurrency,
471
+ giniCoefficientByCurrency,
472
+ medianBalanceByCurrency,
473
+ meanBalanceByCurrency,
474
+ top10PctShareByCurrency,
475
+ meanMedianDivergenceByCurrency,
476
+ priceIndexByCurrency,
477
+ pricesByCurrency,
478
+ priceVolatilityByCurrency,
479
+ poolSizesByCurrency,
480
+ extractionRatioByCurrency: {},
481
+ newUserDependencyByCurrency: {},
482
+ currencyInsulationByCurrency: {},
483
+ arbitrageIndexByCurrency,
484
+ giftTradeRatioByCurrency,
485
+ disposalTradeRatioByCurrency,
486
+ // Aggregates
380
487
  totalSupply,
381
488
  netFlow,
382
489
  velocity,
383
490
  inflationRate,
491
+ faucetVolume,
492
+ sinkVolume,
493
+ tapSinkRatio,
494
+ anchorRatioDrift,
495
+ giniCoefficient,
496
+ medianBalance,
497
+ meanBalance,
498
+ top10PctShare,
499
+ meanMedianDivergence,
500
+ priceIndex,
501
+ prices,
502
+ priceVolatility,
503
+ poolSizes: poolSizesAggregate,
504
+ extractionRatio: NaN,
505
+ newUserDependency: NaN,
506
+ smokeTestRatio: NaN,
507
+ currencyInsulation: NaN,
508
+ arbitrageIndex,
509
+ giftTradeRatio,
510
+ disposalTradeRatio,
511
+ // Unchanged
384
512
  populationByRole,
385
513
  roleShares,
386
514
  totalAgents,
@@ -388,38 +516,18 @@ var Observer = class {
388
516
  churnByRole,
389
517
  personaDistribution: {},
390
518
  // populated by PersonaTracker
391
- giniCoefficient,
392
- medianBalance,
393
- meanBalance,
394
- top10PctShare,
395
- meanMedianDivergence,
396
- priceIndex,
397
519
  productionIndex,
398
520
  capacityUsage,
399
- prices,
400
- priceVolatility,
401
521
  supplyByResource,
402
522
  demandSignals,
403
523
  pinchPoints,
404
524
  avgSatisfaction,
405
525
  blockedAgentCount,
406
526
  timeToValue,
407
- faucetVolume,
408
- sinkVolume,
409
- tapSinkRatio,
410
- poolSizes,
411
- anchorRatioDrift,
412
- extractionRatio: NaN,
413
- newUserDependency: NaN,
414
- smokeTestRatio: NaN,
415
- currencyInsulation: NaN,
416
527
  sharkToothPeaks: this.previousMetrics?.sharkToothPeaks ?? [],
417
528
  sharkToothValleys: this.previousMetrics?.sharkToothValleys ?? [],
418
529
  eventCompletionRate: NaN,
419
- arbitrageIndex,
420
530
  contentDropAge,
421
- giftTradeRatio,
422
- disposalTradeRatio,
423
531
  custom
424
532
  };
425
533
  this.previousMetrics = metrics;
@@ -493,48 +601,75 @@ function emptyMetrics(tick = 0) {
493
601
  return {
494
602
  tick,
495
603
  timestamp: Date.now(),
604
+ currencies: [],
605
+ // Per-currency
606
+ totalSupplyByCurrency: {},
607
+ netFlowByCurrency: {},
608
+ velocityByCurrency: {},
609
+ inflationRateByCurrency: {},
610
+ faucetVolumeByCurrency: {},
611
+ sinkVolumeByCurrency: {},
612
+ tapSinkRatioByCurrency: {},
613
+ anchorRatioDriftByCurrency: {},
614
+ giniCoefficientByCurrency: {},
615
+ medianBalanceByCurrency: {},
616
+ meanBalanceByCurrency: {},
617
+ top10PctShareByCurrency: {},
618
+ meanMedianDivergenceByCurrency: {},
619
+ priceIndexByCurrency: {},
620
+ pricesByCurrency: {},
621
+ priceVolatilityByCurrency: {},
622
+ poolSizesByCurrency: {},
623
+ extractionRatioByCurrency: {},
624
+ newUserDependencyByCurrency: {},
625
+ currencyInsulationByCurrency: {},
626
+ arbitrageIndexByCurrency: {},
627
+ giftTradeRatioByCurrency: {},
628
+ disposalTradeRatioByCurrency: {},
629
+ // Aggregates
496
630
  totalSupply: 0,
497
631
  netFlow: 0,
498
632
  velocity: 0,
499
633
  inflationRate: 0,
500
- populationByRole: {},
501
- roleShares: {},
502
- totalAgents: 0,
503
- churnRate: 0,
504
- churnByRole: {},
505
- personaDistribution: {},
634
+ faucetVolume: 0,
635
+ sinkVolume: 0,
636
+ tapSinkRatio: 1,
637
+ anchorRatioDrift: 0,
506
638
  giniCoefficient: 0,
507
639
  medianBalance: 0,
508
640
  meanBalance: 0,
509
641
  top10PctShare: 0,
510
642
  meanMedianDivergence: 0,
511
643
  priceIndex: 0,
512
- productionIndex: 0,
513
- capacityUsage: 0,
514
644
  prices: {},
515
645
  priceVolatility: {},
646
+ poolSizes: {},
647
+ extractionRatio: NaN,
648
+ newUserDependency: NaN,
649
+ smokeTestRatio: NaN,
650
+ currencyInsulation: NaN,
651
+ arbitrageIndex: 0,
652
+ giftTradeRatio: 0,
653
+ disposalTradeRatio: 0,
654
+ // Unchanged
655
+ populationByRole: {},
656
+ roleShares: {},
657
+ totalAgents: 0,
658
+ churnRate: 0,
659
+ churnByRole: {},
660
+ personaDistribution: {},
661
+ productionIndex: 0,
662
+ capacityUsage: 0,
516
663
  supplyByResource: {},
517
664
  demandSignals: {},
518
665
  pinchPoints: {},
519
666
  avgSatisfaction: 100,
520
667
  blockedAgentCount: 0,
521
668
  timeToValue: 0,
522
- faucetVolume: 0,
523
- sinkVolume: 0,
524
- tapSinkRatio: 1,
525
- poolSizes: {},
526
- anchorRatioDrift: 0,
527
- extractionRatio: NaN,
528
- newUserDependency: NaN,
529
- smokeTestRatio: NaN,
530
- currencyInsulation: NaN,
531
669
  sharkToothPeaks: [],
532
670
  sharkToothValleys: [],
533
671
  eventCompletionRate: NaN,
534
- arbitrageIndex: 0,
535
672
  contentDropAge: 0,
536
- giftTradeRatio: 0,
537
- disposalTradeRatio: 0,
538
673
  custom: {}
539
674
  };
540
675
  }
@@ -1002,36 +1137,42 @@ var P12_OnePrimaryFaucet = {
1002
1137
  category: "currency",
1003
1138
  description: "Multiple independent currency sources (gathering + production + quests) each creating currency causes uncontrolled inflation. One clear primary faucet makes the economy predictable and auditable.",
1004
1139
  check(metrics, thresholds) {
1005
- const { netFlow, faucetVolume, sinkVolume } = metrics;
1006
- if (netFlow > thresholds.netFlowWarnThreshold) {
1007
- return {
1008
- violated: true,
1009
- severity: 5,
1010
- evidence: { netFlow, faucetVolume, sinkVolume },
1011
- suggestedAction: {
1012
- parameter: "productionCost",
1013
- direction: "increase",
1014
- magnitude: 0.15,
1015
- reasoning: `Net flow +${netFlow.toFixed(1)}/tick. Inflationary. Increase production cost (primary sink) to balance faucet output.`
1016
- },
1017
- confidence: 0.8,
1018
- estimatedLag: 8
1019
- };
1020
- }
1021
- if (netFlow < -thresholds.netFlowWarnThreshold) {
1022
- return {
1023
- violated: true,
1024
- severity: 4,
1025
- evidence: { netFlow, faucetVolume, sinkVolume },
1026
- suggestedAction: {
1027
- parameter: "productionCost",
1028
- direction: "decrease",
1029
- magnitude: 0.15,
1030
- reasoning: `Net flow ${netFlow.toFixed(1)}/tick. Deflationary. Decrease production cost to ease sink pressure.`
1031
- },
1032
- confidence: 0.8,
1033
- estimatedLag: 8
1034
- };
1140
+ for (const curr of metrics.currencies) {
1141
+ const netFlow = metrics.netFlowByCurrency[curr] ?? 0;
1142
+ const faucetVolume = metrics.faucetVolumeByCurrency[curr] ?? 0;
1143
+ const sinkVolume = metrics.sinkVolumeByCurrency[curr] ?? 0;
1144
+ if (netFlow > thresholds.netFlowWarnThreshold) {
1145
+ return {
1146
+ violated: true,
1147
+ severity: 5,
1148
+ evidence: { currency: curr, netFlow, faucetVolume, sinkVolume },
1149
+ suggestedAction: {
1150
+ parameter: "productionCost",
1151
+ direction: "increase",
1152
+ currency: curr,
1153
+ magnitude: 0.15,
1154
+ reasoning: `[${curr}] Net flow +${netFlow.toFixed(1)}/tick. Inflationary. Increase production cost (primary sink) to balance faucet output.`
1155
+ },
1156
+ confidence: 0.8,
1157
+ estimatedLag: 8
1158
+ };
1159
+ }
1160
+ if (netFlow < -thresholds.netFlowWarnThreshold) {
1161
+ return {
1162
+ violated: true,
1163
+ severity: 4,
1164
+ evidence: { currency: curr, netFlow, faucetVolume, sinkVolume },
1165
+ suggestedAction: {
1166
+ parameter: "productionCost",
1167
+ direction: "decrease",
1168
+ currency: curr,
1169
+ magnitude: 0.15,
1170
+ reasoning: `[${curr}] Net flow ${netFlow.toFixed(1)}/tick. Deflationary. Decrease production cost to ease sink pressure.`
1171
+ },
1172
+ confidence: 0.8,
1173
+ estimatedLag: 8
1174
+ };
1175
+ }
1035
1176
  }
1036
1177
  return { violated: false };
1037
1178
  }
@@ -1042,28 +1183,30 @@ var P13_PotsAreZeroSumAndSelfRegulate = {
1042
1183
  category: "currency",
1043
1184
  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.",
1044
1185
  check(metrics, thresholds) {
1045
- const { poolSizes, populationByRole } = metrics;
1046
- const totalAgents = metrics.totalAgents;
1186
+ const { populationByRole } = metrics;
1047
1187
  const roleEntries = Object.entries(populationByRole).sort((a, b) => b[1] - a[1]);
1048
- const dominantRole = roleEntries[0]?.[0];
1049
1188
  const dominantCount = roleEntries[0]?.[1] ?? 0;
1050
- for (const [poolName, poolSize] of Object.entries(poolSizes)) {
1051
- if (dominantCount > 5 && poolSize < 50) {
1052
- const { poolWinRate, poolHouseCut } = thresholds;
1053
- const maxSustainableMultiplier = (1 - poolHouseCut) / poolWinRate;
1054
- return {
1055
- violated: true,
1056
- severity: 7,
1057
- evidence: { pool: poolName, poolSize, participants: dominantCount, maxSustainableMultiplier },
1058
- suggestedAction: {
1059
- parameter: "rewardRate",
1060
- direction: "decrease",
1061
- magnitude: 0.15,
1062
- reasoning: `${poolName} pool at ${poolSize.toFixed(0)} currency with ${dominantCount} active participants. Sustainable multiplier \u2264 ${maxSustainableMultiplier.toFixed(2)}. Reduce reward multiplier to prevent pool drain.`
1063
- },
1064
- confidence: 0.85,
1065
- estimatedLag: 3
1066
- };
1189
+ for (const [poolName, currencyAmounts] of Object.entries(metrics.poolSizesByCurrency)) {
1190
+ for (const curr of metrics.currencies) {
1191
+ const poolSize = currencyAmounts[curr] ?? 0;
1192
+ if (dominantCount > 5 && poolSize < 50) {
1193
+ const { poolWinRate, poolHouseCut } = thresholds;
1194
+ const maxSustainableMultiplier = (1 - poolHouseCut) / poolWinRate;
1195
+ return {
1196
+ violated: true,
1197
+ severity: 7,
1198
+ evidence: { currency: curr, pool: poolName, poolSize, participants: dominantCount, maxSustainableMultiplier },
1199
+ suggestedAction: {
1200
+ parameter: "rewardRate",
1201
+ direction: "decrease",
1202
+ currency: curr,
1203
+ magnitude: 0.15,
1204
+ reasoning: `[${curr}] ${poolName} pool at ${poolSize.toFixed(0)} currency with ${dominantCount} active participants. Sustainable multiplier \u2264 ${maxSustainableMultiplier.toFixed(2)}. Reduce reward multiplier to prevent pool drain.`
1205
+ },
1206
+ confidence: 0.85,
1207
+ estimatedLag: 3
1208
+ };
1209
+ }
1067
1210
  }
1068
1211
  }
1069
1212
  return { violated: false };
@@ -1075,22 +1218,27 @@ var P14_TrackActualInjection = {
1075
1218
  category: "currency",
1076
1219
  description: 'Counting resource gathering as "currency injected" is misleading. Currency enters through faucet mechanisms (spawning, rewards). Fake metrics break every downstream decision.',
1077
1220
  check(metrics, _thresholds) {
1078
- const { faucetVolume, netFlow, totalSupply } = metrics;
1079
- const supplyGrowthRate = Math.abs(netFlow) / Math.max(1, totalSupply);
1080
- if (supplyGrowthRate > 0.1) {
1081
- return {
1082
- violated: true,
1083
- severity: 4,
1084
- evidence: { faucetVolume, netFlow, supplyGrowthRate },
1085
- suggestedAction: {
1086
- parameter: "yieldRate",
1087
- direction: "decrease",
1088
- magnitude: 0.1,
1089
- reasoning: `Supply growing at ${(supplyGrowthRate * 100).toFixed(1)}%/tick. Verify currency injection tracking. Resources should not create currency directly.`
1090
- },
1091
- confidence: 0.55,
1092
- estimatedLag: 5
1093
- };
1221
+ for (const curr of metrics.currencies) {
1222
+ const faucetVolume = metrics.faucetVolumeByCurrency[curr] ?? 0;
1223
+ const netFlow = metrics.netFlowByCurrency[curr] ?? 0;
1224
+ const totalSupply = metrics.totalSupplyByCurrency[curr] ?? 0;
1225
+ const supplyGrowthRate = Math.abs(netFlow) / Math.max(1, totalSupply);
1226
+ if (supplyGrowthRate > 0.1) {
1227
+ return {
1228
+ violated: true,
1229
+ severity: 4,
1230
+ evidence: { currency: curr, faucetVolume, netFlow, supplyGrowthRate },
1231
+ suggestedAction: {
1232
+ parameter: "yieldRate",
1233
+ direction: "decrease",
1234
+ currency: curr,
1235
+ magnitude: 0.1,
1236
+ reasoning: `[${curr}] Supply growing at ${(supplyGrowthRate * 100).toFixed(1)}%/tick. Verify currency injection tracking. Resources should not create currency directly.`
1237
+ },
1238
+ confidence: 0.55,
1239
+ estimatedLag: 5
1240
+ };
1241
+ }
1094
1242
  }
1095
1243
  return { violated: false };
1096
1244
  }
@@ -1101,24 +1249,28 @@ var P15_PoolsNeedCapAndDecay = {
1101
1249
  category: "currency",
1102
1250
  description: "Any pool (bank, reward pool) without a cap accumulates infinitely. A pool at 42% of total supply means 42% of the economy is frozen. Cap at 5%, decay at 2%/tick.",
1103
1251
  check(metrics, thresholds) {
1104
- const { poolSizes, totalSupply } = metrics;
1105
1252
  const { poolCapPercent } = thresholds;
1106
- for (const [pool, size] of Object.entries(poolSizes)) {
1107
- const shareOfSupply = size / Math.max(1, totalSupply);
1108
- if (shareOfSupply > poolCapPercent * 2) {
1109
- return {
1110
- violated: true,
1111
- severity: 6,
1112
- evidence: { pool, size, shareOfSupply, cap: poolCapPercent },
1113
- suggestedAction: {
1114
- parameter: "transactionFee",
1115
- direction: "decrease",
1116
- magnitude: 0.1,
1117
- reasoning: `${pool} pool at ${(shareOfSupply * 100).toFixed(1)}% of supply (cap: ${(poolCapPercent * 100).toFixed(0)}%). Currency frozen. Lower fees to encourage circulation over accumulation.`
1118
- },
1119
- confidence: 0.85,
1120
- estimatedLag: 5
1121
- };
1253
+ for (const [pool, currencyAmounts] of Object.entries(metrics.poolSizesByCurrency)) {
1254
+ for (const curr of metrics.currencies) {
1255
+ const size = currencyAmounts[curr] ?? 0;
1256
+ const totalSupply = metrics.totalSupplyByCurrency[curr] ?? 0;
1257
+ const shareOfSupply = size / Math.max(1, totalSupply);
1258
+ if (shareOfSupply > poolCapPercent * 2) {
1259
+ return {
1260
+ violated: true,
1261
+ severity: 6,
1262
+ evidence: { currency: curr, pool, size, shareOfSupply, cap: poolCapPercent },
1263
+ suggestedAction: {
1264
+ parameter: "transactionFee",
1265
+ direction: "decrease",
1266
+ currency: curr,
1267
+ magnitude: 0.1,
1268
+ reasoning: `[${curr}] ${pool} pool at ${(shareOfSupply * 100).toFixed(1)}% of supply (cap: ${(poolCapPercent * 100).toFixed(0)}%). Currency frozen. Lower fees to encourage circulation over accumulation.`
1269
+ },
1270
+ confidence: 0.85,
1271
+ estimatedLag: 5
1272
+ };
1273
+ }
1122
1274
  }
1123
1275
  }
1124
1276
  return { violated: false };
@@ -1130,23 +1282,27 @@ var P16_WithdrawalPenaltyScales = {
1130
1282
  category: "currency",
1131
1283
  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.",
1132
1284
  check(metrics, _thresholds) {
1133
- const { poolSizes, totalSupply } = metrics;
1134
- const stakedEstimate = totalSupply * 0.15;
1135
- for (const [poolName, poolSize] of Object.entries(poolSizes)) {
1136
- if (poolSize < 10 && stakedEstimate > 100) {
1137
- return {
1138
- violated: true,
1139
- severity: 3,
1140
- evidence: { pool: poolName, poolSize, estimatedStaked: stakedEstimate },
1141
- suggestedAction: {
1142
- parameter: "transactionFee",
1143
- direction: "increase",
1144
- magnitude: 0.05,
1145
- reasoning: `${poolName} pool depleted while significant currency should be locked. Early withdrawals may be draining the pool. Ensure withdrawal penalty scales with lock duration.`
1146
- },
1147
- confidence: 0.45,
1148
- estimatedLag: 10
1149
- };
1285
+ for (const [poolName, currencyAmounts] of Object.entries(metrics.poolSizesByCurrency)) {
1286
+ for (const curr of metrics.currencies) {
1287
+ const poolSize = currencyAmounts[curr] ?? 0;
1288
+ const totalSupply = metrics.totalSupplyByCurrency[curr] ?? 0;
1289
+ const stakedEstimate = totalSupply * 0.15;
1290
+ if (poolSize < 10 && stakedEstimate > 100) {
1291
+ return {
1292
+ violated: true,
1293
+ severity: 3,
1294
+ evidence: { currency: curr, pool: poolName, poolSize, estimatedStaked: stakedEstimate },
1295
+ suggestedAction: {
1296
+ parameter: "transactionFee",
1297
+ direction: "increase",
1298
+ currency: curr,
1299
+ magnitude: 0.05,
1300
+ reasoning: `[${curr}] ${poolName} pool depleted while significant currency should be locked. Early withdrawals may be draining the pool. Ensure withdrawal penalty scales with lock duration.`
1301
+ },
1302
+ confidence: 0.45,
1303
+ estimatedLag: 10
1304
+ };
1305
+ }
1150
1306
  }
1151
1307
  }
1152
1308
  return { violated: false };
@@ -1158,22 +1314,27 @@ var P32_VelocityAboveSupply = {
1158
1314
  category: "currency",
1159
1315
  description: "Low transactions despite adequate supply means liquidity is trapped. High supply with low velocity = stagnation, not abundance.",
1160
1316
  check(metrics, _thresholds) {
1161
- const { velocity, totalSupply, supplyByResource } = metrics;
1317
+ const { supplyByResource } = metrics;
1162
1318
  const totalResources = Object.values(supplyByResource).reduce((s, v) => s + v, 0);
1163
- if (velocity < 3 && totalSupply > 100 && totalResources > 20) {
1164
- return {
1165
- violated: true,
1166
- severity: 4,
1167
- evidence: { velocity, totalSupply, totalResources },
1168
- suggestedAction: {
1169
- parameter: "transactionFee",
1170
- direction: "decrease",
1171
- magnitude: 0.2,
1172
- reasoning: `Velocity ${velocity}/t with ${totalResources} resources in system. Economy stagnant despite available supply. Lower trading friction.`
1173
- },
1174
- confidence: 0.75,
1175
- estimatedLag: 5
1176
- };
1319
+ for (const curr of metrics.currencies) {
1320
+ const velocity = metrics.velocityByCurrency[curr] ?? 0;
1321
+ const totalSupply = metrics.totalSupplyByCurrency[curr] ?? 0;
1322
+ if (velocity < 3 && totalSupply > 100 && totalResources > 20) {
1323
+ return {
1324
+ violated: true,
1325
+ severity: 4,
1326
+ evidence: { currency: curr, velocity, totalSupply, totalResources },
1327
+ suggestedAction: {
1328
+ parameter: "transactionFee",
1329
+ direction: "decrease",
1330
+ currency: curr,
1331
+ magnitude: 0.2,
1332
+ reasoning: `[${curr}] Velocity ${velocity}/t with ${totalResources} resources in system. Economy stagnant despite available supply. Lower trading friction.`
1333
+ },
1334
+ confidence: 0.75,
1335
+ estimatedLag: 5
1336
+ };
1337
+ }
1177
1338
  }
1178
1339
  return { violated: false };
1179
1340
  }
@@ -1184,32 +1345,38 @@ var P58_NoNaturalNumeraire = {
1184
1345
  category: "currency",
1185
1346
  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.",
1186
1347
  check(metrics, _thresholds) {
1187
- const { prices, velocity, totalSupply } = metrics;
1188
- const priceValues = Object.values(prices).filter((p) => p > 0);
1189
- if (priceValues.length < 3) return { violated: false };
1190
- const mean = priceValues.reduce((s, p) => s + p, 0) / priceValues.length;
1191
- const coeffOfVariation = mean > 0 ? Math.sqrt(
1192
- priceValues.reduce((s, p) => s + (p - mean) ** 2, 0) / priceValues.length
1193
- ) / mean : 0;
1194
- if (coeffOfVariation < 0.25 && velocity > 5 && totalSupply > 100) {
1195
- return {
1196
- violated: true,
1197
- severity: 3,
1198
- evidence: {
1199
- coeffOfVariation,
1200
- velocity,
1201
- numResources: priceValues.length,
1202
- meanPrice: mean
1203
- },
1204
- suggestedAction: {
1205
- parameter: "productionCost",
1206
- direction: "increase",
1207
- magnitude: 0.1,
1208
- 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.`
1209
- },
1210
- confidence: 0.5,
1211
- estimatedLag: 20
1212
- };
1348
+ for (const curr of metrics.currencies) {
1349
+ const currPrices = metrics.pricesByCurrency[curr] ?? {};
1350
+ const velocity = metrics.velocityByCurrency[curr] ?? 0;
1351
+ const totalSupply = metrics.totalSupplyByCurrency[curr] ?? 0;
1352
+ const priceValues = Object.values(currPrices).filter((p) => p > 0);
1353
+ if (priceValues.length < 3) continue;
1354
+ const mean = priceValues.reduce((s, p) => s + p, 0) / priceValues.length;
1355
+ const coeffOfVariation = mean > 0 ? Math.sqrt(
1356
+ priceValues.reduce((s, p) => s + (p - mean) ** 2, 0) / priceValues.length
1357
+ ) / mean : 0;
1358
+ if (coeffOfVariation < 0.25 && velocity > 5 && totalSupply > 100) {
1359
+ return {
1360
+ violated: true,
1361
+ severity: 3,
1362
+ evidence: {
1363
+ currency: curr,
1364
+ coeffOfVariation,
1365
+ velocity,
1366
+ numResources: priceValues.length,
1367
+ meanPrice: mean
1368
+ },
1369
+ suggestedAction: {
1370
+ parameter: "productionCost",
1371
+ direction: "increase",
1372
+ currency: curr,
1373
+ magnitude: 0.1,
1374
+ reasoning: `[${curr}] 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.`
1375
+ },
1376
+ confidence: 0.5,
1377
+ estimatedLag: 20
1378
+ };
1379
+ }
1213
1380
  }
1214
1381
  return { violated: false };
1215
1382
  }
@@ -1897,26 +2064,33 @@ var P42_TheMedianPrinciple = {
1897
2064
  category: "statistical",
1898
2065
  description: "When (mean - median) / median > 0.3, mean is a lie. A few high-balance agents raise the mean while most agents have low balances. Always balance to median when divergence exceeds 30%.",
1899
2066
  check(metrics, thresholds) {
1900
- const { meanMedianDivergence, giniCoefficient } = metrics;
1901
- if (meanMedianDivergence > thresholds.meanMedianDivergenceMax) {
1902
- return {
1903
- violated: true,
1904
- severity: 5,
1905
- evidence: {
1906
- meanMedianDivergence,
1907
- giniCoefficient,
1908
- meanBalance: metrics.meanBalance,
1909
- medianBalance: metrics.medianBalance
1910
- },
1911
- suggestedAction: {
1912
- parameter: "transactionFee",
1913
- direction: "increase",
1914
- magnitude: 0.15,
1915
- reasoning: `Mean/median divergence ${(meanMedianDivergence * 100).toFixed(0)}% (threshold: ${(thresholds.meanMedianDivergenceMax * 100).toFixed(0)}%). Economy has outliers skewing metrics. Use median for decisions. Raise transaction fees to redistribute wealth.`
1916
- },
1917
- confidence: 0.85,
1918
- estimatedLag: 15
1919
- };
2067
+ for (const curr of metrics.currencies) {
2068
+ const meanMedianDivergence = metrics.meanMedianDivergenceByCurrency[curr] ?? 0;
2069
+ const giniCoefficient = metrics.giniCoefficientByCurrency[curr] ?? 0;
2070
+ const meanBalance = metrics.meanBalanceByCurrency[curr] ?? 0;
2071
+ const medianBalance = metrics.medianBalanceByCurrency[curr] ?? 0;
2072
+ if (meanMedianDivergence > thresholds.meanMedianDivergenceMax) {
2073
+ return {
2074
+ violated: true,
2075
+ severity: 5,
2076
+ evidence: {
2077
+ currency: curr,
2078
+ meanMedianDivergence,
2079
+ giniCoefficient,
2080
+ meanBalance,
2081
+ medianBalance
2082
+ },
2083
+ suggestedAction: {
2084
+ parameter: "transactionFee",
2085
+ direction: "increase",
2086
+ currency: curr,
2087
+ magnitude: 0.15,
2088
+ reasoning: `[${curr}] Mean/median divergence ${(meanMedianDivergence * 100).toFixed(0)}% (threshold: ${(thresholds.meanMedianDivergenceMax * 100).toFixed(0)}%). Economy has outliers skewing metrics. Use median for decisions. Raise transaction fees to redistribute wealth.`
2089
+ },
2090
+ confidence: 0.85,
2091
+ estimatedLag: 15
2092
+ };
2093
+ }
1920
2094
  }
1921
2095
  return { violated: false };
1922
2096
  }
@@ -2124,51 +2298,56 @@ var P33_FairNotEqual = {
2124
2298
  category: "player_experience",
2125
2299
  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.",
2126
2300
  check(metrics, thresholds) {
2127
- const { giniCoefficient } = metrics;
2128
- if (giniCoefficient < 0.1) {
2129
- return {
2130
- violated: true,
2131
- severity: 3,
2132
- evidence: { giniCoefficient },
2133
- suggestedAction: {
2134
- parameter: "rewardRate",
2135
- direction: "increase",
2136
- magnitude: 0.1,
2137
- reasoning: `Gini ${giniCoefficient.toFixed(2)} \u2014 near-perfect equality. Economy lacks stakes. Increase winner rewards to create meaningful spread.`
2138
- },
2139
- confidence: 0.6,
2140
- estimatedLag: 20
2141
- };
2142
- }
2143
- if (giniCoefficient > thresholds.giniRedThreshold) {
2144
- return {
2145
- violated: true,
2146
- severity: 7,
2147
- evidence: { giniCoefficient },
2148
- suggestedAction: {
2149
- parameter: "transactionFee",
2150
- direction: "increase",
2151
- magnitude: 0.2,
2152
- reasoning: `Gini ${giniCoefficient.toFixed(2)} \u2014 oligarchy level. Toxic inequality. Raise transaction fees to redistribute wealth from rich to pool.`
2153
- },
2154
- confidence: 0.85,
2155
- estimatedLag: 10
2156
- };
2157
- }
2158
- if (giniCoefficient > thresholds.giniWarnThreshold) {
2159
- return {
2160
- violated: true,
2161
- severity: 4,
2162
- evidence: { giniCoefficient },
2163
- suggestedAction: {
2164
- parameter: "transactionFee",
2165
- direction: "increase",
2166
- magnitude: 0.1,
2167
- reasoning: `Gini ${giniCoefficient.toFixed(2)} \u2014 high inequality warning. Gently raise fees to slow wealth concentration.`
2168
- },
2169
- confidence: 0.75,
2170
- estimatedLag: 15
2171
- };
2301
+ for (const curr of metrics.currencies) {
2302
+ const giniCoefficient = metrics.giniCoefficientByCurrency[curr] ?? 0;
2303
+ if (giniCoefficient < 0.1) {
2304
+ return {
2305
+ violated: true,
2306
+ severity: 3,
2307
+ evidence: { currency: curr, giniCoefficient },
2308
+ suggestedAction: {
2309
+ parameter: "rewardRate",
2310
+ direction: "increase",
2311
+ currency: curr,
2312
+ magnitude: 0.1,
2313
+ reasoning: `[${curr}] Gini ${giniCoefficient.toFixed(2)} \u2014 near-perfect equality. Economy lacks stakes. Increase winner rewards to create meaningful spread.`
2314
+ },
2315
+ confidence: 0.6,
2316
+ estimatedLag: 20
2317
+ };
2318
+ }
2319
+ if (giniCoefficient > thresholds.giniRedThreshold) {
2320
+ return {
2321
+ violated: true,
2322
+ severity: 7,
2323
+ evidence: { currency: curr, giniCoefficient },
2324
+ suggestedAction: {
2325
+ parameter: "transactionFee",
2326
+ direction: "increase",
2327
+ currency: curr,
2328
+ magnitude: 0.2,
2329
+ reasoning: `[${curr}] Gini ${giniCoefficient.toFixed(2)} \u2014 oligarchy level. Toxic inequality. Raise transaction fees to redistribute wealth from rich to pool.`
2330
+ },
2331
+ confidence: 0.85,
2332
+ estimatedLag: 10
2333
+ };
2334
+ }
2335
+ if (giniCoefficient > thresholds.giniWarnThreshold) {
2336
+ return {
2337
+ violated: true,
2338
+ severity: 4,
2339
+ evidence: { currency: curr, giniCoefficient },
2340
+ suggestedAction: {
2341
+ parameter: "transactionFee",
2342
+ direction: "increase",
2343
+ currency: curr,
2344
+ magnitude: 0.1,
2345
+ reasoning: `[${curr}] Gini ${giniCoefficient.toFixed(2)} \u2014 high inequality warning. Gently raise fees to slow wealth concentration.`
2346
+ },
2347
+ confidence: 0.75,
2348
+ estimatedLag: 15
2349
+ };
2350
+ }
2172
2351
  }
2173
2352
  return { violated: false };
2174
2353
  }
@@ -2701,33 +2880,41 @@ var Simulator = class {
2701
2880
  runForward(metrics, action, ticks, _thresholds) {
2702
2881
  const multiplier = this.actionMultiplier(action);
2703
2882
  const noise = () => 1 + (Math.random() - 0.5) * 0.1;
2704
- let supply = metrics.totalSupply;
2883
+ const currencies = metrics.currencies;
2884
+ const targetCurrency = action.currency;
2885
+ const supplies = { ...metrics.totalSupplyByCurrency };
2886
+ const netFlows = { ...metrics.netFlowByCurrency };
2887
+ const ginis = { ...metrics.giniCoefficientByCurrency };
2888
+ const velocities = { ...metrics.velocityByCurrency };
2705
2889
  let satisfaction = metrics.avgSatisfaction;
2706
- let gini = metrics.giniCoefficient;
2707
- let velocity = metrics.velocity;
2708
- let netFlow = metrics.netFlow;
2709
- const churnRate = metrics.churnRate;
2710
2890
  for (let t = 0; t < ticks; t++) {
2711
- const effectOnFlow = this.flowEffect(action, metrics) * multiplier * noise();
2712
- netFlow = netFlow * 0.9 + effectOnFlow * 0.1;
2713
- supply += netFlow * noise();
2714
- supply = Math.max(0, supply);
2715
- const satDelta = netFlow > 0 && netFlow < 20 ? 0.5 : netFlow < 0 ? -1 : 0;
2891
+ for (const curr of currencies) {
2892
+ const isTarget = !targetCurrency || targetCurrency === curr;
2893
+ const effectOnFlow = isTarget ? this.flowEffect(action, metrics, curr) * multiplier * noise() : 0;
2894
+ netFlows[curr] = (netFlows[curr] ?? 0) * 0.9 + effectOnFlow * 0.1;
2895
+ supplies[curr] = Math.max(0, (supplies[curr] ?? 0) + (netFlows[curr] ?? 0) * noise());
2896
+ ginis[curr] = (ginis[curr] ?? 0) * 0.99 + 0.35 * 0.01 * noise();
2897
+ velocities[curr] = (supplies[curr] ?? 0) / Math.max(1, metrics.totalAgents) * 0.01 * noise();
2898
+ }
2899
+ const avgNetFlow = currencies.length > 0 ? Object.values(netFlows).reduce((s, v) => s + v, 0) / currencies.length : 0;
2900
+ const satDelta = avgNetFlow > 0 && avgNetFlow < 20 ? 0.5 : avgNetFlow < 0 ? -1 : 0;
2716
2901
  satisfaction = Math.min(100, Math.max(0, satisfaction + satDelta * noise()));
2717
- gini = gini * 0.99 + 0.35 * 0.01 * noise();
2718
- velocity = supply / Math.max(1, metrics.totalAgents) * 0.01 * noise();
2719
- const agentLoss = metrics.totalAgents * churnRate * noise();
2720
- void agentLoss;
2721
2902
  }
2903
+ const totalSupply = Object.values(supplies).reduce((s, v) => s + v, 0);
2722
2904
  const projected = {
2723
2905
  ...metrics,
2724
2906
  tick: metrics.tick + ticks,
2725
- totalSupply: supply,
2726
- netFlow,
2727
- velocity,
2728
- giniCoefficient: Math.max(0, Math.min(1, gini)),
2907
+ currencies,
2908
+ totalSupplyByCurrency: supplies,
2909
+ netFlowByCurrency: netFlows,
2910
+ velocityByCurrency: velocities,
2911
+ giniCoefficientByCurrency: ginis,
2912
+ totalSupply,
2913
+ netFlow: Object.values(netFlows).reduce((s, v) => s + v, 0),
2914
+ velocity: totalSupply > 0 && currencies.length > 0 ? Object.values(velocities).reduce((s, v) => s + v, 0) / currencies.length : 0,
2915
+ giniCoefficient: currencies.length > 0 ? Object.values(ginis).reduce((s, v) => s + v, 0) / currencies.length : 0,
2729
2916
  avgSatisfaction: satisfaction,
2730
- inflationRate: metrics.totalSupply > 0 ? (supply - metrics.totalSupply) / metrics.totalSupply : 0
2917
+ inflationRate: metrics.totalSupply > 0 ? (totalSupply - metrics.totalSupply) / metrics.totalSupply : 0
2731
2918
  };
2732
2919
  return projected;
2733
2920
  }
@@ -2735,16 +2922,16 @@ var Simulator = class {
2735
2922
  const base = action.magnitude ?? 0.1;
2736
2923
  return action.direction === "increase" ? 1 + base : 1 - base;
2737
2924
  }
2738
- flowEffect(action, metrics) {
2925
+ flowEffect(action, metrics, currency) {
2739
2926
  const { parameter, direction } = action;
2740
2927
  const sign = direction === "increase" ? -1 : 1;
2741
2928
  const roleEntries = Object.entries(metrics.populationByRole).sort((a, b) => b[1] - a[1]);
2742
2929
  const dominantRoleCount = roleEntries[0]?.[1] ?? 0;
2743
2930
  if (parameter === "productionCost") {
2744
- return sign * metrics.netFlow * 0.2;
2931
+ return sign * (metrics.netFlowByCurrency[currency] ?? 0) * 0.2;
2745
2932
  }
2746
2933
  if (parameter === "transactionFee") {
2747
- return sign * metrics.velocity * 10 * 0.1;
2934
+ return sign * (metrics.velocityByCurrency[currency] ?? 0) * 10 * 0.1;
2748
2935
  }
2749
2936
  if (parameter === "entryFee") {
2750
2937
  return sign * dominantRoleCount * 0.5;
@@ -2753,14 +2940,22 @@ var Simulator = class {
2753
2940
  return -sign * dominantRoleCount * 0.3;
2754
2941
  }
2755
2942
  if (parameter === "yieldRate") {
2756
- return sign * metrics.faucetVolume * 0.15;
2943
+ return sign * (metrics.faucetVolumeByCurrency[currency] ?? 0) * 0.15;
2757
2944
  }
2758
- return sign * metrics.netFlow * 0.1;
2945
+ return sign * (metrics.netFlowByCurrency[currency] ?? 0) * 0.1;
2759
2946
  }
2760
2947
  checkImprovement(before, after, action) {
2761
2948
  const satisfactionImproved = after.avgSatisfaction >= before.avgSatisfaction - 2;
2762
- const flowMoreBalanced = Math.abs(after.netFlow) <= Math.abs(before.netFlow) * 1.2;
2763
- const notWorseGini = after.giniCoefficient <= before.giniCoefficient + 0.05;
2949
+ const flowMoreBalanced = before.currencies.every((curr) => {
2950
+ const afterFlow = Math.abs(after.netFlowByCurrency[curr] ?? 0);
2951
+ const beforeFlow = Math.abs(before.netFlowByCurrency[curr] ?? 0);
2952
+ return afterFlow <= beforeFlow * 1.2 || afterFlow < 1;
2953
+ });
2954
+ const notWorseGini = before.currencies.every((curr) => {
2955
+ const afterGini = after.giniCoefficientByCurrency[curr] ?? 0;
2956
+ const beforeGini = before.giniCoefficientByCurrency[curr] ?? 0;
2957
+ return afterGini <= beforeGini + 0.05;
2958
+ });
2764
2959
  void action;
2765
2960
  return satisfactionImproved && flowMoreBalanced && notWorseGini;
2766
2961
  }
@@ -2771,6 +2966,21 @@ var Simulator = class {
2771
2966
  const vals = outcomes.map((o) => o[key]).filter((v) => typeof v === "number" && !isNaN(v));
2772
2967
  return vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : 0;
2773
2968
  };
2969
+ const avgRecord = (key) => {
2970
+ const allKeys = /* @__PURE__ */ new Set();
2971
+ for (const o of outcomes) {
2972
+ const rec = o[key];
2973
+ if (rec && typeof rec === "object" && !Array.isArray(rec)) {
2974
+ Object.keys(rec).forEach((k) => allKeys.add(k));
2975
+ }
2976
+ }
2977
+ const result = {};
2978
+ for (const k of allKeys) {
2979
+ const vals = outcomes.map((o) => o[key]?.[k]).filter((v) => typeof v === "number" && !isNaN(v));
2980
+ result[k] = vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : 0;
2981
+ }
2982
+ return result;
2983
+ };
2774
2984
  return {
2775
2985
  ...base,
2776
2986
  totalSupply: avg("totalSupply"),
@@ -2778,7 +2988,11 @@ var Simulator = class {
2778
2988
  velocity: avg("velocity"),
2779
2989
  giniCoefficient: avg("giniCoefficient"),
2780
2990
  avgSatisfaction: avg("avgSatisfaction"),
2781
- inflationRate: avg("inflationRate")
2991
+ inflationRate: avg("inflationRate"),
2992
+ totalSupplyByCurrency: avgRecord("totalSupplyByCurrency"),
2993
+ netFlowByCurrency: avgRecord("netFlowByCurrency"),
2994
+ velocityByCurrency: avgRecord("velocityByCurrency"),
2995
+ giniCoefficientByCurrency: avgRecord("giniCoefficientByCurrency")
2782
2996
  };
2783
2997
  }
2784
2998
  };
@@ -2837,6 +3051,7 @@ var Planner = class {
2837
3051
  id: `plan_${metrics.tick}_${param}`,
2838
3052
  diagnosis,
2839
3053
  parameter: param,
3054
+ ...action.currency !== void 0 ? { currency: action.currency } : {},
2840
3055
  currentValue,
2841
3056
  targetValue,
2842
3057
  maxChangePercent: thresholds.maxAdjustmentPercent,
@@ -2878,7 +3093,7 @@ var Executor = class {
2878
3093
  }
2879
3094
  async apply(plan, adapter, currentParams) {
2880
3095
  const originalValue = currentParams[plan.parameter] ?? plan.currentValue;
2881
- await adapter.setParam(plan.parameter, plan.targetValue);
3096
+ await adapter.setParam(plan.parameter, plan.targetValue, plan.currency);
2882
3097
  plan.appliedAt = plan.diagnosis.tick;
2883
3098
  this.activePlans.push({ plan, originalValue });
2884
3099
  }
@@ -2900,7 +3115,7 @@ var Executor = class {
2900
3115
  const metricValue = this.getMetricValue(metrics, rc.metric);
2901
3116
  const shouldRollback = rc.direction === "below" ? metricValue < rc.threshold : metricValue > rc.threshold;
2902
3117
  if (shouldRollback) {
2903
- await adapter.setParam(plan.parameter, originalValue);
3118
+ await adapter.setParam(plan.parameter, originalValue, plan.currency);
2904
3119
  rolledBack.push(plan);
2905
3120
  } else {
2906
3121
  const settledTick = rc.checkAfterTick + 10;
@@ -3033,6 +3248,18 @@ var DecisionLog = class {
3033
3248
  };
3034
3249
 
3035
3250
  // src/MetricStore.ts
3251
+ function getNestedValue(obj, path) {
3252
+ const parts = path.split(".");
3253
+ let val = obj;
3254
+ for (const part of parts) {
3255
+ if (val !== null && typeof val === "object") {
3256
+ val = val[part];
3257
+ } else {
3258
+ return NaN;
3259
+ }
3260
+ }
3261
+ return typeof val === "number" ? val : NaN;
3262
+ }
3036
3263
  var RingBuffer = class {
3037
3264
  constructor(capacity) {
3038
3265
  this.capacity = capacity;
@@ -3110,10 +3337,9 @@ var MetricStore = class {
3110
3337
  if (q.to !== void 0 && m.tick > q.to) return false;
3111
3338
  return true;
3112
3339
  });
3113
- const metricKey = q.metric;
3114
3340
  const points = filtered.map((m) => ({
3115
3341
  tick: m.tick,
3116
- value: typeof m[metricKey] === "number" ? m[metricKey] : NaN
3342
+ value: getNestedValue(m, q.metric)
3117
3343
  }));
3118
3344
  return { metric: q.metric, resolution, points };
3119
3345
  }
@@ -3138,6 +3364,21 @@ var MetricStore = class {
3138
3364
  const vals = snapshots.map((s) => s[key]).filter((v) => !isNaN(v));
3139
3365
  return vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : 0;
3140
3366
  };
3367
+ const avgRecord = (key) => {
3368
+ const allKeys = /* @__PURE__ */ new Set();
3369
+ for (const s of snapshots) {
3370
+ const rec = s[key];
3371
+ if (rec && typeof rec === "object" && !Array.isArray(rec)) {
3372
+ Object.keys(rec).forEach((k) => allKeys.add(k));
3373
+ }
3374
+ }
3375
+ const result = {};
3376
+ for (const k of allKeys) {
3377
+ const vals = snapshots.map((s) => s[key]?.[k]).filter((v) => typeof v === "number" && !isNaN(v));
3378
+ result[k] = vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : 0;
3379
+ }
3380
+ return result;
3381
+ };
3141
3382
  return {
3142
3383
  ...last,
3143
3384
  totalSupply: avg("totalSupply"),
@@ -3157,7 +3398,21 @@ var MetricStore = class {
3157
3398
  tapSinkRatio: avg("tapSinkRatio"),
3158
3399
  productionIndex: avg("productionIndex"),
3159
3400
  capacityUsage: avg("capacityUsage"),
3160
- anchorRatioDrift: avg("anchorRatioDrift")
3401
+ anchorRatioDrift: avg("anchorRatioDrift"),
3402
+ // Per-currency averages
3403
+ totalSupplyByCurrency: avgRecord("totalSupplyByCurrency"),
3404
+ netFlowByCurrency: avgRecord("netFlowByCurrency"),
3405
+ velocityByCurrency: avgRecord("velocityByCurrency"),
3406
+ inflationRateByCurrency: avgRecord("inflationRateByCurrency"),
3407
+ faucetVolumeByCurrency: avgRecord("faucetVolumeByCurrency"),
3408
+ sinkVolumeByCurrency: avgRecord("sinkVolumeByCurrency"),
3409
+ tapSinkRatioByCurrency: avgRecord("tapSinkRatioByCurrency"),
3410
+ anchorRatioDriftByCurrency: avgRecord("anchorRatioDriftByCurrency"),
3411
+ giniCoefficientByCurrency: avgRecord("giniCoefficientByCurrency"),
3412
+ medianBalanceByCurrency: avgRecord("medianBalanceByCurrency"),
3413
+ meanBalanceByCurrency: avgRecord("meanBalanceByCurrency"),
3414
+ top10PctShareByCurrency: avgRecord("top10PctShareByCurrency"),
3415
+ meanMedianDivergenceByCurrency: avgRecord("meanMedianDivergenceByCurrency")
3161
3416
  };
3162
3417
  }
3163
3418
  };