@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,55 @@
|
|
|
1
|
+
import {describe, expect, test} from 'bun:test'
|
|
2
|
+
import {ServerContract} from '../contracts'
|
|
3
|
+
import {rollupGatherer, rollupCrafter, rollupLoaders} from './rollups'
|
|
4
|
+
|
|
5
|
+
const gLane = (slot: number, y: number, d: number, depth: number) =>
|
|
6
|
+
ServerContract.Types.gatherer_lane.from({
|
|
7
|
+
slot_index: slot,
|
|
8
|
+
yield: y,
|
|
9
|
+
drain: d,
|
|
10
|
+
depth,
|
|
11
|
+
output_pct: 100,
|
|
12
|
+
})
|
|
13
|
+
const cLane = (slot: number, s: number, d: number) =>
|
|
14
|
+
ServerContract.Types.crafter_lane.from({slot_index: slot, speed: s, drain: d, output_pct: 100})
|
|
15
|
+
const lLane = (slot: number, m: number, t: number) =>
|
|
16
|
+
ServerContract.Types.loader_lane.from({slot_index: slot, mass: m, thrust: t, output_pct: 100})
|
|
17
|
+
|
|
18
|
+
describe('rollupGatherer', () => {
|
|
19
|
+
test('empty → undefined', () => {
|
|
20
|
+
expect(rollupGatherer([])).toBeUndefined()
|
|
21
|
+
})
|
|
22
|
+
test('sums yield/drain, takes MAX depth', () => {
|
|
23
|
+
const r = rollupGatherer([gLane(2, 300, 1250, 500), gLane(3, 250, 1000, 5495)])!
|
|
24
|
+
expect(r.yield.toNumber()).toBe(550)
|
|
25
|
+
expect(r.drain.toNumber()).toBe(2250)
|
|
26
|
+
expect(r.depth.toNumber()).toBe(5495)
|
|
27
|
+
})
|
|
28
|
+
test('clamps summed yield to uint16', () => {
|
|
29
|
+
const r = rollupGatherer([gLane(2, 60000, 0, 1), gLane(3, 60000, 0, 1)])!
|
|
30
|
+
expect(r.yield.toNumber()).toBe(65535)
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('rollupCrafter', () => {
|
|
35
|
+
test('empty → undefined', () => {
|
|
36
|
+
expect(rollupCrafter([])).toBeUndefined()
|
|
37
|
+
})
|
|
38
|
+
test('sums speed/drain', () => {
|
|
39
|
+
const r = rollupCrafter([cLane(2, 100, 30), cLane(3, 140, 25)])!
|
|
40
|
+
expect(r.speed.toNumber()).toBe(240)
|
|
41
|
+
expect(r.drain.toNumber()).toBe(55)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('rollupLoaders', () => {
|
|
46
|
+
test('empty → undefined', () => {
|
|
47
|
+
expect(rollupLoaders([])).toBeUndefined()
|
|
48
|
+
})
|
|
49
|
+
test('integer-averages mass, sums thrust, counts quantity', () => {
|
|
50
|
+
const r = rollupLoaders([lLane(2, 200, 5), lLane(3, 201, 7)])!
|
|
51
|
+
expect(r.mass.toNumber()).toBe(200) // floor(401/2)
|
|
52
|
+
expect(r.thrust.toNumber()).toBe(12)
|
|
53
|
+
expect(r.quantity.toNumber()).toBe(2)
|
|
54
|
+
})
|
|
55
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {UInt8, UInt16, UInt32} from '@wharfkit/antelope'
|
|
2
|
+
import type {ServerContract} from '../contracts'
|
|
3
|
+
|
|
4
|
+
export function rollupGatherer(
|
|
5
|
+
lanes: ServerContract.Types.gatherer_lane[]
|
|
6
|
+
): {yield: UInt16; drain: UInt32; depth: UInt16} | undefined {
|
|
7
|
+
if (lanes.length === 0) return undefined
|
|
8
|
+
let totalYield = 0
|
|
9
|
+
let totalDrain = 0
|
|
10
|
+
let maxDepth = 0
|
|
11
|
+
for (const l of lanes) {
|
|
12
|
+
totalYield += Number(l.yield)
|
|
13
|
+
totalDrain += Number(l.drain)
|
|
14
|
+
const d = Number(l.depth)
|
|
15
|
+
if (d > maxDepth) maxDepth = d
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
yield: UInt16.from(Math.min(totalYield, 65535)),
|
|
19
|
+
drain: UInt32.from(totalDrain),
|
|
20
|
+
depth: UInt16.from(maxDepth),
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function rollupCrafter(
|
|
25
|
+
lanes: ServerContract.Types.crafter_lane[]
|
|
26
|
+
): {speed: UInt16; drain: UInt32} | undefined {
|
|
27
|
+
if (lanes.length === 0) return undefined
|
|
28
|
+
let totalSpeed = 0
|
|
29
|
+
let totalDrain = 0
|
|
30
|
+
for (const l of lanes) {
|
|
31
|
+
totalSpeed += Number(l.speed)
|
|
32
|
+
totalDrain += Number(l.drain)
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
speed: UInt16.from(Math.min(totalSpeed, 65535)),
|
|
36
|
+
drain: UInt32.from(totalDrain),
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function rollupLoaders(
|
|
41
|
+
lanes: ServerContract.Types.loader_lane[]
|
|
42
|
+
): {mass: UInt32; thrust: UInt16; quantity: UInt8} | undefined {
|
|
43
|
+
if (lanes.length === 0) return undefined
|
|
44
|
+
const count = lanes.length
|
|
45
|
+
let totalMass = 0
|
|
46
|
+
let totalThrust = 0
|
|
47
|
+
for (const l of lanes) {
|
|
48
|
+
totalMass += Number(l.mass)
|
|
49
|
+
totalThrust += Number(l.thrust)
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
mass: UInt32.from(Math.floor(totalMass / count)),
|
|
53
|
+
thrust: UInt16.from(Math.min(totalThrust, 65535)),
|
|
54
|
+
quantity: UInt8.from(count),
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {expect, test} from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
MAX_STARS_PER_STAT,
|
|
4
|
+
MAX_STAR_RATING,
|
|
5
|
+
starRating,
|
|
6
|
+
starsForStat,
|
|
7
|
+
statMagnitude,
|
|
8
|
+
STAR_STEP,
|
|
9
|
+
} from './stars'
|
|
10
|
+
|
|
11
|
+
test('starsForStat bands at the 250 boundaries', () => {
|
|
12
|
+
expect(starsForStat(0)).toBe(0)
|
|
13
|
+
expect(starsForStat(1)).toBe(0)
|
|
14
|
+
expect(starsForStat(249)).toBe(0)
|
|
15
|
+
expect(starsForStat(250)).toBe(1)
|
|
16
|
+
expect(starsForStat(499)).toBe(1)
|
|
17
|
+
expect(starsForStat(500)).toBe(2)
|
|
18
|
+
expect(starsForStat(749)).toBe(2)
|
|
19
|
+
expect(starsForStat(750)).toBe(3)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('starsForStat clamps to MAX_STARS_PER_STAT', () => {
|
|
23
|
+
expect(starsForStat(999)).toBe(MAX_STARS_PER_STAT)
|
|
24
|
+
expect(starsForStat(1000)).toBe(MAX_STARS_PER_STAT)
|
|
25
|
+
expect(starsForStat(10_000)).toBe(MAX_STARS_PER_STAT)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('starsForStat never goes negative', () => {
|
|
29
|
+
expect(starsForStat(-50)).toBe(0)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('starRating sums per-stat stars to a 0-9 grade', () => {
|
|
33
|
+
expect(starRating(0, 0, 0)).toBe(0)
|
|
34
|
+
expect(starRating(250, 0, 0)).toBe(1)
|
|
35
|
+
expect(starRating(750, 750, 750)).toBe(MAX_STAR_RATING)
|
|
36
|
+
expect(starRating(999, 999, 999)).toBe(MAX_STAR_RATING)
|
|
37
|
+
expect(starRating(599, 599, 599)).toBe(6)
|
|
38
|
+
expect(starRating(251, 251, 251)).toBe(3)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('statMagnitude sums raw values for tiebreaking', () => {
|
|
42
|
+
expect(statMagnitude(599, 599, 599)).toBe(1797)
|
|
43
|
+
expect(statMagnitude(251, 251, 251)).toBe(753)
|
|
44
|
+
expect(statMagnitude(599, 599, 599)).toBeGreaterThan(statMagnitude(251, 251, 251))
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('constants hold their documented values', () => {
|
|
48
|
+
expect(STAR_STEP).toBe(250)
|
|
49
|
+
expect(MAX_STARS_PER_STAT).toBe(3)
|
|
50
|
+
expect(MAX_STAR_RATING).toBe(9)
|
|
51
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const STAR_STEP = 250
|
|
2
|
+
export const MAX_STARS_PER_STAT = 3
|
|
3
|
+
export const MAX_STAR_RATING = MAX_STARS_PER_STAT * 3
|
|
4
|
+
|
|
5
|
+
export function starsForStat(value: number): number {
|
|
6
|
+
return Math.max(0, Math.min(MAX_STARS_PER_STAT, Math.floor(value / STAR_STEP)))
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function starRating(stat1: number, stat2: number, stat3: number): number {
|
|
10
|
+
return starsForStat(stat1) + starsForStat(stat2) + starsForStat(stat3)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function statMagnitude(stat1: number, stat2: number, stat3: number): number {
|
|
14
|
+
return stat1 + stat2 + stat3
|
|
15
|
+
}
|
package/src/derivation/stats.ts
CHANGED
|
@@ -25,8 +25,7 @@ const ORE_STATS: StatDefinition[] = [
|
|
|
25
25
|
key: 'density',
|
|
26
26
|
label: 'Density',
|
|
27
27
|
abbreviation: 'DEN',
|
|
28
|
-
purpose: '
|
|
29
|
-
inverted: true,
|
|
28
|
+
purpose: 'Structural integrity — higher rolls produce lighter hulls',
|
|
30
29
|
},
|
|
31
30
|
]
|
|
32
31
|
|
|
@@ -74,10 +73,11 @@ const GAS_STATS: StatDefinition[] = [
|
|
|
74
73
|
|
|
75
74
|
const REGOLITH_STATS: StatDefinition[] = [
|
|
76
75
|
{
|
|
77
|
-
key: '
|
|
78
|
-
label: '
|
|
79
|
-
abbreviation: '
|
|
80
|
-
purpose:
|
|
76
|
+
key: 'cohesion',
|
|
77
|
+
label: 'Cohesion',
|
|
78
|
+
abbreviation: 'COH',
|
|
79
|
+
purpose:
|
|
80
|
+
'Binding strength of the loose aggregate; higher cohesion yields more rigid frames and hulls',
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
83
|
key: 'hardness',
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {Bytes, Checksum256, type Checksum256Type} from '@wharfkit/antelope'
|
|
2
2
|
import {hash512} from '../utils/hash'
|
|
3
3
|
import {Coordinates, type CoordinatesType} from '../types'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import {getItem} from '../data/catalog'
|
|
5
|
+
import {getEligibleResources, getResourceWeight, yieldThresholdAt} from './resources'
|
|
6
|
+
import {RESERVE_TIERS, rollTier, rollWithinTier, applyResourceTierMultiplier} from './tiers'
|
|
6
7
|
|
|
7
8
|
export interface StratumInfo {
|
|
8
9
|
itemId: number
|
|
@@ -33,15 +34,8 @@ export function deriveStratum(
|
|
|
33
34
|
|
|
34
35
|
const rawReserve = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const tierRoll = ((bytes[18] << 8) | bytes[19]) >>> 0
|
|
39
|
-
const withinRoll = ((bytes[20] << 8) | bytes[21]) >>> 0
|
|
40
|
-
const tier = rollTier(tierRoll, stratum)
|
|
41
|
-
reserve = rollWithinTier(withinRoll, RESERVE_TIERS[tier])
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (reserve === 0) return {itemId: 0, seed: 0n, richness: 0, reserve: 0}
|
|
37
|
+
if (rawReserve > yieldThresholdAt(stratum))
|
|
38
|
+
return {itemId: 0, seed: 0n, richness: 0, reserve: 0}
|
|
45
39
|
|
|
46
40
|
const eligible = getEligibleResources(locationType, subtype, stratum)
|
|
47
41
|
if (eligible.length === 0) return {itemId: 0, seed: 0n, richness: 0, reserve: 0}
|
|
@@ -66,6 +60,13 @@ export function deriveStratum(
|
|
|
66
60
|
}
|
|
67
61
|
}
|
|
68
62
|
|
|
63
|
+
const tierRoll = ((bytes[18] << 8) | bytes[19]) >>> 0
|
|
64
|
+
const withinRoll = ((bytes[20] << 8) | bytes[21]) >>> 0
|
|
65
|
+
const tier = rollTier(tierRoll, stratum)
|
|
66
|
+
const selected = getItem(selectedItemId)
|
|
67
|
+
const baseReserve = rollWithinTier(withinRoll, RESERVE_TIERS[tier], selected.mass)
|
|
68
|
+
const reserve = applyResourceTierMultiplier(baseReserve, selected.tier)
|
|
69
|
+
|
|
69
70
|
const seedBigInt =
|
|
70
71
|
(BigInt(bytes[8]) << 56n) |
|
|
71
72
|
(BigInt(bytes[9]) << 48n) |
|
|
@@ -76,15 +77,11 @@ export function deriveStratum(
|
|
|
76
77
|
(BigInt(bytes[14]) << 8n) |
|
|
77
78
|
BigInt(bytes[15])
|
|
78
79
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (stratum > 1) {
|
|
85
|
-
depthBonus = (50 * Math.log(stratum)) / Math.log(65535)
|
|
86
|
-
}
|
|
87
|
-
const richness = Math.min(Math.floor(baseRichness + depthBonus), 1000)
|
|
80
|
+
let byteSum = 0
|
|
81
|
+
for (let i = 22; i <= 33; i++) byteSum += bytes[i]
|
|
82
|
+
const z = (byteSum - 1530) / 256
|
|
83
|
+
const roll = 500 + 100 * z
|
|
84
|
+
const richness = Math.max(1, Math.min(999, Math.round(roll)))
|
|
88
85
|
|
|
89
86
|
return {itemId: selectedItemId, seed: seedBigInt, richness, reserve}
|
|
90
87
|
}
|
package/src/derivation/tiers.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {getItem} from '../data/catalog'
|
|
2
|
+
|
|
1
3
|
export type ReserveTier = 'small' | 'medium' | 'large' | 'massive' | 'motherlode'
|
|
2
4
|
|
|
3
5
|
export interface TierRange {
|
|
@@ -6,11 +8,11 @@ export interface TierRange {
|
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
export const RESERVE_TIERS: Record<ReserveTier, TierRange> = {
|
|
9
|
-
small: {min:
|
|
10
|
-
medium: {min:
|
|
11
|
-
large: {min:
|
|
12
|
-
massive: {min:
|
|
13
|
-
motherlode: {min:
|
|
11
|
+
small: {min: 3_600_000, max: 14_400_000},
|
|
12
|
+
medium: {min: 24_000_000, max: 48_000_000},
|
|
13
|
+
large: {min: 96_000_000, max: 168_000_000},
|
|
14
|
+
massive: {min: 240_000_000, max: 600_000_000},
|
|
15
|
+
motherlode: {min: 960_000_000, max: 2_400_000_000},
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
const SHALLOW_THRESHOLDS = {
|
|
@@ -47,8 +49,39 @@ export function rollTier(tierRoll: number, stratum: number): ReserveTier {
|
|
|
47
49
|
return 'motherlode'
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
export function rollWithinTier(
|
|
52
|
+
export function rollWithinTier(
|
|
53
|
+
withinRoll: number,
|
|
54
|
+
range: TierRange,
|
|
55
|
+
resourceUnitMass: number
|
|
56
|
+
): number {
|
|
51
57
|
const u = withinRoll / 65535
|
|
52
58
|
const skewed = u * u
|
|
53
|
-
|
|
59
|
+
const depositMass = range.min + skewed * (range.max - range.min)
|
|
60
|
+
return Math.max(1, Math.floor(depositMass / resourceUnitMass))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Must mirror the contract tier-multiplier table in tiers.hpp byte-for-byte; values are in tenths (T1..T10).
|
|
64
|
+
export const RESOURCE_TIER_MULT_TENTHS = [200, 154, 118, 91, 70, 54, 41, 32, 24, 19] as const
|
|
65
|
+
|
|
66
|
+
export function applyResourceTierMultiplier(units: number, resourceTier: number): number {
|
|
67
|
+
const idx = resourceTier < 1 ? 0 : resourceTier > 10 ? 9 : resourceTier - 1
|
|
68
|
+
const scaled = Math.floor((units * RESOURCE_TIER_MULT_TENTHS[idx]) / 10)
|
|
69
|
+
return scaled > 0 ? scaled : 1
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const RESERVE_TIER_ENTRIES = Object.entries(RESERVE_TIERS) as Array<[ReserveTier, TierRange]>
|
|
73
|
+
|
|
74
|
+
export function tierOfReserve(reserve: number, itemId: number): ReserveTier | null {
|
|
75
|
+
if (reserve <= 0) return null
|
|
76
|
+
const item = getItem(itemId)
|
|
77
|
+
if (item.mass <= 0) return null
|
|
78
|
+
// Reverse the resource-tier multiplier so bands read relative to the resource tier.
|
|
79
|
+
const idx = item.tier < 1 ? 0 : item.tier > 10 ? 9 : item.tier - 1
|
|
80
|
+
const baseReserve = (reserve * 10) / RESOURCE_TIER_MULT_TENTHS[idx]
|
|
81
|
+
const impliedMassLow = baseReserve * item.mass
|
|
82
|
+
const impliedMassHigh = impliedMassLow + item.mass
|
|
83
|
+
for (const [tier, range] of RESERVE_TIER_ENTRIES) {
|
|
84
|
+
if (impliedMassHigh > range.min && impliedMassLow <= range.max) return tier
|
|
85
|
+
}
|
|
86
|
+
return null
|
|
54
87
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type {Checksum256Type} from '@wharfkit/antelope'
|
|
2
|
+
import {hash512} from '../utils/hash'
|
|
3
|
+
|
|
4
|
+
export const WH = {
|
|
5
|
+
RSIZE: 75,
|
|
6
|
+
ZONE: 16384,
|
|
7
|
+
THRESHOLD: 8192,
|
|
8
|
+
MIN_REACH: 50000,
|
|
9
|
+
TRANSIT_SPEED: 500,
|
|
10
|
+
} as const
|
|
11
|
+
|
|
12
|
+
const HALF = Math.round(Math.log2(WH.ZONE))
|
|
13
|
+
const MASK = WH.ZONE - 1
|
|
14
|
+
|
|
15
|
+
function roll16(seed: Checksum256Type, str: string): number {
|
|
16
|
+
const h = hash512(seed, str).array
|
|
17
|
+
return (h[0] << 8) | h[1]
|
|
18
|
+
}
|
|
19
|
+
function feistelF(seed: Checksum256Type, x: number, round: number, key: string): number {
|
|
20
|
+
return roll16(seed, `feistel-${key}-${round}-${x}`) & MASK
|
|
21
|
+
}
|
|
22
|
+
export function feistel(seed: Checksum256Type, idx: number, key: string): number {
|
|
23
|
+
let L = (idx >>> HALF) & MASK
|
|
24
|
+
let R = idx & MASK
|
|
25
|
+
for (let r = 0; r < 4; r++) {
|
|
26
|
+
const nR = L ^ feistelF(seed, R, r, key)
|
|
27
|
+
L = R
|
|
28
|
+
R = nR
|
|
29
|
+
}
|
|
30
|
+
return (L << HALF) | R
|
|
31
|
+
}
|
|
32
|
+
export function feistelInv(seed: Checksum256Type, idx: number, key: string): number {
|
|
33
|
+
let L = (idx >>> HALF) & MASK
|
|
34
|
+
let R = idx & MASK
|
|
35
|
+
for (let r = 3; r >= 0; r--) {
|
|
36
|
+
const nL = R ^ feistelF(seed, L, r, key)
|
|
37
|
+
R = L
|
|
38
|
+
L = nL
|
|
39
|
+
}
|
|
40
|
+
return (L << HALF) | R
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type Region = {rx: number; ry: number}
|
|
44
|
+
|
|
45
|
+
export function regionOf(x: number, y: number): Region {
|
|
46
|
+
return {rx: Math.floor(x / WH.RSIZE), ry: Math.floor(y / WH.RSIZE)}
|
|
47
|
+
}
|
|
48
|
+
export function partnerRegion(seed: Checksum256Type, R: Region): Region {
|
|
49
|
+
const qx = Math.floor(R.rx / WH.ZONE)
|
|
50
|
+
const qy = Math.floor(R.ry / WH.ZONE)
|
|
51
|
+
const zx = qx * WH.ZONE
|
|
52
|
+
const zy = qy * WH.ZONE
|
|
53
|
+
const key = `${qx}:${qy}`
|
|
54
|
+
const idx = (R.ry - zy) * WH.ZONE + (R.rx - zx)
|
|
55
|
+
const p = feistelInv(seed, feistel(seed, idx, key) ^ 1, key)
|
|
56
|
+
return {rx: zx + (p % WH.ZONE), ry: zy + Math.floor(p / WH.ZONE)}
|
|
57
|
+
}
|
|
58
|
+
function regKey(R: Region): string {
|
|
59
|
+
return `${R.rx}:${R.ry}`
|
|
60
|
+
}
|
|
61
|
+
function pairKey(a: Region, b: Region): string {
|
|
62
|
+
const ka = regKey(a)
|
|
63
|
+
const kb = regKey(b)
|
|
64
|
+
return ka < kb ? `${ka}|${kb}` : `${kb}|${ka}`
|
|
65
|
+
}
|
|
66
|
+
function endpointInRegion(seed: Checksum256Type, R: Region, key: string): {x: number; y: number} {
|
|
67
|
+
const h = hash512(seed, `wh-endpoint-${key}-${regKey(R)}`).array
|
|
68
|
+
const ox = ((h[0] << 24) | (h[1] << 16) | (h[2] << 8) | h[3]) >>> 0
|
|
69
|
+
const oy = ((h[4] << 24) | (h[5] << 16) | (h[6] << 8) | h[7]) >>> 0
|
|
70
|
+
return {x: R.rx * WH.RSIZE + (ox % WH.RSIZE), y: R.ry * WH.RSIZE + (oy % WH.RSIZE)}
|
|
71
|
+
}
|
|
72
|
+
function dist(a: {x: number; y: number}, b: {x: number; y: number}): number {
|
|
73
|
+
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2)
|
|
74
|
+
}
|
|
75
|
+
function wormholeOfRegion(
|
|
76
|
+
seed: Checksum256Type,
|
|
77
|
+
R: Region
|
|
78
|
+
): {A: {x: number; y: number}; B: {x: number; y: number}} | null {
|
|
79
|
+
const P = partnerRegion(seed, R)
|
|
80
|
+
if (P.rx === R.rx && P.ry === R.ry) return null
|
|
81
|
+
const key = pairKey(R, P)
|
|
82
|
+
if (roll16(seed, `wh-exists-${key}`) >= WH.THRESHOLD) return null
|
|
83
|
+
const A = endpointInRegion(seed, R, key)
|
|
84
|
+
const B = endpointInRegion(seed, P, key)
|
|
85
|
+
if (dist(A, B) < WH.MIN_REACH) return null
|
|
86
|
+
return {A, B}
|
|
87
|
+
}
|
|
88
|
+
export function wormholeAtRegionEndpoint(
|
|
89
|
+
seed: Checksum256Type,
|
|
90
|
+
rx: number,
|
|
91
|
+
ry: number
|
|
92
|
+
): {from: {x: number; y: number}; to: {x: number; y: number}} | null {
|
|
93
|
+
const w = wormholeOfRegion(seed, {rx, ry})
|
|
94
|
+
if (!w) return null
|
|
95
|
+
return {from: w.A, to: w.B}
|
|
96
|
+
}
|
|
97
|
+
export function wormholeAt(
|
|
98
|
+
seed: Checksum256Type,
|
|
99
|
+
x: number,
|
|
100
|
+
y: number
|
|
101
|
+
): {x: number; y: number} | null {
|
|
102
|
+
const w = wormholeOfRegion(seed, regionOf(x, y))
|
|
103
|
+
if (!w || w.A.x !== x || w.A.y !== y) return null
|
|
104
|
+
return w.B
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Wormhole mouths (the local A endpoint) within reachTiles of (x,y); regions are RSIZE-wide so only a few overlap.
|
|
108
|
+
export function nearbyWormholes(
|
|
109
|
+
seed: Checksum256Type,
|
|
110
|
+
x: number,
|
|
111
|
+
y: number,
|
|
112
|
+
reachTiles: number
|
|
113
|
+
): {x: number; y: number}[] {
|
|
114
|
+
const min = regionOf(x - reachTiles, y - reachTiles)
|
|
115
|
+
const max = regionOf(x + reachTiles, y + reachTiles)
|
|
116
|
+
const out: {x: number; y: number}[] = []
|
|
117
|
+
for (let rx = min.rx; rx <= max.rx; rx++) {
|
|
118
|
+
for (let ry = min.ry; ry <= max.ry; ry++) {
|
|
119
|
+
const w = wormholeOfRegion(seed, {rx, ry})
|
|
120
|
+
if (!w) continue
|
|
121
|
+
if (w.A.x === x && w.A.y === y) continue
|
|
122
|
+
if (dist({x, y}, w.A) <= reachTiles) out.push(w.A)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return out
|
|
126
|
+
}
|
|
127
|
+
export function isValidWormholePair(
|
|
128
|
+
seed: Checksum256Type,
|
|
129
|
+
ax: number,
|
|
130
|
+
ay: number,
|
|
131
|
+
bx: number,
|
|
132
|
+
by: number
|
|
133
|
+
): boolean {
|
|
134
|
+
const to = wormholeAt(seed, ax, ay)
|
|
135
|
+
return to !== null && to.x === bx && to.y === by
|
|
136
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {UInt64} from '@wharfkit/antelope'
|
|
2
|
+
import {ServerContract} from '../contracts'
|
|
3
|
+
import {
|
|
4
|
+
CAP_DEMOLISH,
|
|
5
|
+
CAP_MODULES,
|
|
6
|
+
CAP_UNDEPLOY,
|
|
7
|
+
CAP_WRAP,
|
|
8
|
+
type EntityClass,
|
|
9
|
+
getEntityClass,
|
|
10
|
+
kindCan,
|
|
11
|
+
} from '../data/kind-registry'
|
|
12
|
+
import {InventoryAccessor} from './inventory-accessor'
|
|
13
|
+
import {Location} from './location'
|
|
14
|
+
import {ScheduleAccessor} from '../scheduling/accessor'
|
|
15
|
+
import * as schedule from '../scheduling/schedule'
|
|
16
|
+
import type {EntityInventory} from './entity-inventory'
|
|
17
|
+
|
|
18
|
+
export class Entity extends ServerContract.Types.entity_info {
|
|
19
|
+
private _sched?: ScheduleAccessor
|
|
20
|
+
private _inv?: InventoryAccessor
|
|
21
|
+
|
|
22
|
+
get name(): string {
|
|
23
|
+
return this.entity_name
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get location(): Location {
|
|
27
|
+
return Location.from(this.coordinates)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get isIdle(): boolean {
|
|
31
|
+
return schedule.isIdle(this)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get sched(): ScheduleAccessor {
|
|
35
|
+
this._sched ??= new ScheduleAccessor(this)
|
|
36
|
+
return this._sched
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get inv(): InventoryAccessor {
|
|
40
|
+
this._inv ??= new InventoryAccessor(this)
|
|
41
|
+
return this._inv
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get inventory(): EntityInventory[] {
|
|
45
|
+
return this.inv.items
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get totalCargoMass(): UInt64 {
|
|
49
|
+
return this.inv.totalMass
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get maxCapacity(): UInt64 {
|
|
53
|
+
return UInt64.from(this.capacity ?? 0)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get availableCapacity(): UInt64 {
|
|
57
|
+
const cargo = this.totalCargoMass
|
|
58
|
+
const max = this.maxCapacity
|
|
59
|
+
return cargo.gte(max) ? UInt64.from(0) : max.subtracting(cargo)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get isFull(): boolean {
|
|
63
|
+
return this.totalCargoMass.gte(this.maxCapacity)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get totalMass(): UInt64 {
|
|
67
|
+
const hull = this.hullmass ? UInt64.from(this.hullmass) : UInt64.from(0)
|
|
68
|
+
return hull.adding(this.totalCargoMass)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get entityClass(): EntityClass {
|
|
72
|
+
return getEntityClass(this.type)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get canWrap(): boolean {
|
|
76
|
+
return kindCan(this.type, CAP_WRAP)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get canUndeploy(): boolean {
|
|
80
|
+
return kindCan(this.type, CAP_UNDEPLOY)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get canDemolish(): boolean {
|
|
84
|
+
return kindCan(this.type, CAP_DEMOLISH)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get canUseModules(): boolean {
|
|
88
|
+
return kindCan(this.type, CAP_MODULES)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
isLoading(now: Date): boolean {
|
|
92
|
+
return schedule.isLoading(this, now)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
isUnloading(now: Date): boolean {
|
|
96
|
+
return schedule.isUnloading(this, now)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {Checksum256, Int64,
|
|
1
|
+
import {Checksum256, Int64, UInt64} from '@wharfkit/antelope'
|
|
2
2
|
import {type PlatformContract, ServerContract} from '../contracts'
|
|
3
3
|
import {type EpochInfo, getCurrentEpoch, getEpochInfo} from '../scheduling/epoch'
|
|
4
4
|
import {hasSystem} from '../utils/system'
|
|
@@ -34,7 +34,7 @@ export class GameState extends ServerContract.Types.state_row {
|
|
|
34
34
|
* Get the current epoch number from the state
|
|
35
35
|
*/
|
|
36
36
|
get currentEpoch(): UInt64 {
|
|
37
|
-
return this.epoch
|
|
37
|
+
return UInt64.from(this.epoch.toString())
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -58,27 +58,6 @@ export class GameState extends ServerContract.Types.state_row {
|
|
|
58
58
|
return this.enabled
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
/**
|
|
62
|
-
* Get the total number of ships in the game
|
|
63
|
-
*/
|
|
64
|
-
get shipCount(): number {
|
|
65
|
-
return Number(this.ships)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Get the current salt value (used for random number generation)
|
|
70
|
-
*/
|
|
71
|
-
get currentSalt(): UInt64 {
|
|
72
|
-
return this.salt
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Get the commit hash for the next epoch
|
|
77
|
-
*/
|
|
78
|
-
get nextEpochCommit(): Checksum256 {
|
|
79
|
-
return this.commit
|
|
80
|
-
}
|
|
81
|
-
|
|
82
61
|
/**
|
|
83
62
|
* Calculate the current epoch from game config (if game is set)
|
|
84
63
|
* This might differ from state.epoch if the blockchain hasn't advanced yet
|
|
@@ -97,7 +76,7 @@ export class GameState extends ServerContract.Types.state_row {
|
|
|
97
76
|
if (!this._game) {
|
|
98
77
|
return undefined
|
|
99
78
|
}
|
|
100
|
-
return getEpochInfo(this._game, this.
|
|
79
|
+
return getEpochInfo(this._game, this.currentEpoch)
|
|
101
80
|
}
|
|
102
81
|
|
|
103
82
|
/**
|
|
@@ -137,16 +116,12 @@ export class GameState extends ServerContract.Types.state_row {
|
|
|
137
116
|
get summary(): {
|
|
138
117
|
enabled: boolean
|
|
139
118
|
epoch: string
|
|
140
|
-
ships: number
|
|
141
119
|
hasSeed: boolean
|
|
142
|
-
hasCommit: boolean
|
|
143
120
|
} {
|
|
144
121
|
return {
|
|
145
122
|
enabled: this.enabled,
|
|
146
123
|
epoch: this.epoch.toString(),
|
|
147
|
-
ships: this.shipCount,
|
|
148
124
|
hasSeed: !this.seed.equals(Checksum256.from('0'.repeat(64))),
|
|
149
|
-
hasCommit: !this.commit.equals(Checksum256.from('0'.repeat(64))),
|
|
150
125
|
}
|
|
151
126
|
}
|
|
152
127
|
}
|