@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/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 +19 -3
- package/lib/shipload.js +119 -21
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +119 -22
- package/lib/shipload.m.js.map +1 -1
- package/package.json +6 -1
- package/src/data/recipes.json +1 -1
- package/src/derivation/capabilities.test.ts +4 -4
- package/src/derivation/capabilities.ts +1 -1
- package/src/derivation/recipe-usage.test.ts +8 -9
- package/src/derivation/resources.ts +2 -2
- package/src/derivation/wormhole.ts +5 -0
- package/src/index-module.ts +3 -1
- package/src/managers/entities.ts +9 -0
- package/src/managers/index.ts +1 -0
- package/src/managers/players.ts +25 -0
- package/src/nft/buildImmutableData.ts +2 -2
- package/src/scan/index.ts +244 -0
- package/src/scan/scan-wasm.base64.ts +2 -0
- package/src/travel/route-planner.ts +82 -1
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.
|
|
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": [
|
package/src/data/recipes.json
CHANGED
|
@@ -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,
|
|
25
|
-
return encodeStats([strength, tolerance,
|
|
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,
|
|
76
|
-
const caps2 = computeGathererCapabilities({strength: 500, tolerance: 100,
|
|
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)
|
|
@@ -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
|
-
[
|
|
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('
|
|
36
|
-
const consumers = getRecipeConsumers(
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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.
|
|
19
|
-
export const YIELD_FRACTION_DEEP = 0.
|
|
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
|
}
|
package/src/index-module.ts
CHANGED
|
@@ -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,
|
package/src/managers/entities.ts
CHANGED
|
@@ -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
|
package/src/managers/index.ts
CHANGED
|
@@ -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'
|
package/src/managers/players.ts
CHANGED
|
@@ -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: '
|
|
207
|
-
base.push({first: '
|
|
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
|
+
}
|