@jgengine/core 0.26.0 → 0.28.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.
Files changed (143) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/dist/ai/policeResponse.d.ts +253 -0
  3. package/dist/ai/policeResponse.js +421 -0
  4. package/dist/anim/actionLayer.d.ts +47 -0
  5. package/dist/anim/actionLayer.js +63 -0
  6. package/dist/anim/ikSolver.d.ts +14 -0
  7. package/dist/anim/ikSolver.js +37 -0
  8. package/dist/anim/locomotionGraph.d.ts +58 -3
  9. package/dist/anim/locomotionGraph.js +142 -15
  10. package/dist/anim/ragdollRig.d.ts +53 -0
  11. package/dist/anim/ragdollRig.js +132 -0
  12. package/dist/anim/skeletonStandard.d.ts +140 -0
  13. package/dist/anim/skeletonStandard.js +326 -0
  14. package/dist/combat/damageRuntime.d.ts +14 -1
  15. package/dist/combat/damageRuntime.js +62 -4
  16. package/dist/combat/death.js +7 -3
  17. package/dist/combat/deathReason.d.ts +7 -1
  18. package/dist/combat/effects.d.ts +25 -0
  19. package/dist/combat/effects.js +16 -0
  20. package/dist/combat/statusRuntime.d.ts +2 -0
  21. package/dist/combat/statusRuntime.js +1 -0
  22. package/dist/economy/loanBook.d.ts +182 -0
  23. package/dist/economy/loanBook.js +303 -0
  24. package/dist/economy/market.d.ts +161 -0
  25. package/dist/economy/market.js +248 -0
  26. package/dist/economy/transactionLedger.d.ts +162 -0
  27. package/dist/economy/transactionLedger.js +301 -0
  28. package/dist/editor/creatorStorage.js +3 -1
  29. package/dist/editor/simulation.d.ts +5 -2
  30. package/dist/editor/simulation.js +29 -19
  31. package/dist/game/cameraConfig.d.ts +14 -0
  32. package/dist/game/clipRoles.d.ts +5 -4
  33. package/dist/game/clipRoles.js +34 -4
  34. package/dist/game/events.d.ts +27 -0
  35. package/dist/game/gameSave.d.ts +142 -0
  36. package/dist/game/gameSave.js +174 -0
  37. package/dist/game/playableGame.d.ts +74 -6
  38. package/dist/game/saveSlotIndex.d.ts +54 -0
  39. package/dist/game/saveSlotIndex.js +71 -0
  40. package/dist/game/saveSlots.d.ts +2 -0
  41. package/dist/game/saveSlots.js +1 -1
  42. package/dist/game/saveStore.d.ts +8 -0
  43. package/dist/game/saveStore.js +16 -0
  44. package/dist/gameplay.d.ts +17 -2
  45. package/dist/gameplay.js +15 -0
  46. package/dist/inventory/carryLoad.d.ts +48 -0
  47. package/dist/inventory/carryLoad.js +44 -0
  48. package/dist/inventory/equipment.d.ts +95 -0
  49. package/dist/inventory/equipment.js +169 -0
  50. package/dist/inventory/inventoryModel.d.ts +15 -6
  51. package/dist/inventory/inventoryModel.js +54 -5
  52. package/dist/inventory/shapedGrid.d.ts +26 -0
  53. package/dist/inventory/shapedGrid.js +47 -0
  54. package/dist/loot/lootGen.d.ts +186 -0
  55. package/dist/loot/lootGen.js +437 -0
  56. package/dist/loot/lootWeapon.d.ts +35 -0
  57. package/dist/loot/lootWeapon.js +70 -0
  58. package/dist/meta/changelog.d.ts +1 -1
  59. package/dist/meta/changelog.js +156 -1
  60. package/dist/mission/compositions.d.ts +85 -0
  61. package/dist/mission/compositions.js +130 -0
  62. package/dist/mission/mission.d.ts +435 -0
  63. package/dist/mission/mission.js +770 -0
  64. package/dist/mission/triggers.d.ts +61 -0
  65. package/dist/mission/triggers.js +108 -0
  66. package/dist/nav/laneGraph.d.ts +178 -0
  67. package/dist/nav/laneGraph.js +671 -0
  68. package/dist/nav/traffic.d.ts +222 -0
  69. package/dist/nav/traffic.js +918 -0
  70. package/dist/physics/kinematicVehicle.d.ts +36 -0
  71. package/dist/physics/kinematicVehicle.js +15 -1
  72. package/dist/physics/vehicleDamage.d.ts +136 -0
  73. package/dist/physics/vehicleDamage.js +214 -0
  74. package/dist/physics/vehicleParts.d.ts +62 -0
  75. package/dist/physics/vehicleParts.js +65 -0
  76. package/dist/progression/gearScore.d.ts +56 -0
  77. package/dist/progression/gearScore.js +59 -0
  78. package/dist/progression/skillTree.d.ts +129 -0
  79. package/dist/progression/skillTree.js +265 -0
  80. package/dist/progression/xpCurve.d.ts +60 -0
  81. package/dist/progression/xpCurve.js +141 -0
  82. package/dist/runtime/context/combat.js +11 -0
  83. package/dist/scene/builtinSceneKinds.js +2 -0
  84. package/dist/settings/accessibilitySettings.d.ts +81 -0
  85. package/dist/settings/accessibilitySettings.js +152 -0
  86. package/dist/settings/graphicsProfile.d.ts +3 -0
  87. package/dist/settings/graphicsProfile.js +4 -0
  88. package/dist/settings/settingsModel.d.ts +1 -1
  89. package/dist/settings/settingsModel.js +1 -0
  90. package/dist/sim/nearAgents.d.ts +8 -1
  91. package/dist/sim/nearAgents.js +34 -17
  92. package/dist/sim/offScreen.d.ts +2 -1
  93. package/dist/sim/offScreen.js +14 -3
  94. package/dist/sim/population.d.ts +147 -0
  95. package/dist/sim/population.js +182 -0
  96. package/dist/sim/queue.d.ts +90 -0
  97. package/dist/sim/queue.js +171 -0
  98. package/dist/sim/rides.d.ts +287 -0
  99. package/dist/sim/rides.js +471 -0
  100. package/dist/sim/routine.js +19 -11
  101. package/dist/ui/loadingTips.d.ts +41 -0
  102. package/dist/ui/loadingTips.js +64 -0
  103. package/dist/ui/menuNav.d.ts +79 -0
  104. package/dist/ui/menuNav.js +147 -0
  105. package/dist/ui/menuTransition.d.ts +50 -0
  106. package/dist/ui/menuTransition.js +67 -0
  107. package/dist/ui.d.ts +2 -0
  108. package/dist/ui.js +2 -0
  109. package/dist/vfx/effectLibrary.d.ts +98 -0
  110. package/dist/vfx/effectLibrary.js +181 -0
  111. package/dist/vfx/particleBudget.d.ts +40 -0
  112. package/dist/vfx/particleBudget.js +39 -0
  113. package/dist/vfx/particleDirector.d.ts +16 -0
  114. package/dist/vfx/particleDirector.js +18 -0
  115. package/dist/world/authoredSimulation.js +7 -1
  116. package/dist/world/captureCamera.d.ts +30 -0
  117. package/dist/world/captureCamera.js +19 -0
  118. package/dist/world/decalKind.d.ts +48 -0
  119. package/dist/world/decalKind.js +86 -0
  120. package/dist/world/features.d.ts +18 -5
  121. package/dist/world/features.js +1 -0
  122. package/dist/world/grassKind.d.ts +5 -0
  123. package/dist/world/grassKind.js +3 -0
  124. package/dist/world/terraform.d.ts +8 -0
  125. package/dist/world/terrain.d.ts +1 -0
  126. package/dist/world/terrain.js +2 -0
  127. package/dist/world.d.ts +7 -2
  128. package/dist/world.js +5 -0
  129. package/package.json +1 -1
  130. package/skills/jgengine/api.md +2 -2
  131. package/skills/jgengine-combat/SKILL.md +9 -1
  132. package/skills/jgengine-combat/api.md +5 -4
  133. package/skills/jgengine-combat/reference.md +4 -0
  134. package/skills/jgengine-gameplay/SKILL.md +86 -0
  135. package/skills/jgengine-gameplay/api.md +485 -16
  136. package/skills/jgengine-gameplay/capabilities.md +131 -4
  137. package/skills/jgengine-gameplay/reference-systems.md +118 -0
  138. package/skills/jgengine-world/SKILL.md +8 -0
  139. package/skills/jgengine-world/api.md +243 -11
  140. package/skills/jgengine-world/capabilities.md +61 -1
  141. package/skills/jgengine-world/recipes/character-animation.md +25 -2
  142. package/skills/jgengine-world/recipes/vehicle-feel.md +5 -0
  143. package/skills/jgengine-world/reference.md +1 -1
@@ -0,0 +1,248 @@
1
+ import { devtools } from "../devtools/devtools.js";
2
+ function check(condition, message) {
3
+ if (!condition)
4
+ throw new RangeError(message);
5
+ }
6
+ function isMinorUnits(value) {
7
+ return Number.isSafeInteger(value);
8
+ }
9
+ function assertGood(def, config, seen) {
10
+ check(def.id.trim() !== "", 'Market good field "id" must not be empty');
11
+ check(!seen.has(def.id), `Market good "${def.id}" is declared twice`);
12
+ seen.add(def.id);
13
+ check(isMinorUnits(def.basePrice) && def.basePrice > 0, `Market good "${def.id}" basePrice must be a positive integer in minor units`);
14
+ check(Number.isInteger(def.stock) && def.stock >= 0, `Market good "${def.id}" stock must be a non-negative integer`);
15
+ check(Number.isFinite(def.demand) && def.demand >= 0, `Market good "${def.id}" demand must be a non-negative finite number`);
16
+ const cover = def.coverPeriods ?? config.coverPeriods ?? 4;
17
+ check(Number.isFinite(cover) && cover > 0, `Market good "${def.id}" coverPeriods must be positive`);
18
+ const elasticity = def.elasticity ?? config.elasticity ?? 1;
19
+ check(Number.isFinite(elasticity) && elasticity > 0, `Market good "${def.id}" elasticity must be positive`);
20
+ const minFactor = def.minFactor ?? config.minFactor ?? 0.25;
21
+ const maxFactor = def.maxFactor ?? config.maxFactor ?? 4;
22
+ check(minFactor > 0 && maxFactor >= minFactor, `Market good "${def.id}" needs 0 < minFactor <= maxFactor`);
23
+ const cost = def.restockCost ?? def.basePrice;
24
+ check(isMinorUnits(cost) && cost >= 0, `Market good "${def.id}" restockCost must be a non-negative integer in minor units`);
25
+ }
26
+ /** The default price curve: `basePrice × (targetStock / stock)^elasticity`, clamped to the factor bounds. */
27
+ function defaultPrice(input) {
28
+ const scarcity = input.targetStock / Math.max(1, input.stock);
29
+ const raw = input.basePrice * scarcity ** input.elasticity;
30
+ const floor = input.basePrice * input.minFactor;
31
+ const ceiling = input.basePrice * input.maxFactor;
32
+ return Math.round(Math.max(floor, Math.min(ceiling, raw)));
33
+ }
34
+ /**
35
+ * A market whose price comes from supply and demand over finite stock, as plain serializable state.
36
+ *
37
+ * The stated curve is `price = clamp(basePrice × (coverPeriods × demand / max(stock, 1))^elasticity)`
38
+ * between `basePrice × minFactor` and `basePrice × maxFactor`. Both terms are live: selling thins
39
+ * stock and raises the price, restocking lowers it, and `recordDemand` moves demand from what
40
+ * buyers actually took and could not take. `priceCurve` replaces the curve, and `retune` changes a
41
+ * good's policy in play. Money is integer minor units throughout.
42
+ *
43
+ * @capability supply-demand-pricing price goods from finite stock and live demand on a stated curve
44
+ */
45
+ export function createMarket(config = {}) {
46
+ const records = new Map();
47
+ const seen = new Set();
48
+ for (const def of config.goods ?? []) {
49
+ assertGood(def, config, seen);
50
+ records.set(def.id, {
51
+ id: def.id,
52
+ basePrice: def.basePrice,
53
+ coverPeriods: def.coverPeriods ?? config.coverPeriods ?? 4,
54
+ elasticity: def.elasticity ?? config.elasticity ?? 1,
55
+ minFactor: def.minFactor ?? config.minFactor ?? 0.25,
56
+ maxFactor: def.maxFactor ?? config.maxFactor ?? 4,
57
+ restockCost: def.restockCost ?? def.basePrice,
58
+ stock: def.stock,
59
+ demand: def.demand,
60
+ });
61
+ }
62
+ const demandSmoothing = config.demandSmoothing ?? 0.5;
63
+ const unmetDemandFactor = config.unmetDemandFactor ?? 0.5;
64
+ const curve = config.priceCurve ?? defaultPrice;
65
+ let counters = { sales: 0, unitsSold: 0, revenue: 0, unmetUnits: 0 };
66
+ const inputsOf = (record, stock) => ({
67
+ basePrice: record.basePrice,
68
+ stock,
69
+ demand: record.demand,
70
+ targetStock: record.coverPeriods * record.demand,
71
+ elasticity: record.elasticity,
72
+ minFactor: record.minFactor,
73
+ maxFactor: record.maxFactor,
74
+ });
75
+ const priceAt = (record, stock) => Math.round(curve(inputsOf(record, stock)));
76
+ const readGoods = () => Array.from(records.values(), (record) => ({ id: record.id, stock: record.stock, demand: record.demand }));
77
+ const probe = config.devtoolsName === undefined
78
+ ? null
79
+ : devtools.probes.register(config.devtoolsName, () => readStats());
80
+ function readStats() {
81
+ let unitsInStock = 0;
82
+ let stockValue = 0;
83
+ for (const record of records.values()) {
84
+ unitsInStock += record.stock;
85
+ stockValue += record.stock * priceAt(record, record.stock);
86
+ }
87
+ return { goods: records.size, unitsInStock, stockValue, ...counters };
88
+ }
89
+ return {
90
+ priceOf(goodId) {
91
+ const record = records.get(goodId);
92
+ return record === undefined ? 0 : priceAt(record, record.stock);
93
+ },
94
+ priceAfter(goodId, qty) {
95
+ const record = records.get(goodId);
96
+ if (record === undefined)
97
+ return 0;
98
+ const moved = Number.isFinite(qty) ? qty : 0;
99
+ return priceAt(record, Math.max(0, record.stock - moved));
100
+ },
101
+ targetStockOf(goodId) {
102
+ const record = records.get(goodId);
103
+ return record === undefined ? 0 : record.coverPeriods * record.demand;
104
+ },
105
+ stockOf(goodId) {
106
+ return records.get(goodId)?.stock ?? 0;
107
+ },
108
+ demandOf(goodId) {
109
+ return records.get(goodId)?.demand ?? 0;
110
+ },
111
+ restockCostOf(goodId) {
112
+ return records.get(goodId)?.restockCost ?? 0;
113
+ },
114
+ quote(goodId, qty) {
115
+ const record = records.get(goodId);
116
+ if (record === undefined)
117
+ return null;
118
+ const unitPrice = priceAt(record, record.stock);
119
+ const served = Math.max(0, Math.min(qty, record.stock));
120
+ return {
121
+ goodId,
122
+ qty,
123
+ unitPrice,
124
+ total: unitPrice * served,
125
+ shortfall: qty - served,
126
+ nextUnitPrice: priceAt(record, record.stock - served),
127
+ };
128
+ },
129
+ sell(goodId, qty) {
130
+ const record = records.get(goodId);
131
+ if (record === undefined)
132
+ return { ok: false, reason: "unknown-good" };
133
+ if (!Number.isInteger(qty) || qty < 1)
134
+ return { ok: false, reason: "invalid-qty" };
135
+ if (qty > record.stock)
136
+ return { ok: false, reason: "out-of-stock", shortfall: qty - record.stock };
137
+ const unitPrice = priceAt(record, record.stock);
138
+ record.stock -= qty;
139
+ counters = {
140
+ sales: counters.sales + 1,
141
+ unitsSold: counters.unitsSold + qty,
142
+ revenue: counters.revenue + unitPrice * qty,
143
+ unmetUnits: counters.unmetUnits,
144
+ };
145
+ return { ok: true, goodId, qty, unitPrice, total: unitPrice * qty };
146
+ },
147
+ restock(goodId, qty) {
148
+ const record = records.get(goodId);
149
+ if (record === undefined)
150
+ return false;
151
+ if (!Number.isInteger(qty) || qty < 1)
152
+ return false;
153
+ record.stock += qty;
154
+ return true;
155
+ },
156
+ recordDemand(goodId, unitsSold, unitsUnmet = 0) {
157
+ const record = records.get(goodId);
158
+ if (record === undefined)
159
+ return;
160
+ const sold = Number.isFinite(unitsSold) ? Math.max(0, unitsSold) : 0;
161
+ const unmet = Number.isFinite(unitsUnmet) ? Math.max(0, unitsUnmet) : 0;
162
+ const smoothed = record.demand + demandSmoothing * (sold - record.demand);
163
+ record.demand = Math.max(0, smoothed + unmetDemandFactor * unmet);
164
+ if (unmet > 0)
165
+ counters = { ...counters, unmetUnits: counters.unmetUnits + unmet };
166
+ },
167
+ setDemand(goodId, demand) {
168
+ const record = records.get(goodId);
169
+ if (record === undefined)
170
+ return;
171
+ check(Number.isFinite(demand) && demand >= 0, `Market good "${goodId}" demand must be a non-negative finite number`);
172
+ record.demand = demand;
173
+ },
174
+ retune(goodId, patch) {
175
+ const record = records.get(goodId);
176
+ if (record === undefined)
177
+ return false;
178
+ const merged = {
179
+ id: goodId,
180
+ basePrice: patch.basePrice ?? record.basePrice,
181
+ stock: record.stock,
182
+ demand: record.demand,
183
+ coverPeriods: patch.coverPeriods ?? record.coverPeriods,
184
+ elasticity: patch.elasticity ?? record.elasticity,
185
+ minFactor: patch.minFactor ?? record.minFactor,
186
+ maxFactor: patch.maxFactor ?? record.maxFactor,
187
+ restockCost: patch.restockCost ?? record.restockCost,
188
+ };
189
+ try {
190
+ assertGood(merged, config, new Set());
191
+ }
192
+ catch {
193
+ return false;
194
+ }
195
+ record.basePrice = merged.basePrice;
196
+ record.coverPeriods = merged.coverPeriods ?? record.coverPeriods;
197
+ record.elasticity = merged.elasticity ?? record.elasticity;
198
+ record.minFactor = merged.minFactor ?? record.minFactor;
199
+ record.maxFactor = merged.maxFactor ?? record.maxFactor;
200
+ record.restockCost = merged.restockCost ?? record.restockCost;
201
+ return true;
202
+ },
203
+ list: readGoods,
204
+ snapshot() {
205
+ return { goods: readGoods(), counters: { ...counters } };
206
+ },
207
+ restore(next) {
208
+ if (!Array.isArray(next.goods))
209
+ return false;
210
+ const restored = [];
211
+ for (const good of next.goods) {
212
+ const record = records.get(good.id);
213
+ if (record === undefined)
214
+ return false;
215
+ if (!Number.isInteger(good.stock) || good.stock < 0)
216
+ return false;
217
+ if (!Number.isFinite(good.demand) || good.demand < 0)
218
+ return false;
219
+ restored.push({ id: good.id, stock: good.stock, demand: good.demand });
220
+ }
221
+ const countersIn = next.counters;
222
+ if (countersIn === undefined ||
223
+ !Number.isInteger(countersIn.sales) || countersIn.sales < 0 ||
224
+ !Number.isInteger(countersIn.unitsSold) || countersIn.unitsSold < 0 ||
225
+ !isMinorUnits(countersIn.revenue) ||
226
+ !Number.isInteger(countersIn.unmetUnits) || countersIn.unmetUnits < 0)
227
+ return false;
228
+ for (const good of restored) {
229
+ const record = records.get(good.id);
230
+ record.stock = good.stock;
231
+ record.demand = good.demand;
232
+ }
233
+ counters = { ...countersIn };
234
+ return true;
235
+ },
236
+ stats: readStats,
237
+ reset() {
238
+ for (const record of records.values()) {
239
+ record.stock = 0;
240
+ record.demand = 0;
241
+ }
242
+ counters = { sales: 0, unitsSold: 0, revenue: 0, unmetUnits: 0 };
243
+ },
244
+ dispose() {
245
+ probe?.();
246
+ },
247
+ };
248
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Money in this module is an integer count of **minor units** — cents when the currency has two
3
+ * decimals, whole coins when it has none. Every posted amount and every balance is a safe integer,
4
+ * so a long simulation never accumulates float drift. `currency` is a free string the game owns.
5
+ */
6
+ /**
7
+ * One recorded movement of money. Every post keeps the balance it produced and the reason it
8
+ * happened, so a summary or a post-mortem reads the books rather than reconstructing them.
9
+ */
10
+ export interface LedgerEntry {
11
+ /** 1-based, monotonic within the ledger. */
12
+ seq: number;
13
+ /** Caller clock, in seconds. */
14
+ at: number;
15
+ account: string;
16
+ currency: string;
17
+ /** Signed integer minor units; negative debits the account, positive credits it. */
18
+ amount: number;
19
+ /** The account's balance in this currency after the entry was applied. */
20
+ balanceAfter: number;
21
+ /** Caller-owned reason code — `"sale"`, `"upkeep"`, `"loan-interest"`. Never interpreted here. */
22
+ reason: string;
23
+ /** Optional id of what caused the entry — a good, an order, a loan. */
24
+ ref?: string;
25
+ }
26
+ /** A post to attempt. */
27
+ export interface LedgerPostInput {
28
+ at: number;
29
+ account: string;
30
+ currency: string;
31
+ /** Signed integer minor units. */
32
+ amount: number;
33
+ reason: string;
34
+ ref?: string;
35
+ /** `true` allows any negative balance, `{ max }` caps how far into the red the post may go. */
36
+ overdraft?: boolean | {
37
+ max: number;
38
+ };
39
+ }
40
+ /** Outcome of {@link TransactionLedger.post}. A rejection leaves every balance untouched. */
41
+ export type LedgerPostResult = {
42
+ ok: true;
43
+ entry: LedgerEntry;
44
+ } | {
45
+ ok: false;
46
+ reason: "unknown-account" | "invalid-amount" | "invalid-reason" | "insufficient-funds";
47
+ };
48
+ /** A recurring charge — upkeep, rent, a subscription — settled by {@link TransactionLedger.chargeDue}. */
49
+ export interface RecurringCharge {
50
+ /** Stable id, unique within the ledger. */
51
+ id: string;
52
+ account: string;
53
+ currency: string;
54
+ /** Positive integer minor units charged each cadence. */
55
+ amountPerPeriod: number;
56
+ /** Cadence in seconds; must be `> 0`. */
57
+ everySeconds: number;
58
+ /** First due time in seconds; defaults to the ledger clock when the charge is added. */
59
+ startSeconds?: number;
60
+ reason: string;
61
+ ref?: string;
62
+ /** How cycles that came due between two advances are settled: `"each"` (default), `"sum"`, `"skip"`. */
63
+ catchUp?: "each" | "sum" | "skip";
64
+ /** Due cycles beyond this in one advance are dropped rather than settled. */
65
+ maxCatchUp?: number;
66
+ }
67
+ /** Mutable per-charge progress, kept apart from the {@link RecurringCharge} definition. */
68
+ export interface ChargeCursor {
69
+ nextDueSeconds: number;
70
+ fired: number;
71
+ /** Cycles that came due and could not be settled for lack of funds. */
72
+ missed: number;
73
+ done: boolean;
74
+ }
75
+ /** What one reason code did to the books, kept across entry trimming. */
76
+ export interface LedgerTotals {
77
+ count: number;
78
+ /** Sum of the signed amounts posted under this reason, in minor units. */
79
+ net: number;
80
+ }
81
+ /** Counters of a ledger, for a probe or a summary line. */
82
+ export interface LedgerStats {
83
+ accounts: number;
84
+ entries: number;
85
+ charges: number;
86
+ seq: number;
87
+ }
88
+ /** Fully serializable ledger state: clock, balances, journal, totals, charges and cursors. */
89
+ export interface LedgerState {
90
+ nowSeconds: number;
91
+ accounts: Record<string, Record<string, number>>;
92
+ entries: readonly LedgerEntry[];
93
+ totals: Record<string, LedgerTotals>;
94
+ charges: Record<string, RecurringCharge>;
95
+ cursors: Record<string, ChargeCursor>;
96
+ seq: number;
97
+ }
98
+ /** What one {@link TransactionLedger.chargeDue} settled, and what it could not. */
99
+ export interface LedgerAdvanceResult {
100
+ charged: readonly LedgerEntry[];
101
+ /** Charges that came due with no funds, and how many cycles were missed. */
102
+ missed: readonly {
103
+ id: string;
104
+ cycles: number;
105
+ }[];
106
+ }
107
+ /** A filter over the journal. */
108
+ export interface LedgerEntryFilter {
109
+ account?: string;
110
+ reason?: string;
111
+ /** Only entries at or after this time. */
112
+ since?: number;
113
+ /** Keep at most this many of the most recent matches. */
114
+ limit?: number;
115
+ }
116
+ /** Options for {@link createTransactionLedger}. */
117
+ export interface TransactionLedgerOptions {
118
+ /** Journal entries retained; older ones drop and live on in {@link LedgerTotals} (default `1024`). */
119
+ maxEntries?: number;
120
+ /** Safety bound on cycles settled per charge per advance (default `256`). */
121
+ maxCyclesPerAdvance?: number;
122
+ /** Register a devtools probe under this name reporting {@link LedgerStats} and per-reason totals. */
123
+ devtoolsName?: string;
124
+ }
125
+ /** The books: balances with a reason-tagged journal, and recurring charges on a cadence. */
126
+ export interface TransactionLedger {
127
+ /** Create an account/currency pair, or add a currency to an existing account. An existing balance is kept. */
128
+ openAccount(account: string, currency: string, initial?: number): void;
129
+ balanceOf(account: string, currency: string): number;
130
+ /** Record one movement of money. Rejected without mutation when the account is unknown, the amount unusable, or funds are short. */
131
+ post(input: LedgerPostInput): LedgerPostResult;
132
+ /** Register a recurring charge. `false` for a duplicate id or unusable terms. */
133
+ addCharge(charge: RecurringCharge): boolean;
134
+ /** Remove a recurring charge and its cursor. `false` if it was not registered. */
135
+ removeCharge(id: string): boolean;
136
+ /** Advance the clock, settling every charge due at or before `nowSeconds` in sorted-id order. */
137
+ chargeDue(nowSeconds: number): LedgerAdvanceResult;
138
+ /** Journal entries, newest last, filtered and capped. Copies, safe to retain. */
139
+ entries(filter?: LedgerEntryFilter): LedgerEntry[];
140
+ /** Per-reason counts and net amounts over the ledger's whole life. */
141
+ totals(): Record<string, LedgerTotals>;
142
+ accounts(): Record<string, Record<string, number>>;
143
+ snapshot(): LedgerState;
144
+ /** Replace state; returns `false` without mutation when the state is malformed. */
145
+ restore(next: LedgerState): boolean;
146
+ stats(): LedgerStats;
147
+ reset(): void;
148
+ /** Drop the devtools probe registered by `devtoolsName`. Safe to call twice. */
149
+ dispose(): void;
150
+ }
151
+ /**
152
+ * The books a business keeps: per-account, per-currency balances in integer minor units with a
153
+ * journal that records every movement, the balance it produced and the reason it happened.
154
+ * {@link TransactionLedger.post} is the one money path — sales, purchases, upkeep and loan
155
+ * payments all go through it, so nothing moves without a reason on the record.
156
+ * {@link TransactionLedger.chargeDue} settles recurring charges (upkeep, rent) on their own
157
+ * cadence, bounded per advance; a cycle with no funds is counted as missed instead of overdrawing.
158
+ * Journal memory is bounded by `maxEntries`, with `totals()` keeping the per-reason summary beyond it.
159
+ *
160
+ * @capability transaction-ledger record every money movement with its reason over per-account integer minor-unit balances
161
+ */
162
+ export declare function createTransactionLedger(options?: TransactionLedgerOptions): TransactionLedger;