@shipload/sdk 1.0.0-next.37 → 1.0.0-next.38

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipload/sdk",
3
3
  "description": "SDKs for Shipload",
4
- "version": "1.0.0-next.37",
4
+ "version": "1.0.0-next.38",
5
5
  "homepage": "https://github.com/shipload/toolkit/tree/master/packages/sdk",
6
6
  "repository": {
7
7
  "type": "git",
@@ -21,20 +21,22 @@ export const capabilityNames: string[] = [
21
21
  'Crafter',
22
22
  'Launch',
23
23
  'Hauler',
24
- 'Battery',
25
24
  ]
26
25
 
27
26
  export const capabilityAttributes: CapabilityAttribute[] = [
28
27
  {capability: 'Hull', attribute: 'mass', description: 'Total mass of the hull'},
29
- {capability: 'Storage', attribute: 'capacity', description: 'Maximum mass that can be stored'},
30
28
  {
31
29
  capability: 'Storage',
32
- attribute: 'bonus',
33
- description: 'Capacity bonus added by an installed Storage module',
30
+ attribute: 'capacity',
31
+ description: 'Cargo capacity added by hulls and installed Cargo Bay modules',
34
32
  },
35
33
  {capability: 'Movement', attribute: 'thrust', description: 'Propulsion force'},
36
34
  {capability: 'Movement', attribute: 'drain', description: 'Energy consumed per movement'},
37
- {capability: 'Energy', attribute: 'capacity', description: 'Maximum energy storage'},
35
+ {
36
+ capability: 'Energy',
37
+ attribute: 'capacity',
38
+ description: 'Energy capacity from Generators and installed Battery Bank modules',
39
+ },
38
40
  {capability: 'Energy', attribute: 'recharge', description: 'Energy regeneration rate'},
39
41
  {capability: 'Loader', attribute: 'mass', description: 'Weight of the loader unit itself'},
40
42
  {capability: 'Loader', attribute: 'thrust', description: 'Loading speed/force'},
@@ -72,11 +74,6 @@ export const capabilityAttributes: CapabilityAttribute[] = [
72
74
  attribute: 'drain',
73
75
  description: 'Energy consumed per target during haul-beam operation',
74
76
  },
75
- {
76
- capability: 'Battery',
77
- attribute: 'bonus',
78
- description: 'Energy capacity bonus added by an installed Battery module',
79
- },
80
77
  ]
81
78
 
82
79
  const invertedAttributes = new Set(['drain', 'mass'])
@@ -48,10 +48,10 @@ export const SLOT_FORMULAS: Record<SlotConsumerKind, Record<number, SlotConsumer
48
48
  1: {capability: 'Crafter', attribute: 'drain'},
49
49
  },
50
50
  storage: {
51
- 0: {capability: 'Storage', attribute: 'bonus'},
52
- 1: {capability: 'Storage', attribute: 'bonus'},
53
- 2: {capability: 'Storage', attribute: 'bonus'},
54
- 3: {capability: 'Storage', attribute: 'bonus'},
51
+ 0: {capability: 'Storage', attribute: 'capacity'},
52
+ 1: {capability: 'Storage', attribute: 'capacity'},
53
+ 2: {capability: 'Storage', attribute: 'capacity'},
54
+ 3: {capability: 'Storage', attribute: 'capacity'},
55
55
  },
56
56
  hauler: {
57
57
  0: {capability: 'Hauler', attribute: 'capacity'},
@@ -62,10 +62,10 @@ export const SLOT_FORMULAS: Record<SlotConsumerKind, Record<number, SlotConsumer
62
62
  0: {capability: 'Warp', attribute: 'range'},
63
63
  },
64
64
  battery: {
65
- 0: {capability: 'Battery', attribute: 'bonus'},
66
- 1: {capability: 'Battery', attribute: 'bonus'},
67
- 2: {capability: 'Battery', attribute: 'bonus'},
68
- 3: {capability: 'Battery', attribute: 'bonus'},
65
+ 0: {capability: 'Energy', attribute: 'capacity'},
66
+ 1: {capability: 'Energy', attribute: 'capacity'},
67
+ 2: {capability: 'Energy', attribute: 'capacity'},
68
+ 3: {capability: 'Energy', attribute: 'capacity'},
69
69
  },
70
70
  'ship-t1': ENTITY_HULL_SLOTS,
71
71
  'container-t1': ENTITY_HULL_SLOTS,
@@ -197,8 +197,8 @@ export const itemMetadata: Record<number, ItemMetadata> = {
197
197
  color: '#B877FF',
198
198
  },
199
199
  10105: {
200
- name: 'Storage',
201
- description: 'Expands cargo capacity based on hull material quality.',
200
+ name: 'Cargo Bay',
201
+ description: 'Expanded cargo storage with reinforced internal holds.',
202
202
  color: '#8B7355',
203
203
  },
204
204
  10106: {
@@ -214,9 +214,8 @@ export const itemMetadata: Record<number, ItemMetadata> = {
214
214
  color: '#9be4ff',
215
215
  },
216
216
  10108: {
217
- name: 'Battery',
218
- description:
219
- 'Extends energy capacity. Stores additional charge produced by generators, letting builds chain more high-drain actions between recharges.',
217
+ name: 'Battery Bank',
218
+ description: 'Stores additional charge produced by generators.',
220
219
  color: '#4ADBFF',
221
220
  },
222
221
 
@@ -450,11 +450,11 @@
450
450
  "outputMass": 960000,
451
451
  "inputs": [
452
452
  {
453
- "itemId": 10008,
453
+ "itemId": 10009,
454
454
  "quantity": 300
455
455
  },
456
456
  {
457
- "itemId": 10009,
457
+ "itemId": 10006,
458
458
  "quantity": 300
459
459
  }
460
460
  ],
@@ -462,7 +462,7 @@
462
462
  {
463
463
  "sources": [
464
464
  {
465
- "inputIndex": 1,
465
+ "inputIndex": 0,
466
466
  "statIndex": 0
467
467
  }
468
468
  ]
@@ -470,8 +470,8 @@
470
470
  {
471
471
  "sources": [
472
472
  {
473
- "inputIndex": 0,
474
- "statIndex": 1
473
+ "inputIndex": 1,
474
+ "statIndex": 0
475
475
  }
476
476
  ]
477
477
  }
@@ -20,8 +20,8 @@ function makeGathererStats(strength: number, tolerance: number, conductivity: nu
20
20
  return encodeStats([strength, tolerance, conductivity, 0])
21
21
  }
22
22
 
23
- function makeCrafterStats(reactivity: number, fineness: number): bigint {
24
- return encodeStats([reactivity, fineness])
23
+ function makeCrafterStats(reactivity: number, conductivity: number): bigint {
24
+ return encodeStats([reactivity, conductivity])
25
25
  }
26
26
 
27
27
  function makeLoaderStats(insulation: number, plasticity: number): bigint {
@@ -92,7 +92,7 @@ test('computeEntityCapabilities emits crafterLanes alongside legacy crafter sum'
92
92
  expect(result.crafterLanes!.length).toBe(1)
93
93
  expect(result.crafterLanes![0].slotIndex).toBe(0)
94
94
 
95
- const caps = computeCrafterCapabilities({reactivity: 400, fineness: 300})
95
+ const caps = computeCrafterCapabilities({reactivity: 400, conductivity: 300})
96
96
  const expectedSpeed = applySlotMultiplier(caps.speed, 120)
97
97
  expect(result.crafterLanes![0].speed).toBe(expectedSpeed)
98
98
  expect(result.crafterLanes![0].drain).toBe(caps.drain)
@@ -107,11 +107,11 @@ export function computeCrafterCapabilities(stats: Record<string, number>): {
107
107
  drain: number
108
108
  } {
109
109
  const rea = stats.reactivity
110
- const fin = stats.fineness
110
+ const con = stats.conductivity
111
111
 
112
112
  return {
113
113
  speed: 100 + Math.floor((rea * 4) / 5),
114
- drain: Math.max(5, 30 - Math.floor(fin / 33)),
114
+ drain: Math.max(5, 30 - Math.floor(con / 33)),
115
115
  }
116
116
  }
117
117
 
@@ -131,23 +131,28 @@ export function computeHaulerCapabilities(stats: Record<string, number>): {
131
131
  }
132
132
  }
133
133
 
134
- export function computeStorageCapabilities(
135
- stats: Record<string, number>,
136
- baseCapacity: number
137
- ): {
138
- capacityBonus: number
134
+ export function computeStorageCapabilities(stats: Record<string, number>): {
135
+ capacity: number
139
136
  } {
140
- const strength = stats.strength
141
- const density = stats.density
142
- const hardness = stats.hardness
143
- const cohesion = stats.cohesion
137
+ const strength = stats.strength ?? 0
138
+ const density = stats.density ?? 0
139
+ const hardness = stats.hardness ?? 0
140
+ const cohesion = stats.cohesion ?? 0
144
141
 
145
142
  const statSum = strength + density + hardness + cohesion
146
- const capacityBonus = Math.floor(
147
- (baseCapacity * (10 + Math.floor((statSum * 10) / 2997))) / 100
148
- )
143
+ return {capacity: 10_000_000 + Math.floor((statSum * 50_000_000) / 3996)}
144
+ }
145
+
146
+ export function computeBatteryCapabilities(stats: Record<string, number>): {
147
+ capacity: number
148
+ } {
149
+ const volatility = stats.volatility ?? 0
150
+ const thermal = stats.thermal ?? 0
151
+ const plasticity = stats.plasticity ?? 0
152
+ const insulation = stats.insulation ?? 0
149
153
 
150
- return {capacityBonus}
154
+ const statSum = volatility + thermal + plasticity + insulation
155
+ return {capacity: 2_500 + Math.floor((statSum * 7_500) / 3996)}
151
156
  }
152
157
 
153
158
  import {
@@ -174,6 +179,7 @@ import {getItem} from '../data/catalog'
174
179
  import {decodeCraftedItemStats} from './crafting'
175
180
  import {
176
181
  applySlotMultiplier,
182
+ applySlotMultiplierUint32,
177
183
  clampUint16,
178
184
  clampUint32,
179
185
  getSlotAmp,
@@ -278,7 +284,7 @@ export function computeEntityCapabilities(
278
284
  let maxGathDepth = 0
279
285
  let hasGatherer = false
280
286
 
281
- let totalStorageBonus = 0
287
+ let totalStorageCapacity = 0
282
288
  const baseCapacity = computeBaseCapacity(itemId, stats)
283
289
  let installedModuleMass = 0
284
290
 
@@ -294,8 +300,7 @@ export function computeEntityCapabilities(
294
300
  let totalWarpRange = 0
295
301
  let hasWarp = false
296
302
 
297
- let totalBatteryStatSum = 0
298
- let batteryCount = 0
303
+ let totalBatteryCapacity = 0
299
304
 
300
305
  const gathererLanes: GathererLaneEntry[] = []
301
306
  const crafterLanes: CrafterLaneEntry[] = []
@@ -346,8 +351,8 @@ export function computeEntityCapabilities(
346
351
  outputPct: amp,
347
352
  })
348
353
  } else if (modType === MODULE_STORAGE) {
349
- const caps = computeStorageCapabilities(decodedStats, baseCapacity)
350
- totalStorageBonus += caps.capacityBonus
354
+ const caps = computeStorageCapabilities(decodedStats)
355
+ totalStorageCapacity += applySlotMultiplierUint32(caps.capacity, amp)
351
356
  } else if (modType === MODULE_CRAFTER) {
352
357
  hasCrafter = true
353
358
  const caps = computeCrafterCapabilities(decodedStats)
@@ -372,24 +377,18 @@ export function computeEntityCapabilities(
372
377
  const caps = computeWarpCapabilities(decodedStats)
373
378
  totalWarpRange += applySlotMultiplier(caps.range, amp)
374
379
  } else if (modType === MODULE_BATTERY) {
375
- batteryCount++
376
- const vol = decodedStats.volatility ?? 0
377
- const thm = decodedStats.thermal ?? 0
378
- const pla = decodedStats.plasticity ?? 0
379
- const ins = decodedStats.insulation ?? 0
380
- totalBatteryStatSum += vol + thm + pla + ins
380
+ const caps = computeBatteryCapabilities(decodedStats)
381
+ totalBatteryCapacity += applySlotMultiplierUint32(caps.capacity, amp)
381
382
  }
382
383
  }
383
384
 
384
- if (hasGenerator && batteryCount > 0) {
385
- const genCapBase = totalGenCapacity
386
- const bonusPctNum = 10 * batteryCount + Math.floor((totalBatteryStatSum * 10) / 2997)
387
- totalGenCapacity += Math.floor((genCapBase * bonusPctNum) / 100)
385
+ if (hasGenerator && totalBatteryCapacity > 0) {
386
+ totalGenCapacity += totalBatteryCapacity
388
387
  }
389
388
 
390
389
  const result: ComputedCapabilities = {
391
390
  hullmass: computeBaseHullmass(stats) + installedModuleMass,
392
- capacity: baseCapacity + totalStorageBonus,
391
+ capacity: clampUint32(baseCapacity + totalStorageCapacity),
393
392
  }
394
393
 
395
394
  if (hasEngine) {
@@ -34,6 +34,10 @@ export function applySlotMultiplier(value: number, outputPct: number): number {
34
34
  return clampUint16(Math.floor((value * outputPct) / 100))
35
35
  }
36
36
 
37
+ export function applySlotMultiplierUint32(value: number, outputPct: number): number {
38
+ return clampUint32(Math.floor((value * outputPct) / 100))
39
+ }
40
+
37
41
  export function getSlotAmp(layout: EntitySlot[], slotIndex: number): number {
38
42
  return layout[slotIndex]?.outputPct ?? 100
39
43
  }
@@ -191,7 +191,7 @@ export type {
191
191
  HasScheduleAndLocation,
192
192
  } from './travel/travel'
193
193
 
194
- export {planRoute, sdkSystemGraph} from './travel/route-planner'
194
+ export {planRoute, sdkSystemGraph, MAX_LEGS} from './travel/route-planner'
195
195
  export type {
196
196
  Coord,
197
197
  Neighbor,
@@ -379,6 +379,7 @@ export {
379
379
  computeCrafterCapabilities,
380
380
  computeWarehouseHullCapabilities,
381
381
  computeStorageCapabilities,
382
+ computeBatteryCapabilities,
382
383
  computeContainerCapabilities,
383
384
  computeContainerT2Capabilities,
384
385
  computeWarpCapabilities,
@@ -2,6 +2,7 @@ import {Serializer} from '@wharfkit/antelope'
2
2
  import {getItem} from '../data/catalog'
3
3
  import {
4
4
  getModuleCapabilityType,
5
+ MODULE_BATTERY,
5
6
  MODULE_CRAFTER,
6
7
  MODULE_ENGINE,
7
8
  MODULE_GATHERER,
@@ -26,6 +27,8 @@ import {
26
27
  computeGathererYield,
27
28
  computeGeneratorCap,
28
29
  computeGeneratorRech,
30
+ computeCargoBayCapacity,
31
+ computeBatteryBankCapacity,
29
32
  computeHaulerCapacity,
30
33
  computeHaulerDrain,
31
34
  computeHaulerEfficiency,
@@ -224,11 +227,11 @@ export function buildModuleImmutable(
224
227
  }
225
228
  case MODULE_CRAFTER: {
226
229
  const rea = decodeStat(stats, 0)
227
- const fin = decodeStat(stats, 1)
230
+ const con = decodeStat(stats, 1)
228
231
  base.push({first: 'reactivity', second: ['uint16', rea]})
229
- base.push({first: 'fineness', second: ['uint16', fin]})
232
+ base.push({first: 'conductivity', second: ['uint16', con]})
230
233
  base.push({first: 'speed', second: ['uint16', computeCrafterSpeed(rea)]})
231
- base.push({first: 'drain', second: ['uint16', computeCrafterDrain(fin)]})
234
+ base.push({first: 'drain', second: ['uint16', computeCrafterDrain(con)]})
232
235
  break
233
236
  }
234
237
  case MODULE_STORAGE: {
@@ -236,14 +239,28 @@ export function buildModuleImmutable(
236
239
  const den = decodeStat(stats, 1)
237
240
  const hrd = decodeStat(stats, 2)
238
241
  const com = decodeStat(stats, 3)
239
- const sum = str + den + hrd + com
240
242
  base.push({first: 'strength', second: ['uint16', str]})
241
243
  base.push({first: 'density', second: ['uint16', den]})
242
244
  base.push({first: 'hardness', second: ['uint16', hrd]})
243
245
  base.push({first: 'cohesion', second: ['uint16', com]})
244
246
  base.push({
245
- first: 'capacity_bonus_pct',
246
- second: ['uint16', 10 + Math.floor((sum * 10) / 2997)],
247
+ first: 'capacity',
248
+ second: ['uint32', computeCargoBayCapacity(str, den, hrd, com)],
249
+ })
250
+ break
251
+ }
252
+ case MODULE_BATTERY: {
253
+ const vol = decodeStat(stats, 0)
254
+ const thm = decodeStat(stats, 1)
255
+ const pla = decodeStat(stats, 2)
256
+ const ins = decodeStat(stats, 3)
257
+ base.push({first: 'volatility', second: ['uint16', vol]})
258
+ base.push({first: 'thermal', second: ['uint16', thm]})
259
+ base.push({first: 'plasticity', second: ['uint16', pla]})
260
+ base.push({first: 'insulation', second: ['uint16', ins]})
261
+ base.push({
262
+ first: 'capacity',
263
+ second: ['uint32', computeBatteryBankCapacity(vol, thm, pla, ins)],
247
264
  })
248
265
  break
249
266
  }
@@ -4,6 +4,7 @@ import {
4
4
  MODULE_ENGINE,
5
5
  MODULE_GATHERER,
6
6
  MODULE_GENERATOR,
7
+ MODULE_BATTERY,
7
8
  MODULE_HAULER,
8
9
  MODULE_LOADER,
9
10
  MODULE_STORAGE,
@@ -19,6 +20,7 @@ import {
19
20
  ITEM_GATHERER_T1,
20
21
  ITEM_GENERATOR_T1,
21
22
  ITEM_HAULER_T1,
23
+ ITEM_BATTERY_T1,
22
24
  ITEM_LOADER_T1,
23
25
  ITEM_SHIP_T1_PACKED,
24
26
  ITEM_STORAGE_T1,
@@ -75,6 +77,18 @@ export const computeHaulerCapacity = (fin: number): number => Math.max(1, 1 + id
75
77
  export const computeHaulerEfficiency = (con: number): number => 2000 + con * 6
76
78
  export const computeHaulerDrain = (com: number): number => Math.max(3, 15 - idiv(com, 80))
77
79
  export const computeWarpRange = (stat: number): number => 100 + stat * 3
80
+ export const computeCargoBayCapacity = (
81
+ strength: number,
82
+ density: number,
83
+ hardness: number,
84
+ cohesion: number
85
+ ): number => 10_000_000 + idiv((strength + density + hardness + cohesion) * 50_000_000, 3996)
86
+ export const computeBatteryBankCapacity = (
87
+ volatility: number,
88
+ thermal: number,
89
+ plasticity: number,
90
+ insulation: number
91
+ ): number => 2_500 + idiv((volatility + thermal + plasticity + insulation) * 7_500, 3996)
78
92
 
79
93
  export function entityDisplayName(itemId: number): string {
80
94
  switch (itemId) {
@@ -108,11 +122,13 @@ export function moduleDisplayName(itemId: number): string {
108
122
  case ITEM_CRAFTER_T1:
109
123
  return 'Crafter'
110
124
  case ITEM_STORAGE_T1:
111
- return 'Storage'
125
+ return 'Cargo Bay'
112
126
  case ITEM_HAULER_T1:
113
127
  return 'Hauler'
114
128
  case ITEM_WARP_T1:
115
129
  return 'Warp'
130
+ case ITEM_BATTERY_T1:
131
+ return 'Battery Bank'
116
132
  default:
117
133
  return 'Module'
118
134
  }
@@ -160,17 +176,16 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
160
176
  }
161
177
  case MODULE_CRAFTER: {
162
178
  const rea = decodeStat(stats, 0)
163
- const com = decodeStat(stats, 1)
164
- out += ` Speed ${computeCrafterSpeed(rea)} Drain ${computeCrafterDrain(com)}`
179
+ const con = decodeStat(stats, 1)
180
+ out += ` Speed ${computeCrafterSpeed(rea)} Drain ${computeCrafterDrain(con)}`
165
181
  break
166
182
  }
167
183
  case MODULE_STORAGE: {
168
184
  const str = decodeStat(stats, 0)
169
- const fin = decodeStat(stats, 2)
170
- const sat = decodeStat(stats, 3)
171
- const sum = str + fin + sat
172
- const pct = 10 + idiv(sum * 10, 2997)
173
- out += ` +${pct}% capacity`
185
+ const den = decodeStat(stats, 1)
186
+ const hrd = decodeStat(stats, 2)
187
+ const com = decodeStat(stats, 3)
188
+ out += ` Cargo Capacity ${computeCargoBayCapacity(str, den, hrd, com)}`
174
189
  break
175
190
  }
176
191
  case MODULE_HAULER: {
@@ -185,6 +200,14 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
185
200
  out += ` Range ${computeWarpRange(stat)}`
186
201
  break
187
202
  }
203
+ case MODULE_BATTERY: {
204
+ const vol = decodeStat(stats, 0)
205
+ const thm = decodeStat(stats, 1)
206
+ const pla = decodeStat(stats, 2)
207
+ const ins = decodeStat(stats, 3)
208
+ out += ` Energy Capacity ${computeBatteryBankCapacity(vol, thm, pla, ins)}`
209
+ break
210
+ }
188
211
  }
189
212
  return out
190
213
  }
@@ -81,9 +81,15 @@ const TEMPLATES: Record<string, TemplateSpec> = {
81
81
  },
82
82
  storage: {
83
83
  id: 'module.storage.description',
84
- template: 'boosts cargo capacity by {bonus}%',
85
- params: [['bonus', 'Capacity Bonus']],
86
- highlightKeys: ['bonus'],
84
+ template: 'adds {capacity} cargo capacity',
85
+ params: [['capacity', 'Cargo Capacity']],
86
+ highlightKeys: ['capacity'],
87
+ },
88
+ energy: {
89
+ id: 'module.energy-capacity.description',
90
+ template: 'adds {capacity} energy capacity',
91
+ params: [['capacity', 'Energy Capacity']],
92
+ highlightKeys: ['capacity'],
87
93
  },
88
94
  hauler: {
89
95
  id: 'module.hauler.description',
@@ -124,8 +130,10 @@ export function describeModuleForItem(resolved: ResolvedItem): ModuleDescription
124
130
  }
125
131
 
126
132
  export function describeModuleForSlot(slot: ResolvedModuleSlot): ModuleDescription | null {
127
- if (!slot.installed || !slot.name || !slot.attributes) return null
128
- return describeModule({capability: slot.name, attributes: slot.attributes})
133
+ if (!slot.installed || !slot.attributes) return null
134
+ const capability = slot.capability ?? slot.name
135
+ if (!capability) return null
136
+ return describeModule({capability, attributes: slot.attributes})
129
137
  }
130
138
 
131
139
  export function renderDescription(
@@ -9,6 +9,7 @@ import {
9
9
  isModuleItem,
10
10
  MODULE_CRAFTER,
11
11
  MODULE_ENGINE,
12
+ MODULE_BATTERY,
12
13
  MODULE_GATHERER,
13
14
  MODULE_GENERATOR,
14
15
  MODULE_HAULER,
@@ -19,6 +20,7 @@ import {decodeCraftedItemStats, decodeStat} from '../derivation/crafting'
19
20
  import {getStatDefinitions} from '../derivation/stats'
20
21
  import {
21
22
  computeCrafterCapabilities,
23
+ computeBatteryCapabilities,
22
24
  computeEngineCapabilities,
23
25
  computeGathererCapabilities,
24
26
  computeGeneratorCapabilities,
@@ -28,7 +30,9 @@ import {
28
30
  computeWarehouseHullCapabilities,
29
31
  computeContainerCapabilities,
30
32
  computeContainerT2Capabilities,
33
+ computeStorageCapabilities,
31
34
  } from '../derivation/capabilities'
35
+ import {applySlotMultiplierUint32} from '../entities/slot-multiplier'
32
36
  import {categoryColors, componentIcon, itemAbbreviations, moduleIcon} from '../data/colors'
33
37
  import type {ServerContract} from '../contracts'
34
38
  import {
@@ -58,6 +62,7 @@ export type ResolvedItemType = 'resource' | 'component' | 'module' | 'entity'
58
62
 
59
63
  export interface ResolvedModuleSlot {
60
64
  name?: string
65
+ capability?: string
61
66
  installed: boolean
62
67
  attributes?: {label: string; value: number}[]
63
68
  }
@@ -152,7 +157,8 @@ function resolveComponent(id: number, stats?: UInt64Type): ResolvedItem {
152
157
  function computeCapabilityGroup(
153
158
  moduleType: number,
154
159
  stats: Record<string, number>,
155
- tier: number
160
+ tier: number,
161
+ outputPct = 100
156
162
  ): ResolvedAttributeGroup | undefined {
157
163
  switch (moduleType) {
158
164
  case MODULE_ENGINE: {
@@ -219,13 +225,28 @@ function computeCapabilityGroup(
219
225
  }
220
226
  }
221
227
  case MODULE_STORAGE: {
222
- const str = stats.strength
223
- const den = stats.density
224
- const hrd = stats.hardness
225
- const com = stats.cohesion
226
- const statSum = str + den + hrd + com
227
- const pct = 10 + Math.floor((statSum * 10) / 2997)
228
- return {capability: 'Storage', attributes: [{label: 'Capacity Bonus', value: pct}]}
228
+ const caps = computeStorageCapabilities(stats)
229
+ return {
230
+ capability: 'Storage',
231
+ attributes: [
232
+ {
233
+ label: 'Cargo Capacity',
234
+ value: applySlotMultiplierUint32(caps.capacity, outputPct),
235
+ },
236
+ ],
237
+ }
238
+ }
239
+ case MODULE_BATTERY: {
240
+ const caps = computeBatteryCapabilities(stats)
241
+ return {
242
+ capability: 'Energy',
243
+ attributes: [
244
+ {
245
+ label: 'Energy Capacity',
246
+ value: applySlotMultiplierUint32(caps.capacity, outputPct),
247
+ },
248
+ ],
249
+ }
229
250
  }
230
251
  default:
231
252
  return undefined
@@ -321,9 +342,10 @@ function resolveEntity(
321
342
  } catch {
322
343
  modName = itemMetadata[modItemId]?.name ?? 'Module'
323
344
  }
324
- const group = computeCapabilityGroup(modType, decodedStats, modTier)
345
+ const group = computeCapabilityGroup(modType, decodedStats, modTier, slot.outputPct)
325
346
  return {
326
347
  name: modName,
348
+ capability: group?.capability,
327
349
  installed: true,
328
350
  attributes: group?.attributes,
329
351
  }
@@ -32,6 +32,7 @@ export interface RouteFailure {
32
32
  reason: RouteFailureReason
33
33
  furthest?: Coord
34
34
  legsNeeded?: number
35
+ partialWaypoints?: Coord[]
35
36
  }
36
37
 
37
38
  export type RouteResult = RoutePlan | RouteFailure
@@ -50,11 +51,13 @@ const key = (c: Coord): string => `${c.x},${c.y}`
50
51
  const sameCoord = (a: Coord, b: Coord): boolean => a.x === b.x && a.y === b.y
51
52
  const dist = (a: Coord, b: Coord): number => Math.hypot(a.x - b.x, a.y - b.y)
52
53
 
54
+ export const MAX_LEGS = 12
55
+
53
56
  export function planRoute(params: PlanRouteParams): RouteResult {
54
57
  const {origin, dest, perLegReach, graph} = params
55
58
  const corridorSlack = params.corridorSlack ?? perLegReach
56
59
  const nodeBudget = params.nodeBudget ?? 5000
57
- const maxLegs = params.maxLegs ?? 12
60
+ const maxLegs = params.maxLegs ?? MAX_LEGS
58
61
 
59
62
  if (!graph.hasSystem(dest)) {
60
63
  return {ok: false, reason: 'empty-destination'}
@@ -122,9 +125,32 @@ export function planRoute(params: PlanRouteParams): RouteResult {
122
125
  }
123
126
 
124
127
  if (cappedByMaxLegs) {
125
- return {ok: false, reason: 'max-legs', furthest}
128
+ return {
129
+ ok: false,
130
+ reason: 'max-legs',
131
+ furthest,
132
+ partialWaypoints: reconstructWaypoints(cameFrom, origin, furthest),
133
+ }
134
+ }
135
+ return {
136
+ ok: false,
137
+ reason: 'no-path',
138
+ furthest,
139
+ partialWaypoints: reconstructWaypoints(cameFrom, origin, furthest),
140
+ }
141
+ }
142
+
143
+ function reconstructWaypoints(cameFrom: Map<string, Coord>, origin: Coord, target: Coord): Coord[] {
144
+ if (sameCoord(target, origin)) return []
145
+ const path: Coord[] = [target]
146
+ let cur = target
147
+ while (!sameCoord(cur, origin)) {
148
+ const prev = cameFrom.get(key(cur))
149
+ if (!prev) break
150
+ path.unshift(prev)
151
+ cur = prev
126
152
  }
127
- return {ok: false, reason: 'no-path', furthest}
153
+ return path.slice(1)
128
154
  }
129
155
 
130
156
  function reconstruct(cameFrom: Map<string, Coord>, origin: Coord, dest: Coord): RoutePlan {