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