@shipload/sdk 1.0.0-next.4 → 1.0.0-next.41

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 (134) hide show
  1. package/lib/scan.d.ts +34 -0
  2. package/lib/scan.js +136 -0
  3. package/lib/scan.js.map +1 -0
  4. package/lib/scan.m.js +129 -0
  5. package/lib/scan.m.js.map +1 -0
  6. package/lib/shipload.d.ts +2473 -973
  7. package/lib/shipload.js +11529 -5211
  8. package/lib/shipload.js.map +1 -1
  9. package/lib/shipload.m.js +11338 -5162
  10. package/lib/shipload.m.js.map +1 -1
  11. package/lib/testing.d.ts +970 -0
  12. package/lib/testing.js +4013 -0
  13. package/lib/testing.js.map +1 -0
  14. package/lib/testing.m.js +4007 -0
  15. package/lib/testing.m.js.map +1 -0
  16. package/package.json +20 -2
  17. package/src/capabilities/craftable.ts +51 -0
  18. package/src/capabilities/crafting.test.ts +7 -0
  19. package/src/capabilities/crafting.ts +5 -6
  20. package/src/capabilities/gathering.test.ts +16 -0
  21. package/src/capabilities/gathering.ts +35 -18
  22. package/src/capabilities/index.ts +0 -1
  23. package/src/capabilities/modules.ts +9 -0
  24. package/src/capabilities/storage.ts +16 -1
  25. package/src/contracts/platform.ts +231 -3
  26. package/src/contracts/server.ts +1021 -481
  27. package/src/coordinates/address.ts +88 -0
  28. package/src/coordinates/constants.test.ts +15 -0
  29. package/src/coordinates/constants.ts +23 -0
  30. package/src/coordinates/index.ts +15 -0
  31. package/src/coordinates/memo.test.ts +47 -0
  32. package/src/coordinates/memo.ts +20 -0
  33. package/src/coordinates/permutation.ts +77 -0
  34. package/src/coordinates/regions.ts +48 -0
  35. package/src/coordinates/sectors.ts +115 -0
  36. package/src/data/capabilities.ts +12 -5
  37. package/src/data/capability-formulas.ts +14 -7
  38. package/src/data/catalog.ts +0 -5
  39. package/src/data/colors.ts +14 -47
  40. package/src/data/entities.json +76 -10
  41. package/src/data/item-ids.ts +18 -12
  42. package/src/data/items.json +321 -38
  43. package/src/data/kind-registry.json +109 -0
  44. package/src/data/kind-registry.ts +165 -0
  45. package/src/data/metadata.ts +119 -33
  46. package/src/data/recipes-runtime.ts +3 -23
  47. package/src/data/recipes.json +238 -117
  48. package/src/derivation/build-methods.ts +45 -0
  49. package/src/derivation/capabilities.test.ts +151 -0
  50. package/src/derivation/capabilities.ts +512 -0
  51. package/src/derivation/capability-mappings.ts +9 -12
  52. package/src/derivation/crafting.ts +23 -24
  53. package/src/derivation/index.ts +25 -2
  54. package/src/derivation/recipe-usage.test.ts +78 -0
  55. package/src/derivation/recipe-usage.ts +141 -0
  56. package/src/derivation/reserve-regen.ts +34 -0
  57. package/src/derivation/resources.ts +125 -38
  58. package/src/derivation/rollups.test.ts +55 -0
  59. package/src/derivation/rollups.ts +56 -0
  60. package/src/derivation/stars.test.ts +51 -0
  61. package/src/derivation/stars.ts +15 -0
  62. package/src/derivation/stats.ts +6 -6
  63. package/src/derivation/stratum.ts +17 -20
  64. package/src/derivation/tiers.ts +40 -7
  65. package/src/derivation/wormhole.ts +136 -0
  66. package/src/entities/entity.ts +98 -0
  67. package/src/entities/gamestate.ts +3 -28
  68. package/src/entities/makers.ts +124 -134
  69. package/src/entities/slot-multiplier.ts +43 -0
  70. package/src/errors.ts +12 -16
  71. package/src/format.ts +26 -4
  72. package/src/index-module.ts +267 -47
  73. package/src/managers/actions.ts +528 -95
  74. package/src/managers/base.ts +6 -2
  75. package/src/managers/construction-types.ts +80 -0
  76. package/src/managers/construction.ts +412 -0
  77. package/src/managers/context.ts +20 -1
  78. package/src/managers/coordinates.ts +14 -0
  79. package/src/managers/entities.ts +18 -66
  80. package/src/managers/epochs.ts +40 -0
  81. package/src/managers/index.ts +17 -1
  82. package/src/managers/locations.ts +25 -29
  83. package/src/managers/nft.test.ts +14 -0
  84. package/src/managers/nft.ts +70 -0
  85. package/src/managers/plot.ts +122 -0
  86. package/src/nft/atomicassets.abi.json +1342 -0
  87. package/src/nft/atomicassets.ts +237 -0
  88. package/src/nft/atomicdata.ts +130 -0
  89. package/src/nft/buildImmutableData.ts +338 -0
  90. package/src/nft/description.ts +98 -24
  91. package/src/nft/index.ts +3 -0
  92. package/src/planner/index.ts +127 -0
  93. package/src/planner/planner.test.ts +319 -0
  94. package/src/resolution/describe-module.ts +18 -13
  95. package/src/resolution/display-name.ts +38 -10
  96. package/src/resolution/resolve-item.test.ts +37 -0
  97. package/src/resolution/resolve-item.ts +55 -24
  98. package/src/scan/index.ts +180 -0
  99. package/src/scan/scan-wasm.base64.ts +2 -0
  100. package/src/scheduling/accessor.ts +68 -22
  101. package/src/scheduling/availability.ts +108 -0
  102. package/src/scheduling/cancel.test.ts +348 -0
  103. package/src/scheduling/cancel.ts +209 -0
  104. package/src/scheduling/energy.ts +47 -0
  105. package/src/scheduling/idle-resolve.ts +45 -0
  106. package/src/scheduling/lane-core.ts +128 -0
  107. package/src/scheduling/lanes.test.ts +249 -0
  108. package/src/scheduling/lanes.ts +198 -0
  109. package/src/scheduling/projection.ts +209 -105
  110. package/src/scheduling/schedule.ts +241 -104
  111. package/src/scheduling/task-cargo.ts +46 -0
  112. package/src/shipload.ts +21 -1
  113. package/src/subscriptions/manager.ts +229 -142
  114. package/src/subscriptions/mappers.ts +5 -8
  115. package/src/subscriptions/types.ts +11 -3
  116. package/src/testing/catalog-hash.ts +19 -0
  117. package/src/testing/index.ts +2 -0
  118. package/src/testing/projection-parity.ts +167 -0
  119. package/src/travel/reach.ts +23 -0
  120. package/src/travel/route-planner.ts +196 -0
  121. package/src/travel/travel.ts +200 -112
  122. package/src/types/capabilities.ts +29 -6
  123. package/src/types/entity.ts +3 -3
  124. package/src/types/index.ts +0 -1
  125. package/src/types.ts +28 -13
  126. package/src/utils/cargo.ts +27 -0
  127. package/src/utils/display-name.ts +70 -0
  128. package/src/utils/system.ts +36 -24
  129. package/src/capabilities/loading.ts +0 -8
  130. package/src/entities/container.ts +0 -108
  131. package/src/entities/ship-deploy.ts +0 -259
  132. package/src/entities/ship.ts +0 -204
  133. package/src/entities/warehouse.ts +0 -119
  134. package/src/types/entity-traits.ts +0 -69
@@ -0,0 +1,45 @@
1
+ import {EntityClass, getKindMeta, getTemplateMeta} from '../data/kind-registry'
2
+ import {getRecipe} from '../data/recipes-runtime'
3
+ import {getItems} from '../data/catalog'
4
+ import type {Item} from '../types'
5
+
6
+ export type BuildMethod = 'craft+deploy' | 'plot'
7
+
8
+ export function availableBuildMethods(itemId: number): BuildMethod[] {
9
+ const recipe = getRecipe(itemId)
10
+ if (!recipe) return []
11
+
12
+ const template = getTemplateMeta(itemId)
13
+ if (!template) return ['craft+deploy']
14
+
15
+ const kindMeta = getKindMeta(template.kind)
16
+ if (!kindMeta) return ['craft+deploy']
17
+
18
+ if (kindMeta.classification === EntityClass.PlanetaryStructure) {
19
+ return ['craft+deploy', 'plot']
20
+ }
21
+ return ['craft+deploy']
22
+ }
23
+
24
+ export function isBuildable(itemId: number): boolean {
25
+ return availableBuildMethods(itemId).length > 0
26
+ }
27
+
28
+ export function isPlotBuildable(itemId: number): boolean {
29
+ return availableBuildMethods(itemId).includes('plot')
30
+ }
31
+
32
+ export function filterByBuildMethod<T extends {itemId: number}>(
33
+ items: T[],
34
+ method: BuildMethod
35
+ ): T[] {
36
+ return items.filter((i) => availableBuildMethods(i.itemId).includes(method))
37
+ }
38
+
39
+ export function allBuildableItems(): Item[] {
40
+ return getItems().filter((item) => isBuildable(item.id))
41
+ }
42
+
43
+ export function allPlotBuildableItems(): Item[] {
44
+ return getItems().filter((item) => isPlotBuildable(item.id))
45
+ }
@@ -0,0 +1,151 @@
1
+ import {expect, test} from 'bun:test'
2
+ import {
3
+ computeEntityCapabilities,
4
+ computeGathererCapabilities,
5
+ computeCrafterCapabilities,
6
+ computeLoaderCapabilities,
7
+ computeBaseCapacity,
8
+ computeContainerCapabilities,
9
+ } from './capabilities'
10
+ import {applySlotMultiplier, U16_MAX} from '../entities/slot-multiplier'
11
+ import {encodeStats} from './crafting'
12
+ import {
13
+ ITEM_EXTRACTOR_T1_PACKED,
14
+ ITEM_FACTORY_T1_PACKED,
15
+ ITEM_MASS_DRIVER_T1_PACKED,
16
+ ITEM_MASS_CATCHER_T1_PACKED,
17
+ ITEM_GATHERER_T1,
18
+ ITEM_CRAFTER_T1,
19
+ ITEM_LOADER_T1,
20
+ } from '../data/item-ids'
21
+ import type {InstalledModule} from '../entities/slot-multiplier'
22
+ import type {EntitySlot} from '../data/recipes-runtime'
23
+
24
+ function makeGathererStats(strength: number, tolerance: number, conductivity: number): bigint {
25
+ return encodeStats([strength, tolerance, conductivity, 0])
26
+ }
27
+
28
+ function makeCrafterStats(reactivity: number, conductivity: number): bigint {
29
+ return encodeStats([reactivity, conductivity])
30
+ }
31
+
32
+ function makeLoaderStats(insulation: number, plasticity: number): bigint {
33
+ return encodeStats([insulation, plasticity])
34
+ }
35
+
36
+ test('computeBaseCapacity uses container formula for all container-class entities', () => {
37
+ const stats = {strength: 300, hardness: 400, density: 100}
38
+ const expected = computeContainerCapabilities(stats).capacity
39
+ for (const itemId of [
40
+ ITEM_EXTRACTOR_T1_PACKED,
41
+ ITEM_FACTORY_T1_PACKED,
42
+ ITEM_MASS_DRIVER_T1_PACKED,
43
+ ITEM_MASS_CATCHER_T1_PACKED,
44
+ ]) {
45
+ expect(computeBaseCapacity(itemId, stats)).toBe(expected)
46
+ }
47
+ })
48
+
49
+ test('computeEntityCapabilities emits gathererLanes alongside legacy gatherer sum', () => {
50
+ // Two gatherers with distinct stats in separate slots, amp=100 for both
51
+ const gathStats1 = makeGathererStats(300, 200, 400)
52
+ const gathStats2 = makeGathererStats(500, 100, 300)
53
+
54
+ const modules: InstalledModule[] = [
55
+ {slotIndex: 0, itemId: ITEM_GATHERER_T1, stats: gathStats1},
56
+ {slotIndex: 1, itemId: ITEM_GATHERER_T1, stats: gathStats2},
57
+ ]
58
+
59
+ const layout: EntitySlot[] = [
60
+ {type: 'gatherer', outputPct: 100},
61
+ {type: 'gatherer', outputPct: 100},
62
+ ]
63
+
64
+ const result = computeEntityCapabilities({}, ITEM_EXTRACTOR_T1_PACKED, modules, layout)
65
+
66
+ // Lane lists must exist
67
+ expect(result.gathererLanes).toBeDefined()
68
+ expect(result.gathererLanes!.length).toBe(2)
69
+
70
+ // Each lane has the right slotIndex
71
+ expect(result.gathererLanes![0].slotIndex).toBe(0)
72
+ expect(result.gathererLanes![1].slotIndex).toBe(1)
73
+
74
+ // Yields are amp-scaled and distinct
75
+ const caps1 = computeGathererCapabilities({strength: 300, tolerance: 200, conductivity: 400}, 1)
76
+ const caps2 = computeGathererCapabilities({strength: 500, tolerance: 100, conductivity: 300}, 1)
77
+ const expectedYield1 = applySlotMultiplier(caps1.yield, 100)
78
+ const expectedYield2 = applySlotMultiplier(caps2.yield, 100)
79
+ expect(result.gathererLanes![0].yield).toBe(expectedYield1)
80
+ expect(result.gathererLanes![1].yield).toBe(expectedYield2)
81
+ expect(result.gathererLanes![0].yield).not.toBe(result.gathererLanes![1].yield)
82
+
83
+ // Unscaled per-module drain and depth carried verbatim from the compute helper
84
+ expect(result.gathererLanes![0].drain).toBe(caps1.drain)
85
+ expect(result.gathererLanes![1].drain).toBe(caps2.drain)
86
+ expect(result.gathererLanes![0].depth).toBe(caps1.depth)
87
+ expect(result.gathererLanes![1].depth).toBe(caps2.depth)
88
+
89
+ // outputPct reflects the slot amp
90
+ expect(result.gathererLanes![0].outputPct).toBe(100)
91
+ expect(result.gathererLanes![1].outputPct).toBe(100)
92
+
93
+ // Legacy sum still equals sum of both lane yields
94
+ expect(result.gatherer).toBeDefined()
95
+ expect(result.gatherer!.yield).toBe(expectedYield1 + expectedYield2)
96
+ })
97
+
98
+ test('computeEntityCapabilities emits crafterLanes alongside legacy crafter sum', () => {
99
+ const crafterStats = makeCrafterStats(400, 300)
100
+
101
+ const modules: InstalledModule[] = [
102
+ {slotIndex: 0, itemId: ITEM_CRAFTER_T1, stats: crafterStats},
103
+ ]
104
+
105
+ const layout: EntitySlot[] = [{type: 'crafter', outputPct: 120}]
106
+
107
+ const result = computeEntityCapabilities({}, ITEM_EXTRACTOR_T1_PACKED, modules, layout)
108
+
109
+ expect(result.crafterLanes).toBeDefined()
110
+ expect(result.crafterLanes!.length).toBe(1)
111
+ expect(result.crafterLanes![0].slotIndex).toBe(0)
112
+
113
+ const caps = computeCrafterCapabilities({reactivity: 400, conductivity: 300})
114
+ const expectedSpeed = applySlotMultiplier(caps.speed, 120)
115
+ expect(result.crafterLanes![0].speed).toBe(expectedSpeed)
116
+ expect(result.crafterLanes![0].drain).toBe(caps.drain)
117
+ expect(result.crafterLanes![0].outputPct).toBe(120)
118
+
119
+ // Legacy crafter speed equals single-lane speed
120
+ expect(result.crafter).toBeDefined()
121
+ expect(result.crafter!.speed).toBe(expectedSpeed)
122
+ })
123
+
124
+ test('computeEntityCapabilities emits loaderLanes alongside legacy loaders sum', () => {
125
+ const loaderStats = makeLoaderStats(600, 500)
126
+
127
+ const modules: InstalledModule[] = [{slotIndex: 0, itemId: ITEM_LOADER_T1, stats: loaderStats}]
128
+
129
+ const layout: EntitySlot[] = [{type: 'loader', outputPct: 80}]
130
+
131
+ const result = computeEntityCapabilities({}, ITEM_EXTRACTOR_T1_PACKED, modules, layout)
132
+
133
+ expect(result.loaderLanes).toBeDefined()
134
+ expect(result.loaderLanes!.length).toBe(1)
135
+ expect(result.loaderLanes![0].slotIndex).toBe(0)
136
+
137
+ const caps = computeLoaderCapabilities({insulation: 600, plasticity: 500})
138
+ // mass is unscaled (raw); thrust is amp-scaled
139
+ expect(result.loaderLanes![0].mass).toBe(caps.mass)
140
+ expect(result.loaderLanes![0].thrust).toBe(applySlotMultiplier(caps.thrust, 80))
141
+ expect(result.loaderLanes![0].outputPct).toBe(80)
142
+
143
+ // Legacy loaders.mass is total (same as single-lane raw mass here)
144
+ expect(result.loaders).toBeDefined()
145
+ expect(result.loaders!.mass).toBe(caps.mass)
146
+ })
147
+
148
+ test('per-lane amp-scaled stats clamp to UInt16, matching the contract clamp_to_uint16', () => {
149
+ expect(applySlotMultiplier(60000, 200)).toBe(U16_MAX)
150
+ expect(applySlotMultiplier(1000, 150)).toBe(1500)
151
+ })
@@ -0,0 +1,512 @@
1
+ export function computeBaseHullmass(stats: Record<string, number>): number {
2
+ return 100000 - 75 * stats.density
3
+ }
4
+
5
+ export function computeShipHullCapabilities(stats: Record<string, number>): {
6
+ hullmass: number
7
+ capacity: number
8
+ } {
9
+ const statSum = (stats.strength ?? 0) + (stats.hardness ?? 0)
10
+ const exponent = statSum / 1998.0
11
+ return {
12
+ hullmass: computeBaseHullmass(stats),
13
+ capacity: Math.floor(5000000 * 6 ** exponent),
14
+ }
15
+ }
16
+
17
+ export function computeEngineCapabilities(stats: Record<string, number>): {
18
+ thrust: number
19
+ drain: number
20
+ } {
21
+ const vol = stats.volatility
22
+ const thm = stats.thermal
23
+
24
+ return {
25
+ thrust: 400 + Math.floor((vol * 3) / 4),
26
+ drain: 2 * Math.max(30, 50 - Math.floor(thm / 70)),
27
+ }
28
+ }
29
+
30
+ export function computeGeneratorCapabilities(stats: Record<string, number>): {
31
+ capacity: number
32
+ recharge: number
33
+ } {
34
+ const res = stats.resonance
35
+ const ref = stats.reflectivity
36
+
37
+ return {
38
+ capacity: 950 + Math.floor(res / 2),
39
+ recharge: 2 * (1 + Math.floor((ref * 3) / 1000)),
40
+ }
41
+ }
42
+
43
+ export interface GathererDepthParams {
44
+ readonly floor: number
45
+ readonly slope: number
46
+ }
47
+
48
+ export const GATHERER_DEPTH_TABLE: readonly GathererDepthParams[] = [
49
+ {floor: 500, slope: 5},
50
+ {floor: 2000, slope: 11},
51
+ {floor: 7000, slope: 16},
52
+ {floor: 15000, slope: 18},
53
+ {floor: 25000, slope: 19},
54
+ {floor: 35000, slope: 16},
55
+ {floor: 46000, slope: 12},
56
+ {floor: 53500, slope: 10},
57
+ {floor: 60000, slope: 5},
58
+ {floor: 63537, slope: 2},
59
+ ]
60
+
61
+ export const GATHERER_DEPTH_MAX_TIER = 10
62
+
63
+ export function gathererDepthForTier(tol: number, tier: number): number {
64
+ if (tier < 1 || tier > GATHERER_DEPTH_MAX_TIER) {
65
+ throw new Error(`gatherer tier out of range: ${tier}`)
66
+ }
67
+ const p = GATHERER_DEPTH_TABLE[tier - 1]
68
+ return p.floor + tol * p.slope
69
+ }
70
+
71
+ export function computeGathererCapabilities(
72
+ stats: Record<string, number>,
73
+ tier: number
74
+ ): {
75
+ yield: number
76
+ drain: number
77
+ depth: number
78
+ } {
79
+ const str = stats.strength
80
+ const con = stats.conductivity
81
+ const tol = stats.tolerance
82
+
83
+ return {
84
+ yield: 200 + str,
85
+ drain: 2 * Math.max(250, 1250 - Math.floor((con * 25) / 20)),
86
+ depth: gathererDepthForTier(tol, tier),
87
+ }
88
+ }
89
+
90
+ export function computeLoaderCapabilities(stats: Record<string, number>): {
91
+ mass: number
92
+ thrust: number
93
+ quantity: number
94
+ } {
95
+ const insulation = stats.insulation
96
+ const plasticity = stats.plasticity
97
+
98
+ return {
99
+ mass: Math.max(200, 2000 - Math.floor(insulation * 2)),
100
+ thrust: 1 + Math.floor((plasticity * plasticity) / 10000),
101
+ quantity: 1,
102
+ }
103
+ }
104
+
105
+ export function computeCrafterCapabilities(stats: Record<string, number>): {
106
+ speed: number
107
+ drain: number
108
+ } {
109
+ const rea = stats.reactivity
110
+ const con = stats.conductivity
111
+
112
+ return {
113
+ speed: 100 + Math.floor((rea * 4) / 5),
114
+ drain: Math.max(5, 30 - Math.floor(con / 33)),
115
+ }
116
+ }
117
+
118
+ export function computeHaulerCapabilities(stats: Record<string, number>): {
119
+ capacity: number
120
+ efficiency: number
121
+ drain: number
122
+ } {
123
+ const resonance = stats.resonance
124
+ const plasticity = stats.plasticity
125
+ const reflectivity = stats.reflectivity
126
+
127
+ return {
128
+ capacity: Math.max(1, 1 + Math.floor(resonance / 400)),
129
+ efficiency: 2000 + plasticity * 6,
130
+ drain: Math.max(3, 15 - Math.floor(reflectivity / 80)),
131
+ }
132
+ }
133
+
134
+ export function computeLauncherCapabilities(
135
+ stats: {charge_rate: number; velocity: number; drain: number},
136
+ amp = 100
137
+ ): {chargeRate: number; velocity: number; drain: number} {
138
+ return {
139
+ chargeRate: Math.floor((stats.charge_rate * amp) / 100),
140
+ velocity: Math.floor((stats.velocity * amp) / 100),
141
+ drain: stats.drain,
142
+ }
143
+ }
144
+
145
+ export function computeStorageCapabilities(stats: Record<string, number>): {
146
+ capacity: number
147
+ } {
148
+ const strength = stats.strength ?? 0
149
+ const density = stats.density ?? 0
150
+ const hardness = stats.hardness ?? 0
151
+ const cohesion = stats.cohesion ?? 0
152
+
153
+ const statSum = strength + density + hardness + cohesion
154
+ return {capacity: 10_000_000 + Math.floor((statSum * 50_000_000) / 3996)}
155
+ }
156
+
157
+ export function computeBatteryCapabilities(stats: Record<string, number>): {
158
+ capacity: number
159
+ } {
160
+ const volatility = stats.volatility ?? 0
161
+ const thermal = stats.thermal ?? 0
162
+ const plasticity = stats.plasticity ?? 0
163
+ const insulation = stats.insulation ?? 0
164
+
165
+ const statSum = volatility + thermal + plasticity + insulation
166
+ return {capacity: 2_500 + Math.floor((statSum * 7_500) / 3996)}
167
+ }
168
+
169
+ import {
170
+ ITEM_CONTAINER_T1_PACKED,
171
+ ITEM_CONTAINER_T2_PACKED,
172
+ ITEM_EXTRACTOR_T1_PACKED,
173
+ ITEM_FACTORY_T1_PACKED,
174
+ ITEM_MASS_CATCHER_T1_PACKED,
175
+ ITEM_MASS_DRIVER_T1_PACKED,
176
+ ITEM_SHIP_T1_PACKED,
177
+ ITEM_WAREHOUSE_T1_PACKED,
178
+ } from '../data/item-ids'
179
+ import {
180
+ getModuleCapabilityType,
181
+ MODULE_BATTERY,
182
+ MODULE_ENGINE,
183
+ MODULE_GENERATOR,
184
+ MODULE_GATHERER,
185
+ MODULE_LOADER,
186
+ MODULE_STORAGE,
187
+ MODULE_CRAFTER,
188
+ MODULE_HAULER,
189
+ MODULE_WARP,
190
+ MODULE_LAUNCHER,
191
+ } from '../capabilities/modules'
192
+ import {getItem} from '../data/catalog'
193
+ import {decodeCraftedItemStats, decodeStat} from './crafting'
194
+ import {
195
+ applySlotMultiplier,
196
+ applySlotMultiplierUint32,
197
+ clampUint16,
198
+ clampUint32,
199
+ getSlotAmp,
200
+ type InstalledModule,
201
+ } from '../entities/slot-multiplier'
202
+ import type {EntitySlot} from '../data/recipes-runtime'
203
+ import {computeTravelDrain} from '../nft/description'
204
+
205
+ export function computeBaseCapacity(itemId: number, stats: Record<string, number>): number {
206
+ switch (itemId) {
207
+ case ITEM_SHIP_T1_PACKED:
208
+ return computeShipHullCapabilities(stats).capacity
209
+ case ITEM_EXTRACTOR_T1_PACKED:
210
+ case ITEM_FACTORY_T1_PACKED:
211
+ case ITEM_MASS_DRIVER_T1_PACKED:
212
+ case ITEM_MASS_CATCHER_T1_PACKED:
213
+ case ITEM_CONTAINER_T1_PACKED:
214
+ return computeContainerCapabilities(stats).capacity
215
+ case ITEM_WAREHOUSE_T1_PACKED:
216
+ return computeWarehouseHullCapabilities(stats).capacity
217
+ case ITEM_CONTAINER_T2_PACKED:
218
+ return computeContainerT2Capabilities(stats).capacity
219
+ default:
220
+ return 0
221
+ }
222
+ }
223
+
224
+ export function computeWarpCapabilities(stats: Record<string, number>): {
225
+ range: number
226
+ } {
227
+ const resonance = stats.resonance
228
+ return {range: 100 + resonance * 3}
229
+ }
230
+
231
+ export function computeWarehouseHullCapabilities(stats: Record<string, number>): {
232
+ hullmass: number
233
+ capacity: number
234
+ } {
235
+ const statSum = (stats.strength ?? 0) + (stats.hardness ?? 0)
236
+ const exponent = statSum / 1998.0
237
+ return {
238
+ hullmass: computeBaseHullmass(stats),
239
+ capacity: Math.floor(100000000 * 6 ** exponent),
240
+ }
241
+ }
242
+
243
+ export interface GathererLaneEntry {
244
+ slotIndex: number
245
+ yield: number
246
+ drain: number
247
+ depth: number
248
+ outputPct: number
249
+ }
250
+
251
+ export interface CrafterLaneEntry {
252
+ slotIndex: number
253
+ speed: number
254
+ drain: number
255
+ outputPct: number
256
+ }
257
+
258
+ export interface LoaderLaneEntry {
259
+ slotIndex: number
260
+ mass: number
261
+ thrust: number
262
+ outputPct: number
263
+ }
264
+
265
+ export interface ComputedCapabilities {
266
+ hullmass: number
267
+ capacity: number
268
+ engines?: {thrust: number; drain: number}
269
+ generator?: {capacity: number; recharge: number}
270
+ gatherer?: {yield: number; drain: number; depth: number}
271
+ gathererLanes?: GathererLaneEntry[]
272
+ loaders?: {mass: number; thrust: number; quantity: number}
273
+ loaderLanes?: LoaderLaneEntry[]
274
+ crafter?: {speed: number; drain: number}
275
+ crafterLanes?: CrafterLaneEntry[]
276
+ hauler?: {capacity: number; efficiency: number; drain: number}
277
+ warp?: {range: number}
278
+ launcher?: {chargeRate: number; velocity: number; drain: number}
279
+ }
280
+
281
+ export function computeEntityCapabilities(
282
+ stats: Record<string, number>,
283
+ itemId: number,
284
+ modules: InstalledModule[],
285
+ layout: EntitySlot[]
286
+ ): ComputedCapabilities {
287
+ let totalThrust = 0
288
+ let totalEngineThm = 0
289
+ let engineCount = 0
290
+ let hasEngine = false
291
+
292
+ let totalGenCapacity = 0
293
+ let totalGenRecharge = 0
294
+ let hasGenerator = false
295
+
296
+ let totalLoaderMass = 0
297
+ let totalLoaderThrust = 0
298
+ let totalLoaderQuantity = 0
299
+ let hasLoader = false
300
+
301
+ let totalGathYield = 0
302
+ let totalGathDrain = 0
303
+ let maxGathDepth = 0
304
+ let hasGatherer = false
305
+
306
+ let totalStorageCapacity = 0
307
+ const baseCapacity = computeBaseCapacity(itemId, stats)
308
+ let installedModuleMass = 0
309
+
310
+ let totalCrafterSpeed = 0
311
+ let totalCrafterDrain = 0
312
+ let hasCrafter = false
313
+
314
+ let totalHaulerCapacity = 0
315
+ let weightedHaulerEffNum = 0n
316
+ let totalHaulerDrain = 0
317
+ let hasHauler = false
318
+
319
+ let totalWarpRange = 0
320
+ let hasWarp = false
321
+
322
+ let totalLauncherChargeRate = 0
323
+ let totalLauncherVelocity = 0
324
+ let totalLauncherDrain = 0
325
+ let hasLauncher = false
326
+
327
+ let totalBatteryCapacity = 0
328
+
329
+ const gathererLanes: GathererLaneEntry[] = []
330
+ const crafterLanes: CrafterLaneEntry[] = []
331
+ const loaderLanes: LoaderLaneEntry[] = []
332
+
333
+ for (const mod of modules) {
334
+ const item = getItem(mod.itemId)
335
+ const modType = getModuleCapabilityType(mod.itemId)
336
+ const amp = getSlotAmp(layout, mod.slotIndex)
337
+ const decodedStats = decodeCraftedItemStats(mod.itemId, mod.stats)
338
+ installedModuleMass += item.mass
339
+
340
+ if (modType === MODULE_ENGINE) {
341
+ hasEngine = true
342
+ const caps = computeEngineCapabilities(decodedStats)
343
+ totalThrust += applySlotMultiplier(caps.thrust, amp)
344
+ totalEngineThm += decodedStats.thermal ?? 0
345
+ engineCount += 1
346
+ } else if (modType === MODULE_GENERATOR) {
347
+ hasGenerator = true
348
+ const caps = computeGeneratorCapabilities(decodedStats)
349
+ totalGenCapacity += applySlotMultiplier(caps.capacity, amp)
350
+ totalGenRecharge += applySlotMultiplier(caps.recharge, amp)
351
+ } else if (modType === MODULE_GATHERER) {
352
+ hasGatherer = true
353
+ const tier = item.tier
354
+ const caps = computeGathererCapabilities(decodedStats, tier)
355
+ const scaledYield = applySlotMultiplier(caps.yield, amp)
356
+ totalGathYield += scaledYield
357
+ totalGathDrain += caps.drain
358
+ if (caps.depth > maxGathDepth) maxGathDepth = caps.depth
359
+ gathererLanes.push({
360
+ slotIndex: mod.slotIndex,
361
+ yield: scaledYield,
362
+ drain: caps.drain,
363
+ depth: caps.depth,
364
+ outputPct: amp,
365
+ })
366
+ } else if (modType === MODULE_LOADER) {
367
+ hasLoader = true
368
+ const caps = computeLoaderCapabilities(decodedStats)
369
+ totalLoaderMass += caps.mass
370
+ totalLoaderThrust += applySlotMultiplier(caps.thrust, amp)
371
+ totalLoaderQuantity += caps.quantity
372
+ loaderLanes.push({
373
+ slotIndex: mod.slotIndex,
374
+ mass: caps.mass,
375
+ thrust: applySlotMultiplier(caps.thrust, amp),
376
+ outputPct: amp,
377
+ })
378
+ } else if (modType === MODULE_STORAGE) {
379
+ const caps = computeStorageCapabilities(decodedStats)
380
+ totalStorageCapacity += applySlotMultiplierUint32(caps.capacity, amp)
381
+ } else if (modType === MODULE_CRAFTER) {
382
+ hasCrafter = true
383
+ const caps = computeCrafterCapabilities(decodedStats)
384
+ const scaledSpeed = applySlotMultiplier(caps.speed, amp)
385
+ totalCrafterSpeed += scaledSpeed
386
+ totalCrafterDrain += caps.drain
387
+ crafterLanes.push({
388
+ slotIndex: mod.slotIndex,
389
+ speed: scaledSpeed,
390
+ drain: caps.drain,
391
+ outputPct: amp,
392
+ })
393
+ } else if (modType === MODULE_HAULER) {
394
+ hasHauler = true
395
+ const caps = computeHaulerCapabilities(decodedStats)
396
+ const eff = applySlotMultiplier(caps.efficiency, amp)
397
+ totalHaulerCapacity += caps.capacity
398
+ weightedHaulerEffNum += BigInt(eff) * BigInt(caps.capacity)
399
+ totalHaulerDrain += caps.drain
400
+ } else if (modType === MODULE_WARP) {
401
+ hasWarp = true
402
+ const caps = computeWarpCapabilities(decodedStats)
403
+ totalWarpRange += applySlotMultiplier(caps.range, amp)
404
+ } else if (modType === MODULE_LAUNCHER) {
405
+ hasLauncher = true
406
+ const caps = computeLauncherCapabilities(
407
+ {
408
+ charge_rate: decodedStats.charge_rate ?? decodeStat(mod.stats, 0),
409
+ velocity: decodedStats.velocity ?? decodeStat(mod.stats, 1),
410
+ drain: decodedStats.drain ?? decodeStat(mod.stats, 2),
411
+ },
412
+ amp
413
+ )
414
+ totalLauncherChargeRate = clampUint16(totalLauncherChargeRate + caps.chargeRate)
415
+ totalLauncherVelocity = clampUint16(totalLauncherVelocity + caps.velocity)
416
+ totalLauncherDrain = clampUint16(totalLauncherDrain + caps.drain)
417
+ } else if (modType === MODULE_BATTERY) {
418
+ const caps = computeBatteryCapabilities(decodedStats)
419
+ totalBatteryCapacity += applySlotMultiplierUint32(caps.capacity, amp)
420
+ }
421
+ }
422
+
423
+ if (hasGenerator && totalBatteryCapacity > 0) {
424
+ totalGenCapacity += totalBatteryCapacity
425
+ }
426
+
427
+ const result: ComputedCapabilities = {
428
+ hullmass: computeBaseHullmass(stats) + installedModuleMass,
429
+ capacity: clampUint32(baseCapacity + totalStorageCapacity),
430
+ }
431
+
432
+ if (hasEngine) {
433
+ const avgThm = engineCount > 0 ? Math.trunc(totalEngineThm / engineCount) : 0
434
+ result.engines = {thrust: totalThrust, drain: computeTravelDrain(totalThrust, avgThm)}
435
+ }
436
+ if (hasGenerator) {
437
+ result.generator = {
438
+ capacity: clampUint32(totalGenCapacity),
439
+ recharge: clampUint32(totalGenRecharge),
440
+ }
441
+ }
442
+ if (hasGatherer) {
443
+ result.gatherer = {
444
+ yield: clampUint16(totalGathYield),
445
+ drain: totalGathDrain,
446
+ depth: maxGathDepth,
447
+ }
448
+ result.gathererLanes = gathererLanes
449
+ }
450
+ if (hasLoader) {
451
+ result.loaders = {
452
+ mass: totalLoaderMass,
453
+ thrust: clampUint16(totalLoaderThrust),
454
+ quantity: totalLoaderQuantity,
455
+ }
456
+ result.loaderLanes = loaderLanes
457
+ }
458
+ if (hasCrafter) {
459
+ result.crafter = {speed: clampUint16(totalCrafterSpeed), drain: totalCrafterDrain}
460
+ result.crafterLanes = crafterLanes
461
+ }
462
+ if (hasHauler) {
463
+ const efficiency =
464
+ totalHaulerCapacity > 0 ? Number(weightedHaulerEffNum / BigInt(totalHaulerCapacity)) : 0
465
+ result.hauler = {
466
+ capacity: totalHaulerCapacity,
467
+ efficiency: clampUint16(efficiency),
468
+ drain: totalHaulerDrain,
469
+ }
470
+ }
471
+ if (hasWarp) {
472
+ result.warp = {range: totalWarpRange}
473
+ }
474
+ if (hasLauncher) {
475
+ result.launcher = {
476
+ chargeRate: totalLauncherChargeRate,
477
+ velocity: totalLauncherVelocity,
478
+ drain: totalLauncherDrain,
479
+ }
480
+ }
481
+
482
+ return result
483
+ }
484
+
485
+ export function computeContainerCapabilities(stats: Record<string, number>): {
486
+ hullmass: number
487
+ capacity: number
488
+ } {
489
+ const statSum = (stats.strength ?? 0) + (stats.hardness ?? 0)
490
+ const exponent = statSum / 1998.0
491
+ return {
492
+ hullmass: computeBaseHullmass(stats),
493
+ capacity: Math.floor(22000000 * 6 ** exponent),
494
+ }
495
+ }
496
+
497
+ export function computeContainerT2Capabilities(stats: Record<string, number>): {
498
+ hullmass: number
499
+ capacity: number
500
+ } {
501
+ const strength = stats.strength ?? 0
502
+ const density = stats.density ?? 0
503
+ const hardness = stats.hardness ?? 0
504
+
505
+ const hullmass = 70000 - 50 * density
506
+
507
+ const statSum = strength + hardness
508
+ const exponent = statSum / 2947
509
+ const capacity = Math.floor(24000000 * 6 ** exponent)
510
+
511
+ return {hullmass, capacity}
512
+ }