@agent-e/core 1.2.1 → 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 +29 -9
- package/dist/index.d.mts +45 -18
- package/dist/index.d.ts +45 -18
- package/dist/index.js +603 -348
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +603 -348
- 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,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 =
|
|
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
|
-
|
|
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
|
-
|
|
224
|
+
faucetVolumeByCurrency[curr] = (faucetVolumeByCurrency[curr] ?? 0) + (e.amount ?? 0);
|
|
224
225
|
break;
|
|
225
226
|
case "burn":
|
|
226
227
|
case "consume":
|
|
227
|
-
|
|
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
|
|
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
|
|
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
|
|
253
|
-
const
|
|
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
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
|
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
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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
|
|
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
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
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 {
|
|
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,
|
|
1051
|
-
|
|
1052
|
-
const
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
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
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
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,
|
|
1107
|
-
const
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
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
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
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 {
|
|
1317
|
+
const { supplyByResource } = metrics;
|
|
1162
1318
|
const totalResources = Object.values(supplyByResource).reduce((s, v) => s + v, 0);
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
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
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
priceValues.
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
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
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
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
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
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 ? (
|
|
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.
|
|
2931
|
+
return sign * (metrics.netFlowByCurrency[currency] ?? 0) * 0.2;
|
|
2745
2932
|
}
|
|
2746
2933
|
if (parameter === "transactionFee") {
|
|
2747
|
-
return sign * metrics.
|
|
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.
|
|
2943
|
+
return sign * (metrics.faucetVolumeByCurrency[currency] ?? 0) * 0.15;
|
|
2757
2944
|
}
|
|
2758
|
-
return sign * metrics.
|
|
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 =
|
|
2763
|
-
|
|
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.diagnosis.violation.suggestedAction.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.diagnosis.violation.suggestedAction.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:
|
|
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
|
};
|