@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.
- package/lib/scan.d.ts +34 -0
- package/lib/scan.js +136 -0
- package/lib/scan.js.map +1 -0
- package/lib/scan.m.js +129 -0
- package/lib/scan.m.js.map +1 -0
- package/lib/shipload.d.ts +2473 -973
- package/lib/shipload.js +11529 -5211
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +11338 -5162
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +970 -0
- package/lib/testing.js +4013 -0
- package/lib/testing.js.map +1 -0
- package/lib/testing.m.js +4007 -0
- package/lib/testing.m.js.map +1 -0
- package/package.json +20 -2
- package/src/capabilities/craftable.ts +51 -0
- package/src/capabilities/crafting.test.ts +7 -0
- package/src/capabilities/crafting.ts +5 -6
- package/src/capabilities/gathering.test.ts +16 -0
- package/src/capabilities/gathering.ts +35 -18
- package/src/capabilities/index.ts +0 -1
- package/src/capabilities/modules.ts +9 -0
- package/src/capabilities/storage.ts +16 -1
- package/src/contracts/platform.ts +231 -3
- package/src/contracts/server.ts +1021 -481
- 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 +12 -5
- package/src/data/capability-formulas.ts +14 -7
- package/src/data/catalog.ts +0 -5
- package/src/data/colors.ts +14 -47
- package/src/data/entities.json +76 -10
- package/src/data/item-ids.ts +18 -12
- package/src/data/items.json +321 -38
- package/src/data/kind-registry.json +109 -0
- package/src/data/kind-registry.ts +165 -0
- package/src/data/metadata.ts +119 -33
- package/src/data/recipes-runtime.ts +3 -23
- package/src/data/recipes.json +238 -117
- package/src/derivation/build-methods.ts +45 -0
- package/src/derivation/capabilities.test.ts +151 -0
- package/src/derivation/capabilities.ts +512 -0
- package/src/derivation/capability-mappings.ts +9 -12
- package/src/derivation/crafting.ts +23 -24
- package/src/derivation/index.ts +25 -2
- package/src/derivation/recipe-usage.test.ts +78 -0
- package/src/derivation/recipe-usage.ts +141 -0
- package/src/derivation/reserve-regen.ts +34 -0
- package/src/derivation/resources.ts +125 -38
- package/src/derivation/rollups.test.ts +55 -0
- package/src/derivation/rollups.ts +56 -0
- package/src/derivation/stars.test.ts +51 -0
- package/src/derivation/stars.ts +15 -0
- package/src/derivation/stats.ts +6 -6
- package/src/derivation/stratum.ts +17 -20
- package/src/derivation/tiers.ts +40 -7
- package/src/derivation/wormhole.ts +136 -0
- package/src/entities/entity.ts +98 -0
- package/src/entities/gamestate.ts +3 -28
- package/src/entities/makers.ts +124 -134
- 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 +267 -47
- package/src/managers/actions.ts +528 -95
- package/src/managers/base.ts +6 -2
- package/src/managers/construction-types.ts +80 -0
- package/src/managers/construction.ts +412 -0
- package/src/managers/context.ts +20 -1
- package/src/managers/coordinates.ts +14 -0
- package/src/managers/entities.ts +18 -66
- package/src/managers/epochs.ts +40 -0
- package/src/managers/index.ts +17 -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/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 +338 -0
- package/src/nft/description.ts +98 -24
- package/src/nft/index.ts +3 -0
- package/src/planner/index.ts +127 -0
- package/src/planner/planner.test.ts +319 -0
- package/src/resolution/describe-module.ts +18 -13
- package/src/resolution/display-name.ts +38 -10
- package/src/resolution/resolve-item.test.ts +37 -0
- package/src/resolution/resolve-item.ts +55 -24
- package/src/scan/index.ts +180 -0
- package/src/scan/scan-wasm.base64.ts +2 -0
- package/src/scheduling/accessor.ts +68 -22
- package/src/scheduling/availability.ts +108 -0
- package/src/scheduling/cancel.test.ts +348 -0
- package/src/scheduling/cancel.ts +209 -0
- package/src/scheduling/energy.ts +47 -0
- package/src/scheduling/idle-resolve.ts +45 -0
- package/src/scheduling/lane-core.ts +128 -0
- package/src/scheduling/lanes.test.ts +249 -0
- package/src/scheduling/lanes.ts +198 -0
- package/src/scheduling/projection.ts +209 -105
- package/src/scheduling/schedule.ts +241 -104
- package/src/scheduling/task-cargo.ts +46 -0
- package/src/shipload.ts +21 -1
- package/src/subscriptions/manager.ts +229 -142
- package/src/subscriptions/mappers.ts +5 -8
- package/src/subscriptions/types.ts +11 -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 +23 -0
- package/src/travel/route-planner.ts +196 -0
- package/src/travel/travel.ts +200 -112
- package/src/types/capabilities.ts +29 -6
- package/src/types/entity.ts +3 -3
- package/src/types/index.ts +0 -1
- package/src/types.ts +28 -13
- package/src/utils/cargo.ts +27 -0
- package/src/utils/display-name.ts +70 -0
- package/src/utils/system.ts +36 -24
- package/src/capabilities/loading.ts +0 -8
- package/src/entities/container.ts +0 -108
- package/src/entities/ship-deploy.ts +0 -259
- package/src/entities/ship.ts +0 -204
- package/src/entities/warehouse.ts +0 -119
- 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
|
+
}
|