@shipload/sdk 2.0.0-rc1 → 2.0.0-rc11

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 (74) hide show
  1. package/lib/shipload.d.ts +1701 -1183
  2. package/lib/shipload.js +6746 -3447
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +6429 -3229
  5. package/lib/shipload.m.js.map +1 -1
  6. package/package.json +6 -6
  7. package/src/capabilities/crafting.ts +26 -0
  8. package/src/capabilities/gathering.ts +36 -0
  9. package/src/capabilities/guards.ts +38 -0
  10. package/src/capabilities/hauling.ts +22 -0
  11. package/src/capabilities/index.ts +8 -0
  12. package/src/capabilities/loading.ts +8 -0
  13. package/src/capabilities/modules.ts +57 -0
  14. package/src/capabilities/movement.ts +29 -0
  15. package/src/capabilities/storage.ts +72 -0
  16. package/src/contracts/server.ts +932 -314
  17. package/src/data/capabilities.ts +408 -0
  18. package/src/data/categories.ts +58 -0
  19. package/src/data/colors.ts +53 -0
  20. package/src/data/items.json +17 -0
  21. package/src/data/locations.ts +53 -0
  22. package/src/data/nebula-adjectives.json +211 -0
  23. package/src/data/nebula-nouns.json +151 -0
  24. package/src/data/recipes.ts +571 -0
  25. package/src/data/syllables.json +1386 -780
  26. package/src/data/tiers.ts +45 -0
  27. package/src/derivation/crafting.ts +197 -0
  28. package/src/derivation/index.ts +28 -0
  29. package/src/derivation/location-size.ts +15 -0
  30. package/src/derivation/resources.ts +142 -0
  31. package/src/derivation/stats.ts +146 -0
  32. package/src/derivation/stratum.ts +124 -0
  33. package/src/entities/cargo-utils.ts +46 -9
  34. package/src/entities/container.ts +106 -0
  35. package/src/entities/entity-inventory.ts +13 -13
  36. package/src/entities/inventory-accessor.ts +42 -0
  37. package/src/entities/location.ts +7 -188
  38. package/src/entities/makers.ts +72 -0
  39. package/src/entities/player.ts +1 -273
  40. package/src/entities/ship-deploy.ts +263 -0
  41. package/src/entities/ship.ts +93 -453
  42. package/src/entities/warehouse.ts +34 -148
  43. package/src/errors.ts +4 -4
  44. package/src/index-module.ts +226 -42
  45. package/src/managers/actions.ts +111 -79
  46. package/src/managers/context.ts +0 -9
  47. package/src/managers/entities.ts +22 -5
  48. package/src/managers/index.ts +0 -1
  49. package/src/managers/locations.ts +15 -79
  50. package/src/market/items.ts +30 -0
  51. package/src/nft/description.ts +175 -0
  52. package/src/nft/deserializers.ts +81 -0
  53. package/src/nft/index.ts +2 -0
  54. package/src/resolution/resolve-item.ts +313 -0
  55. package/src/scheduling/accessor.ts +82 -0
  56. package/src/scheduling/projection.ts +158 -54
  57. package/src/scheduling/schedule.ts +24 -0
  58. package/src/shipload.ts +0 -5
  59. package/src/travel/travel.ts +93 -19
  60. package/src/types/capabilities.ts +71 -0
  61. package/src/types/entity-traits.ts +69 -0
  62. package/src/types/entity.ts +39 -0
  63. package/src/types/index.ts +3 -0
  64. package/src/types.ts +76 -33
  65. package/src/utils/hash.ts +1 -1
  66. package/src/utils/system.ts +148 -11
  67. package/src/data/goods.json +0 -23
  68. package/src/managers/trades.ts +0 -119
  69. package/src/market/goods.ts +0 -31
  70. package/src/market/market.ts +0 -209
  71. package/src/market/rolls.ts +0 -8
  72. package/src/trading/collect.ts +0 -939
  73. package/src/trading/deal.ts +0 -208
  74. package/src/trading/trade.ts +0 -203
@@ -1,287 +1,15 @@
1
- import {
2
- Int64,
3
- Int64Type,
4
- Name,
5
- NameType,
6
- UInt32,
7
- UInt32Type,
8
- UInt64,
9
- UInt64Type,
10
- } from '@wharfkit/antelope'
1
+ import {Name, NameType} from '@wharfkit/antelope'
11
2
  import {ServerContract} from '../contracts'
12
3
 
13
4
  export interface PlayerStateInput {
14
5
  owner: NameType
15
- balance: UInt64Type
16
- debt: UInt32Type
17
- networth: Int64Type
18
6
  }
19
7
 
20
- /**
21
- * Player helper class extending player_row with computed financial properties.
22
- * Provides easy access to balance, debt, networth, and loan calculations.
23
- */
24
8
  export class Player extends ServerContract.Types.player_row {
25
- /**
26
- * Construct a Player instance from individual state pieces.
27
- * Used by UI's ReactivePlayer to reconstruct Player from reactive state.
28
- */
29
9
  static fromState(state: PlayerStateInput): Player {
30
10
  const playerRow = ServerContract.Types.player_row.from({
31
11
  owner: Name.from(state.owner),
32
- balance: UInt64.from(state.balance),
33
- debt: UInt32.from(state.debt),
34
- networth: Int64.from(state.networth),
35
12
  })
36
13
  return new Player(playerRow)
37
14
  }
38
- // Constants for game rules (match smart contract)
39
- private static readonly MAX_LOAN = 1000000
40
- private static readonly BASE_SHIP_COST = 100 // pow(5, sequence) * 100
41
- private static readonly SHIP_COST_MULTIPLIER = 5
42
-
43
- // Optional ship count for nextShipCost calculation
44
- private _shipCount?: number
45
-
46
- /**
47
- * Set the current ship count (needed for nextShipCost calculation)
48
- */
49
- setShipCount(count: number): void {
50
- this._shipCount = count
51
- }
52
-
53
- /**
54
- * Get the current ship count (if set)
55
- */
56
- get shipCount(): number | undefined {
57
- return this._shipCount
58
- }
59
-
60
- /**
61
- * Calculate the cost of the next ship based on current ship count
62
- * Matches contract: pow(5, sequence) * 100
63
- * @param shipCount - Optional ship count (uses cached value if not provided)
64
- */
65
- getNextShipCost(shipCount?: number): UInt64 {
66
- const count = shipCount ?? this._shipCount ?? 0
67
- const cost = Math.pow(Player.SHIP_COST_MULTIPLIER, count) * Player.BASE_SHIP_COST
68
- return UInt64.from(Math.floor(cost))
69
- }
70
-
71
- /**
72
- * Get the cost of the next ship based on cached ship count
73
- */
74
- get nextShipCost(): UInt64 {
75
- return this.getNextShipCost()
76
- }
77
-
78
- /**
79
- * Check if player can afford to buy a ship
80
- * @param shipCount - Optional ship count (uses cached value if not provided)
81
- */
82
- canBuyShip(shipCount?: number): boolean {
83
- return UInt64.from(this.balance).gte(this.getNextShipCost(shipCount))
84
- }
85
-
86
- /**
87
- * Calculate available loan amount (max loan - current debt)
88
- */
89
- get availableLoan(): UInt64 {
90
- const maxLoan = UInt64.from(Player.MAX_LOAN)
91
- if (UInt64.from(this.debt).gte(maxLoan)) {
92
- return UInt64.from(0)
93
- }
94
- return maxLoan.subtracting(this.debt)
95
- }
96
-
97
- /**
98
- * Check if player can take out a loan
99
- */
100
- get canTakeLoan(): boolean {
101
- return this.availableLoan.gt(UInt64.zero)
102
- }
103
-
104
- /**
105
- * Check if player can pay back loan
106
- */
107
- get canPayLoan(): boolean {
108
- return UInt64.from(this.debt).gt(UInt64.zero) && UInt64.from(this.balance).gt(UInt64.zero)
109
- }
110
-
111
- /**
112
- * Calculate maximum payback amount (min of debt and balance)
113
- */
114
- get maxPayback(): UInt64 {
115
- return UInt64.from(this.debt).lt(this.balance) ? this.debt : this.balance
116
- }
117
-
118
- /**
119
- * Get the maximum loan amount (constant)
120
- */
121
- static get MAX_LOAN_LIMIT(): number {
122
- return Player.MAX_LOAN
123
- }
124
-
125
- /**
126
- * Check if player is in debt
127
- */
128
- get hasDebt(): boolean {
129
- return UInt64.from(this.debt).gt(UInt64.zero)
130
- }
131
-
132
- /**
133
- * Check if player is solvent (positive networth)
134
- */
135
- get isSolvent(): boolean {
136
- return this.networth.gte(Int64.zero)
137
- }
138
-
139
- /**
140
- * Create an optimistic update for balance changes
141
- * Uses integer math to match contract
142
- * @param delta - Amount to change (can be negative)
143
- */
144
- withBalanceChange(delta: UInt64 | number): Player {
145
- const newPlayer = Player.from(this)
146
- const amount = typeof delta === 'number' ? UInt64.from(Math.abs(delta)) : delta
147
-
148
- if (typeof delta === 'number' && delta < 0) {
149
- // Subtract, ensuring we don't go below 0
150
- newPlayer.balance = UInt64.from(this.balance).gte(amount)
151
- ? this.balance.subtracting(amount)
152
- : UInt64.from(0)
153
- } else {
154
- // Add
155
- newPlayer.balance = this.balance.adding(amount)
156
- }
157
-
158
- // Calculate networth as Int64 (can be negative)
159
- const balanceInt = Int64.from(newPlayer.balance)
160
- const debtInt = Int64.from(newPlayer.debt)
161
- newPlayer.networth = balanceInt.subtracting(debtInt)
162
- return newPlayer
163
- }
164
-
165
- /**
166
- * Create an optimistic update for debt changes
167
- * Uses integer math to match contract
168
- * @param delta - Amount to change (can be negative)
169
- */
170
- withDebtChange(delta: UInt64 | number): Player {
171
- const newPlayer = Player.from(this)
172
- const amount = typeof delta === 'number' ? UInt64.from(Math.abs(delta)) : delta
173
-
174
- if (typeof delta === 'number' && delta < 0) {
175
- // Subtract, ensuring we don't go below 0
176
- newPlayer.debt = UInt64.from(this.debt).gte(amount)
177
- ? this.debt.subtracting(amount)
178
- : UInt64.from(0)
179
- } else {
180
- // Add
181
- newPlayer.debt = this.debt.adding(amount)
182
- }
183
-
184
- // Calculate networth as Int64 (can be negative)
185
- const balanceInt = Int64.from(newPlayer.balance)
186
- const debtInt = Int64.from(newPlayer.debt)
187
- newPlayer.networth = balanceInt.subtracting(debtInt)
188
- return newPlayer
189
- }
190
-
191
- /**
192
- * Create an optimistic update for taking a loan
193
- */
194
- withLoan(amount: UInt64): Player {
195
- const newPlayer = Player.from(this)
196
- newPlayer.balance = this.balance.adding(amount)
197
- newPlayer.debt = this.debt.adding(amount)
198
- // Calculate networth as Int64 (can be negative)
199
- const balanceInt = Int64.from(newPlayer.balance)
200
- const debtInt = Int64.from(newPlayer.debt)
201
- newPlayer.networth = balanceInt.subtracting(debtInt)
202
- return newPlayer
203
- }
204
-
205
- /**
206
- * Create an optimistic update for paying back a loan
207
- */
208
- withLoanPayment(amount: UInt64): Player {
209
- const actualPayment = this.maxPayback.lt(amount) ? this.maxPayback : amount
210
- const newPlayer = Player.from(this)
211
- newPlayer.balance = this.balance.subtracting(actualPayment)
212
- newPlayer.debt = this.debt.subtracting(actualPayment)
213
- // Calculate networth as Int64 (can be negative)
214
- const balanceInt = Int64.from(newPlayer.balance)
215
- const debtInt = Int64.from(newPlayer.debt)
216
- newPlayer.networth = balanceInt.subtracting(debtInt)
217
- return newPlayer
218
- }
219
-
220
- /**
221
- * Simulate networth update from selling goods.
222
- * Matches contract: networth += (sellPrice - paid * quantity)
223
- * Contract reference: market.cpp:75
224
- *
225
- * @param sellPrice - Total revenue from sale (price * quantity)
226
- * @param paidPerUnit - Average cost per unit paid (from cargo.paid)
227
- * @param quantity - Quantity being sold
228
- * @returns New player with updated networth
229
- *
230
- * @example
231
- * // Sold 10 units at 150 each (revenue=1500), paid 100 per unit
232
- * const newPlayer = player.withSaleNetworth(
233
- * UInt64.from(1500),
234
- * UInt64.from(100),
235
- * UInt32.from(10)
236
- * )
237
- * // Networth increases by: 1500 - (100*10) = 500
238
- */
239
- withSaleNetworth(sellPrice: UInt64, paidPerUnit: UInt64, quantity: UInt64): Player {
240
- // Match contract: price - paid * quantity
241
- const cost = paidPerUnit.multiplying(quantity)
242
- const profit = sellPrice.gte(cost) ? sellPrice.subtracting(cost) : Int64.from(0)
243
-
244
- const newPlayer = Player.from(this)
245
- newPlayer.networth = Int64.from(this.networth).adding(profit)
246
- return newPlayer
247
- }
248
-
249
- /**
250
- * Simulate complete sell goods transaction.
251
- * Updates both balance (adds revenue) and networth (adds profit).
252
- * Matches contract actions: update_balance + update_networth
253
- *
254
- * @param sellPrice - Total revenue from sale (price * quantity)
255
- * @param paidPerUnit - Average cost per unit paid (from cargo.paid)
256
- * @param quantity - Quantity being sold
257
- * @returns New player with updated balance and networth
258
- */
259
- withSellGoods(sellPrice: UInt64, paidPerUnit: UInt64, quantity: UInt64): Player {
260
- const cost = paidPerUnit.multiplying(quantity)
261
- const profit = sellPrice.gte(cost) ? sellPrice.subtracting(cost) : Int64.from(0)
262
-
263
- const newPlayer = Player.from(this)
264
- newPlayer.balance = this.balance.adding(sellPrice)
265
- newPlayer.networth = Int64.from(this.networth).adding(profit)
266
- return newPlayer
267
- }
268
-
269
- /**
270
- * Simulate complete buy goods transaction.
271
- * Updates balance (subtracts cost).
272
- *
273
- * @param purchaseCost - Total cost of purchase (price * quantity)
274
- * @returns New player with updated balance
275
- */
276
- withBuyGoods(purchaseCost: UInt64): Player {
277
- const newPlayer = Player.from(this)
278
- newPlayer.balance = UInt64.from(this.balance).gte(purchaseCost)
279
- ? this.balance.subtracting(purchaseCost)
280
- : UInt64.from(0)
281
- // Calculate networth as Int64 (can be negative)
282
- const balanceInt = Int64.from(newPlayer.balance)
283
- const debtInt = Int64.from(newPlayer.debt)
284
- newPlayer.networth = balanceInt.subtracting(debtInt)
285
- return newPlayer
286
- }
287
15
  }
@@ -0,0 +1,263 @@
1
+ import {decodeCraftedItemStats} from '../derivation/crafting'
2
+ import {
3
+ getModuleCapabilityType,
4
+ MODULE_CRAFTER,
5
+ MODULE_ENGINE,
6
+ MODULE_GATHERER,
7
+ MODULE_GENERATOR,
8
+ MODULE_HAULER,
9
+ MODULE_LOADER,
10
+ } from '../capabilities/modules'
11
+
12
+ export function computeShipHullCapabilities(stats: Record<string, number>): {
13
+ hullmass: number
14
+ capacity: number
15
+ } {
16
+ const density = stats.density ?? 500
17
+ const strength = stats.strength ?? 500
18
+ const ductility = stats.ductility ?? 500
19
+ const purity = stats.purity ?? 500
20
+
21
+ const hullmass = 25000 + 75 * density
22
+ const statSum = strength + ductility + purity
23
+ const exponent = statSum / 2997.0
24
+ const capacity = Math.floor(1000000 * Math.pow(10, exponent))
25
+
26
+ return {hullmass, capacity}
27
+ }
28
+
29
+ export function computeEngineCapabilities(stats: Record<string, number>): {
30
+ thrust: number
31
+ drain: number
32
+ } {
33
+ const vol = stats.volatility ?? 500
34
+ const thm = stats.thermal ?? 500
35
+
36
+ return {
37
+ thrust: 400 + Math.floor((vol * 3) / 4),
38
+ drain: Math.max(30, 50 - Math.floor(thm / 70)),
39
+ }
40
+ }
41
+
42
+ export function computeGeneratorCapabilities(stats: Record<string, number>): {
43
+ capacity: number
44
+ recharge: number
45
+ } {
46
+ const res = stats.resonance ?? 500
47
+ const clr = stats.clarity ?? 500
48
+
49
+ return {
50
+ capacity: 300 + Math.floor(res / 6),
51
+ recharge: 5 + Math.floor((clr * 15) / 1000),
52
+ }
53
+ }
54
+
55
+ export function computeGathererCapabilities(stats: Record<string, number>): {
56
+ yield: number
57
+ drain: number
58
+ depth: number
59
+ speed: number
60
+ } {
61
+ const str = stats.strength ?? 500
62
+ const con = stats.conductivity ?? 500
63
+ const ref = stats.reflectivity ?? 500
64
+ const tol = stats.tolerance ?? 500
65
+
66
+ return {
67
+ yield: 200 + str,
68
+ drain: Math.max(10, 50 - Math.floor(con / 20)),
69
+ depth: 200 + Math.floor((tol * 3) / 2),
70
+ speed: 100 + Math.floor((ref * 4) / 5),
71
+ }
72
+ }
73
+
74
+ export function computeLoaderCapabilities(stats: Record<string, number>): {
75
+ mass: number
76
+ thrust: number
77
+ quantity: number
78
+ } {
79
+ const duc = stats.ductility ?? 500
80
+ const pla = stats.plasticity ?? 500
81
+
82
+ return {
83
+ mass: Math.max(200, 2000 - Math.floor(duc * 2)),
84
+ thrust: 1 + Math.floor(pla / 500),
85
+ quantity: 1,
86
+ }
87
+ }
88
+
89
+ export function computeManufacturingCapabilities(stats: Record<string, number>): {
90
+ speed: number
91
+ drain: number
92
+ } {
93
+ const rea = stats.reactivity ?? 500
94
+ const clr = stats.clarity ?? 500
95
+
96
+ return {
97
+ speed: 100 + Math.floor((rea * 4) / 5),
98
+ drain: Math.max(5, 30 - Math.floor(clr / 33)),
99
+ }
100
+ }
101
+
102
+ export function computeHaulerCapabilities(stats: Record<string, number>): {
103
+ capacity: number
104
+ efficiency: number
105
+ drain: number
106
+ } {
107
+ const res = stats.resonance ?? 500
108
+ const con = stats.conductivity ?? 500
109
+ const clr = stats.clarity ?? 500
110
+
111
+ return {
112
+ capacity: Math.max(1, 1 + Math.floor(res / 400)),
113
+ efficiency: 2000 + con * 6,
114
+ drain: Math.max(3, 15 - Math.floor(clr / 80)),
115
+ }
116
+ }
117
+
118
+ export function computeStorageCapabilities(
119
+ stats: Record<string, number>,
120
+ baseCapacity: number
121
+ ): {
122
+ capacityBonus: number
123
+ } {
124
+ const strength = stats.strength ?? 500
125
+ const ductility = stats.ductility ?? 500
126
+ const purity = stats.purity ?? 500
127
+
128
+ const statSum = strength + ductility + purity
129
+ const capacityBonus = Math.floor(
130
+ (baseCapacity * (10 + Math.floor((statSum * 10) / 2997))) / 100
131
+ )
132
+
133
+ return {capacityBonus}
134
+ }
135
+
136
+ export function computeWarehouseHullCapabilities(stats: Record<string, number>): {
137
+ hullmass: number
138
+ capacity: number
139
+ } {
140
+ const density = stats.density ?? 500
141
+ const strength = stats.strength ?? 500
142
+ const ductility = stats.ductility ?? 500
143
+ const purity = stats.purity ?? 500
144
+
145
+ const hullmass = 25000 + 75 * density
146
+ const statSum = strength + ductility + purity
147
+ const exponent = statSum / 2997.0
148
+ const capacity = Math.floor(20000000 * Math.pow(10, exponent))
149
+
150
+ return {hullmass, capacity}
151
+ }
152
+
153
+ export interface ShipCapabilities {
154
+ engines?: {thrust: number; drain: number}
155
+ generator?: {capacity: number; recharge: number}
156
+ gatherer?: {yield: number; drain: number; depth: number; speed: number}
157
+ hauler?: {capacity: number; efficiency: number; drain: number}
158
+ loaders?: {mass: number; thrust: number; quantity: number}
159
+ crafter?: {speed: number; drain: number}
160
+ }
161
+
162
+ export function computeShipCapabilities(
163
+ modules: {itemId: number; seed: bigint}[]
164
+ ): ShipCapabilities {
165
+ const ship: ShipCapabilities = {}
166
+
167
+ const engineModules = modules.filter((m) => getModuleCapabilityType(m.itemId) === MODULE_ENGINE)
168
+ if (engineModules.length > 0) {
169
+ let totalThrust = 0
170
+ let totalDrain = 0
171
+ for (const m of engineModules) {
172
+ const caps = computeEngineCapabilities(decodeCraftedItemStats(m.itemId, m.seed))
173
+ totalThrust += caps.thrust
174
+ totalDrain += caps.drain
175
+ }
176
+ ship.engines = {thrust: totalThrust, drain: totalDrain}
177
+ }
178
+
179
+ const generatorModules = modules.filter(
180
+ (m) => getModuleCapabilityType(m.itemId) === MODULE_GENERATOR
181
+ )
182
+ if (generatorModules.length > 0) {
183
+ let totalCapacity = 0
184
+ let totalRecharge = 0
185
+ for (const m of generatorModules) {
186
+ const caps = computeGeneratorCapabilities(decodeCraftedItemStats(m.itemId, m.seed))
187
+ totalCapacity += caps.capacity
188
+ totalRecharge += caps.recharge
189
+ }
190
+ ship.generator = {capacity: totalCapacity, recharge: totalRecharge}
191
+ }
192
+
193
+ const gathererModules = modules.filter(
194
+ (m) => getModuleCapabilityType(m.itemId) === MODULE_GATHERER
195
+ )
196
+ if (gathererModules.length > 0) {
197
+ let totalYield = 0
198
+ let totalDrain = 0
199
+ let totalDepth = 0
200
+ let totalSpeed = 0
201
+ for (const m of gathererModules) {
202
+ const caps = computeGathererCapabilities(decodeCraftedItemStats(m.itemId, m.seed))
203
+ totalYield += caps.yield
204
+ totalDrain += caps.drain
205
+ totalDepth += caps.depth
206
+ totalSpeed += caps.speed
207
+ }
208
+ ship.gatherer = {yield: totalYield, drain: totalDrain, depth: totalDepth, speed: totalSpeed}
209
+ }
210
+
211
+ const haulerModules = modules.filter((m) => getModuleCapabilityType(m.itemId) === MODULE_HAULER)
212
+ if (haulerModules.length > 0) {
213
+ let totalCapacity = 0
214
+ let weightedEffNum = 0
215
+ let totalDrain = 0
216
+ for (const m of haulerModules) {
217
+ const decoded = decodeCraftedItemStats(m.itemId, m.seed)
218
+ const caps = computeHaulerCapabilities({
219
+ resonance: decoded.capacity,
220
+ conductivity: decoded.efficiency,
221
+ clarity: decoded.drain,
222
+ })
223
+ totalCapacity += caps.capacity
224
+ weightedEffNum += caps.efficiency * caps.capacity
225
+ totalDrain += caps.drain
226
+ }
227
+ ship.hauler = {
228
+ capacity: totalCapacity,
229
+ efficiency: totalCapacity > 0 ? Math.floor(weightedEffNum / totalCapacity) : 0,
230
+ drain: totalDrain,
231
+ }
232
+ }
233
+
234
+ const loaderModules = modules.filter((m) => getModuleCapabilityType(m.itemId) === MODULE_LOADER)
235
+ if (loaderModules.length > 0) {
236
+ let totalMass = 0
237
+ let totalThrust = 0
238
+ let totalQuantity = 0
239
+ for (const m of loaderModules) {
240
+ const caps = computeLoaderCapabilities(decodeCraftedItemStats(m.itemId, m.seed))
241
+ totalMass += caps.mass
242
+ totalThrust += caps.thrust
243
+ totalQuantity += caps.quantity
244
+ }
245
+ ship.loaders = {mass: totalMass, thrust: totalThrust, quantity: totalQuantity}
246
+ }
247
+
248
+ const crafterModules = modules.filter(
249
+ (m) => getModuleCapabilityType(m.itemId) === MODULE_CRAFTER
250
+ )
251
+ if (crafterModules.length > 0) {
252
+ let totalSpeed = 0
253
+ let totalDrain = 0
254
+ for (const m of crafterModules) {
255
+ const caps = computeManufacturingCapabilities(decodeCraftedItemStats(m.itemId, m.seed))
256
+ totalSpeed += caps.speed
257
+ totalDrain += caps.drain
258
+ }
259
+ ship.crafter = {speed: totalSpeed, drain: totalDrain}
260
+ }
261
+
262
+ return ship
263
+ }