@shipload/sdk 1.0.0-next.7 → 1.0.0-next.71

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 (203) hide show
  1. package/lib/scan.d.ts +52 -0
  2. package/lib/scan.js +162 -0
  3. package/lib/scan.js.map +1 -0
  4. package/lib/scan.m.js +152 -0
  5. package/lib/scan.m.js.map +1 -0
  6. package/lib/shipload.d.ts +5822 -1484
  7. package/lib/shipload.js +20838 -5419
  8. package/lib/shipload.js.map +1 -1
  9. package/lib/shipload.m.js +20674 -5669
  10. package/lib/shipload.m.js.map +1 -1
  11. package/lib/testing.d.ts +1691 -0
  12. package/lib/testing.js +7109 -0
  13. package/lib/testing.js.map +1 -0
  14. package/lib/testing.m.js +7103 -0
  15. package/lib/testing.m.js.map +1 -0
  16. package/package.json +20 -2
  17. package/src/capabilities/craftable.test.ts +82 -0
  18. package/src/capabilities/craftable.ts +82 -0
  19. package/src/capabilities/crafting.test.ts +80 -0
  20. package/src/capabilities/crafting.ts +50 -8
  21. package/src/capabilities/gathering.test.ts +29 -0
  22. package/src/capabilities/gathering.ts +38 -20
  23. package/src/capabilities/hauling.ts +0 -5
  24. package/src/capabilities/index.ts +0 -1
  25. package/src/capabilities/modules.ts +27 -30
  26. package/src/capabilities/movement.ts +1 -1
  27. package/src/capabilities/storage.test.ts +26 -0
  28. package/src/capabilities/storage.ts +22 -7
  29. package/src/contracts/fund.ts +306 -0
  30. package/src/contracts/platform.ts +312 -3
  31. package/src/contracts/server.ts +2793 -478
  32. package/src/contracts/token.ts +170 -0
  33. package/src/contracts.ts +2 -0
  34. package/src/coordinates/address.ts +88 -0
  35. package/src/coordinates/constants.test.ts +15 -0
  36. package/src/coordinates/constants.ts +23 -0
  37. package/src/coordinates/index.ts +15 -0
  38. package/src/coordinates/memo.test.ts +47 -0
  39. package/src/coordinates/memo.ts +20 -0
  40. package/src/coordinates/permutation.ts +77 -0
  41. package/src/coordinates/regions.ts +48 -0
  42. package/src/coordinates/sectors.ts +115 -0
  43. package/src/data/capabilities.ts +49 -18
  44. package/src/data/capability-formulas.ts +26 -14
  45. package/src/data/catalog.test.ts +19 -0
  46. package/src/data/catalog.ts +13 -3
  47. package/src/data/charters.json +396 -0
  48. package/src/data/citizenry-adjectives.json +42 -0
  49. package/src/data/citizenry-nouns.json +50 -0
  50. package/src/data/colors.ts +14 -32
  51. package/src/data/entities.json +421 -15
  52. package/src/data/item-ids.ts +53 -13
  53. package/src/data/items.json +330 -77
  54. package/src/data/kind-registry.json +212 -0
  55. package/src/data/kind-registry.ts +192 -0
  56. package/src/data/metadata.ts +312 -51
  57. package/src/data/recipes-runtime.ts +6 -23
  58. package/src/data/recipes.json +2173 -173
  59. package/src/data/tiers.ts +9 -6
  60. package/src/derivation/build-methods.test.ts +71 -0
  61. package/src/derivation/build-methods.ts +50 -0
  62. package/src/derivation/capabilities.test.ts +348 -0
  63. package/src/derivation/capabilities.ts +707 -0
  64. package/src/derivation/capability-mappings.ts +60 -16
  65. package/src/derivation/crafting.test.ts +17 -0
  66. package/src/derivation/crafting.ts +57 -34
  67. package/src/derivation/index.ts +27 -2
  68. package/src/derivation/recipe-usage.test.ts +88 -0
  69. package/src/derivation/recipe-usage.ts +141 -0
  70. package/src/derivation/reserve-regen.ts +34 -0
  71. package/src/derivation/resources.ts +9 -1
  72. package/src/derivation/rollups.test.ts +55 -0
  73. package/src/derivation/rollups.ts +72 -0
  74. package/src/derivation/stars.test.ts +67 -0
  75. package/src/derivation/stars.ts +25 -0
  76. package/src/derivation/stat-scaling.ts +12 -0
  77. package/src/derivation/stats.ts +6 -6
  78. package/src/derivation/strata.ts +5 -3
  79. package/src/derivation/stratum.ts +26 -22
  80. package/src/derivation/tiers.ts +40 -7
  81. package/src/derivation/upgrades.ts +14 -0
  82. package/src/derivation/wormhole.ts +154 -0
  83. package/src/entities/entity.ts +102 -0
  84. package/src/entities/gamestate.ts +3 -28
  85. package/src/entities/makers.ts +144 -136
  86. package/src/entities/slot-multiplier.ts +43 -0
  87. package/src/entity/hydrate.test.ts +156 -0
  88. package/src/entity/hydrate.ts +86 -0
  89. package/src/errors.ts +21 -16
  90. package/src/format.ts +42 -9
  91. package/src/index-module.ts +425 -32
  92. package/src/influence/ballot.test.ts +103 -0
  93. package/src/influence/ballot.ts +98 -0
  94. package/src/influence/categories.ts +32 -0
  95. package/src/influence/charter-metadata.test.ts +136 -0
  96. package/src/influence/charter-metadata.ts +236 -0
  97. package/src/influence/charters.test.ts +112 -0
  98. package/src/influence/charters.ts +150 -0
  99. package/src/influence/citizenry.ts +38 -0
  100. package/src/influence/civic-shuttle.test.ts +202 -0
  101. package/src/influence/civic-shuttle.ts +142 -0
  102. package/src/influence/civic.test.ts +287 -0
  103. package/src/influence/civic.ts +269 -0
  104. package/src/influence/constants.ts +84 -0
  105. package/src/influence/decay.ts +20 -0
  106. package/src/influence/decomp.ts +95 -0
  107. package/src/influence/demand.ts +106 -0
  108. package/src/influence/duration.ts +101 -0
  109. package/src/influence/index.ts +118 -0
  110. package/src/influence/influence.test.ts +450 -0
  111. package/src/influence/quality.ts +25 -0
  112. package/src/influence/valuation.ts +154 -0
  113. package/src/managers/actions.ts +950 -93
  114. package/src/managers/balances.test.ts +161 -0
  115. package/src/managers/balances.ts +93 -0
  116. package/src/managers/base.ts +10 -2
  117. package/src/managers/cluster.test.ts +39 -0
  118. package/src/managers/cluster.ts +53 -0
  119. package/src/managers/construction-types.ts +80 -0
  120. package/src/managers/construction.ts +453 -0
  121. package/src/managers/context.ts +58 -1
  122. package/src/managers/coordinates.ts +14 -0
  123. package/src/managers/entities.ts +35 -63
  124. package/src/managers/epochs.ts +72 -1
  125. package/src/managers/index.ts +38 -1
  126. package/src/managers/influence.test.ts +56 -0
  127. package/src/managers/influence.ts +425 -0
  128. package/src/managers/jobs.test.ts +96 -0
  129. package/src/managers/jobs.ts +58 -0
  130. package/src/managers/locations.ts +25 -29
  131. package/src/managers/nft.test.ts +21 -0
  132. package/src/managers/nft.ts +110 -0
  133. package/src/managers/players.ts +25 -0
  134. package/src/managers/plot.ts +122 -0
  135. package/src/nft/atomicassets.abi.json +1342 -0
  136. package/src/nft/atomicassets.ts +237 -0
  137. package/src/nft/atomicdata.ts +130 -0
  138. package/src/nft/buildImmutableData.ts +388 -0
  139. package/src/nft/description.ts +222 -100
  140. package/src/nft/index.ts +3 -0
  141. package/src/planner/index.ts +296 -0
  142. package/src/planner/planner.test.ts +459 -0
  143. package/src/receipts.ts +22 -0
  144. package/src/resolution/describe-module.ts +43 -25
  145. package/src/resolution/display-name.ts +39 -11
  146. package/src/resolution/resolve-item.test.ts +48 -0
  147. package/src/resolution/resolve-item.ts +112 -58
  148. package/src/scan/index.ts +244 -0
  149. package/src/scan/scan-wasm.base64.ts +2 -0
  150. package/src/scheduling/accessor.ts +65 -23
  151. package/src/scheduling/availability.test.ts +320 -0
  152. package/src/scheduling/availability.ts +251 -0
  153. package/src/scheduling/cancel.test.ts +550 -0
  154. package/src/scheduling/cancel.ts +254 -0
  155. package/src/scheduling/cluster-stock.test.ts +31 -0
  156. package/src/scheduling/cluster-stock.ts +15 -0
  157. package/src/scheduling/energy.ts +47 -0
  158. package/src/scheduling/epoch.ts +1 -0
  159. package/src/scheduling/idle-resolve.ts +45 -0
  160. package/src/scheduling/jobs.test.ts +448 -0
  161. package/src/scheduling/jobs.ts +343 -0
  162. package/src/scheduling/lane-core.ts +128 -0
  163. package/src/scheduling/lanes.test.ts +250 -0
  164. package/src/scheduling/lanes.ts +279 -0
  165. package/src/scheduling/projection.ts +265 -149
  166. package/src/scheduling/schedule.test.ts +163 -0
  167. package/src/scheduling/schedule.ts +282 -116
  168. package/src/scheduling/task-cargo.test.ts +110 -0
  169. package/src/scheduling/task-cargo.ts +62 -0
  170. package/src/scheduling/task-effects.test.ts +35 -0
  171. package/src/scheduling/task-effects.ts +34 -0
  172. package/src/scheduling/unwrap.test.ts +86 -0
  173. package/src/scheduling/unwrap.ts +190 -0
  174. package/src/shipload.ts +63 -4
  175. package/src/subscriptions/manager.cluster.test.ts +51 -0
  176. package/src/subscriptions/manager.ts +245 -172
  177. package/src/subscriptions/mappers.ts +5 -8
  178. package/src/subscriptions/types.ts +28 -3
  179. package/src/testing/catalog-hash.ts +19 -0
  180. package/src/testing/index.ts +2 -0
  181. package/src/testing/projection-parity.ts +167 -0
  182. package/src/travel/reach.test.ts +86 -0
  183. package/src/travel/reach.ts +34 -0
  184. package/src/travel/route-build.test.ts +63 -0
  185. package/src/travel/route-build.ts +60 -0
  186. package/src/travel/route-planner.ts +288 -0
  187. package/src/travel/route-simulator.ts +170 -0
  188. package/src/travel/travel.ts +190 -123
  189. package/src/types/capabilities.ts +37 -8
  190. package/src/types/entity.ts +3 -3
  191. package/src/types/index.ts +0 -1
  192. package/src/types.ts +81 -23
  193. package/src/utils/cargo.test.ts +14 -0
  194. package/src/utils/cargo.ts +2 -0
  195. package/src/utils/display-name.ts +70 -0
  196. package/src/utils/hash.ts +22 -2
  197. package/src/utils/system.ts +24 -5
  198. package/src/capabilities/loading.ts +0 -8
  199. package/src/entities/container.ts +0 -123
  200. package/src/entities/ship-deploy.ts +0 -295
  201. package/src/entities/ship.ts +0 -221
  202. package/src/entities/warehouse.ts +0 -127
  203. package/src/types/entity-traits.ts +0 -69
@@ -0,0 +1,287 @@
1
+ import {describe, expect, test} from 'bun:test'
2
+ import {Name} from '@wharfkit/antelope'
3
+
4
+ import {
5
+ MODULE_BUILDER,
6
+ MODULE_CRAFTER,
7
+ MODULE_LOADER,
8
+ MODULE_STORAGE,
9
+ } from '../capabilities/modules'
10
+ import {
11
+ ITEM_BUILDER_T1,
12
+ ITEM_CONSTRUCTION_DOCK_T1_PACKED,
13
+ ITEM_CRAFTER_T1,
14
+ ITEM_DEPOT_T1_PACKED,
15
+ ITEM_LOADER_T1,
16
+ ITEM_STORAGE_T1,
17
+ ITEM_WORKSHOP_T1_PACKED,
18
+ } from '../data/item-ids'
19
+ import {ENTITY_DEPOT, ENTITY_SHIP, ENTITY_WORKSHOP} from '../data/kind-registry'
20
+ import {decodeStats, encodeStats} from '../derivation/crafting'
21
+ import {CHARTER_REGISTRY, charterNode, type CharterNode} from './charters'
22
+ import {
23
+ civicBuildingFor,
24
+ civicHullItem,
25
+ civicModuleItemFor,
26
+ civicStatModuleType,
27
+ emptyCivicModules,
28
+ fitCivicModules,
29
+ isCivicEntity,
30
+ isCivicKind,
31
+ previewCharterGrants,
32
+ raiseCivicStat,
33
+ type CivicStanding,
34
+ } from './civic'
35
+ import {
36
+ CHARTER_BASELINE_STAT,
37
+ CHARTER_RUNG_STEP,
38
+ CIVIC_DEPOT,
39
+ CIVIC_DOCK,
40
+ CIVIC_GRANT_RUNG,
41
+ CIVIC_SPEED_CAP,
42
+ CIVIC_STAT_CRAFT_SPEED,
43
+ CIVIC_STAT_STORAGE_CAPACITY,
44
+ CIVIC_STAT_TRANSFER_SPEED,
45
+ CIVIC_WORKSHOP,
46
+ } from './constants'
47
+ import {getStatCount} from './quality'
48
+
49
+ const NODE_WORKSHOP = 10001
50
+ const NODE_WORKSHOP_TUNEUPS = [10100201, 10100202, 10100203, 10100204]
51
+ const NODE_WORKSHOP_TUNEUP = NODE_WORKSHOP_TUNEUPS[0]
52
+ const NODE_DOCK = 30100001
53
+ const NODE_DOCK_TUNEUPS = [30100301, 30100302, 30100303, 30100304]
54
+ const NODE_DEPOT = 40100001
55
+ const NODE_DEPOT_BAYS = [40100101, 40100102, 40100103, 40100104]
56
+ const NODE_DEPOT_LOADERS = [40100401, 40100402, 40100403, 40100404]
57
+ const DEPOT_LOADER_SLOTS = [0, 1]
58
+ const DEPOT_BAY_SLOTS = [2, 3, 4, 5]
59
+
60
+ function node(nodeId: number): CharterNode {
61
+ const found = charterNode(nodeId)
62
+ if (!found) throw new Error(`no charter node ${nodeId}`)
63
+ return found
64
+ }
65
+
66
+ function stats(
67
+ preview: {modules: {installed?: {item_id: unknown; stats: unknown}}[]},
68
+ slot: number
69
+ ) {
70
+ const installed = preview.modules[slot].installed
71
+ if (!installed) throw new Error(`slot ${slot} is empty`)
72
+ const itemId = Number(installed.item_id)
73
+ return {itemId, stats: decodeStats(BigInt(String(installed.stats)), getStatCount(itemId))}
74
+ }
75
+
76
+ function complete(standing: CivicStanding, nodeId: number): CivicStanding {
77
+ const next = {...standing}
78
+ for (const preview of previewCharterGrants(node(nodeId), standing)) {
79
+ next[preview.building] = preview.modules
80
+ }
81
+ return next
82
+ }
83
+
84
+ describe('civic building tables', () => {
85
+ test('each building maps to its restored hull item', () => {
86
+ expect(civicHullItem(CIVIC_WORKSHOP)).toBe(ITEM_WORKSHOP_T1_PACKED)
87
+ expect(civicHullItem(CIVIC_DOCK)).toBe(ITEM_CONSTRUCTION_DOCK_T1_PACKED)
88
+ expect(civicHullItem(CIVIC_DEPOT)).toBe(ITEM_DEPOT_T1_PACKED)
89
+ expect(civicHullItem(0)).toBe(0)
90
+ })
91
+
92
+ test('each civic stat is carried by one player T1 module type', () => {
93
+ expect(civicStatModuleType(CIVIC_STAT_CRAFT_SPEED)).toBe(MODULE_CRAFTER)
94
+ expect(civicStatModuleType(CIVIC_STAT_STORAGE_CAPACITY)).toBe(MODULE_STORAGE)
95
+ expect(civicStatModuleType(CIVIC_STAT_TRANSFER_SPEED)).toBe(MODULE_LOADER)
96
+ expect(civicModuleItemFor(MODULE_CRAFTER)).toBe(ITEM_CRAFTER_T1)
97
+ expect(civicModuleItemFor(MODULE_BUILDER)).toBe(ITEM_BUILDER_T1)
98
+ expect(civicModuleItemFor(MODULE_LOADER)).toBe(ITEM_LOADER_T1)
99
+ expect(civicModuleItemFor(MODULE_STORAGE)).toBe(ITEM_STORAGE_T1)
100
+ })
101
+
102
+ test('civic kinds are the four buildings and nothing else', () => {
103
+ expect(isCivicKind(ENTITY_WORKSHOP)).toBe(true)
104
+ expect(isCivicKind('depot')).toBe(true)
105
+ expect(isCivicKind(ENTITY_SHIP)).toBe(false)
106
+ expect(civicBuildingFor(ENTITY_DEPOT)).toBe(CIVIC_DEPOT)
107
+ })
108
+
109
+ test('civic-ness is the owner, not the kind', () => {
110
+ const civic = Name.from('eon.shipload')
111
+ expect(isCivicEntity({owner: 'eon.shipload'}, civic)).toBe(true)
112
+ expect(isCivicEntity({owner: Name.from('alice')}, civic)).toBe(false)
113
+ })
114
+
115
+ test('every rung in the registry lands on a stat its building defines', () => {
116
+ for (const entry of CHARTER_REGISTRY) {
117
+ for (const grant of entry.grants) {
118
+ if (grant.kind !== CIVIC_GRANT_RUNG) continue
119
+ expect(civicStatModuleType(grant.stat)).not.toBe(0)
120
+ const layout = emptyCivicModules(grant.building).map((m) => Number(m.type))
121
+ expect(layout).toContain(civicStatModuleType(grant.stat))
122
+ }
123
+ }
124
+ })
125
+ })
126
+
127
+ describe('the fourteen nodes reproduce the chain-fitted buildings', () => {
128
+ test('the Workshop node spawns five crafters at the baseline stat', () => {
129
+ const [workshop] = previewCharterGrants(node(NODE_WORKSHOP))
130
+ expect(workshop.building).toBe(CIVIC_WORKSHOP)
131
+ expect(workshop.spawned).toBe(true)
132
+ expect(workshop.itemId).toBe(ITEM_WORKSHOP_T1_PACKED)
133
+ expect(workshop.modules.length).toBe(5)
134
+ for (let slot = 0; slot < 5; slot++) {
135
+ expect(stats(workshop, slot)).toEqual({
136
+ itemId: ITEM_CRAFTER_T1,
137
+ stats: [CHARTER_BASELINE_STAT, CHARTER_BASELINE_STAT],
138
+ })
139
+ }
140
+ })
141
+
142
+ test('each Workshop tune-up raises every crafter by one rung step, reaching 400', () => {
143
+ let standing = complete({}, NODE_WORKSHOP)
144
+ let raised = CHARTER_BASELINE_STAT
145
+ for (const tuneup of NODE_WORKSHOP_TUNEUPS) {
146
+ const [workshop] = previewCharterGrants(node(tuneup), standing)
147
+ expect(workshop.spawned).toBe(false)
148
+ raised += CHARTER_RUNG_STEP
149
+ for (let slot = 0; slot < 5; slot++) {
150
+ expect(stats(workshop, slot).stats).toEqual([raised, raised])
151
+ }
152
+ standing = complete(standing, tuneup)
153
+ }
154
+ expect(raised).toBe(400)
155
+ })
156
+
157
+ test('the Dock node fits one builder and its four tune-ups reach 400', () => {
158
+ let standing = complete(complete({}, NODE_WORKSHOP), NODE_DOCK)
159
+ expect(stats({modules: standing[CIVIC_DOCK]!}, 0)).toEqual({
160
+ itemId: ITEM_BUILDER_T1,
161
+ stats: [CHARTER_BASELINE_STAT, CHARTER_BASELINE_STAT],
162
+ })
163
+ let raised = CHARTER_BASELINE_STAT
164
+ for (const tuneup of NODE_DOCK_TUNEUPS) {
165
+ const [dock] = previewCharterGrants(node(tuneup), standing)
166
+ raised += CHARTER_RUNG_STEP
167
+ expect(stats(dock, 0).stats).toEqual([raised, raised])
168
+ standing = complete(standing, tuneup)
169
+ }
170
+ expect(raised).toBe(400)
171
+ })
172
+
173
+ test('the Depot node fits two baseline loaders and leaves the bays empty', () => {
174
+ const standing = complete(complete({}, NODE_WORKSHOP), NODE_DEPOT)
175
+ const depot = {modules: standing[CIVIC_DEPOT]!}
176
+ for (const slot of DEPOT_LOADER_SLOTS) {
177
+ expect(stats(depot, slot)).toEqual({
178
+ itemId: ITEM_LOADER_T1,
179
+ stats: [CHARTER_BASELINE_STAT, CHARTER_BASELINE_STAT],
180
+ })
181
+ }
182
+ for (const slot of DEPOT_BAY_SLOTS) expect(depot.modules[slot].installed).toBeUndefined()
183
+ })
184
+
185
+ test('the four bays fill the storage slots in order at the baseline stat', () => {
186
+ let standing = complete(complete({}, NODE_WORKSHOP), NODE_DEPOT)
187
+ for (let i = 0; i < NODE_DEPOT_BAYS.length; i++) {
188
+ standing = complete(standing, NODE_DEPOT_BAYS[i])
189
+ const depot = {modules: standing[CIVIC_DEPOT]!}
190
+ for (let bay = 0; bay < DEPOT_BAY_SLOTS.length; bay++) {
191
+ const slot = DEPOT_BAY_SLOTS[bay]
192
+ if (bay <= i) {
193
+ expect(stats(depot, slot)).toEqual({
194
+ itemId: ITEM_STORAGE_T1,
195
+ stats: [
196
+ CHARTER_BASELINE_STAT,
197
+ CHARTER_BASELINE_STAT,
198
+ CHARTER_BASELINE_STAT,
199
+ CHARTER_BASELINE_STAT,
200
+ ],
201
+ })
202
+ } else {
203
+ expect(depot.modules[slot].installed).toBeUndefined()
204
+ }
205
+ }
206
+ }
207
+ })
208
+
209
+ test('the four loader rungs step both loaders by one rung each, reaching 400', () => {
210
+ let standing = complete(complete({}, NODE_WORKSHOP), NODE_DEPOT)
211
+ for (let i = 0; i < NODE_DEPOT_LOADERS.length; i++) {
212
+ standing = complete(standing, NODE_DEPOT_LOADERS[i])
213
+ const depot = {modules: standing[CIVIC_DEPOT]!}
214
+ const stat = CHARTER_BASELINE_STAT + CHARTER_RUNG_STEP * (i + 1)
215
+ for (const slot of DEPOT_LOADER_SLOTS) {
216
+ expect(stats(depot, slot)).toEqual({itemId: ITEM_LOADER_T1, stats: [stat, stat]})
217
+ }
218
+ for (const slot of DEPOT_BAY_SLOTS)
219
+ expect(depot.modules[slot].installed).toBeUndefined()
220
+ }
221
+ })
222
+ })
223
+
224
+ describe('grant mechanics', () => {
225
+ test('a module grant joins at a sibling stat', () => {
226
+ const base = fitCivicModules(emptyCivicModules(CIVIC_DEPOT), 1)
227
+ const raised = raiseCivicStat(base, {
228
+ kind: CIVIC_GRANT_RUNG,
229
+ building: CIVIC_DEPOT,
230
+ stat: CIVIC_STAT_TRANSFER_SPEED,
231
+ value: 50,
232
+ })
233
+ const withSecondLoader = fitCivicModules(raised, 1)
234
+ expect(stats({modules: withSecondLoader}, 1).stats).toEqual([50, 50])
235
+ const withBay = fitCivicModules(withSecondLoader, 1)
236
+ expect(stats({modules: withBay}, 2).stats).toEqual([0, 0, 0, 0])
237
+ const withSecondBay = fitCivicModules(
238
+ raiseCivicStat(withBay, {
239
+ kind: CIVIC_GRANT_RUNG,
240
+ building: CIVIC_DEPOT,
241
+ stat: CIVIC_STAT_STORAGE_CAPACITY,
242
+ value: 7,
243
+ }),
244
+ 1
245
+ )
246
+ expect(stats({modules: withSecondBay}, 3).stats).toEqual([7, 7, 7, 7])
247
+ })
248
+
249
+ test('a rung caps at the civic speed cap', () => {
250
+ const fitted = fitCivicModules(emptyCivicModules(CIVIC_WORKSHOP), 5)
251
+ const raised = raiseCivicStat(fitted, {
252
+ kind: CIVIC_GRANT_RUNG,
253
+ building: CIVIC_WORKSHOP,
254
+ stat: CIVIC_STAT_CRAFT_SPEED,
255
+ value: 5000,
256
+ })
257
+ expect(stats({modules: raised}, 0).stats).toEqual([CIVIC_SPEED_CAP, CIVIC_SPEED_CAP])
258
+ })
259
+
260
+ test('a module grant past the layout is refused', () => {
261
+ const full = fitCivicModules(emptyCivicModules(CIVIC_DOCK), 2)
262
+ expect(() => fitCivicModules(full, 1)).toThrow('no open slot')
263
+ })
264
+
265
+ test('a rung with no carrier is refused', () => {
266
+ expect(() =>
267
+ raiseCivicStat(emptyCivicModules(CIVIC_DEPOT), {
268
+ kind: CIVIC_GRANT_RUNG,
269
+ building: CIVIC_DEPOT,
270
+ stat: CIVIC_STAT_TRANSFER_SPEED,
271
+ value: 1,
272
+ })
273
+ ).toThrow('no module carrying')
274
+ })
275
+
276
+ test('a grant on a building that is not standing is refused', () => {
277
+ expect(() => previewCharterGrants(node(NODE_WORKSHOP_TUNEUP))).toThrow('not standing')
278
+ })
279
+
280
+ test('inputs are never mutated', () => {
281
+ const before = emptyCivicModules(CIVIC_WORKSHOP)
282
+ const snapshot = before.map((m) => String(m.installed?.stats ?? 'empty'))
283
+ fitCivicModules(before, 5)
284
+ expect(before.map((m) => String(m.installed?.stats ?? 'empty'))).toEqual(snapshot)
285
+ expect(encodeStats([1, 2])).not.toBe(0n)
286
+ })
287
+ })
@@ -0,0 +1,269 @@
1
+ import {Name, type NameType, UInt16, UInt64, UInt8} from '@wharfkit/antelope'
2
+ import {
3
+ getModuleCapabilityType,
4
+ MODULE_BUILDER,
5
+ MODULE_CRAFTER,
6
+ MODULE_LOADER,
7
+ MODULE_STORAGE,
8
+ moduleSlotTypeToCode,
9
+ } from '../capabilities/modules'
10
+ import {ServerContract} from '../contracts'
11
+ import {
12
+ ITEM_BUILDER_T1,
13
+ ITEM_CONSTRUCTION_DOCK_T1_PACKED,
14
+ ITEM_CRAFTER_T1,
15
+ ITEM_DEPOT_T1_PACKED,
16
+ ITEM_LOADER_T1,
17
+ ITEM_NEXUS_T1_PACKED,
18
+ ITEM_STORAGE_T1,
19
+ ITEM_WORKSHOP_T1_PACKED,
20
+ } from '../data/item-ids'
21
+ import {
22
+ ENTITY_CONSTRUCTION_DOCK,
23
+ ENTITY_DEPOT,
24
+ ENTITY_NEXUS,
25
+ ENTITY_WORKSHOP,
26
+ } from '../data/kind-registry'
27
+ import {getEntityLayout} from '../data/recipes-runtime'
28
+ import {decodeStat, encodeStats} from '../derivation/crafting'
29
+ import type {CharterGrant, CharterNode} from './charters'
30
+ import {
31
+ CIVIC_DEPOT,
32
+ CIVIC_DOCK,
33
+ CIVIC_GRANT_LEVEL_GATE,
34
+ CIVIC_GRANT_MODULE,
35
+ CIVIC_GRANT_RUNG,
36
+ CIVIC_NEXUS,
37
+ CIVIC_SPEED_CAP,
38
+ CIVIC_STAT_BUILD_SPEED,
39
+ CIVIC_STAT_CRAFT_SPEED,
40
+ CIVIC_STAT_STORAGE_CAPACITY,
41
+ CIVIC_STAT_TRANSFER_SPEED,
42
+ CIVIC_WORKSHOP,
43
+ } from './constants'
44
+ import {getStatCount} from './quality'
45
+
46
+ type ModuleEntry = ServerContract.Types.module_entry
47
+
48
+ export interface CivicStatDef {
49
+ building: number
50
+ stat: number
51
+ base: number
52
+ cap: number
53
+ }
54
+
55
+ export const CIVIC_STAT_DEFS: readonly CivicStatDef[] = [
56
+ {building: CIVIC_WORKSHOP, stat: CIVIC_STAT_CRAFT_SPEED, base: 0, cap: CIVIC_SPEED_CAP},
57
+ {building: CIVIC_DOCK, stat: CIVIC_STAT_BUILD_SPEED, base: 0, cap: CIVIC_SPEED_CAP},
58
+ {building: CIVIC_DEPOT, stat: CIVIC_STAT_TRANSFER_SPEED, base: 0, cap: CIVIC_SPEED_CAP},
59
+ {building: CIVIC_DEPOT, stat: CIVIC_STAT_STORAGE_CAPACITY, base: 0, cap: CIVIC_SPEED_CAP},
60
+ ]
61
+
62
+ export function findCivicStatDef(building: number, stat: number): CivicStatDef | undefined {
63
+ return CIVIC_STAT_DEFS.find((def) => def.building === building && def.stat === stat)
64
+ }
65
+
66
+ export function civicHullItem(building: number): number {
67
+ switch (building) {
68
+ case CIVIC_WORKSHOP:
69
+ return ITEM_WORKSHOP_T1_PACKED
70
+ case CIVIC_NEXUS:
71
+ return ITEM_NEXUS_T1_PACKED
72
+ case CIVIC_DOCK:
73
+ return ITEM_CONSTRUCTION_DOCK_T1_PACKED
74
+ case CIVIC_DEPOT:
75
+ return ITEM_DEPOT_T1_PACKED
76
+ default:
77
+ return 0
78
+ }
79
+ }
80
+
81
+ export function civicModuleItemFor(moduleType: number): number {
82
+ switch (moduleType) {
83
+ case MODULE_CRAFTER:
84
+ return ITEM_CRAFTER_T1
85
+ case MODULE_BUILDER:
86
+ return ITEM_BUILDER_T1
87
+ case MODULE_LOADER:
88
+ return ITEM_LOADER_T1
89
+ case MODULE_STORAGE:
90
+ return ITEM_STORAGE_T1
91
+ default:
92
+ return 0
93
+ }
94
+ }
95
+
96
+ export function civicStatModuleType(stat: number): number {
97
+ switch (stat) {
98
+ case CIVIC_STAT_CRAFT_SPEED:
99
+ return MODULE_CRAFTER
100
+ case CIVIC_STAT_BUILD_SPEED:
101
+ return MODULE_BUILDER
102
+ case CIVIC_STAT_TRANSFER_SPEED:
103
+ return MODULE_LOADER
104
+ case CIVIC_STAT_STORAGE_CAPACITY:
105
+ return MODULE_STORAGE
106
+ default:
107
+ return 0
108
+ }
109
+ }
110
+
111
+ export function civicBuildingKind(building: number): Name | undefined {
112
+ switch (building) {
113
+ case CIVIC_WORKSHOP:
114
+ return ENTITY_WORKSHOP
115
+ case CIVIC_NEXUS:
116
+ return ENTITY_NEXUS
117
+ case CIVIC_DOCK:
118
+ return ENTITY_CONSTRUCTION_DOCK
119
+ case CIVIC_DEPOT:
120
+ return ENTITY_DEPOT
121
+ default:
122
+ return undefined
123
+ }
124
+ }
125
+
126
+ export function civicBuildingFor(kind: NameType): number | undefined {
127
+ const name = Name.from(kind)
128
+ if (name.equals(ENTITY_WORKSHOP)) return CIVIC_WORKSHOP
129
+ if (name.equals(ENTITY_NEXUS)) return CIVIC_NEXUS
130
+ if (name.equals(ENTITY_CONSTRUCTION_DOCK)) return CIVIC_DOCK
131
+ if (name.equals(ENTITY_DEPOT)) return CIVIC_DEPOT
132
+ return undefined
133
+ }
134
+
135
+ export function isCivicKind(kind: NameType): boolean {
136
+ return civicBuildingFor(kind) !== undefined
137
+ }
138
+
139
+ export function isCivicEntity(entity: {owner: NameType}, civicOwner: NameType): boolean {
140
+ return Name.from(entity.owner).equals(Name.from(civicOwner))
141
+ }
142
+
143
+ function moduleItemId(module: ServerContract.Types.packed_module): number {
144
+ return Number(module.item_id.value ?? module.item_id)
145
+ }
146
+
147
+ function moduleStats(module: ServerContract.Types.packed_module): bigint {
148
+ return BigInt(module.stats.toString())
149
+ }
150
+
151
+ function packedModule(itemId: number, stats: bigint): ServerContract.Types.packed_module {
152
+ return ServerContract.Types.packed_module.from({
153
+ item_id: UInt16.from(itemId),
154
+ stats: UInt64.from(stats.toString()),
155
+ })
156
+ }
157
+
158
+ function cloneEntry(
159
+ entry: ModuleEntry,
160
+ installed?: ServerContract.Types.packed_module
161
+ ): ModuleEntry {
162
+ return ServerContract.Types.module_entry.from({
163
+ type: UInt8.from(Number(entry.type.value ?? entry.type)),
164
+ installed:
165
+ installed ??
166
+ (entry.installed
167
+ ? packedModule(moduleItemId(entry.installed), moduleStats(entry.installed))
168
+ : undefined),
169
+ })
170
+ }
171
+
172
+ export function emptyCivicModules(building: number): ModuleEntry[] {
173
+ const layout = getEntityLayout(civicHullItem(building))?.slots ?? []
174
+ return layout.map((slot) =>
175
+ ServerContract.Types.module_entry.from({type: UInt8.from(moduleSlotTypeToCode(slot.type))})
176
+ )
177
+ }
178
+
179
+ export function fitCivicModules(modules: ModuleEntry[], count: number): ModuleEntry[] {
180
+ const next = modules.map((entry) => cloneEntry(entry))
181
+ let remaining = count
182
+ for (let i = 0; i < next.length && remaining > 0; i++) {
183
+ if (next[i].installed) continue
184
+ const type = Number(next[i].type.value ?? next[i].type)
185
+ const item = civicModuleItemFor(type)
186
+ if (item === 0) throw new Error('charter grant found no open slot of its module type')
187
+ let stats = encodeStats(new Array(getStatCount(item)).fill(0))
188
+ for (const sibling of next) {
189
+ if (
190
+ sibling.installed &&
191
+ getModuleCapabilityType(moduleItemId(sibling.installed)) === type
192
+ ) {
193
+ stats = moduleStats(sibling.installed)
194
+ break
195
+ }
196
+ }
197
+ next[i] = cloneEntry(next[i], packedModule(item, stats))
198
+ remaining--
199
+ }
200
+ if (remaining > 0) throw new Error('charter grant found no open slot of its module type')
201
+ return next
202
+ }
203
+
204
+ export function raiseCivicStat(modules: ModuleEntry[], grant: CharterGrant): ModuleEntry[] {
205
+ const def = findCivicStatDef(grant.building, grant.stat)
206
+ if (!def) throw new Error(`no civic stat ${grant.stat} on building ${grant.building}`)
207
+ const type = civicStatModuleType(grant.stat)
208
+ let raised = 0
209
+ const next = modules.map((entry) => {
210
+ if (!entry.installed || getModuleCapabilityType(moduleItemId(entry.installed)) !== type) {
211
+ return cloneEntry(entry)
212
+ }
213
+ const item = moduleItemId(entry.installed)
214
+ const stats = moduleStats(entry.installed)
215
+ const values: number[] = []
216
+ for (let c = 0; c < getStatCount(item); c++) {
217
+ values.push(Math.min(decodeStat(stats, c) + grant.value, def.cap))
218
+ }
219
+ raised++
220
+ return cloneEntry(entry, packedModule(item, encodeStats(values)))
221
+ })
222
+ if (raised === 0) throw new Error('charter rung found no module carrying its stat')
223
+ return next
224
+ }
225
+
226
+ export interface CivicBuildingPreview {
227
+ building: number
228
+ itemId: number
229
+ spawned: boolean
230
+ modules: ModuleEntry[]
231
+ }
232
+
233
+ export type CivicStanding = Partial<Record<number, ModuleEntry[]>>
234
+
235
+ export function previewCharterGrants(
236
+ node: CharterNode,
237
+ standing: CivicStanding = {}
238
+ ): CivicBuildingPreview[] {
239
+ const previews = new Map<number, CivicBuildingPreview>()
240
+ const target = (building: number, spawn: boolean): CivicBuildingPreview => {
241
+ let preview = previews.get(building)
242
+ if (preview) return preview
243
+ const current = standing[building]
244
+ if (!current && !spawn)
245
+ throw new Error('charter grant targets a building that is not standing')
246
+ preview = {
247
+ building,
248
+ itemId: civicHullItem(building),
249
+ spawned: !current,
250
+ modules: current
251
+ ? current.map((entry) => cloneEntry(entry))
252
+ : emptyCivicModules(building),
253
+ }
254
+ previews.set(building, preview)
255
+ return preview
256
+ }
257
+ for (const grant of node.grants) {
258
+ if (grant.kind === CIVIC_GRANT_LEVEL_GATE) {
259
+ target(grant.building, true)
260
+ } else if (grant.kind === CIVIC_GRANT_MODULE) {
261
+ const preview = target(grant.building, false)
262
+ preview.modules = fitCivicModules(preview.modules, grant.value)
263
+ } else if (grant.kind === CIVIC_GRANT_RUNG) {
264
+ const preview = target(grant.building, false)
265
+ preview.modules = raiseCivicStat(preview.modules, grant)
266
+ }
267
+ }
268
+ return [...previews.values()]
269
+ }
@@ -0,0 +1,84 @@
1
+ import {MASS_UNITS_PER_TONNE} from '../types'
2
+
3
+ export const INFLUENCE_ATOMIC_PER_POINT = 10_000
4
+ export const NEED_FP_SCALE = 10_000
5
+ export const WEIGHT_FP_SCALE = 10_000
6
+
7
+ export const INFLUENCE_WEIGHT_FP_MAX = 1_000_000n
8
+ export const INFLUENCE_NEED_FP_MAX = 1_000_000n
9
+ export const INFLUENCE_MASS_MAX = 1n << 48n
10
+
11
+ export const DECAY_RETAIN_NUM = 19n
12
+ export const DECAY_RETAIN_DEN = 20n
13
+ export const DECAY_MAX_ITERATIONS = 1_024
14
+
15
+ export const W_EFF_ATOMIC_PER_UNIT = BigInt(INFLUENCE_ATOMIC_PER_POINT / MASS_UNITS_PER_TONNE)
16
+
17
+ export const DECOMP_MAX_BUCKETS = 12
18
+ export const DECOMP_MAX_DEPTH = 8
19
+ export const DECOMP_RAW_MASS_MAX = 1_000_000
20
+ export const DECOMP_PROCESSED_MASS_MAX = 1_000_000
21
+
22
+ export const RESOURCE_ORE = 0
23
+ export const RESOURCE_GAS = 1
24
+ export const RESOURCE_REGOLITH = 2
25
+ export const RESOURCE_BIOMASS = 3
26
+ export const RESOURCE_CRYSTAL = 4
27
+ export const RESOURCE_CATEGORY_COUNT = 5
28
+
29
+ export const CIVIC_LOADER_STAT = 213
30
+ export const CIVIC_LOADER_TIER = 1
31
+ export const DEFAULT_ORBITAL_Z = 800
32
+
33
+ export const GRADE_STANDARD = 0
34
+ export const GRADE_PREMIUM = 1
35
+ export const MINTREADY_DEFAULT_CAP = 10
36
+
37
+ export const BAR_SEED = 102_363
38
+ export const BAR_STEP_UNIT = 20
39
+ export const BAR_STEP_UP = BAR_STEP_UNIT * 9
40
+ export const BAR_STEP_DOWN = BAR_STEP_UNIT
41
+
42
+ export const CHARTER_NONE = 0
43
+ export const CHARTER_WORKSHOP = 10001
44
+ export const CIVIC_GRANT_NONE = 0
45
+ export const CIVIC_GRANT_MODULE = 1
46
+ export const CIVIC_GRANT_RUNG = 2
47
+ export const CIVIC_GRANT_LEVEL_GATE = 3
48
+ export const CIVIC_GRANT_ADVANCEMENT = 4
49
+
50
+ export const CIVIC_WORLD = 0
51
+ export const CIVIC_WORKSHOP = 1
52
+ export const CIVIC_NEXUS = 2
53
+ export const CIVIC_DOCK = 3
54
+ export const CIVIC_DEPOT = 4
55
+
56
+ export const CIVIC_STAT_NONE = 0
57
+ export const CIVIC_STAT_CRAFT_SPEED = 1
58
+ export const CIVIC_STAT_BUILD_SPEED = 2
59
+ export const CIVIC_STAT_TRANSFER_SPEED = 3
60
+ export const CIVIC_STAT_STORAGE_CAPACITY = 4
61
+ export const VOTE_READY_DEFAULT_MAX = 20
62
+ export const BALLOT_CHARTER = 1
63
+ export const BALLOT_MAX_PICKS = 8
64
+ export const CHARTER_WORKSHOP_COST = 2_000_000_000n
65
+ export const CHARTER_BASELINE_STAT = 200
66
+ export const CHARTER_RUNG_STEP = 50
67
+ export const CIVIC_SPEED_CAP = 999
68
+ export const CHARTER_MAX_PREREQS = 4
69
+ export const CHARTER_MAX_GRANTS = 4
70
+
71
+ export const INFLUENCE_D1_SEED = 45_369
72
+ export const DEMAND_TRIPLE_SEED = {peak: 20_000n, base: 10_000n, floor: 1_000n}
73
+
74
+ export function poolKey(category: number, tier: number, grade: number): bigint {
75
+ return (BigInt(category) << 16n) | (BigInt(tier) << 8n) | BigInt(grade)
76
+ }
77
+
78
+ export function influenceWeightKey(category: number, tier: number): bigint {
79
+ return (BigInt(category) << 8n) | BigInt(tier)
80
+ }
81
+
82
+ export function atomicToPoints(atomic: bigint | number | string): number {
83
+ return Math.floor(Number(BigInt(atomic.toString())) / INFLUENCE_ATOMIC_PER_POINT)
84
+ }
@@ -0,0 +1,20 @@
1
+ import {DECAY_MAX_ITERATIONS, DECAY_RETAIN_DEN, DECAY_RETAIN_NUM} from './constants'
2
+
3
+ export function decayActive(active: bigint, epochsElapsed: number): bigint {
4
+ const iterations = epochsElapsed > DECAY_MAX_ITERATIONS ? DECAY_MAX_ITERATIONS : epochsElapsed
5
+ let value = active
6
+ for (let i = 0; i < iterations && value > 0n; i++) {
7
+ value = (value * DECAY_RETAIN_NUM) / DECAY_RETAIN_DEN
8
+ }
9
+ return value
10
+ }
11
+
12
+ export interface DecayableStanding {
13
+ active: bigint
14
+ lastUpdateEpoch: number
15
+ }
16
+
17
+ export function normalizeActive(standing: DecayableStanding, epoch: number): bigint {
18
+ const elapsed = epoch > standing.lastUpdateEpoch ? epoch - standing.lastUpdateEpoch : 0
19
+ return decayActive(standing.active, elapsed)
20
+ }