@shipload/sdk 1.0.0-next.7 → 1.0.0-next.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/lib/scan.d.ts +52 -0
  2. package/lib/scan.js +162 -0
  3. package/lib/scan.js.map +1 -0
  4. package/lib/scan.m.js +152 -0
  5. package/lib/scan.m.js.map +1 -0
  6. package/lib/shipload.d.ts +5696 -1503
  7. package/lib/shipload.js +20294 -5200
  8. package/lib/shipload.js.map +1 -1
  9. package/lib/shipload.m.js +20371 -5683
  10. package/lib/shipload.m.js.map +1 -1
  11. package/lib/testing.d.ts +1648 -0
  12. package/lib/testing.js +6980 -0
  13. package/lib/testing.js.map +1 -0
  14. package/lib/testing.m.js +6974 -0
  15. package/lib/testing.m.js.map +1 -0
  16. package/package.json +20 -2
  17. package/src/capabilities/craftable.test.ts +82 -0
  18. package/src/capabilities/craftable.ts +82 -0
  19. package/src/capabilities/crafting.test.ts +80 -0
  20. package/src/capabilities/crafting.ts +50 -8
  21. package/src/capabilities/gathering.test.ts +29 -0
  22. package/src/capabilities/gathering.ts +38 -20
  23. package/src/capabilities/hauling.ts +0 -5
  24. package/src/capabilities/index.ts +0 -1
  25. package/src/capabilities/modules.ts +27 -30
  26. package/src/capabilities/movement.ts +1 -1
  27. package/src/capabilities/storage.test.ts +26 -0
  28. package/src/capabilities/storage.ts +22 -7
  29. package/src/contracts/fund.ts +306 -0
  30. package/src/contracts/platform.ts +312 -3
  31. package/src/contracts/server.ts +2661 -466
  32. package/src/contracts/token.ts +170 -0
  33. package/src/contracts.ts +2 -0
  34. package/src/coordinates/address.ts +88 -0
  35. package/src/coordinates/constants.test.ts +15 -0
  36. package/src/coordinates/constants.ts +23 -0
  37. package/src/coordinates/index.ts +15 -0
  38. package/src/coordinates/memo.test.ts +47 -0
  39. package/src/coordinates/memo.ts +20 -0
  40. package/src/coordinates/permutation.ts +77 -0
  41. package/src/coordinates/regions.ts +48 -0
  42. package/src/coordinates/sectors.ts +115 -0
  43. package/src/data/capabilities.ts +49 -18
  44. package/src/data/capability-formulas.ts +26 -14
  45. package/src/data/catalog.test.ts +19 -0
  46. package/src/data/catalog.ts +13 -3
  47. package/src/data/charters.json +396 -0
  48. package/src/data/citizenry-adjectives.json +42 -0
  49. package/src/data/citizenry-nouns.json +50 -0
  50. package/src/data/colors.ts +14 -32
  51. package/src/data/entities.json +421 -15
  52. package/src/data/item-ids.ts +53 -13
  53. package/src/data/items.json +330 -77
  54. package/src/data/kind-registry.json +212 -0
  55. package/src/data/kind-registry.ts +192 -0
  56. package/src/data/metadata.ts +312 -51
  57. package/src/data/recipes-runtime.ts +6 -23
  58. package/src/data/recipes.json +2173 -173
  59. package/src/data/tiers.ts +9 -6
  60. package/src/derivation/build-methods.test.ts +71 -0
  61. package/src/derivation/build-methods.ts +50 -0
  62. package/src/derivation/capabilities.test.ts +348 -0
  63. package/src/derivation/capabilities.ts +707 -0
  64. package/src/derivation/capability-mappings.ts +60 -16
  65. package/src/derivation/crafting.test.ts +17 -0
  66. package/src/derivation/crafting.ts +57 -34
  67. package/src/derivation/index.ts +27 -2
  68. package/src/derivation/recipe-usage.test.ts +88 -0
  69. package/src/derivation/recipe-usage.ts +141 -0
  70. package/src/derivation/reserve-regen.ts +34 -0
  71. package/src/derivation/resources.ts +9 -1
  72. package/src/derivation/rollups.test.ts +55 -0
  73. package/src/derivation/rollups.ts +72 -0
  74. package/src/derivation/stars.test.ts +67 -0
  75. package/src/derivation/stars.ts +25 -0
  76. package/src/derivation/stat-scaling.ts +12 -0
  77. package/src/derivation/stats.ts +6 -6
  78. package/src/derivation/strata.ts +5 -3
  79. package/src/derivation/stratum.ts +26 -22
  80. package/src/derivation/tiers.ts +40 -7
  81. package/src/derivation/upgrades.ts +14 -0
  82. package/src/derivation/wormhole.ts +154 -0
  83. package/src/entities/entity.ts +102 -0
  84. package/src/entities/gamestate.ts +3 -28
  85. package/src/entities/makers.ts +144 -136
  86. package/src/entities/slot-multiplier.ts +43 -0
  87. package/src/entity/hydrate.test.ts +156 -0
  88. package/src/entity/hydrate.ts +86 -0
  89. package/src/errors.ts +21 -16
  90. package/src/format.ts +42 -9
  91. package/src/index-module.ts +411 -32
  92. package/src/influence/ballot.test.ts +103 -0
  93. package/src/influence/ballot.ts +98 -0
  94. package/src/influence/categories.ts +32 -0
  95. package/src/influence/charter-metadata.test.ts +136 -0
  96. package/src/influence/charter-metadata.ts +236 -0
  97. package/src/influence/charters.test.ts +112 -0
  98. package/src/influence/charters.ts +150 -0
  99. package/src/influence/citizenry.ts +38 -0
  100. package/src/influence/civic-shuttle.test.ts +202 -0
  101. package/src/influence/civic-shuttle.ts +142 -0
  102. package/src/influence/civic.test.ts +287 -0
  103. package/src/influence/civic.ts +269 -0
  104. package/src/influence/constants.ts +84 -0
  105. package/src/influence/decay.ts +20 -0
  106. package/src/influence/decomp.ts +95 -0
  107. package/src/influence/demand.ts +106 -0
  108. package/src/influence/duration.ts +101 -0
  109. package/src/influence/index.ts +118 -0
  110. package/src/influence/influence.test.ts +450 -0
  111. package/src/influence/quality.ts +25 -0
  112. package/src/influence/valuation.ts +154 -0
  113. package/src/managers/actions.ts +940 -93
  114. package/src/managers/balances.test.ts +161 -0
  115. package/src/managers/balances.ts +93 -0
  116. package/src/managers/base.ts +10 -2
  117. package/src/managers/cluster.test.ts +39 -0
  118. package/src/managers/cluster.ts +53 -0
  119. package/src/managers/construction-types.ts +80 -0
  120. package/src/managers/construction.ts +453 -0
  121. package/src/managers/context.ts +58 -1
  122. package/src/managers/coordinates.ts +14 -0
  123. package/src/managers/entities.ts +35 -63
  124. package/src/managers/epochs.ts +72 -1
  125. package/src/managers/index.ts +38 -1
  126. package/src/managers/influence.test.ts +56 -0
  127. package/src/managers/influence.ts +425 -0
  128. package/src/managers/jobs.test.ts +80 -0
  129. package/src/managers/jobs.ts +54 -0
  130. package/src/managers/locations.ts +25 -29
  131. package/src/managers/nft.test.ts +21 -0
  132. package/src/managers/nft.ts +110 -0
  133. package/src/managers/players.ts +25 -0
  134. package/src/managers/plot.ts +122 -0
  135. package/src/nft/atomicassets.abi.json +1342 -0
  136. package/src/nft/atomicassets.ts +237 -0
  137. package/src/nft/atomicdata.ts +130 -0
  138. package/src/nft/buildImmutableData.ts +388 -0
  139. package/src/nft/description.ts +222 -100
  140. package/src/nft/index.ts +3 -0
  141. package/src/planner/index.ts +296 -0
  142. package/src/planner/planner.test.ts +459 -0
  143. package/src/receipts.ts +22 -0
  144. package/src/resolution/describe-module.ts +43 -25
  145. package/src/resolution/display-name.ts +39 -11
  146. package/src/resolution/resolve-item.test.ts +48 -0
  147. package/src/resolution/resolve-item.ts +112 -58
  148. package/src/scan/index.ts +244 -0
  149. package/src/scan/scan-wasm.base64.ts +2 -0
  150. package/src/scheduling/accessor.ts +65 -23
  151. package/src/scheduling/availability.test.ts +320 -0
  152. package/src/scheduling/availability.ts +251 -0
  153. package/src/scheduling/cancel.test.ts +550 -0
  154. package/src/scheduling/cancel.ts +254 -0
  155. package/src/scheduling/cluster-stock.test.ts +31 -0
  156. package/src/scheduling/cluster-stock.ts +15 -0
  157. package/src/scheduling/energy.ts +47 -0
  158. package/src/scheduling/epoch.ts +1 -0
  159. package/src/scheduling/idle-resolve.ts +45 -0
  160. package/src/scheduling/jobs.test.ts +49 -0
  161. package/src/scheduling/jobs.ts +137 -0
  162. package/src/scheduling/lane-core.ts +128 -0
  163. package/src/scheduling/lanes.test.ts +250 -0
  164. package/src/scheduling/lanes.ts +227 -0
  165. package/src/scheduling/projection.ts +265 -149
  166. package/src/scheduling/schedule.test.ts +163 -0
  167. package/src/scheduling/schedule.ts +282 -116
  168. package/src/scheduling/task-cargo.test.ts +110 -0
  169. package/src/scheduling/task-cargo.ts +62 -0
  170. package/src/scheduling/task-effects.test.ts +35 -0
  171. package/src/scheduling/task-effects.ts +34 -0
  172. package/src/scheduling/unwrap.test.ts +86 -0
  173. package/src/scheduling/unwrap.ts +190 -0
  174. package/src/shipload.ts +63 -4
  175. package/src/subscriptions/manager.cluster.test.ts +51 -0
  176. package/src/subscriptions/manager.ts +245 -172
  177. package/src/subscriptions/mappers.ts +5 -8
  178. package/src/subscriptions/types.ts +28 -3
  179. package/src/testing/catalog-hash.ts +19 -0
  180. package/src/testing/index.ts +2 -0
  181. package/src/testing/projection-parity.ts +167 -0
  182. package/src/travel/reach.test.ts +86 -0
  183. package/src/travel/reach.ts +34 -0
  184. package/src/travel/route-build.test.ts +63 -0
  185. package/src/travel/route-build.ts +60 -0
  186. package/src/travel/route-planner.ts +288 -0
  187. package/src/travel/route-simulator.ts +170 -0
  188. package/src/travel/travel.ts +190 -123
  189. package/src/types/capabilities.ts +37 -8
  190. package/src/types/entity.ts +3 -3
  191. package/src/types/index.ts +0 -1
  192. package/src/types.ts +81 -23
  193. package/src/utils/cargo.test.ts +14 -0
  194. package/src/utils/cargo.ts +2 -0
  195. package/src/utils/display-name.ts +70 -0
  196. package/src/utils/hash.ts +22 -2
  197. package/src/utils/system.ts +24 -5
  198. package/src/capabilities/loading.ts +0 -8
  199. package/src/entities/container.ts +0 -123
  200. package/src/entities/ship-deploy.ts +0 -295
  201. package/src/entities/ship.ts +0 -221
  202. package/src/entities/warehouse.ts +0 -127
  203. 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, {type Contract} from '@wharfkit/contract'
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
- this._context = new GameContext(apiClient, server, platform)
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
  }