@shipload/sdk 1.0.0-next.35 → 1.0.0-next.36

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 (50) hide show
  1. package/lib/shipload.d.ts +237 -80
  2. package/lib/shipload.js +2979 -2598
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +2960 -2599
  5. package/lib/shipload.m.js.map +1 -1
  6. package/lib/testing.d.ts +66 -20
  7. package/lib/testing.js +95 -57
  8. package/lib/testing.js.map +1 -1
  9. package/lib/testing.m.js +95 -57
  10. package/lib/testing.m.js.map +1 -1
  11. package/package.json +1 -1
  12. package/src/capabilities/crafting.ts +2 -3
  13. package/src/capabilities/gathering.test.ts +16 -0
  14. package/src/capabilities/gathering.ts +8 -11
  15. package/src/contracts/server.ts +45 -29
  16. package/src/coordinates/address.ts +9 -5
  17. package/src/coordinates/constants.test.ts +15 -0
  18. package/src/coordinates/constants.ts +5 -3
  19. package/src/coordinates/index.ts +11 -0
  20. package/src/coordinates/memo.test.ts +47 -0
  21. package/src/coordinates/memo.ts +20 -0
  22. package/src/data/capability-formulas.ts +0 -1
  23. package/src/data/entities.json +4 -4
  24. package/src/data/items.json +5 -5
  25. package/src/data/recipes.json +39 -65
  26. package/src/derivation/capabilities.test.ts +133 -0
  27. package/src/derivation/capabilities.ts +66 -14
  28. package/src/derivation/rollups.test.ts +55 -0
  29. package/src/derivation/rollups.ts +56 -0
  30. package/src/entities/makers.ts +30 -3
  31. package/src/index-module.ts +15 -2
  32. package/src/managers/actions.ts +34 -3
  33. package/src/managers/construction.ts +6 -4
  34. package/src/managers/context.ts +9 -0
  35. package/src/managers/coordinates.ts +14 -0
  36. package/src/managers/plot.ts +2 -4
  37. package/src/nft/description.ts +25 -6
  38. package/src/planner/index.ts +127 -0
  39. package/src/planner/planner.test.ts +319 -0
  40. package/src/resolution/resolve-item.ts +4 -1
  41. package/src/scheduling/cancel.test.ts +21 -0
  42. package/src/scheduling/lanes.test.ts +249 -0
  43. package/src/scheduling/lanes.ts +140 -2
  44. package/src/scheduling/projection.ts +73 -16
  45. package/src/shipload.ts +5 -0
  46. package/src/testing/projection-parity.ts +26 -2
  47. package/src/travel/travel.ts +102 -101
  48. package/src/types/capabilities.ts +23 -6
  49. package/src/types/entity.ts +3 -3
  50. package/src/types.ts +1 -1
@@ -1,12 +1,47 @@
1
1
  import type {ServerContract} from '../contracts'
2
2
  import {getItem} from '../data/catalog'
3
+ import {getEntityLayout} from '../data/recipes-runtime'
4
+ import {decodeStat} from '../derivation/crafting'
5
+ import {gathererDepthForTier} from '../derivation/capabilities'
6
+ import {applySlotMultiplier, getSlotAmp} from '../entities/slot-multiplier'
7
+ import {
8
+ computeGathererYield,
9
+ computeGathererDrain,
10
+ computeLoaderThrust,
11
+ computeLoaderMass,
12
+ computeCrafterSpeed,
13
+ computeCrafterDrain,
14
+ } from '../nft/description'
3
15
  import type {ModuleType} from '../types'
4
- import {getLane, type ScheduleData} from './schedule'
16
+ import {getLane, LANE_MOBILITY, type ScheduleData} from './schedule'
5
17
 
6
18
  type ModuleEntry = ServerContract.Types.module_entry
7
19
  type Lane = ServerContract.Types.lane
8
20
  type Schedule = ServerContract.Types.schedule
9
21
 
22
+ export interface ResolvedGathererLane {
23
+ slotIndex: number
24
+ yield: number
25
+ drain: number
26
+ depth: number
27
+ outputPct: number
28
+ }
29
+
30
+ export interface ResolvedCrafterLane {
31
+ slotIndex: number
32
+ speed: number
33
+ drain: number
34
+ outputPct: number
35
+ }
36
+
37
+ export interface ResolvedLoaderLane {
38
+ slotIndex: number
39
+ thrust: number
40
+ mass: number
41
+ outputPct: number
42
+ valid: boolean
43
+ }
44
+
10
45
  export function laneKeyForModule(slotIndex: number): number {
11
46
  return slotIndex + 1
12
47
  }
@@ -16,11 +51,114 @@ function laneIsFree(lanes: Lane[], laneKey: number): boolean {
16
51
  return lane ? lane.schedule.tasks.length === 0 : true
17
52
  }
18
53
 
54
+ export function resolveLaneGatherer(
55
+ modules: ModuleEntry[],
56
+ entityItemId: number,
57
+ laneKey: number
58
+ ): ResolvedGathererLane {
59
+ const idx = laneKey - 1
60
+ const installed = idx >= 0 && idx < modules.length ? modules[idx].installed : undefined
61
+ if (!installed) throw new Error('gatherer lane has no module')
62
+ const item = getItem(Number(installed.item_id.value ?? installed.item_id))
63
+ if (item.moduleType !== 'gatherer') throw new Error('lane module is not a gatherer')
64
+ const stats = BigInt(installed.stats.toString())
65
+ const str = decodeStat(stats, 0)
66
+ const tol = decodeStat(stats, 1)
67
+ const con = decodeStat(stats, 2)
68
+ const layout = getEntityLayout(entityItemId)?.slots ?? []
69
+ const amp = getSlotAmp(layout, idx)
70
+ const yieldVal = applySlotMultiplier(computeGathererYield(str), amp)
71
+ const drain = computeGathererDrain(con)
72
+ const depth = gathererDepthForTier(tol, item.tier ?? 1)
73
+ return {slotIndex: idx, yield: yieldVal, drain, depth, outputPct: amp}
74
+ }
75
+
76
+ // Encapsulates the gather handler's lane selection (gathering.cpp:108-112): both error paths.
77
+ export function selectGatherLane(
78
+ modules: ModuleEntry[],
79
+ entityItemId: number,
80
+ lanes: Lane[],
81
+ stratum: number,
82
+ explicitSlot?: number
83
+ ): number {
84
+ if (explicitSlot !== undefined) {
85
+ const laneKey = laneKeyForModule(explicitSlot)
86
+ const lane = resolveLaneGatherer(modules, entityItemId, laneKey)
87
+ if (stratum > lane.depth) throw new Error('stratum exceeds gatherer depth')
88
+ return laneKey
89
+ }
90
+ return workerLaneKey(modules, 'gatherer', lanes, stratum)
91
+ }
92
+
93
+ export function resolveLaneCrafter(
94
+ modules: ModuleEntry[],
95
+ entityItemId: number,
96
+ laneKey: number
97
+ ): ResolvedCrafterLane {
98
+ const idx = laneKey - 1
99
+ const installed = idx >= 0 && idx < modules.length ? modules[idx].installed : undefined
100
+ if (!installed) throw new Error('crafter lane has no module')
101
+ const item = getItem(Number(installed.item_id.value ?? installed.item_id))
102
+ if (item.moduleType !== 'crafter') throw new Error('lane module is not a crafter')
103
+ const stats = BigInt(installed.stats.toString())
104
+ const rea = decodeStat(stats, 0)
105
+ const fin = decodeStat(stats, 1)
106
+ const layout = getEntityLayout(entityItemId)?.slots ?? []
107
+ const amp = getSlotAmp(layout, idx)
108
+ const speed = applySlotMultiplier(computeCrafterSpeed(rea), amp)
109
+ const drain = computeCrafterDrain(fin)
110
+ return {slotIndex: idx, speed, drain, outputPct: amp}
111
+ }
112
+
113
+ // LANE_MOBILITY or a missing module soft-returns valid=false (never throws); callers check `valid`.
114
+ export function resolveLaneLoader(
115
+ modules: ModuleEntry[],
116
+ entityItemId: number,
117
+ laneKey: number
118
+ ): ResolvedLoaderLane {
119
+ if (laneKey === LANE_MOBILITY) {
120
+ return {slotIndex: -1, thrust: 0, mass: 0, outputPct: 0, valid: false}
121
+ }
122
+ const idx = laneKey - 1
123
+ const installed = idx >= 0 && idx < modules.length ? modules[idx].installed : undefined
124
+ if (!installed) {
125
+ return {slotIndex: idx, thrust: 0, mass: 0, outputPct: 0, valid: false}
126
+ }
127
+ const stats = BigInt(installed.stats.toString())
128
+ const ins = decodeStat(stats, 0)
129
+ const pla = decodeStat(stats, 1)
130
+ const layout = getEntityLayout(entityItemId)?.slots ?? []
131
+ const amp = getSlotAmp(layout, idx)
132
+ const thrust = applySlotMultiplier(computeLoaderThrust(pla), amp)
133
+ const mass = computeLoaderMass(ins)
134
+ return {slotIndex: idx, thrust, mass, outputPct: amp, valid: true}
135
+ }
136
+
19
137
  export function workerLaneKey(
20
138
  modules: ModuleEntry[],
21
139
  moduleSubtype: ModuleType,
22
- lanes: Lane[]
140
+ lanes: Lane[],
141
+ stratum?: number
23
142
  ): number {
143
+ if (moduleSubtype === 'gatherer' && stratum !== undefined) {
144
+ let lowestReaching: number | undefined
145
+ for (let i = 0; i < modules.length; i++) {
146
+ const installed = modules[i].installed
147
+ if (!installed) continue
148
+ const item = getItem(Number(installed.item_id.value ?? installed.item_id))
149
+ if (item.moduleType !== 'gatherer') continue
150
+ const stats = BigInt(installed.stats.toString())
151
+ const tol = decodeStat(stats, 1)
152
+ const depth = gathererDepthForTier(tol, item.tier ?? 1)
153
+ if (depth < stratum) continue
154
+ const laneKey = laneKeyForModule(i)
155
+ if (lowestReaching === undefined) lowestReaching = laneKey
156
+ if (laneIsFree(lanes, laneKey)) return laneKey
157
+ }
158
+ if (lowestReaching === undefined) throw new Error('no gatherer reaches this stratum')
159
+ return lowestReaching
160
+ }
161
+
24
162
  const occupiedMatchingLaneKeys: number[] = []
25
163
 
26
164
  for (let slotIndex = 0; slotIndex < modules.length; slotIndex++) {
@@ -2,7 +2,6 @@ import {Name, UInt16, UInt32, UInt64} from '@wharfkit/antelope'
2
2
  import {ServerContract} from '../contracts'
3
3
  import {Coordinates, TaskType} from '../types'
4
4
  import {
5
- capsHasLoaders,
6
5
  capsHasMovement,
7
6
  capsHasStorage,
8
7
  type EntityCapabilities,
@@ -18,7 +17,7 @@ import {
18
17
  } from '../errors'
19
18
  import {getEntityLayout, getRecipe, type RecipeInput} from '../data/recipes-runtime'
20
19
  import {computeEntityCapabilities} from '../derivation/capabilities'
21
- import {decodeCraftedItemStats} from '../derivation/crafting'
20
+ import {decodeCraftedItemStats, decodeStat} from '../derivation/crafting'
22
21
  import {packedModulesToInstalled, type InstalledModule} from '../entities/slot-multiplier'
23
22
  import {lerp} from '../travel/travel'
24
23
  import {
@@ -39,11 +38,13 @@ export interface ProjectedEntity {
39
38
  shipMass: UInt32
40
39
  capacity?: UInt64
41
40
  engines?: ServerContract.Types.movement_stats
42
- loaders?: ServerContract.Types.loader_stats
41
+ loaderLanes: ServerContract.Types.loader_lane[]
43
42
  generator?: ServerContract.Types.energy_stats
44
43
  hauler?: ServerContract.Types.hauler_stats
45
44
  readonly cargoMass: UInt64
46
45
  readonly totalMass: UInt64
46
+ readonly gathererLanes: ServerContract.Types.gatherer_lane[]
47
+ readonly crafterLanes: ServerContract.Types.crafter_lane[]
47
48
 
48
49
  hasMovement(): boolean
49
50
  hasStorage(): boolean
@@ -59,7 +60,9 @@ export interface Projectable extends ScheduleData {
59
60
  hullmass?: UInt32
60
61
  generator?: ServerContract.Types.energy_stats
61
62
  engines?: ServerContract.Types.movement_stats
62
- loaders?: ServerContract.Types.loader_stats
63
+ loader_lanes?: ServerContract.Types.loader_lane[]
64
+ gatherer_lanes?: ServerContract.Types.gatherer_lane[]
65
+ crafter_lanes?: ServerContract.Types.crafter_lane[]
63
66
  hauler?: ServerContract.Types.hauler_stats
64
67
  capacity?: UInt32
65
68
  cargo: ServerContract.Types.cargo_item[]
@@ -84,7 +87,9 @@ interface ProjectedCaps {
84
87
  capacity?: UInt32
85
88
  engines?: ServerContract.Types.movement_stats
86
89
  generator?: ServerContract.Types.energy_stats
87
- loaders?: ServerContract.Types.loader_stats
90
+ loaderLanes: ServerContract.Types.loader_lane[]
91
+ gathererLanes: ServerContract.Types.gatherer_lane[]
92
+ crafterLanes: ServerContract.Types.crafter_lane[]
88
93
  hauler?: ServerContract.Types.hauler_stats
89
94
  }
90
95
 
@@ -101,10 +106,53 @@ function recomputeCaps(entity: Projectable): ProjectedCaps | undefined {
101
106
  typeof entity.item_id === 'number' ? entity.item_id : entity.item_id.value
102
107
  )
103
108
  const hullStats = decodeCraftedItemStats(itemId, entity.stats)
109
+ if (hullStats.strength === undefined) hullStats.strength = decodeStat(entity.stats, 0)
110
+ if (hullStats.hardness === undefined) hullStats.hardness = decodeStat(entity.stats, 2)
104
111
  const layout = getEntityLayout(itemId)?.slots ?? []
105
112
  const installed = toInstalledModules(entity.modules)
106
113
  const caps = computeEntityCapabilities(hullStats, itemId, installed, layout)
107
114
 
115
+ const toLoaderLane = (l: {
116
+ slotIndex: number
117
+ mass: number
118
+ thrust: number
119
+ outputPct: number
120
+ }): ServerContract.Types.loader_lane =>
121
+ ServerContract.Types.loader_lane.from({
122
+ slot_index: l.slotIndex,
123
+ mass: l.mass,
124
+ thrust: l.thrust,
125
+ output_pct: l.outputPct,
126
+ })
127
+
128
+ const toGathererLane = (l: {
129
+ slotIndex: number
130
+ yield: number
131
+ drain: number
132
+ depth: number
133
+ outputPct: number
134
+ }): ServerContract.Types.gatherer_lane =>
135
+ ServerContract.Types.gatherer_lane.from({
136
+ slot_index: l.slotIndex,
137
+ yield: l.yield,
138
+ drain: l.drain,
139
+ depth: l.depth,
140
+ output_pct: l.outputPct,
141
+ })
142
+
143
+ const toCrafterLane = (l: {
144
+ slotIndex: number
145
+ speed: number
146
+ drain: number
147
+ outputPct: number
148
+ }): ServerContract.Types.crafter_lane =>
149
+ ServerContract.Types.crafter_lane.from({
150
+ slot_index: l.slotIndex,
151
+ speed: l.speed,
152
+ drain: l.drain,
153
+ output_pct: l.outputPct,
154
+ })
155
+
108
156
  return {
109
157
  hullmass: UInt32.from(caps.hullmass),
110
158
  capacity: UInt32.from(caps.capacity),
@@ -112,15 +160,23 @@ function recomputeCaps(entity: Projectable): ProjectedCaps | undefined {
112
160
  generator: caps.generator
113
161
  ? ServerContract.Types.energy_stats.from(caps.generator)
114
162
  : undefined,
115
- loaders: caps.loaders ? ServerContract.Types.loader_stats.from(caps.loaders) : undefined,
163
+ loaderLanes: (caps.loaderLanes ?? []).map(toLoaderLane),
164
+ gathererLanes: (caps.gathererLanes ?? []).map(toGathererLane),
165
+ crafterLanes: (caps.crafterLanes ?? []).map(toCrafterLane),
116
166
  hauler: caps.hauler ? ServerContract.Types.hauler_stats.from(caps.hauler) : undefined,
117
167
  }
118
168
  }
119
169
 
170
+ function loaderLanesTotalMass(lanes: ServerContract.Types.loader_lane[]): UInt64 {
171
+ let total = 0
172
+ for (const l of lanes) total += Number(l.mass)
173
+ return UInt64.from(total)
174
+ }
175
+
120
176
  export function createProjectedEntity(entity: Projectable): ProjectedEntity {
121
177
  const needsRecompute =
122
178
  entity.hullmass === undefined ||
123
- entity.loaders === undefined ||
179
+ entity.loader_lanes === undefined ||
124
180
  entity.engines === undefined ||
125
181
  entity.generator === undefined ||
126
182
  entity.hauler === undefined ||
@@ -128,7 +184,9 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
128
184
  const caps = needsRecompute ? recomputeCaps(entity) : undefined
129
185
 
130
186
  const shipMass = UInt32.from(entity.hullmass ?? caps?.hullmass ?? 0)
131
- const loaders = entity.loaders ?? caps?.loaders
187
+ const loaderLanes = entity.loader_lanes ?? caps?.loaderLanes ?? []
188
+ const gathererLanes = entity.gatherer_lanes ?? caps?.gathererLanes ?? []
189
+ const crafterLanes = entity.crafter_lanes ?? caps?.crafterLanes ?? []
132
190
  const engines = entity.engines ?? caps?.engines
133
191
  const generator = entity.generator ?? caps?.generator
134
192
  const hauler = entity.hauler ?? caps?.hauler
@@ -145,18 +203,18 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
145
203
  engines,
146
204
  generator,
147
205
  hauler,
148
- loaders,
206
+ loaderLanes,
207
+ gathererLanes,
208
+ crafterLanes,
149
209
 
150
210
  get cargoMass() {
151
211
  return calcStacksMass(this.cargo)
152
212
  },
153
213
 
154
214
  get totalMass() {
155
- let mass = UInt64.from(this.shipMass).adding(this.cargoMass)
156
- if (this.loaders) {
157
- mass = mass.adding(this.loaders.mass.multiplying(this.loaders.quantity))
158
- }
159
- return mass
215
+ return UInt64.from(this.shipMass)
216
+ .adding(this.cargoMass)
217
+ .adding(loaderLanesTotalMass(this.loaderLanes))
160
218
  },
161
219
 
162
220
  hasMovement() {
@@ -168,7 +226,7 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
168
226
  },
169
227
 
170
228
  hasLoaders() {
171
- return capsHasLoaders(this.capabilities())
229
+ return this.loaderLanes.length > 0
172
230
  },
173
231
 
174
232
  capabilities(): EntityCapabilities {
@@ -177,7 +235,6 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
177
235
  capacity: this.capacity ? UInt32.from(this.capacity) : undefined,
178
236
  engines: this.engines,
179
237
  generator: this.generator,
180
- loaders: this.loaders,
181
238
  }
182
239
  },
183
240
 
package/src/shipload.ts CHANGED
@@ -7,6 +7,7 @@ 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'
12
13
  import type {NftManager} from './managers/nft'
@@ -110,6 +111,10 @@ export class Shipload {
110
111
  return this._context.locations
111
112
  }
112
113
 
114
+ get coordinates(): CoordinatesManager {
115
+ return this._context.coordinates
116
+ }
117
+
113
118
  get epochs(): EpochsManager {
114
119
  return this._context.epochs
115
120
  }
@@ -12,9 +12,11 @@ export interface ContractProjectedState {
12
12
  hullmass?: UInt32
13
13
  capacity?: UInt32
14
14
  engines?: ServerContract.Types.movement_stats
15
- loaders?: ServerContract.Types.loader_stats
16
15
  generator?: ServerContract.Types.energy_stats
17
16
  hauler?: ServerContract.Types.hauler_stats
17
+ gatherer_lanes: ServerContract.Types.gatherer_lane[]
18
+ crafter_lanes: ServerContract.Types.crafter_lane[]
19
+ loader_lanes: ServerContract.Types.loader_lane[]
18
20
  }
19
21
 
20
22
  export interface ProjectionComparisonOptions {
@@ -55,10 +57,32 @@ export function assertProjectionEquals(
55
57
  record('capacity', toNum(contract.capacity), sdk.capacity ? Number(sdk.capacity) : undefined)
56
58
 
57
59
  recordStatBlock('engines', contract.engines, sdk.engines)
58
- recordStatBlock('loaders', contract.loaders, sdk.loaders)
59
60
  recordStatBlock('generator', contract.generator, sdk.generator)
60
61
  recordStatBlock('hauler', contract.hauler, sdk.hauler)
61
62
 
63
+ const normLane = (l: {slot_index?: unknown; [k: string]: unknown}) =>
64
+ Object.fromEntries(
65
+ Object.entries(l)
66
+ .filter(([k]) => k !== 'slot_index')
67
+ .map(([k, v]) => [k, toNum(v) ?? 0])
68
+ )
69
+
70
+ const compareLanes = (name: string, cLanes: unknown[], sLanes: unknown[]) => {
71
+ if (cLanes.length !== sLanes.length) {
72
+ mismatches.push(` ${name}.length: contract=${cLanes.length} sdk=${sLanes.length}`)
73
+ return
74
+ }
75
+ for (let i = 0; i < cLanes.length; i++) {
76
+ const cn = JSON.stringify(normLane(cLanes[i] as Record<string, unknown>))
77
+ const sn = JSON.stringify(normLane(sLanes[i] as Record<string, unknown>))
78
+ if (cn !== sn) mismatches.push(` ${name}[${i}]: contract=${cn} sdk=${sn}`)
79
+ }
80
+ }
81
+
82
+ compareLanes('gatherer_lanes', contract.gatherer_lanes as unknown[], sdk.gathererLanes ?? [])
83
+ compareLanes('crafter_lanes', contract.crafter_lanes as unknown[], sdk.crafterLanes ?? [])
84
+ compareLanes('loader_lanes', contract.loader_lanes as unknown[], sdk.loaderLanes ?? [])
85
+
62
86
  if (contract.cargo.length > 0 || sdk.cargo.length > 0) {
63
87
  const contractCargo = normaliseCargo(mergeContractCargo(contract.cargo))
64
88
  const sdkCargo = normaliseCargo(sdk.cargo)