@shipload/sdk 1.0.0-next.40 → 1.0.0-next.42

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.40",
4
+ "version": "1.0.0-next.42",
5
5
  "homepage": "https://github.com/shipload/toolkit/tree/master/packages/sdk",
6
6
  "repository": {
7
7
  "type": "git",
@@ -24,6 +24,11 @@
24
24
  "import": "./lib/testing.m.js",
25
25
  "require": "./lib/testing.js"
26
26
  },
27
+ "./scan": {
28
+ "types": "./lib/scan.d.ts",
29
+ "import": "./lib/scan.m.js",
30
+ "require": "./lib/scan.js"
31
+ },
27
32
  "./package.json": "./package.json"
28
33
  },
29
34
  "files": [
@@ -376,7 +376,7 @@
376
376
  "quantity": 300
377
377
  },
378
378
  {
379
- "itemId": 10006,
379
+ "itemId": 10010,
380
380
  "quantity": 300
381
381
  }
382
382
  ],
@@ -21,8 +21,8 @@ import {
21
21
  import type {InstalledModule} from '../entities/slot-multiplier'
22
22
  import type {EntitySlot} from '../data/recipes-runtime'
23
23
 
24
- function makeGathererStats(strength: number, tolerance: number, conductivity: number): bigint {
25
- return encodeStats([strength, tolerance, conductivity, 0])
24
+ function makeGathererStats(strength: number, tolerance: number, saturation: number): bigint {
25
+ return encodeStats([strength, tolerance, saturation, 0])
26
26
  }
27
27
 
28
28
  function makeCrafterStats(reactivity: number, conductivity: number): bigint {
@@ -72,8 +72,8 @@ test('computeEntityCapabilities emits gathererLanes alongside legacy gatherer su
72
72
  expect(result.gathererLanes![1].slotIndex).toBe(1)
73
73
 
74
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)
75
+ const caps1 = computeGathererCapabilities({strength: 300, tolerance: 200, saturation: 400}, 1)
76
+ const caps2 = computeGathererCapabilities({strength: 500, tolerance: 100, saturation: 300}, 1)
77
77
  const expectedYield1 = applySlotMultiplier(caps1.yield, 100)
78
78
  const expectedYield2 = applySlotMultiplier(caps2.yield, 100)
79
79
  expect(result.gathererLanes![0].yield).toBe(expectedYield1)
@@ -77,7 +77,7 @@ export function computeGathererCapabilities(
77
77
  depth: number
78
78
  } {
79
79
  const str = stats.strength
80
- const con = stats.conductivity
80
+ const con = stats.saturation
81
81
  const tol = stats.tolerance
82
82
 
83
83
  return {
@@ -26,14 +26,12 @@ test('getRecipeConsumers lists every recipe that consumes Sensor', () => {
26
26
  const consumers = getRecipeConsumers(ITEM_SENSOR)
27
27
  const ids = consumers.map((c) => c.outputItemId).sort((a, b) => a - b)
28
28
  expect(ids).toEqual(
29
- [ITEM_GATHERER_T1, ITEM_CRAFTER_T1, ITEM_SHIP_T1_PACKED, ITEM_EXTRACTOR_T1_PACKED].sort(
30
- (a, b) => a - b
31
- )
29
+ [ITEM_CRAFTER_T1, ITEM_SHIP_T1_PACKED, ITEM_EXTRACTOR_T1_PACKED].sort((a, b) => a - b)
32
30
  )
33
31
  })
34
32
 
35
- test('Sensor feeds the gatherer drain stat', () => {
36
- const consumers = getRecipeConsumers(ITEM_SENSOR)
33
+ test('Resin feeds the gatherer drain stat', () => {
34
+ const consumers = getRecipeConsumers(ITEM_RESIN)
37
35
  const gatherer = consumers.find((c) => c.outputItemId === ITEM_GATHERER_T1)
38
36
  expect(gatherer).toBeDefined()
39
37
  const drain = gatherer?.statFlows.find(
@@ -58,11 +56,12 @@ test('getResourceDemand traces a dual-resource component to both resources', ()
58
56
  })
59
57
 
60
58
  test('getResourceDemand recurses through a module to raw resources', () => {
61
- // Gatherer = 300 Beam (5 ore + 5 gas each) + 300 Sensor (10 crystal each)
59
+ // Gatherer = 300 Beam (5 ore + 5 gas each) + 300 Resin (5 biomass + 5 crystal each)
62
60
  expect(getResourceDemand(ITEM_GATHERER_T1)).toEqual({
63
61
  ore: 1500,
64
62
  gas: 1500,
65
- crystal: 3000,
63
+ biomass: 1500,
64
+ crystal: 1500,
66
65
  })
67
66
  })
68
67
 
@@ -70,9 +69,9 @@ test('getResourceDemand scales by quantity', () => {
70
69
  expect(getResourceDemand(ITEM_PLATE, 3)).toEqual({ore: 30})
71
70
  })
72
71
 
73
- test('getComponentDemand reports Resin as consumed by exactly one recipe', () => {
72
+ test('getComponentDemand reports Resin as consumed by two recipes', () => {
74
73
  const demand = getComponentDemand()
75
74
  const resin = demand.find((d) => d.itemId === ITEM_RESIN)
76
75
  expect(resin).toBeDefined()
77
- expect(resin?.consumerCount).toBe(1)
76
+ expect(resin?.consumerCount).toBe(2)
78
77
  })
@@ -15,8 +15,8 @@ export const DEPTH_THRESHOLD_T10 = 63000
15
15
  export const LOCATION_MIN_DEPTH = 500
16
16
  export const LOCATION_MAX_DEPTH = 65535
17
17
 
18
- export const YIELD_FRACTION_SHALLOW = 0.005
19
- export const YIELD_FRACTION_DEEP = 0.001
18
+ export const YIELD_FRACTION_SHALLOW = 0.002
19
+ export const YIELD_FRACTION_DEEP = 0.0004
20
20
 
21
21
  export function yieldThresholdAt(stratum: number): number {
22
22
  const clamped = stratum > 65535 ? 65535 : stratum
@@ -1,5 +1,6 @@
1
1
  import type {Checksum256Type} from '@wharfkit/antelope'
2
2
  import {hash512} from '../utils/hash'
3
+ import {COORD_MAX, COORD_MIN} from '../coordinates/constants'
3
4
 
4
5
  export const WH = {
5
6
  RSIZE: 75,
@@ -72,6 +73,9 @@ function endpointInRegion(seed: Checksum256Type, R: Region, key: string): {x: nu
72
73
  function dist(a: {x: number; y: number}, b: {x: number; y: number}): number {
73
74
  return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2)
74
75
  }
76
+ function inBounds(c: {x: number; y: number}): boolean {
77
+ return c.x >= COORD_MIN && c.x <= COORD_MAX && c.y >= COORD_MIN && c.y <= COORD_MAX
78
+ }
75
79
  function wormholeOfRegion(
76
80
  seed: Checksum256Type,
77
81
  R: Region
@@ -82,6 +86,7 @@ function wormholeOfRegion(
82
86
  if (roll16(seed, `wh-exists-${key}`) >= WH.THRESHOLD) return null
83
87
  const A = endpointInRegion(seed, R, key)
84
88
  const B = endpointInRegion(seed, P, key)
89
+ if (!inBounds(A) || !inBounds(B)) return null
85
90
  if (dist(A, B) < WH.MIN_REACH) return null
86
91
  return {A, B}
87
92
  }
@@ -50,6 +50,7 @@ export {
50
50
  ConstructionManager,
51
51
  } from './managers'
52
52
  export type {
53
+ PlayerRosterEntry,
53
54
  LocationStratum,
54
55
  NftConfigForItem,
55
56
  BuildableTarget,
@@ -206,11 +207,12 @@ export type {
206
207
  HasScheduleAndLocation,
207
208
  } from './travel/travel'
208
209
 
209
- export {planRoute, sdkSystemGraph, MAX_LEGS} from './travel/route-planner'
210
+ export {planRoute, sdkSystemGraph, setScanProvider, MAX_LEGS} from './travel/route-planner'
210
211
  export type {
211
212
  Coord,
212
213
  Neighbor,
213
214
  SystemGraph,
215
+ ScanProvider,
214
216
  RoutePlan,
215
217
  RouteFailure,
216
218
  RouteResult,
@@ -34,6 +34,15 @@ export class EntitiesManager extends BaseManager {
34
34
  return entities.map((e) => new Entity(e))
35
35
  }
36
36
 
37
+ async getAllEntities(kind?: EntityTypeName): Promise<ServerContract.Types.entity_row[]> {
38
+ const rows = await this.server.table('entity').all()
39
+ if (!kind) {
40
+ return rows
41
+ }
42
+ const wanted = Name.from(kind)
43
+ return rows.filter((row) => wanted.equals(row.kind))
44
+ }
45
+
37
46
  async getSummaries(
38
47
  owner: NameType | ServerContract.Types.player_row,
39
48
  kind?: EntityTypeName
@@ -3,6 +3,7 @@ export {BaseManager} from './base'
3
3
  export {EntitiesManager} from './entities'
4
4
  export type {EntityTypeName} from './entities'
5
5
  export {PlayersManager} from './players'
6
+ export type {PlayerRosterEntry} from './players'
6
7
  export {LocationsManager} from './locations'
7
8
  export type {LocationStratum} from './locations'
8
9
  export {EpochsManager} from './epochs'
@@ -2,6 +2,11 @@ import {Name, type NameType} from '@wharfkit/antelope'
2
2
  import {BaseManager} from './base'
3
3
  import {Player} from '../entities/player'
4
4
 
5
+ export interface PlayerRosterEntry {
6
+ owner: Name
7
+ company?: string
8
+ }
9
+
5
10
  export class PlayersManager extends BaseManager {
6
11
  async getPlayer(account: NameType): Promise<Player | undefined> {
7
12
  const playerRow = await this.server.table('player').get(Name.from(account))
@@ -10,4 +15,24 @@ export class PlayersManager extends BaseManager {
10
15
  }
11
16
  return new Player(playerRow)
12
17
  }
18
+
19
+ async getPlayers(): Promise<Player[]> {
20
+ const rows = await this.server.table('player').all()
21
+ return rows.map((row) => new Player(row))
22
+ }
23
+
24
+ async getRoster(): Promise<PlayerRosterEntry[]> {
25
+ const [players, companies] = await Promise.all([
26
+ this.server.table('player').all(),
27
+ this.platform.table('company').all(),
28
+ ])
29
+ const companyNames = new Map<string, string>()
30
+ for (const company of companies) {
31
+ companyNames.set(company.account.toString(), company.name)
32
+ }
33
+ return players.map((player) => ({
34
+ owner: player.owner,
35
+ company: companyNames.get(player.owner.toString()),
36
+ }))
37
+ }
13
38
  }
@@ -203,8 +203,8 @@ export function buildModuleImmutable(
203
203
  const ref = decodeStat(stats, 3)
204
204
  base.push({first: 'strength', second: ['uint16', str]})
205
205
  base.push({first: 'tolerance', second: ['uint16', tol]})
206
- base.push({first: 'conductivity', second: ['uint16', con]})
207
- base.push({first: 'reflectivity', second: ['uint16', ref]})
206
+ base.push({first: 'saturation', second: ['uint16', con]})
207
+ base.push({first: 'plasticity', second: ['uint16', ref]})
208
208
  base.push({first: 'yield', second: ['uint16', computeGathererYield(str)]})
209
209
  base.push({first: 'drain', second: ['uint16', computeGathererDrain(con)]})
210
210
  base.push({first: 'depth', second: ['uint16', computeGathererDepth(tol, item.tier)]})
@@ -0,0 +1,244 @@
1
+ import {SCAN_WASM_B64} from './scan-wasm.base64'
2
+
3
+ const stubImports: WebAssembly.Imports = new Proxy(
4
+ {},
5
+ {
6
+ get: () => new Proxy({}, {get: () => () => 0}),
7
+ }
8
+ ) as any
9
+
10
+ let inst: WebAssembly.Instance | null = null
11
+ let readyPromise: Promise<void> | null = null
12
+
13
+ function bytes(): Uint8Array {
14
+ return Uint8Array.from(atob(SCAN_WASM_B64), (c) => c.charCodeAt(0))
15
+ }
16
+
17
+ function finish(i: WebAssembly.Instance) {
18
+ const ex = i.exports as any
19
+ if (typeof ex._initialize === 'function') ex._initialize()
20
+ inst = i
21
+ }
22
+
23
+ export function scanReady(): Promise<void> {
24
+ if (inst) return Promise.resolve()
25
+ if (!readyPromise)
26
+ readyPromise = (
27
+ WebAssembly.instantiate(bytes().buffer as ArrayBuffer, stubImports) as Promise<{
28
+ instance: WebAssembly.Instance
29
+ }>
30
+ ).then((r) => finish(r.instance))
31
+ return readyPromise
32
+ }
33
+
34
+ function ex(): any {
35
+ if (!inst)
36
+ finish(
37
+ new WebAssembly.Instance(
38
+ new WebAssembly.Module(bytes().buffer as ArrayBuffer),
39
+ stubImports
40
+ )
41
+ )
42
+ return inst!.exports
43
+ }
44
+
45
+ const hex = (h: string) => Uint8Array.from(h.match(/../g)!.map((b) => parseInt(b, 16)))
46
+
47
+ // Shared marshalling for the `*_in_box` exports; grow-and-retry once on overflow (export returns -needed).
48
+ function boxScan<T>(
49
+ exportName: string,
50
+ stride: number,
51
+ gameSeed: string,
52
+ xMin: number,
53
+ yMin: number,
54
+ xMax: number,
55
+ yMax: number,
56
+ decode: (dv: DataView, o: number) => T
57
+ ): T[] {
58
+ const e = ex()
59
+ const mem = e.memory as WebAssembly.Memory
60
+ const g = e.malloc(32)
61
+ new Uint8Array(mem.buffer, g, 32).set(hex(gameSeed))
62
+ let cap = 256
63
+ let out = e.malloc(cap * stride)
64
+ let n = e[exportName](g, xMin, yMin, xMax, yMax, out, cap)
65
+ if (n < 0) {
66
+ e.free(out)
67
+ cap = -n
68
+ out = e.malloc(cap * stride)
69
+ n = e[exportName](g, xMin, yMin, xMax, yMax, out, cap)
70
+ }
71
+ const dv = new DataView(mem.buffer, out, n * stride)
72
+ const res: T[] = []
73
+ for (let i = 0; i < n; i++) res.push(decode(dv, i * stride))
74
+ e.free(g)
75
+ e.free(out)
76
+ return res
77
+ }
78
+
79
+ export function getLocationType(gameSeed: string, x: number, y: number): number {
80
+ const e = ex()
81
+ const mem = e.memory as WebAssembly.Memory
82
+ const g = e.malloc(32)
83
+ new Uint8Array(mem.buffer, g, 32).set(hex(gameSeed))
84
+ const t = e.get_location_type(g, BigInt(x), BigInt(y))
85
+ e.free(g)
86
+ return t
87
+ }
88
+
89
+ export interface SystemCell {
90
+ x: number
91
+ y: number
92
+ locType: number
93
+ }
94
+
95
+ export interface Coord {
96
+ x: number
97
+ y: number
98
+ }
99
+
100
+ export interface Deposit {
101
+ x: number
102
+ y: number
103
+ depth: number
104
+ itemId: number
105
+ richness: number
106
+ reserve: number
107
+ stats: [number, number, number]
108
+ }
109
+
110
+ export interface DerivedCell {
111
+ location: {x: number; y: number; locType: number; subtype: number; size: number}
112
+ deposits: Deposit[]
113
+ }
114
+
115
+ export function systemsInBox(
116
+ gameSeed: string,
117
+ xMin: number,
118
+ yMin: number,
119
+ xMax: number,
120
+ yMax: number
121
+ ): SystemCell[] {
122
+ return boxScan('systems_in_box', 12, gameSeed, xMin, yMin, xMax, yMax, (dv, o) => ({
123
+ x: dv.getInt32(o, true),
124
+ y: dv.getInt32(o + 4, true),
125
+ locType: dv.getUint32(o + 8, true),
126
+ }))
127
+ }
128
+
129
+ export interface LocationCell {
130
+ x: number
131
+ y: number
132
+ locType: number
133
+ subtype: number
134
+ size: number
135
+ }
136
+
137
+ export function locationsInBox(
138
+ gameSeed: string,
139
+ xMin: number,
140
+ yMin: number,
141
+ xMax: number,
142
+ yMax: number
143
+ ): LocationCell[] {
144
+ return boxScan('locations_in_box', 16, gameSeed, xMin, yMin, xMax, yMax, (dv, o) => ({
145
+ x: dv.getInt32(o, true),
146
+ y: dv.getInt32(o + 4, true),
147
+ locType: dv.getUint8(o + 8),
148
+ subtype: dv.getUint8(o + 9),
149
+ size: dv.getUint32(o + 12, true),
150
+ }))
151
+ }
152
+
153
+ export interface WormholeCell {
154
+ x: number
155
+ y: number
156
+ exit: {x: number; y: number}
157
+ }
158
+
159
+ export function wormholesInBox(
160
+ gameSeed: string,
161
+ xMin: number,
162
+ yMin: number,
163
+ xMax: number,
164
+ yMax: number
165
+ ): WormholeCell[] {
166
+ return boxScan('wormholes_in_box', 16, gameSeed, xMin, yMin, xMax, yMax, (dv, o) => ({
167
+ x: dv.getInt32(o, true),
168
+ y: dv.getInt32(o + 4, true),
169
+ exit: {x: dv.getInt32(o + 8, true), y: dv.getInt32(o + 12, true)},
170
+ }))
171
+ }
172
+
173
+ export async function scanCells(
174
+ gameSeed: string,
175
+ epochSeed: string,
176
+ cells: Coord[]
177
+ ): Promise<DerivedCell[]> {
178
+ await scanReady()
179
+ return scanCellsCore(gameSeed, epochSeed, cells)
180
+ }
181
+
182
+ // Sync sibling of scanCells; caller must warm the instance via scanReady() first.
183
+ export function scanCellsSync(gameSeed: string, epochSeed: string, cells: Coord[]): DerivedCell[] {
184
+ return scanCellsCore(gameSeed, epochSeed, cells)
185
+ }
186
+
187
+ function scanCellsCore(gameSeed: string, epochSeed: string, cells: Coord[]): DerivedCell[] {
188
+ const e = ex()
189
+ const mem = e.memory as WebAssembly.Memory
190
+ const write = (b: Uint8Array) => {
191
+ const p = e.malloc(b.length)
192
+ new Uint8Array(mem.buffer, p, b.length).set(b)
193
+ return p
194
+ }
195
+ const gp = write(hex(gameSeed))
196
+ const ep = write(hex(epochSeed))
197
+ const cellArr = new Int32Array(cells.length * 2)
198
+ cells.forEach((c, i) => {
199
+ cellArr[i * 2] = c.x
200
+ cellArr[i * 2 + 1] = c.y
201
+ })
202
+ const cp = write(new Uint8Array(cellArr.buffer))
203
+ const locOut = e.malloc(cells.length * 8)
204
+ let cap = Math.max(64, cells.length * 8)
205
+ let depOut = e.malloc(cap * 40)
206
+ let n = e.scan_cells(gp, ep, cp, cells.length, locOut, depOut, cap)
207
+ if (n < 0) {
208
+ e.free(depOut)
209
+ cap = -n
210
+ depOut = e.malloc(cap * 40)
211
+ n = e.scan_cells(gp, ep, cp, cells.length, locOut, depOut, cap)
212
+ }
213
+ const locView = new DataView(mem.buffer, locOut, cells.length * 8)
214
+ const depView = new DataView(mem.buffer, depOut, n * 40)
215
+ const out: DerivedCell[] = cells.map((c, i) => ({
216
+ location: {
217
+ x: c.x,
218
+ y: c.y,
219
+ locType: locView.getUint8(i * 8),
220
+ subtype: locView.getUint8(i * 8 + 1),
221
+ size: locView.getUint32(i * 8 + 4, true),
222
+ },
223
+ deposits: [],
224
+ }))
225
+ for (let i = 0; i < n; i++) {
226
+ const o = i * 40
227
+ const ci = depView.getUint32(o, true)
228
+ out[ci].deposits.push({
229
+ x: cells[ci].x,
230
+ y: cells[ci].y,
231
+ depth: depView.getUint32(o + 4, true),
232
+ itemId: depView.getUint32(o + 8, true),
233
+ richness: depView.getUint32(o + 12, true),
234
+ reserve: depView.getFloat64(o + 32, true),
235
+ stats: [
236
+ depView.getUint32(o + 16, true),
237
+ depView.getUint32(o + 20, true),
238
+ depView.getUint32(o + 24, true),
239
+ ],
240
+ })
241
+ }
242
+ for (const p of [gp, ep, cp, locOut, depOut]) e.free(p)
243
+ return out
244
+ }