@shipload/sdk 1.0.0-next.11 → 1.0.0-next.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipload/sdk",
3
3
  "description": "SDKs for Shipload",
4
- "version": "1.0.0-next.11",
4
+ "version": "1.0.0-next.12",
5
5
  "homepage": "https://github.com/shipload/toolkit/tree/master/packages/sdk",
6
6
  "repository": {
7
7
  "type": "git",
@@ -0,0 +1,78 @@
1
+ {
2
+ "kinds": [
3
+ {
4
+ "kind": "ship",
5
+ "classification": "OrbitalVessel",
6
+ "capabilityFlags": 19,
7
+ "zCoord": 800,
8
+ "defaultLabel": "Ship"
9
+ },
10
+ {
11
+ "kind": "warehouse",
12
+ "classification": "PlanetaryStructure",
13
+ "capabilityFlags": 20,
14
+ "zCoord": 0,
15
+ "defaultLabel": "Warehouse"
16
+ },
17
+ {
18
+ "kind": "extractor",
19
+ "classification": "PlanetaryStructure",
20
+ "capabilityFlags": 20,
21
+ "zCoord": 0,
22
+ "defaultLabel": "Extractor"
23
+ },
24
+ {
25
+ "kind": "factory",
26
+ "classification": "PlanetaryStructure",
27
+ "capabilityFlags": 20,
28
+ "zCoord": 0,
29
+ "defaultLabel": "Factory"
30
+ },
31
+ {
32
+ "kind": "container",
33
+ "classification": "OrbitalVessel",
34
+ "capabilityFlags": 3,
35
+ "zCoord": 300,
36
+ "defaultLabel": "Container"
37
+ },
38
+ {
39
+ "kind": "nexus",
40
+ "classification": "OrbitalVessel",
41
+ "capabilityFlags": 0,
42
+ "zCoord": 800,
43
+ "defaultLabel": "Nexus"
44
+ }
45
+ ],
46
+ "templates": [
47
+ {
48
+ "itemId": 10201,
49
+ "kind": "ship",
50
+ "displayLabel": ""
51
+ },
52
+ {
53
+ "itemId": 10202,
54
+ "kind": "warehouse",
55
+ "displayLabel": ""
56
+ },
57
+ {
58
+ "itemId": 10203,
59
+ "kind": "extractor",
60
+ "displayLabel": ""
61
+ },
62
+ {
63
+ "itemId": 10204,
64
+ "kind": "factory",
65
+ "displayLabel": ""
66
+ },
67
+ {
68
+ "itemId": 10200,
69
+ "kind": "container",
70
+ "displayLabel": ""
71
+ },
72
+ {
73
+ "itemId": 20200,
74
+ "kind": "container",
75
+ "displayLabel": ""
76
+ }
77
+ ]
78
+ }
@@ -0,0 +1,133 @@
1
+ import {Name, type NameType} from '@wharfkit/antelope'
2
+ import kindRegistryJson from './kind-registry.json'
3
+
4
+ export const CAP_WRAP = 0x01
5
+ export const CAP_UNDEPLOY = 0x02
6
+ export const CAP_DEMOLISH = 0x04
7
+ export const CAP_MODULES = 0x10
8
+
9
+ export enum EntityClass {
10
+ OrbitalVessel = 0,
11
+ PlanetaryStructure = 1,
12
+ }
13
+
14
+ const CLASSIFICATION_BY_NAME: Record<string, EntityClass> = {
15
+ OrbitalVessel: EntityClass.OrbitalVessel,
16
+ PlanetaryStructure: EntityClass.PlanetaryStructure,
17
+ }
18
+
19
+ export type EntityTypeName = 'ship' | 'warehouse' | 'extractor' | 'factory' | 'container' | 'nexus'
20
+
21
+ export interface KindMeta {
22
+ kind: Name
23
+ classification: EntityClass
24
+ capabilityFlags: number
25
+ zCoord: number
26
+ defaultLabel: string
27
+ }
28
+
29
+ export interface TemplateMeta {
30
+ itemId: number
31
+ kind: Name
32
+ displayLabel: string
33
+ }
34
+
35
+ interface RawKindEntry {
36
+ kind: string
37
+ classification: string
38
+ capabilityFlags: number
39
+ zCoord: number
40
+ defaultLabel: string
41
+ }
42
+
43
+ interface RawTemplateEntry {
44
+ itemId: number
45
+ kind: string
46
+ displayLabel: string
47
+ }
48
+
49
+ const KIND_META: Map<string, KindMeta> = (() => {
50
+ const m = new Map<string, KindMeta>()
51
+ for (const r of kindRegistryJson.kinds as RawKindEntry[]) {
52
+ const cls = CLASSIFICATION_BY_NAME[r.classification]
53
+ if (cls === undefined) {
54
+ throw new Error(
55
+ `kind-registry: unknown classification "${r.classification}" for kind ${r.kind}`
56
+ )
57
+ }
58
+ m.set(r.kind, {
59
+ kind: Name.from(r.kind),
60
+ classification: cls,
61
+ capabilityFlags: r.capabilityFlags,
62
+ zCoord: r.zCoord,
63
+ defaultLabel: r.defaultLabel,
64
+ })
65
+ }
66
+ return m
67
+ })()
68
+
69
+ const TEMPLATE_BY_ITEM_ID: Map<number, TemplateMeta> = (() => {
70
+ const m = new Map<number, TemplateMeta>()
71
+ for (const r of kindRegistryJson.templates as RawTemplateEntry[]) {
72
+ m.set(r.itemId, {
73
+ itemId: r.itemId,
74
+ kind: Name.from(r.kind),
75
+ displayLabel: r.displayLabel,
76
+ })
77
+ }
78
+ return m
79
+ })()
80
+
81
+ function nameKey(kind: NameType | EntityTypeName): string {
82
+ if (typeof kind === 'string') return kind
83
+ return Name.from(kind).toString()
84
+ }
85
+
86
+ export function getKindMeta(kind: NameType | EntityTypeName): KindMeta | undefined {
87
+ return KIND_META.get(nameKey(kind))
88
+ }
89
+
90
+ export function getTemplateMeta(itemId: number): TemplateMeta | undefined {
91
+ return TEMPLATE_BY_ITEM_ID.get(itemId)
92
+ }
93
+
94
+ export function getPackedEntityType(itemId: number): Name | null {
95
+ return TEMPLATE_BY_ITEM_ID.get(itemId)?.kind ?? null
96
+ }
97
+
98
+ export function kindCan(kind: NameType | EntityTypeName, cap: number): boolean {
99
+ const m = KIND_META.get(nameKey(kind))
100
+ return m !== undefined && (m.capabilityFlags & cap) !== 0
101
+ }
102
+
103
+ export function getEntityClass(kind: NameType | EntityTypeName): EntityClass {
104
+ const m = KIND_META.get(nameKey(kind))
105
+ if (!m) throw new Error(`Entity type has no class: ${nameKey(kind)}`)
106
+ return m.classification
107
+ }
108
+
109
+ export const ENTITY_SHIP = Name.from('ship')
110
+ export const ENTITY_WAREHOUSE = Name.from('warehouse')
111
+ export const ENTITY_EXTRACTOR = Name.from('extractor')
112
+ export const ENTITY_FACTORY = Name.from('factory')
113
+ export const ENTITY_CONTAINER = Name.from('container')
114
+ export const ENTITY_NEXUS = Name.from('nexus')
115
+
116
+ export function isShip(entity: {type?: Name}): boolean {
117
+ return entity.type?.equals(ENTITY_SHIP) ?? false
118
+ }
119
+ export function isWarehouse(entity: {type?: Name}): boolean {
120
+ return entity.type?.equals(ENTITY_WAREHOUSE) ?? false
121
+ }
122
+ export function isExtractor(entity: {type?: Name}): boolean {
123
+ return entity.type?.equals(ENTITY_EXTRACTOR) ?? false
124
+ }
125
+ export function isFactory(entity: {type?: Name}): boolean {
126
+ return entity.type?.equals(ENTITY_FACTORY) ?? false
127
+ }
128
+ export function isContainer(entity: {type?: Name}): boolean {
129
+ return entity.type?.equals(ENTITY_CONTAINER) ?? false
130
+ }
131
+ export function isNexus(entity: {type?: Name}): boolean {
132
+ return entity.type?.equals(ENTITY_NEXUS) ?? false
133
+ }
@@ -0,0 +1,397 @@
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 + stats.hardness + stats.saturation
10
+ const exponent = statSum / 2997.0
11
+ return {
12
+ hullmass: computeBaseHullmass(stats),
13
+ capacity: Math.floor(1000000 * 10 ** 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: 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 com = stats.composition
35
+ const fin = stats.fineness
36
+
37
+ return {
38
+ capacity: 300 + Math.floor(com / 6),
39
+ recharge: 1 + Math.floor((fin * 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: 63500, 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
+ speed: number
79
+ } {
80
+ const str = stats.strength
81
+ const con = stats.conductivity
82
+ const ref = stats.reflectivity
83
+ const tol = stats.tolerance
84
+
85
+ return {
86
+ yield: 200 + str,
87
+ drain: Math.max(250, 1250 - Math.floor((con * 25) / 20)),
88
+ depth: gathererDepthForTier(tol, tier),
89
+ speed: 100 + Math.floor((ref * 4) / 5),
90
+ }
91
+ }
92
+
93
+ export function computeLoaderCapabilities(stats: Record<string, number>): {
94
+ mass: number
95
+ thrust: number
96
+ quantity: number
97
+ } {
98
+ const insulation = stats.insulation
99
+ const plasticity = stats.plasticity
100
+
101
+ return {
102
+ mass: Math.max(200, 2000 - Math.floor(insulation * 2)),
103
+ thrust: 1 + Math.floor(plasticity / 500),
104
+ quantity: 1,
105
+ }
106
+ }
107
+
108
+ export function computeCrafterCapabilities(stats: Record<string, number>): {
109
+ speed: number
110
+ drain: number
111
+ } {
112
+ const rea = stats.reactivity
113
+ const fin = stats.fineness
114
+
115
+ return {
116
+ speed: 100 + Math.floor((rea * 4) / 5),
117
+ drain: Math.max(5, 30 - Math.floor(fin / 33)),
118
+ }
119
+ }
120
+
121
+ export function computeHaulerCapabilities(stats: Record<string, number>): {
122
+ capacity: number
123
+ efficiency: number
124
+ drain: number
125
+ } {
126
+ const fineness = stats.fineness
127
+ const conductivity = stats.conductivity
128
+ const composition = stats.composition
129
+
130
+ return {
131
+ capacity: Math.max(1, 1 + Math.floor(fineness / 400)),
132
+ efficiency: 2000 + conductivity * 6,
133
+ drain: Math.max(3, 15 - Math.floor(composition / 80)),
134
+ }
135
+ }
136
+
137
+ export function computeStorageCapabilities(
138
+ stats: Record<string, number>,
139
+ baseCapacity: number
140
+ ): {
141
+ capacityBonus: number
142
+ } {
143
+ const strength = stats.strength
144
+ const density = stats.density
145
+ const hardness = stats.hardness
146
+ const saturation = stats.saturation
147
+
148
+ const statSum = strength + density + hardness + saturation
149
+ const capacityBonus = Math.floor(
150
+ (baseCapacity * (10 + Math.floor((statSum * 10) / 2997))) / 100
151
+ )
152
+
153
+ return {capacityBonus}
154
+ }
155
+
156
+ import {
157
+ ITEM_CONTAINER_T1_PACKED,
158
+ ITEM_CONTAINER_T2_PACKED,
159
+ ITEM_EXTRACTOR_T1_PACKED,
160
+ ITEM_FACTORY_T1_PACKED,
161
+ ITEM_SHIP_T1_PACKED,
162
+ ITEM_WAREHOUSE_T1_PACKED,
163
+ } from '../data/item-ids'
164
+ import {
165
+ getModuleCapabilityType,
166
+ MODULE_ENGINE,
167
+ MODULE_GENERATOR,
168
+ MODULE_GATHERER,
169
+ MODULE_LOADER,
170
+ MODULE_STORAGE,
171
+ MODULE_CRAFTER,
172
+ MODULE_HAULER,
173
+ MODULE_WARP,
174
+ } from '../capabilities/modules'
175
+ import {getItem} from '../data/catalog'
176
+ import {decodeCraftedItemStats} from './crafting'
177
+ import {
178
+ applySlotMultiplier,
179
+ clampUint16,
180
+ getSlotAmp,
181
+ type InstalledModule,
182
+ } from '../entities/slot-multiplier'
183
+ import type {EntitySlot} from '../data/recipes-runtime'
184
+
185
+ export function computeBaseCapacity(itemId: number, stats: Record<string, number>): number {
186
+ switch (itemId) {
187
+ case ITEM_SHIP_T1_PACKED:
188
+ case ITEM_EXTRACTOR_T1_PACKED:
189
+ case ITEM_FACTORY_T1_PACKED:
190
+ case ITEM_CONTAINER_T1_PACKED:
191
+ return computeShipHullCapabilities(stats).capacity
192
+ case ITEM_WAREHOUSE_T1_PACKED:
193
+ return computeWarehouseHullCapabilities(stats).capacity
194
+ case ITEM_CONTAINER_T2_PACKED:
195
+ return computeContainerT2Capabilities(stats).capacity
196
+ default:
197
+ return 0
198
+ }
199
+ }
200
+
201
+ export function computeWarpCapabilities(stats: Record<string, number>): {
202
+ range: number
203
+ } {
204
+ const res = stats.resonance
205
+ return {range: 100 + res * 3}
206
+ }
207
+
208
+ export function computeWarehouseHullCapabilities(stats: Record<string, number>): {
209
+ hullmass: number
210
+ capacity: number
211
+ } {
212
+ const statSum = stats.strength + stats.hardness + stats.saturation
213
+ const exponent = statSum / 2997.0
214
+ return {
215
+ hullmass: computeBaseHullmass(stats),
216
+ capacity: Math.floor(20000000 * 10 ** exponent),
217
+ }
218
+ }
219
+
220
+ export interface ComputedCapabilities {
221
+ hullmass: number
222
+ capacity: number
223
+ engines?: {thrust: number; drain: number}
224
+ generator?: {capacity: number; recharge: number}
225
+ gatherer?: {yield: number; drain: number; depth: number; speed: number}
226
+ loaders?: {mass: number; thrust: number; quantity: number}
227
+ crafter?: {speed: number; drain: number}
228
+ hauler?: {capacity: number; efficiency: number; drain: number}
229
+ warp?: {range: number}
230
+ }
231
+
232
+ export function computeEntityCapabilities(
233
+ stats: Record<string, number>,
234
+ itemId: number,
235
+ modules: InstalledModule[],
236
+ layout: EntitySlot[]
237
+ ): ComputedCapabilities {
238
+ let totalThrust = 0
239
+ let totalEngineDrain = 0
240
+ let hasEngine = false
241
+
242
+ let totalGenCapacity = 0
243
+ let totalGenRecharge = 0
244
+ let hasGenerator = false
245
+
246
+ let totalLoaderMass = 0
247
+ let totalLoaderThrust = 0
248
+ let totalLoaderQuantity = 0
249
+ let hasLoader = false
250
+
251
+ let totalGathYield = 0
252
+ let totalGathDrain = 0
253
+ let maxGathDepth = 0
254
+ let totalGathSpeed = 0
255
+ let hasGatherer = false
256
+
257
+ let totalStorageBonus = 0
258
+ const baseCapacity = computeBaseCapacity(itemId, stats)
259
+ let installedModuleMass = 0
260
+
261
+ let totalCrafterSpeed = 0
262
+ let totalCrafterDrain = 0
263
+ let hasCrafter = false
264
+
265
+ let totalHaulerCapacity = 0
266
+ let weightedHaulerEffNum = 0n
267
+ let totalHaulerDrain = 0
268
+ let hasHauler = false
269
+
270
+ let totalWarpRange = 0
271
+ let hasWarp = false
272
+
273
+ for (const mod of modules) {
274
+ const item = getItem(mod.itemId)
275
+ const modType = getModuleCapabilityType(mod.itemId)
276
+ const amp = getSlotAmp(layout, mod.slotIndex)
277
+ const decodedStats = decodeCraftedItemStats(mod.itemId, mod.stats)
278
+ installedModuleMass += item.mass
279
+
280
+ if (modType === MODULE_ENGINE) {
281
+ hasEngine = true
282
+ const caps = computeEngineCapabilities(decodedStats)
283
+ totalThrust += applySlotMultiplier(caps.thrust, amp)
284
+ totalEngineDrain += caps.drain
285
+ } else if (modType === MODULE_GENERATOR) {
286
+ hasGenerator = true
287
+ const caps = computeGeneratorCapabilities(decodedStats)
288
+ totalGenCapacity += applySlotMultiplier(caps.capacity, amp)
289
+ totalGenRecharge += applySlotMultiplier(caps.recharge, amp)
290
+ } else if (modType === MODULE_GATHERER) {
291
+ hasGatherer = true
292
+ const tier = item.tier
293
+ const caps = computeGathererCapabilities(decodedStats, tier)
294
+ totalGathYield += applySlotMultiplier(caps.yield, amp)
295
+ totalGathDrain += caps.drain
296
+ if (caps.depth > maxGathDepth) maxGathDepth = caps.depth
297
+ totalGathSpeed += applySlotMultiplier(caps.speed, amp)
298
+ } else if (modType === MODULE_LOADER) {
299
+ hasLoader = true
300
+ const caps = computeLoaderCapabilities(decodedStats)
301
+ totalLoaderMass += caps.mass
302
+ totalLoaderThrust += applySlotMultiplier(caps.thrust, amp)
303
+ totalLoaderQuantity += caps.quantity
304
+ } else if (modType === MODULE_STORAGE) {
305
+ const caps = computeStorageCapabilities(decodedStats, baseCapacity)
306
+ totalStorageBonus += caps.capacityBonus
307
+ } else if (modType === MODULE_CRAFTER) {
308
+ hasCrafter = true
309
+ const caps = computeCrafterCapabilities(decodedStats)
310
+ totalCrafterSpeed += applySlotMultiplier(caps.speed, amp)
311
+ totalCrafterDrain += caps.drain
312
+ } else if (modType === MODULE_HAULER) {
313
+ hasHauler = true
314
+ const caps = computeHaulerCapabilities(decodedStats)
315
+ const eff = applySlotMultiplier(caps.efficiency, amp)
316
+ totalHaulerCapacity += caps.capacity
317
+ weightedHaulerEffNum += BigInt(eff) * BigInt(caps.capacity)
318
+ totalHaulerDrain += caps.drain
319
+ } else if (modType === MODULE_WARP) {
320
+ hasWarp = true
321
+ const caps = computeWarpCapabilities(decodedStats)
322
+ totalWarpRange += applySlotMultiplier(caps.range, amp)
323
+ }
324
+ }
325
+
326
+ const result: ComputedCapabilities = {
327
+ hullmass: computeBaseHullmass(stats) + installedModuleMass,
328
+ capacity: baseCapacity + totalStorageBonus,
329
+ }
330
+
331
+ if (hasEngine) {
332
+ result.engines = {thrust: totalThrust, drain: totalEngineDrain}
333
+ }
334
+ if (hasGenerator) {
335
+ result.generator = {
336
+ capacity: clampUint16(totalGenCapacity),
337
+ recharge: clampUint16(totalGenRecharge),
338
+ }
339
+ }
340
+ if (hasGatherer) {
341
+ result.gatherer = {
342
+ yield: clampUint16(totalGathYield),
343
+ drain: totalGathDrain,
344
+ depth: maxGathDepth,
345
+ speed: clampUint16(totalGathSpeed),
346
+ }
347
+ }
348
+ if (hasLoader) {
349
+ result.loaders = {
350
+ mass: totalLoaderMass,
351
+ thrust: clampUint16(totalLoaderThrust),
352
+ quantity: totalLoaderQuantity,
353
+ }
354
+ }
355
+ if (hasCrafter) {
356
+ result.crafter = {speed: clampUint16(totalCrafterSpeed), drain: totalCrafterDrain}
357
+ }
358
+ if (hasHauler) {
359
+ const efficiency =
360
+ totalHaulerCapacity > 0 ? Number(weightedHaulerEffNum / BigInt(totalHaulerCapacity)) : 0
361
+ result.hauler = {
362
+ capacity: totalHaulerCapacity,
363
+ efficiency: clampUint16(efficiency),
364
+ drain: totalHaulerDrain,
365
+ }
366
+ }
367
+ if (hasWarp) {
368
+ result.warp = {range: totalWarpRange}
369
+ }
370
+
371
+ return result
372
+ }
373
+
374
+ export function computeContainerCapabilities(stats: Record<string, number>): {
375
+ hullmass: number
376
+ capacity: number
377
+ } {
378
+ return computeShipHullCapabilities(stats)
379
+ }
380
+
381
+ export function computeContainerT2Capabilities(stats: Record<string, number>): {
382
+ hullmass: number
383
+ capacity: number
384
+ } {
385
+ const strength = stats.strength
386
+ const density = stats.density
387
+ const hardness = stats.hardness
388
+ const saturation = stats.saturation
389
+
390
+ const hullmass = 70000 - 50 * density
391
+
392
+ const statSum = strength + hardness + saturation
393
+ const exponent = statSum / 2500
394
+ const capacity = Math.floor(1500000 * 10 ** exponent)
395
+
396
+ return {hullmass, capacity}
397
+ }
@@ -340,7 +340,7 @@ export function computeCraftedOutputStats(
340
340
  * returns a UInt64 whose bit-packed form matches what the contract writes
341
341
  * to cargo_item.stats on gather.
342
342
  *
343
- * Use this whenever off-chain code simulates a gather (testmap, player
343
+ * Use this whenever off-chain code simulates a gather (webapp, player
344
344
  * scanners that project cargo outcomes) and needs a value that matches
345
345
  * what on-chain cargo would carry.
346
346
  */