@shipload/sdk 1.0.0-next.10 → 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.
Files changed (39) hide show
  1. package/lib/shipload.d.ts +759 -868
  2. package/lib/shipload.js +2463 -2588
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +2432 -2557
  5. package/lib/shipload.m.js.map +1 -1
  6. package/package.json +1 -1
  7. package/src/data/entities.json +13 -0
  8. package/src/data/item-ids.ts +1 -0
  9. package/src/data/items.json +6 -0
  10. package/src/data/kind-registry.json +78 -0
  11. package/src/data/kind-registry.ts +133 -0
  12. package/src/data/metadata.ts +6 -0
  13. package/src/data/recipes.json +57 -0
  14. package/src/derivation/capabilities.ts +397 -0
  15. package/src/derivation/crafting.ts +1 -1
  16. package/src/entities/entity.ts +98 -0
  17. package/src/entities/makers.ts +76 -170
  18. package/src/errors.ts +10 -13
  19. package/src/index-module.ts +39 -22
  20. package/src/managers/entities.ts +18 -80
  21. package/src/managers/index.ts +1 -1
  22. package/src/nft/atomicdata.ts +5 -0
  23. package/src/nft/description.ts +1 -1
  24. package/src/resolution/resolve-item.ts +3 -2
  25. package/src/scheduling/projection.ts +2 -2
  26. package/src/subscriptions/manager.ts +3 -5
  27. package/src/subscriptions/mappers.ts +3 -8
  28. package/src/subscriptions/types.ts +2 -2
  29. package/src/testing/catalog-hash.ts +19 -0
  30. package/src/testing/index.ts +2 -0
  31. package/src/testing/projection-parity.ts +143 -0
  32. package/src/types/index.ts +0 -1
  33. package/src/types.ts +0 -9
  34. package/src/entities/container.ts +0 -123
  35. package/src/entities/extractor.ts +0 -144
  36. package/src/entities/ship-deploy.ts +0 -316
  37. package/src/entities/ship.ts +0 -221
  38. package/src/entities/warehouse.ts +0 -136
  39. package/src/types/entity-traits.ts +0 -132
@@ -14,7 +14,7 @@ import {
14
14
  RECIPE_INPUTS_INSUFFICIENT,
15
15
  RECIPE_INPUTS_INVALID,
16
16
  RECIPE_NOT_FOUND,
17
- SHIP_CARGO_NOT_LOADED,
17
+ ENTITY_CARGO_NOT_LOADED,
18
18
  } from '../errors'
19
19
  import {getRecipe, type RecipeInput} from '../data/recipes-runtime'
20
20
  import {getItem} from '../data/catalog'
@@ -389,7 +389,7 @@ function validateCraftTask(task: ServerContract.Types.task, projected: Projected
389
389
  break
390
390
  }
391
391
  }
392
- if (!found) throw new Error(SHIP_CARGO_NOT_LOADED)
392
+ if (!found) throw new Error(ENTITY_CARGO_NOT_LOADED)
393
393
  }
394
394
  }
395
395
 
@@ -13,12 +13,10 @@ import type {
13
13
  WireEntity,
14
14
  } from './types'
15
15
  import {mapEntity, parseWireEntity} from './mappers'
16
- import type {Ship} from '../entities/ship'
17
- import type {Warehouse} from '../entities/warehouse'
18
- import type {Container} from '../entities/container'
16
+ import type {Entity} from '../entities/entity'
19
17
 
20
- export type SubscriptionEntityType = 'ship' | 'warehouse' | 'container'
21
- export type EntityInstance = Ship | Warehouse | Container
18
+ export type SubscriptionEntityType = 'ship' | 'warehouse' | 'container' | 'nexus'
19
+ export type EntityInstance = Entity
22
20
 
23
21
  export interface SubscriptionsOptions {
24
22
  url: string
@@ -1,14 +1,9 @@
1
1
  import {ServerContract} from '../contracts'
2
- import {Ship} from '../entities/ship'
3
- import {Warehouse} from '../entities/warehouse'
4
- import {Container} from '../entities/container'
2
+ import {Entity} from '../entities/entity'
5
3
  import type {WireEntity} from './types'
6
4
 
7
- export function mapEntity(ei: ServerContract.Types.entity_info): Ship | Warehouse | Container {
8
- if (ei.type.equals('ship')) return new Ship(ei)
9
- if (ei.type.equals('warehouse')) return new Warehouse(ei)
10
- if (ei.type.equals('container')) return new Container(ei)
11
- throw new Error(`mapEntity: unknown entity type ${ei.type.toString()}`)
5
+ export function mapEntity(ei: ServerContract.Types.entity_info): Entity {
6
+ return new Entity(ei)
12
7
  }
13
8
 
14
9
  export function parseWireEntity(raw: WireEntity): ServerContract.Types.entity_info {
@@ -39,7 +39,7 @@ export type UnsubscribeMessage = {
39
39
  export type SubscribeEntityMessage = {
40
40
  type: 'subscribe_entity'
41
41
  sub_id: string
42
- entity_type: 'ship' | 'warehouse' | 'container'
42
+ entity_type: 'ship' | 'warehouse' | 'container' | 'nexus'
43
43
  entity_id: string
44
44
  }
45
45
 
@@ -80,7 +80,7 @@ export type AckMessage = {
80
80
 
81
81
  export type WireEntity = Record<string, unknown> & {
82
82
  type: number
83
- type_name: 'ship' | 'warehouse' | 'container'
83
+ type_name: 'ship' | 'warehouse' | 'container' | 'nexus'
84
84
  id: string | number
85
85
  owner: string
86
86
  coordinates: WireCoordinates
@@ -0,0 +1,19 @@
1
+ import {createHash} from 'node:crypto'
2
+ import {readFileSync} from 'node:fs'
3
+
4
+ export const CATALOG_FILES_REL = [
5
+ 'items.json',
6
+ 'recipes.json',
7
+ 'entities.json',
8
+ 'kind-registry.json',
9
+ 'item-ids.ts',
10
+ ] as const
11
+
12
+ export function computeCatalogHash(filePaths: ReadonlyArray<string>): string {
13
+ const hash = createHash('sha256')
14
+ for (const p of filePaths) {
15
+ hash.update(readFileSync(p))
16
+ hash.update('\0')
17
+ }
18
+ return hash.digest('hex')
19
+ }
@@ -0,0 +1,2 @@
1
+ export * from './catalog-hash'
2
+ export * from './projection-parity'
@@ -0,0 +1,143 @@
1
+ import type {UInt16, UInt32} from '@wharfkit/antelope'
2
+ import type {ServerContract} from '../contracts'
3
+ import type {ProjectedEntity} from '../scheduling/projection'
4
+ import {type CargoStack, cargoItemToStack, mergeStacks} from '../capabilities/storage'
5
+
6
+ export interface ContractProjectedState {
7
+ owner: {toString(): string}
8
+ coordinates: ServerContract.Types.coordinates
9
+ energy?: UInt16
10
+ cargomass: UInt32
11
+ cargo: ServerContract.Types.cargo_view[]
12
+ hullmass?: UInt32
13
+ capacity?: UInt32
14
+ engines?: ServerContract.Types.movement_stats
15
+ loaders?: ServerContract.Types.loader_stats
16
+ generator?: ServerContract.Types.energy_stats
17
+ hauler?: ServerContract.Types.hauler_stats
18
+ }
19
+
20
+ export interface ProjectionComparisonOptions {
21
+ step?: number
22
+ }
23
+
24
+ export function assertProjectionEquals(
25
+ contract: ContractProjectedState,
26
+ sdk: ProjectedEntity,
27
+ options: ProjectionComparisonOptions = {}
28
+ ): void {
29
+ const mismatches: string[] = []
30
+
31
+ const record = (name: string, c: unknown, s: unknown) => {
32
+ if (c !== s) mismatches.push(` ${name}: contract=${fmt(c)} sdk=${fmt(s)}`)
33
+ }
34
+
35
+ const recordStatBlock = (name: string, c: unknown, s: unknown) => {
36
+ const cPresent = c !== undefined && c !== null
37
+ const sPresent = s !== undefined && s !== null
38
+ if (cPresent !== sPresent) {
39
+ mismatches.push(
40
+ ` ${name}: contract=${cPresent ? 'present' : 'absent'} sdk=${sPresent ? 'present' : 'absent'}`
41
+ )
42
+ return
43
+ }
44
+ if (!cPresent) return
45
+ const cn = JSON.stringify(normaliseStatBlock(c))
46
+ const sn = JSON.stringify(normaliseStatBlock(s))
47
+ if (cn !== sn) mismatches.push(` ${name}: contract=${cn} sdk=${sn}`)
48
+ }
49
+
50
+ record('coordinates.x', toNum(contract.coordinates.x), Number(sdk.location.x))
51
+ record('coordinates.y', toNum(contract.coordinates.y), Number(sdk.location.y))
52
+ record('energy', toNum(contract.energy), Number(sdk.energy))
53
+ record('cargomass', toNum(contract.cargomass), Number(sdk.cargoMass))
54
+ record('hullmass', toNum(contract.hullmass), Number(sdk.shipMass))
55
+ record('capacity', toNum(contract.capacity), sdk.capacity ? Number(sdk.capacity) : undefined)
56
+
57
+ recordStatBlock('engines', contract.engines, sdk.engines)
58
+ recordStatBlock('loaders', contract.loaders, sdk.loaders)
59
+ recordStatBlock('generator', contract.generator, sdk.generator)
60
+ recordStatBlock('hauler', contract.hauler, sdk.hauler)
61
+
62
+ if (contract.cargo.length > 0 || sdk.cargo.length > 0) {
63
+ const contractCargo = normaliseCargo(mergeContractCargo(contract.cargo))
64
+ const sdkCargo = normaliseCargo(sdk.cargo)
65
+ if (contractCargo.length !== sdkCargo.length) {
66
+ mismatches.push(
67
+ ` cargo.length: contract=${contractCargo.length} sdk=${sdkCargo.length}`
68
+ )
69
+ } else {
70
+ for (let i = 0; i < contractCargo.length; i++) {
71
+ const c = contractCargo[i]
72
+ const s = sdkCargo[i]
73
+ if (c.itemId !== s.itemId || c.stats !== s.stats || c.quantity !== s.quantity) {
74
+ mismatches.push(
75
+ ` cargo[${i}]: contract={item:${c.itemId},stats:${c.stats},qty:${c.quantity}} sdk={item:${s.itemId},stats:${s.stats},qty:${s.quantity}}`
76
+ )
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ if (mismatches.length > 0) {
83
+ const header =
84
+ options.step !== undefined
85
+ ? `projection divergence at step ${options.step}:`
86
+ : 'projection divergence:'
87
+ throw new Error([header, ...mismatches].join('\n'))
88
+ }
89
+ }
90
+
91
+ interface NormalisedStack {
92
+ itemId: number
93
+ stats: string
94
+ quantity: string
95
+ }
96
+
97
+ function mergeContractCargo(cargo: ServerContract.Types.cargo_view[]): CargoStack[] {
98
+ return cargo.reduce<CargoStack[]>(
99
+ (acc, row) =>
100
+ mergeStacks(acc, cargoItemToStack(row as unknown as ServerContract.Types.cargo_item)),
101
+ []
102
+ )
103
+ }
104
+
105
+ function normaliseCargo(cargo: CargoStack[]): NormalisedStack[] {
106
+ return cargo
107
+ .map((s) => ({
108
+ itemId: Number(s.item_id),
109
+ stats: BigInt(s.stats.toString()).toString(),
110
+ quantity: BigInt(s.quantity.toString()).toString(),
111
+ }))
112
+ .sort(stackSort)
113
+ }
114
+
115
+ function stackSort(a: NormalisedStack, b: NormalisedStack): number {
116
+ if (a.itemId !== b.itemId) return a.itemId - b.itemId
117
+ return a.stats < b.stats ? -1 : a.stats > b.stats ? 1 : 0
118
+ }
119
+
120
+ function toNum(v: unknown): number | undefined {
121
+ if (v === undefined || v === null) return undefined
122
+ if (typeof v === 'number') return v
123
+ if (typeof v === 'bigint') return Number(v)
124
+ if (typeof (v as {toNumber?: unknown}).toNumber === 'function') {
125
+ return (v as {toNumber(): number}).toNumber()
126
+ }
127
+ return Number(v as number)
128
+ }
129
+
130
+ function fmt(v: unknown): string {
131
+ if (v === undefined) return 'undefined'
132
+ if (v === null) return 'null'
133
+ return String(v)
134
+ }
135
+
136
+ function normaliseStatBlock(block: unknown): Record<string, number> {
137
+ const out: Record<string, number> = {}
138
+ const obj = block as Record<string, unknown>
139
+ for (const k of Object.keys(obj).sort()) {
140
+ out[k] = toNum(obj[k]) ?? 0
141
+ }
142
+ return out
143
+ }
@@ -1,3 +1,2 @@
1
1
  export * from './capabilities'
2
2
  export * from './entity'
3
- export * from './entity-traits'
package/src/types.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  type Int64Type,
3
- Name,
4
3
  type UInt16,
5
4
  type UInt16Type,
6
5
  type UInt32,
@@ -72,14 +71,6 @@ export enum TaskCancelable {
72
71
  ALWAYS = 2,
73
72
  }
74
73
 
75
- export const EntityType = {
76
- SHIP: Name.from('ship'),
77
- WAREHOUSE: Name.from('warehouse'),
78
- CONTAINER: Name.from('container'),
79
- } as const
80
-
81
- export type EntityTypeName = (typeof EntityType)[keyof typeof EntityType]
82
-
83
74
  export type CoordinatesType =
84
75
  | Coordinates
85
76
  | ServerContract.Types.coordinates
@@ -1,123 +0,0 @@
1
- import {UInt64, type UInt64Type} from '@wharfkit/antelope'
2
- import {ServerContract} from '../contracts'
3
- import type {CoordinatesType} from '../types'
4
- import {type FloatPosition, getInterpolatedPosition} from '../travel/travel'
5
- import {Location} from './location'
6
- import {ScheduleAccessor} from '../scheduling/accessor'
7
- import * as schedule from '../scheduling/schedule'
8
-
9
- export interface ContainerStateInput {
10
- id: UInt64Type
11
- owner: string
12
- name: string
13
- coordinates: CoordinatesType | {x: number; y: number; z?: number}
14
- hullmass: number
15
- capacity: number
16
- cargomass?: number
17
- cargo?: ServerContract.Types.cargo_item[]
18
- schedule?: ServerContract.Types.schedule
19
- }
20
-
21
- export class Container extends ServerContract.Types.entity_info {
22
- private _sched?: ScheduleAccessor
23
-
24
- get name(): string {
25
- return this.entity_name
26
- }
27
-
28
- get entityClass(): 'mobile' {
29
- return 'mobile'
30
- }
31
-
32
- get canUndeploy(): boolean {
33
- return true
34
- }
35
-
36
- get sched(): ScheduleAccessor {
37
- this._sched ??= new ScheduleAccessor(this)
38
- return this._sched
39
- }
40
-
41
- get isIdle(): boolean {
42
- return this.is_idle
43
- }
44
-
45
- interpolatedPositionAt(now: Date): FloatPosition {
46
- const taskIndex = this.sched.currentTaskIndex(now)
47
- const progress = this.sched.currentTaskProgressFloat(now)
48
- return getInterpolatedPosition(this, taskIndex, progress)
49
- }
50
-
51
- isLoading(now: Date): boolean {
52
- return schedule.isLoading(this, now)
53
- }
54
-
55
- isUnloading(now: Date): boolean {
56
- return schedule.isUnloading(this, now)
57
- }
58
-
59
- get location(): Location {
60
- return Location.from(this.coordinates)
61
- }
62
-
63
- get totalMass(): UInt64 {
64
- return UInt64.from(this.hullmass ?? 0).adding(this.cargomass)
65
- }
66
-
67
- get maxCapacity(): UInt64 {
68
- return UInt64.from(this.capacity)
69
- }
70
-
71
- get availableCapacity(): UInt64 {
72
- const cargo = UInt64.from(this.cargomass)
73
- return cargo.gte(this.maxCapacity) ? UInt64.from(0) : this.maxCapacity.subtracting(cargo)
74
- }
75
-
76
- hasSpace(additionalMass: UInt64): boolean {
77
- return UInt64.from(this.cargomass).adding(additionalMass).lte(this.maxCapacity)
78
- }
79
-
80
- get isFull(): boolean {
81
- return UInt64.from(this.cargomass).gte(this.maxCapacity)
82
- }
83
-
84
- get orbitalAltitude(): number {
85
- return this.coordinates.z?.toNumber() || 0
86
- }
87
- }
88
-
89
- export function computeContainerCapabilities(stats: Record<string, number>): {
90
- hullmass: number
91
- capacity: number
92
- } {
93
- const density = stats.density
94
- const strength = stats.strength
95
- const hardness = stats.hardness
96
- const saturation = stats.saturation
97
-
98
- const hullmass = 100000 - 75 * density
99
-
100
- const statSum = strength + hardness + saturation
101
- const exponent = statSum / 2997
102
- const capacity = Math.floor(1000000 * 10 ** exponent)
103
-
104
- return {hullmass, capacity}
105
- }
106
-
107
- export function computeContainerT2Capabilities(stats: Record<string, number>): {
108
- hullmass: number
109
- capacity: number
110
- } {
111
- const strength = stats.strength
112
- const density = stats.density
113
- const hardness = stats.hardness
114
- const saturation = stats.saturation
115
-
116
- const hullmass = 70000 - 50 * density
117
-
118
- const statSum = strength + hardness + saturation
119
- const exponent = statSum / 2500
120
- const capacity = Math.floor(1500000 * 10 ** exponent)
121
-
122
- return {hullmass, capacity}
123
- }
@@ -1,144 +0,0 @@
1
- import {UInt64, type UInt64Type} from '@wharfkit/antelope'
2
- import {ServerContract} from '../contracts'
3
- import type {CoordinatesType} from '../types'
4
- import {Location} from './location'
5
- import {ScheduleAccessor} from '../scheduling/accessor'
6
- import {InventoryAccessor} from './inventory-accessor'
7
- import type {EntityInventory} from './entity-inventory'
8
- import type {PackedModuleInput} from './ship'
9
- import {decodeCraftedItemStats} from '../derivation/crafting'
10
- import {getModuleCapabilityType, MODULE_GATHERER, MODULE_GENERATOR} from '../capabilities/modules'
11
- import {computeGathererCapabilities, computeGeneratorCapabilities} from './ship-deploy'
12
- import {applySlotMultiplier, clampUint16, getSlotAmp, type InstalledModule} from './slot-multiplier'
13
- import type {EntitySlot} from '../data/recipes-runtime'
14
- import {getItem} from '../data/catalog'
15
-
16
- export interface ExtractorStateInput {
17
- id: UInt64Type
18
- owner: string
19
- name: string
20
- coordinates: CoordinatesType | {x: number; y: number; z?: number}
21
- hullmass?: number
22
- capacity?: number
23
- energy?: number
24
- modules?: PackedModuleInput[]
25
- schedule?: ServerContract.Types.schedule
26
- cargo?: ServerContract.Types.cargo_item[]
27
- }
28
-
29
- export class Extractor extends ServerContract.Types.entity_info {
30
- private _sched?: ScheduleAccessor
31
- private _inv?: InventoryAccessor
32
-
33
- get name(): string {
34
- return this.entity_name
35
- }
36
-
37
- get entityClass(): 'building' {
38
- return 'building'
39
- }
40
-
41
- get canDemolish(): boolean {
42
- return true
43
- }
44
-
45
- get inv(): InventoryAccessor {
46
- this._inv ??= new InventoryAccessor(this)
47
- return this._inv
48
- }
49
-
50
- get inventory(): EntityInventory[] {
51
- return this.inv.items
52
- }
53
-
54
- get sched(): ScheduleAccessor {
55
- this._sched ??= new ScheduleAccessor(this)
56
- return this._sched
57
- }
58
-
59
- get isIdle(): boolean {
60
- return this.is_idle
61
- }
62
-
63
- get location(): Location {
64
- return Location.from(this.coordinates)
65
- }
66
-
67
- get totalCargoMass(): UInt64 {
68
- return this.inv.totalMass
69
- }
70
-
71
- get maxCapacity(): UInt64 {
72
- return UInt64.from(this.capacity)
73
- }
74
-
75
- get availableCapacity(): UInt64 {
76
- const cargo = this.totalCargoMass
77
- return cargo.gte(this.maxCapacity) ? UInt64.from(0) : this.maxCapacity.subtracting(cargo)
78
- }
79
-
80
- get isFull(): boolean {
81
- return this.totalCargoMass.gte(this.maxCapacity)
82
- }
83
-
84
- get totalMass(): UInt64 {
85
- const hull = this.hullmass ? UInt64.from(this.hullmass) : UInt64.from(0)
86
- return hull.adding(this.totalCargoMass)
87
- }
88
- }
89
-
90
- export interface ExtractorCapabilities {
91
- generator?: {capacity: number; recharge: number}
92
- gatherer?: {yield: number; drain: number; depth: number; speed: number}
93
- }
94
-
95
- export function computeExtractorCapabilities(
96
- modules: InstalledModule[],
97
- layout: EntitySlot[]
98
- ): ExtractorCapabilities {
99
- const out: ExtractorCapabilities = {}
100
-
101
- const genModules = modules.filter((m) => getModuleCapabilityType(m.itemId) === MODULE_GENERATOR)
102
- if (genModules.length > 0) {
103
- let totalCapacity = 0
104
- let totalRecharge = 0
105
- for (const m of genModules) {
106
- const caps = computeGeneratorCapabilities(decodeCraftedItemStats(m.itemId, m.stats))
107
- const amp = getSlotAmp(layout, m.slotIndex)
108
- totalCapacity += applySlotMultiplier(caps.capacity, amp)
109
- totalRecharge += applySlotMultiplier(caps.recharge, amp)
110
- }
111
- out.generator = {
112
- capacity: clampUint16(totalCapacity),
113
- recharge: clampUint16(totalRecharge),
114
- }
115
- }
116
-
117
- const gathModules = modules.filter((m) => getModuleCapabilityType(m.itemId) === MODULE_GATHERER)
118
- if (gathModules.length > 0) {
119
- let totalYield = 0
120
- let totalDrain = 0
121
- let maxDepth = 0
122
- let totalSpeed = 0
123
- for (const m of gathModules) {
124
- const tier = getItem(m.itemId).tier
125
- const caps = computeGathererCapabilities(
126
- decodeCraftedItemStats(m.itemId, m.stats),
127
- tier
128
- )
129
- const amp = getSlotAmp(layout, m.slotIndex)
130
- totalYield += applySlotMultiplier(caps.yield, amp)
131
- totalDrain += caps.drain
132
- if (caps.depth > maxDepth) maxDepth = caps.depth
133
- totalSpeed += applySlotMultiplier(caps.speed, amp)
134
- }
135
- out.gatherer = {
136
- yield: clampUint16(totalYield),
137
- drain: totalDrain,
138
- depth: maxDepth,
139
- speed: clampUint16(totalSpeed),
140
- }
141
- }
142
-
143
- return out
144
- }