@shipload/sdk 1.0.0-next.59 → 1.0.0-next.60

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.
@@ -2086,6 +2086,88 @@
2086
2086
  ],
2087
2087
  "blendWeights": []
2088
2088
  },
2089
+ {
2090
+ "outputItemId": 20100,
2091
+ "outputMass": 1536000,
2092
+ "inputs": [
2093
+ {
2094
+ "itemId": 20003,
2095
+ "quantity": 600
2096
+ },
2097
+ {
2098
+ "itemId": 10100,
2099
+ "quantity": 1
2100
+ }
2101
+ ],
2102
+ "statSlots": [
2103
+ {
2104
+ "sources": [
2105
+ {
2106
+ "inputIndex": 0,
2107
+ "statIndex": 0
2108
+ },
2109
+ {
2110
+ "inputIndex": 1,
2111
+ "statIndex": 0
2112
+ }
2113
+ ]
2114
+ },
2115
+ {
2116
+ "sources": [
2117
+ {
2118
+ "inputIndex": 0,
2119
+ "statIndex": 1
2120
+ },
2121
+ {
2122
+ "inputIndex": 1,
2123
+ "statIndex": 1
2124
+ }
2125
+ ]
2126
+ }
2127
+ ],
2128
+ "blendWeights": []
2129
+ },
2130
+ {
2131
+ "outputItemId": 20101,
2132
+ "outputMass": 1536000,
2133
+ "inputs": [
2134
+ {
2135
+ "itemId": 20004,
2136
+ "quantity": 600
2137
+ },
2138
+ {
2139
+ "itemId": 10101,
2140
+ "quantity": 1
2141
+ }
2142
+ ],
2143
+ "statSlots": [
2144
+ {
2145
+ "sources": [
2146
+ {
2147
+ "inputIndex": 0,
2148
+ "statIndex": 0
2149
+ },
2150
+ {
2151
+ "inputIndex": 1,
2152
+ "statIndex": 0
2153
+ }
2154
+ ]
2155
+ },
2156
+ {
2157
+ "sources": [
2158
+ {
2159
+ "inputIndex": 0,
2160
+ "statIndex": 1
2161
+ },
2162
+ {
2163
+ "inputIndex": 1,
2164
+ "statIndex": 1
2165
+ }
2166
+ ]
2167
+ }
2168
+ ],
2169
+ "blendWeights": []
2170
+ },
2089
2171
  {
2090
2172
  "outputItemId": 20102,
2091
2173
  "outputMass": 1536000,
@@ -2143,6 +2225,92 @@
2143
2225
  ],
2144
2226
  "blendWeights": []
2145
2227
  },
2228
+ {
2229
+ "outputItemId": 20103,
2230
+ "outputMass": 1536000,
2231
+ "inputs": [
2232
+ {
2233
+ "itemId": 20007,
2234
+ "quantity": 600
2235
+ },
2236
+ {
2237
+ "itemId": 10103,
2238
+ "quantity": 1
2239
+ }
2240
+ ],
2241
+ "statSlots": [
2242
+ {
2243
+ "sources": [
2244
+ {
2245
+ "inputIndex": 0,
2246
+ "statIndex": 1
2247
+ },
2248
+ {
2249
+ "inputIndex": 1,
2250
+ "statIndex": 0
2251
+ }
2252
+ ]
2253
+ },
2254
+ {
2255
+ "sources": [
2256
+ {
2257
+ "inputIndex": 0,
2258
+ "statIndex": 0
2259
+ },
2260
+ {
2261
+ "inputIndex": 1,
2262
+ "statIndex": 1
2263
+ }
2264
+ ]
2265
+ }
2266
+ ],
2267
+ "blendWeights": []
2268
+ },
2269
+ {
2270
+ "outputItemId": 20104,
2271
+ "outputMass": 1536000,
2272
+ "inputs": [
2273
+ {
2274
+ "itemId": 20009,
2275
+ "quantity": 300
2276
+ },
2277
+ {
2278
+ "itemId": 20006,
2279
+ "quantity": 300
2280
+ },
2281
+ {
2282
+ "itemId": 10104,
2283
+ "quantity": 1
2284
+ }
2285
+ ],
2286
+ "statSlots": [
2287
+ {
2288
+ "sources": [
2289
+ {
2290
+ "inputIndex": 0,
2291
+ "statIndex": 2
2292
+ },
2293
+ {
2294
+ "inputIndex": 2,
2295
+ "statIndex": 0
2296
+ }
2297
+ ]
2298
+ },
2299
+ {
2300
+ "sources": [
2301
+ {
2302
+ "inputIndex": 1,
2303
+ "statIndex": 0
2304
+ },
2305
+ {
2306
+ "inputIndex": 2,
2307
+ "statIndex": 1
2308
+ }
2309
+ ]
2310
+ }
2311
+ ],
2312
+ "blendWeights": []
2313
+ },
2146
2314
  {
2147
2315
  "outputItemId": 20106,
2148
2316
  "outputMass": 1536000,
@@ -2200,6 +2368,84 @@
2200
2368
  ],
2201
2369
  "blendWeights": []
2202
2370
  },
2371
+ {
2372
+ "outputItemId": 20107,
2373
+ "outputMass": 1536000,
2374
+ "inputs": [
2375
+ {
2376
+ "itemId": 20004,
2377
+ "quantity": 300
2378
+ },
2379
+ {
2380
+ "itemId": 20009,
2381
+ "quantity": 300
2382
+ },
2383
+ {
2384
+ "itemId": 10107,
2385
+ "quantity": 1
2386
+ }
2387
+ ],
2388
+ "statSlots": [
2389
+ {
2390
+ "sources": [
2391
+ {
2392
+ "inputIndex": 0,
2393
+ "statIndex": 1
2394
+ },
2395
+ {
2396
+ "inputIndex": 2,
2397
+ "statIndex": 0
2398
+ }
2399
+ ]
2400
+ }
2401
+ ],
2402
+ "blendWeights": []
2403
+ },
2404
+ {
2405
+ "outputItemId": 20110,
2406
+ "outputMass": 1536000,
2407
+ "inputs": [
2408
+ {
2409
+ "itemId": 20006,
2410
+ "quantity": 300
2411
+ },
2412
+ {
2413
+ "itemId": 20008,
2414
+ "quantity": 300
2415
+ },
2416
+ {
2417
+ "itemId": 10110,
2418
+ "quantity": 1
2419
+ }
2420
+ ],
2421
+ "statSlots": [
2422
+ {
2423
+ "sources": [
2424
+ {
2425
+ "inputIndex": 0,
2426
+ "statIndex": 2
2427
+ },
2428
+ {
2429
+ "inputIndex": 2,
2430
+ "statIndex": 0
2431
+ }
2432
+ ]
2433
+ },
2434
+ {
2435
+ "sources": [
2436
+ {
2437
+ "inputIndex": 1,
2438
+ "statIndex": 1
2439
+ },
2440
+ {
2441
+ "inputIndex": 2,
2442
+ "statIndex": 1
2443
+ }
2444
+ ]
2445
+ }
2446
+ ],
2447
+ "blendWeights": []
2448
+ },
2203
2449
  {
2204
2450
  "outputItemId": 20200,
2205
2451
  "outputMass": 3100000,
@@ -1,15 +1,34 @@
1
- import {expect, test} from 'bun:test'
1
+ import {describe, expect, test} from 'bun:test'
2
2
  import {
3
3
  computeEntityCapabilities,
4
4
  computeEngineCapabilities,
5
5
  computeGathererCapabilities,
6
+ computeGathererYield,
6
7
  computeGeneratorCapabilities,
7
8
  computeCrafterCapabilities,
8
9
  computeBuilderCapabilities,
9
10
  computeLoaderCapabilities,
10
11
  computeBaseCapacity,
11
12
  computeContainerCapabilities,
13
+ GATHERER_YIELD_TIER_TABLE,
14
+ ENGINE_THRUST_TIER_PCT,
15
+ GENERATOR_CAPACITY_TIER_PCT,
16
+ GENERATOR_RECHARGE_TIER_PCT,
17
+ CRAFTER_SPEED_TIER_PCT,
18
+ BUILDER_SPEED_TIER_PCT,
19
+ WARP_RANGE_TIER_PCT,
20
+ LOADER_THRUST_TIER_PCT,
21
+ moduleTierPct,
12
22
  } from './capabilities'
23
+ import {
24
+ computeEngineThrust,
25
+ computeGeneratorCap,
26
+ computeGeneratorRech,
27
+ computeCrafterSpeed,
28
+ computeBuilderSpeed,
29
+ computeWarpRange,
30
+ computeLoaderThrust,
31
+ } from '../nft/description'
13
32
  import {applySlotMultiplier, U16_MAX} from '../entities/slot-multiplier'
14
33
  import {encodeStats} from './crafting'
15
34
  import {
@@ -147,7 +166,7 @@ test('computeEntityCapabilities emits crafterLanes alongside legacy crafter sum'
147
166
  expect(result.crafterLanes!.length).toBe(1)
148
167
  expect(result.crafterLanes![0].slotIndex).toBe(0)
149
168
 
150
- const caps = computeCrafterCapabilities({fineness: 400, conductivity: 300})
169
+ const caps = computeCrafterCapabilities({fineness: 400, conductivity: 300}, 1)
151
170
  const expectedSpeed = applySlotMultiplier(caps.speed, 120)
152
171
  expect(result.crafterLanes![0].speed).toBe(expectedSpeed)
153
172
  expect(result.crafterLanes![0].drain).toBe(caps.drain)
@@ -159,7 +178,7 @@ test('computeEntityCapabilities emits crafterLanes alongside legacy crafter sum'
159
178
  })
160
179
 
161
180
  test('builder capabilities read canonical resonance and fineness slots', () => {
162
- expect(computeBuilderCapabilities({resonance: 500, fineness: 330})).toEqual({
181
+ expect(computeBuilderCapabilities({resonance: 500, fineness: 330}, 1)).toEqual({
163
182
  speed: 500,
164
183
  drain: 20_000,
165
184
  })
@@ -189,7 +208,7 @@ test('computeEntityCapabilities emits loaderLanes alongside legacy loaders sum',
189
208
  expect(result.loaderLanes!.length).toBe(1)
190
209
  expect(result.loaderLanes![0].slotIndex).toBe(0)
191
210
 
192
- const caps = computeLoaderCapabilities({insulation: 600, plasticity: 500})
211
+ const caps = computeLoaderCapabilities({insulation: 600, plasticity: 500}, 1)
193
212
  // mass is unscaled (raw); thrust is amp-scaled
194
213
  expect(result.loaderLanes![0].mass).toBe(caps.mass)
195
214
  expect(result.loaderLanes![0].thrust).toBe(applySlotMultiplier(caps.thrust, 80))
@@ -206,7 +225,7 @@ test('per-lane amp-scaled stats clamp to UInt16, matching the contract clamp_to_
206
225
  })
207
226
 
208
227
  test('generator capacity and recharge are denominated to milli-energy', () => {
209
- const caps = computeGeneratorCapabilities({resonance: 213, reflectivity: 213})
228
+ const caps = computeGeneratorCapabilities({resonance: 213, reflectivity: 213}, 1)
210
229
  expect(caps.capacity).toBe(1_406_500)
211
230
  expect(caps.recharge).toBe(3_278)
212
231
  })
@@ -222,14 +241,14 @@ test('engine and generator capabilities use tapered quality consistently', () =>
222
241
  ]
223
242
 
224
243
  const result = computeEntityCapabilities({}, ITEM_ROUSTABOUT_T1A_PACKED, modules, layout)
225
- const engines = computeEngineCapabilities({volatility: 500, thermal: 500})
244
+ const engines = computeEngineCapabilities({volatility: 500, thermal: 500}, 1)
226
245
 
227
246
  expect(result.engines).toEqual({
228
247
  thrust: engines.thrust,
229
248
  drain: computeTravelDrain(engines.thrust, computeEffectiveModuleStat(500)),
230
249
  })
231
250
  expect(result.generator).toEqual(
232
- computeGeneratorCapabilities({resonance: 500, reflectivity: 500})
251
+ computeGeneratorCapabilities({resonance: 500, reflectivity: 500}, 1)
233
252
  )
234
253
  })
235
254
 
@@ -237,7 +256,7 @@ test('gatherer/crafter/hauler drains are denominated', () => {
237
256
  expect(computeGathererCapabilities({strength: 0, hardness: 0, saturation: 213}, 1).drain).toBe(
238
257
  1_967_500
239
258
  )
240
- expect(computeCrafterCapabilities({fineness: 0, conductivity: 213}).drain).toBe(23_546)
259
+ expect(computeCrafterCapabilities({fineness: 0, conductivity: 213}, 1).drain).toBe(23_546)
241
260
  })
242
261
 
243
262
  test('gatherer depth accepts canonical tolerance and legacy recipe-labelled hardness', () => {
@@ -248,3 +267,82 @@ test('gatherer depth accepts canonical tolerance and legacy recipe-labelled hard
248
267
  1_565
249
268
  )
250
269
  })
270
+
271
+ describe('computeGathererYield', () => {
272
+ test('table is the k=0.2 integer-percent curve T1..T10', () => {
273
+ expect([...GATHERER_YIELD_TIER_TABLE]).toEqual([
274
+ 100, 120, 140, 160, 180, 200, 220, 240, 260, 280,
275
+ ])
276
+ })
277
+
278
+ test('T1 equals the base yield 200 + str (no change)', () => {
279
+ expect(computeGathererYield(500, 1)).toBe(700)
280
+ expect(computeGathererYield(0, 1)).toBe(200)
281
+ })
282
+
283
+ test('T2 scales base yield by 1.2, floored', () => {
284
+ expect(computeGathererYield(500, 2)).toBe(840) // floor(700 * 120 / 100)
285
+ expect(computeGathererYield(213, 2)).toBe(495) // floor(413 * 120 / 100) = floor(495.6)
286
+ })
287
+
288
+ test('T10 scales base yield by 2.8, floored', () => {
289
+ expect(computeGathererYield(500, 10)).toBe(1960) // 700 * 280 / 100
290
+ })
291
+
292
+ test('tier is clamped to [1, 10]', () => {
293
+ expect(computeGathererYield(500, 0)).toBe(700)
294
+ expect(computeGathererYield(500, 99)).toBe(1960)
295
+ })
296
+ })
297
+
298
+ describe('module tier tables', () => {
299
+ const ALL_TABLES = [
300
+ ENGINE_THRUST_TIER_PCT,
301
+ GENERATOR_CAPACITY_TIER_PCT,
302
+ GENERATOR_RECHARGE_TIER_PCT,
303
+ CRAFTER_SPEED_TIER_PCT,
304
+ BUILDER_SPEED_TIER_PCT,
305
+ WARP_RANGE_TIER_PCT,
306
+ LOADER_THRUST_TIER_PCT,
307
+ ]
308
+ test('every table has 10 rows and T1 = 100', () => {
309
+ for (const table of ALL_TABLES) {
310
+ expect(table.length).toBe(10)
311
+ expect(table[0]).toBe(100)
312
+ }
313
+ })
314
+ test('moduleTierPct clamps out-of-range tiers', () => {
315
+ expect(moduleTierPct(ENGINE_THRUST_TIER_PCT, 0)).toBe(100)
316
+ expect(moduleTierPct(ENGINE_THRUST_TIER_PCT, 11)).toBe(280)
317
+ })
318
+ test('engine thrust tiers (stat 500 pre-effective)', () => {
319
+ expect(computeEngineThrust(500, 1)).toBe(775)
320
+ expect(computeEngineThrust(500, 2)).toBe(930)
321
+ expect(computeEngineThrust(500, 10)).toBe(2170)
322
+ })
323
+ test('generator capacity k=0.1, recharge k=0.2', () => {
324
+ expect(computeGeneratorCap(500, 1)).toBe(1_550_000)
325
+ expect(computeGeneratorCap(500, 2)).toBe(1_705_000)
326
+ expect(computeGeneratorCap(500, 10)).toBe(2_945_000)
327
+ expect(computeGeneratorRech(500, 1)).toBe(5000)
328
+ expect(computeGeneratorRech(500, 2)).toBe(6000)
329
+ expect(computeGeneratorRech(500, 10)).toBe(14000)
330
+ })
331
+ test('crafter/builder speed tiers', () => {
332
+ expect(computeCrafterSpeed(500, 1)).toBe(500)
333
+ expect(computeCrafterSpeed(500, 2)).toBe(600)
334
+ expect(computeBuilderSpeed(500, 2)).toBe(600)
335
+ expect(computeBuilderSpeed(500, 10)).toBe(1400)
336
+ })
337
+ test('warp range tiers', () => {
338
+ expect(computeWarpRange(500, 1)).toBe(1600)
339
+ expect(computeWarpRange(500, 2)).toBe(1920)
340
+ expect(computeWarpRange(500, 10)).toBe(4480)
341
+ })
342
+ test('loader thrust tiers floor correctly', () => {
343
+ expect(computeLoaderThrust(500, 1)).toBe(26)
344
+ expect(computeLoaderThrust(500, 2)).toBe(31)
345
+ expect(computeLoaderThrust(500, 10)).toBe(72)
346
+ expect(computeLoaderThrust(0, 2)).toBe(1)
347
+ })
348
+ })
@@ -39,29 +39,33 @@ export function computeShipHullCapabilities(
39
39
  }
40
40
  }
41
41
 
42
- export function computeEngineCapabilities(stats: Record<string, number>): {
43
- thrust: number
44
- drain: number
45
- } {
42
+ export function computeEngineCapabilities(
43
+ stats: Record<string, number>,
44
+ tier: number
45
+ ): {thrust: number; drain: number} {
46
46
  const vol = computeEffectiveModuleStat(stats.volatility)
47
47
  const thm = computeEffectiveModuleStat(stats.thermal)
48
-
49
48
  return {
50
- thrust: 400 + Math.floor((vol * 3) / 4),
49
+ thrust: Math.floor(
50
+ ((400 + Math.floor((vol * 3) / 4)) * moduleTierPct(ENGINE_THRUST_TIER_PCT, tier)) / 100
51
+ ),
51
52
  drain: 2 * Math.max(30, 50 - Math.floor(thm / 70)),
52
53
  }
53
54
  }
54
55
 
55
- export function computeGeneratorCapabilities(stats: Record<string, number>): {
56
- capacity: number
57
- recharge: number
58
- } {
56
+ export function computeGeneratorCapabilities(
57
+ stats: Record<string, number>,
58
+ tier: number
59
+ ): {capacity: number; recharge: number} {
59
60
  const res = computeEffectiveModuleStat(stats.resonance)
60
61
  const ref = computeEffectiveModuleStat(stats.reflectivity)
61
-
62
62
  return {
63
- capacity: 1_300_000 + res * 500,
64
- recharge: 2000 + ref * 6,
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
+ ),
65
69
  }
66
70
  }
67
71
 
@@ -109,49 +113,55 @@ export function computeGathererCapabilities(
109
113
  const tol = stats.tolerance ?? stats.hardness
110
114
 
111
115
  return {
112
- yield: 200 + str,
116
+ yield: computeGathererYield(str, tier),
113
117
  drain: 2 * Math.max(250_000, 1_250_000 - con * 1250),
114
118
  depth: gathererDepthForTier(tol, tier),
115
119
  }
116
120
  }
117
121
 
118
- export function computeLoaderCapabilities(stats: Record<string, number>): {
119
- mass: number
120
- thrust: number
121
- quantity: number
122
- } {
122
+ export function computeLoaderCapabilities(
123
+ stats: Record<string, number>,
124
+ tier: number
125
+ ): {mass: number; thrust: number; quantity: number} {
123
126
  const insulation = stats.insulation
124
127
  const plasticity = stats.plasticity
125
-
126
128
  return {
127
129
  mass: Math.max(200, 2000 - Math.floor(insulation * 2)),
128
- thrust: 1 + Math.floor((plasticity * plasticity) / 10000),
130
+ thrust: Math.floor(
131
+ ((1 + Math.floor((plasticity * plasticity) / 10000)) *
132
+ moduleTierPct(LOADER_THRUST_TIER_PCT, tier)) /
133
+ 100
134
+ ),
129
135
  quantity: 1,
130
136
  }
131
137
  }
132
138
 
133
- export function computeCrafterCapabilities(stats: Record<string, number>): {
134
- speed: number
135
- drain: number
136
- } {
139
+ export function computeCrafterCapabilities(
140
+ stats: Record<string, number>,
141
+ tier: number
142
+ ): {speed: number; drain: number} {
137
143
  const fin = stats.fineness
138
144
  const con = stats.conductivity
139
-
140
145
  return {
141
- speed: 100 + Math.floor((fin * 4) / 5),
146
+ speed: Math.floor(
147
+ ((100 + Math.floor((fin * 4) / 5)) * moduleTierPct(CRAFTER_SPEED_TIER_PCT, tier)) / 100
148
+ ),
142
149
  drain: Math.max(5000, 30000 - Math.floor((con * 1000) / 33)),
143
150
  }
144
151
  }
145
152
 
146
- export function computeBuilderCapabilities(stats: Record<string, number>): {
147
- speed: number
148
- drain: number
149
- } {
153
+ export function computeBuilderCapabilities(
154
+ stats: Record<string, number>,
155
+ tier: number
156
+ ): {speed: number; drain: number} {
150
157
  const resonance = stats.resonance
151
158
  const fineness = stats.fineness
152
-
153
159
  return {
154
- speed: 100 + Math.floor((resonance * 4) / 5),
160
+ speed: Math.floor(
161
+ ((100 + Math.floor((resonance * 4) / 5)) *
162
+ moduleTierPct(BUILDER_SPEED_TIER_PCT, tier)) /
163
+ 100
164
+ ),
155
165
  drain: Math.max(5000, 30000 - Math.floor((fineness * 1000) / 33)),
156
166
  }
157
167
  }
@@ -225,6 +235,7 @@ import {
225
235
  ITEM_MASS_DRIVER_T1_PACKED,
226
236
  ITEM_SHIP_T1_PACKED,
227
237
  ITEM_WAREHOUSE_T1_PACKED,
238
+ ITEM_WORKSHOP_T1_PACKED,
228
239
  } from '../data/item-ids'
229
240
  import {
230
241
  getModuleCapabilityType,
@@ -270,6 +281,33 @@ export function applyCapacityTier(baseCapacity: number, tier: number): number {
270
281
  return clampUint32(Math.floor(baseCapacity * capacityTierMultiplier(tier)))
271
282
  }
272
283
 
284
+ export const GATHERER_YIELD_TIER_TABLE = [100, 120, 140, 160, 180, 200, 220, 240, 260, 280] as const
285
+
286
+ export const MODULE_MAX_TIER = 10
287
+
288
+ export const ENGINE_THRUST_TIER_PCT = [100, 120, 140, 160, 180, 200, 220, 240, 260, 280] as const
289
+ export const GENERATOR_CAPACITY_TIER_PCT = [
290
+ 100, 110, 120, 130, 140, 150, 160, 170, 180, 190,
291
+ ] as const
292
+ export const GENERATOR_RECHARGE_TIER_PCT = [
293
+ 100, 120, 140, 160, 180, 200, 220, 240, 260, 280,
294
+ ] as const
295
+ export const CRAFTER_SPEED_TIER_PCT = [100, 120, 140, 160, 180, 200, 220, 240, 260, 280] as const
296
+ export const BUILDER_SPEED_TIER_PCT = [100, 120, 140, 160, 180, 200, 220, 240, 260, 280] as const
297
+ export const WARP_RANGE_TIER_PCT = [100, 120, 140, 160, 180, 200, 220, 240, 260, 280] as const
298
+ export const LOADER_THRUST_TIER_PCT = [100, 120, 140, 160, 180, 200, 220, 240, 260, 280] as const
299
+
300
+ export function moduleTierPct(table: readonly number[], tier: number): number {
301
+ const clamped = Math.min(Math.max(tier, 1), MODULE_MAX_TIER)
302
+ return table[clamped - 1]
303
+ }
304
+
305
+ export function computeGathererYield(str: number, tier: number): number {
306
+ const clampedTier = Math.min(Math.max(tier, 1), GATHERER_YIELD_TIER_TABLE.length)
307
+ const pct = GATHERER_YIELD_TIER_TABLE[clampedTier - 1]
308
+ return Math.floor(((200 + str) * pct) / 100)
309
+ }
310
+
273
311
  export function computeBaseCapacity(itemId: number, stats: Record<string, number>): number {
274
312
  let base: number
275
313
  if (isShipHull(itemId)) {
@@ -288,6 +326,9 @@ export function computeBaseCapacity(itemId: number, stats: Record<string, number
288
326
  case ITEM_WAREHOUSE_T1_PACKED:
289
327
  base = computeWarehouseHullCapabilities(stats).capacity
290
328
  break
329
+ case ITEM_WORKSHOP_T1_PACKED:
330
+ base = computeWorkshopHullCapabilities(stats).capacity
331
+ break
291
332
  default:
292
333
  return 0
293
334
  }
@@ -295,11 +336,16 @@ export function computeBaseCapacity(itemId: number, stats: Record<string, number
295
336
  return applyCapacityTier(base, getItem(itemId).tier)
296
337
  }
297
338
 
298
- export function computeWarpCapabilities(stats: Record<string, number>): {
299
- range: number
300
- } {
339
+ export function computeWarpCapabilities(
340
+ stats: Record<string, number>,
341
+ tier: number
342
+ ): {range: number} {
301
343
  const reflectivity = stats.reflectivity
302
- return {range: 100 + reflectivity * 3}
344
+ return {
345
+ range: Math.floor(
346
+ ((100 + reflectivity * 3) * moduleTierPct(WARP_RANGE_TIER_PCT, tier)) / 100
347
+ ),
348
+ }
303
349
  }
304
350
 
305
351
  export function computeWarehouseHullCapabilities(stats: Record<string, number>): {
@@ -314,6 +360,18 @@ export function computeWarehouseHullCapabilities(stats: Record<string, number>):
314
360
  }
315
361
  }
316
362
 
363
+ export function computeWorkshopHullCapabilities(stats: Record<string, number>): {
364
+ hullmass: number
365
+ capacity: number
366
+ } {
367
+ const statSum = (stats.strength ?? 0) + (stats.hardness ?? 0)
368
+ const exponent = statSum / 1998.0
369
+ return {
370
+ hullmass: computeBaseHullmass(ITEM_WORKSHOP_T1_PACKED, stats),
371
+ capacity: Math.floor(5000000 * 6 ** exponent),
372
+ }
373
+ }
374
+
317
375
  export interface GathererLaneEntry {
318
376
  slotIndex: number
319
377
  yield: number
@@ -442,13 +500,13 @@ export function computeEntityCapabilities(
442
500
 
443
501
  if (modType === MODULE_ENGINE) {
444
502
  hasEngine = true
445
- const caps = computeEngineCapabilities(decodedStats)
503
+ const caps = computeEngineCapabilities(decodedStats, item.tier)
446
504
  totalThrust += applySlotMultiplier(caps.thrust, amp)
447
505
  totalEngineThm += computeEffectiveModuleStat(decodedStats.thermal ?? 0)
448
506
  engineCount += 1
449
507
  } else if (modType === MODULE_GENERATOR) {
450
508
  hasGenerator = true
451
- const caps = computeGeneratorCapabilities(decodedStats)
509
+ const caps = computeGeneratorCapabilities(decodedStats, item.tier)
452
510
  totalGenCapacity += applySlotMultiplierUint32(caps.capacity, amp)
453
511
  totalGenRecharge += applySlotMultiplierUint32(caps.recharge, amp)
454
512
  } else if (modType === MODULE_GATHERER) {
@@ -475,7 +533,7 @@ export function computeEntityCapabilities(
475
533
  })
476
534
  } else if (modType === MODULE_LOADER) {
477
535
  hasLoader = true
478
- const caps = computeLoaderCapabilities(decodedStats)
536
+ const caps = computeLoaderCapabilities(decodedStats, item.tier)
479
537
  totalLoaderMass += caps.mass
480
538
  totalLoaderThrust += applySlotMultiplier(caps.thrust, amp)
481
539
  totalLoaderQuantity += caps.quantity
@@ -491,7 +549,7 @@ export function computeEntityCapabilities(
491
549
  totalCargoHoldDrain += caps.drain
492
550
  } else if (modType === MODULE_CRAFTER) {
493
551
  hasCrafter = true
494
- const caps = computeCrafterCapabilities(decodedStats)
552
+ const caps = computeCrafterCapabilities(decodedStats, item.tier)
495
553
  const scaledSpeed = applySlotMultiplier(caps.speed, amp)
496
554
  totalCrafterSpeed += scaledSpeed
497
555
  totalCrafterDrain += caps.drain
@@ -503,7 +561,7 @@ export function computeEntityCapabilities(
503
561
  })
504
562
  } else if (modType === MODULE_BUILDER) {
505
563
  hasBuilder = true
506
- const caps = computeBuilderCapabilities(decodedStats)
564
+ const caps = computeBuilderCapabilities(decodedStats, item.tier)
507
565
  const scaledSpeed = applySlotMultiplier(caps.speed, amp)
508
566
  totalBuilderSpeed += scaledSpeed
509
567
  totalBuilderDrain += caps.drain
@@ -523,7 +581,7 @@ export function computeEntityCapabilities(
523
581
  totalHaulerDrain += caps.drain
524
582
  } else if (modType === MODULE_WARP) {
525
583
  hasWarp = true
526
- const caps = computeWarpCapabilities(decodedStats)
584
+ const caps = computeWarpCapabilities(decodedStats, item.tier)
527
585
  totalWarpRange += applySlotMultiplier(caps.range, amp)
528
586
  } else if (modType === MODULE_LAUNCHER) {
529
587
  hasLauncher = true