@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.mjs
CHANGED
|
@@ -84,9 +84,9 @@ var DEFAULT_TICK_CONFIG = {
|
|
|
84
84
|
var Observer = class {
|
|
85
85
|
constructor(tickConfig) {
|
|
86
86
|
this.previousMetrics = null;
|
|
87
|
-
this.
|
|
87
|
+
this.previousPricesByCurrency = {};
|
|
88
88
|
this.customMetricFns = {};
|
|
89
|
-
this.
|
|
89
|
+
this.anchorBaselineByCurrency = {};
|
|
90
90
|
this.tickConfig = { ...DEFAULT_TICK_CONFIG, ...tickConfig };
|
|
91
91
|
}
|
|
92
92
|
registerCustomMetric(name, fn) {
|
|
@@ -94,23 +94,28 @@ var Observer = class {
|
|
|
94
94
|
}
|
|
95
95
|
compute(state, recentEvents) {
|
|
96
96
|
const tick = state.tick;
|
|
97
|
-
const balances = Object.values(state.agentBalances);
|
|
98
97
|
const roles = Object.values(state.agentRoles);
|
|
99
|
-
const totalAgents =
|
|
100
|
-
let
|
|
101
|
-
|
|
98
|
+
const totalAgents = Object.keys(state.agentBalances).length;
|
|
99
|
+
let productionAmount = 0;
|
|
100
|
+
const faucetVolumeByCurrency = {};
|
|
101
|
+
const sinkVolumeByCurrency = {};
|
|
102
102
|
const tradeEvents = [];
|
|
103
103
|
const roleChangeEvents = [];
|
|
104
104
|
let churnCount = 0;
|
|
105
|
+
const defaultCurrency = state.currencies[0] ?? "default";
|
|
105
106
|
for (const e of recentEvents) {
|
|
107
|
+
const curr = e.currency ?? defaultCurrency;
|
|
106
108
|
switch (e.type) {
|
|
107
109
|
case "mint":
|
|
108
110
|
case "spawn":
|
|
109
|
-
|
|
111
|
+
faucetVolumeByCurrency[curr] = (faucetVolumeByCurrency[curr] ?? 0) + (e.amount ?? 0);
|
|
110
112
|
break;
|
|
111
113
|
case "burn":
|
|
112
114
|
case "consume":
|
|
113
|
-
|
|
115
|
+
sinkVolumeByCurrency[curr] = (sinkVolumeByCurrency[curr] ?? 0) + (e.amount ?? 0);
|
|
116
|
+
break;
|
|
117
|
+
case "produce":
|
|
118
|
+
productionAmount += e.amount ?? 1;
|
|
114
119
|
break;
|
|
115
120
|
case "trade":
|
|
116
121
|
tradeEvents.push(e);
|
|
@@ -124,20 +129,68 @@ var Observer = class {
|
|
|
124
129
|
break;
|
|
125
130
|
}
|
|
126
131
|
}
|
|
127
|
-
const
|
|
132
|
+
const currencies = state.currencies;
|
|
133
|
+
const totalSupplyByCurrency = {};
|
|
134
|
+
const balancesByCurrency = {};
|
|
135
|
+
for (const [_agentId, balances] of Object.entries(state.agentBalances)) {
|
|
136
|
+
for (const [curr, bal] of Object.entries(balances)) {
|
|
137
|
+
totalSupplyByCurrency[curr] = (totalSupplyByCurrency[curr] ?? 0) + bal;
|
|
138
|
+
if (!balancesByCurrency[curr]) balancesByCurrency[curr] = [];
|
|
139
|
+
balancesByCurrency[curr].push(bal);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const netFlowByCurrency = {};
|
|
143
|
+
const tapSinkRatioByCurrency = {};
|
|
144
|
+
const inflationRateByCurrency = {};
|
|
145
|
+
const velocityByCurrency = {};
|
|
146
|
+
for (const curr of currencies) {
|
|
147
|
+
const faucet = faucetVolumeByCurrency[curr] ?? 0;
|
|
148
|
+
const sink = sinkVolumeByCurrency[curr] ?? 0;
|
|
149
|
+
netFlowByCurrency[curr] = faucet - sink;
|
|
150
|
+
tapSinkRatioByCurrency[curr] = sink > 0 ? faucet / sink : faucet > 0 ? Infinity : 1;
|
|
151
|
+
const prevSupply = this.previousMetrics?.totalSupplyByCurrency?.[curr] ?? totalSupplyByCurrency[curr] ?? 0;
|
|
152
|
+
const currSupply = totalSupplyByCurrency[curr] ?? 0;
|
|
153
|
+
inflationRateByCurrency[curr] = prevSupply > 0 ? (currSupply - prevSupply) / prevSupply : 0;
|
|
154
|
+
const currTrades = tradeEvents.filter((e) => (e.currency ?? defaultCurrency) === curr);
|
|
155
|
+
velocityByCurrency[curr] = currSupply > 0 ? currTrades.length / currSupply : 0;
|
|
156
|
+
}
|
|
157
|
+
const giniCoefficientByCurrency = {};
|
|
158
|
+
const medianBalanceByCurrency = {};
|
|
159
|
+
const meanBalanceByCurrency = {};
|
|
160
|
+
const top10PctShareByCurrency = {};
|
|
161
|
+
const meanMedianDivergenceByCurrency = {};
|
|
162
|
+
for (const curr of currencies) {
|
|
163
|
+
const bals = balancesByCurrency[curr] ?? [];
|
|
164
|
+
const sorted = [...bals].sort((a, b) => a - b);
|
|
165
|
+
const supply = totalSupplyByCurrency[curr] ?? 0;
|
|
166
|
+
const count = sorted.length;
|
|
167
|
+
const median = computeMedian(sorted);
|
|
168
|
+
const mean = count > 0 ? supply / count : 0;
|
|
169
|
+
const top10Idx = Math.floor(count * 0.9);
|
|
170
|
+
const top10Sum = sorted.slice(top10Idx).reduce((s, b) => s + b, 0);
|
|
171
|
+
giniCoefficientByCurrency[curr] = computeGini(sorted);
|
|
172
|
+
medianBalanceByCurrency[curr] = median;
|
|
173
|
+
meanBalanceByCurrency[curr] = mean;
|
|
174
|
+
top10PctShareByCurrency[curr] = supply > 0 ? top10Sum / supply : 0;
|
|
175
|
+
meanMedianDivergenceByCurrency[curr] = median > 0 ? Math.abs(mean - median) / median : 0;
|
|
176
|
+
}
|
|
177
|
+
const avgOf = (rec) => {
|
|
178
|
+
const vals = Object.values(rec);
|
|
179
|
+
return vals.length > 0 ? vals.reduce((s, v) => s + v, 0) / vals.length : 0;
|
|
180
|
+
};
|
|
181
|
+
const totalSupply = Object.values(totalSupplyByCurrency).reduce((s, v) => s + v, 0);
|
|
182
|
+
const faucetVolume = Object.values(faucetVolumeByCurrency).reduce((s, v) => s + v, 0);
|
|
183
|
+
const sinkVolume = Object.values(sinkVolumeByCurrency).reduce((s, v) => s + v, 0);
|
|
128
184
|
const netFlow = faucetVolume - sinkVolume;
|
|
129
185
|
const tapSinkRatio = sinkVolume > 0 ? faucetVolume / sinkVolume : faucetVolume > 0 ? Infinity : 1;
|
|
130
|
-
const prevSupply = this.previousMetrics?.totalSupply ?? totalSupply;
|
|
131
|
-
const inflationRate = prevSupply > 0 ? (totalSupply - prevSupply) / prevSupply : 0;
|
|
132
186
|
const velocity = totalSupply > 0 ? tradeEvents.length / totalSupply : 0;
|
|
133
|
-
const
|
|
187
|
+
const prevTotalSupply = this.previousMetrics?.totalSupply ?? totalSupply;
|
|
188
|
+
const inflationRate = prevTotalSupply > 0 ? (totalSupply - prevTotalSupply) / prevTotalSupply : 0;
|
|
189
|
+
const giniCoefficient = avgOf(giniCoefficientByCurrency);
|
|
190
|
+
const medianBalance = avgOf(medianBalanceByCurrency);
|
|
134
191
|
const meanBalance = totalAgents > 0 ? totalSupply / totalAgents : 0;
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
const top10Sum = sortedBalances.slice(top10Idx).reduce((s, b) => s + b, 0);
|
|
138
|
-
const top10PctShare = totalSupply > 0 ? top10Sum / totalSupply : 0;
|
|
139
|
-
const giniCoefficient = computeGini(sortedBalances);
|
|
140
|
-
const meanMedianDivergence = medianBalance > 0 ? Math.abs(meanBalance - medianBalance) / medianBalance : 0;
|
|
192
|
+
const top10PctShare = avgOf(top10PctShareByCurrency);
|
|
193
|
+
const meanMedianDivergence = avgOf(meanMedianDivergenceByCurrency);
|
|
141
194
|
const populationByRole = {};
|
|
142
195
|
const roleShares = {};
|
|
143
196
|
for (const role of roles) {
|
|
@@ -152,15 +205,25 @@ var Observer = class {
|
|
|
152
205
|
churnByRole[role] = (churnByRole[role] ?? 0) + 1;
|
|
153
206
|
}
|
|
154
207
|
const churnRate = churnCount / Math.max(1, totalAgents);
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
208
|
+
const pricesByCurrency = {};
|
|
209
|
+
const priceVolatilityByCurrency = {};
|
|
210
|
+
const priceIndexByCurrency = {};
|
|
211
|
+
for (const [curr, resourcePrices] of Object.entries(state.marketPrices)) {
|
|
212
|
+
pricesByCurrency[curr] = { ...resourcePrices };
|
|
213
|
+
const pricePrev = this.previousPricesByCurrency?.[curr] ?? {};
|
|
214
|
+
const volMap = {};
|
|
215
|
+
for (const [resource, price] of Object.entries(resourcePrices)) {
|
|
216
|
+
const prev = pricePrev[resource] ?? price;
|
|
217
|
+
volMap[resource] = prev > 0 ? Math.abs(price - prev) / prev : 0;
|
|
218
|
+
}
|
|
219
|
+
priceVolatilityByCurrency[curr] = volMap;
|
|
220
|
+
const pVals = Object.values(resourcePrices);
|
|
221
|
+
priceIndexByCurrency[curr] = pVals.length > 0 ? pVals.reduce((s, p) => s + p, 0) / pVals.length : 0;
|
|
222
|
+
}
|
|
223
|
+
this.previousPricesByCurrency = JSON.parse(JSON.stringify(pricesByCurrency));
|
|
224
|
+
const prices = pricesByCurrency[defaultCurrency] ?? {};
|
|
225
|
+
const priceVolatility = priceVolatilityByCurrency[defaultCurrency] ?? {};
|
|
226
|
+
const priceIndex = priceIndexByCurrency[defaultCurrency] ?? 0;
|
|
164
227
|
const supplyByResource = {};
|
|
165
228
|
for (const inv of Object.values(state.agentInventories)) {
|
|
166
229
|
for (const [resource, qty] of Object.entries(inv)) {
|
|
@@ -185,70 +248,92 @@ var Observer = class {
|
|
|
185
248
|
pinchPoints[resource] = "optimal";
|
|
186
249
|
}
|
|
187
250
|
}
|
|
188
|
-
const productionIndex =
|
|
251
|
+
const productionIndex = productionAmount;
|
|
189
252
|
const maxPossibleProduction = productionIndex + sinkVolume;
|
|
190
253
|
const capacityUsage = maxPossibleProduction > 0 ? productionIndex / maxPossibleProduction : 0;
|
|
191
254
|
const satisfactions = Object.values(state.agentSatisfaction ?? {});
|
|
192
255
|
const avgSatisfaction = satisfactions.length > 0 ? satisfactions.reduce((s, v) => s + v, 0) / satisfactions.length : 80;
|
|
193
256
|
const blockedAgentCount = satisfactions.filter((s) => s < 20).length;
|
|
194
257
|
const timeToValue = totalAgents > 0 ? blockedAgentCount / totalAgents * 100 : 0;
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
258
|
+
const poolSizesByCurrency = {};
|
|
259
|
+
const poolSizesAggregate = {};
|
|
260
|
+
if (state.poolSizes) {
|
|
261
|
+
for (const [pool, currencyAmounts] of Object.entries(state.poolSizes)) {
|
|
262
|
+
poolSizesByCurrency[pool] = { ...currencyAmounts };
|
|
263
|
+
poolSizesAggregate[pool] = Object.values(currencyAmounts).reduce((s, v) => s + v, 0);
|
|
264
|
+
}
|
|
201
265
|
}
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
let totalDivergence = 0;
|
|
212
|
-
for (let i = 0; i < priceKeys.length; i++) {
|
|
213
|
-
for (let j = i + 1; j < priceKeys.length; j++) {
|
|
214
|
-
const pA = prices[priceKeys[i]];
|
|
215
|
-
const pB = prices[priceKeys[j]];
|
|
216
|
-
let ratio = pA / pB;
|
|
217
|
-
ratio = Math.max(1e-3, Math.min(1e3, ratio));
|
|
218
|
-
totalDivergence += Math.abs(Math.log(ratio));
|
|
219
|
-
pairCount++;
|
|
266
|
+
const anchorRatioDriftByCurrency = {};
|
|
267
|
+
if (tick === 1) {
|
|
268
|
+
for (const curr of currencies) {
|
|
269
|
+
const supply = totalSupplyByCurrency[curr] ?? 0;
|
|
270
|
+
if (supply > 0) {
|
|
271
|
+
this.anchorBaselineByCurrency[curr] = {
|
|
272
|
+
currencyPerPeriod: supply / Math.max(1, totalAgents),
|
|
273
|
+
itemsPerCurrency: (priceIndexByCurrency[curr] ?? 0) > 0 ? 1 / priceIndexByCurrency[curr] : 0
|
|
274
|
+
};
|
|
220
275
|
}
|
|
221
276
|
}
|
|
222
|
-
arbitrageIndex = pairCount > 0 ? Math.min(1, totalDivergence / pairCount) : 0;
|
|
223
277
|
}
|
|
278
|
+
for (const curr of currencies) {
|
|
279
|
+
const baseline = this.anchorBaselineByCurrency[curr];
|
|
280
|
+
if (baseline && totalAgents > 0) {
|
|
281
|
+
const currentCPP = (totalSupplyByCurrency[curr] ?? 0) / totalAgents;
|
|
282
|
+
anchorRatioDriftByCurrency[curr] = baseline.currencyPerPeriod > 0 ? (currentCPP - baseline.currencyPerPeriod) / baseline.currencyPerPeriod : 0;
|
|
283
|
+
} else {
|
|
284
|
+
anchorRatioDriftByCurrency[curr] = 0;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const anchorRatioDrift = avgOf(anchorRatioDriftByCurrency);
|
|
288
|
+
const arbitrageIndexByCurrency = {};
|
|
289
|
+
for (const curr of currencies) {
|
|
290
|
+
const cPrices = pricesByCurrency[curr] ?? {};
|
|
291
|
+
const priceKeys = Object.keys(cPrices).filter((k) => cPrices[k] > 0);
|
|
292
|
+
if (priceKeys.length >= 2) {
|
|
293
|
+
let pairCount = 0;
|
|
294
|
+
let totalDivergence = 0;
|
|
295
|
+
for (let i = 0; i < priceKeys.length; i++) {
|
|
296
|
+
for (let j = i + 1; j < priceKeys.length; j++) {
|
|
297
|
+
const pA = cPrices[priceKeys[i]];
|
|
298
|
+
const pB = cPrices[priceKeys[j]];
|
|
299
|
+
let ratio = pA / pB;
|
|
300
|
+
ratio = Math.max(1e-3, Math.min(1e3, ratio));
|
|
301
|
+
totalDivergence += Math.abs(Math.log(ratio));
|
|
302
|
+
pairCount++;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
arbitrageIndexByCurrency[curr] = pairCount > 0 ? Math.min(1, totalDivergence / pairCount) : 0;
|
|
306
|
+
} else {
|
|
307
|
+
arbitrageIndexByCurrency[curr] = 0;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const arbitrageIndex = avgOf(arbitrageIndexByCurrency);
|
|
224
311
|
const contentDropEvents = recentEvents.filter(
|
|
225
312
|
(e) => e.metadata?.["contentDrop"] === true
|
|
226
313
|
);
|
|
227
314
|
const contentDropAge = contentDropEvents.length > 0 ? tick - Math.max(...contentDropEvents.map((e) => e.timestamp)) : (this.previousMetrics?.contentDropAge ?? 0) + 1;
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
315
|
+
const giftTradeRatioByCurrency = {};
|
|
316
|
+
const disposalTradeRatioByCurrency = {};
|
|
317
|
+
for (const curr of currencies) {
|
|
318
|
+
const currTrades = tradeEvents.filter((e) => (e.currency ?? defaultCurrency) === curr);
|
|
319
|
+
const cPrices = pricesByCurrency[curr] ?? {};
|
|
320
|
+
let gifts = 0;
|
|
321
|
+
let disposals = 0;
|
|
322
|
+
for (const e of currTrades) {
|
|
323
|
+
const marketPrice = cPrices[e.resource ?? ""] ?? 0;
|
|
232
324
|
const tradePrice = e.price ?? 0;
|
|
233
|
-
if (tradePrice === 0 || marketPrice > 0 && tradePrice < marketPrice * 0.3)
|
|
234
|
-
giftTrades++;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
const giftTradeRatio = tradeEvents.length > 0 ? giftTrades / tradeEvents.length : 0;
|
|
239
|
-
let disposalTrades = 0;
|
|
240
|
-
if (tradeEvents.length > 0) {
|
|
241
|
-
for (const e of tradeEvents) {
|
|
325
|
+
if (tradePrice === 0 || marketPrice > 0 && tradePrice < marketPrice * 0.3) gifts++;
|
|
242
326
|
if (e.from && e.resource) {
|
|
243
327
|
const sellerInv = state.agentInventories[e.from]?.[e.resource] ?? 0;
|
|
244
328
|
const avgInv = (supplyByResource[e.resource] ?? 0) / Math.max(1, totalAgents);
|
|
245
|
-
if (sellerInv > avgInv * 3)
|
|
246
|
-
disposalTrades++;
|
|
247
|
-
}
|
|
329
|
+
if (sellerInv > avgInv * 3) disposals++;
|
|
248
330
|
}
|
|
249
331
|
}
|
|
332
|
+
giftTradeRatioByCurrency[curr] = currTrades.length > 0 ? gifts / currTrades.length : 0;
|
|
333
|
+
disposalTradeRatioByCurrency[curr] = currTrades.length > 0 ? disposals / currTrades.length : 0;
|
|
250
334
|
}
|
|
251
|
-
const
|
|
335
|
+
const giftTradeRatio = avgOf(giftTradeRatioByCurrency);
|
|
336
|
+
const disposalTradeRatio = avgOf(disposalTradeRatioByCurrency);
|
|
252
337
|
const custom = {};
|
|
253
338
|
for (const [name, fn] of Object.entries(this.customMetricFns)) {
|
|
254
339
|
try {
|
|
@@ -260,10 +345,57 @@ var Observer = class {
|
|
|
260
345
|
const metrics = {
|
|
261
346
|
tick,
|
|
262
347
|
timestamp: Date.now(),
|
|
348
|
+
currencies,
|
|
349
|
+
// Per-currency
|
|
350
|
+
totalSupplyByCurrency,
|
|
351
|
+
netFlowByCurrency,
|
|
352
|
+
velocityByCurrency,
|
|
353
|
+
inflationRateByCurrency,
|
|
354
|
+
faucetVolumeByCurrency,
|
|
355
|
+
sinkVolumeByCurrency,
|
|
356
|
+
tapSinkRatioByCurrency,
|
|
357
|
+
anchorRatioDriftByCurrency,
|
|
358
|
+
giniCoefficientByCurrency,
|
|
359
|
+
medianBalanceByCurrency,
|
|
360
|
+
meanBalanceByCurrency,
|
|
361
|
+
top10PctShareByCurrency,
|
|
362
|
+
meanMedianDivergenceByCurrency,
|
|
363
|
+
priceIndexByCurrency,
|
|
364
|
+
pricesByCurrency,
|
|
365
|
+
priceVolatilityByCurrency,
|
|
366
|
+
poolSizesByCurrency,
|
|
367
|
+
extractionRatioByCurrency: {},
|
|
368
|
+
newUserDependencyByCurrency: {},
|
|
369
|
+
currencyInsulationByCurrency: {},
|
|
370
|
+
arbitrageIndexByCurrency,
|
|
371
|
+
giftTradeRatioByCurrency,
|
|
372
|
+
disposalTradeRatioByCurrency,
|
|
373
|
+
// Aggregates
|
|
263
374
|
totalSupply,
|
|
264
375
|
netFlow,
|
|
265
376
|
velocity,
|
|
266
377
|
inflationRate,
|
|
378
|
+
faucetVolume,
|
|
379
|
+
sinkVolume,
|
|
380
|
+
tapSinkRatio,
|
|
381
|
+
anchorRatioDrift,
|
|
382
|
+
giniCoefficient,
|
|
383
|
+
medianBalance,
|
|
384
|
+
meanBalance,
|
|
385
|
+
top10PctShare,
|
|
386
|
+
meanMedianDivergence,
|
|
387
|
+
priceIndex,
|
|
388
|
+
prices,
|
|
389
|
+
priceVolatility,
|
|
390
|
+
poolSizes: poolSizesAggregate,
|
|
391
|
+
extractionRatio: NaN,
|
|
392
|
+
newUserDependency: NaN,
|
|
393
|
+
smokeTestRatio: NaN,
|
|
394
|
+
currencyInsulation: NaN,
|
|
395
|
+
arbitrageIndex,
|
|
396
|
+
giftTradeRatio,
|
|
397
|
+
disposalTradeRatio,
|
|
398
|
+
// Unchanged
|
|
267
399
|
populationByRole,
|
|
268
400
|
roleShares,
|
|
269
401
|
totalAgents,
|
|
@@ -271,38 +403,18 @@ var Observer = class {
|
|
|
271
403
|
churnByRole,
|
|
272
404
|
personaDistribution: {},
|
|
273
405
|
// populated by PersonaTracker
|
|
274
|
-
giniCoefficient,
|
|
275
|
-
medianBalance,
|
|
276
|
-
meanBalance,
|
|
277
|
-
top10PctShare,
|
|
278
|
-
meanMedianDivergence,
|
|
279
|
-
priceIndex,
|
|
280
406
|
productionIndex,
|
|
281
407
|
capacityUsage,
|
|
282
|
-
prices,
|
|
283
|
-
priceVolatility,
|
|
284
408
|
supplyByResource,
|
|
285
409
|
demandSignals,
|
|
286
410
|
pinchPoints,
|
|
287
411
|
avgSatisfaction,
|
|
288
412
|
blockedAgentCount,
|
|
289
413
|
timeToValue,
|
|
290
|
-
faucetVolume,
|
|
291
|
-
sinkVolume,
|
|
292
|
-
tapSinkRatio,
|
|
293
|
-
poolSizes,
|
|
294
|
-
anchorRatioDrift,
|
|
295
|
-
extractionRatio: NaN,
|
|
296
|
-
newUserDependency: NaN,
|
|
297
|
-
smokeTestRatio: NaN,
|
|
298
|
-
currencyInsulation: NaN,
|
|
299
414
|
sharkToothPeaks: this.previousMetrics?.sharkToothPeaks ?? [],
|
|
300
415
|
sharkToothValleys: this.previousMetrics?.sharkToothValleys ?? [],
|
|
301
416
|
eventCompletionRate: NaN,
|
|
302
|
-
arbitrageIndex,
|
|
303
417
|
contentDropAge,
|
|
304
|
-
giftTradeRatio,
|
|
305
|
-
disposalTradeRatio,
|
|
306
418
|
custom
|
|
307
419
|
};
|
|
308
420
|
this.previousMetrics = metrics;
|
|
@@ -376,48 +488,75 @@ function emptyMetrics(tick = 0) {
|
|
|
376
488
|
return {
|
|
377
489
|
tick,
|
|
378
490
|
timestamp: Date.now(),
|
|
491
|
+
currencies: [],
|
|
492
|
+
// Per-currency
|
|
493
|
+
totalSupplyByCurrency: {},
|
|
494
|
+
netFlowByCurrency: {},
|
|
495
|
+
velocityByCurrency: {},
|
|
496
|
+
inflationRateByCurrency: {},
|
|
497
|
+
faucetVolumeByCurrency: {},
|
|
498
|
+
sinkVolumeByCurrency: {},
|
|
499
|
+
tapSinkRatioByCurrency: {},
|
|
500
|
+
anchorRatioDriftByCurrency: {},
|
|
501
|
+
giniCoefficientByCurrency: {},
|
|
502
|
+
medianBalanceByCurrency: {},
|
|
503
|
+
meanBalanceByCurrency: {},
|
|
504
|
+
top10PctShareByCurrency: {},
|
|
505
|
+
meanMedianDivergenceByCurrency: {},
|
|
506
|
+
priceIndexByCurrency: {},
|
|
507
|
+
pricesByCurrency: {},
|
|
508
|
+
priceVolatilityByCurrency: {},
|
|
509
|
+
poolSizesByCurrency: {},
|
|
510
|
+
extractionRatioByCurrency: {},
|
|
511
|
+
newUserDependencyByCurrency: {},
|
|
512
|
+
currencyInsulationByCurrency: {},
|
|
513
|
+
arbitrageIndexByCurrency: {},
|
|
514
|
+
giftTradeRatioByCurrency: {},
|
|
515
|
+
disposalTradeRatioByCurrency: {},
|
|
516
|
+
// Aggregates
|
|
379
517
|
totalSupply: 0,
|
|
380
518
|
netFlow: 0,
|
|
381
519
|
velocity: 0,
|
|
382
520
|
inflationRate: 0,
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
churnByRole: {},
|
|
388
|
-
personaDistribution: {},
|
|
521
|
+
faucetVolume: 0,
|
|
522
|
+
sinkVolume: 0,
|
|
523
|
+
tapSinkRatio: 1,
|
|
524
|
+
anchorRatioDrift: 0,
|
|
389
525
|
giniCoefficient: 0,
|
|
390
526
|
medianBalance: 0,
|
|
391
527
|
meanBalance: 0,
|
|
392
528
|
top10PctShare: 0,
|
|
393
529
|
meanMedianDivergence: 0,
|
|
394
530
|
priceIndex: 0,
|
|
395
|
-
productionIndex: 0,
|
|
396
|
-
capacityUsage: 0,
|
|
397
531
|
prices: {},
|
|
398
532
|
priceVolatility: {},
|
|
533
|
+
poolSizes: {},
|
|
534
|
+
extractionRatio: NaN,
|
|
535
|
+
newUserDependency: NaN,
|
|
536
|
+
smokeTestRatio: NaN,
|
|
537
|
+
currencyInsulation: NaN,
|
|
538
|
+
arbitrageIndex: 0,
|
|
539
|
+
giftTradeRatio: 0,
|
|
540
|
+
disposalTradeRatio: 0,
|
|
541
|
+
// Unchanged
|
|
542
|
+
populationByRole: {},
|
|
543
|
+
roleShares: {},
|
|
544
|
+
totalAgents: 0,
|
|
545
|
+
churnRate: 0,
|
|
546
|
+
churnByRole: {},
|
|
547
|
+
personaDistribution: {},
|
|
548
|
+
productionIndex: 0,
|
|
549
|
+
capacityUsage: 0,
|
|
399
550
|
supplyByResource: {},
|
|
400
551
|
demandSignals: {},
|
|
401
552
|
pinchPoints: {},
|
|
402
553
|
avgSatisfaction: 100,
|
|
403
554
|
blockedAgentCount: 0,
|
|
404
555
|
timeToValue: 0,
|
|
405
|
-
faucetVolume: 0,
|
|
406
|
-
sinkVolume: 0,
|
|
407
|
-
tapSinkRatio: 1,
|
|
408
|
-
poolSizes: {},
|
|
409
|
-
anchorRatioDrift: 0,
|
|
410
|
-
extractionRatio: NaN,
|
|
411
|
-
newUserDependency: NaN,
|
|
412
|
-
smokeTestRatio: NaN,
|
|
413
|
-
currencyInsulation: NaN,
|
|
414
556
|
sharkToothPeaks: [],
|
|
415
557
|
sharkToothValleys: [],
|
|
416
558
|
eventCompletionRate: NaN,
|
|
417
|
-
arbitrageIndex: 0,
|
|
418
559
|
contentDropAge: 0,
|
|
419
|
-
giftTradeRatio: 0,
|
|
420
|
-
disposalTradeRatio: 0,
|
|
421
560
|
custom: {}
|
|
422
561
|
};
|
|
423
562
|
}
|
|
@@ -883,38 +1022,44 @@ var P12_OnePrimaryFaucet = {
|
|
|
883
1022
|
id: "P12",
|
|
884
1023
|
name: "One Primary Faucet",
|
|
885
1024
|
category: "currency",
|
|
886
|
-
description: "Multiple independent currency sources (gathering +
|
|
1025
|
+
description: "Multiple independent currency sources (gathering + production + quests) each creating currency causes uncontrolled inflation. One clear primary faucet makes the economy predictable and auditable.",
|
|
887
1026
|
check(metrics, thresholds) {
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1027
|
+
for (const curr of metrics.currencies) {
|
|
1028
|
+
const netFlow = metrics.netFlowByCurrency[curr] ?? 0;
|
|
1029
|
+
const faucetVolume = metrics.faucetVolumeByCurrency[curr] ?? 0;
|
|
1030
|
+
const sinkVolume = metrics.sinkVolumeByCurrency[curr] ?? 0;
|
|
1031
|
+
if (netFlow > thresholds.netFlowWarnThreshold) {
|
|
1032
|
+
return {
|
|
1033
|
+
violated: true,
|
|
1034
|
+
severity: 5,
|
|
1035
|
+
evidence: { currency: curr, netFlow, faucetVolume, sinkVolume },
|
|
1036
|
+
suggestedAction: {
|
|
1037
|
+
parameter: "productionCost",
|
|
1038
|
+
direction: "increase",
|
|
1039
|
+
currency: curr,
|
|
1040
|
+
magnitude: 0.15,
|
|
1041
|
+
reasoning: `[${curr}] Net flow +${netFlow.toFixed(1)}/tick. Inflationary. Increase production cost (primary sink) to balance faucet output.`
|
|
1042
|
+
},
|
|
1043
|
+
confidence: 0.8,
|
|
1044
|
+
estimatedLag: 8
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
if (netFlow < -thresholds.netFlowWarnThreshold) {
|
|
1048
|
+
return {
|
|
1049
|
+
violated: true,
|
|
1050
|
+
severity: 4,
|
|
1051
|
+
evidence: { currency: curr, netFlow, faucetVolume, sinkVolume },
|
|
1052
|
+
suggestedAction: {
|
|
1053
|
+
parameter: "productionCost",
|
|
1054
|
+
direction: "decrease",
|
|
1055
|
+
currency: curr,
|
|
1056
|
+
magnitude: 0.15,
|
|
1057
|
+
reasoning: `[${curr}] Net flow ${netFlow.toFixed(1)}/tick. Deflationary. Decrease production cost to ease sink pressure.`
|
|
1058
|
+
},
|
|
1059
|
+
confidence: 0.8,
|
|
1060
|
+
estimatedLag: 8
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
918
1063
|
}
|
|
919
1064
|
return { violated: false };
|
|
920
1065
|
}
|
|
@@ -925,28 +1070,30 @@ var P13_PotsAreZeroSumAndSelfRegulate = {
|
|
|
925
1070
|
category: "currency",
|
|
926
1071
|
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.",
|
|
927
1072
|
check(metrics, thresholds) {
|
|
928
|
-
const {
|
|
929
|
-
const totalAgents = metrics.totalAgents;
|
|
1073
|
+
const { populationByRole } = metrics;
|
|
930
1074
|
const roleEntries = Object.entries(populationByRole).sort((a, b) => b[1] - a[1]);
|
|
931
|
-
const dominantRole = roleEntries[0]?.[0];
|
|
932
1075
|
const dominantCount = roleEntries[0]?.[1] ?? 0;
|
|
933
|
-
for (const [poolName,
|
|
934
|
-
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1076
|
+
for (const [poolName, currencyAmounts] of Object.entries(metrics.poolSizesByCurrency)) {
|
|
1077
|
+
for (const curr of metrics.currencies) {
|
|
1078
|
+
const poolSize = currencyAmounts[curr] ?? 0;
|
|
1079
|
+
if (dominantCount > 5 && poolSize < 50) {
|
|
1080
|
+
const { poolWinRate, poolHouseCut } = thresholds;
|
|
1081
|
+
const maxSustainableMultiplier = (1 - poolHouseCut) / poolWinRate;
|
|
1082
|
+
return {
|
|
1083
|
+
violated: true,
|
|
1084
|
+
severity: 7,
|
|
1085
|
+
evidence: { currency: curr, pool: poolName, poolSize, participants: dominantCount, maxSustainableMultiplier },
|
|
1086
|
+
suggestedAction: {
|
|
1087
|
+
parameter: "rewardRate",
|
|
1088
|
+
direction: "decrease",
|
|
1089
|
+
currency: curr,
|
|
1090
|
+
magnitude: 0.15,
|
|
1091
|
+
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.`
|
|
1092
|
+
},
|
|
1093
|
+
confidence: 0.85,
|
|
1094
|
+
estimatedLag: 3
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
950
1097
|
}
|
|
951
1098
|
}
|
|
952
1099
|
return { violated: false };
|
|
@@ -958,22 +1105,27 @@ var P14_TrackActualInjection = {
|
|
|
958
1105
|
category: "currency",
|
|
959
1106
|
description: 'Counting resource gathering as "currency injected" is misleading. Currency enters through faucet mechanisms (spawning, rewards). Fake metrics break every downstream decision.',
|
|
960
1107
|
check(metrics, _thresholds) {
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1108
|
+
for (const curr of metrics.currencies) {
|
|
1109
|
+
const faucetVolume = metrics.faucetVolumeByCurrency[curr] ?? 0;
|
|
1110
|
+
const netFlow = metrics.netFlowByCurrency[curr] ?? 0;
|
|
1111
|
+
const totalSupply = metrics.totalSupplyByCurrency[curr] ?? 0;
|
|
1112
|
+
const supplyGrowthRate = Math.abs(netFlow) / Math.max(1, totalSupply);
|
|
1113
|
+
if (supplyGrowthRate > 0.1) {
|
|
1114
|
+
return {
|
|
1115
|
+
violated: true,
|
|
1116
|
+
severity: 4,
|
|
1117
|
+
evidence: { currency: curr, faucetVolume, netFlow, supplyGrowthRate },
|
|
1118
|
+
suggestedAction: {
|
|
1119
|
+
parameter: "yieldRate",
|
|
1120
|
+
direction: "decrease",
|
|
1121
|
+
currency: curr,
|
|
1122
|
+
magnitude: 0.1,
|
|
1123
|
+
reasoning: `[${curr}] Supply growing at ${(supplyGrowthRate * 100).toFixed(1)}%/tick. Verify currency injection tracking. Resources should not create currency directly.`
|
|
1124
|
+
},
|
|
1125
|
+
confidence: 0.55,
|
|
1126
|
+
estimatedLag: 5
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
977
1129
|
}
|
|
978
1130
|
return { violated: false };
|
|
979
1131
|
}
|
|
@@ -982,26 +1134,30 @@ var P15_PoolsNeedCapAndDecay = {
|
|
|
982
1134
|
id: "P15",
|
|
983
1135
|
name: "Pools Need Cap + Decay",
|
|
984
1136
|
category: "currency",
|
|
985
|
-
description: "Any pool (bank, reward pool) without a cap accumulates infinitely.
|
|
1137
|
+
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.",
|
|
986
1138
|
check(metrics, thresholds) {
|
|
987
|
-
const { poolSizes, totalSupply } = metrics;
|
|
988
1139
|
const { poolCapPercent } = thresholds;
|
|
989
|
-
for (const [pool,
|
|
990
|
-
const
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1140
|
+
for (const [pool, currencyAmounts] of Object.entries(metrics.poolSizesByCurrency)) {
|
|
1141
|
+
for (const curr of metrics.currencies) {
|
|
1142
|
+
const size = currencyAmounts[curr] ?? 0;
|
|
1143
|
+
const totalSupply = metrics.totalSupplyByCurrency[curr] ?? 0;
|
|
1144
|
+
const shareOfSupply = size / Math.max(1, totalSupply);
|
|
1145
|
+
if (shareOfSupply > poolCapPercent * 2) {
|
|
1146
|
+
return {
|
|
1147
|
+
violated: true,
|
|
1148
|
+
severity: 6,
|
|
1149
|
+
evidence: { currency: curr, pool, size, shareOfSupply, cap: poolCapPercent },
|
|
1150
|
+
suggestedAction: {
|
|
1151
|
+
parameter: "transactionFee",
|
|
1152
|
+
direction: "decrease",
|
|
1153
|
+
currency: curr,
|
|
1154
|
+
magnitude: 0.1,
|
|
1155
|
+
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.`
|
|
1156
|
+
},
|
|
1157
|
+
confidence: 0.85,
|
|
1158
|
+
estimatedLag: 5
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1005
1161
|
}
|
|
1006
1162
|
}
|
|
1007
1163
|
return { violated: false };
|
|
@@ -1013,23 +1169,27 @@ var P16_WithdrawalPenaltyScales = {
|
|
|
1013
1169
|
category: "currency",
|
|
1014
1170
|
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.",
|
|
1015
1171
|
check(metrics, _thresholds) {
|
|
1016
|
-
const
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1172
|
+
for (const [poolName, currencyAmounts] of Object.entries(metrics.poolSizesByCurrency)) {
|
|
1173
|
+
for (const curr of metrics.currencies) {
|
|
1174
|
+
const poolSize = currencyAmounts[curr] ?? 0;
|
|
1175
|
+
const totalSupply = metrics.totalSupplyByCurrency[curr] ?? 0;
|
|
1176
|
+
const stakedEstimate = totalSupply * 0.15;
|
|
1177
|
+
if (poolSize < 10 && stakedEstimate > 100) {
|
|
1178
|
+
return {
|
|
1179
|
+
violated: true,
|
|
1180
|
+
severity: 3,
|
|
1181
|
+
evidence: { currency: curr, pool: poolName, poolSize, estimatedStaked: stakedEstimate },
|
|
1182
|
+
suggestedAction: {
|
|
1183
|
+
parameter: "transactionFee",
|
|
1184
|
+
direction: "increase",
|
|
1185
|
+
currency: curr,
|
|
1186
|
+
magnitude: 0.05,
|
|
1187
|
+
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.`
|
|
1188
|
+
},
|
|
1189
|
+
confidence: 0.45,
|
|
1190
|
+
estimatedLag: 10
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1033
1193
|
}
|
|
1034
1194
|
}
|
|
1035
1195
|
return { violated: false };
|
|
@@ -1041,22 +1201,27 @@ var P32_VelocityAboveSupply = {
|
|
|
1041
1201
|
category: "currency",
|
|
1042
1202
|
description: "Low transactions despite adequate supply means liquidity is trapped. High supply with low velocity = stagnation, not abundance.",
|
|
1043
1203
|
check(metrics, _thresholds) {
|
|
1044
|
-
const {
|
|
1204
|
+
const { supplyByResource } = metrics;
|
|
1045
1205
|
const totalResources = Object.values(supplyByResource).reduce((s, v) => s + v, 0);
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1206
|
+
for (const curr of metrics.currencies) {
|
|
1207
|
+
const velocity = metrics.velocityByCurrency[curr] ?? 0;
|
|
1208
|
+
const totalSupply = metrics.totalSupplyByCurrency[curr] ?? 0;
|
|
1209
|
+
if (velocity < 3 && totalSupply > 100 && totalResources > 20) {
|
|
1210
|
+
return {
|
|
1211
|
+
violated: true,
|
|
1212
|
+
severity: 4,
|
|
1213
|
+
evidence: { currency: curr, velocity, totalSupply, totalResources },
|
|
1214
|
+
suggestedAction: {
|
|
1215
|
+
parameter: "transactionFee",
|
|
1216
|
+
direction: "decrease",
|
|
1217
|
+
currency: curr,
|
|
1218
|
+
magnitude: 0.2,
|
|
1219
|
+
reasoning: `[${curr}] Velocity ${velocity}/t with ${totalResources} resources in system. Economy stagnant despite available supply. Lower trading friction.`
|
|
1220
|
+
},
|
|
1221
|
+
confidence: 0.75,
|
|
1222
|
+
estimatedLag: 5
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1060
1225
|
}
|
|
1061
1226
|
return { violated: false };
|
|
1062
1227
|
}
|
|
@@ -1067,32 +1232,38 @@ var P58_NoNaturalNumeraire = {
|
|
|
1067
1232
|
category: "currency",
|
|
1068
1233
|
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.",
|
|
1069
1234
|
check(metrics, _thresholds) {
|
|
1070
|
-
const
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
priceValues.
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1235
|
+
for (const curr of metrics.currencies) {
|
|
1236
|
+
const currPrices = metrics.pricesByCurrency[curr] ?? {};
|
|
1237
|
+
const velocity = metrics.velocityByCurrency[curr] ?? 0;
|
|
1238
|
+
const totalSupply = metrics.totalSupplyByCurrency[curr] ?? 0;
|
|
1239
|
+
const priceValues = Object.values(currPrices).filter((p) => p > 0);
|
|
1240
|
+
if (priceValues.length < 3) continue;
|
|
1241
|
+
const mean = priceValues.reduce((s, p) => s + p, 0) / priceValues.length;
|
|
1242
|
+
const coeffOfVariation = mean > 0 ? Math.sqrt(
|
|
1243
|
+
priceValues.reduce((s, p) => s + (p - mean) ** 2, 0) / priceValues.length
|
|
1244
|
+
) / mean : 0;
|
|
1245
|
+
if (coeffOfVariation < 0.25 && velocity > 5 && totalSupply > 100) {
|
|
1246
|
+
return {
|
|
1247
|
+
violated: true,
|
|
1248
|
+
severity: 3,
|
|
1249
|
+
evidence: {
|
|
1250
|
+
currency: curr,
|
|
1251
|
+
coeffOfVariation,
|
|
1252
|
+
velocity,
|
|
1253
|
+
numResources: priceValues.length,
|
|
1254
|
+
meanPrice: mean
|
|
1255
|
+
},
|
|
1256
|
+
suggestedAction: {
|
|
1257
|
+
parameter: "productionCost",
|
|
1258
|
+
direction: "increase",
|
|
1259
|
+
currency: curr,
|
|
1260
|
+
magnitude: 0.1,
|
|
1261
|
+
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.`
|
|
1262
|
+
},
|
|
1263
|
+
confidence: 0.5,
|
|
1264
|
+
estimatedLag: 20
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1096
1267
|
}
|
|
1097
1268
|
return { violated: false };
|
|
1098
1269
|
}
|
|
@@ -1139,25 +1310,22 @@ var P18_FirstProducerNeedsStartingInventory = {
|
|
|
1139
1310
|
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.",
|
|
1140
1311
|
check(metrics, _thresholds) {
|
|
1141
1312
|
if (metrics.tick > 20) return { violated: false };
|
|
1313
|
+
const hasAgents = metrics.totalAgents > 0;
|
|
1142
1314
|
for (const [resource, supply] of Object.entries(metrics.supplyByResource)) {
|
|
1143
|
-
if (supply === 0) {
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
estimatedLag: 2
|
|
1158
|
-
};
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1315
|
+
if (supply === 0 && hasAgents) {
|
|
1316
|
+
return {
|
|
1317
|
+
violated: true,
|
|
1318
|
+
severity: 8,
|
|
1319
|
+
evidence: { tick: metrics.tick, resource, supply, totalAgents: metrics.totalAgents },
|
|
1320
|
+
suggestedAction: {
|
|
1321
|
+
parameter: "productionCost",
|
|
1322
|
+
direction: "decrease",
|
|
1323
|
+
magnitude: 0.5,
|
|
1324
|
+
reasoning: `Bootstrap failure: ${resource} supply is 0 at tick ${metrics.tick} with ${metrics.totalAgents} agents. Drastically reduce production cost to allow immediate output.`
|
|
1325
|
+
},
|
|
1326
|
+
confidence: 0.9,
|
|
1327
|
+
estimatedLag: 2
|
|
1328
|
+
};
|
|
1161
1329
|
}
|
|
1162
1330
|
}
|
|
1163
1331
|
return { violated: false };
|
|
@@ -1783,26 +1951,33 @@ var P42_TheMedianPrinciple = {
|
|
|
1783
1951
|
category: "statistical",
|
|
1784
1952
|
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%.",
|
|
1785
1953
|
check(metrics, thresholds) {
|
|
1786
|
-
const
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1954
|
+
for (const curr of metrics.currencies) {
|
|
1955
|
+
const meanMedianDivergence = metrics.meanMedianDivergenceByCurrency[curr] ?? 0;
|
|
1956
|
+
const giniCoefficient = metrics.giniCoefficientByCurrency[curr] ?? 0;
|
|
1957
|
+
const meanBalance = metrics.meanBalanceByCurrency[curr] ?? 0;
|
|
1958
|
+
const medianBalance = metrics.medianBalanceByCurrency[curr] ?? 0;
|
|
1959
|
+
if (meanMedianDivergence > thresholds.meanMedianDivergenceMax) {
|
|
1960
|
+
return {
|
|
1961
|
+
violated: true,
|
|
1962
|
+
severity: 5,
|
|
1963
|
+
evidence: {
|
|
1964
|
+
currency: curr,
|
|
1965
|
+
meanMedianDivergence,
|
|
1966
|
+
giniCoefficient,
|
|
1967
|
+
meanBalance,
|
|
1968
|
+
medianBalance
|
|
1969
|
+
},
|
|
1970
|
+
suggestedAction: {
|
|
1971
|
+
parameter: "transactionFee",
|
|
1972
|
+
direction: "increase",
|
|
1973
|
+
currency: curr,
|
|
1974
|
+
magnitude: 0.15,
|
|
1975
|
+
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.`
|
|
1976
|
+
},
|
|
1977
|
+
confidence: 0.85,
|
|
1978
|
+
estimatedLag: 15
|
|
1979
|
+
};
|
|
1980
|
+
}
|
|
1806
1981
|
}
|
|
1807
1982
|
return { violated: false };
|
|
1808
1983
|
}
|
|
@@ -2010,51 +2185,56 @@ var P33_FairNotEqual = {
|
|
|
2010
2185
|
category: "player_experience",
|
|
2011
2186
|
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.",
|
|
2012
2187
|
check(metrics, thresholds) {
|
|
2013
|
-
const
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2188
|
+
for (const curr of metrics.currencies) {
|
|
2189
|
+
const giniCoefficient = metrics.giniCoefficientByCurrency[curr] ?? 0;
|
|
2190
|
+
if (giniCoefficient < 0.1) {
|
|
2191
|
+
return {
|
|
2192
|
+
violated: true,
|
|
2193
|
+
severity: 3,
|
|
2194
|
+
evidence: { currency: curr, giniCoefficient },
|
|
2195
|
+
suggestedAction: {
|
|
2196
|
+
parameter: "rewardRate",
|
|
2197
|
+
direction: "increase",
|
|
2198
|
+
currency: curr,
|
|
2199
|
+
magnitude: 0.1,
|
|
2200
|
+
reasoning: `[${curr}] Gini ${giniCoefficient.toFixed(2)} \u2014 near-perfect equality. Economy lacks stakes. Increase winner rewards to create meaningful spread.`
|
|
2201
|
+
},
|
|
2202
|
+
confidence: 0.6,
|
|
2203
|
+
estimatedLag: 20
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
if (giniCoefficient > thresholds.giniRedThreshold) {
|
|
2207
|
+
return {
|
|
2208
|
+
violated: true,
|
|
2209
|
+
severity: 7,
|
|
2210
|
+
evidence: { currency: curr, giniCoefficient },
|
|
2211
|
+
suggestedAction: {
|
|
2212
|
+
parameter: "transactionFee",
|
|
2213
|
+
direction: "increase",
|
|
2214
|
+
currency: curr,
|
|
2215
|
+
magnitude: 0.2,
|
|
2216
|
+
reasoning: `[${curr}] Gini ${giniCoefficient.toFixed(2)} \u2014 oligarchy level. Toxic inequality. Raise transaction fees to redistribute wealth from rich to pool.`
|
|
2217
|
+
},
|
|
2218
|
+
confidence: 0.85,
|
|
2219
|
+
estimatedLag: 10
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
2222
|
+
if (giniCoefficient > thresholds.giniWarnThreshold) {
|
|
2223
|
+
return {
|
|
2224
|
+
violated: true,
|
|
2225
|
+
severity: 4,
|
|
2226
|
+
evidence: { currency: curr, giniCoefficient },
|
|
2227
|
+
suggestedAction: {
|
|
2228
|
+
parameter: "transactionFee",
|
|
2229
|
+
direction: "increase",
|
|
2230
|
+
currency: curr,
|
|
2231
|
+
magnitude: 0.1,
|
|
2232
|
+
reasoning: `[${curr}] Gini ${giniCoefficient.toFixed(2)} \u2014 high inequality warning. Gently raise fees to slow wealth concentration.`
|
|
2233
|
+
},
|
|
2234
|
+
confidence: 0.75,
|
|
2235
|
+
estimatedLag: 15
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2058
2238
|
}
|
|
2059
2239
|
return { violated: false };
|
|
2060
2240
|
}
|
|
@@ -2587,33 +2767,41 @@ var Simulator = class {
|
|
|
2587
2767
|
runForward(metrics, action, ticks, _thresholds) {
|
|
2588
2768
|
const multiplier = this.actionMultiplier(action);
|
|
2589
2769
|
const noise = () => 1 + (Math.random() - 0.5) * 0.1;
|
|
2590
|
-
|
|
2770
|
+
const currencies = metrics.currencies;
|
|
2771
|
+
const targetCurrency = action.currency;
|
|
2772
|
+
const supplies = { ...metrics.totalSupplyByCurrency };
|
|
2773
|
+
const netFlows = { ...metrics.netFlowByCurrency };
|
|
2774
|
+
const ginis = { ...metrics.giniCoefficientByCurrency };
|
|
2775
|
+
const velocities = { ...metrics.velocityByCurrency };
|
|
2591
2776
|
let satisfaction = metrics.avgSatisfaction;
|
|
2592
|
-
let gini = metrics.giniCoefficient;
|
|
2593
|
-
let velocity = metrics.velocity;
|
|
2594
|
-
let netFlow = metrics.netFlow;
|
|
2595
|
-
const churnRate = metrics.churnRate;
|
|
2596
2777
|
for (let t = 0; t < ticks; t++) {
|
|
2597
|
-
const
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2778
|
+
for (const curr of currencies) {
|
|
2779
|
+
const isTarget = !targetCurrency || targetCurrency === curr;
|
|
2780
|
+
const effectOnFlow = isTarget ? this.flowEffect(action, metrics, curr) * multiplier * noise() : 0;
|
|
2781
|
+
netFlows[curr] = (netFlows[curr] ?? 0) * 0.9 + effectOnFlow * 0.1;
|
|
2782
|
+
supplies[curr] = Math.max(0, (supplies[curr] ?? 0) + (netFlows[curr] ?? 0) * noise());
|
|
2783
|
+
ginis[curr] = (ginis[curr] ?? 0) * 0.99 + 0.35 * 0.01 * noise();
|
|
2784
|
+
velocities[curr] = (supplies[curr] ?? 0) / Math.max(1, metrics.totalAgents) * 0.01 * noise();
|
|
2785
|
+
}
|
|
2786
|
+
const avgNetFlow = currencies.length > 0 ? Object.values(netFlows).reduce((s, v) => s + v, 0) / currencies.length : 0;
|
|
2787
|
+
const satDelta = avgNetFlow > 0 && avgNetFlow < 20 ? 0.5 : avgNetFlow < 0 ? -1 : 0;
|
|
2602
2788
|
satisfaction = Math.min(100, Math.max(0, satisfaction + satDelta * noise()));
|
|
2603
|
-
gini = gini * 0.99 + 0.35 * 0.01 * noise();
|
|
2604
|
-
velocity = supply / Math.max(1, metrics.totalAgents) * 0.01 * noise();
|
|
2605
|
-
const agentLoss = metrics.totalAgents * churnRate * noise();
|
|
2606
|
-
void agentLoss;
|
|
2607
2789
|
}
|
|
2790
|
+
const totalSupply = Object.values(supplies).reduce((s, v) => s + v, 0);
|
|
2608
2791
|
const projected = {
|
|
2609
2792
|
...metrics,
|
|
2610
2793
|
tick: metrics.tick + ticks,
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2794
|
+
currencies,
|
|
2795
|
+
totalSupplyByCurrency: supplies,
|
|
2796
|
+
netFlowByCurrency: netFlows,
|
|
2797
|
+
velocityByCurrency: velocities,
|
|
2798
|
+
giniCoefficientByCurrency: ginis,
|
|
2799
|
+
totalSupply,
|
|
2800
|
+
netFlow: Object.values(netFlows).reduce((s, v) => s + v, 0),
|
|
2801
|
+
velocity: totalSupply > 0 && currencies.length > 0 ? Object.values(velocities).reduce((s, v) => s + v, 0) / currencies.length : 0,
|
|
2802
|
+
giniCoefficient: currencies.length > 0 ? Object.values(ginis).reduce((s, v) => s + v, 0) / currencies.length : 0,
|
|
2615
2803
|
avgSatisfaction: satisfaction,
|
|
2616
|
-
inflationRate: metrics.totalSupply > 0 ? (
|
|
2804
|
+
inflationRate: metrics.totalSupply > 0 ? (totalSupply - metrics.totalSupply) / metrics.totalSupply : 0
|
|
2617
2805
|
};
|
|
2618
2806
|
return projected;
|
|
2619
2807
|
}
|
|
@@ -2621,16 +2809,16 @@ var Simulator = class {
|
|
|
2621
2809
|
const base = action.magnitude ?? 0.1;
|
|
2622
2810
|
return action.direction === "increase" ? 1 + base : 1 - base;
|
|
2623
2811
|
}
|
|
2624
|
-
flowEffect(action, metrics) {
|
|
2812
|
+
flowEffect(action, metrics, currency) {
|
|
2625
2813
|
const { parameter, direction } = action;
|
|
2626
2814
|
const sign = direction === "increase" ? -1 : 1;
|
|
2627
2815
|
const roleEntries = Object.entries(metrics.populationByRole).sort((a, b) => b[1] - a[1]);
|
|
2628
2816
|
const dominantRoleCount = roleEntries[0]?.[1] ?? 0;
|
|
2629
2817
|
if (parameter === "productionCost") {
|
|
2630
|
-
return sign * metrics.
|
|
2818
|
+
return sign * (metrics.netFlowByCurrency[currency] ?? 0) * 0.2;
|
|
2631
2819
|
}
|
|
2632
2820
|
if (parameter === "transactionFee") {
|
|
2633
|
-
return sign * metrics.
|
|
2821
|
+
return sign * (metrics.velocityByCurrency[currency] ?? 0) * 10 * 0.1;
|
|
2634
2822
|
}
|
|
2635
2823
|
if (parameter === "entryFee") {
|
|
2636
2824
|
return sign * dominantRoleCount * 0.5;
|
|
@@ -2639,14 +2827,22 @@ var Simulator = class {
|
|
|
2639
2827
|
return -sign * dominantRoleCount * 0.3;
|
|
2640
2828
|
}
|
|
2641
2829
|
if (parameter === "yieldRate") {
|
|
2642
|
-
return sign * metrics.
|
|
2830
|
+
return sign * (metrics.faucetVolumeByCurrency[currency] ?? 0) * 0.15;
|
|
2643
2831
|
}
|
|
2644
|
-
return sign * metrics.
|
|
2832
|
+
return sign * (metrics.netFlowByCurrency[currency] ?? 0) * 0.1;
|
|
2645
2833
|
}
|
|
2646
2834
|
checkImprovement(before, after, action) {
|
|
2647
2835
|
const satisfactionImproved = after.avgSatisfaction >= before.avgSatisfaction - 2;
|
|
2648
|
-
const flowMoreBalanced =
|
|
2649
|
-
|
|
2836
|
+
const flowMoreBalanced = before.currencies.every((curr) => {
|
|
2837
|
+
const afterFlow = Math.abs(after.netFlowByCurrency[curr] ?? 0);
|
|
2838
|
+
const beforeFlow = Math.abs(before.netFlowByCurrency[curr] ?? 0);
|
|
2839
|
+
return afterFlow <= beforeFlow * 1.2 || afterFlow < 1;
|
|
2840
|
+
});
|
|
2841
|
+
const notWorseGini = before.currencies.every((curr) => {
|
|
2842
|
+
const afterGini = after.giniCoefficientByCurrency[curr] ?? 0;
|
|
2843
|
+
const beforeGini = before.giniCoefficientByCurrency[curr] ?? 0;
|
|
2844
|
+
return afterGini <= beforeGini + 0.05;
|
|
2845
|
+
});
|
|
2650
2846
|
void action;
|
|
2651
2847
|
return satisfactionImproved && flowMoreBalanced && notWorseGini;
|
|
2652
2848
|
}
|
|
@@ -2657,6 +2853,21 @@ var Simulator = class {
|
|
|
2657
2853
|
const vals = outcomes.map((o) => o[key]).filter((v) => typeof v === "number" && !isNaN(v));
|
|
2658
2854
|
return vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : 0;
|
|
2659
2855
|
};
|
|
2856
|
+
const avgRecord = (key) => {
|
|
2857
|
+
const allKeys = /* @__PURE__ */ new Set();
|
|
2858
|
+
for (const o of outcomes) {
|
|
2859
|
+
const rec = o[key];
|
|
2860
|
+
if (rec && typeof rec === "object" && !Array.isArray(rec)) {
|
|
2861
|
+
Object.keys(rec).forEach((k) => allKeys.add(k));
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
const result = {};
|
|
2865
|
+
for (const k of allKeys) {
|
|
2866
|
+
const vals = outcomes.map((o) => o[key]?.[k]).filter((v) => typeof v === "number" && !isNaN(v));
|
|
2867
|
+
result[k] = vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : 0;
|
|
2868
|
+
}
|
|
2869
|
+
return result;
|
|
2870
|
+
};
|
|
2660
2871
|
return {
|
|
2661
2872
|
...base,
|
|
2662
2873
|
totalSupply: avg("totalSupply"),
|
|
@@ -2664,7 +2875,11 @@ var Simulator = class {
|
|
|
2664
2875
|
velocity: avg("velocity"),
|
|
2665
2876
|
giniCoefficient: avg("giniCoefficient"),
|
|
2666
2877
|
avgSatisfaction: avg("avgSatisfaction"),
|
|
2667
|
-
inflationRate: avg("inflationRate")
|
|
2878
|
+
inflationRate: avg("inflationRate"),
|
|
2879
|
+
totalSupplyByCurrency: avgRecord("totalSupplyByCurrency"),
|
|
2880
|
+
netFlowByCurrency: avgRecord("netFlowByCurrency"),
|
|
2881
|
+
velocityByCurrency: avgRecord("velocityByCurrency"),
|
|
2882
|
+
giniCoefficientByCurrency: avgRecord("giniCoefficientByCurrency")
|
|
2668
2883
|
};
|
|
2669
2884
|
}
|
|
2670
2885
|
};
|
|
@@ -2723,6 +2938,7 @@ var Planner = class {
|
|
|
2723
2938
|
id: `plan_${metrics.tick}_${param}`,
|
|
2724
2939
|
diagnosis,
|
|
2725
2940
|
parameter: param,
|
|
2941
|
+
...action.currency !== void 0 ? { currency: action.currency } : {},
|
|
2726
2942
|
currentValue,
|
|
2727
2943
|
targetValue,
|
|
2728
2944
|
maxChangePercent: thresholds.maxAdjustmentPercent,
|
|
@@ -2764,7 +2980,7 @@ var Executor = class {
|
|
|
2764
2980
|
}
|
|
2765
2981
|
async apply(plan, adapter, currentParams) {
|
|
2766
2982
|
const originalValue = currentParams[plan.parameter] ?? plan.currentValue;
|
|
2767
|
-
await adapter.setParam(plan.parameter, plan.targetValue);
|
|
2983
|
+
await adapter.setParam(plan.parameter, plan.targetValue, plan.diagnosis.violation.suggestedAction.currency);
|
|
2768
2984
|
plan.appliedAt = plan.diagnosis.tick;
|
|
2769
2985
|
this.activePlans.push({ plan, originalValue });
|
|
2770
2986
|
}
|
|
@@ -2786,7 +3002,7 @@ var Executor = class {
|
|
|
2786
3002
|
const metricValue = this.getMetricValue(metrics, rc.metric);
|
|
2787
3003
|
const shouldRollback = rc.direction === "below" ? metricValue < rc.threshold : metricValue > rc.threshold;
|
|
2788
3004
|
if (shouldRollback) {
|
|
2789
|
-
await adapter.setParam(plan.parameter, originalValue);
|
|
3005
|
+
await adapter.setParam(plan.parameter, originalValue, plan.diagnosis.violation.suggestedAction.currency);
|
|
2790
3006
|
rolledBack.push(plan);
|
|
2791
3007
|
} else {
|
|
2792
3008
|
const settledTick = rc.checkAfterTick + 10;
|
|
@@ -2919,6 +3135,18 @@ var DecisionLog = class {
|
|
|
2919
3135
|
};
|
|
2920
3136
|
|
|
2921
3137
|
// src/MetricStore.ts
|
|
3138
|
+
function getNestedValue(obj, path) {
|
|
3139
|
+
const parts = path.split(".");
|
|
3140
|
+
let val = obj;
|
|
3141
|
+
for (const part of parts) {
|
|
3142
|
+
if (val !== null && typeof val === "object") {
|
|
3143
|
+
val = val[part];
|
|
3144
|
+
} else {
|
|
3145
|
+
return NaN;
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
return typeof val === "number" ? val : NaN;
|
|
3149
|
+
}
|
|
2922
3150
|
var RingBuffer = class {
|
|
2923
3151
|
constructor(capacity) {
|
|
2924
3152
|
this.capacity = capacity;
|
|
@@ -2996,10 +3224,9 @@ var MetricStore = class {
|
|
|
2996
3224
|
if (q.to !== void 0 && m.tick > q.to) return false;
|
|
2997
3225
|
return true;
|
|
2998
3226
|
});
|
|
2999
|
-
const metricKey = q.metric;
|
|
3000
3227
|
const points = filtered.map((m) => ({
|
|
3001
3228
|
tick: m.tick,
|
|
3002
|
-
value:
|
|
3229
|
+
value: getNestedValue(m, q.metric)
|
|
3003
3230
|
}));
|
|
3004
3231
|
return { metric: q.metric, resolution, points };
|
|
3005
3232
|
}
|
|
@@ -3024,6 +3251,21 @@ var MetricStore = class {
|
|
|
3024
3251
|
const vals = snapshots.map((s) => s[key]).filter((v) => !isNaN(v));
|
|
3025
3252
|
return vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : 0;
|
|
3026
3253
|
};
|
|
3254
|
+
const avgRecord = (key) => {
|
|
3255
|
+
const allKeys = /* @__PURE__ */ new Set();
|
|
3256
|
+
for (const s of snapshots) {
|
|
3257
|
+
const rec = s[key];
|
|
3258
|
+
if (rec && typeof rec === "object" && !Array.isArray(rec)) {
|
|
3259
|
+
Object.keys(rec).forEach((k) => allKeys.add(k));
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
const result = {};
|
|
3263
|
+
for (const k of allKeys) {
|
|
3264
|
+
const vals = snapshots.map((s) => s[key]?.[k]).filter((v) => typeof v === "number" && !isNaN(v));
|
|
3265
|
+
result[k] = vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : 0;
|
|
3266
|
+
}
|
|
3267
|
+
return result;
|
|
3268
|
+
};
|
|
3027
3269
|
return {
|
|
3028
3270
|
...last,
|
|
3029
3271
|
totalSupply: avg("totalSupply"),
|
|
@@ -3043,7 +3285,21 @@ var MetricStore = class {
|
|
|
3043
3285
|
tapSinkRatio: avg("tapSinkRatio"),
|
|
3044
3286
|
productionIndex: avg("productionIndex"),
|
|
3045
3287
|
capacityUsage: avg("capacityUsage"),
|
|
3046
|
-
anchorRatioDrift: avg("anchorRatioDrift")
|
|
3288
|
+
anchorRatioDrift: avg("anchorRatioDrift"),
|
|
3289
|
+
// Per-currency averages
|
|
3290
|
+
totalSupplyByCurrency: avgRecord("totalSupplyByCurrency"),
|
|
3291
|
+
netFlowByCurrency: avgRecord("netFlowByCurrency"),
|
|
3292
|
+
velocityByCurrency: avgRecord("velocityByCurrency"),
|
|
3293
|
+
inflationRateByCurrency: avgRecord("inflationRateByCurrency"),
|
|
3294
|
+
faucetVolumeByCurrency: avgRecord("faucetVolumeByCurrency"),
|
|
3295
|
+
sinkVolumeByCurrency: avgRecord("sinkVolumeByCurrency"),
|
|
3296
|
+
tapSinkRatioByCurrency: avgRecord("tapSinkRatioByCurrency"),
|
|
3297
|
+
anchorRatioDriftByCurrency: avgRecord("anchorRatioDriftByCurrency"),
|
|
3298
|
+
giniCoefficientByCurrency: avgRecord("giniCoefficientByCurrency"),
|
|
3299
|
+
medianBalanceByCurrency: avgRecord("medianBalanceByCurrency"),
|
|
3300
|
+
meanBalanceByCurrency: avgRecord("meanBalanceByCurrency"),
|
|
3301
|
+
top10PctShareByCurrency: avgRecord("top10PctShareByCurrency"),
|
|
3302
|
+
meanMedianDivergenceByCurrency: avgRecord("meanMedianDivergenceByCurrency")
|
|
3047
3303
|
};
|
|
3048
3304
|
}
|
|
3049
3305
|
};
|