@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,707 @@
1
+ import {getEntityLayout} from '../data/recipes-runtime'
2
+ import {ENTITY_SHIP, getPackedEntityType} from '../data/kind-registry'
3
+ import {computeEffectiveModuleStat} from './stat-scaling'
4
+
5
+ export const DEFAULT_BASE_HULLMASS = 1_000
6
+
7
+ export function getBaseHullmassFor(itemId: number): number {
8
+ return getEntityLayout(itemId)?.baseHullmass ?? DEFAULT_BASE_HULLMASS
9
+ }
10
+
11
+ function isShipHull(itemId: number): boolean {
12
+ return getPackedEntityType(itemId)?.equals(ENTITY_SHIP) ?? false
13
+ }
14
+
15
+ function sumHullStats(stats: Record<string, number>): number {
16
+ return Object.values(stats).reduce((sum, value) => sum + value, 0)
17
+ }
18
+
19
+ export function computeBaseHullmass(itemId: number, stats: Record<string, number>): number {
20
+ if (isShipHull(itemId)) {
21
+ return Math.floor((getBaseHullmassFor(itemId) * (10_000 - sumHullStats(stats))) / 10_000)
22
+ }
23
+ const lightness = stats.density ?? stats.hardness ?? 0
24
+ return Math.floor((getBaseHullmassFor(itemId) * (2000 - lightness)) / 2000)
25
+ }
26
+
27
+ export function computeShipHullCapabilities(
28
+ stats: Record<string, number>,
29
+ itemId: number = ITEM_SHIP_T1_PACKED
30
+ ): {
31
+ hullmass: number
32
+ capacity: number
33
+ } {
34
+ const statSum = sumHullStats(stats)
35
+ const exponent = statSum / 4995.0
36
+ return {
37
+ hullmass: computeBaseHullmass(itemId, stats),
38
+ capacity: Math.floor(50000 * 6 ** exponent),
39
+ }
40
+ }
41
+
42
+ export function computeEngineCapabilities(
43
+ stats: Record<string, number>,
44
+ tier: number
45
+ ): {thrust: number; drain: number} {
46
+ const vol = computeEffectiveModuleStat(stats.volatility)
47
+ const thm = computeEffectiveModuleStat(stats.thermal)
48
+ return {
49
+ thrust: Math.floor(
50
+ ((400 + Math.floor((vol * 3) / 4)) * moduleTierPct(ENGINE_THRUST_TIER_PCT, tier)) / 100
51
+ ),
52
+ drain: 2 * Math.max(30, 50 - Math.floor(thm / 70)),
53
+ }
54
+ }
55
+
56
+ export function computeGeneratorCapabilities(
57
+ stats: Record<string, number>,
58
+ tier: number
59
+ ): {capacity: number; recharge: number} {
60
+ const res = computeEffectiveModuleStat(stats.resonance)
61
+ const ref = computeEffectiveModuleStat(stats.reflectivity)
62
+ return {
63
+ capacity: Math.floor(
64
+ ((1_300_000 + res * 500) * moduleTierPct(GENERATOR_CAPACITY_TIER_PCT, tier)) / 100
65
+ ),
66
+ recharge: Math.floor(
67
+ ((2000 + ref * 6) * moduleTierPct(GENERATOR_RECHARGE_TIER_PCT, tier)) / 100
68
+ ),
69
+ }
70
+ }
71
+
72
+ export interface GathererDepthParams {
73
+ readonly floor: number
74
+ readonly slope: number
75
+ }
76
+
77
+ export const GATHERER_DEPTH_TABLE: readonly GathererDepthParams[] = [
78
+ {floor: 500, slope: 5},
79
+ {floor: 2000, slope: 11},
80
+ {floor: 7000, slope: 16},
81
+ {floor: 15000, slope: 18},
82
+ {floor: 25000, slope: 19},
83
+ {floor: 35000, slope: 16},
84
+ {floor: 46000, slope: 12},
85
+ {floor: 53500, slope: 10},
86
+ {floor: 60000, slope: 5},
87
+ {floor: 63537, slope: 2},
88
+ ]
89
+
90
+ export const GATHERER_DEPTH_MAX_TIER = 10
91
+
92
+ export function gathererDepthForTier(tol: number, tier: number): number {
93
+ if (tier < 1 || tier > GATHERER_DEPTH_MAX_TIER) {
94
+ throw new Error(`gatherer tier out of range: ${tier}`)
95
+ }
96
+ const p = GATHERER_DEPTH_TABLE[tier - 1]
97
+ return p.floor + tol * p.slope
98
+ }
99
+
100
+ export function computeGathererCapabilities(
101
+ stats: Record<string, number>,
102
+ tier: number
103
+ ): {
104
+ yield: number
105
+ drain: number
106
+ depth: number
107
+ } {
108
+ const str = stats.strength
109
+ const con = stats.saturation
110
+ // Gatherer capabilities are positional in the contract: packed slot 1 is tolerance.
111
+ // T1 recipe presentation historically labels that blended slot as hardness, so retain
112
+ // that fallback for callers that only have recipe-decoded named stats.
113
+ const tol = stats.tolerance ?? stats.hardness
114
+
115
+ return {
116
+ yield: computeGathererYield(str, tier),
117
+ drain: 2 * Math.max(250_000, 1_250_000 - con * 1250),
118
+ depth: gathererDepthForTier(tol, tier),
119
+ }
120
+ }
121
+
122
+ export function computeLoaderCapabilities(
123
+ stats: Record<string, number>,
124
+ tier: number
125
+ ): {mass: number; thrust: number; quantity: number} {
126
+ const insulation = stats.insulation
127
+ const plasticity = stats.plasticity
128
+ return {
129
+ mass: Math.max(2, 20 - Math.floor(insulation / 50)),
130
+ thrust: Math.floor(
131
+ ((1 + Math.floor((plasticity * plasticity) / 10000)) *
132
+ moduleTierPct(LOADER_THRUST_TIER_PCT, tier)) /
133
+ 100
134
+ ),
135
+ quantity: 1,
136
+ }
137
+ }
138
+
139
+ export function computeCrafterCapabilities(
140
+ stats: Record<string, number>,
141
+ tier: number
142
+ ): {speed: number; drain: number} {
143
+ const fin = stats.fineness
144
+ const con = stats.conductivity
145
+ return {
146
+ speed: Math.floor(
147
+ ((100 + Math.floor((fin * 4) / 5)) * moduleTierPct(CRAFTER_SPEED_TIER_PCT, tier)) / 100
148
+ ),
149
+ drain: Math.max(5000, 30000 - Math.floor((con * 1000) / 33)),
150
+ }
151
+ }
152
+
153
+ export function computeBuilderCapabilities(
154
+ stats: Record<string, number>,
155
+ tier: number
156
+ ): {speed: number; drain: number} {
157
+ const resonance = stats.resonance
158
+ const fineness = stats.fineness
159
+ return {
160
+ speed: Math.floor(
161
+ ((100 + Math.floor((resonance * 4) / 5)) *
162
+ moduleTierPct(BUILDER_SPEED_TIER_PCT, tier)) /
163
+ 100
164
+ ),
165
+ drain: Math.max(5000, 30000 - Math.floor((fineness * 1000) / 33)),
166
+ }
167
+ }
168
+
169
+ export function computeHaulerCapabilities(
170
+ stats: Record<string, number>,
171
+ tier: number
172
+ ): {
173
+ capacity: number
174
+ efficiency: number
175
+ drain: number
176
+ } {
177
+ const resonance = stats.resonance
178
+ const plasticity = stats.plasticity
179
+ const conductivity = stats.conductivity
180
+
181
+ return {
182
+ capacity: computeHaulerCapacity(resonance, tier),
183
+ efficiency: 2000 + plasticity * 6,
184
+ drain: computeHaulerDrain(conductivity, tier),
185
+ }
186
+ }
187
+
188
+ export function computeLauncherCapabilities(
189
+ stats: {charge_rate: number; velocity: number; drain: number},
190
+ amp = 100
191
+ ): {chargeRate: number; velocity: number; drain: number} {
192
+ return {
193
+ chargeRate: Math.floor((stats.charge_rate * amp) / 100),
194
+ velocity: Math.floor((stats.velocity * amp) / 100),
195
+ drain: stats.drain,
196
+ }
197
+ }
198
+
199
+ export function computeStorageCapabilities(
200
+ stats: Record<string, number>,
201
+ tier: number
202
+ ): {
203
+ capacity: number
204
+ drain: number
205
+ } {
206
+ return {
207
+ capacity: computeCargoBayCapacity(
208
+ stats.strength ?? 0,
209
+ stats.density ?? 0,
210
+ stats.hardness ?? 0,
211
+ tier
212
+ ),
213
+ drain: computeCargoBayDrain(stats.cohesion ?? 0, tier),
214
+ }
215
+ }
216
+
217
+ export function computeBatteryCapabilities(
218
+ stats: Record<string, number>,
219
+ tier: number
220
+ ): {
221
+ capacity: number
222
+ } {
223
+ return {
224
+ capacity: computeBatteryBankCapacity(
225
+ stats.volatility ?? 0,
226
+ stats.thermal ?? 0,
227
+ stats.plasticity ?? 0,
228
+ stats.insulation ?? 0,
229
+ tier
230
+ ),
231
+ }
232
+ }
233
+
234
+ import {
235
+ ITEM_CONTAINER_T1_PACKED,
236
+ ITEM_DEPOT_T1_PACKED,
237
+ ITEM_SHIP_T1_PACKED,
238
+ ITEM_WAREHOUSE_T1_PACKED,
239
+ ITEM_WORKSHOP_T1_PACKED,
240
+ } from '../data/item-ids'
241
+ import {
242
+ getModuleCapabilityType,
243
+ MODULE_BATTERY,
244
+ MODULE_ENGINE,
245
+ MODULE_GENERATOR,
246
+ MODULE_GATHERER,
247
+ MODULE_LOADER,
248
+ MODULE_STORAGE,
249
+ MODULE_CRAFTER,
250
+ MODULE_BUILDER,
251
+ MODULE_HAULER,
252
+ MODULE_WARP,
253
+ MODULE_LAUNCHER,
254
+ } from '../capabilities/modules'
255
+ import {getItem} from '../data/catalog'
256
+ import {decodeCraftedItemStats, decodeStat} from './crafting'
257
+ import {
258
+ applySlotMultiplier,
259
+ applySlotMultiplierUint32,
260
+ clampUint16,
261
+ clampUint32,
262
+ getSlotAmp,
263
+ type InstalledModule,
264
+ } from '../entities/slot-multiplier'
265
+ import type {EntitySlot} from '../data/recipes-runtime'
266
+ import {
267
+ computeCargoBayCapacity,
268
+ computeCargoBayDrain,
269
+ computeBatteryBankCapacity,
270
+ computeHaulerCapacity,
271
+ computeHaulerDrain,
272
+ computeTravelDrain,
273
+ } from '../nft/description'
274
+
275
+ export const CAPACITY_TIER_TABLE = [1.0, 1.4, 1.8, 2.2, 2.6, 3.0, 3.4, 3.8, 4.2, 4.6]
276
+
277
+ export function capacityTierMultiplier(tier: number): number {
278
+ const clampedTier = tier >= 1 && tier <= 10 ? tier : 1
279
+ return CAPACITY_TIER_TABLE[clampedTier - 1]
280
+ }
281
+
282
+ export function applyCapacityTier(baseCapacity: number, tier: number): number {
283
+ return clampUint32(Math.floor(baseCapacity * capacityTierMultiplier(tier)))
284
+ }
285
+
286
+ export const GATHERER_YIELD_TIER_TABLE = [100, 120, 140, 160, 180, 200, 220, 240, 260, 280] as const
287
+
288
+ export const MODULE_MAX_TIER = 10
289
+
290
+ export const ENGINE_THRUST_TIER_PCT = [100, 120, 140, 160, 180, 200, 220, 240, 260, 280] as const
291
+ export const GENERATOR_CAPACITY_TIER_PCT = [
292
+ 100, 110, 120, 130, 140, 150, 160, 170, 180, 190,
293
+ ] as const
294
+ export const GENERATOR_RECHARGE_TIER_PCT = [
295
+ 100, 120, 140, 160, 180, 200, 220, 240, 260, 280,
296
+ ] as const
297
+ export const CRAFTER_SPEED_TIER_PCT = [100, 120, 140, 160, 180, 200, 220, 240, 260, 280] as const
298
+ export const BUILDER_SPEED_TIER_PCT = [100, 120, 140, 160, 180, 200, 220, 240, 260, 280] as const
299
+ export const WARP_RANGE_TIER_PCT = [100, 120, 140, 160, 180, 200, 220, 240, 260, 280] as const
300
+ export const LOADER_THRUST_TIER_PCT = [100, 120, 140, 160, 180, 200, 220, 240, 260, 280] as const
301
+ export const CARGO_BAY_CAPACITY_TIER_PCT = [
302
+ 100, 110, 120, 130, 140, 150, 160, 170, 180, 190,
303
+ ] as const
304
+ export const BATTERY_CAPACITY_TIER_PCT = [100, 110, 120, 130, 140, 150, 160, 170, 180, 190] as const
305
+
306
+ export function moduleTierPct(table: readonly number[], tier: number): number {
307
+ const clamped = Math.min(Math.max(tier, 1), MODULE_MAX_TIER)
308
+ return table[clamped - 1]
309
+ }
310
+
311
+ export function computeGathererYield(str: number, tier: number): number {
312
+ const clampedTier = Math.min(Math.max(tier, 1), GATHERER_YIELD_TIER_TABLE.length)
313
+ const pct = GATHERER_YIELD_TIER_TABLE[clampedTier - 1]
314
+ return Math.floor(((200 + str) * pct) / 100)
315
+ }
316
+
317
+ const BASE_CAPACITY_FN_BY_KIND: Record<string, (stats: Record<string, number>) => number> = {
318
+ warehouse: (stats) => computeWarehouseHullCapabilities(stats).capacity,
319
+ depot: (stats) => computeDepotHullCapabilities(stats).capacity,
320
+ workshop: (stats) => computeWorkshopHullCapabilities(stats).capacity,
321
+ extractor: (stats) => computeContainerCapabilities(stats).capacity,
322
+ factory: (stats) => computeContainerCapabilities(stats).capacity,
323
+ builddock: (stats) => computeContainerCapabilities(stats).capacity,
324
+ mdriver: (stats) => computeContainerCapabilities(stats).capacity,
325
+ mcatcher: (stats) => computeContainerCapabilities(stats).capacity,
326
+ container: (stats) => computeContainerCapabilities(stats).capacity,
327
+ }
328
+
329
+ export function computeBaseCapacity(itemId: number, stats: Record<string, number>): number {
330
+ let base: number
331
+ if (isShipHull(itemId)) {
332
+ base = computeShipHullCapabilities(stats, itemId).capacity
333
+ } else {
334
+ const kind = getPackedEntityType(itemId)
335
+ const capacityFn = kind ? BASE_CAPACITY_FN_BY_KIND[kind.toString()] : undefined
336
+ if (!capacityFn) return 0
337
+ base = capacityFn(stats)
338
+ }
339
+ return applyCapacityTier(base, getItem(itemId).tier)
340
+ }
341
+
342
+ export function computeWarpCapabilities(
343
+ stats: Record<string, number>,
344
+ tier: number
345
+ ): {range: number} {
346
+ const reflectivity = stats.reflectivity
347
+ return {
348
+ range: Math.floor(
349
+ ((100 + reflectivity * 3) * moduleTierPct(WARP_RANGE_TIER_PCT, tier)) / 100
350
+ ),
351
+ }
352
+ }
353
+
354
+ export function computeWarehouseHullCapabilities(stats: Record<string, number>): {
355
+ hullmass: number
356
+ capacity: number
357
+ } {
358
+ const statSum = (stats.strength ?? 0) + (stats.hardness ?? 0)
359
+ const exponent = statSum / 1998.0
360
+ return {
361
+ hullmass: computeBaseHullmass(ITEM_WAREHOUSE_T1_PACKED, stats),
362
+ capacity: Math.floor(1000000 * 6 ** exponent),
363
+ }
364
+ }
365
+
366
+ export function computeDepotHullCapabilities(stats: Record<string, number>): {
367
+ hullmass: number
368
+ capacity: number
369
+ } {
370
+ const statSum = (stats.strength ?? 0) + (stats.hardness ?? 0)
371
+ const exponent = statSum / 1998.0
372
+ return {
373
+ hullmass: computeBaseHullmass(ITEM_DEPOT_T1_PACKED, stats),
374
+ capacity: Math.floor(500000 * 6 ** exponent),
375
+ }
376
+ }
377
+
378
+ export function computeWorkshopHullCapabilities(stats: Record<string, number>): {
379
+ hullmass: number
380
+ capacity: number
381
+ } {
382
+ const statSum = (stats.strength ?? 0) + (stats.hardness ?? 0)
383
+ const exponent = statSum / 1998.0
384
+ return {
385
+ hullmass: computeBaseHullmass(ITEM_WORKSHOP_T1_PACKED, stats),
386
+ capacity: Math.floor(50000 * 6 ** exponent),
387
+ }
388
+ }
389
+
390
+ export interface GathererLaneEntry {
391
+ slotIndex: number
392
+ yield: number
393
+ drain: number
394
+ depth: number
395
+ outputPct: number
396
+ }
397
+
398
+ export interface CrafterLaneEntry {
399
+ slotIndex: number
400
+ speed: number
401
+ drain: number
402
+ outputPct: number
403
+ }
404
+
405
+ export interface BuilderLaneEntry {
406
+ slotIndex: number
407
+ speed: number
408
+ drain: number
409
+ outputPct: number
410
+ }
411
+
412
+ export interface LoaderLaneEntry {
413
+ slotIndex: number
414
+ mass: number
415
+ thrust: number
416
+ outputPct: number
417
+ }
418
+
419
+ export interface TravelDrainBreakdown {
420
+ engine: number
421
+ cargoHolds: number
422
+ tractorBeams: number
423
+ total: number
424
+ }
425
+
426
+ export interface ComputedCapabilities {
427
+ hullmass: number
428
+ capacity: number
429
+ engines?: {thrust: number; drain: number}
430
+ travelDrain?: TravelDrainBreakdown
431
+ generator?: {capacity: number; recharge: number}
432
+ gatherer?: {yield: number; drain: number; depth: number}
433
+ gathererLanes?: GathererLaneEntry[]
434
+ loaders?: {mass: number; thrust: number; quantity: number}
435
+ loaderLanes?: LoaderLaneEntry[]
436
+ crafter?: {speed: number; drain: number}
437
+ crafterLanes?: CrafterLaneEntry[]
438
+ builder?: {speed: number; drain: number}
439
+ builderLanes?: BuilderLaneEntry[]
440
+ hauler?: {
441
+ capacity: number
442
+ efficiency: number
443
+ drain: number
444
+ capacityByTier: {tier: number; capacity: number}[]
445
+ }
446
+ warp?: {range: number}
447
+ launcher?: {chargeRate: number; velocity: number; drain: number}
448
+ }
449
+
450
+ export function computeEntityCapabilities(
451
+ stats: Record<string, number>,
452
+ itemId: number,
453
+ modules: InstalledModule[],
454
+ layout: EntitySlot[]
455
+ ): ComputedCapabilities {
456
+ let totalThrust = 0
457
+ let totalEngineThm = 0
458
+ let engineCount = 0
459
+ let hasEngine = false
460
+
461
+ let totalGenCapacity = 0
462
+ let totalGenRecharge = 0
463
+ let hasGenerator = false
464
+
465
+ let totalLoaderMass = 0
466
+ let totalLoaderThrust = 0
467
+ let totalLoaderQuantity = 0
468
+ let hasLoader = false
469
+
470
+ let totalGathYield = 0
471
+ let totalGathDrain = 0
472
+ let maxGathDepth = 0
473
+ let hasGatherer = false
474
+
475
+ let totalStorageCapacity = 0
476
+ let totalCargoHoldDrain = 0
477
+ const baseCapacity = computeBaseCapacity(itemId, stats)
478
+ let installedModuleMass = 0
479
+
480
+ let totalCrafterSpeed = 0
481
+ let totalCrafterDrain = 0
482
+ let hasCrafter = false
483
+
484
+ let totalBuilderSpeed = 0
485
+ let totalBuilderDrain = 0
486
+ let hasBuilder = false
487
+
488
+ let totalHaulerCapacity = 0
489
+ let weightedHaulerEffNum = 0n
490
+ let totalHaulerDrain = 0
491
+ let hasHauler = false
492
+ const haulerCapByTier = new Map<number, number>()
493
+
494
+ let totalWarpRange = 0
495
+ let hasWarp = false
496
+
497
+ let totalLauncherChargeRate = 0
498
+ let totalLauncherVelocity = 0
499
+ let totalLauncherDrain = 0
500
+ let hasLauncher = false
501
+
502
+ let totalBatteryCapacity = 0
503
+
504
+ const gathererLanes: GathererLaneEntry[] = []
505
+ const crafterLanes: CrafterLaneEntry[] = []
506
+ const builderLanes: BuilderLaneEntry[] = []
507
+ const loaderLanes: LoaderLaneEntry[] = []
508
+
509
+ for (const mod of modules) {
510
+ const item = getItem(mod.itemId)
511
+ const modType = getModuleCapabilityType(mod.itemId)
512
+ const amp = getSlotAmp(layout, mod.slotIndex)
513
+ const decodedStats = decodeCraftedItemStats(mod.itemId, mod.stats)
514
+ installedModuleMass += item.mass
515
+
516
+ if (modType === MODULE_ENGINE) {
517
+ hasEngine = true
518
+ const caps = computeEngineCapabilities(decodedStats, item.tier)
519
+ totalThrust += applySlotMultiplier(caps.thrust, amp)
520
+ totalEngineThm += computeEffectiveModuleStat(decodedStats.thermal ?? 0)
521
+ engineCount += 1
522
+ } else if (modType === MODULE_GENERATOR) {
523
+ hasGenerator = true
524
+ const caps = computeGeneratorCapabilities(decodedStats, item.tier)
525
+ totalGenCapacity += applySlotMultiplierUint32(caps.capacity, amp)
526
+ totalGenRecharge += applySlotMultiplierUint32(caps.recharge, amp)
527
+ } else if (modType === MODULE_GATHERER) {
528
+ hasGatherer = true
529
+ const tier = item.tier
530
+ const caps = computeGathererCapabilities(
531
+ {
532
+ strength: decodeStat(mod.stats, 0),
533
+ tolerance: decodeStat(mod.stats, 1),
534
+ saturation: decodeStat(mod.stats, 2),
535
+ },
536
+ tier
537
+ )
538
+ const scaledYield = applySlotMultiplier(caps.yield, amp)
539
+ totalGathYield += scaledYield
540
+ totalGathDrain += caps.drain
541
+ if (caps.depth > maxGathDepth) maxGathDepth = caps.depth
542
+ gathererLanes.push({
543
+ slotIndex: mod.slotIndex,
544
+ yield: scaledYield,
545
+ drain: caps.drain,
546
+ depth: caps.depth,
547
+ outputPct: amp,
548
+ })
549
+ } else if (modType === MODULE_LOADER) {
550
+ hasLoader = true
551
+ const caps = computeLoaderCapabilities(decodedStats, item.tier)
552
+ totalLoaderMass += caps.mass
553
+ totalLoaderThrust += applySlotMultiplier(caps.thrust, amp)
554
+ totalLoaderQuantity += caps.quantity
555
+ loaderLanes.push({
556
+ slotIndex: mod.slotIndex,
557
+ mass: caps.mass,
558
+ thrust: applySlotMultiplier(caps.thrust, amp),
559
+ outputPct: amp,
560
+ })
561
+ } else if (modType === MODULE_STORAGE) {
562
+ const caps = computeStorageCapabilities(decodedStats, item.tier)
563
+ totalStorageCapacity += applySlotMultiplierUint32(caps.capacity, amp)
564
+ totalCargoHoldDrain += caps.drain
565
+ } else if (modType === MODULE_CRAFTER) {
566
+ hasCrafter = true
567
+ const caps = computeCrafterCapabilities(decodedStats, item.tier)
568
+ const scaledSpeed = applySlotMultiplier(caps.speed, amp)
569
+ totalCrafterSpeed += scaledSpeed
570
+ totalCrafterDrain += caps.drain
571
+ crafterLanes.push({
572
+ slotIndex: mod.slotIndex,
573
+ speed: scaledSpeed,
574
+ drain: caps.drain,
575
+ outputPct: amp,
576
+ })
577
+ } else if (modType === MODULE_BUILDER) {
578
+ hasBuilder = true
579
+ const caps = computeBuilderCapabilities(decodedStats, item.tier)
580
+ const scaledSpeed = applySlotMultiplier(caps.speed, amp)
581
+ totalBuilderSpeed += scaledSpeed
582
+ totalBuilderDrain += caps.drain
583
+ builderLanes.push({
584
+ slotIndex: mod.slotIndex,
585
+ speed: scaledSpeed,
586
+ drain: caps.drain,
587
+ outputPct: amp,
588
+ })
589
+ } else if (modType === MODULE_HAULER) {
590
+ hasHauler = true
591
+ const caps = computeHaulerCapabilities(decodedStats, item.tier)
592
+ const eff = applySlotMultiplier(caps.efficiency, amp)
593
+ totalHaulerCapacity += caps.capacity
594
+ haulerCapByTier.set(item.tier, (haulerCapByTier.get(item.tier) ?? 0) + caps.capacity)
595
+ weightedHaulerEffNum += BigInt(eff) * BigInt(caps.capacity)
596
+ totalHaulerDrain += caps.drain
597
+ } else if (modType === MODULE_WARP) {
598
+ hasWarp = true
599
+ const caps = computeWarpCapabilities(decodedStats, item.tier)
600
+ totalWarpRange += applySlotMultiplier(caps.range, amp)
601
+ } else if (modType === MODULE_LAUNCHER) {
602
+ hasLauncher = true
603
+ const caps = computeLauncherCapabilities(
604
+ {
605
+ charge_rate: decodedStats.charge_rate ?? decodeStat(mod.stats, 0),
606
+ velocity: decodedStats.velocity ?? decodeStat(mod.stats, 1),
607
+ drain: decodedStats.drain ?? decodeStat(mod.stats, 2),
608
+ },
609
+ amp
610
+ )
611
+ totalLauncherChargeRate = clampUint16(totalLauncherChargeRate + caps.chargeRate)
612
+ totalLauncherVelocity = clampUint16(totalLauncherVelocity + caps.velocity)
613
+ totalLauncherDrain = clampUint16(totalLauncherDrain + caps.drain)
614
+ } else if (modType === MODULE_BATTERY) {
615
+ const caps = computeBatteryCapabilities(decodedStats, item.tier)
616
+ totalBatteryCapacity += applySlotMultiplierUint32(caps.capacity, amp)
617
+ }
618
+ }
619
+
620
+ if (hasGenerator && totalBatteryCapacity > 0) {
621
+ totalGenCapacity += totalBatteryCapacity
622
+ }
623
+
624
+ const result: ComputedCapabilities = {
625
+ hullmass: computeBaseHullmass(itemId, stats) + installedModuleMass,
626
+ capacity: clampUint32(baseCapacity + totalStorageCapacity),
627
+ }
628
+
629
+ if (hasEngine) {
630
+ const avgThm = engineCount > 0 ? Math.trunc(totalEngineThm / engineCount) : 0
631
+ const engineDrain = computeTravelDrain(totalThrust, avgThm)
632
+ const totalDrain = clampUint32(engineDrain + totalCargoHoldDrain + totalHaulerDrain)
633
+ result.engines = {thrust: totalThrust, drain: totalDrain}
634
+ result.travelDrain = {
635
+ engine: engineDrain,
636
+ cargoHolds: totalCargoHoldDrain,
637
+ tractorBeams: totalHaulerDrain,
638
+ total: totalDrain,
639
+ }
640
+ }
641
+ if (hasGenerator) {
642
+ result.generator = {
643
+ capacity: clampUint32(totalGenCapacity),
644
+ recharge: clampUint32(totalGenRecharge),
645
+ }
646
+ }
647
+ if (hasGatherer) {
648
+ result.gatherer = {
649
+ yield: clampUint16(totalGathYield),
650
+ drain: totalGathDrain,
651
+ depth: maxGathDepth,
652
+ }
653
+ result.gathererLanes = gathererLanes
654
+ }
655
+ if (hasLoader) {
656
+ result.loaders = {
657
+ mass: totalLoaderMass,
658
+ thrust: clampUint16(totalLoaderThrust),
659
+ quantity: totalLoaderQuantity,
660
+ }
661
+ result.loaderLanes = loaderLanes
662
+ }
663
+ if (hasCrafter) {
664
+ result.crafter = {speed: clampUint16(totalCrafterSpeed), drain: totalCrafterDrain}
665
+ result.crafterLanes = crafterLanes
666
+ }
667
+ if (hasBuilder) {
668
+ result.builder = {speed: clampUint16(totalBuilderSpeed), drain: totalBuilderDrain}
669
+ result.builderLanes = builderLanes
670
+ }
671
+ if (hasHauler) {
672
+ const efficiency =
673
+ totalHaulerCapacity > 0 ? Number(weightedHaulerEffNum / BigInt(totalHaulerCapacity)) : 0
674
+ result.hauler = {
675
+ capacity: totalHaulerCapacity,
676
+ efficiency: clampUint16(efficiency),
677
+ drain: totalHaulerDrain,
678
+ capacityByTier: [...haulerCapByTier.entries()]
679
+ .sort((a, b) => a[0] - b[0])
680
+ .map(([tier, capacity]) => ({tier, capacity})),
681
+ }
682
+ }
683
+ if (hasWarp) {
684
+ result.warp = {range: totalWarpRange}
685
+ }
686
+ if (hasLauncher) {
687
+ result.launcher = {
688
+ chargeRate: totalLauncherChargeRate,
689
+ velocity: totalLauncherVelocity,
690
+ drain: totalLauncherDrain,
691
+ }
692
+ }
693
+
694
+ return result
695
+ }
696
+
697
+ export function computeContainerCapabilities(stats: Record<string, number>): {
698
+ hullmass: number
699
+ capacity: number
700
+ } {
701
+ const statSum = (stats.strength ?? 0) + (stats.hardness ?? 0)
702
+ const exponent = statSum / 1998.0
703
+ return {
704
+ hullmass: computeBaseHullmass(ITEM_CONTAINER_T1_PACKED, stats),
705
+ capacity: Math.floor(220000 * 6 ** exponent),
706
+ }
707
+ }