@shipload/sdk 1.0.0-next.4 → 1.0.0-next.40
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/shipload.d.ts +2473 -973
- package/lib/shipload.js +11529 -5211
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +11338 -5162
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +970 -0
- package/lib/testing.js +4013 -0
- package/lib/testing.js.map +1 -0
- package/lib/testing.m.js +4007 -0
- package/lib/testing.m.js.map +1 -0
- package/package.json +15 -2
- package/src/capabilities/craftable.ts +51 -0
- package/src/capabilities/crafting.test.ts +7 -0
- package/src/capabilities/crafting.ts +5 -6
- package/src/capabilities/gathering.test.ts +16 -0
- package/src/capabilities/gathering.ts +35 -18
- package/src/capabilities/index.ts +0 -1
- package/src/capabilities/modules.ts +9 -0
- package/src/capabilities/storage.ts +16 -1
- package/src/contracts/platform.ts +231 -3
- package/src/contracts/server.ts +1021 -481
- package/src/coordinates/address.ts +88 -0
- package/src/coordinates/constants.test.ts +15 -0
- package/src/coordinates/constants.ts +23 -0
- package/src/coordinates/index.ts +15 -0
- package/src/coordinates/memo.test.ts +47 -0
- package/src/coordinates/memo.ts +20 -0
- package/src/coordinates/permutation.ts +77 -0
- package/src/coordinates/regions.ts +48 -0
- package/src/coordinates/sectors.ts +115 -0
- package/src/data/capabilities.ts +12 -5
- package/src/data/capability-formulas.ts +14 -7
- package/src/data/catalog.ts +0 -5
- package/src/data/colors.ts +14 -47
- package/src/data/entities.json +76 -10
- package/src/data/item-ids.ts +18 -12
- package/src/data/items.json +321 -38
- package/src/data/kind-registry.json +109 -0
- package/src/data/kind-registry.ts +165 -0
- package/src/data/metadata.ts +119 -33
- package/src/data/recipes-runtime.ts +3 -23
- package/src/data/recipes.json +238 -117
- package/src/derivation/build-methods.ts +45 -0
- package/src/derivation/capabilities.test.ts +151 -0
- package/src/derivation/capabilities.ts +512 -0
- package/src/derivation/capability-mappings.ts +9 -12
- package/src/derivation/crafting.ts +23 -24
- package/src/derivation/index.ts +25 -2
- package/src/derivation/recipe-usage.test.ts +78 -0
- package/src/derivation/recipe-usage.ts +141 -0
- package/src/derivation/reserve-regen.ts +34 -0
- package/src/derivation/resources.ts +125 -38
- package/src/derivation/rollups.test.ts +55 -0
- package/src/derivation/rollups.ts +56 -0
- package/src/derivation/stars.test.ts +51 -0
- package/src/derivation/stars.ts +15 -0
- package/src/derivation/stats.ts +6 -6
- package/src/derivation/stratum.ts +17 -20
- package/src/derivation/tiers.ts +40 -7
- package/src/derivation/wormhole.ts +136 -0
- package/src/entities/entity.ts +98 -0
- package/src/entities/gamestate.ts +3 -28
- package/src/entities/makers.ts +124 -134
- package/src/entities/slot-multiplier.ts +43 -0
- package/src/errors.ts +12 -16
- package/src/format.ts +26 -4
- package/src/index-module.ts +267 -47
- package/src/managers/actions.ts +528 -95
- package/src/managers/base.ts +6 -2
- package/src/managers/construction-types.ts +80 -0
- package/src/managers/construction.ts +412 -0
- package/src/managers/context.ts +20 -1
- package/src/managers/coordinates.ts +14 -0
- package/src/managers/entities.ts +18 -66
- package/src/managers/epochs.ts +40 -0
- package/src/managers/index.ts +17 -1
- package/src/managers/locations.ts +25 -29
- package/src/managers/nft.test.ts +14 -0
- package/src/managers/nft.ts +70 -0
- package/src/managers/plot.ts +122 -0
- package/src/nft/atomicassets.abi.json +1342 -0
- package/src/nft/atomicassets.ts +237 -0
- package/src/nft/atomicdata.ts +130 -0
- package/src/nft/buildImmutableData.ts +338 -0
- package/src/nft/description.ts +98 -24
- package/src/nft/index.ts +3 -0
- package/src/planner/index.ts +127 -0
- package/src/planner/planner.test.ts +319 -0
- package/src/resolution/describe-module.ts +18 -13
- package/src/resolution/display-name.ts +38 -10
- package/src/resolution/resolve-item.test.ts +37 -0
- package/src/resolution/resolve-item.ts +55 -24
- package/src/scheduling/accessor.ts +68 -22
- package/src/scheduling/availability.ts +108 -0
- package/src/scheduling/cancel.test.ts +348 -0
- package/src/scheduling/cancel.ts +209 -0
- package/src/scheduling/energy.ts +47 -0
- package/src/scheduling/idle-resolve.ts +45 -0
- package/src/scheduling/lane-core.ts +128 -0
- package/src/scheduling/lanes.test.ts +249 -0
- package/src/scheduling/lanes.ts +198 -0
- package/src/scheduling/projection.ts +209 -105
- package/src/scheduling/schedule.ts +241 -104
- package/src/scheduling/task-cargo.ts +46 -0
- package/src/shipload.ts +21 -1
- package/src/subscriptions/manager.ts +229 -142
- package/src/subscriptions/mappers.ts +5 -8
- package/src/subscriptions/types.ts +11 -3
- package/src/testing/catalog-hash.ts +19 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/projection-parity.ts +167 -0
- package/src/travel/reach.ts +23 -0
- package/src/travel/route-planner.ts +196 -0
- package/src/travel/travel.ts +200 -112
- package/src/types/capabilities.ts +29 -6
- package/src/types/entity.ts +3 -3
- package/src/types/index.ts +0 -1
- package/src/types.ts +28 -13
- package/src/utils/cargo.ts +27 -0
- package/src/utils/display-name.ts +70 -0
- package/src/utils/system.ts +36 -24
- package/src/capabilities/loading.ts +0 -8
- package/src/entities/container.ts +0 -108
- package/src/entities/ship-deploy.ts +0 -259
- package/src/entities/ship.ts +0 -204
- package/src/entities/warehouse.ts +0 -119
- 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
|
+
}
|
package/src/data/capabilities.ts
CHANGED
|
@@ -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: '
|
|
32
|
-
description: '
|
|
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
|
-
{
|
|
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
|
-
|
|
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: '
|
|
52
|
-
1: {capability: 'Storage', attribute: '
|
|
53
|
-
2: {capability: 'Storage', attribute: '
|
|
54
|
-
3: {capability: 'Storage', attribute: '
|
|
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
|
}
|
package/src/data/catalog.ts
CHANGED
|
@@ -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).
|
package/src/data/colors.ts
CHANGED
|
@@ -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: '#
|
|
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: '
|
|
63
|
-
10002: '
|
|
64
|
-
10003: '
|
|
65
|
-
10004: '
|
|
66
|
-
10005: '
|
|
67
|
-
10006: '
|
|
68
|
-
10007: '
|
|
69
|
-
10008: '
|
|
70
|
-
10009: '
|
|
71
|
-
10010: '
|
|
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: '
|
|
83
|
-
20002: '
|
|
49
|
+
20001: 'PL',
|
|
50
|
+
20002: 'FR',
|
|
84
51
|
20200: 'CT',
|
|
85
52
|
}
|