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

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 (155) 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 +3589 -1363
  7. package/lib/shipload.js +15700 -5454
  8. package/lib/shipload.js.map +1 -1
  9. package/lib/shipload.m.js +15423 -5402
  10. package/lib/shipload.m.js.map +1 -1
  11. package/lib/testing.d.ts +1173 -0
  12. package/lib/testing.js +4947 -0
  13. package/lib/testing.js.map +1 -0
  14. package/lib/testing.m.js +4941 -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 +42 -0
  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 +0 -5
  42. package/src/data/colors.ts +13 -47
  43. package/src/data/entities.json +379 -14
  44. package/src/data/item-ids.ts +48 -12
  45. package/src/data/items.json +301 -74
  46. package/src/data/kind-registry.json +195 -0
  47. package/src/data/kind-registry.ts +182 -0
  48. package/src/data/metadata.ts +277 -48
  49. package/src/data/recipes-runtime.ts +6 -23
  50. package/src/data/recipes.json +2074 -173
  51. package/src/derivation/build-methods.test.ts +13 -0
  52. package/src/derivation/build-methods.ts +48 -0
  53. package/src/derivation/capabilities.test.ts +348 -0
  54. package/src/derivation/capabilities.ts +692 -0
  55. package/src/derivation/capability-mappings.ts +60 -16
  56. package/src/derivation/crafting.test.ts +17 -0
  57. package/src/derivation/crafting.ts +56 -33
  58. package/src/derivation/index.ts +27 -2
  59. package/src/derivation/recipe-usage.test.ts +88 -0
  60. package/src/derivation/recipe-usage.ts +141 -0
  61. package/src/derivation/reserve-regen.ts +34 -0
  62. package/src/derivation/resources.ts +9 -1
  63. package/src/derivation/rollups.test.ts +55 -0
  64. package/src/derivation/rollups.ts +72 -0
  65. package/src/derivation/stars.test.ts +67 -0
  66. package/src/derivation/stars.ts +25 -0
  67. package/src/derivation/stat-scaling.ts +12 -0
  68. package/src/derivation/stats.ts +6 -6
  69. package/src/derivation/stratum.ts +26 -22
  70. package/src/derivation/tiers.ts +40 -7
  71. package/src/derivation/upgrades.ts +14 -0
  72. package/src/derivation/wormhole.ts +154 -0
  73. package/src/entities/entity.ts +102 -0
  74. package/src/entities/gamestate.ts +3 -28
  75. package/src/entities/makers.ts +141 -135
  76. package/src/entities/slot-multiplier.ts +43 -0
  77. package/src/errors.ts +12 -16
  78. package/src/format.ts +26 -4
  79. package/src/index-module.ts +333 -35
  80. package/src/managers/actions.ts +668 -103
  81. package/src/managers/base.ts +6 -2
  82. package/src/managers/cluster.test.ts +39 -0
  83. package/src/managers/cluster.ts +53 -0
  84. package/src/managers/construction-types.ts +80 -0
  85. package/src/managers/construction.ts +443 -0
  86. package/src/managers/context.ts +29 -1
  87. package/src/managers/coordinates.ts +14 -0
  88. package/src/managers/entities.ts +27 -66
  89. package/src/managers/epochs.ts +40 -0
  90. package/src/managers/index.ts +20 -1
  91. package/src/managers/locations.ts +25 -29
  92. package/src/managers/nft.test.ts +14 -0
  93. package/src/managers/nft.ts +70 -0
  94. package/src/managers/players.ts +25 -0
  95. package/src/managers/plot.ts +122 -0
  96. package/src/nft/atomicassets.abi.json +1342 -0
  97. package/src/nft/atomicassets.ts +237 -0
  98. package/src/nft/atomicdata.ts +130 -0
  99. package/src/nft/buildImmutableData.ts +385 -0
  100. package/src/nft/description.ts +225 -52
  101. package/src/nft/index.ts +3 -0
  102. package/src/planner/index.ts +242 -0
  103. package/src/planner/planner.test.ts +334 -0
  104. package/src/resolution/describe-module.ts +43 -25
  105. package/src/resolution/display-name.ts +38 -10
  106. package/src/resolution/resolve-item.test.ts +48 -0
  107. package/src/resolution/resolve-item.ts +112 -58
  108. package/src/scan/index.ts +244 -0
  109. package/src/scan/scan-wasm.base64.ts +2 -0
  110. package/src/scheduling/accessor.ts +65 -23
  111. package/src/scheduling/availability.test.ts +204 -0
  112. package/src/scheduling/availability.ts +211 -0
  113. package/src/scheduling/cancel.test.ts +542 -0
  114. package/src/scheduling/cancel.ts +254 -0
  115. package/src/scheduling/cluster-stock.test.ts +31 -0
  116. package/src/scheduling/cluster-stock.ts +15 -0
  117. package/src/scheduling/energy.ts +47 -0
  118. package/src/scheduling/idle-resolve.ts +45 -0
  119. package/src/scheduling/jobs.ts +71 -0
  120. package/src/scheduling/lane-core.ts +128 -0
  121. package/src/scheduling/lanes.test.ts +250 -0
  122. package/src/scheduling/lanes.ts +227 -0
  123. package/src/scheduling/projection.ts +257 -133
  124. package/src/scheduling/schedule.test.ts +119 -0
  125. package/src/scheduling/schedule.ts +257 -117
  126. package/src/scheduling/task-cargo.test.ts +44 -0
  127. package/src/scheduling/task-cargo.ts +46 -0
  128. package/src/scheduling/unwrap.test.ts +86 -0
  129. package/src/scheduling/unwrap.ts +190 -0
  130. package/src/shipload.ts +26 -1
  131. package/src/subscriptions/manager.cluster.test.ts +51 -0
  132. package/src/subscriptions/manager.ts +245 -172
  133. package/src/subscriptions/mappers.ts +5 -8
  134. package/src/subscriptions/types.ts +28 -3
  135. package/src/testing/catalog-hash.ts +19 -0
  136. package/src/testing/index.ts +2 -0
  137. package/src/testing/projection-parity.ts +167 -0
  138. package/src/travel/reach.ts +21 -0
  139. package/src/travel/route-planner.ts +288 -0
  140. package/src/travel/route-simulator.ts +170 -0
  141. package/src/travel/travel.ts +188 -122
  142. package/src/types/capabilities.ts +37 -8
  143. package/src/types/entity.ts +3 -3
  144. package/src/types/index.ts +0 -1
  145. package/src/types.ts +51 -16
  146. package/src/utils/cargo.test.ts +14 -0
  147. package/src/utils/cargo.ts +29 -0
  148. package/src/utils/display-name.ts +70 -0
  149. package/src/utils/system.ts +15 -16
  150. package/src/capabilities/loading.ts +0 -8
  151. package/src/entities/container.ts +0 -123
  152. package/src/entities/ship-deploy.ts +0 -295
  153. package/src/entities/ship.ts +0 -221
  154. package/src/entities/warehouse.ts +0 -127
  155. package/src/types/entity-traits.ts +0 -69
@@ -0,0 +1,88 @@
1
+ import type {Checksum256Type} from '@wharfkit/antelope'
2
+ import {
3
+ COORD_MAX,
4
+ COORD_MIN,
5
+ COORD_OFFSET,
6
+ LOCAL_HALF,
7
+ LOCAL_MAX,
8
+ LOCAL_MIN,
9
+ REGION_DIV,
10
+ REGION_PER_AXIS,
11
+ SECTOR_DIV,
12
+ } from './constants'
13
+ import {decodeRegion, encodeRegion} from './regions'
14
+ import {decodeSector, encodeSector} from './sectors'
15
+
16
+ export interface CoordinateAddress {
17
+ sector: string
18
+ region: string
19
+ localX: number
20
+ localY: number
21
+ }
22
+
23
+ interface AxisSlices {
24
+ sector: number
25
+ region: number
26
+ local: number
27
+ }
28
+
29
+ function sliceAxis(coord: number): AxisSlices {
30
+ if (!Number.isInteger(coord) || coord < COORD_MIN || coord > COORD_MAX) {
31
+ throw new RangeError(`coordinate out of range: ${coord}`)
32
+ }
33
+ const u = coord + COORD_OFFSET
34
+ return {
35
+ sector: Math.floor(u / SECTOR_DIV),
36
+ region: Math.floor(u / REGION_DIV) % REGION_PER_AXIS,
37
+ local: (u % REGION_DIV) - LOCAL_HALF,
38
+ }
39
+ }
40
+
41
+ export function encodeAddress(seed: Checksum256Type, x: number, y: number): CoordinateAddress {
42
+ const ax = sliceAxis(x)
43
+ const ay = sliceAxis(y)
44
+ return {
45
+ sector: encodeSector(seed, ax.sector, ay.sector),
46
+ region: encodeRegion(seed, ax.region, ay.region),
47
+ localX: ax.local,
48
+ localY: ay.local,
49
+ }
50
+ }
51
+
52
+ export function decodeAddress(
53
+ seed: Checksum256Type,
54
+ addr: CoordinateAddress
55
+ ): {x: number; y: number} {
56
+ if (
57
+ !Number.isInteger(addr.localX) ||
58
+ !Number.isInteger(addr.localY) ||
59
+ addr.localX < LOCAL_MIN ||
60
+ addr.localX > LOCAL_MAX ||
61
+ addr.localY < LOCAL_MIN ||
62
+ addr.localY > LOCAL_MAX
63
+ ) {
64
+ throw new RangeError(`local position out of range: ${addr.localX}, ${addr.localY}`)
65
+ }
66
+ const sector = decodeSector(seed, addr.sector)
67
+ const region = decodeRegion(seed, addr.region)
68
+ const x =
69
+ sector.sx * SECTOR_DIV + region.rx * REGION_DIV + (addr.localX + LOCAL_HALF) - COORD_OFFSET
70
+ const y =
71
+ sector.sy * SECTOR_DIV + region.ry * REGION_DIV + (addr.localY + LOCAL_HALF) - COORD_OFFSET
72
+ if (x < COORD_MIN || x > COORD_MAX || y < COORD_MIN || y > COORD_MAX) {
73
+ throw new RangeError(`address decodes outside the coordinate range: ${x}, ${y}`)
74
+ }
75
+ return {x, y}
76
+ }
77
+
78
+ export function addressFromCoordinates(
79
+ seed: Checksum256Type,
80
+ coords: {
81
+ x: number | {toNumber(): number}
82
+ y: number | {toNumber(): number}
83
+ }
84
+ ): CoordinateAddress {
85
+ const x = typeof coords.x === 'number' ? coords.x : coords.x.toNumber()
86
+ const y = typeof coords.y === 'number' ? coords.y : coords.y.toNumber()
87
+ return encodeAddress(seed, x, y)
88
+ }
@@ -0,0 +1,15 @@
1
+ import {describe, expect, test} from 'bun:test'
2
+ import * as sdk from '../index-module'
3
+
4
+ describe('coordinate constants re-export', () => {
5
+ test('frozen spatial constants are reachable from the package root', () => {
6
+ expect(sdk.REGION_DIV).toBe(10_000)
7
+ expect(sdk.SECTOR_DIV).toBe(100_000_000)
8
+ expect(sdk.COORD_OFFSET).toBe(2_147_485_000)
9
+ expect(sdk.SECTORS_PER_AXIS).toBe(43)
10
+ expect(sdk.REGION_PER_AXIS).toBe(10_000)
11
+ expect(sdk.LOCAL_HALF).toBe(5_000)
12
+ expect(sdk.COORD_MIN).toBe(-2_147_483_648)
13
+ expect(sdk.COORD_MAX).toBe(2_147_483_647)
14
+ })
15
+ })
@@ -0,0 +1,23 @@
1
+ import type {FeistelConfig} from './permutation'
2
+
3
+ // FROZEN INTERFACE — these values define the on-the-wire address format.
4
+ export const COORD_MIN = -2_147_483_648
5
+ export const COORD_MAX = 2_147_483_647
6
+ // Centers the region grid on the origin: raw (0,0) sits at a region center (local 0,0), not a corner; u stays >= 0 across int32.
7
+ export const COORD_OFFSET = 2_147_485_000
8
+ export const AXIS_SPAN = 4_294_967_296 // 2^32 distinct values per axis
9
+
10
+ export const SECTOR_DIV = 100_000_000 // 10^8 tiles per sector side
11
+ export const REGION_DIV = 10_000 // 10^4 tiles per region side
12
+ export const SECTORS_PER_AXIS = 43 // floor((2^32 - 1) / 10^8) + 1
13
+ export const REGION_PER_AXIS = 10_000
14
+ export const LOCAL_HALF = 5_000 // half a region; local is signed, measured from the region center
15
+ export const LOCAL_MIN = -5_000
16
+ export const LOCAL_MAX = 4_999
17
+
18
+ export const SECTOR_COUNT = SECTORS_PER_AXIS * SECTORS_PER_AXIS // 1849
19
+ export const REGION_COUNT = REGION_PER_AXIS * REGION_PER_AXIS // 100,000,000
20
+
21
+ // 2*halfBits must give a block domain >= the tier's count.
22
+ export const SECTOR_FEISTEL: FeistelConfig = {n: SECTOR_COUNT, halfBits: 6, label: 'sector'} // 2^12 = 4096
23
+ export const REGION_FEISTEL: FeistelConfig = {n: REGION_COUNT, halfBits: 14, label: 'region'} // 2^28 ≈ 2.68e8
@@ -0,0 +1,15 @@
1
+ export type {CoordinateAddress} from './address'
2
+ export {addressFromCoordinates, decodeAddress, encodeAddress} from './address'
3
+ export {encodeSector, decodeSector} from './sectors'
4
+ export {encodeRegion, decodeRegion} from './regions'
5
+ export {encodeAddressMemo} from './memo'
6
+ export {
7
+ COORD_MIN,
8
+ COORD_MAX,
9
+ COORD_OFFSET,
10
+ REGION_DIV,
11
+ SECTOR_DIV,
12
+ SECTORS_PER_AXIS,
13
+ REGION_PER_AXIS,
14
+ LOCAL_HALF,
15
+ } from './constants'
@@ -0,0 +1,47 @@
1
+ import {describe, expect, test} from 'bun:test'
2
+ import {Checksum256} from '@wharfkit/antelope'
3
+ import {encodeAddress, decodeAddress} from './address'
4
+ import {encodeAddressMemo} from './memo'
5
+
6
+ const SEED = Checksum256.from('73e4385a2708e6d704883c670c50b0aa502ba1098072ed100ae1920eee3597d9')
7
+
8
+ describe('encodeAddressMemo', () => {
9
+ test('matches encodeAddress', () => {
10
+ for (const [x, y] of [
11
+ [0, 0],
12
+ [12, -9],
13
+ [2_147_483_647, -2_147_483_648],
14
+ ] as const) {
15
+ expect(encodeAddressMemo(SEED, x, y)).toEqual(encodeAddress(SEED, x, y))
16
+ }
17
+ })
18
+
19
+ test('returns the same cached object reference for repeat calls', () => {
20
+ const a = encodeAddressMemo(SEED, 4827, 1190)
21
+ const b = encodeAddressMemo(SEED, 4827, 1190)
22
+ expect(a).toBe(b)
23
+ })
24
+
25
+ test('round-trips through decodeAddress', () => {
26
+ const addr = encodeAddressMemo(SEED, -5, 7)
27
+ expect(decodeAddress(SEED, addr)).toEqual({x: -5, y: 7})
28
+ })
29
+
30
+ test('origin is centered: raw (0,0) is local (0,0)', () => {
31
+ const addr = encodeAddressMemo(SEED, 0, 0)
32
+ expect([addr.localX, addr.localY]).toEqual([0, 0])
33
+ })
34
+
35
+ test('origin neighbors share the origin region', () => {
36
+ const origin = encodeAddressMemo(SEED, 0, 0)
37
+ for (const [x, y] of [
38
+ [-1, -1],
39
+ [4999, 4999],
40
+ [-5000, -5000],
41
+ ] as const) {
42
+ expect(encodeAddressMemo(SEED, x, y).region).toBe(origin.region)
43
+ }
44
+ // first tile outside the origin region on each axis falls into a different region
45
+ expect(encodeAddressMemo(SEED, 5000, 0).region).not.toBe(origin.region)
46
+ })
47
+ })
@@ -0,0 +1,20 @@
1
+ import type {Checksum256Type} from '@wharfkit/antelope'
2
+ import {Checksum256} from '@wharfkit/antelope'
3
+ import {type CoordinateAddress, encodeAddress} from './address'
4
+
5
+ const cache = new Map<string, CoordinateAddress>()
6
+ const CACHE_MAX = 4096
7
+
8
+ export function encodeAddressMemo(seed: Checksum256Type, x: number, y: number): CoordinateAddress {
9
+ const key = `${Checksum256.from(seed).toString()}:${x},${y}`
10
+ let hit = cache.get(key)
11
+ if (!hit) {
12
+ hit = encodeAddress(seed, x, y)
13
+ if (cache.size >= CACHE_MAX) {
14
+ const oldest = cache.keys().next().value
15
+ if (oldest !== undefined) cache.delete(oldest)
16
+ }
17
+ cache.set(key, hit)
18
+ }
19
+ return hit
20
+ }
@@ -0,0 +1,77 @@
1
+ import type {Checksum256Type} from '@wharfkit/antelope'
2
+ import {hash512} from '../utils/hash'
3
+
4
+ // FROZEN INTERFACE — round count, key-derivation strings, and mixing constants must never change; per-game variation comes from the seed.
5
+ const ROUNDS = 4
6
+
7
+ export interface FeistelConfig {
8
+ n: number
9
+ halfBits: number
10
+ label: string
11
+ }
12
+
13
+ const keyCache = new Map<string, number[]>()
14
+
15
+ function deriveRoundKeys(seed: Checksum256Type, label: string): number[] {
16
+ const cacheKey = `${seed}:${label}`
17
+ const cached = keyCache.get(cacheKey)
18
+ if (cached) return cached
19
+ const h = hash512(seed, `coord-keys-${label}`).array
20
+ const keys: number[] = []
21
+ for (let i = 0; i < ROUNDS; i++) {
22
+ const o = i * 4
23
+ keys.push(((h[o] << 24) | (h[o + 1] << 16) | (h[o + 2] << 8) | h[o + 3]) >>> 0)
24
+ }
25
+ keyCache.set(cacheKey, keys)
26
+ return keys
27
+ }
28
+
29
+ function roundFn(r: number, key: number, halfBits: number): number {
30
+ let x = (r ^ key) >>> 0
31
+ x = Math.imul(x ^ (x >>> 16), 0x9e3779b1) >>> 0
32
+ x = Math.imul(x ^ (x >>> 13), 0x7feb352d) >>> 0
33
+ x = (x ^ (x >>> 16)) >>> 0
34
+ return x & ((1 << halfBits) - 1)
35
+ }
36
+
37
+ function encryptBlock(x: number, halfBits: number, keys: number[]): number {
38
+ const mask = (1 << halfBits) - 1
39
+ let L = (x >>> halfBits) & mask
40
+ let R = x & mask
41
+ for (let i = 0; i < ROUNDS; i++) {
42
+ const F = roundFn(R, keys[i], halfBits)
43
+ const nL = R
44
+ const nR = (L ^ F) & mask
45
+ L = nL
46
+ R = nR
47
+ }
48
+ return ((L << halfBits) | R) >>> 0
49
+ }
50
+
51
+ function decryptBlock(y: number, halfBits: number, keys: number[]): number {
52
+ const mask = (1 << halfBits) - 1
53
+ let L = (y >>> halfBits) & mask
54
+ let R = y & mask
55
+ for (let i = ROUNDS - 1; i >= 0; i--) {
56
+ const F = roundFn(L, keys[i], halfBits)
57
+ const nR = L
58
+ const nL = (R ^ F) & mask
59
+ L = nL
60
+ R = nR
61
+ }
62
+ return ((L << halfBits) | R) >>> 0
63
+ }
64
+
65
+ export function permute(seed: Checksum256Type, x: number, cfg: FeistelConfig): number {
66
+ const keys = deriveRoundKeys(seed, cfg.label)
67
+ let v = encryptBlock(x, cfg.halfBits, keys)
68
+ while (v >= cfg.n) v = encryptBlock(v, cfg.halfBits, keys)
69
+ return v
70
+ }
71
+
72
+ export function unpermute(seed: Checksum256Type, y: number, cfg: FeistelConfig): number {
73
+ const keys = deriveRoundKeys(seed, cfg.label)
74
+ let v = decryptBlock(y, cfg.halfBits, keys)
75
+ while (v >= cfg.n) v = decryptBlock(v, cfg.halfBits, keys)
76
+ return v
77
+ }
@@ -0,0 +1,48 @@
1
+ import type {Checksum256Type} from '@wharfkit/antelope'
2
+ import {REGION_FEISTEL, REGION_PER_AXIS} from './constants'
3
+ import {permute, unpermute} from './permutation'
4
+
5
+ // FROZEN INTERFACE — phoneme tables; tune aesthetics before launch, then never reorder.
6
+ const ONSETS = ['b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 't'] // 12
7
+ const VOWELS = ['a', 'e', 'i', 'o', 'u'] // 5
8
+ const CODAS = ['n', 'r', 'l', 's', 'k', 'm', 't', 'x'] // 8
9
+ const SYL_BASE = ONSETS.length * VOWELS.length * CODAS.length // 480
10
+
11
+ function syllable(digit: number): string {
12
+ const onset = Math.floor(digit / (VOWELS.length * CODAS.length))
13
+ const rem = digit % (VOWELS.length * CODAS.length)
14
+ const vowel = Math.floor(rem / CODAS.length)
15
+ const coda = rem % CODAS.length
16
+ return ONSETS[onset] + VOWELS[vowel] + CODAS[coda]
17
+ }
18
+
19
+ function unsyllable(chunk: string): number {
20
+ const onset = ONSETS.indexOf(chunk[0])
21
+ const vowel = VOWELS.indexOf(chunk[1])
22
+ const coda = CODAS.indexOf(chunk[2])
23
+ if (onset < 0 || vowel < 0 || coda < 0) throw new Error(`invalid region token chunk: ${chunk}`)
24
+ return onset * (VOWELS.length * CODAS.length) + vowel * CODAS.length + coda
25
+ }
26
+
27
+ export function encodeRegion(seed: Checksum256Type, rx: number, ry: number): string {
28
+ const index = rx * REGION_PER_AXIS + ry
29
+ let n = permute(seed, index, REGION_FEISTEL)
30
+ const d0 = n % SYL_BASE
31
+ n = Math.floor(n / SYL_BASE)
32
+ const d1 = n % SYL_BASE
33
+ const d2 = Math.floor(n / SYL_BASE)
34
+ const token = syllable(d2) + syllable(d1) + syllable(d0)
35
+ return token.charAt(0).toUpperCase() + token.slice(1)
36
+ }
37
+
38
+ export function decodeRegion(seed: Checksum256Type, token: string): {rx: number; ry: number} {
39
+ if (token.length !== 9) throw new Error(`invalid region token length: ${token}`)
40
+ const lower = token.toLowerCase()
41
+ const d2 = unsyllable(lower.slice(0, 3))
42
+ const d1 = unsyllable(lower.slice(3, 6))
43
+ const d0 = unsyllable(lower.slice(6, 9))
44
+ const scrambled = (d2 * SYL_BASE + d1) * SYL_BASE + d0
45
+ if (scrambled >= REGION_FEISTEL.n) throw new Error(`invalid region token: ${token}`)
46
+ const index = unpermute(seed, scrambled, REGION_FEISTEL)
47
+ return {rx: Math.floor(index / REGION_PER_AXIS), ry: index % REGION_PER_AXIS}
48
+ }
@@ -0,0 +1,115 @@
1
+ import type {Checksum256Type} from '@wharfkit/antelope'
2
+ import {SECTOR_FEISTEL, SECTORS_PER_AXIS} from './constants'
3
+ import {permute, unpermute} from './permutation'
4
+
5
+ // FROZEN INTERFACE — curation seed; review before launch, then never reorder.
6
+ export const SECTOR_ADJECTIVES: readonly string[] = [
7
+ 'Amber',
8
+ 'Azure',
9
+ 'Brass',
10
+ 'Cinder',
11
+ 'Cobalt',
12
+ 'Copper',
13
+ 'Coral',
14
+ 'Crimson',
15
+ 'Crystal',
16
+ 'Dusk',
17
+ 'Ember',
18
+ 'Emerald',
19
+ 'Frost',
20
+ 'Glimmer',
21
+ 'Golden',
22
+ 'Hazy',
23
+ 'Indigo',
24
+ 'Iron',
25
+ 'Ivory',
26
+ 'Jade',
27
+ 'Lunar',
28
+ 'Misty',
29
+ 'Neon',
30
+ 'Onyx',
31
+ 'Opal',
32
+ 'Pearl',
33
+ 'Plasma',
34
+ 'Quartz',
35
+ 'Rusty',
36
+ 'Saffron',
37
+ 'Scarlet',
38
+ 'Silver',
39
+ 'Solar',
40
+ 'Static',
41
+ 'Stormy',
42
+ 'Sunny',
43
+ 'Teal',
44
+ 'Umber',
45
+ 'Velvet',
46
+ 'Verdant',
47
+ 'Vermilion',
48
+ 'Violet',
49
+ 'Wispy',
50
+ ]
51
+
52
+ export const SECTOR_NOUNS: readonly string[] = [
53
+ 'Belt',
54
+ 'Bluff',
55
+ 'Cluster',
56
+ 'Coil',
57
+ 'Crest',
58
+ 'Drift',
59
+ 'Expanse',
60
+ 'Fathom',
61
+ 'Flare',
62
+ 'Gulf',
63
+ 'Halo',
64
+ 'Haven',
65
+ 'Hollow',
66
+ 'Maw',
67
+ 'Mesa',
68
+ 'Mire',
69
+ 'Notch',
70
+ 'Nook',
71
+ 'Oasis',
72
+ 'Lagoon',
73
+ 'Peak',
74
+ 'Pocket',
75
+ 'Reach',
76
+ 'Reef',
77
+ 'Ridge',
78
+ 'Rift',
79
+ 'Run',
80
+ 'Shoal',
81
+ 'Shroud',
82
+ 'Span',
83
+ 'Spire',
84
+ 'Spur',
85
+ 'Stretch',
86
+ 'Sprawl',
87
+ 'Tangle',
88
+ 'Trace',
89
+ 'Trench',
90
+ 'Vale',
91
+ 'Vault',
92
+ 'Verge',
93
+ 'Vortex',
94
+ 'Ward',
95
+ 'Wisp',
96
+ ]
97
+
98
+ export function encodeSector(seed: Checksum256Type, sx: number, sy: number): string {
99
+ const index = sx * SECTORS_PER_AXIS + sy
100
+ const scrambled = permute(seed, index, SECTOR_FEISTEL)
101
+ const adj = Math.floor(scrambled / SECTORS_PER_AXIS)
102
+ const noun = scrambled % SECTORS_PER_AXIS
103
+ return `${SECTOR_ADJECTIVES[adj]} ${SECTOR_NOUNS[noun]}`
104
+ }
105
+
106
+ export function decodeSector(seed: Checksum256Type, name: string): {sx: number; sy: number} {
107
+ const parts = name.trim().split(/\s+/)
108
+ if (parts.length !== 2) throw new Error(`invalid sector name: ${name}`)
109
+ const adj = SECTOR_ADJECTIVES.indexOf(parts[0])
110
+ const noun = SECTOR_NOUNS.indexOf(parts[1])
111
+ if (adj < 0 || noun < 0) throw new Error(`unknown sector name: ${name}`)
112
+ const scrambled = adj * SECTORS_PER_AXIS + noun
113
+ const index = unpermute(seed, scrambled, SECTOR_FEISTEL)
114
+ return {sx: Math.floor(index / SECTORS_PER_AXIS), sy: index % SECTORS_PER_AXIS}
115
+ }
@@ -8,6 +8,14 @@ export interface StatMapping {
8
8
  stat: string
9
9
  capability: string
10
10
  attribute: string
11
+ source: string // producing module/role, always present
12
+ }
13
+
14
+ export interface CapabilityAttributeRow {
15
+ capability: string
16
+ attribute: string
17
+ description: string
18
+ source?: string // producing module/role; absent when no formula-derived producer exists
11
19
  }
12
20
 
13
21
  export const capabilityNames: string[] = [
@@ -15,57 +23,80 @@ export const capabilityNames: string[] = [
15
23
  'Storage',
16
24
  'Movement',
17
25
  'Energy',
18
- 'Loader',
26
+ 'Loading',
19
27
  'Gathering',
20
28
  'Warp',
21
- 'Crafter',
29
+ 'Crafting',
30
+ 'Build',
22
31
  'Launch',
23
- 'Hauler',
32
+ 'Hauling',
24
33
  ]
25
34
 
26
35
  export const capabilityAttributes: CapabilityAttribute[] = [
27
36
  {capability: 'Hull', attribute: 'mass', description: 'Total mass of the hull'},
28
- {capability: 'Storage', attribute: 'capacity', description: 'Maximum mass that can be stored'},
29
37
  {
30
38
  capability: 'Storage',
31
- attribute: 'bonus',
32
- description: 'Capacity bonus added by an installed Storage module',
39
+ attribute: 'capacity',
40
+ description: 'Cargo capacity added by hulls and installed Cargo Hold modules',
41
+ },
42
+ {
43
+ capability: 'Storage',
44
+ attribute: 'drain',
45
+ description: 'Fixed normal-travel drain contributed by an installed Cargo Hold',
33
46
  },
34
47
  {capability: 'Movement', attribute: 'thrust', description: 'Propulsion force'},
35
- {capability: 'Movement', attribute: 'drain', description: 'Energy consumed per movement'},
36
- {capability: 'Energy', attribute: 'capacity', description: 'Maximum energy storage'},
48
+ {
49
+ capability: 'Movement',
50
+ attribute: 'drain',
51
+ description: 'Total installed energy consumed per tile of normal travel',
52
+ },
53
+ {
54
+ capability: 'Energy',
55
+ attribute: 'capacity',
56
+ description: 'Energy capacity from Power Cores and installed Battery Bank modules',
57
+ },
37
58
  {capability: 'Energy', attribute: 'recharge', description: 'Energy regeneration rate'},
38
- {capability: 'Loader', attribute: 'mass', description: 'Weight of the loader unit itself'},
39
- {capability: 'Loader', attribute: 'thrust', description: 'Loading speed/force'},
59
+ {capability: 'Loading', attribute: 'mass', description: 'Weight of the loader unit itself'},
60
+ {capability: 'Loading', attribute: 'thrust', description: 'Loading speed/force'},
61
+ {
62
+ capability: 'Loading',
63
+ attribute: 'quantity',
64
+ description: 'Number of cargo items moved per load operation',
65
+ },
40
66
  {capability: 'Gathering', attribute: 'yield', description: 'Mass gathered per second'},
41
67
  {capability: 'Gathering', attribute: 'drain', description: 'Energy consumed per gather'},
42
68
  {capability: 'Gathering', attribute: 'depth', description: 'Maximum gather depth'},
43
- {capability: 'Gathering', attribute: 'speed', description: 'Gathering speed/penetration'},
44
69
  {capability: 'Warp', attribute: 'range', description: 'Maximum warp distance'},
45
- {capability: 'Crafter', attribute: 'speed', description: 'Crafting time per item'},
70
+ {capability: 'Crafting', attribute: 'speed', description: 'Crafting time per item'},
46
71
  {
47
- capability: 'Crafter',
72
+ capability: 'Crafting',
48
73
  attribute: 'drain',
49
74
  description: 'Energy consumed per second while crafting',
50
75
  },
51
- {capability: 'Crafter', attribute: 'quality', description: 'Modifier on output quality'},
76
+ {capability: 'Crafting', attribute: 'quality', description: 'Modifier on output quality'},
77
+ {capability: 'Build', attribute: 'speed', description: 'Build progress per second'},
78
+ {
79
+ capability: 'Build',
80
+ attribute: 'drain',
81
+ description: 'Energy consumed per second while building',
82
+ },
52
83
  {capability: 'Launch', attribute: 'range', description: 'Maximum launch distance'},
53
84
  {capability: 'Launch', attribute: 'capacity', description: 'Maximum mass per launch'},
54
85
  {capability: 'Launch', attribute: 'drain', description: 'Energy consumed per launch'},
55
86
  {
56
- capability: 'Hauler',
87
+ capability: 'Hauling',
57
88
  attribute: 'capacity',
58
89
  description: 'Number of targets the haul beam can lock onto simultaneously',
59
90
  },
60
91
  {
61
- capability: 'Hauler',
92
+ capability: 'Hauling',
62
93
  attribute: 'efficiency',
63
94
  description: 'Thrust penalty reduction per hauled target',
64
95
  },
65
96
  {
66
- capability: 'Hauler',
97
+ capability: 'Hauling',
67
98
  attribute: 'drain',
68
- description: 'Energy consumed per target during haul-beam operation',
99
+ description: 'Fixed normal-travel drain contributed by an installed Tractor Beam',
69
100
  },
70
101
  ]
71
102
 
@@ -9,19 +9,21 @@ export type SlotConsumerKind =
9
9
  | 'gatherer'
10
10
  | 'loader'
11
11
  | 'crafter'
12
+ | 'builder'
12
13
  | 'storage'
13
14
  | 'hauler'
14
15
  | 'warp'
16
+ | 'battery'
15
17
  | 'ship-t1'
16
18
  | 'container-t1'
17
19
  | 'warehouse-t1'
20
+ | 'extractor-t1'
18
21
  | 'container-t2'
19
22
 
20
23
  const ENTITY_HULL_SLOTS: Record<number, SlotConsumer> = {
21
24
  0: {capability: 'Storage', attribute: 'capacity'},
22
25
  1: {capability: 'Hull', attribute: 'mass'},
23
26
  2: {capability: 'Storage', attribute: 'capacity'},
24
- 3: {capability: 'Storage', attribute: 'capacity'},
25
27
  }
26
28
 
27
29
  export const SLOT_FORMULAS: Record<SlotConsumerKind, Record<number, SlotConsumer>> = {
@@ -36,33 +38,43 @@ export const SLOT_FORMULAS: Record<SlotConsumerKind, Record<number, SlotConsumer
36
38
  gatherer: {
37
39
  0: {capability: 'Gathering', attribute: 'yield'},
38
40
  1: {capability: 'Gathering', attribute: 'depth'},
39
- 3: {capability: 'Gathering', attribute: 'drain'},
40
- 4: {capability: 'Gathering', attribute: 'speed'},
41
+ 2: {capability: 'Gathering', attribute: 'drain'},
41
42
  },
42
43
  loader: {
43
- 0: {capability: 'Loader', attribute: 'mass'},
44
- 1: {capability: 'Loader', attribute: 'thrust'},
44
+ 0: {capability: 'Loading', attribute: 'mass'},
45
+ 1: {capability: 'Loading', attribute: 'thrust'},
45
46
  },
46
47
  crafter: {
47
- 0: {capability: 'Crafter', attribute: 'speed'},
48
- 1: {capability: 'Crafter', attribute: 'drain'},
48
+ 0: {capability: 'Crafting', attribute: 'speed'},
49
+ 1: {capability: 'Crafting', attribute: 'drain'},
50
+ },
51
+ builder: {
52
+ 0: {capability: 'Build', attribute: 'speed'},
53
+ 1: {capability: 'Build', attribute: 'drain'},
49
54
  },
50
55
  storage: {
51
- 0: {capability: 'Storage', attribute: 'bonus'},
52
- 1: {capability: 'Storage', attribute: 'bonus'},
53
- 2: {capability: 'Storage', attribute: 'bonus'},
54
- 3: {capability: 'Storage', attribute: 'bonus'},
56
+ 0: {capability: 'Storage', attribute: 'capacity'},
57
+ 1: {capability: 'Storage', attribute: 'capacity'},
58
+ 2: {capability: 'Storage', attribute: 'capacity'},
59
+ 3: {capability: 'Storage', attribute: 'capacity'},
55
60
  },
56
61
  hauler: {
57
- 0: {capability: 'Hauler', attribute: 'capacity'},
58
- 1: {capability: 'Hauler', attribute: 'efficiency'},
59
- 2: {capability: 'Hauler', attribute: 'drain'},
62
+ 0: {capability: 'Hauling', attribute: 'capacity'},
63
+ 1: {capability: 'Hauling', attribute: 'efficiency'},
64
+ 2: {capability: 'Hauling', attribute: 'drain'},
60
65
  },
61
66
  warp: {
62
67
  0: {capability: 'Warp', attribute: 'range'},
63
68
  },
69
+ battery: {
70
+ 0: {capability: 'Energy', attribute: 'capacity'},
71
+ 1: {capability: 'Energy', attribute: 'capacity'},
72
+ 2: {capability: 'Energy', attribute: 'capacity'},
73
+ 3: {capability: 'Energy', attribute: 'capacity'},
74
+ },
64
75
  'ship-t1': ENTITY_HULL_SLOTS,
65
76
  'container-t1': ENTITY_HULL_SLOTS,
66
77
  'warehouse-t1': ENTITY_HULL_SLOTS,
78
+ 'extractor-t1': ENTITY_HULL_SLOTS,
67
79
  'container-t2': ENTITY_HULL_SLOTS,
68
80
  }
@@ -1,6 +1,5 @@
1
1
  import {UInt16, type UInt16Type} from '@wharfkit/antelope'
2
2
  import items from './items.json'
3
- import {tierLabels} from './colors'
4
3
  import {itemMetadata} from './metadata'
5
4
  import {
6
5
  CATEGORY_LABELS,
@@ -115,10 +114,6 @@ export function categoryLabel(cat: ResourceCategory): string {
115
114
  return CATEGORY_LABELS[cat]
116
115
  }
117
116
 
118
- export function tierLabel(tier: number): string {
119
- return tierLabels[tier] ?? `T${tier}`
120
- }
121
-
122
117
  // Chain rescat enum order from server::getrescats.
123
118
  // IMPORTANT: gas=1, crystal=4 — does NOT match the player-facing T-prefix
124
119
  // order (ore=100, crystal=200, gas=300, regolith=400, biomass=500).