@holoscript/std 7.0.0 → 7.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/{chunk-PFX2JM4X.js → chunk-FAMOLKH3.js} +2 -1
- package/dist/{chunk-PFX2JM4X.js.map → chunk-FAMOLKH3.js.map} +1 -1
- package/dist/{chunk-DZHAVOCH.js → chunk-SOKH6FJ7.js} +31 -6
- package/dist/chunk-SOKH6FJ7.js.map +1 -0
- package/dist/collections.cjs +1 -0
- package/dist/collections.cjs.map +1 -1
- package/dist/collections.js +2 -2
- package/dist/fs/fs.d.ts.map +1 -1
- package/dist/fs/index.cjs +221 -8510
- package/dist/fs/index.cjs.map +1 -1
- package/dist/fs/index.js +832 -118
- package/dist/fs/index.js.map +1 -1
- package/dist/fs/sandbox.d.ts +49 -0
- package/dist/fs/sandbox.d.ts.map +1 -0
- package/dist/index.cjs +3494 -12672
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -7
- package/dist/index.js.map +1 -1
- package/dist/math.cjs +18 -3
- package/dist/math.cjs.map +1 -1
- package/dist/math.js +2 -2
- package/dist/string.js +1 -1
- package/dist/time.js +1 -1
- package/dist/traits/EconomicPrimitives.js +1 -1
- package/dist/traits/EconomicTraits.js +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +19 -9
- package/dist/__tests__/EconomicPrimitives.test.d.ts +0 -2
- package/dist/__tests__/EconomicPrimitives.test.d.ts.map +0 -1
- package/dist/__tests__/EconomicTraits.test.d.ts +0 -2
- package/dist/__tests__/EconomicTraits.test.d.ts.map +0 -1
- package/dist/__tests__/SimulationLabPrimitives.test.d.ts +0 -2
- package/dist/__tests__/SimulationLabPrimitives.test.d.ts.map +0 -1
- package/dist/__tests__/collections.test.d.ts +0 -2
- package/dist/__tests__/collections.test.d.ts.map +0 -1
- package/dist/__tests__/events.test.d.ts +0 -2
- package/dist/__tests__/events.test.d.ts.map +0 -1
- package/dist/__tests__/index.test.d.ts +0 -2
- package/dist/__tests__/index.test.d.ts.map +0 -1
- package/dist/__tests__/materials.test.d.ts +0 -2
- package/dist/__tests__/materials.test.d.ts.map +0 -1
- package/dist/__tests__/math.test.d.ts +0 -2
- package/dist/__tests__/math.test.d.ts.map +0 -1
- package/dist/__tests__/physics.test.d.ts +0 -2
- package/dist/__tests__/physics.test.d.ts.map +0 -1
- package/dist/__tests__/spatial.test.d.ts +0 -2
- package/dist/__tests__/spatial.test.d.ts.map +0 -1
- package/dist/__tests__/standard-library-fundamentals.test.d.ts +0 -2
- package/dist/__tests__/standard-library-fundamentals.test.d.ts.map +0 -1
- package/dist/__tests__/string-and-time-utilities.test.d.ts +0 -2
- package/dist/__tests__/string-and-time-utilities.test.d.ts.map +0 -1
- package/dist/__tests__/string-decoupled.test.d.ts +0 -2
- package/dist/__tests__/string-decoupled.test.d.ts.map +0 -1
- package/dist/__tests__/string.test.d.ts +0 -2
- package/dist/__tests__/string.test.d.ts.map +0 -1
- package/dist/__tests__/time.test.d.ts +0 -2
- package/dist/__tests__/time.test.d.ts.map +0 -1
- package/dist/chunk-DZHAVOCH.js.map +0 -1
- package/dist/chunk-PR4QN5HX.js +0 -43
- package/dist/chunk-XBA4ARST.js +0 -9313
- package/dist/chunk-XBA4ARST.js.map +0 -1
- package/dist/fs/__tests__/file-watch-system.test.d.ts +0 -2
- package/dist/fs/__tests__/file-watch-system.test.d.ts.map +0 -1
- package/dist/fs/__tests__/path-utilities.test.d.ts +0 -2
- package/dist/fs/__tests__/path-utilities.test.d.ts.map +0 -1
- package/dist/fs/__tests__/path.test.d.ts +0 -2
- package/dist/fs/__tests__/path.test.d.ts.map +0 -1
- package/dist/string.basic.test.d.ts +0 -5
- package/dist/string.basic.test.d.ts.map +0 -1
- package/dist/string.test.d.ts +0 -5
- package/dist/string.test.d.ts.map +0 -1
- package/dist/traits/__tests__/ARTraits.test.d.ts +0 -2
- package/dist/traits/__tests__/ARTraits.test.d.ts.map +0 -1
- package/dist/traits/__tests__/EconomicPrimitives.test.d.ts +0 -2
- package/dist/traits/__tests__/EconomicPrimitives.test.d.ts.map +0 -1
- package/dist/traits/__tests__/EconomicTraits.test.d.ts +0 -2
- package/dist/traits/__tests__/EconomicTraits.test.d.ts.map +0 -1
- package/error-utf8.log +0 -6
- package/error.log +0 -0
- package/src/__tests__/EconomicPrimitives.test.ts +0 -690
- package/src/__tests__/EconomicTraits.test.ts +0 -425
- package/src/__tests__/SimulationLabPrimitives.test.ts +0 -415
- package/src/__tests__/collections.test.ts +0 -182
- package/src/__tests__/events.test.ts +0 -135
- package/src/__tests__/index.test.ts +0 -14
- package/src/__tests__/materials.test.ts +0 -84
- package/src/__tests__/math.test.ts +0 -246
- package/src/__tests__/physics.test.ts +0 -83
- package/src/__tests__/spatial.test.ts +0 -333
- package/src/__tests__/standard-library-fundamentals.test.ts +0 -1318
- package/src/__tests__/string-and-time-utilities.test.ts +0 -1070
- package/src/__tests__/string-decoupled.test.ts +0 -13
- package/src/__tests__/string.test.ts +0 -164
- package/src/__tests__/time.test.ts +0 -110
- package/src/collections.ts +0 -850
- package/src/events.ts +0 -88
- package/src/fs/__tests__/file-watch-system.test.ts +0 -675
- package/src/fs/__tests__/path-utilities.test.ts +0 -375
- package/src/fs/__tests__/path.test.ts +0 -287
- package/src/fs/fs.ts +0 -692
- package/src/fs/index.ts +0 -194
- package/src/fs/path.ts +0 -310
- package/src/fs/watch.ts +0 -413
- package/src/index.ts +0 -476
- package/src/materials.ts +0 -109
- package/src/math.ts +0 -674
- package/src/physics.ts +0 -141
- package/src/spatial.ts +0 -320
- package/src/string.basic.test.ts +0 -14
- package/src/string.test.ts +0 -335
- package/src/string.ts +0 -531
- package/src/time.ts +0 -621
- package/src/traits/ARTraits.ts +0 -103
- package/src/traits/EconomicPrimitives.ts +0 -755
- package/src/traits/EconomicTraits.ts +0 -552
- package/src/traits/IoTTraits.ts +0 -102
- package/src/traits/SimulationLabPrimitives.ts +0 -650
- package/src/traits/SimulationLabTraits.ts +0 -191
- package/src/traits/VRRTraits.ts +0 -326
- package/src/traits/__tests__/ARTraits.test.ts +0 -8
- package/src/traits/__tests__/EconomicPrimitives.test.ts +0 -14
- package/src/traits/__tests__/EconomicTraits.test.ts +0 -16
- package/src/types.ts +0 -418
- package/std_error.log +0 -5
- package/tsconfig.json +0 -18
- package/tsup.config.ts +0 -20
- package/vitest.config.ts +0 -10
- /package/dist/{chunk-PR4QN5HX.js.map → chunk-DGUM43GV.js.map} +0 -0
|
@@ -1,690 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
generateTxHash,
|
|
4
|
-
executeTrade,
|
|
5
|
-
calculateDepreciation,
|
|
6
|
-
depreciatedValue,
|
|
7
|
-
isDestroyed,
|
|
8
|
-
calculateRepairCost,
|
|
9
|
-
bondingCurvePrice,
|
|
10
|
-
bondingCurveBuyCost,
|
|
11
|
-
bondingCurveSellRefund,
|
|
12
|
-
spatialPrice,
|
|
13
|
-
calculateTaxRate,
|
|
14
|
-
calculateTaxAmount,
|
|
15
|
-
calculateRedistribution,
|
|
16
|
-
createPIDState,
|
|
17
|
-
updatePID,
|
|
18
|
-
createDualLoopPIDState,
|
|
19
|
-
updateDualLoopPID,
|
|
20
|
-
createFaucetSinkMetrics,
|
|
21
|
-
recordFaucet,
|
|
22
|
-
recordSink,
|
|
23
|
-
resetMetricsPeriod,
|
|
24
|
-
DEFAULT_DEPRECIATION,
|
|
25
|
-
DEFAULT_BONDING_CURVE,
|
|
26
|
-
DEFAULT_WEALTH_TAX,
|
|
27
|
-
DEFAULT_PID,
|
|
28
|
-
} from '../traits/EconomicPrimitives.js';
|
|
29
|
-
import type {
|
|
30
|
-
OwnershipRecord,
|
|
31
|
-
BondingCurveConfig,
|
|
32
|
-
WealthTaxConfig,
|
|
33
|
-
PIDConfig,
|
|
34
|
-
DualLoopPIDConfig,
|
|
35
|
-
} from '../traits/EconomicPrimitives.js';
|
|
36
|
-
|
|
37
|
-
// =============================================================================
|
|
38
|
-
// TRADEABLE PRIMITIVES
|
|
39
|
-
// =============================================================================
|
|
40
|
-
|
|
41
|
-
describe('Tradeable Primitives', () => {
|
|
42
|
-
describe('generateTxHash', () => {
|
|
43
|
-
it('should produce deterministic hash from inputs', () => {
|
|
44
|
-
const hash1 = generateTxHash('alice', 'bob', 100, 1000);
|
|
45
|
-
const hash2 = generateTxHash('alice', 'bob', 100, 1000);
|
|
46
|
-
expect(hash1).toBe(hash2);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should produce different hashes for different inputs', () => {
|
|
50
|
-
const hash1 = generateTxHash('alice', 'bob', 100, 1000);
|
|
51
|
-
const hash2 = generateTxHash('alice', 'carol', 100, 1000);
|
|
52
|
-
const hash3 = generateTxHash('alice', 'bob', 200, 1000);
|
|
53
|
-
expect(hash1).not.toBe(hash2);
|
|
54
|
-
expect(hash1).not.toBe(hash3);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('should return an 8-character hex string', () => {
|
|
58
|
-
const hash = generateTxHash('alice', 'bob', 100, 1000);
|
|
59
|
-
expect(hash).toMatch(/^[0-9a-f]{8}$/);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe('executeTrade', () => {
|
|
64
|
-
const baseRecord: OwnershipRecord = {
|
|
65
|
-
owner: 'alice',
|
|
66
|
-
history: [],
|
|
67
|
-
locked: false,
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
it('should transfer ownership to buyer', () => {
|
|
71
|
-
const result = executeTrade(baseRecord, 'bob', 100, Date.now());
|
|
72
|
-
expect(result.ok).toBe(true);
|
|
73
|
-
if (result.ok) {
|
|
74
|
-
expect(result.value.owner).toBe('bob');
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('should add transfer record to history', () => {
|
|
79
|
-
const result = executeTrade(baseRecord, 'bob', 100, 1000);
|
|
80
|
-
expect(result.ok).toBe(true);
|
|
81
|
-
if (result.ok) {
|
|
82
|
-
expect(result.value.history).toHaveLength(1);
|
|
83
|
-
expect(result.value.history[0].from).toBe('alice');
|
|
84
|
-
expect(result.value.history[0].to).toBe('bob');
|
|
85
|
-
expect(result.value.history[0].price).toBe(100);
|
|
86
|
-
expect(result.value.history[0].txHash).toBeDefined();
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('should fail if record is locked', () => {
|
|
91
|
-
const locked: OwnershipRecord = { ...baseRecord, locked: true };
|
|
92
|
-
const result = executeTrade(locked, 'bob', 100, Date.now());
|
|
93
|
-
expect(result.ok).toBe(false);
|
|
94
|
-
if (!result.ok) {
|
|
95
|
-
expect(result.error).toBe('TRANSFER_BLOCKED');
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('should fail for negative price', () => {
|
|
100
|
-
const result = executeTrade(baseRecord, 'bob', -10, Date.now());
|
|
101
|
-
expect(result.ok).toBe(false);
|
|
102
|
-
if (!result.ok) {
|
|
103
|
-
expect(result.error).toBe('INVALID_AMOUNT');
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('should cap history at 50 entries', () => {
|
|
108
|
-
const longHistory: OwnershipRecord = {
|
|
109
|
-
owner: 'alice',
|
|
110
|
-
history: Array.from({ length: 55 }, (_, i) => ({
|
|
111
|
-
from: `agent-${i}`,
|
|
112
|
-
to: `agent-${i + 1}`,
|
|
113
|
-
price: 10,
|
|
114
|
-
timestamp: i,
|
|
115
|
-
txHash: `hash-${i}`,
|
|
116
|
-
})),
|
|
117
|
-
locked: false,
|
|
118
|
-
};
|
|
119
|
-
const result = executeTrade(longHistory, 'bob', 100, Date.now());
|
|
120
|
-
expect(result.ok).toBe(true);
|
|
121
|
-
if (result.ok) {
|
|
122
|
-
expect(result.value.history.length).toBeLessThanOrEqual(50);
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('should allow zero-price trade (gift)', () => {
|
|
127
|
-
const result = executeTrade(baseRecord, 'bob', 0, Date.now());
|
|
128
|
-
expect(result.ok).toBe(true);
|
|
129
|
-
if (result.ok) {
|
|
130
|
-
expect(result.value.owner).toBe('bob');
|
|
131
|
-
expect(result.value.history[0].price).toBe(0);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// =============================================================================
|
|
138
|
-
// DEPRECIATION PRIMITIVES
|
|
139
|
-
// =============================================================================
|
|
140
|
-
|
|
141
|
-
describe('Depreciation Primitives', () => {
|
|
142
|
-
describe('calculateDepreciation', () => {
|
|
143
|
-
it('should return initial condition when no time has elapsed', () => {
|
|
144
|
-
const result = calculateDepreciation(1.0, 0.001, 0);
|
|
145
|
-
expect(result).toBe(1.0);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('should decay over time with exponential curve', () => {
|
|
149
|
-
const result = calculateDepreciation(1.0, 0.001, 100);
|
|
150
|
-
expect(result).toBeLessThan(1.0);
|
|
151
|
-
expect(result).toBeGreaterThan(0.0);
|
|
152
|
-
// e^(-0.001 * 100) = e^(-0.1) ~ 0.9048
|
|
153
|
-
expect(result).toBeCloseTo(0.9048, 3);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('should return initial condition for zero decay rate', () => {
|
|
157
|
-
const result = calculateDepreciation(1.0, 0, 1000);
|
|
158
|
-
expect(result).toBe(1.0);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('should approach zero for large elapsed time', () => {
|
|
162
|
-
const result = calculateDepreciation(1.0, 0.01, 10000);
|
|
163
|
-
expect(result).toBeCloseTo(0, 5);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('should handle negative elapsed time by returning initial', () => {
|
|
167
|
-
const result = calculateDepreciation(1.0, 0.001, -10);
|
|
168
|
-
expect(result).toBe(1.0);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('should scale proportionally with initial condition', () => {
|
|
172
|
-
const full = calculateDepreciation(1.0, 0.001, 100);
|
|
173
|
-
const half = calculateDepreciation(0.5, 0.001, 100);
|
|
174
|
-
expect(half).toBeCloseTo(full * 0.5, 6);
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
describe('depreciatedValue', () => {
|
|
179
|
-
it('should return full value at condition 1.0', () => {
|
|
180
|
-
expect(depreciatedValue(1000, 1.0)).toBe(1000);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it('should return zero value at condition 0.0', () => {
|
|
184
|
-
expect(depreciatedValue(1000, 0.0)).toBe(0);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it('should return half value at condition 0.5', () => {
|
|
188
|
-
expect(depreciatedValue(1000, 0.5)).toBe(500);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it('should clamp condition to [0, 1]', () => {
|
|
192
|
-
expect(depreciatedValue(1000, 1.5)).toBe(1000);
|
|
193
|
-
expect(depreciatedValue(1000, -0.5)).toBe(0);
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
describe('isDestroyed', () => {
|
|
198
|
-
it('should return true when condition is at threshold', () => {
|
|
199
|
-
expect(isDestroyed(0.01, 0.01)).toBe(true);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('should return true when condition is below threshold', () => {
|
|
203
|
-
expect(isDestroyed(0.005, 0.01)).toBe(true);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it('should return false when condition is above threshold', () => {
|
|
207
|
-
expect(isDestroyed(0.5, 0.01)).toBe(false);
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
describe('calculateRepairCost', () => {
|
|
212
|
-
it('should cost nothing to repair full condition item', () => {
|
|
213
|
-
expect(calculateRepairCost(1000, 1.0, 0.5)).toBe(0);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it('should cost half of value to fully repair from 0 at 0.5 multiplier', () => {
|
|
217
|
-
expect(calculateRepairCost(1000, 0.0, 0.5)).toBe(500);
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it('should scale with condition deficit', () => {
|
|
221
|
-
const cost = calculateRepairCost(1000, 0.5, 0.5);
|
|
222
|
-
// deficit = 0.5, cost = 1000 * 0.5 * 0.5 = 250
|
|
223
|
-
expect(cost).toBe(250);
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
describe('DEFAULT_DEPRECIATION', () => {
|
|
228
|
-
it('should have reasonable default values', () => {
|
|
229
|
-
expect(DEFAULT_DEPRECIATION.decayRate).toBeGreaterThan(0);
|
|
230
|
-
expect(DEFAULT_DEPRECIATION.condition).toBe(1.0);
|
|
231
|
-
expect(DEFAULT_DEPRECIATION.destroyThreshold).toBeGreaterThan(0);
|
|
232
|
-
expect(DEFAULT_DEPRECIATION.destroyThreshold).toBeLessThan(1);
|
|
233
|
-
expect(DEFAULT_DEPRECIATION.repairable).toBe(true);
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// =============================================================================
|
|
239
|
-
// BONDING CURVE PRIMITIVES
|
|
240
|
-
// =============================================================================
|
|
241
|
-
|
|
242
|
-
describe('Bonding Curve Primitives', () => {
|
|
243
|
-
describe('bondingCurvePrice', () => {
|
|
244
|
-
it('should return 0 for zero supply', () => {
|
|
245
|
-
expect(bondingCurvePrice(0, 1.0, 2.0, 'exponential')).toBe(0);
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it('should return 0 for zero reserve ratio', () => {
|
|
249
|
-
expect(bondingCurvePrice(100, 0, 2.0, 'exponential')).toBe(0);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it('should increase price as supply increases (exponential)', () => {
|
|
253
|
-
const p1 = bondingCurvePrice(10, 1.0, 2.0, 'exponential');
|
|
254
|
-
const p2 = bondingCurvePrice(100, 1.0, 2.0, 'exponential');
|
|
255
|
-
expect(p2).toBeGreaterThan(p1);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it('should scale linearly for linear curve', () => {
|
|
259
|
-
const p1 = bondingCurvePrice(10, 1.0, 2.0, 'linear');
|
|
260
|
-
const p2 = bondingCurvePrice(20, 1.0, 2.0, 'linear');
|
|
261
|
-
expect(p2).toBeCloseTo(p1 * 2, 6);
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
it('should calculate exponential: P = R * S^(1/n)', () => {
|
|
265
|
-
// R=1, S=100, n=2 -> P = 1 * 100^(1/2) = 10
|
|
266
|
-
const price = bondingCurvePrice(100, 1.0, 2.0, 'exponential');
|
|
267
|
-
expect(price).toBeCloseTo(10, 6);
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
it('should calculate logarithmic: P = R * ln(S+1)', () => {
|
|
271
|
-
const price = bondingCurvePrice(100, 1.0, 2.0, 'logarithmic');
|
|
272
|
-
expect(price).toBeCloseTo(Math.log(101), 6);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('should calculate sigmoid with plateau', () => {
|
|
276
|
-
const lowSupply = bondingCurvePrice(10, 1.0, 2.0, 'sigmoid', 100);
|
|
277
|
-
const highSupply = bondingCurvePrice(1000, 1.0, 2.0, 'sigmoid', 100);
|
|
278
|
-
// Sigmoid should plateau
|
|
279
|
-
expect(highSupply).toBeGreaterThan(lowSupply);
|
|
280
|
-
expect(highSupply).toBeLessThanOrEqual(1.0); // Bounded by R
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it('should scale with reserve ratio', () => {
|
|
284
|
-
const p1 = bondingCurvePrice(100, 1.0, 2.0, 'exponential');
|
|
285
|
-
const p2 = bondingCurvePrice(100, 2.0, 2.0, 'exponential');
|
|
286
|
-
expect(p2).toBeCloseTo(p1 * 2, 6);
|
|
287
|
-
});
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
describe('bondingCurveBuyCost', () => {
|
|
291
|
-
it('should return 0 for buying 0 amount', () => {
|
|
292
|
-
expect(bondingCurveBuyCost(DEFAULT_BONDING_CURVE, 0)).toBe(0);
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
it('should increase with amount bought', () => {
|
|
296
|
-
const config: BondingCurveConfig = {
|
|
297
|
-
...DEFAULT_BONDING_CURVE,
|
|
298
|
-
reserveRatio: 1.0,
|
|
299
|
-
currentSupply: 10,
|
|
300
|
-
};
|
|
301
|
-
const cost1 = bondingCurveBuyCost(config, 1);
|
|
302
|
-
const cost10 = bondingCurveBuyCost(config, 10);
|
|
303
|
-
expect(cost10).toBeGreaterThan(cost1);
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it('should include transaction fee', () => {
|
|
307
|
-
const noFee: BondingCurveConfig = {
|
|
308
|
-
...DEFAULT_BONDING_CURVE,
|
|
309
|
-
reserveRatio: 1.0,
|
|
310
|
-
currentSupply: 10,
|
|
311
|
-
transactionFee: 0,
|
|
312
|
-
};
|
|
313
|
-
const withFee: BondingCurveConfig = {
|
|
314
|
-
...noFee,
|
|
315
|
-
transactionFee: 0.02,
|
|
316
|
-
};
|
|
317
|
-
const costNoFee = bondingCurveBuyCost(noFee, 5);
|
|
318
|
-
const costWithFee = bondingCurveBuyCost(withFee, 5);
|
|
319
|
-
expect(costWithFee).toBeGreaterThan(costNoFee);
|
|
320
|
-
expect(costWithFee).toBeCloseTo(costNoFee * 1.02, 1);
|
|
321
|
-
});
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
describe('bondingCurveSellRefund', () => {
|
|
325
|
-
it('should return 0 for selling 0 amount', () => {
|
|
326
|
-
expect(bondingCurveSellRefund(DEFAULT_BONDING_CURVE, 0)).toBe(0);
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it('should return 0 when selling more than supply', () => {
|
|
330
|
-
const config: BondingCurveConfig = {
|
|
331
|
-
...DEFAULT_BONDING_CURVE,
|
|
332
|
-
currentSupply: 5,
|
|
333
|
-
};
|
|
334
|
-
expect(bondingCurveSellRefund(config, 10)).toBe(0);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
it('should return less than buy cost (spread from fees)', () => {
|
|
338
|
-
const config: BondingCurveConfig = {
|
|
339
|
-
...DEFAULT_BONDING_CURVE,
|
|
340
|
-
reserveRatio: 1.0,
|
|
341
|
-
currentSupply: 100,
|
|
342
|
-
transactionFee: 0.02,
|
|
343
|
-
};
|
|
344
|
-
const sellRefund = bondingCurveSellRefund(config, 10);
|
|
345
|
-
// Same config but from supply=90 for buy comparison
|
|
346
|
-
const buyConfig = { ...config, currentSupply: 90 };
|
|
347
|
-
const buyCost = bondingCurveBuyCost(buyConfig, 10);
|
|
348
|
-
expect(sellRefund).toBeLessThan(buyCost);
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
describe('spatialPrice', () => {
|
|
353
|
-
it('should return base price when distance factor is 0', () => {
|
|
354
|
-
expect(spatialPrice(100, 50, 0)).toBe(100);
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
it('should return base price when distance is 0', () => {
|
|
358
|
-
expect(spatialPrice(100, 0, 0.01)).toBe(100);
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
it('should increase price with distance', () => {
|
|
362
|
-
const near = spatialPrice(100, 10, 0.01);
|
|
363
|
-
const far = spatialPrice(100, 100, 0.01);
|
|
364
|
-
expect(far).toBeGreaterThan(near);
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
it('should calculate: P_spatial = P_bonding * (1 + factor * distance)', () => {
|
|
368
|
-
const result = spatialPrice(100, 50, 0.01);
|
|
369
|
-
// 100 * (1 + 0.01 * 50) = 100 * 1.5 = 150
|
|
370
|
-
expect(result).toBeCloseTo(150, 6);
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
// =============================================================================
|
|
376
|
-
// TAXABLE WEALTH PRIMITIVES
|
|
377
|
-
// =============================================================================
|
|
378
|
-
|
|
379
|
-
describe('Taxable Wealth Primitives', () => {
|
|
380
|
-
describe('calculateTaxRate', () => {
|
|
381
|
-
it('should return 0 for wealth below threshold', () => {
|
|
382
|
-
expect(calculateTaxRate(5000, 10000, 0.01, 0.05)).toBe(0);
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
it('should return 0 for wealth equal to threshold', () => {
|
|
386
|
-
expect(calculateTaxRate(10000, 10000, 0.01, 0.05)).toBe(0);
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
it('should return positive rate for wealth above threshold', () => {
|
|
390
|
-
const rate = calculateTaxRate(20000, 10000, 0.01, 0.05);
|
|
391
|
-
// log(20000/10000) * 0.01 = log(2) * 0.01 ~ 0.00693
|
|
392
|
-
expect(rate).toBeGreaterThan(0);
|
|
393
|
-
expect(rate).toBeCloseTo(Math.log(2) * 0.01, 4);
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
it('should cap at max effective rate', () => {
|
|
397
|
-
const rate = calculateTaxRate(10000000, 100, 0.1, 0.05);
|
|
398
|
-
expect(rate).toBe(0.05);
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
it('should handle zero threshold', () => {
|
|
402
|
-
expect(calculateTaxRate(1000, 0, 0.01, 0.05)).toBe(0);
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
it('should increase with wealth (logarithmic, W.034)', () => {
|
|
406
|
-
const r1 = calculateTaxRate(20000, 10000, 0.01, 0.05);
|
|
407
|
-
const r2 = calculateTaxRate(100000, 10000, 0.01, 0.05);
|
|
408
|
-
expect(r2).toBeGreaterThan(r1);
|
|
409
|
-
});
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
describe('calculateTaxAmount', () => {
|
|
413
|
-
it('should return 0 for wealth below threshold', () => {
|
|
414
|
-
const amount = calculateTaxAmount(5000, DEFAULT_WEALTH_TAX);
|
|
415
|
-
expect(amount).toBe(0);
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
it('should return positive amount for wealth above threshold', () => {
|
|
419
|
-
const config: WealthTaxConfig = {
|
|
420
|
-
...DEFAULT_WEALTH_TAX,
|
|
421
|
-
threshold: 10000,
|
|
422
|
-
baseRate: 0.01,
|
|
423
|
-
};
|
|
424
|
-
const amount = calculateTaxAmount(50000, config);
|
|
425
|
-
expect(amount).toBeGreaterThan(0);
|
|
426
|
-
});
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
describe('calculateRedistribution', () => {
|
|
430
|
-
it('should split tax between redistribution and burn', () => {
|
|
431
|
-
const result = calculateRedistribution(1000, 10, 0.7);
|
|
432
|
-
expect(result.perRecipient).toBeCloseTo(70, 6); // 700 / 10
|
|
433
|
-
expect(result.burned).toBeCloseTo(300, 6);
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
it('should burn everything when no recipients', () => {
|
|
437
|
-
const result = calculateRedistribution(1000, 0, 0.7);
|
|
438
|
-
expect(result.perRecipient).toBe(0);
|
|
439
|
-
expect(result.burned).toBe(1000);
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
it('should handle zero tax collected', () => {
|
|
443
|
-
const result = calculateRedistribution(0, 10, 0.7);
|
|
444
|
-
expect(result.perRecipient).toBe(0);
|
|
445
|
-
expect(result.burned).toBe(0);
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
it('should burn all when redistribution fraction is 0', () => {
|
|
449
|
-
const result = calculateRedistribution(1000, 10, 0);
|
|
450
|
-
expect(result.perRecipient).toBe(0);
|
|
451
|
-
expect(result.burned).toBe(1000);
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
it('should redistribute all when fraction is 1.0', () => {
|
|
455
|
-
const result = calculateRedistribution(1000, 10, 1.0);
|
|
456
|
-
expect(result.perRecipient).toBeCloseTo(100, 6);
|
|
457
|
-
expect(result.burned).toBeCloseTo(0, 6);
|
|
458
|
-
});
|
|
459
|
-
});
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
// =============================================================================
|
|
463
|
-
// PID CONTROLLER PRIMITIVES
|
|
464
|
-
// =============================================================================
|
|
465
|
-
|
|
466
|
-
describe('PID Controller Primitives', () => {
|
|
467
|
-
describe('createPIDState', () => {
|
|
468
|
-
it('should initialize with zero state', () => {
|
|
469
|
-
const state = createPIDState();
|
|
470
|
-
expect(state.previousError).toBe(0);
|
|
471
|
-
expect(state.integral).toBe(0);
|
|
472
|
-
expect(state.lastOutput).toBe(0);
|
|
473
|
-
expect(state.initialized).toBe(false);
|
|
474
|
-
});
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
describe('updatePID', () => {
|
|
478
|
-
const config: PIDConfig = {
|
|
479
|
-
kp: 1.0,
|
|
480
|
-
ki: 0.1,
|
|
481
|
-
kd: 0.01,
|
|
482
|
-
setpoint: 100,
|
|
483
|
-
outputMin: -1.0,
|
|
484
|
-
outputMax: 1.0,
|
|
485
|
-
integralLimit: 10.0,
|
|
486
|
-
};
|
|
487
|
-
|
|
488
|
-
it('should produce positive output when measurement is below setpoint', () => {
|
|
489
|
-
const state = createPIDState();
|
|
490
|
-
const output = updatePID(config, state, 50, 1.0);
|
|
491
|
-
expect(output).toBeGreaterThan(0);
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
it('should produce negative output when measurement is above setpoint', () => {
|
|
495
|
-
const state = createPIDState();
|
|
496
|
-
const output = updatePID(config, state, 150, 1.0);
|
|
497
|
-
expect(output).toBeLessThan(0);
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
it('should produce zero output when measurement equals setpoint', () => {
|
|
501
|
-
const state = createPIDState();
|
|
502
|
-
const output = updatePID(config, state, 100, 1.0);
|
|
503
|
-
expect(output).toBeCloseTo(0, 6);
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
it('should clamp output to [min, max]', () => {
|
|
507
|
-
const state = createPIDState();
|
|
508
|
-
const output = updatePID(config, state, 0, 1.0);
|
|
509
|
-
expect(output).toBeLessThanOrEqual(config.outputMax);
|
|
510
|
-
expect(output).toBeGreaterThanOrEqual(config.outputMin);
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
it('should return last output for dt <= 0', () => {
|
|
514
|
-
const state = createPIDState();
|
|
515
|
-
state.lastOutput = 0.5;
|
|
516
|
-
const output = updatePID(config, state, 50, 0);
|
|
517
|
-
expect(output).toBe(0.5);
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
it('should converge toward setpoint over iterations', () => {
|
|
521
|
-
const state = createPIDState();
|
|
522
|
-
let measurement = 50;
|
|
523
|
-
const outputs: number[] = [];
|
|
524
|
-
|
|
525
|
-
for (let i = 0; i < 20; i++) {
|
|
526
|
-
const output = updatePID(config, state, measurement, 0.1);
|
|
527
|
-
outputs.push(output);
|
|
528
|
-
// Simulate system responding to control output
|
|
529
|
-
measurement += output * 5;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// Measurement should approach setpoint
|
|
533
|
-
expect(measurement).toBeGreaterThan(50);
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
it('should apply anti-windup to integral term', () => {
|
|
537
|
-
const state = createPIDState();
|
|
538
|
-
// Drive integral high by persistent error
|
|
539
|
-
for (let i = 0; i < 1000; i++) {
|
|
540
|
-
updatePID(config, state, 0, 1.0);
|
|
541
|
-
}
|
|
542
|
-
expect(Math.abs(state.integral)).toBeLessThanOrEqual(config.integralLimit);
|
|
543
|
-
});
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
describe('Dual-Loop PID', () => {
|
|
547
|
-
it('should create initial state with multiplier 1.0', () => {
|
|
548
|
-
const state = createDualLoopPIDState();
|
|
549
|
-
expect(state.faucetMultiplier).toBe(1.0);
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
it('should decrease faucet multiplier when supply is above target', () => {
|
|
553
|
-
const config: DualLoopPIDConfig = {
|
|
554
|
-
innerLoop: { ...DEFAULT_PID },
|
|
555
|
-
outerLoop: { ...DEFAULT_PID, kp: 0.3, ki: 0.005, kd: 0.05 },
|
|
556
|
-
targetMoneySupply: 100000,
|
|
557
|
-
activePlayerCount: 100,
|
|
558
|
-
perCapitaTarget: 1000,
|
|
559
|
-
};
|
|
560
|
-
const state = createDualLoopPIDState();
|
|
561
|
-
|
|
562
|
-
// Supply is double the target
|
|
563
|
-
const multiplier = updateDualLoopPID(config, state, 200000, 100, 1.0);
|
|
564
|
-
// Should reduce faucet since there is too much money
|
|
565
|
-
expect(multiplier).toBeLessThan(1.0);
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
it('should increase faucet multiplier when supply is below target', () => {
|
|
569
|
-
const config: DualLoopPIDConfig = {
|
|
570
|
-
innerLoop: { ...DEFAULT_PID },
|
|
571
|
-
outerLoop: { ...DEFAULT_PID, kp: 0.3, ki: 0.005, kd: 0.05 },
|
|
572
|
-
targetMoneySupply: 100000,
|
|
573
|
-
activePlayerCount: 100,
|
|
574
|
-
perCapitaTarget: 1000,
|
|
575
|
-
};
|
|
576
|
-
const state = createDualLoopPIDState();
|
|
577
|
-
|
|
578
|
-
// Supply is half the target
|
|
579
|
-
const multiplier = updateDualLoopPID(config, state, 50000, 100, 1.0);
|
|
580
|
-
expect(multiplier).toBeGreaterThan(1.0);
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
it('should keep multiplier in [0, 2] range', () => {
|
|
584
|
-
const config: DualLoopPIDConfig = {
|
|
585
|
-
innerLoop: { ...DEFAULT_PID },
|
|
586
|
-
outerLoop: { ...DEFAULT_PID },
|
|
587
|
-
targetMoneySupply: 100000,
|
|
588
|
-
activePlayerCount: 100,
|
|
589
|
-
perCapitaTarget: 1000,
|
|
590
|
-
};
|
|
591
|
-
const state = createDualLoopPIDState();
|
|
592
|
-
|
|
593
|
-
for (let i = 0; i < 100; i++) {
|
|
594
|
-
const m = updateDualLoopPID(config, state, 0, 100, 1.0);
|
|
595
|
-
expect(m).toBeGreaterThanOrEqual(0);
|
|
596
|
-
expect(m).toBeLessThanOrEqual(2);
|
|
597
|
-
}
|
|
598
|
-
});
|
|
599
|
-
});
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
// =============================================================================
|
|
603
|
-
// FAUCET-SINK TRACKING
|
|
604
|
-
// =============================================================================
|
|
605
|
-
|
|
606
|
-
describe('Faucet-Sink Metrics', () => {
|
|
607
|
-
describe('createFaucetSinkMetrics', () => {
|
|
608
|
-
it('should initialize with ratio 1.0', () => {
|
|
609
|
-
const metrics = createFaucetSinkMetrics(1000);
|
|
610
|
-
expect(metrics.ratio).toBe(1.0);
|
|
611
|
-
expect(metrics.totalFaucet).toBe(0);
|
|
612
|
-
expect(metrics.totalSink).toBe(0);
|
|
613
|
-
});
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
describe('recordFaucet', () => {
|
|
617
|
-
it('should accumulate faucet amount', () => {
|
|
618
|
-
const metrics = createFaucetSinkMetrics(1000);
|
|
619
|
-
recordFaucet(metrics, 100);
|
|
620
|
-
expect(metrics.totalFaucet).toBe(100);
|
|
621
|
-
recordFaucet(metrics, 50);
|
|
622
|
-
expect(metrics.totalFaucet).toBe(150);
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
it('should update ratio when sinks exist', () => {
|
|
626
|
-
const metrics = createFaucetSinkMetrics(1000);
|
|
627
|
-
recordSink(metrics, 100);
|
|
628
|
-
recordFaucet(metrics, 200);
|
|
629
|
-
expect(metrics.ratio).toBeCloseTo(2.0, 6);
|
|
630
|
-
});
|
|
631
|
-
|
|
632
|
-
it('should set ratio to Infinity when no sinks', () => {
|
|
633
|
-
const metrics = createFaucetSinkMetrics(1000);
|
|
634
|
-
recordFaucet(metrics, 100);
|
|
635
|
-
expect(metrics.ratio).toBe(Infinity);
|
|
636
|
-
});
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
describe('recordSink', () => {
|
|
640
|
-
it('should accumulate sink amount', () => {
|
|
641
|
-
const metrics = createFaucetSinkMetrics(1000);
|
|
642
|
-
recordSink(metrics, 50);
|
|
643
|
-
expect(metrics.totalSink).toBe(50);
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
it('should update ratio correctly', () => {
|
|
647
|
-
const metrics = createFaucetSinkMetrics(1000);
|
|
648
|
-
recordFaucet(metrics, 100);
|
|
649
|
-
recordSink(metrics, 100);
|
|
650
|
-
expect(metrics.ratio).toBeCloseTo(1.0, 6);
|
|
651
|
-
});
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
describe('resetMetricsPeriod', () => {
|
|
655
|
-
it('should archive current ratio to history', () => {
|
|
656
|
-
const metrics = createFaucetSinkMetrics(1000);
|
|
657
|
-
recordFaucet(metrics, 200);
|
|
658
|
-
recordSink(metrics, 100);
|
|
659
|
-
resetMetricsPeriod(metrics, 2000);
|
|
660
|
-
expect(metrics.ratioHistory).toHaveLength(1);
|
|
661
|
-
expect(metrics.ratioHistory[0]).toBeCloseTo(2.0, 6);
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
it('should reset counters after period reset', () => {
|
|
665
|
-
const metrics = createFaucetSinkMetrics(1000);
|
|
666
|
-
recordFaucet(metrics, 200);
|
|
667
|
-
recordSink(metrics, 100);
|
|
668
|
-
resetMetricsPeriod(metrics, 2000);
|
|
669
|
-
expect(metrics.totalFaucet).toBe(0);
|
|
670
|
-
expect(metrics.totalSink).toBe(0);
|
|
671
|
-
expect(metrics.ratio).toBe(1.0);
|
|
672
|
-
});
|
|
673
|
-
|
|
674
|
-
it('should cap history at 100 entries', () => {
|
|
675
|
-
const metrics = createFaucetSinkMetrics(0);
|
|
676
|
-
for (let i = 0; i < 110; i++) {
|
|
677
|
-
recordFaucet(metrics, 100);
|
|
678
|
-
recordSink(metrics, 50);
|
|
679
|
-
resetMetricsPeriod(metrics, i + 1);
|
|
680
|
-
}
|
|
681
|
-
expect(metrics.ratioHistory.length).toBeLessThanOrEqual(100);
|
|
682
|
-
});
|
|
683
|
-
|
|
684
|
-
it('should not archive if no activity', () => {
|
|
685
|
-
const metrics = createFaucetSinkMetrics(1000);
|
|
686
|
-
resetMetricsPeriod(metrics, 2000);
|
|
687
|
-
expect(metrics.ratioHistory).toHaveLength(0);
|
|
688
|
-
});
|
|
689
|
-
});
|
|
690
|
-
});
|