@shipload/sdk 1.0.0-next.6 → 1.0.0-next.61

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 (156) hide show
  1. package/lib/scan.d.ts +52 -0
  2. package/lib/scan.js +162 -0
  3. package/lib/scan.js.map +1 -0
  4. package/lib/scan.m.js +152 -0
  5. package/lib/scan.m.js.map +1 -0
  6. package/lib/shipload.d.ts +3601 -1364
  7. package/lib/shipload.js +15671 -5261
  8. package/lib/shipload.js.map +1 -1
  9. package/lib/shipload.m.js +15392 -5209
  10. package/lib/shipload.m.js.map +1 -1
  11. package/lib/testing.d.ts +1173 -0
  12. package/lib/testing.js +4971 -0
  13. package/lib/testing.js.map +1 -0
  14. package/lib/testing.m.js +4965 -0
  15. package/lib/testing.m.js.map +1 -0
  16. package/package.json +20 -2
  17. package/src/capabilities/craftable.test.ts +82 -0
  18. package/src/capabilities/craftable.ts +82 -0
  19. package/src/capabilities/crafting.test.ts +35 -0
  20. package/src/capabilities/crafting.ts +34 -7
  21. package/src/capabilities/gathering.test.ts +29 -0
  22. package/src/capabilities/gathering.ts +37 -19
  23. package/src/capabilities/hauling.ts +0 -5
  24. package/src/capabilities/index.ts +0 -1
  25. package/src/capabilities/modules.ts +27 -30
  26. package/src/capabilities/movement.ts +1 -1
  27. package/src/capabilities/storage.ts +16 -1
  28. package/src/contracts/platform.ts +231 -3
  29. package/src/contracts/server.ts +1518 -501
  30. package/src/coordinates/address.ts +88 -0
  31. package/src/coordinates/constants.test.ts +15 -0
  32. package/src/coordinates/constants.ts +23 -0
  33. package/src/coordinates/index.ts +15 -0
  34. package/src/coordinates/memo.test.ts +47 -0
  35. package/src/coordinates/memo.ts +20 -0
  36. package/src/coordinates/permutation.ts +77 -0
  37. package/src/coordinates/regions.ts +48 -0
  38. package/src/coordinates/sectors.ts +115 -0
  39. package/src/data/capabilities.ts +49 -18
  40. package/src/data/capability-formulas.ts +26 -14
  41. package/src/data/catalog.ts +7 -8
  42. package/src/data/colors.ts +14 -48
  43. package/src/data/entities.json +380 -15
  44. package/src/data/item-ids.ts +51 -13
  45. package/src/data/items.json +318 -77
  46. package/src/data/kind-registry.json +195 -0
  47. package/src/data/kind-registry.ts +182 -0
  48. package/src/data/metadata.ts +291 -51
  49. package/src/data/recipes-runtime.ts +6 -23
  50. package/src/data/recipes.json +2260 -173
  51. package/src/data/tiers.ts +9 -6
  52. package/src/derivation/build-methods.test.ts +13 -0
  53. package/src/derivation/build-methods.ts +48 -0
  54. package/src/derivation/capabilities.test.ts +348 -0
  55. package/src/derivation/capabilities.ts +693 -0
  56. package/src/derivation/capability-mappings.ts +60 -16
  57. package/src/derivation/crafting.test.ts +17 -0
  58. package/src/derivation/crafting.ts +57 -34
  59. package/src/derivation/index.ts +27 -2
  60. package/src/derivation/recipe-usage.test.ts +90 -0
  61. package/src/derivation/recipe-usage.ts +141 -0
  62. package/src/derivation/reserve-regen.ts +34 -0
  63. package/src/derivation/resources.ts +9 -1
  64. package/src/derivation/rollups.test.ts +55 -0
  65. package/src/derivation/rollups.ts +72 -0
  66. package/src/derivation/stars.test.ts +67 -0
  67. package/src/derivation/stars.ts +25 -0
  68. package/src/derivation/stat-scaling.ts +12 -0
  69. package/src/derivation/stats.ts +6 -6
  70. package/src/derivation/stratum.ts +26 -22
  71. package/src/derivation/tiers.ts +40 -7
  72. package/src/derivation/upgrades.ts +14 -0
  73. package/src/derivation/wormhole.ts +154 -0
  74. package/src/entities/entity.ts +102 -0
  75. package/src/entities/gamestate.ts +3 -28
  76. package/src/entities/makers.ts +141 -135
  77. package/src/entities/slot-multiplier.ts +43 -0
  78. package/src/errors.ts +12 -16
  79. package/src/format.ts +26 -4
  80. package/src/index-module.ts +333 -35
  81. package/src/managers/actions.ts +668 -103
  82. package/src/managers/base.ts +6 -2
  83. package/src/managers/cluster.test.ts +39 -0
  84. package/src/managers/cluster.ts +53 -0
  85. package/src/managers/construction-types.ts +80 -0
  86. package/src/managers/construction.ts +443 -0
  87. package/src/managers/context.ts +29 -1
  88. package/src/managers/coordinates.ts +14 -0
  89. package/src/managers/entities.ts +27 -66
  90. package/src/managers/epochs.ts +40 -0
  91. package/src/managers/index.ts +20 -1
  92. package/src/managers/locations.ts +25 -29
  93. package/src/managers/nft.test.ts +14 -0
  94. package/src/managers/nft.ts +70 -0
  95. package/src/managers/players.ts +25 -0
  96. package/src/managers/plot.ts +122 -0
  97. package/src/nft/atomicassets.abi.json +1342 -0
  98. package/src/nft/atomicassets.ts +237 -0
  99. package/src/nft/atomicdata.ts +130 -0
  100. package/src/nft/buildImmutableData.ts +388 -0
  101. package/src/nft/description.ts +215 -99
  102. package/src/nft/index.ts +3 -0
  103. package/src/planner/index.ts +242 -0
  104. package/src/planner/planner.test.ts +334 -0
  105. package/src/resolution/describe-module.ts +43 -25
  106. package/src/resolution/display-name.ts +38 -10
  107. package/src/resolution/resolve-item.test.ts +48 -0
  108. package/src/resolution/resolve-item.ts +112 -58
  109. package/src/scan/index.ts +244 -0
  110. package/src/scan/scan-wasm.base64.ts +2 -0
  111. package/src/scheduling/accessor.ts +65 -23
  112. package/src/scheduling/availability.test.ts +204 -0
  113. package/src/scheduling/availability.ts +211 -0
  114. package/src/scheduling/cancel.test.ts +542 -0
  115. package/src/scheduling/cancel.ts +254 -0
  116. package/src/scheduling/cluster-stock.test.ts +31 -0
  117. package/src/scheduling/cluster-stock.ts +15 -0
  118. package/src/scheduling/energy.ts +47 -0
  119. package/src/scheduling/idle-resolve.ts +45 -0
  120. package/src/scheduling/jobs.ts +71 -0
  121. package/src/scheduling/lane-core.ts +128 -0
  122. package/src/scheduling/lanes.test.ts +250 -0
  123. package/src/scheduling/lanes.ts +227 -0
  124. package/src/scheduling/projection.ts +257 -133
  125. package/src/scheduling/schedule.test.ts +119 -0
  126. package/src/scheduling/schedule.ts +257 -117
  127. package/src/scheduling/task-cargo.test.ts +44 -0
  128. package/src/scheduling/task-cargo.ts +46 -0
  129. package/src/scheduling/unwrap.test.ts +86 -0
  130. package/src/scheduling/unwrap.ts +190 -0
  131. package/src/shipload.ts +26 -1
  132. package/src/subscriptions/manager.cluster.test.ts +51 -0
  133. package/src/subscriptions/manager.ts +245 -172
  134. package/src/subscriptions/mappers.ts +5 -8
  135. package/src/subscriptions/types.ts +28 -3
  136. package/src/testing/catalog-hash.ts +19 -0
  137. package/src/testing/index.ts +2 -0
  138. package/src/testing/projection-parity.ts +167 -0
  139. package/src/travel/reach.ts +21 -0
  140. package/src/travel/route-planner.ts +288 -0
  141. package/src/travel/route-simulator.ts +170 -0
  142. package/src/travel/travel.ts +188 -122
  143. package/src/types/capabilities.ts +37 -8
  144. package/src/types/entity.ts +3 -3
  145. package/src/types/index.ts +0 -1
  146. package/src/types.ts +51 -16
  147. package/src/utils/cargo.test.ts +14 -0
  148. package/src/utils/cargo.ts +29 -0
  149. package/src/utils/display-name.ts +70 -0
  150. package/src/utils/system.ts +15 -16
  151. package/src/capabilities/loading.ts +0 -8
  152. package/src/entities/container.ts +0 -123
  153. package/src/entities/ship-deploy.ts +0 -295
  154. package/src/entities/ship.ts +0 -221
  155. package/src/entities/warehouse.ts +0 -127
  156. package/src/types/entity-traits.ts +0 -69
@@ -1,14 +1,17 @@
1
1
  import {UInt16, UInt64} from '@wharfkit/antelope'
2
2
  import type {UInt16Type, UInt64Type} from '@wharfkit/antelope'
3
3
  import type {ResourceCategory} from '../types'
4
- import {getItem} from '../data/catalog'
4
+ import {getItem, getModules} from '../data/catalog'
5
+ import {ENTITY_SHIP, getPackedEntityType} from '../data/kind-registry'
5
6
  import {getEntityLayout} from '../data/recipes-runtime'
6
7
  import {entityMetadata, itemMetadata} from '../data/metadata'
7
8
  import {
8
9
  getModuleCapabilityType,
9
10
  isModuleItem,
11
+ MODULE_BUILDER,
10
12
  MODULE_CRAFTER,
11
13
  MODULE_ENGINE,
14
+ MODULE_BATTERY,
12
15
  MODULE_GATHERER,
13
16
  MODULE_GENERATOR,
14
17
  MODULE_HAULER,
@@ -19,29 +22,21 @@ import {decodeCraftedItemStats, decodeStat} from '../derivation/crafting'
19
22
  import {getStatDefinitions} from '../derivation/stats'
20
23
  import {
21
24
  computeCrafterCapabilities,
25
+ computeBuilderCapabilities,
26
+ computeBatteryCapabilities,
22
27
  computeEngineCapabilities,
23
28
  computeGathererCapabilities,
24
29
  computeGeneratorCapabilities,
25
30
  computeHaulerCapabilities,
26
31
  computeLoaderCapabilities,
27
- computeShipHullCapabilities,
28
- computeWarehouseHullCapabilities,
29
- } from '../entities/ship-deploy'
30
- import {computeContainerCapabilities, computeContainerT2Capabilities} from '../entities/container'
31
- import {
32
- categoryColors,
33
- categoryIcons,
34
- componentIcon,
35
- itemAbbreviations,
36
- moduleIcon,
37
- } from '../data/colors'
32
+ computeBaseCapacity,
33
+ computeBaseHullmass,
34
+ computeStorageCapabilities,
35
+ } from '../derivation/capabilities'
36
+ import {applySlotMultiplierUint32} from '../entities/slot-multiplier'
37
+ import {toWholeEnergy} from '../nft/description'
38
+ import {categoryColors, componentIcon, itemAbbreviations, moduleIcon} from '../data/colors'
38
39
  import type {ServerContract} from '../contracts'
39
- import {
40
- ITEM_CONTAINER_T1_PACKED,
41
- ITEM_CONTAINER_T2_PACKED,
42
- ITEM_SHIP_T1_PACKED,
43
- ITEM_WAREHOUSE_T1_PACKED,
44
- } from '../data/item-ids'
45
40
 
46
41
  export interface ResolvedItemStat {
47
42
  key: string
@@ -62,8 +57,13 @@ export type ResolvedItemType = 'resource' | 'component' | 'module' | 'entity'
62
57
 
63
58
  export interface ResolvedModuleSlot {
64
59
  name?: string
60
+ capability?: string
65
61
  installed: boolean
66
62
  attributes?: {label: string; value: number}[]
63
+ slotLabel?: string
64
+ slotType?: string
65
+ outputPct?: number
66
+ maxTier?: number
67
67
  }
68
68
 
69
69
  export interface ResolvedItem {
@@ -109,7 +109,7 @@ function resolveResource(id: number, stats?: UInt64Type): ResolvedItem {
109
109
  return {
110
110
  itemId: id,
111
111
  name: item.name,
112
- icon: cat ? categoryIcons[cat] : '',
112
+ icon: '',
113
113
  abbreviation: null,
114
114
  category: cat,
115
115
  tier: item.tier,
@@ -156,11 +156,12 @@ function resolveComponent(id: number, stats?: UInt64Type): ResolvedItem {
156
156
  function computeCapabilityGroup(
157
157
  moduleType: number,
158
158
  stats: Record<string, number>,
159
- tier: number
159
+ tier: number,
160
+ outputPct = 100
160
161
  ): ResolvedAttributeGroup | undefined {
161
162
  switch (moduleType) {
162
163
  case MODULE_ENGINE: {
163
- const caps = computeEngineCapabilities(stats)
164
+ const caps = computeEngineCapabilities(stats, tier)
164
165
  return {
165
166
  capability: 'Engine',
166
167
  attributes: [
@@ -170,12 +171,12 @@ function computeCapabilityGroup(
170
171
  }
171
172
  }
172
173
  case MODULE_GENERATOR: {
173
- const caps = computeGeneratorCapabilities(stats)
174
+ const caps = computeGeneratorCapabilities(stats, tier)
174
175
  return {
175
176
  capability: 'Generator',
176
177
  attributes: [
177
- {label: 'Capacity', value: caps.capacity},
178
- {label: 'Recharge', value: caps.recharge},
178
+ {label: 'Capacity', value: toWholeEnergy(caps.capacity)},
179
+ {label: 'Recharge', value: toWholeEnergy(caps.recharge)},
179
180
  ],
180
181
  }
181
182
  }
@@ -185,16 +186,15 @@ function computeCapabilityGroup(
185
186
  capability: 'Gatherer',
186
187
  attributes: [
187
188
  {label: 'Yield', value: caps.yield},
188
- {label: 'Drain', value: caps.drain},
189
+ {label: 'Drain', value: toWholeEnergy(caps.drain)},
189
190
  {label: 'Depth', value: caps.depth},
190
- {label: 'Speed', value: caps.speed},
191
191
  ],
192
192
  }
193
193
  }
194
194
  case MODULE_LOADER: {
195
- const caps = computeLoaderCapabilities(stats)
195
+ const caps = computeLoaderCapabilities(stats, tier)
196
196
  return {
197
- capability: 'Loader',
197
+ capability: 'Loading',
198
198
  attributes: [
199
199
  {label: 'Mass', value: caps.mass},
200
200
  {label: 'Thrust', value: caps.thrust},
@@ -203,45 +203,82 @@ function computeCapabilityGroup(
203
203
  }
204
204
  }
205
205
  case MODULE_CRAFTER: {
206
- const caps = computeCrafterCapabilities(stats)
206
+ const caps = computeCrafterCapabilities(stats, tier)
207
207
  return {
208
- capability: 'Crafter',
208
+ capability: 'Crafting',
209
209
  attributes: [
210
210
  {label: 'Speed', value: caps.speed},
211
- {label: 'Drain', value: caps.drain},
211
+ {label: 'Drain', value: toWholeEnergy(caps.drain)},
212
+ ],
213
+ }
214
+ }
215
+ case MODULE_BUILDER: {
216
+ const caps = computeBuilderCapabilities(stats, tier)
217
+ return {
218
+ capability: 'Build',
219
+ attributes: [
220
+ {label: 'Speed', value: caps.speed},
221
+ {label: 'Drain', value: toWholeEnergy(caps.drain)},
212
222
  ],
213
223
  }
214
224
  }
215
225
  case MODULE_HAULER: {
216
- const caps = computeHaulerCapabilities(stats)
226
+ const caps = computeHaulerCapabilities(stats, tier)
217
227
  return {
218
- capability: 'Hauler',
228
+ capability: 'Hauling',
219
229
  attributes: [
220
230
  {label: 'Capacity', value: caps.capacity},
221
231
  {label: 'Efficiency', value: caps.efficiency},
222
- {label: 'Drain', value: caps.drain},
232
+ {label: 'Drain', value: toWholeEnergy(caps.drain)},
223
233
  ],
224
234
  }
225
235
  }
226
236
  case MODULE_STORAGE: {
227
- const str = stats.strength
228
- const den = stats.density
229
- const hrd = stats.hardness
230
- const sat = stats.saturation
231
- const statSum = str + den + hrd + sat
232
- const pct = 10 + Math.floor((statSum * 10) / 2997)
233
- return {capability: 'Storage', attributes: [{label: 'Capacity Bonus', value: pct}]}
237
+ const caps = computeStorageCapabilities(stats, tier)
238
+ return {
239
+ capability: 'Storage',
240
+ attributes: [
241
+ {
242
+ label: 'Cargo Capacity',
243
+ value: applySlotMultiplierUint32(caps.capacity, outputPct),
244
+ },
245
+ {label: 'Drain', value: toWholeEnergy(caps.drain)},
246
+ ],
247
+ }
248
+ }
249
+ case MODULE_BATTERY: {
250
+ const caps = computeBatteryCapabilities(stats, tier)
251
+ return {
252
+ capability: 'Energy',
253
+ attributes: [
254
+ {
255
+ label: 'Energy Capacity',
256
+ value: toWholeEnergy(applySlotMultiplierUint32(caps.capacity, outputPct)),
257
+ },
258
+ ],
259
+ }
234
260
  }
235
261
  default:
236
262
  return undefined
237
263
  }
238
264
  }
239
265
 
266
+ // Recipe-less higher-tier modules reuse the T1 stat layout of their module type.
267
+ function decodeModuleStats(id: number, big: bigint): Record<string, number> {
268
+ const decoded = decodeCraftedItemStats(id, big)
269
+ if (Object.keys(decoded).length > 0) return decoded
270
+ const moduleType = getItem(id).moduleType
271
+ if (!moduleType) return decoded
272
+ const t1 = getModules({moduleType, tier: 1})[0]
273
+ if (!t1) return decoded
274
+ return decodeCraftedItemStats(Number(t1.id), big)
275
+ }
276
+
240
277
  function resolveModule(id: number, stats?: UInt64Type): ResolvedItem {
241
278
  const item = getItem(id)
242
279
  let attributes: ResolvedAttributeGroup[] | undefined
243
280
  if (stats !== undefined) {
244
- const decoded = decodeCraftedItemStats(id, toBigStats(stats))
281
+ const decoded = decodeModuleStats(id, toBigStats(stats))
245
282
  const modType = getModuleCapabilityType(id)
246
283
  const group = computeCapabilityGroup(modType, decoded, item.tier)
247
284
  if (group) attributes = [group]
@@ -260,22 +297,26 @@ function resolveModule(id: number, stats?: UInt64Type): ResolvedItem {
260
297
 
261
298
  function hullCapsForEntity(
262
299
  itemId: number,
263
- decoded: Record<string, number>
300
+ decoded: Record<string, number>,
301
+ packedStats: bigint
264
302
  ): {
265
303
  hullmass: number
266
304
  capacity: number
267
305
  } {
268
- switch (itemId) {
269
- case ITEM_SHIP_T1_PACKED:
270
- return computeShipHullCapabilities(decoded)
271
- case ITEM_WAREHOUSE_T1_PACKED:
272
- return computeWarehouseHullCapabilities(decoded)
273
- case ITEM_CONTAINER_T1_PACKED:
274
- return computeContainerCapabilities(decoded)
275
- case ITEM_CONTAINER_T2_PACKED:
276
- return computeContainerT2Capabilities(decoded)
277
- default:
278
- throw new Error(`resolveItem: no capacity formula wired for entity item ${itemId}`)
306
+ // A ship hull consumes all five positional packed channels. This is separate
307
+ // from the recipe-facing stat labels, which remain historical for Ship T1.
308
+ const hullStats = getPackedEntityType(itemId)?.equals(ENTITY_SHIP)
309
+ ? {
310
+ strength: decodeStat(packedStats, 0),
311
+ hardness: decodeStat(packedStats, 1),
312
+ plasticity: decodeStat(packedStats, 2),
313
+ volatility: decodeStat(packedStats, 3),
314
+ conductivity: decodeStat(packedStats, 4),
315
+ }
316
+ : decoded
317
+ return {
318
+ hullmass: computeBaseHullmass(itemId, hullStats),
319
+ capacity: computeBaseCapacity(itemId, hullStats),
279
320
  }
280
321
  }
281
322
 
@@ -290,8 +331,12 @@ function resolveEntity(
290
331
  let moduleSlots: ResolvedModuleSlot[] | undefined
291
332
 
292
333
  if (stats !== undefined) {
293
- const decoded = decodeCraftedItemStats(id, toBigStats(stats))
294
- const hullCaps = hullCapsForEntity(id, decoded)
334
+ const bigStats = toBigStats(stats)
335
+ const decoded = decodeCraftedItemStats(id, bigStats)
336
+ if (decoded.strength === undefined) decoded.strength = decodeStat(bigStats, 0)
337
+ if (decoded.density === undefined) decoded.density = decodeStat(bigStats, 1)
338
+ if (decoded.hardness === undefined) decoded.hardness = decodeStat(bigStats, 2)
339
+ const hullCaps = hullCapsForEntity(id, decoded, bigStats)
295
340
  attributes = [
296
341
  {
297
342
  capability: 'Hull',
@@ -306,11 +351,17 @@ function resolveEntity(
306
351
  if (layout && layout.slots.length > 0) {
307
352
  const slotLabels = entityMetadata[id]?.moduleSlotLabels ?? []
308
353
  moduleSlots = layout.slots.map((slot, i) => {
354
+ const slotInfo = {
355
+ slotLabel: slotLabels[i] ?? slot.type,
356
+ slotType: slot.type,
357
+ outputPct: slot.outputPct,
358
+ maxTier: slot.maxTier,
359
+ }
309
360
  const mod = modules?.[i]
310
361
  if (mod?.installed) {
311
362
  const modItemId = Number(mod.installed.item_id.value.toString())
312
363
  const modStats = BigInt(mod.installed.stats.toString())
313
- const decodedStats = decodeCraftedItemStats(modItemId, modStats)
364
+ const decodedStats = decodeModuleStats(modItemId, modStats)
314
365
  const modType = getModuleCapabilityType(modItemId)
315
366
  let modName = 'Module'
316
367
  let modTier = 1
@@ -321,16 +372,19 @@ function resolveEntity(
321
372
  } catch {
322
373
  modName = itemMetadata[modItemId]?.name ?? 'Module'
323
374
  }
324
- const group = computeCapabilityGroup(modType, decodedStats, modTier)
375
+ const group = computeCapabilityGroup(modType, decodedStats, modTier, slot.outputPct)
325
376
  return {
326
377
  name: modName,
378
+ capability: group?.capability,
327
379
  installed: true,
328
380
  attributes: group?.attributes,
381
+ ...slotInfo,
329
382
  }
330
383
  }
331
384
  return {
332
385
  name: slotLabels[i] ?? slot.type,
333
386
  installed: false,
387
+ ...slotInfo,
334
388
  }
335
389
  })
336
390
  }
@@ -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
+ }