@shipload/sdk 1.0.0-next.4 → 1.0.0-next.41

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 (134) hide show
  1. package/lib/scan.d.ts +34 -0
  2. package/lib/scan.js +136 -0
  3. package/lib/scan.js.map +1 -0
  4. package/lib/scan.m.js +129 -0
  5. package/lib/scan.m.js.map +1 -0
  6. package/lib/shipload.d.ts +2473 -973
  7. package/lib/shipload.js +11529 -5211
  8. package/lib/shipload.js.map +1 -1
  9. package/lib/shipload.m.js +11338 -5162
  10. package/lib/shipload.m.js.map +1 -1
  11. package/lib/testing.d.ts +970 -0
  12. package/lib/testing.js +4013 -0
  13. package/lib/testing.js.map +1 -0
  14. package/lib/testing.m.js +4007 -0
  15. package/lib/testing.m.js.map +1 -0
  16. package/package.json +20 -2
  17. package/src/capabilities/craftable.ts +51 -0
  18. package/src/capabilities/crafting.test.ts +7 -0
  19. package/src/capabilities/crafting.ts +5 -6
  20. package/src/capabilities/gathering.test.ts +16 -0
  21. package/src/capabilities/gathering.ts +35 -18
  22. package/src/capabilities/index.ts +0 -1
  23. package/src/capabilities/modules.ts +9 -0
  24. package/src/capabilities/storage.ts +16 -1
  25. package/src/contracts/platform.ts +231 -3
  26. package/src/contracts/server.ts +1021 -481
  27. package/src/coordinates/address.ts +88 -0
  28. package/src/coordinates/constants.test.ts +15 -0
  29. package/src/coordinates/constants.ts +23 -0
  30. package/src/coordinates/index.ts +15 -0
  31. package/src/coordinates/memo.test.ts +47 -0
  32. package/src/coordinates/memo.ts +20 -0
  33. package/src/coordinates/permutation.ts +77 -0
  34. package/src/coordinates/regions.ts +48 -0
  35. package/src/coordinates/sectors.ts +115 -0
  36. package/src/data/capabilities.ts +12 -5
  37. package/src/data/capability-formulas.ts +14 -7
  38. package/src/data/catalog.ts +0 -5
  39. package/src/data/colors.ts +14 -47
  40. package/src/data/entities.json +76 -10
  41. package/src/data/item-ids.ts +18 -12
  42. package/src/data/items.json +321 -38
  43. package/src/data/kind-registry.json +109 -0
  44. package/src/data/kind-registry.ts +165 -0
  45. package/src/data/metadata.ts +119 -33
  46. package/src/data/recipes-runtime.ts +3 -23
  47. package/src/data/recipes.json +238 -117
  48. package/src/derivation/build-methods.ts +45 -0
  49. package/src/derivation/capabilities.test.ts +151 -0
  50. package/src/derivation/capabilities.ts +512 -0
  51. package/src/derivation/capability-mappings.ts +9 -12
  52. package/src/derivation/crafting.ts +23 -24
  53. package/src/derivation/index.ts +25 -2
  54. package/src/derivation/recipe-usage.test.ts +78 -0
  55. package/src/derivation/recipe-usage.ts +141 -0
  56. package/src/derivation/reserve-regen.ts +34 -0
  57. package/src/derivation/resources.ts +125 -38
  58. package/src/derivation/rollups.test.ts +55 -0
  59. package/src/derivation/rollups.ts +56 -0
  60. package/src/derivation/stars.test.ts +51 -0
  61. package/src/derivation/stars.ts +15 -0
  62. package/src/derivation/stats.ts +6 -6
  63. package/src/derivation/stratum.ts +17 -20
  64. package/src/derivation/tiers.ts +40 -7
  65. package/src/derivation/wormhole.ts +136 -0
  66. package/src/entities/entity.ts +98 -0
  67. package/src/entities/gamestate.ts +3 -28
  68. package/src/entities/makers.ts +124 -134
  69. package/src/entities/slot-multiplier.ts +43 -0
  70. package/src/errors.ts +12 -16
  71. package/src/format.ts +26 -4
  72. package/src/index-module.ts +267 -47
  73. package/src/managers/actions.ts +528 -95
  74. package/src/managers/base.ts +6 -2
  75. package/src/managers/construction-types.ts +80 -0
  76. package/src/managers/construction.ts +412 -0
  77. package/src/managers/context.ts +20 -1
  78. package/src/managers/coordinates.ts +14 -0
  79. package/src/managers/entities.ts +18 -66
  80. package/src/managers/epochs.ts +40 -0
  81. package/src/managers/index.ts +17 -1
  82. package/src/managers/locations.ts +25 -29
  83. package/src/managers/nft.test.ts +14 -0
  84. package/src/managers/nft.ts +70 -0
  85. package/src/managers/plot.ts +122 -0
  86. package/src/nft/atomicassets.abi.json +1342 -0
  87. package/src/nft/atomicassets.ts +237 -0
  88. package/src/nft/atomicdata.ts +130 -0
  89. package/src/nft/buildImmutableData.ts +338 -0
  90. package/src/nft/description.ts +98 -24
  91. package/src/nft/index.ts +3 -0
  92. package/src/planner/index.ts +127 -0
  93. package/src/planner/planner.test.ts +319 -0
  94. package/src/resolution/describe-module.ts +18 -13
  95. package/src/resolution/display-name.ts +38 -10
  96. package/src/resolution/resolve-item.test.ts +37 -0
  97. package/src/resolution/resolve-item.ts +55 -24
  98. package/src/scan/index.ts +180 -0
  99. package/src/scan/scan-wasm.base64.ts +2 -0
  100. package/src/scheduling/accessor.ts +68 -22
  101. package/src/scheduling/availability.ts +108 -0
  102. package/src/scheduling/cancel.test.ts +348 -0
  103. package/src/scheduling/cancel.ts +209 -0
  104. package/src/scheduling/energy.ts +47 -0
  105. package/src/scheduling/idle-resolve.ts +45 -0
  106. package/src/scheduling/lane-core.ts +128 -0
  107. package/src/scheduling/lanes.test.ts +249 -0
  108. package/src/scheduling/lanes.ts +198 -0
  109. package/src/scheduling/projection.ts +209 -105
  110. package/src/scheduling/schedule.ts +241 -104
  111. package/src/scheduling/task-cargo.ts +46 -0
  112. package/src/shipload.ts +21 -1
  113. package/src/subscriptions/manager.ts +229 -142
  114. package/src/subscriptions/mappers.ts +5 -8
  115. package/src/subscriptions/types.ts +11 -3
  116. package/src/testing/catalog-hash.ts +19 -0
  117. package/src/testing/index.ts +2 -0
  118. package/src/testing/projection-parity.ts +167 -0
  119. package/src/travel/reach.ts +23 -0
  120. package/src/travel/route-planner.ts +196 -0
  121. package/src/travel/travel.ts +200 -112
  122. package/src/types/capabilities.ts +29 -6
  123. package/src/types/entity.ts +3 -3
  124. package/src/types/index.ts +0 -1
  125. package/src/types.ts +28 -13
  126. package/src/utils/cargo.ts +27 -0
  127. package/src/utils/display-name.ts +70 -0
  128. package/src/utils/system.ts +36 -24
  129. package/src/capabilities/loading.ts +0 -8
  130. package/src/entities/container.ts +0 -108
  131. package/src/entities/ship-deploy.ts +0 -259
  132. package/src/entities/ship.ts +0 -204
  133. package/src/entities/warehouse.ts +0 -119
  134. 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
+ }
@@ -25,22 +25,29 @@ export const capabilityNames: string[] = [
25
25
 
26
26
  export const capabilityAttributes: CapabilityAttribute[] = [
27
27
  {capability: 'Hull', attribute: 'mass', description: 'Total mass of the hull'},
28
- {capability: 'Storage', attribute: 'capacity', description: 'Maximum mass that can be stored'},
29
28
  {
30
29
  capability: 'Storage',
31
- attribute: 'bonus',
32
- description: 'Capacity bonus added by an installed Storage module',
30
+ attribute: 'capacity',
31
+ description: 'Cargo capacity added by hulls and installed Cargo Bay modules',
33
32
  },
34
33
  {capability: 'Movement', attribute: 'thrust', description: 'Propulsion force'},
35
34
  {capability: 'Movement', attribute: 'drain', description: 'Energy consumed per movement'},
36
- {capability: 'Energy', attribute: 'capacity', description: 'Maximum energy storage'},
35
+ {
36
+ capability: 'Energy',
37
+ attribute: 'capacity',
38
+ description: 'Energy capacity from Generators and installed Battery Bank modules',
39
+ },
37
40
  {capability: 'Energy', attribute: 'recharge', description: 'Energy regeneration rate'},
38
41
  {capability: 'Loader', attribute: 'mass', description: 'Weight of the loader unit itself'},
39
42
  {capability: 'Loader', attribute: 'thrust', description: 'Loading speed/force'},
43
+ {
44
+ capability: 'Loader',
45
+ attribute: 'quantity',
46
+ description: 'Number of cargo items moved per load operation',
47
+ },
40
48
  {capability: 'Gathering', attribute: 'yield', description: 'Mass gathered per second'},
41
49
  {capability: 'Gathering', attribute: 'drain', description: 'Energy consumed per gather'},
42
50
  {capability: 'Gathering', attribute: 'depth', description: 'Maximum gather depth'},
43
- {capability: 'Gathering', attribute: 'speed', description: 'Gathering speed/penetration'},
44
51
  {capability: 'Warp', attribute: 'range', description: 'Maximum warp distance'},
45
52
  {capability: 'Crafter', attribute: 'speed', description: 'Crafting time per item'},
46
53
  {
@@ -12,16 +12,17 @@ export type SlotConsumerKind =
12
12
  | 'storage'
13
13
  | 'hauler'
14
14
  | 'warp'
15
+ | 'battery'
15
16
  | 'ship-t1'
16
17
  | 'container-t1'
17
18
  | 'warehouse-t1'
19
+ | 'extractor-t1'
18
20
  | 'container-t2'
19
21
 
20
22
  const ENTITY_HULL_SLOTS: Record<number, SlotConsumer> = {
21
23
  0: {capability: 'Storage', attribute: 'capacity'},
22
24
  1: {capability: 'Hull', attribute: 'mass'},
23
25
  2: {capability: 'Storage', attribute: 'capacity'},
24
- 3: {capability: 'Storage', attribute: 'capacity'},
25
26
  }
26
27
 
27
28
  export const SLOT_FORMULAS: Record<SlotConsumerKind, Record<number, SlotConsumer>> = {
@@ -36,8 +37,7 @@ export const SLOT_FORMULAS: Record<SlotConsumerKind, Record<number, SlotConsumer
36
37
  gatherer: {
37
38
  0: {capability: 'Gathering', attribute: 'yield'},
38
39
  1: {capability: 'Gathering', attribute: 'depth'},
39
- 3: {capability: 'Gathering', attribute: 'drain'},
40
- 4: {capability: 'Gathering', attribute: 'speed'},
40
+ 2: {capability: 'Gathering', attribute: 'drain'},
41
41
  },
42
42
  loader: {
43
43
  0: {capability: 'Loader', attribute: 'mass'},
@@ -48,10 +48,10 @@ export const SLOT_FORMULAS: Record<SlotConsumerKind, Record<number, SlotConsumer
48
48
  1: {capability: 'Crafter', attribute: 'drain'},
49
49
  },
50
50
  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'},
51
+ 0: {capability: 'Storage', attribute: 'capacity'},
52
+ 1: {capability: 'Storage', attribute: 'capacity'},
53
+ 2: {capability: 'Storage', attribute: 'capacity'},
54
+ 3: {capability: 'Storage', attribute: 'capacity'},
55
55
  },
56
56
  hauler: {
57
57
  0: {capability: 'Hauler', attribute: 'capacity'},
@@ -61,8 +61,15 @@ export const SLOT_FORMULAS: Record<SlotConsumerKind, Record<number, SlotConsumer
61
61
  warp: {
62
62
  0: {capability: 'Warp', attribute: 'range'},
63
63
  },
64
+ battery: {
65
+ 0: {capability: 'Energy', attribute: 'capacity'},
66
+ 1: {capability: 'Energy', attribute: 'capacity'},
67
+ 2: {capability: 'Energy', attribute: 'capacity'},
68
+ 3: {capability: 'Energy', attribute: 'capacity'},
69
+ },
64
70
  'ship-t1': ENTITY_HULL_SLOTS,
65
71
  'container-t1': ENTITY_HULL_SLOTS,
66
72
  'warehouse-t1': ENTITY_HULL_SLOTS,
73
+ 'extractor-t1': ENTITY_HULL_SLOTS,
67
74
  'container-t2': ENTITY_HULL_SLOTS,
68
75
  }
@@ -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).
@@ -3,7 +3,7 @@ import type {ResourceCategory} from '../types'
3
3
  export const categoryColors: Record<ResourceCategory, string> = {
4
4
  ore: '#C26D3F',
5
5
  crystal: '#4ADBFF',
6
- gas: '#B8E4A0',
6
+ gas: '#B877FF',
7
7
  regolith: '#C4A57B',
8
8
  biomass: '#5A8B3E',
9
9
  }
@@ -21,65 +21,32 @@ export const tierColors: Record<number, string> = {
21
21
  10: '#ffffff',
22
22
  }
23
23
 
24
- // Rarity-tier names (badge labels). Kept disjoint from TIER_ADJECTIVES in
25
- // types.ts (resource descriptors like "Pristine Ore") so the two vocabularies
26
- // never collide at any tier.
27
- export const tierLabels: Record<number, string> = {
28
- 1: 'Common',
29
- 2: 'Uncommon',
30
- 3: 'Rare',
31
- 4: 'Epic',
32
- 5: 'Legendary',
33
- 6: 'Mythic',
34
- 7: 'Divine',
35
- 8: 'Celestial',
36
- 9: 'Eternal',
37
- 10: 'Transcendent',
38
- }
39
-
40
- export const categoryIcons: Record<ResourceCategory, string> = {
41
- ore: '⬡',
42
- crystal: '◈',
43
- gas: '◎',
44
- regolith: '■',
45
- biomass: '❋',
46
- }
47
-
48
- export type CategoryIconShape = 'hex' | 'diamond' | 'star' | 'circle' | 'square'
49
-
50
- export const categoryIconShapes: Record<ResourceCategory, CategoryIconShape> = {
51
- ore: 'hex',
52
- crystal: 'diamond',
53
- gas: 'circle',
54
- regolith: 'square',
55
- biomass: 'star',
56
- }
57
-
58
24
  export const componentIcon = '▣'
59
25
  export const moduleIcon = '⬢'
60
26
 
61
27
  export const itemAbbreviations: Record<number, string> = {
62
- 10001: 'HP',
63
- 10002: 'CL',
64
- 10003: 'TC',
65
- 10004: 'PC',
66
- 10005: 'DS',
67
- 10006: 'EP',
68
- 10007: 'CA',
69
- 10008: 'TB',
70
- 10009: 'RC',
71
- 10010: 'FA',
28
+ 10001: 'PL',
29
+ 10002: 'FR',
30
+ 10003: 'PC',
31
+ 10004: 'RS',
32
+ 10005: 'BM',
33
+ 10006: 'SN',
34
+ 10007: 'PM',
35
+ 10008: 'CR',
36
+ 10009: 'RX',
37
+ 10010: 'RE',
72
38
  10100: 'EN',
73
39
  10101: 'GN',
74
40
  10102: 'EX',
75
41
  10103: 'LD',
76
42
  10104: 'MF',
77
43
  10105: 'ST',
44
+ 10106: 'HL',
78
45
  10107: 'WP',
79
46
  10200: 'CT',
80
47
  10201: 'SH',
81
48
  10202: 'WH',
82
- 20001: 'HP',
83
- 20002: 'CL',
49
+ 20001: 'PL',
50
+ 20002: 'FR',
84
51
  20200: 'CT',
85
52
  }