@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.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,24 +94,25 @@ 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
|
-
|
|
105
|
+
const defaultCurrency = state.currencies[0] ?? "default";
|
|
106
106
|
for (const e of recentEvents) {
|
|
107
|
+
const curr = e.currency ?? defaultCurrency;
|
|
107
108
|
switch (e.type) {
|
|
108
109
|
case "mint":
|
|
109
110
|
case "spawn":
|
|
110
|
-
|
|
111
|
+
faucetVolumeByCurrency[curr] = (faucetVolumeByCurrency[curr] ?? 0) + (e.amount ?? 0);
|
|
111
112
|
break;
|
|
112
113
|
case "burn":
|
|
113
114
|
case "consume":
|
|
114
|
-
|
|
115
|
+
sinkVolumeByCurrency[curr] = (sinkVolumeByCurrency[curr] ?? 0) + (e.amount ?? 0);
|
|
115
116
|
break;
|
|
116
117
|
case "produce":
|
|
117
118
|
productionAmount += e.amount ?? 1;
|
|
@@ -128,20 +129,68 @@ var Observer = class {
|
|
|
128
129
|
break;
|
|
129
130
|
}
|
|
130
131
|
}
|
|
131
|
-
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);
|
|
132
184
|
const netFlow = faucetVolume - sinkVolume;
|
|
133
185
|
const tapSinkRatio = sinkVolume > 0 ? faucetVolume / sinkVolume : faucetVolume > 0 ? Infinity : 1;
|
|
134
|
-
const prevSupply = this.previousMetrics?.totalSupply ?? totalSupply;
|
|
135
|
-
const inflationRate = prevSupply > 0 ? (totalSupply - prevSupply) / prevSupply : 0;
|
|
136
186
|
const velocity = totalSupply > 0 ? tradeEvents.length / totalSupply : 0;
|
|
137
|
-
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);
|
|
138
191
|
const meanBalance = totalAgents > 0 ? totalSupply / totalAgents : 0;
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
const top10Sum = sortedBalances.slice(top10Idx).reduce((s, b) => s + b, 0);
|
|
142
|
-
const top10PctShare = totalSupply > 0 ? top10Sum / totalSupply : 0;
|
|
143
|
-
const giniCoefficient = computeGini(sortedBalances);
|
|
144
|
-
const meanMedianDivergence = medianBalance > 0 ? Math.abs(meanBalance - medianBalance) / medianBalance : 0;
|
|
192
|
+
const top10PctShare = avgOf(top10PctShareByCurrency);
|
|
193
|
+
const meanMedianDivergence = avgOf(meanMedianDivergenceByCurrency);
|
|
145
194
|
const populationByRole = {};
|
|
146
195
|
const roleShares = {};
|
|
147
196
|
for (const role of roles) {
|
|
@@ -156,15 +205,25 @@ var Observer = class {
|
|
|
156
205
|
churnByRole[role] = (churnByRole[role] ?? 0) + 1;
|
|
157
206
|
}
|
|
158
207
|
const churnRate = churnCount / Math.max(1, totalAgents);
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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;
|
|
168
227
|
const supplyByResource = {};
|
|
169
228
|
for (const inv of Object.values(state.agentInventories)) {
|
|
170
229
|
for (const [resource, qty] of Object.entries(inv)) {
|
|
@@ -196,63 +255,85 @@ var Observer = class {
|
|
|
196
255
|
const avgSatisfaction = satisfactions.length > 0 ? satisfactions.reduce((s, v) => s + v, 0) / satisfactions.length : 80;
|
|
197
256
|
const blockedAgentCount = satisfactions.filter((s) => s < 20).length;
|
|
198
257
|
const timeToValue = totalAgents > 0 ? blockedAgentCount / totalAgents * 100 : 0;
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
+
}
|
|
265
|
+
}
|
|
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
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
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
|
+
}
|
|
205
286
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
pairCount++;
|
|
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
|
+
}
|
|
224
304
|
}
|
|
305
|
+
arbitrageIndexByCurrency[curr] = pairCount > 0 ? Math.min(1, totalDivergence / pairCount) : 0;
|
|
306
|
+
} else {
|
|
307
|
+
arbitrageIndexByCurrency[curr] = 0;
|
|
225
308
|
}
|
|
226
|
-
arbitrageIndex = pairCount > 0 ? Math.min(1, totalDivergence / pairCount) : 0;
|
|
227
309
|
}
|
|
310
|
+
const arbitrageIndex = avgOf(arbitrageIndexByCurrency);
|
|
228
311
|
const contentDropEvents = recentEvents.filter(
|
|
229
312
|
(e) => e.metadata?.["contentDrop"] === true
|
|
230
313
|
);
|
|
231
314
|
const contentDropAge = contentDropEvents.length > 0 ? tick - Math.max(...contentDropEvents.map((e) => e.timestamp)) : (this.previousMetrics?.contentDropAge ?? 0) + 1;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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;
|
|
236
324
|
const tradePrice = e.price ?? 0;
|
|
237
|
-
if (tradePrice === 0 || marketPrice > 0 && tradePrice < marketPrice * 0.3)
|
|
238
|
-
giftTrades++;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
const giftTradeRatio = tradeEvents.length > 0 ? giftTrades / tradeEvents.length : 0;
|
|
243
|
-
let disposalTrades = 0;
|
|
244
|
-
if (tradeEvents.length > 0) {
|
|
245
|
-
for (const e of tradeEvents) {
|
|
325
|
+
if (tradePrice === 0 || marketPrice > 0 && tradePrice < marketPrice * 0.3) gifts++;
|
|
246
326
|
if (e.from && e.resource) {
|
|
247
327
|
const sellerInv = state.agentInventories[e.from]?.[e.resource] ?? 0;
|
|
248
328
|
const avgInv = (supplyByResource[e.resource] ?? 0) / Math.max(1, totalAgents);
|
|
249
|
-
if (sellerInv > avgInv * 3)
|
|
250
|
-
disposalTrades++;
|
|
251
|
-
}
|
|
329
|
+
if (sellerInv > avgInv * 3) disposals++;
|
|
252
330
|
}
|
|
253
331
|
}
|
|
332
|
+
giftTradeRatioByCurrency[curr] = currTrades.length > 0 ? gifts / currTrades.length : 0;
|
|
333
|
+
disposalTradeRatioByCurrency[curr] = currTrades.length > 0 ? disposals / currTrades.length : 0;
|
|
254
334
|
}
|
|
255
|
-
const
|
|
335
|
+
const giftTradeRatio = avgOf(giftTradeRatioByCurrency);
|
|
336
|
+
const disposalTradeRatio = avgOf(disposalTradeRatioByCurrency);
|
|
256
337
|
const custom = {};
|
|
257
338
|
for (const [name, fn] of Object.entries(this.customMetricFns)) {
|
|
258
339
|
try {
|
|
@@ -264,10 +345,57 @@ var Observer = class {
|
|
|
264
345
|
const metrics = {
|
|
265
346
|
tick,
|
|
266
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
|
|
267
374
|
totalSupply,
|
|
268
375
|
netFlow,
|
|
269
376
|
velocity,
|
|
270
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
|
|
271
399
|
populationByRole,
|
|
272
400
|
roleShares,
|
|
273
401
|
totalAgents,
|
|
@@ -275,38 +403,18 @@ var Observer = class {
|
|
|
275
403
|
churnByRole,
|
|
276
404
|
personaDistribution: {},
|
|
277
405
|
// populated by PersonaTracker
|
|
278
|
-
giniCoefficient,
|
|
279
|
-
medianBalance,
|
|
280
|
-
meanBalance,
|
|
281
|
-
top10PctShare,
|
|
282
|
-
meanMedianDivergence,
|
|
283
|
-
priceIndex,
|
|
284
406
|
productionIndex,
|
|
285
407
|
capacityUsage,
|
|
286
|
-
prices,
|
|
287
|
-
priceVolatility,
|
|
288
408
|
supplyByResource,
|
|
289
409
|
demandSignals,
|
|
290
410
|
pinchPoints,
|
|
291
411
|
avgSatisfaction,
|
|
292
412
|
blockedAgentCount,
|
|
293
413
|
timeToValue,
|
|
294
|
-
faucetVolume,
|
|
295
|
-
sinkVolume,
|
|
296
|
-
tapSinkRatio,
|
|
297
|
-
poolSizes,
|
|
298
|
-
anchorRatioDrift,
|
|
299
|
-
extractionRatio: NaN,
|
|
300
|
-
newUserDependency: NaN,
|
|
301
|
-
smokeTestRatio: NaN,
|
|
302
|
-
currencyInsulation: NaN,
|
|
303
414
|
sharkToothPeaks: this.previousMetrics?.sharkToothPeaks ?? [],
|
|
304
415
|
sharkToothValleys: this.previousMetrics?.sharkToothValleys ?? [],
|
|
305
416
|
eventCompletionRate: NaN,
|
|
306
|
-
arbitrageIndex,
|
|
307
417
|
contentDropAge,
|
|
308
|
-
giftTradeRatio,
|
|
309
|
-
disposalTradeRatio,
|
|
310
418
|
custom
|
|
311
419
|
};
|
|
312
420
|
this.previousMetrics = metrics;
|
|
@@ -380,48 +488,75 @@ function emptyMetrics(tick = 0) {
|
|
|
380
488
|
return {
|
|
381
489
|
tick,
|
|
382
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
|
|
383
517
|
totalSupply: 0,
|
|
384
518
|
netFlow: 0,
|
|
385
519
|
velocity: 0,
|
|
386
520
|
inflationRate: 0,
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
churnByRole: {},
|
|
392
|
-
personaDistribution: {},
|
|
521
|
+
faucetVolume: 0,
|
|
522
|
+
sinkVolume: 0,
|
|
523
|
+
tapSinkRatio: 1,
|
|
524
|
+
anchorRatioDrift: 0,
|
|
393
525
|
giniCoefficient: 0,
|
|
394
526
|
medianBalance: 0,
|
|
395
527
|
meanBalance: 0,
|
|
396
528
|
top10PctShare: 0,
|
|
397
529
|
meanMedianDivergence: 0,
|
|
398
530
|
priceIndex: 0,
|
|
399
|
-
productionIndex: 0,
|
|
400
|
-
capacityUsage: 0,
|
|
401
531
|
prices: {},
|
|
402
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,
|
|
403
550
|
supplyByResource: {},
|
|
404
551
|
demandSignals: {},
|
|
405
552
|
pinchPoints: {},
|
|
406
553
|
avgSatisfaction: 100,
|
|
407
554
|
blockedAgentCount: 0,
|
|
408
555
|
timeToValue: 0,
|
|
409
|
-
faucetVolume: 0,
|
|
410
|
-
sinkVolume: 0,
|
|
411
|
-
tapSinkRatio: 1,
|
|
412
|
-
poolSizes: {},
|
|
413
|
-
anchorRatioDrift: 0,
|
|
414
|
-
extractionRatio: NaN,
|
|
415
|
-
newUserDependency: NaN,
|
|
416
|
-
smokeTestRatio: NaN,
|
|
417
|
-
currencyInsulation: NaN,
|
|
418
556
|
sharkToothPeaks: [],
|
|
419
557
|
sharkToothValleys: [],
|
|
420
558
|
eventCompletionRate: NaN,
|
|
421
|
-
arbitrageIndex: 0,
|
|
422
559
|
contentDropAge: 0,
|
|
423
|
-
giftTradeRatio: 0,
|
|
424
|
-
disposalTradeRatio: 0,
|
|
425
560
|
custom: {}
|
|
426
561
|
};
|
|
427
562
|
}
|
|
@@ -889,36 +1024,42 @@ var P12_OnePrimaryFaucet = {
|
|
|
889
1024
|
category: "currency",
|
|
890
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.",
|
|
891
1026
|
check(metrics, thresholds) {
|
|
892
|
-
const
|
|
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
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
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
|
+
}
|
|
922
1063
|
}
|
|
923
1064
|
return { violated: false };
|
|
924
1065
|
}
|
|
@@ -929,28 +1070,30 @@ var P13_PotsAreZeroSumAndSelfRegulate = {
|
|
|
929
1070
|
category: "currency",
|
|
930
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.",
|
|
931
1072
|
check(metrics, thresholds) {
|
|
932
|
-
const {
|
|
933
|
-
const totalAgents = metrics.totalAgents;
|
|
1073
|
+
const { populationByRole } = metrics;
|
|
934
1074
|
const roleEntries = Object.entries(populationByRole).sort((a, b) => b[1] - a[1]);
|
|
935
|
-
const dominantRole = roleEntries[0]?.[0];
|
|
936
1075
|
const dominantCount = roleEntries[0]?.[1] ?? 0;
|
|
937
|
-
for (const [poolName,
|
|
938
|
-
|
|
939
|
-
const
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
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
|
+
}
|
|
954
1097
|
}
|
|
955
1098
|
}
|
|
956
1099
|
return { violated: false };
|
|
@@ -962,22 +1105,27 @@ var P14_TrackActualInjection = {
|
|
|
962
1105
|
category: "currency",
|
|
963
1106
|
description: 'Counting resource gathering as "currency injected" is misleading. Currency enters through faucet mechanisms (spawning, rewards). Fake metrics break every downstream decision.',
|
|
964
1107
|
check(metrics, _thresholds) {
|
|
965
|
-
const
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
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
|
+
}
|
|
981
1129
|
}
|
|
982
1130
|
return { violated: false };
|
|
983
1131
|
}
|
|
@@ -988,24 +1136,28 @@ var P15_PoolsNeedCapAndDecay = {
|
|
|
988
1136
|
category: "currency",
|
|
989
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.",
|
|
990
1138
|
check(metrics, thresholds) {
|
|
991
|
-
const { poolSizes, totalSupply } = metrics;
|
|
992
1139
|
const { poolCapPercent } = thresholds;
|
|
993
|
-
for (const [pool,
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
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
|
+
}
|
|
1009
1161
|
}
|
|
1010
1162
|
}
|
|
1011
1163
|
return { violated: false };
|
|
@@ -1017,23 +1169,27 @@ var P16_WithdrawalPenaltyScales = {
|
|
|
1017
1169
|
category: "currency",
|
|
1018
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.",
|
|
1019
1171
|
check(metrics, _thresholds) {
|
|
1020
|
-
const
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
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
|
+
}
|
|
1037
1193
|
}
|
|
1038
1194
|
}
|
|
1039
1195
|
return { violated: false };
|
|
@@ -1045,22 +1201,27 @@ var P32_VelocityAboveSupply = {
|
|
|
1045
1201
|
category: "currency",
|
|
1046
1202
|
description: "Low transactions despite adequate supply means liquidity is trapped. High supply with low velocity = stagnation, not abundance.",
|
|
1047
1203
|
check(metrics, _thresholds) {
|
|
1048
|
-
const {
|
|
1204
|
+
const { supplyByResource } = metrics;
|
|
1049
1205
|
const totalResources = Object.values(supplyByResource).reduce((s, v) => s + v, 0);
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
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
|
+
}
|
|
1064
1225
|
}
|
|
1065
1226
|
return { violated: false };
|
|
1066
1227
|
}
|
|
@@ -1071,32 +1232,38 @@ var P58_NoNaturalNumeraire = {
|
|
|
1071
1232
|
category: "currency",
|
|
1072
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.",
|
|
1073
1234
|
check(metrics, _thresholds) {
|
|
1074
|
-
const
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
priceValues.
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
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
|
+
}
|
|
1100
1267
|
}
|
|
1101
1268
|
return { violated: false };
|
|
1102
1269
|
}
|
|
@@ -1784,26 +1951,33 @@ var P42_TheMedianPrinciple = {
|
|
|
1784
1951
|
category: "statistical",
|
|
1785
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%.",
|
|
1786
1953
|
check(metrics, thresholds) {
|
|
1787
|
-
const
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
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
|
+
}
|
|
1807
1981
|
}
|
|
1808
1982
|
return { violated: false };
|
|
1809
1983
|
}
|
|
@@ -2011,51 +2185,56 @@ var P33_FairNotEqual = {
|
|
|
2011
2185
|
category: "player_experience",
|
|
2012
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.",
|
|
2013
2187
|
check(metrics, thresholds) {
|
|
2014
|
-
const
|
|
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
|
-
|
|
2058
|
-
|
|
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
|
+
}
|
|
2059
2238
|
}
|
|
2060
2239
|
return { violated: false };
|
|
2061
2240
|
}
|
|
@@ -2588,33 +2767,41 @@ var Simulator = class {
|
|
|
2588
2767
|
runForward(metrics, action, ticks, _thresholds) {
|
|
2589
2768
|
const multiplier = this.actionMultiplier(action);
|
|
2590
2769
|
const noise = () => 1 + (Math.random() - 0.5) * 0.1;
|
|
2591
|
-
|
|
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 };
|
|
2592
2776
|
let satisfaction = metrics.avgSatisfaction;
|
|
2593
|
-
let gini = metrics.giniCoefficient;
|
|
2594
|
-
let velocity = metrics.velocity;
|
|
2595
|
-
let netFlow = metrics.netFlow;
|
|
2596
|
-
const churnRate = metrics.churnRate;
|
|
2597
2777
|
for (let t = 0; t < ticks; t++) {
|
|
2598
|
-
const
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
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;
|
|
2603
2788
|
satisfaction = Math.min(100, Math.max(0, satisfaction + satDelta * noise()));
|
|
2604
|
-
gini = gini * 0.99 + 0.35 * 0.01 * noise();
|
|
2605
|
-
velocity = supply / Math.max(1, metrics.totalAgents) * 0.01 * noise();
|
|
2606
|
-
const agentLoss = metrics.totalAgents * churnRate * noise();
|
|
2607
|
-
void agentLoss;
|
|
2608
2789
|
}
|
|
2790
|
+
const totalSupply = Object.values(supplies).reduce((s, v) => s + v, 0);
|
|
2609
2791
|
const projected = {
|
|
2610
2792
|
...metrics,
|
|
2611
2793
|
tick: metrics.tick + ticks,
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
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,
|
|
2616
2803
|
avgSatisfaction: satisfaction,
|
|
2617
|
-
inflationRate: metrics.totalSupply > 0 ? (
|
|
2804
|
+
inflationRate: metrics.totalSupply > 0 ? (totalSupply - metrics.totalSupply) / metrics.totalSupply : 0
|
|
2618
2805
|
};
|
|
2619
2806
|
return projected;
|
|
2620
2807
|
}
|
|
@@ -2622,16 +2809,16 @@ var Simulator = class {
|
|
|
2622
2809
|
const base = action.magnitude ?? 0.1;
|
|
2623
2810
|
return action.direction === "increase" ? 1 + base : 1 - base;
|
|
2624
2811
|
}
|
|
2625
|
-
flowEffect(action, metrics) {
|
|
2812
|
+
flowEffect(action, metrics, currency) {
|
|
2626
2813
|
const { parameter, direction } = action;
|
|
2627
2814
|
const sign = direction === "increase" ? -1 : 1;
|
|
2628
2815
|
const roleEntries = Object.entries(metrics.populationByRole).sort((a, b) => b[1] - a[1]);
|
|
2629
2816
|
const dominantRoleCount = roleEntries[0]?.[1] ?? 0;
|
|
2630
2817
|
if (parameter === "productionCost") {
|
|
2631
|
-
return sign * metrics.
|
|
2818
|
+
return sign * (metrics.netFlowByCurrency[currency] ?? 0) * 0.2;
|
|
2632
2819
|
}
|
|
2633
2820
|
if (parameter === "transactionFee") {
|
|
2634
|
-
return sign * metrics.
|
|
2821
|
+
return sign * (metrics.velocityByCurrency[currency] ?? 0) * 10 * 0.1;
|
|
2635
2822
|
}
|
|
2636
2823
|
if (parameter === "entryFee") {
|
|
2637
2824
|
return sign * dominantRoleCount * 0.5;
|
|
@@ -2640,14 +2827,22 @@ var Simulator = class {
|
|
|
2640
2827
|
return -sign * dominantRoleCount * 0.3;
|
|
2641
2828
|
}
|
|
2642
2829
|
if (parameter === "yieldRate") {
|
|
2643
|
-
return sign * metrics.
|
|
2830
|
+
return sign * (metrics.faucetVolumeByCurrency[currency] ?? 0) * 0.15;
|
|
2644
2831
|
}
|
|
2645
|
-
return sign * metrics.
|
|
2832
|
+
return sign * (metrics.netFlowByCurrency[currency] ?? 0) * 0.1;
|
|
2646
2833
|
}
|
|
2647
2834
|
checkImprovement(before, after, action) {
|
|
2648
2835
|
const satisfactionImproved = after.avgSatisfaction >= before.avgSatisfaction - 2;
|
|
2649
|
-
const flowMoreBalanced =
|
|
2650
|
-
|
|
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
|
+
});
|
|
2651
2846
|
void action;
|
|
2652
2847
|
return satisfactionImproved && flowMoreBalanced && notWorseGini;
|
|
2653
2848
|
}
|
|
@@ -2658,6 +2853,21 @@ var Simulator = class {
|
|
|
2658
2853
|
const vals = outcomes.map((o) => o[key]).filter((v) => typeof v === "number" && !isNaN(v));
|
|
2659
2854
|
return vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : 0;
|
|
2660
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
|
+
};
|
|
2661
2871
|
return {
|
|
2662
2872
|
...base,
|
|
2663
2873
|
totalSupply: avg("totalSupply"),
|
|
@@ -2665,7 +2875,11 @@ var Simulator = class {
|
|
|
2665
2875
|
velocity: avg("velocity"),
|
|
2666
2876
|
giniCoefficient: avg("giniCoefficient"),
|
|
2667
2877
|
avgSatisfaction: avg("avgSatisfaction"),
|
|
2668
|
-
inflationRate: avg("inflationRate")
|
|
2878
|
+
inflationRate: avg("inflationRate"),
|
|
2879
|
+
totalSupplyByCurrency: avgRecord("totalSupplyByCurrency"),
|
|
2880
|
+
netFlowByCurrency: avgRecord("netFlowByCurrency"),
|
|
2881
|
+
velocityByCurrency: avgRecord("velocityByCurrency"),
|
|
2882
|
+
giniCoefficientByCurrency: avgRecord("giniCoefficientByCurrency")
|
|
2669
2883
|
};
|
|
2670
2884
|
}
|
|
2671
2885
|
};
|
|
@@ -2724,6 +2938,7 @@ var Planner = class {
|
|
|
2724
2938
|
id: `plan_${metrics.tick}_${param}`,
|
|
2725
2939
|
diagnosis,
|
|
2726
2940
|
parameter: param,
|
|
2941
|
+
...action.currency !== void 0 ? { currency: action.currency } : {},
|
|
2727
2942
|
currentValue,
|
|
2728
2943
|
targetValue,
|
|
2729
2944
|
maxChangePercent: thresholds.maxAdjustmentPercent,
|
|
@@ -2765,7 +2980,7 @@ var Executor = class {
|
|
|
2765
2980
|
}
|
|
2766
2981
|
async apply(plan, adapter, currentParams) {
|
|
2767
2982
|
const originalValue = currentParams[plan.parameter] ?? plan.currentValue;
|
|
2768
|
-
await adapter.setParam(plan.parameter, plan.targetValue);
|
|
2983
|
+
await adapter.setParam(plan.parameter, plan.targetValue, plan.diagnosis.violation.suggestedAction.currency);
|
|
2769
2984
|
plan.appliedAt = plan.diagnosis.tick;
|
|
2770
2985
|
this.activePlans.push({ plan, originalValue });
|
|
2771
2986
|
}
|
|
@@ -2787,7 +3002,7 @@ var Executor = class {
|
|
|
2787
3002
|
const metricValue = this.getMetricValue(metrics, rc.metric);
|
|
2788
3003
|
const shouldRollback = rc.direction === "below" ? metricValue < rc.threshold : metricValue > rc.threshold;
|
|
2789
3004
|
if (shouldRollback) {
|
|
2790
|
-
await adapter.setParam(plan.parameter, originalValue);
|
|
3005
|
+
await adapter.setParam(plan.parameter, originalValue, plan.diagnosis.violation.suggestedAction.currency);
|
|
2791
3006
|
rolledBack.push(plan);
|
|
2792
3007
|
} else {
|
|
2793
3008
|
const settledTick = rc.checkAfterTick + 10;
|
|
@@ -2920,6 +3135,18 @@ var DecisionLog = class {
|
|
|
2920
3135
|
};
|
|
2921
3136
|
|
|
2922
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
|
+
}
|
|
2923
3150
|
var RingBuffer = class {
|
|
2924
3151
|
constructor(capacity) {
|
|
2925
3152
|
this.capacity = capacity;
|
|
@@ -2997,10 +3224,9 @@ var MetricStore = class {
|
|
|
2997
3224
|
if (q.to !== void 0 && m.tick > q.to) return false;
|
|
2998
3225
|
return true;
|
|
2999
3226
|
});
|
|
3000
|
-
const metricKey = q.metric;
|
|
3001
3227
|
const points = filtered.map((m) => ({
|
|
3002
3228
|
tick: m.tick,
|
|
3003
|
-
value:
|
|
3229
|
+
value: getNestedValue(m, q.metric)
|
|
3004
3230
|
}));
|
|
3005
3231
|
return { metric: q.metric, resolution, points };
|
|
3006
3232
|
}
|
|
@@ -3025,6 +3251,21 @@ var MetricStore = class {
|
|
|
3025
3251
|
const vals = snapshots.map((s) => s[key]).filter((v) => !isNaN(v));
|
|
3026
3252
|
return vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : 0;
|
|
3027
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
|
+
};
|
|
3028
3269
|
return {
|
|
3029
3270
|
...last,
|
|
3030
3271
|
totalSupply: avg("totalSupply"),
|
|
@@ -3044,7 +3285,21 @@ var MetricStore = class {
|
|
|
3044
3285
|
tapSinkRatio: avg("tapSinkRatio"),
|
|
3045
3286
|
productionIndex: avg("productionIndex"),
|
|
3046
3287
|
capacityUsage: avg("capacityUsage"),
|
|
3047
|
-
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")
|
|
3048
3303
|
};
|
|
3049
3304
|
}
|
|
3050
3305
|
};
|