@agent-e/core 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -15
- package/dist/index.d.mts +45 -18
- package/dist/index.d.ts +45 -18
- package/dist/index.js +624 -368
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +624 -368
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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.
|
|
200
|
+
this.previousPricesByCurrency = {};
|
|
201
201
|
this.customMetricFns = {};
|
|
202
|
-
this.
|
|
202
|
+
this.anchorBaselineByCurrency = {};
|
|
203
203
|
this.tickConfig = { ...DEFAULT_TICK_CONFIG, ...tickConfig };
|
|
204
204
|
}
|
|
205
205
|
registerCustomMetric(name, fn) {
|
|
@@ -207,23 +207,28 @@ 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 =
|
|
213
|
-
let
|
|
214
|
-
|
|
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
|
+
const defaultCurrency = state.currencies[0] ?? "default";
|
|
218
219
|
for (const e of recentEvents) {
|
|
220
|
+
const curr = e.currency ?? defaultCurrency;
|
|
219
221
|
switch (e.type) {
|
|
220
222
|
case "mint":
|
|
221
223
|
case "spawn":
|
|
222
|
-
|
|
224
|
+
faucetVolumeByCurrency[curr] = (faucetVolumeByCurrency[curr] ?? 0) + (e.amount ?? 0);
|
|
223
225
|
break;
|
|
224
226
|
case "burn":
|
|
225
227
|
case "consume":
|
|
226
|
-
|
|
228
|
+
sinkVolumeByCurrency[curr] = (sinkVolumeByCurrency[curr] ?? 0) + (e.amount ?? 0);
|
|
229
|
+
break;
|
|
230
|
+
case "produce":
|
|
231
|
+
productionAmount += e.amount ?? 1;
|
|
227
232
|
break;
|
|
228
233
|
case "trade":
|
|
229
234
|
tradeEvents.push(e);
|
|
@@ -237,20 +242,68 @@ var Observer = class {
|
|
|
237
242
|
break;
|
|
238
243
|
}
|
|
239
244
|
}
|
|
240
|
-
const
|
|
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);
|
|
241
297
|
const netFlow = faucetVolume - sinkVolume;
|
|
242
298
|
const tapSinkRatio = sinkVolume > 0 ? faucetVolume / sinkVolume : faucetVolume > 0 ? Infinity : 1;
|
|
243
|
-
const prevSupply = this.previousMetrics?.totalSupply ?? totalSupply;
|
|
244
|
-
const inflationRate = prevSupply > 0 ? (totalSupply - prevSupply) / prevSupply : 0;
|
|
245
299
|
const velocity = totalSupply > 0 ? tradeEvents.length / totalSupply : 0;
|
|
246
|
-
const
|
|
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);
|
|
247
304
|
const meanBalance = totalAgents > 0 ? totalSupply / totalAgents : 0;
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
-
const top10Sum = sortedBalances.slice(top10Idx).reduce((s, b) => s + b, 0);
|
|
251
|
-
const top10PctShare = totalSupply > 0 ? top10Sum / totalSupply : 0;
|
|
252
|
-
const giniCoefficient = computeGini(sortedBalances);
|
|
253
|
-
const meanMedianDivergence = medianBalance > 0 ? Math.abs(meanBalance - medianBalance) / medianBalance : 0;
|
|
305
|
+
const top10PctShare = avgOf(top10PctShareByCurrency);
|
|
306
|
+
const meanMedianDivergence = avgOf(meanMedianDivergenceByCurrency);
|
|
254
307
|
const populationByRole = {};
|
|
255
308
|
const roleShares = {};
|
|
256
309
|
for (const role of roles) {
|
|
@@ -265,15 +318,25 @@ var Observer = class {
|
|
|
265
318
|
churnByRole[role] = (churnByRole[role] ?? 0) + 1;
|
|
266
319
|
}
|
|
267
320
|
const churnRate = churnCount / Math.max(1, totalAgents);
|
|
268
|
-
const
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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;
|
|
277
340
|
const supplyByResource = {};
|
|
278
341
|
for (const inv of Object.values(state.agentInventories)) {
|
|
279
342
|
for (const [resource, qty] of Object.entries(inv)) {
|
|
@@ -298,70 +361,92 @@ var Observer = class {
|
|
|
298
361
|
pinchPoints[resource] = "optimal";
|
|
299
362
|
}
|
|
300
363
|
}
|
|
301
|
-
const productionIndex =
|
|
364
|
+
const productionIndex = productionAmount;
|
|
302
365
|
const maxPossibleProduction = productionIndex + sinkVolume;
|
|
303
366
|
const capacityUsage = maxPossibleProduction > 0 ? productionIndex / maxPossibleProduction : 0;
|
|
304
367
|
const satisfactions = Object.values(state.agentSatisfaction ?? {});
|
|
305
368
|
const avgSatisfaction = satisfactions.length > 0 ? satisfactions.reduce((s, v) => s + v, 0) / satisfactions.length : 80;
|
|
306
369
|
const blockedAgentCount = satisfactions.filter((s) => s < 20).length;
|
|
307
370
|
const timeToValue = totalAgents > 0 ? blockedAgentCount / totalAgents * 100 : 0;
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
+
}
|
|
314
378
|
}
|
|
315
|
-
|
|
316
|
-
if (
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
let totalDivergence = 0;
|
|
325
|
-
for (let i = 0; i < priceKeys.length; i++) {
|
|
326
|
-
for (let j = i + 1; j < priceKeys.length; j++) {
|
|
327
|
-
const pA = prices[priceKeys[i]];
|
|
328
|
-
const pB = prices[priceKeys[j]];
|
|
329
|
-
let ratio = pA / pB;
|
|
330
|
-
ratio = Math.max(1e-3, Math.min(1e3, ratio));
|
|
331
|
-
totalDivergence += Math.abs(Math.log(ratio));
|
|
332
|
-
pairCount++;
|
|
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
|
+
};
|
|
333
388
|
}
|
|
334
389
|
}
|
|
335
|
-
arbitrageIndex = pairCount > 0 ? Math.min(1, totalDivergence / pairCount) : 0;
|
|
336
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
|
+
}
|
|
399
|
+
}
|
|
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
|
+
}
|
|
417
|
+
}
|
|
418
|
+
arbitrageIndexByCurrency[curr] = pairCount > 0 ? Math.min(1, totalDivergence / pairCount) : 0;
|
|
419
|
+
} else {
|
|
420
|
+
arbitrageIndexByCurrency[curr] = 0;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const arbitrageIndex = avgOf(arbitrageIndexByCurrency);
|
|
337
424
|
const contentDropEvents = recentEvents.filter(
|
|
338
425
|
(e) => e.metadata?.["contentDrop"] === true
|
|
339
426
|
);
|
|
340
427
|
const contentDropAge = contentDropEvents.length > 0 ? tick - Math.max(...contentDropEvents.map((e) => e.timestamp)) : (this.previousMetrics?.contentDropAge ?? 0) + 1;
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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;
|
|
345
437
|
const tradePrice = e.price ?? 0;
|
|
346
|
-
if (tradePrice === 0 || marketPrice > 0 && tradePrice < marketPrice * 0.3)
|
|
347
|
-
giftTrades++;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
const giftTradeRatio = tradeEvents.length > 0 ? giftTrades / tradeEvents.length : 0;
|
|
352
|
-
let disposalTrades = 0;
|
|
353
|
-
if (tradeEvents.length > 0) {
|
|
354
|
-
for (const e of tradeEvents) {
|
|
438
|
+
if (tradePrice === 0 || marketPrice > 0 && tradePrice < marketPrice * 0.3) gifts++;
|
|
355
439
|
if (e.from && e.resource) {
|
|
356
440
|
const sellerInv = state.agentInventories[e.from]?.[e.resource] ?? 0;
|
|
357
441
|
const avgInv = (supplyByResource[e.resource] ?? 0) / Math.max(1, totalAgents);
|
|
358
|
-
if (sellerInv > avgInv * 3)
|
|
359
|
-
disposalTrades++;
|
|
360
|
-
}
|
|
442
|
+
if (sellerInv > avgInv * 3) disposals++;
|
|
361
443
|
}
|
|
362
444
|
}
|
|
445
|
+
giftTradeRatioByCurrency[curr] = currTrades.length > 0 ? gifts / currTrades.length : 0;
|
|
446
|
+
disposalTradeRatioByCurrency[curr] = currTrades.length > 0 ? disposals / currTrades.length : 0;
|
|
363
447
|
}
|
|
364
|
-
const
|
|
448
|
+
const giftTradeRatio = avgOf(giftTradeRatioByCurrency);
|
|
449
|
+
const disposalTradeRatio = avgOf(disposalTradeRatioByCurrency);
|
|
365
450
|
const custom = {};
|
|
366
451
|
for (const [name, fn] of Object.entries(this.customMetricFns)) {
|
|
367
452
|
try {
|
|
@@ -373,10 +458,57 @@ var Observer = class {
|
|
|
373
458
|
const metrics = {
|
|
374
459
|
tick,
|
|
375
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
|
|
376
487
|
totalSupply,
|
|
377
488
|
netFlow,
|
|
378
489
|
velocity,
|
|
379
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
|
|
380
512
|
populationByRole,
|
|
381
513
|
roleShares,
|
|
382
514
|
totalAgents,
|
|
@@ -384,38 +516,18 @@ var Observer = class {
|
|
|
384
516
|
churnByRole,
|
|
385
517
|
personaDistribution: {},
|
|
386
518
|
// populated by PersonaTracker
|
|
387
|
-
giniCoefficient,
|
|
388
|
-
medianBalance,
|
|
389
|
-
meanBalance,
|
|
390
|
-
top10PctShare,
|
|
391
|
-
meanMedianDivergence,
|
|
392
|
-
priceIndex,
|
|
393
519
|
productionIndex,
|
|
394
520
|
capacityUsage,
|
|
395
|
-
prices,
|
|
396
|
-
priceVolatility,
|
|
397
521
|
supplyByResource,
|
|
398
522
|
demandSignals,
|
|
399
523
|
pinchPoints,
|
|
400
524
|
avgSatisfaction,
|
|
401
525
|
blockedAgentCount,
|
|
402
526
|
timeToValue,
|
|
403
|
-
faucetVolume,
|
|
404
|
-
sinkVolume,
|
|
405
|
-
tapSinkRatio,
|
|
406
|
-
poolSizes,
|
|
407
|
-
anchorRatioDrift,
|
|
408
|
-
extractionRatio: NaN,
|
|
409
|
-
newUserDependency: NaN,
|
|
410
|
-
smokeTestRatio: NaN,
|
|
411
|
-
currencyInsulation: NaN,
|
|
412
527
|
sharkToothPeaks: this.previousMetrics?.sharkToothPeaks ?? [],
|
|
413
528
|
sharkToothValleys: this.previousMetrics?.sharkToothValleys ?? [],
|
|
414
529
|
eventCompletionRate: NaN,
|
|
415
|
-
arbitrageIndex,
|
|
416
530
|
contentDropAge,
|
|
417
|
-
giftTradeRatio,
|
|
418
|
-
disposalTradeRatio,
|
|
419
531
|
custom
|
|
420
532
|
};
|
|
421
533
|
this.previousMetrics = metrics;
|
|
@@ -489,48 +601,75 @@ function emptyMetrics(tick = 0) {
|
|
|
489
601
|
return {
|
|
490
602
|
tick,
|
|
491
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
|
|
492
630
|
totalSupply: 0,
|
|
493
631
|
netFlow: 0,
|
|
494
632
|
velocity: 0,
|
|
495
633
|
inflationRate: 0,
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
churnByRole: {},
|
|
501
|
-
personaDistribution: {},
|
|
634
|
+
faucetVolume: 0,
|
|
635
|
+
sinkVolume: 0,
|
|
636
|
+
tapSinkRatio: 1,
|
|
637
|
+
anchorRatioDrift: 0,
|
|
502
638
|
giniCoefficient: 0,
|
|
503
639
|
medianBalance: 0,
|
|
504
640
|
meanBalance: 0,
|
|
505
641
|
top10PctShare: 0,
|
|
506
642
|
meanMedianDivergence: 0,
|
|
507
643
|
priceIndex: 0,
|
|
508
|
-
productionIndex: 0,
|
|
509
|
-
capacityUsage: 0,
|
|
510
644
|
prices: {},
|
|
511
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,
|
|
512
663
|
supplyByResource: {},
|
|
513
664
|
demandSignals: {},
|
|
514
665
|
pinchPoints: {},
|
|
515
666
|
avgSatisfaction: 100,
|
|
516
667
|
blockedAgentCount: 0,
|
|
517
668
|
timeToValue: 0,
|
|
518
|
-
faucetVolume: 0,
|
|
519
|
-
sinkVolume: 0,
|
|
520
|
-
tapSinkRatio: 1,
|
|
521
|
-
poolSizes: {},
|
|
522
|
-
anchorRatioDrift: 0,
|
|
523
|
-
extractionRatio: NaN,
|
|
524
|
-
newUserDependency: NaN,
|
|
525
|
-
smokeTestRatio: NaN,
|
|
526
|
-
currencyInsulation: NaN,
|
|
527
669
|
sharkToothPeaks: [],
|
|
528
670
|
sharkToothValleys: [],
|
|
529
671
|
eventCompletionRate: NaN,
|
|
530
|
-
arbitrageIndex: 0,
|
|
531
672
|
contentDropAge: 0,
|
|
532
|
-
giftTradeRatio: 0,
|
|
533
|
-
disposalTradeRatio: 0,
|
|
534
673
|
custom: {}
|
|
535
674
|
};
|
|
536
675
|
}
|
|
@@ -996,38 +1135,44 @@ var P12_OnePrimaryFaucet = {
|
|
|
996
1135
|
id: "P12",
|
|
997
1136
|
name: "One Primary Faucet",
|
|
998
1137
|
category: "currency",
|
|
999
|
-
description: "Multiple independent currency sources (gathering +
|
|
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.",
|
|
1000
1139
|
check(metrics, thresholds) {
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
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
|
+
}
|
|
1031
1176
|
}
|
|
1032
1177
|
return { violated: false };
|
|
1033
1178
|
}
|
|
@@ -1038,28 +1183,30 @@ var P13_PotsAreZeroSumAndSelfRegulate = {
|
|
|
1038
1183
|
category: "currency",
|
|
1039
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.",
|
|
1040
1185
|
check(metrics, thresholds) {
|
|
1041
|
-
const {
|
|
1042
|
-
const totalAgents = metrics.totalAgents;
|
|
1186
|
+
const { populationByRole } = metrics;
|
|
1043
1187
|
const roleEntries = Object.entries(populationByRole).sort((a, b) => b[1] - a[1]);
|
|
1044
|
-
const dominantRole = roleEntries[0]?.[0];
|
|
1045
1188
|
const dominantCount = roleEntries[0]?.[1] ?? 0;
|
|
1046
|
-
for (const [poolName,
|
|
1047
|
-
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
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
|
+
}
|
|
1063
1210
|
}
|
|
1064
1211
|
}
|
|
1065
1212
|
return { violated: false };
|
|
@@ -1071,22 +1218,27 @@ var P14_TrackActualInjection = {
|
|
|
1071
1218
|
category: "currency",
|
|
1072
1219
|
description: 'Counting resource gathering as "currency injected" is misleading. Currency enters through faucet mechanisms (spawning, rewards). Fake metrics break every downstream decision.',
|
|
1073
1220
|
check(metrics, _thresholds) {
|
|
1074
|
-
const
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
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
|
+
}
|
|
1090
1242
|
}
|
|
1091
1243
|
return { violated: false };
|
|
1092
1244
|
}
|
|
@@ -1095,26 +1247,30 @@ var P15_PoolsNeedCapAndDecay = {
|
|
|
1095
1247
|
id: "P15",
|
|
1096
1248
|
name: "Pools Need Cap + Decay",
|
|
1097
1249
|
category: "currency",
|
|
1098
|
-
description: "Any pool (bank, reward pool) without a cap accumulates infinitely.
|
|
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.",
|
|
1099
1251
|
check(metrics, thresholds) {
|
|
1100
|
-
const { poolSizes, totalSupply } = metrics;
|
|
1101
1252
|
const { poolCapPercent } = thresholds;
|
|
1102
|
-
for (const [pool,
|
|
1103
|
-
const
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
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
|
+
}
|
|
1118
1274
|
}
|
|
1119
1275
|
}
|
|
1120
1276
|
return { violated: false };
|
|
@@ -1126,23 +1282,27 @@ var P16_WithdrawalPenaltyScales = {
|
|
|
1126
1282
|
category: "currency",
|
|
1127
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.",
|
|
1128
1284
|
check(metrics, _thresholds) {
|
|
1129
|
-
const
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
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
|
+
}
|
|
1146
1306
|
}
|
|
1147
1307
|
}
|
|
1148
1308
|
return { violated: false };
|
|
@@ -1154,22 +1314,27 @@ var P32_VelocityAboveSupply = {
|
|
|
1154
1314
|
category: "currency",
|
|
1155
1315
|
description: "Low transactions despite adequate supply means liquidity is trapped. High supply with low velocity = stagnation, not abundance.",
|
|
1156
1316
|
check(metrics, _thresholds) {
|
|
1157
|
-
const {
|
|
1317
|
+
const { supplyByResource } = metrics;
|
|
1158
1318
|
const totalResources = Object.values(supplyByResource).reduce((s, v) => s + v, 0);
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
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
|
+
}
|
|
1173
1338
|
}
|
|
1174
1339
|
return { violated: false };
|
|
1175
1340
|
}
|
|
@@ -1180,32 +1345,38 @@ var P58_NoNaturalNumeraire = {
|
|
|
1180
1345
|
category: "currency",
|
|
1181
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.",
|
|
1182
1347
|
check(metrics, _thresholds) {
|
|
1183
|
-
const
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
priceValues.
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
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
|
+
}
|
|
1209
1380
|
}
|
|
1210
1381
|
return { violated: false };
|
|
1211
1382
|
}
|
|
@@ -1252,25 +1423,22 @@ var P18_FirstProducerNeedsStartingInventory = {
|
|
|
1252
1423
|
description: "A producer with 0 resources and 0 currency must sell nothing to get currency before they can buy raw materials. This creates a chicken-and-egg freeze. Starting inventory (2 goods + 4 raw materials + 40 currency) breaks the deadlock.",
|
|
1253
1424
|
check(metrics, _thresholds) {
|
|
1254
1425
|
if (metrics.tick > 20) return { violated: false };
|
|
1426
|
+
const hasAgents = metrics.totalAgents > 0;
|
|
1255
1427
|
for (const [resource, supply] of Object.entries(metrics.supplyByResource)) {
|
|
1256
|
-
if (supply === 0) {
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
estimatedLag: 2
|
|
1271
|
-
};
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1428
|
+
if (supply === 0 && hasAgents) {
|
|
1429
|
+
return {
|
|
1430
|
+
violated: true,
|
|
1431
|
+
severity: 8,
|
|
1432
|
+
evidence: { tick: metrics.tick, resource, supply, totalAgents: metrics.totalAgents },
|
|
1433
|
+
suggestedAction: {
|
|
1434
|
+
parameter: "productionCost",
|
|
1435
|
+
direction: "decrease",
|
|
1436
|
+
magnitude: 0.5,
|
|
1437
|
+
reasoning: `Bootstrap failure: ${resource} supply is 0 at tick ${metrics.tick} with ${metrics.totalAgents} agents. Drastically reduce production cost to allow immediate output.`
|
|
1438
|
+
},
|
|
1439
|
+
confidence: 0.9,
|
|
1440
|
+
estimatedLag: 2
|
|
1441
|
+
};
|
|
1274
1442
|
}
|
|
1275
1443
|
}
|
|
1276
1444
|
return { violated: false };
|
|
@@ -1896,26 +2064,33 @@ var P42_TheMedianPrinciple = {
|
|
|
1896
2064
|
category: "statistical",
|
|
1897
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%.",
|
|
1898
2066
|
check(metrics, thresholds) {
|
|
1899
|
-
const
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
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
|
+
}
|
|
1919
2094
|
}
|
|
1920
2095
|
return { violated: false };
|
|
1921
2096
|
}
|
|
@@ -2123,51 +2298,56 @@ var P33_FairNotEqual = {
|
|
|
2123
2298
|
category: "player_experience",
|
|
2124
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.",
|
|
2125
2300
|
check(metrics, thresholds) {
|
|
2126
|
-
const
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
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
|
+
}
|
|
2171
2351
|
}
|
|
2172
2352
|
return { violated: false };
|
|
2173
2353
|
}
|
|
@@ -2700,33 +2880,41 @@ var Simulator = class {
|
|
|
2700
2880
|
runForward(metrics, action, ticks, _thresholds) {
|
|
2701
2881
|
const multiplier = this.actionMultiplier(action);
|
|
2702
2882
|
const noise = () => 1 + (Math.random() - 0.5) * 0.1;
|
|
2703
|
-
|
|
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 };
|
|
2704
2889
|
let satisfaction = metrics.avgSatisfaction;
|
|
2705
|
-
let gini = metrics.giniCoefficient;
|
|
2706
|
-
let velocity = metrics.velocity;
|
|
2707
|
-
let netFlow = metrics.netFlow;
|
|
2708
|
-
const churnRate = metrics.churnRate;
|
|
2709
2890
|
for (let t = 0; t < ticks; t++) {
|
|
2710
|
-
const
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
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;
|
|
2715
2901
|
satisfaction = Math.min(100, Math.max(0, satisfaction + satDelta * noise()));
|
|
2716
|
-
gini = gini * 0.99 + 0.35 * 0.01 * noise();
|
|
2717
|
-
velocity = supply / Math.max(1, metrics.totalAgents) * 0.01 * noise();
|
|
2718
|
-
const agentLoss = metrics.totalAgents * churnRate * noise();
|
|
2719
|
-
void agentLoss;
|
|
2720
2902
|
}
|
|
2903
|
+
const totalSupply = Object.values(supplies).reduce((s, v) => s + v, 0);
|
|
2721
2904
|
const projected = {
|
|
2722
2905
|
...metrics,
|
|
2723
2906
|
tick: metrics.tick + ticks,
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
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,
|
|
2728
2916
|
avgSatisfaction: satisfaction,
|
|
2729
|
-
inflationRate: metrics.totalSupply > 0 ? (
|
|
2917
|
+
inflationRate: metrics.totalSupply > 0 ? (totalSupply - metrics.totalSupply) / metrics.totalSupply : 0
|
|
2730
2918
|
};
|
|
2731
2919
|
return projected;
|
|
2732
2920
|
}
|
|
@@ -2734,16 +2922,16 @@ var Simulator = class {
|
|
|
2734
2922
|
const base = action.magnitude ?? 0.1;
|
|
2735
2923
|
return action.direction === "increase" ? 1 + base : 1 - base;
|
|
2736
2924
|
}
|
|
2737
|
-
flowEffect(action, metrics) {
|
|
2925
|
+
flowEffect(action, metrics, currency) {
|
|
2738
2926
|
const { parameter, direction } = action;
|
|
2739
2927
|
const sign = direction === "increase" ? -1 : 1;
|
|
2740
2928
|
const roleEntries = Object.entries(metrics.populationByRole).sort((a, b) => b[1] - a[1]);
|
|
2741
2929
|
const dominantRoleCount = roleEntries[0]?.[1] ?? 0;
|
|
2742
2930
|
if (parameter === "productionCost") {
|
|
2743
|
-
return sign * metrics.
|
|
2931
|
+
return sign * (metrics.netFlowByCurrency[currency] ?? 0) * 0.2;
|
|
2744
2932
|
}
|
|
2745
2933
|
if (parameter === "transactionFee") {
|
|
2746
|
-
return sign * metrics.
|
|
2934
|
+
return sign * (metrics.velocityByCurrency[currency] ?? 0) * 10 * 0.1;
|
|
2747
2935
|
}
|
|
2748
2936
|
if (parameter === "entryFee") {
|
|
2749
2937
|
return sign * dominantRoleCount * 0.5;
|
|
@@ -2752,14 +2940,22 @@ var Simulator = class {
|
|
|
2752
2940
|
return -sign * dominantRoleCount * 0.3;
|
|
2753
2941
|
}
|
|
2754
2942
|
if (parameter === "yieldRate") {
|
|
2755
|
-
return sign * metrics.
|
|
2943
|
+
return sign * (metrics.faucetVolumeByCurrency[currency] ?? 0) * 0.15;
|
|
2756
2944
|
}
|
|
2757
|
-
return sign * metrics.
|
|
2945
|
+
return sign * (metrics.netFlowByCurrency[currency] ?? 0) * 0.1;
|
|
2758
2946
|
}
|
|
2759
2947
|
checkImprovement(before, after, action) {
|
|
2760
2948
|
const satisfactionImproved = after.avgSatisfaction >= before.avgSatisfaction - 2;
|
|
2761
|
-
const flowMoreBalanced =
|
|
2762
|
-
|
|
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
|
+
});
|
|
2763
2959
|
void action;
|
|
2764
2960
|
return satisfactionImproved && flowMoreBalanced && notWorseGini;
|
|
2765
2961
|
}
|
|
@@ -2770,6 +2966,21 @@ var Simulator = class {
|
|
|
2770
2966
|
const vals = outcomes.map((o) => o[key]).filter((v) => typeof v === "number" && !isNaN(v));
|
|
2771
2967
|
return vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : 0;
|
|
2772
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
|
+
};
|
|
2773
2984
|
return {
|
|
2774
2985
|
...base,
|
|
2775
2986
|
totalSupply: avg("totalSupply"),
|
|
@@ -2777,7 +2988,11 @@ var Simulator = class {
|
|
|
2777
2988
|
velocity: avg("velocity"),
|
|
2778
2989
|
giniCoefficient: avg("giniCoefficient"),
|
|
2779
2990
|
avgSatisfaction: avg("avgSatisfaction"),
|
|
2780
|
-
inflationRate: avg("inflationRate")
|
|
2991
|
+
inflationRate: avg("inflationRate"),
|
|
2992
|
+
totalSupplyByCurrency: avgRecord("totalSupplyByCurrency"),
|
|
2993
|
+
netFlowByCurrency: avgRecord("netFlowByCurrency"),
|
|
2994
|
+
velocityByCurrency: avgRecord("velocityByCurrency"),
|
|
2995
|
+
giniCoefficientByCurrency: avgRecord("giniCoefficientByCurrency")
|
|
2781
2996
|
};
|
|
2782
2997
|
}
|
|
2783
2998
|
};
|
|
@@ -2836,6 +3051,7 @@ var Planner = class {
|
|
|
2836
3051
|
id: `plan_${metrics.tick}_${param}`,
|
|
2837
3052
|
diagnosis,
|
|
2838
3053
|
parameter: param,
|
|
3054
|
+
...action.currency !== void 0 ? { currency: action.currency } : {},
|
|
2839
3055
|
currentValue,
|
|
2840
3056
|
targetValue,
|
|
2841
3057
|
maxChangePercent: thresholds.maxAdjustmentPercent,
|
|
@@ -2877,7 +3093,7 @@ var Executor = class {
|
|
|
2877
3093
|
}
|
|
2878
3094
|
async apply(plan, adapter, currentParams) {
|
|
2879
3095
|
const originalValue = currentParams[plan.parameter] ?? plan.currentValue;
|
|
2880
|
-
await adapter.setParam(plan.parameter, plan.targetValue);
|
|
3096
|
+
await adapter.setParam(plan.parameter, plan.targetValue, plan.diagnosis.violation.suggestedAction.currency);
|
|
2881
3097
|
plan.appliedAt = plan.diagnosis.tick;
|
|
2882
3098
|
this.activePlans.push({ plan, originalValue });
|
|
2883
3099
|
}
|
|
@@ -2899,7 +3115,7 @@ var Executor = class {
|
|
|
2899
3115
|
const metricValue = this.getMetricValue(metrics, rc.metric);
|
|
2900
3116
|
const shouldRollback = rc.direction === "below" ? metricValue < rc.threshold : metricValue > rc.threshold;
|
|
2901
3117
|
if (shouldRollback) {
|
|
2902
|
-
await adapter.setParam(plan.parameter, originalValue);
|
|
3118
|
+
await adapter.setParam(plan.parameter, originalValue, plan.diagnosis.violation.suggestedAction.currency);
|
|
2903
3119
|
rolledBack.push(plan);
|
|
2904
3120
|
} else {
|
|
2905
3121
|
const settledTick = rc.checkAfterTick + 10;
|
|
@@ -3032,6 +3248,18 @@ var DecisionLog = class {
|
|
|
3032
3248
|
};
|
|
3033
3249
|
|
|
3034
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
|
+
}
|
|
3035
3263
|
var RingBuffer = class {
|
|
3036
3264
|
constructor(capacity) {
|
|
3037
3265
|
this.capacity = capacity;
|
|
@@ -3109,10 +3337,9 @@ var MetricStore = class {
|
|
|
3109
3337
|
if (q.to !== void 0 && m.tick > q.to) return false;
|
|
3110
3338
|
return true;
|
|
3111
3339
|
});
|
|
3112
|
-
const metricKey = q.metric;
|
|
3113
3340
|
const points = filtered.map((m) => ({
|
|
3114
3341
|
tick: m.tick,
|
|
3115
|
-
value:
|
|
3342
|
+
value: getNestedValue(m, q.metric)
|
|
3116
3343
|
}));
|
|
3117
3344
|
return { metric: q.metric, resolution, points };
|
|
3118
3345
|
}
|
|
@@ -3137,6 +3364,21 @@ var MetricStore = class {
|
|
|
3137
3364
|
const vals = snapshots.map((s) => s[key]).filter((v) => !isNaN(v));
|
|
3138
3365
|
return vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : 0;
|
|
3139
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
|
+
};
|
|
3140
3382
|
return {
|
|
3141
3383
|
...last,
|
|
3142
3384
|
totalSupply: avg("totalSupply"),
|
|
@@ -3156,7 +3398,21 @@ var MetricStore = class {
|
|
|
3156
3398
|
tapSinkRatio: avg("tapSinkRatio"),
|
|
3157
3399
|
productionIndex: avg("productionIndex"),
|
|
3158
3400
|
capacityUsage: avg("capacityUsage"),
|
|
3159
|
-
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")
|
|
3160
3416
|
};
|
|
3161
3417
|
}
|
|
3162
3418
|
};
|