@shipload/sdk 1.0.0-next.7 → 1.0.0-next.71
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/scan.d.ts +52 -0
- package/lib/scan.js +162 -0
- package/lib/scan.js.map +1 -0
- package/lib/scan.m.js +152 -0
- package/lib/scan.m.js.map +1 -0
- package/lib/shipload.d.ts +5822 -1484
- package/lib/shipload.js +20838 -5419
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +20674 -5669
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +1691 -0
- package/lib/testing.js +7109 -0
- package/lib/testing.js.map +1 -0
- package/lib/testing.m.js +7103 -0
- package/lib/testing.m.js.map +1 -0
- package/package.json +20 -2
- package/src/capabilities/craftable.test.ts +82 -0
- package/src/capabilities/craftable.ts +82 -0
- package/src/capabilities/crafting.test.ts +80 -0
- package/src/capabilities/crafting.ts +50 -8
- package/src/capabilities/gathering.test.ts +29 -0
- package/src/capabilities/gathering.ts +38 -20
- package/src/capabilities/hauling.ts +0 -5
- package/src/capabilities/index.ts +0 -1
- package/src/capabilities/modules.ts +27 -30
- package/src/capabilities/movement.ts +1 -1
- package/src/capabilities/storage.test.ts +26 -0
- package/src/capabilities/storage.ts +22 -7
- package/src/contracts/fund.ts +306 -0
- package/src/contracts/platform.ts +312 -3
- package/src/contracts/server.ts +2793 -478
- package/src/contracts/token.ts +170 -0
- package/src/contracts.ts +2 -0
- 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 +49 -18
- package/src/data/capability-formulas.ts +26 -14
- package/src/data/catalog.test.ts +19 -0
- package/src/data/catalog.ts +13 -3
- package/src/data/charters.json +396 -0
- package/src/data/citizenry-adjectives.json +42 -0
- package/src/data/citizenry-nouns.json +50 -0
- package/src/data/colors.ts +14 -32
- package/src/data/entities.json +421 -15
- package/src/data/item-ids.ts +53 -13
- package/src/data/items.json +330 -77
- package/src/data/kind-registry.json +212 -0
- package/src/data/kind-registry.ts +192 -0
- package/src/data/metadata.ts +312 -51
- package/src/data/recipes-runtime.ts +6 -23
- package/src/data/recipes.json +2173 -173
- package/src/data/tiers.ts +9 -6
- package/src/derivation/build-methods.test.ts +71 -0
- package/src/derivation/build-methods.ts +50 -0
- package/src/derivation/capabilities.test.ts +348 -0
- package/src/derivation/capabilities.ts +707 -0
- package/src/derivation/capability-mappings.ts +60 -16
- package/src/derivation/crafting.test.ts +17 -0
- package/src/derivation/crafting.ts +57 -34
- package/src/derivation/index.ts +27 -2
- package/src/derivation/recipe-usage.test.ts +88 -0
- package/src/derivation/recipe-usage.ts +141 -0
- package/src/derivation/reserve-regen.ts +34 -0
- package/src/derivation/resources.ts +9 -1
- package/src/derivation/rollups.test.ts +55 -0
- package/src/derivation/rollups.ts +72 -0
- package/src/derivation/stars.test.ts +67 -0
- package/src/derivation/stars.ts +25 -0
- package/src/derivation/stat-scaling.ts +12 -0
- package/src/derivation/stats.ts +6 -6
- package/src/derivation/strata.ts +5 -3
- package/src/derivation/stratum.ts +26 -22
- package/src/derivation/tiers.ts +40 -7
- package/src/derivation/upgrades.ts +14 -0
- package/src/derivation/wormhole.ts +154 -0
- package/src/entities/entity.ts +102 -0
- package/src/entities/gamestate.ts +3 -28
- package/src/entities/makers.ts +144 -136
- package/src/entities/slot-multiplier.ts +43 -0
- package/src/entity/hydrate.test.ts +156 -0
- package/src/entity/hydrate.ts +86 -0
- package/src/errors.ts +21 -16
- package/src/format.ts +42 -9
- package/src/index-module.ts +425 -32
- package/src/influence/ballot.test.ts +103 -0
- package/src/influence/ballot.ts +98 -0
- package/src/influence/categories.ts +32 -0
- package/src/influence/charter-metadata.test.ts +136 -0
- package/src/influence/charter-metadata.ts +236 -0
- package/src/influence/charters.test.ts +112 -0
- package/src/influence/charters.ts +150 -0
- package/src/influence/citizenry.ts +38 -0
- package/src/influence/civic-shuttle.test.ts +202 -0
- package/src/influence/civic-shuttle.ts +142 -0
- package/src/influence/civic.test.ts +287 -0
- package/src/influence/civic.ts +269 -0
- package/src/influence/constants.ts +84 -0
- package/src/influence/decay.ts +20 -0
- package/src/influence/decomp.ts +95 -0
- package/src/influence/demand.ts +106 -0
- package/src/influence/duration.ts +101 -0
- package/src/influence/index.ts +118 -0
- package/src/influence/influence.test.ts +450 -0
- package/src/influence/quality.ts +25 -0
- package/src/influence/valuation.ts +154 -0
- package/src/managers/actions.ts +950 -93
- package/src/managers/balances.test.ts +161 -0
- package/src/managers/balances.ts +93 -0
- package/src/managers/base.ts +10 -2
- package/src/managers/cluster.test.ts +39 -0
- package/src/managers/cluster.ts +53 -0
- package/src/managers/construction-types.ts +80 -0
- package/src/managers/construction.ts +453 -0
- package/src/managers/context.ts +58 -1
- package/src/managers/coordinates.ts +14 -0
- package/src/managers/entities.ts +35 -63
- package/src/managers/epochs.ts +72 -1
- package/src/managers/index.ts +38 -1
- package/src/managers/influence.test.ts +56 -0
- package/src/managers/influence.ts +425 -0
- package/src/managers/jobs.test.ts +96 -0
- package/src/managers/jobs.ts +58 -0
- package/src/managers/locations.ts +25 -29
- package/src/managers/nft.test.ts +21 -0
- package/src/managers/nft.ts +110 -0
- package/src/managers/players.ts +25 -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 +388 -0
- package/src/nft/description.ts +222 -100
- package/src/nft/index.ts +3 -0
- package/src/planner/index.ts +296 -0
- package/src/planner/planner.test.ts +459 -0
- package/src/receipts.ts +22 -0
- package/src/resolution/describe-module.ts +43 -25
- package/src/resolution/display-name.ts +39 -11
- package/src/resolution/resolve-item.test.ts +48 -0
- package/src/resolution/resolve-item.ts +112 -58
- package/src/scan/index.ts +244 -0
- package/src/scan/scan-wasm.base64.ts +2 -0
- package/src/scheduling/accessor.ts +65 -23
- package/src/scheduling/availability.test.ts +320 -0
- package/src/scheduling/availability.ts +251 -0
- package/src/scheduling/cancel.test.ts +550 -0
- package/src/scheduling/cancel.ts +254 -0
- package/src/scheduling/cluster-stock.test.ts +31 -0
- package/src/scheduling/cluster-stock.ts +15 -0
- package/src/scheduling/energy.ts +47 -0
- package/src/scheduling/epoch.ts +1 -0
- package/src/scheduling/idle-resolve.ts +45 -0
- package/src/scheduling/jobs.test.ts +448 -0
- package/src/scheduling/jobs.ts +343 -0
- package/src/scheduling/lane-core.ts +128 -0
- package/src/scheduling/lanes.test.ts +250 -0
- package/src/scheduling/lanes.ts +279 -0
- package/src/scheduling/projection.ts +265 -149
- package/src/scheduling/schedule.test.ts +163 -0
- package/src/scheduling/schedule.ts +282 -116
- package/src/scheduling/task-cargo.test.ts +110 -0
- package/src/scheduling/task-cargo.ts +62 -0
- package/src/scheduling/task-effects.test.ts +35 -0
- package/src/scheduling/task-effects.ts +34 -0
- package/src/scheduling/unwrap.test.ts +86 -0
- package/src/scheduling/unwrap.ts +190 -0
- package/src/shipload.ts +63 -4
- package/src/subscriptions/manager.cluster.test.ts +51 -0
- package/src/subscriptions/manager.ts +245 -172
- package/src/subscriptions/mappers.ts +5 -8
- package/src/subscriptions/types.ts +28 -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.test.ts +86 -0
- package/src/travel/reach.ts +34 -0
- package/src/travel/route-build.test.ts +63 -0
- package/src/travel/route-build.ts +60 -0
- package/src/travel/route-planner.ts +288 -0
- package/src/travel/route-simulator.ts +170 -0
- package/src/travel/travel.ts +190 -123
- package/src/types/capabilities.ts +37 -8
- package/src/types/entity.ts +3 -3
- package/src/types/index.ts +0 -1
- package/src/types.ts +81 -23
- package/src/utils/cargo.test.ts +14 -0
- package/src/utils/cargo.ts +2 -0
- package/src/utils/display-name.ts +70 -0
- package/src/utils/hash.ts +22 -2
- package/src/utils/system.ts +24 -5
- package/src/capabilities/loading.ts +0 -8
- package/src/entities/container.ts +0 -123
- package/src/entities/ship-deploy.ts +0 -295
- package/src/entities/ship.ts +0 -221
- package/src/entities/warehouse.ts +0 -127
- package/src/types/entity-traits.ts +0 -69
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import {describe, expect, test} from 'bun:test'
|
|
2
|
+
import {ServerContract, TaskType} from '../index-module'
|
|
3
|
+
import {taskCargoChanges, taskCargoChangesChecked} from './task-cargo'
|
|
4
|
+
|
|
5
|
+
function cargoItem(itemId: number, quantity: number) {
|
|
6
|
+
return ServerContract.Types.cargo_item.from({item_id: itemId, stats: 0, modules: [], quantity})
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function craftTask(couplings: ReturnType<typeof coupling>[]) {
|
|
10
|
+
return ServerContract.Types.task.from({
|
|
11
|
+
type: TaskType.CRAFT,
|
|
12
|
+
duration: 60,
|
|
13
|
+
cancelable: 0,
|
|
14
|
+
cargo: [cargoItem(401, 19680), cargoItem(10008, 1968)],
|
|
15
|
+
couplings,
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function coupling() {
|
|
20
|
+
return ServerContract.Types.coupling.from({
|
|
21
|
+
counterpart: {entity_type: 'warehouse', entity_id: 51},
|
|
22
|
+
hold: 1,
|
|
23
|
+
kind: 3,
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('taskCargoChanges', () => {
|
|
28
|
+
test('a self-craft consumes inputs and delivers the output to the crafter', () => {
|
|
29
|
+
const changes = taskCargoChanges(craftTask([]))
|
|
30
|
+
|
|
31
|
+
expect(changes).toEqual([
|
|
32
|
+
{direction: 'out', item_id: 401, stats: 0n, modules: [], quantity: 19680},
|
|
33
|
+
{direction: 'in', item_id: 10008, stats: 0n, modules: [], quantity: 1968},
|
|
34
|
+
])
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('a coupled craft consumes inputs but does not deliver the output to the crafter', () => {
|
|
38
|
+
const changes = taskCargoChanges(craftTask([coupling()]))
|
|
39
|
+
|
|
40
|
+
expect(changes).toEqual([
|
|
41
|
+
{direction: 'out', item_id: 401, stats: 0n, modules: [], quantity: 19680},
|
|
42
|
+
])
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('TaskType', () => {
|
|
47
|
+
test('covers the civic transfer task types the contract defines', () => {
|
|
48
|
+
expect(TaskType.CIVIC_DEPOSIT).toBe(21)
|
|
49
|
+
expect(TaskType.CIVIC_WITHDRAW).toBe(22)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('keeps the former depot names as aliases of the civic transfer types', () => {
|
|
53
|
+
expect(TaskType.DEPOT_STORE).toBe(TaskType.CIVIC_DEPOSIT)
|
|
54
|
+
expect(TaskType.DEPOT_TAKE).toBe(TaskType.CIVIC_WITHDRAW)
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
function simpleTask(type: number) {
|
|
59
|
+
return ServerContract.Types.task.from({
|
|
60
|
+
type,
|
|
61
|
+
duration: 60,
|
|
62
|
+
cancelable: 0,
|
|
63
|
+
cargo: [cargoItem(401, 100)],
|
|
64
|
+
couplings: [],
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
describe('taskCargoChanges task coverage', () => {
|
|
69
|
+
test('a depot store moves cargo out of the entity', () => {
|
|
70
|
+
const changes = taskCargoChanges(simpleTask(TaskType.CIVIC_DEPOSIT))
|
|
71
|
+
expect(changes).toHaveLength(1)
|
|
72
|
+
expect(changes[0].direction).toBe('out')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('a depot take moves cargo into the entity', () => {
|
|
76
|
+
const changes = taskCargoChanges(simpleTask(TaskType.CIVIC_WITHDRAW))
|
|
77
|
+
expect(changes).toHaveLength(1)
|
|
78
|
+
expect(changes[0].direction).toBe('in')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('an upgrade consumes its cargo', () => {
|
|
82
|
+
const changes = taskCargoChanges(simpleTask(TaskType.UPGRADE))
|
|
83
|
+
expect(changes).toHaveLength(1)
|
|
84
|
+
expect(changes[0].direction).toBe('out')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('an undeploy returns cargo to the entity', () => {
|
|
88
|
+
const changes = taskCargoChanges(simpleTask(TaskType.UNDEPLOY))
|
|
89
|
+
expect(changes).toHaveLength(1)
|
|
90
|
+
expect(changes[0].direction).toBe('in')
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe('taskCargoChangesChecked', () => {
|
|
95
|
+
test('reports a known task type with its changes', () => {
|
|
96
|
+
const result = taskCargoChangesChecked(simpleTask(TaskType.CIVIC_WITHDRAW))
|
|
97
|
+
expect(result.known).toBe(true)
|
|
98
|
+
if (result.known) expect(result.changes[0].direction).toBe('in')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('distinguishes an unrecognised task type from one that moves nothing', () => {
|
|
102
|
+
const unknown = taskCargoChangesChecked(simpleTask(99))
|
|
103
|
+
expect(unknown.known).toBe(false)
|
|
104
|
+
if (!unknown.known) expect(unknown.taskType).toBe(99)
|
|
105
|
+
|
|
106
|
+
const travel = taskCargoChangesChecked(simpleTask(TaskType.TRAVEL))
|
|
107
|
+
expect(travel.known).toBe(true)
|
|
108
|
+
if (travel.known) expect(travel.changes).toHaveLength(0)
|
|
109
|
+
})
|
|
110
|
+
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type {ServerContract} from '../contracts'
|
|
2
|
+
import {type TaskCargoRule, taskCargoRule} from './task-effects'
|
|
3
|
+
|
|
4
|
+
export type TaskCargoDirection = 'in' | 'out'
|
|
5
|
+
|
|
6
|
+
export interface TaskCargoChange {
|
|
7
|
+
direction: TaskCargoDirection
|
|
8
|
+
item_id: number
|
|
9
|
+
stats: bigint
|
|
10
|
+
modules: ServerContract.Types.module_entry[]
|
|
11
|
+
quantity: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type TaskCargoResult =
|
|
15
|
+
| {known: true; changes: TaskCargoChange[]}
|
|
16
|
+
| {known: false; taskType: number}
|
|
17
|
+
|
|
18
|
+
function toChange(
|
|
19
|
+
item: ServerContract.Types.cargo_item,
|
|
20
|
+
direction: TaskCargoDirection
|
|
21
|
+
): TaskCargoChange {
|
|
22
|
+
return {
|
|
23
|
+
direction,
|
|
24
|
+
item_id: Number(item.item_id),
|
|
25
|
+
stats: BigInt(item.stats.toString()),
|
|
26
|
+
modules: item.modules ?? [],
|
|
27
|
+
quantity: Number(item.quantity),
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function changesForRule(rule: TaskCargoRule, task: ServerContract.Types.task): TaskCargoChange[] {
|
|
32
|
+
const items = task.cargo ?? []
|
|
33
|
+
if (items.length === 0) return []
|
|
34
|
+
switch (rule) {
|
|
35
|
+
case 'all-in':
|
|
36
|
+
case 'undeploy':
|
|
37
|
+
return items.map((i) => toChange(i, 'in'))
|
|
38
|
+
case 'all-out':
|
|
39
|
+
return items.map((i) => toChange(i, 'out'))
|
|
40
|
+
case 'gather':
|
|
41
|
+
return task.couplings.length > 0 ? [] : items.map((i) => toChange(i, 'in'))
|
|
42
|
+
case 'craft':
|
|
43
|
+
return [
|
|
44
|
+
...items.slice(0, -1).map((i) => toChange(i, 'out')),
|
|
45
|
+
...(task.couplings.length > 0 ? [] : [toChange(items[items.length - 1], 'in')]),
|
|
46
|
+
]
|
|
47
|
+
default:
|
|
48
|
+
return []
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function taskCargoChanges(task: ServerContract.Types.task): TaskCargoChange[] {
|
|
53
|
+
const rule = taskCargoRule(Number(task.type))
|
|
54
|
+
return rule ? changesForRule(rule, task) : []
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function taskCargoChangesChecked(task: ServerContract.Types.task): TaskCargoResult {
|
|
58
|
+
const type = Number(task.type)
|
|
59
|
+
const rule = taskCargoRule(type)
|
|
60
|
+
if (!rule) return {known: false, taskType: type}
|
|
61
|
+
return {known: true, changes: changesForRule(rule, task)}
|
|
62
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {describe, expect, test} from 'bun:test'
|
|
2
|
+
import {TaskType} from '../types'
|
|
3
|
+
import {taskCargoRule} from './task-effects'
|
|
4
|
+
|
|
5
|
+
describe('taskCargoRule', () => {
|
|
6
|
+
test('maps the task types that move cargo out of the entity', () => {
|
|
7
|
+
expect(taskCargoRule(TaskType.UNLOAD)).toBe('all-out')
|
|
8
|
+
expect(taskCargoRule(TaskType.UPGRADE)).toBe('all-out')
|
|
9
|
+
expect(taskCargoRule(TaskType.CONTRIBUTE)).toBe('all-out')
|
|
10
|
+
expect(taskCargoRule(TaskType.CIVIC_DEPOSIT)).toBe('all-out')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test('maps the task types that move cargo into the entity', () => {
|
|
14
|
+
expect(taskCargoRule(TaskType.LOAD)).toBe('all-in')
|
|
15
|
+
expect(taskCargoRule(TaskType.UNWRAP)).toBe('all-in')
|
|
16
|
+
expect(taskCargoRule(TaskType.CIVIC_WITHDRAW)).toBe('all-in')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('maps the conditional task types to their own rules', () => {
|
|
20
|
+
expect(taskCargoRule(TaskType.GATHER)).toBe('gather')
|
|
21
|
+
expect(taskCargoRule(TaskType.CRAFT)).toBe('craft')
|
|
22
|
+
expect(taskCargoRule(TaskType.UNDEPLOY)).toBe('undeploy')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('maps the task types that move no cargo', () => {
|
|
26
|
+
expect(taskCargoRule(TaskType.TRAVEL)).toBe('none')
|
|
27
|
+
expect(taskCargoRule(TaskType.RECHARGE)).toBe('none')
|
|
28
|
+
expect(taskCargoRule(TaskType.DEMOLISH)).toBe('none')
|
|
29
|
+
expect(taskCargoRule(TaskType.BUILDPLOT)).toBe('none')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('returns undefined for a task type the table does not know', () => {
|
|
33
|
+
expect(taskCargoRule(99)).toBeUndefined()
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {TaskType} from '../types'
|
|
2
|
+
|
|
3
|
+
export type TaskCargoRule = 'none' | 'all-in' | 'all-out' | 'gather' | 'craft' | 'undeploy'
|
|
4
|
+
|
|
5
|
+
// Transcribed from the switch in contracts/src/server/src/projection.cpp.
|
|
6
|
+
export const TASK_CARGO_EFFECTS: ReadonlyMap<number, TaskCargoRule> = new Map<
|
|
7
|
+
number,
|
|
8
|
+
TaskCargoRule
|
|
9
|
+
>([
|
|
10
|
+
[TaskType.IDLE, 'none'],
|
|
11
|
+
[TaskType.TRAVEL, 'none'],
|
|
12
|
+
[TaskType.RECHARGE, 'none'],
|
|
13
|
+
[TaskType.WARP, 'none'],
|
|
14
|
+
[TaskType.TRANSIT, 'none'],
|
|
15
|
+
[TaskType.LAUNCH, 'none'],
|
|
16
|
+
[TaskType.DEMOLISH, 'none'],
|
|
17
|
+
[TaskType.BUILDPLOT, 'none'],
|
|
18
|
+
[TaskType.CHARGE, 'none'],
|
|
19
|
+
[TaskType.SHUTTLE, 'none'],
|
|
20
|
+
[TaskType.LOAD, 'all-in'],
|
|
21
|
+
[TaskType.UNWRAP, 'all-in'],
|
|
22
|
+
[TaskType.CIVIC_WITHDRAW, 'all-in'],
|
|
23
|
+
[TaskType.UNLOAD, 'all-out'],
|
|
24
|
+
[TaskType.UPGRADE, 'all-out'],
|
|
25
|
+
[TaskType.CONTRIBUTE, 'all-out'],
|
|
26
|
+
[TaskType.CIVIC_DEPOSIT, 'all-out'],
|
|
27
|
+
[TaskType.GATHER, 'gather'],
|
|
28
|
+
[TaskType.CRAFT, 'craft'],
|
|
29
|
+
[TaskType.UNDEPLOY, 'undeploy'],
|
|
30
|
+
])
|
|
31
|
+
|
|
32
|
+
export function taskCargoRule(taskType: number): TaskCargoRule | undefined {
|
|
33
|
+
return TASK_CARGO_EFFECTS.get(taskType)
|
|
34
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {describe, expect, test} from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
derivedLoaders,
|
|
4
|
+
unwrapTransitDuration,
|
|
5
|
+
unwrapLoadDuration,
|
|
6
|
+
estimateUnwrapDuration,
|
|
7
|
+
incomingHoldMass,
|
|
8
|
+
projectedPeakCargomass,
|
|
9
|
+
} from './unwrap'
|
|
10
|
+
|
|
11
|
+
describe('unwrap duration mirror', () => {
|
|
12
|
+
test('derivedLoaders aggregates lanes like derived_loaders()', () => {
|
|
13
|
+
expect(derivedLoaders([])).toBeNull()
|
|
14
|
+
expect(
|
|
15
|
+
derivedLoaders([
|
|
16
|
+
{mass: 1000, thrust: 10},
|
|
17
|
+
{mass: 1400, thrust: 20},
|
|
18
|
+
])
|
|
19
|
+
).toEqual({mass: 1200, thrust: 30, quantity: 2}) // floor(2400/2)=1200, sum thrust, count
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('transit floors distance then flight time', () => {
|
|
23
|
+
// distance = floor(sqrt(3^2+4^2)*10000)=50000; accel=400/(mass*100)*10000; flight=floor(2*sqrt(d/accel))
|
|
24
|
+
const mass = 1000
|
|
25
|
+
const accel = (400 / (mass * 100)) * 10000
|
|
26
|
+
const expected = Math.floor(2 * Math.sqrt(50000 / accel))
|
|
27
|
+
expect(unwrapTransitDuration(mass, {x: 0, y: 0}, {x: 3, y: 4})).toBe(expected)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('load uses altitude z, adds loader mass, divides by quantity', () => {
|
|
31
|
+
const loaders = {mass: 1200, thrust: 30, quantity: 2}
|
|
32
|
+
const itemMass = 800
|
|
33
|
+
const accel = (30 / ((itemMass + 1200) * 100)) * 10000
|
|
34
|
+
const flight = Math.floor(2 * Math.sqrt(3000 / accel))
|
|
35
|
+
expect(unwrapLoadDuration(loaders, itemMass, 3000)).toBe(Math.floor(flight / 2))
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('zero item mass and no loaders are safe', () => {
|
|
39
|
+
expect(unwrapTransitDuration(0, {x: 0, y: 0}, {x: 9, y: 9})).toBe(0)
|
|
40
|
+
expect(unwrapLoadDuration(null, 500, 3000)).toBe(0)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('load altitude is floored so ground-level (z=0) destinations are not instant', () => {
|
|
44
|
+
const loaders = {mass: 500, thrust: 200, quantity: 1}
|
|
45
|
+
const atZero = unwrapLoadDuration(loaders, 1000, 0)
|
|
46
|
+
const at800 = unwrapLoadDuration(loaders, 1000, 800)
|
|
47
|
+
expect(atZero).toBeGreaterThan(0)
|
|
48
|
+
expect(atZero).toBe(at800)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('estimateUnwrapDuration uses worst-loader baseline when dest has no loaders', () => {
|
|
52
|
+
// IRON (101); same coords so transit is 0 and the estimate is baseline load alone
|
|
53
|
+
const item = {itemId: 101, quantity: 10, modules: [], originX: 0, originY: 0}
|
|
54
|
+
const bareDest = {loader_lanes: [], coordinates: {x: 0, y: 0, z: 800}}
|
|
55
|
+
|
|
56
|
+
const bare = estimateUnwrapDuration(bareDest, item)
|
|
57
|
+
expect(bare).toBeGreaterThan(0)
|
|
58
|
+
|
|
59
|
+
const loadedDest = {
|
|
60
|
+
loader_lanes: [{mass: 500, thrust: 200}],
|
|
61
|
+
coordinates: {x: 0, y: 0, z: 800},
|
|
62
|
+
}
|
|
63
|
+
const loaded = estimateUnwrapDuration(loadedDest, item)
|
|
64
|
+
expect(loaded).toBeLessThanOrEqual(bare)
|
|
65
|
+
|
|
66
|
+
expect(unwrapLoadDuration(null, 500, 3000)).toBe(0)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('incomingHoldMass sums incoming-kind hold mass', () => {
|
|
71
|
+
expect(incomingHoldMass([])).toBe(0)
|
|
72
|
+
// PUSH(2) + FLIGHT(5) count; BUILD(4) does not
|
|
73
|
+
expect(
|
|
74
|
+
incomingHoldMass([
|
|
75
|
+
{kind: 2, incoming_mass: 100},
|
|
76
|
+
{kind: 4, incoming_mass: 999},
|
|
77
|
+
{kind: 5, incoming_mass: 50},
|
|
78
|
+
])
|
|
79
|
+
).toBe(150)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('projectedPeakCargomass tracks the running peak from cargomass', () => {
|
|
83
|
+
const entity = {cargomass: 1000, lanes: [], cargo: [], schedule: undefined} as never
|
|
84
|
+
// No pending tasks: peak = base + candidate add.
|
|
85
|
+
expect(projectedPeakCargomass(entity, new Date(0), 500)).toBe(1500)
|
|
86
|
+
})
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type {UInt16Type, UInt32Type} from '@wharfkit/antelope'
|
|
2
|
+
import {calcCargoItemMass} from '../capabilities/storage'
|
|
3
|
+
import type {ServerContract} from '../contracts'
|
|
4
|
+
import {MASS_STAT_SCALE, PRECISION} from '../types'
|
|
5
|
+
import * as sched from './schedule'
|
|
6
|
+
import {taskCargoEffect} from './availability'
|
|
7
|
+
import {candidateLaneCompletesAt} from './lanes'
|
|
8
|
+
|
|
9
|
+
const NFT_TRANSIT_THRUST = 400
|
|
10
|
+
const BASELINE_LOADER: DerivedLoaders = {mass: 20, thrust: 1, quantity: 1}
|
|
11
|
+
// ground-level entities (warehouses, z=0) still incur a base orbital climb of load effort
|
|
12
|
+
const MIN_LOAD_Z = 800
|
|
13
|
+
|
|
14
|
+
export interface DerivedLoaders {
|
|
15
|
+
mass: number
|
|
16
|
+
thrust: number
|
|
17
|
+
quantity: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function derivedLoaders(
|
|
21
|
+
lanes: {mass: UInt32Type | number; thrust: UInt16Type | number}[] | undefined
|
|
22
|
+
): DerivedLoaders | null {
|
|
23
|
+
if (!lanes || lanes.length === 0) return null
|
|
24
|
+
let totalMass = 0
|
|
25
|
+
let totalThrust = 0
|
|
26
|
+
for (const l of lanes) {
|
|
27
|
+
totalMass += Number(l.mass)
|
|
28
|
+
totalThrust += Number(l.thrust)
|
|
29
|
+
}
|
|
30
|
+
const count = lanes.length
|
|
31
|
+
return {
|
|
32
|
+
mass: Math.floor(totalMass / count),
|
|
33
|
+
thrust: Math.min(totalThrust, 65_535),
|
|
34
|
+
quantity: count,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function acceleration(thrust: number, mass: number): number {
|
|
39
|
+
if (mass <= 0) return 0
|
|
40
|
+
return (thrust / (mass * MASS_STAT_SCALE)) * PRECISION
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function flightTime(distance: number, accel: number): number {
|
|
44
|
+
if (accel <= 0 || distance <= 0) return 0
|
|
45
|
+
return Math.floor(2 * Math.sqrt(distance / accel))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function distance2d(ax: number, ay: number, bx: number, by: number): number {
|
|
49
|
+
const dx = ax - bx
|
|
50
|
+
const dy = ay - by
|
|
51
|
+
return Math.floor(Math.sqrt(dx * dx + dy * dy) * PRECISION)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function unwrapTransitDuration(
|
|
55
|
+
itemMass: number,
|
|
56
|
+
origin: {x: number; y: number},
|
|
57
|
+
dest: {x: number; y: number}
|
|
58
|
+
): number {
|
|
59
|
+
if (itemMass <= 0) return 0
|
|
60
|
+
return flightTime(
|
|
61
|
+
distance2d(origin.x, origin.y, dest.x, dest.y),
|
|
62
|
+
acceleration(NFT_TRANSIT_THRUST, itemMass)
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function unwrapLoadDuration(
|
|
67
|
+
loaders: DerivedLoaders | null,
|
|
68
|
+
itemMass: number,
|
|
69
|
+
destZ: number
|
|
70
|
+
): number {
|
|
71
|
+
if (!loaders || itemMass <= 0) return 0
|
|
72
|
+
const total = itemMass + loaders.mass
|
|
73
|
+
const flight = flightTime(Math.max(destZ, MIN_LOAD_Z), acceleration(loaders.thrust, total))
|
|
74
|
+
return Math.floor(flight / loaders.quantity)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface UnwrapItem {
|
|
78
|
+
itemId: number
|
|
79
|
+
quantity: number
|
|
80
|
+
modules: ServerContract.Types.module_entry[]
|
|
81
|
+
originX: number
|
|
82
|
+
originY: number
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface UnwrapDestination {
|
|
86
|
+
loader_lanes?: {mass: UInt32Type | number; thrust: UInt16Type | number}[]
|
|
87
|
+
coordinates: {x: UInt32Type | number; y: UInt32Type | number; z?: UInt32Type | number}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function estimateUnwrapDuration(dest: UnwrapDestination, item: UnwrapItem): number {
|
|
91
|
+
const itemMass = Number(
|
|
92
|
+
calcCargoItemMass({
|
|
93
|
+
item_id: item.itemId as never,
|
|
94
|
+
quantity: item.quantity as never,
|
|
95
|
+
modules: item.modules,
|
|
96
|
+
})
|
|
97
|
+
)
|
|
98
|
+
const loaders = derivedLoaders(dest.loader_lanes) ?? BASELINE_LOADER
|
|
99
|
+
const dz = Number(dest.coordinates.z ?? 0)
|
|
100
|
+
const load = unwrapLoadDuration(loaders, itemMass, dz)
|
|
101
|
+
const transit = unwrapTransitDuration(
|
|
102
|
+
itemMass,
|
|
103
|
+
{x: item.originX, y: item.originY},
|
|
104
|
+
{
|
|
105
|
+
x: Number(dest.coordinates.x),
|
|
106
|
+
y: Number(dest.coordinates.y),
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
return load + transit
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Hold kinds that count as incoming (mirror is_incoming_hold_kind in holds.hpp).
|
|
113
|
+
const INCOMING_HOLD_KINDS = new Set<number>([2, 3, 5])
|
|
114
|
+
|
|
115
|
+
export function incomingHoldMass(
|
|
116
|
+
holds:
|
|
117
|
+
| {kind: number | {toNumber(): number}; incoming_mass: number | {toNumber(): number}}[]
|
|
118
|
+
| undefined
|
|
119
|
+
): number {
|
|
120
|
+
if (!holds) return 0
|
|
121
|
+
let total = 0
|
|
122
|
+
for (const h of holds) {
|
|
123
|
+
if (INCOMING_HOLD_KINDS.has(Number(h.kind))) total += Number(h.incoming_mass)
|
|
124
|
+
}
|
|
125
|
+
return total
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
type CargoItem = ServerContract.Types.cargo_item
|
|
129
|
+
|
|
130
|
+
function cargoListMass(items: CargoItem[]): number {
|
|
131
|
+
let m = 0
|
|
132
|
+
for (const it of items) m += Number(calcCargoItemMass(it))
|
|
133
|
+
return m
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function projectedPeakCargomass(
|
|
137
|
+
entity: sched.ScheduleData & {cargomass: number | {toNumber(): number}},
|
|
138
|
+
at: Date,
|
|
139
|
+
addMass: number,
|
|
140
|
+
removeMass = 0
|
|
141
|
+
): number {
|
|
142
|
+
const events: {t: number; delta: number}[] = []
|
|
143
|
+
for (const ordered of sched.orderedTasks(entity)) {
|
|
144
|
+
const eff = taskCargoEffect(ordered.task)
|
|
145
|
+
const delta = cargoListMass(eff.added) - cargoListMass(eff.removed)
|
|
146
|
+
events.push({t: ordered.completesAt.getTime(), delta})
|
|
147
|
+
}
|
|
148
|
+
events.push({t: at.getTime(), delta: addMass - removeMass})
|
|
149
|
+
events.sort((a, b) => (a.t !== b.t ? a.t - b.t : b.delta - a.delta))
|
|
150
|
+
let running = Number(entity.cargomass)
|
|
151
|
+
let peak = running
|
|
152
|
+
for (const e of events) {
|
|
153
|
+
running += e.delta
|
|
154
|
+
if (running < 0) running = 0
|
|
155
|
+
if (running > peak) peak = running
|
|
156
|
+
}
|
|
157
|
+
return Math.min(peak, 0xffff_ffff)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function receiveFits(
|
|
161
|
+
dest: UnwrapDestination &
|
|
162
|
+
sched.ScheduleData & {
|
|
163
|
+
cargomass: number | {toNumber(): number}
|
|
164
|
+
capacity?: number | {toNumber(): number}
|
|
165
|
+
holds?: {
|
|
166
|
+
kind: number | {toNumber(): number}
|
|
167
|
+
incoming_mass: number | {toNumber(): number}
|
|
168
|
+
}[]
|
|
169
|
+
},
|
|
170
|
+
item: UnwrapItem,
|
|
171
|
+
now: Date
|
|
172
|
+
): boolean {
|
|
173
|
+
const capacity = Number(dest.capacity ?? 0)
|
|
174
|
+
if (capacity <= 0) return false
|
|
175
|
+
const itemMass = Number(
|
|
176
|
+
calcCargoItemMass({
|
|
177
|
+
item_id: item.itemId as never,
|
|
178
|
+
quantity: item.quantity as never,
|
|
179
|
+
modules: item.modules,
|
|
180
|
+
})
|
|
181
|
+
)
|
|
182
|
+
const duration = estimateUnwrapDuration(dest, item)
|
|
183
|
+
const candidateCompletes = candidateLaneCompletesAt(dest, sched.LANE_MOBILITY, duration, now)
|
|
184
|
+
const peak = projectedPeakCargomass(
|
|
185
|
+
dest,
|
|
186
|
+
candidateCompletes,
|
|
187
|
+
itemMass + incomingHoldMass(dest.holds)
|
|
188
|
+
)
|
|
189
|
+
return peak <= capacity
|
|
190
|
+
}
|
package/src/shipload.ts
CHANGED
|
@@ -1,34 +1,43 @@
|
|
|
1
1
|
import {APIClient} from '@wharfkit/antelope'
|
|
2
|
-
import {PlatformContract, ServerContract} from './contracts'
|
|
2
|
+
import {FundContract, PlatformContract, ServerContract} from './contracts'
|
|
3
3
|
import type {ChainDefinition} from '@wharfkit/common'
|
|
4
|
-
import ContractKit,
|
|
4
|
+
import {ContractKit, type Contract} from '@wharfkit/contract'
|
|
5
5
|
|
|
6
6
|
import {GameContext} from './managers/context'
|
|
7
7
|
import type {EntitiesManager} from './managers/entities'
|
|
8
8
|
import type {PlayersManager} from './managers/players'
|
|
9
9
|
import type {LocationsManager} from './managers/locations'
|
|
10
|
+
import type {CoordinatesManager} from './managers/coordinates'
|
|
10
11
|
import type {EpochsManager} from './managers/epochs'
|
|
11
12
|
import type {ActionsManager} from './managers/actions'
|
|
13
|
+
import type {ClusterManager} from './managers/cluster'
|
|
14
|
+
import type {NftManager} from './managers/nft'
|
|
15
|
+
import type {BalancesManager} from './managers/balances'
|
|
16
|
+
import type {JobsManager} from './managers/jobs'
|
|
17
|
+
import type {InfluenceManager} from './managers/influence'
|
|
12
18
|
import type {SubscriptionsManager} from './subscriptions/manager'
|
|
13
19
|
import type {GameState} from './entities/gamestate'
|
|
14
20
|
|
|
15
21
|
interface ShiploadOptions {
|
|
16
22
|
platformContractName?: string
|
|
17
23
|
serverContractName?: string
|
|
24
|
+
fundContractName?: string
|
|
18
25
|
client?: APIClient
|
|
19
26
|
subscriptionsUrl?: string
|
|
27
|
+
atomicAssetsAccount?: string
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
interface ShiploadConstructorOptions extends ShiploadOptions {
|
|
23
31
|
platformContract?: Contract
|
|
24
32
|
serverContract?: Contract
|
|
33
|
+
fundContract?: Contract
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
export class Shipload {
|
|
28
37
|
private readonly _context: GameContext
|
|
29
38
|
|
|
30
39
|
constructor(chain: ChainDefinition, constructorOptions?: ShiploadConstructorOptions) {
|
|
31
|
-
const {client, platformContract, serverContract} = constructorOptions || {}
|
|
40
|
+
const {client, platformContract, serverContract, fundContract} = constructorOptions || {}
|
|
32
41
|
const apiClient = client || new APIClient({url: chain.url})
|
|
33
42
|
|
|
34
43
|
const platform = platformContract
|
|
@@ -39,7 +48,15 @@ export class Shipload {
|
|
|
39
48
|
? serverContract
|
|
40
49
|
: new ServerContract.Contract({client: apiClient})
|
|
41
50
|
|
|
42
|
-
|
|
51
|
+
const fund = fundContract ? fundContract : new FundContract.Contract({client: apiClient})
|
|
52
|
+
|
|
53
|
+
this._context = new GameContext(
|
|
54
|
+
apiClient,
|
|
55
|
+
server,
|
|
56
|
+
platform,
|
|
57
|
+
constructorOptions?.atomicAssetsAccount ?? 'atomicassets',
|
|
58
|
+
fund
|
|
59
|
+
)
|
|
43
60
|
|
|
44
61
|
if (constructorOptions?.subscriptionsUrl) {
|
|
45
62
|
this._context.setSubscriptionsUrl(constructorOptions.subscriptionsUrl)
|
|
@@ -68,10 +85,20 @@ export class Shipload {
|
|
|
68
85
|
server = await contractKit.load(shiploadOptions.serverContractName)
|
|
69
86
|
}
|
|
70
87
|
|
|
88
|
+
let fund: Contract = new FundContract.Contract({
|
|
89
|
+
client: new APIClient({url: chain.url}),
|
|
90
|
+
})
|
|
91
|
+
if (shiploadOptions?.fundContractName) {
|
|
92
|
+
const client = shiploadOptions.client || new APIClient({url: chain.url})
|
|
93
|
+
const contractKit = new ContractKit({client})
|
|
94
|
+
fund = await contractKit.load(shiploadOptions.fundContractName)
|
|
95
|
+
}
|
|
96
|
+
|
|
71
97
|
return new Shipload(chain, {
|
|
72
98
|
...shiploadOptions,
|
|
73
99
|
platformContract: platform,
|
|
74
100
|
serverContract: server,
|
|
101
|
+
fundContract: fund,
|
|
75
102
|
})
|
|
76
103
|
}
|
|
77
104
|
|
|
@@ -79,6 +106,10 @@ export class Shipload {
|
|
|
79
106
|
return this._context.client
|
|
80
107
|
}
|
|
81
108
|
|
|
109
|
+
get atomicAssetsAccount(): string {
|
|
110
|
+
return this._context.atomicAssetsAccount
|
|
111
|
+
}
|
|
112
|
+
|
|
82
113
|
get server(): Contract {
|
|
83
114
|
return this._context.server
|
|
84
115
|
}
|
|
@@ -87,6 +118,10 @@ export class Shipload {
|
|
|
87
118
|
return this._context.platform
|
|
88
119
|
}
|
|
89
120
|
|
|
121
|
+
get fund(): Contract {
|
|
122
|
+
return this._context.fund
|
|
123
|
+
}
|
|
124
|
+
|
|
90
125
|
get entities(): EntitiesManager {
|
|
91
126
|
return this._context.entities
|
|
92
127
|
}
|
|
@@ -99,6 +134,10 @@ export class Shipload {
|
|
|
99
134
|
return this._context.locations
|
|
100
135
|
}
|
|
101
136
|
|
|
137
|
+
get coordinates(): CoordinatesManager {
|
|
138
|
+
return this._context.coordinates
|
|
139
|
+
}
|
|
140
|
+
|
|
102
141
|
get epochs(): EpochsManager {
|
|
103
142
|
return this._context.epochs
|
|
104
143
|
}
|
|
@@ -107,6 +146,26 @@ export class Shipload {
|
|
|
107
146
|
return this._context.actions
|
|
108
147
|
}
|
|
109
148
|
|
|
149
|
+
get clusters(): ClusterManager {
|
|
150
|
+
return this._context.clusters
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
get nft(): NftManager {
|
|
154
|
+
return this._context.nft
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
get balances(): BalancesManager {
|
|
158
|
+
return this._context.balances
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
get jobs(): JobsManager {
|
|
162
|
+
return this._context.jobs
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
get influence(): InfluenceManager {
|
|
166
|
+
return this._context.influence
|
|
167
|
+
}
|
|
168
|
+
|
|
110
169
|
get subscriptions(): SubscriptionsManager {
|
|
111
170
|
return this._context.subscriptions
|
|
112
171
|
}
|