@shipload/sdk 1.0.0-next.6 → 1.0.0-next.60

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 (155) 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 +3589 -1363
  7. package/lib/shipload.js +15700 -5454
  8. package/lib/shipload.js.map +1 -1
  9. package/lib/shipload.m.js +15423 -5402
  10. package/lib/shipload.m.js.map +1 -1
  11. package/lib/testing.d.ts +1173 -0
  12. package/lib/testing.js +4947 -0
  13. package/lib/testing.js.map +1 -0
  14. package/lib/testing.m.js +4941 -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 +35 -0
  20. package/src/capabilities/crafting.ts +34 -7
  21. package/src/capabilities/gathering.test.ts +29 -0
  22. package/src/capabilities/gathering.ts +37 -19
  23. package/src/capabilities/hauling.ts +0 -5
  24. package/src/capabilities/index.ts +0 -1
  25. package/src/capabilities/modules.ts +42 -0
  26. package/src/capabilities/movement.ts +1 -1
  27. package/src/capabilities/storage.ts +16 -1
  28. package/src/contracts/platform.ts +231 -3
  29. package/src/contracts/server.ts +1518 -501
  30. package/src/coordinates/address.ts +88 -0
  31. package/src/coordinates/constants.test.ts +15 -0
  32. package/src/coordinates/constants.ts +23 -0
  33. package/src/coordinates/index.ts +15 -0
  34. package/src/coordinates/memo.test.ts +47 -0
  35. package/src/coordinates/memo.ts +20 -0
  36. package/src/coordinates/permutation.ts +77 -0
  37. package/src/coordinates/regions.ts +48 -0
  38. package/src/coordinates/sectors.ts +115 -0
  39. package/src/data/capabilities.ts +49 -18
  40. package/src/data/capability-formulas.ts +26 -14
  41. package/src/data/catalog.ts +0 -5
  42. package/src/data/colors.ts +13 -47
  43. package/src/data/entities.json +379 -14
  44. package/src/data/item-ids.ts +48 -12
  45. package/src/data/items.json +301 -74
  46. package/src/data/kind-registry.json +195 -0
  47. package/src/data/kind-registry.ts +182 -0
  48. package/src/data/metadata.ts +277 -48
  49. package/src/data/recipes-runtime.ts +6 -23
  50. package/src/data/recipes.json +2074 -173
  51. package/src/derivation/build-methods.test.ts +13 -0
  52. package/src/derivation/build-methods.ts +48 -0
  53. package/src/derivation/capabilities.test.ts +348 -0
  54. package/src/derivation/capabilities.ts +692 -0
  55. package/src/derivation/capability-mappings.ts +60 -16
  56. package/src/derivation/crafting.test.ts +17 -0
  57. package/src/derivation/crafting.ts +56 -33
  58. package/src/derivation/index.ts +27 -2
  59. package/src/derivation/recipe-usage.test.ts +88 -0
  60. package/src/derivation/recipe-usage.ts +141 -0
  61. package/src/derivation/reserve-regen.ts +34 -0
  62. package/src/derivation/resources.ts +9 -1
  63. package/src/derivation/rollups.test.ts +55 -0
  64. package/src/derivation/rollups.ts +72 -0
  65. package/src/derivation/stars.test.ts +67 -0
  66. package/src/derivation/stars.ts +25 -0
  67. package/src/derivation/stat-scaling.ts +12 -0
  68. package/src/derivation/stats.ts +6 -6
  69. package/src/derivation/stratum.ts +26 -22
  70. package/src/derivation/tiers.ts +40 -7
  71. package/src/derivation/upgrades.ts +14 -0
  72. package/src/derivation/wormhole.ts +154 -0
  73. package/src/entities/entity.ts +102 -0
  74. package/src/entities/gamestate.ts +3 -28
  75. package/src/entities/makers.ts +141 -135
  76. package/src/entities/slot-multiplier.ts +43 -0
  77. package/src/errors.ts +12 -16
  78. package/src/format.ts +26 -4
  79. package/src/index-module.ts +333 -35
  80. package/src/managers/actions.ts +668 -103
  81. package/src/managers/base.ts +6 -2
  82. package/src/managers/cluster.test.ts +39 -0
  83. package/src/managers/cluster.ts +53 -0
  84. package/src/managers/construction-types.ts +80 -0
  85. package/src/managers/construction.ts +443 -0
  86. package/src/managers/context.ts +29 -1
  87. package/src/managers/coordinates.ts +14 -0
  88. package/src/managers/entities.ts +27 -66
  89. package/src/managers/epochs.ts +40 -0
  90. package/src/managers/index.ts +20 -1
  91. package/src/managers/locations.ts +25 -29
  92. package/src/managers/nft.test.ts +14 -0
  93. package/src/managers/nft.ts +70 -0
  94. package/src/managers/players.ts +25 -0
  95. package/src/managers/plot.ts +122 -0
  96. package/src/nft/atomicassets.abi.json +1342 -0
  97. package/src/nft/atomicassets.ts +237 -0
  98. package/src/nft/atomicdata.ts +130 -0
  99. package/src/nft/buildImmutableData.ts +385 -0
  100. package/src/nft/description.ts +225 -52
  101. package/src/nft/index.ts +3 -0
  102. package/src/planner/index.ts +242 -0
  103. package/src/planner/planner.test.ts +334 -0
  104. package/src/resolution/describe-module.ts +43 -25
  105. package/src/resolution/display-name.ts +38 -10
  106. package/src/resolution/resolve-item.test.ts +48 -0
  107. package/src/resolution/resolve-item.ts +112 -58
  108. package/src/scan/index.ts +244 -0
  109. package/src/scan/scan-wasm.base64.ts +2 -0
  110. package/src/scheduling/accessor.ts +65 -23
  111. package/src/scheduling/availability.test.ts +204 -0
  112. package/src/scheduling/availability.ts +211 -0
  113. package/src/scheduling/cancel.test.ts +542 -0
  114. package/src/scheduling/cancel.ts +254 -0
  115. package/src/scheduling/cluster-stock.test.ts +31 -0
  116. package/src/scheduling/cluster-stock.ts +15 -0
  117. package/src/scheduling/energy.ts +47 -0
  118. package/src/scheduling/idle-resolve.ts +45 -0
  119. package/src/scheduling/jobs.ts +71 -0
  120. package/src/scheduling/lane-core.ts +128 -0
  121. package/src/scheduling/lanes.test.ts +250 -0
  122. package/src/scheduling/lanes.ts +227 -0
  123. package/src/scheduling/projection.ts +257 -133
  124. package/src/scheduling/schedule.test.ts +119 -0
  125. package/src/scheduling/schedule.ts +257 -117
  126. package/src/scheduling/task-cargo.test.ts +44 -0
  127. package/src/scheduling/task-cargo.ts +46 -0
  128. package/src/scheduling/unwrap.test.ts +86 -0
  129. package/src/scheduling/unwrap.ts +190 -0
  130. package/src/shipload.ts +26 -1
  131. package/src/subscriptions/manager.cluster.test.ts +51 -0
  132. package/src/subscriptions/manager.ts +245 -172
  133. package/src/subscriptions/mappers.ts +5 -8
  134. package/src/subscriptions/types.ts +28 -3
  135. package/src/testing/catalog-hash.ts +19 -0
  136. package/src/testing/index.ts +2 -0
  137. package/src/testing/projection-parity.ts +167 -0
  138. package/src/travel/reach.ts +21 -0
  139. package/src/travel/route-planner.ts +288 -0
  140. package/src/travel/route-simulator.ts +170 -0
  141. package/src/travel/travel.ts +188 -122
  142. package/src/types/capabilities.ts +37 -8
  143. package/src/types/entity.ts +3 -3
  144. package/src/types/index.ts +0 -1
  145. package/src/types.ts +51 -16
  146. package/src/utils/cargo.test.ts +14 -0
  147. package/src/utils/cargo.ts +29 -0
  148. package/src/utils/display-name.ts +70 -0
  149. package/src/utils/system.ts +15 -16
  150. package/src/capabilities/loading.ts +0 -8
  151. package/src/entities/container.ts +0 -123
  152. package/src/entities/ship-deploy.ts +0 -295
  153. package/src/entities/ship.ts +0 -221
  154. package/src/entities/warehouse.ts +0 -127
  155. package/src/types/entity-traits.ts +0 -69
@@ -1,8 +1,7 @@
1
- import {Name, TimePoint, UInt16, UInt32, UInt64} from '@wharfkit/antelope'
1
+ import {Name, type UInt16, UInt32, UInt64} from '@wharfkit/antelope'
2
2
  import {ServerContract} from '../contracts'
3
- import {Coordinates, PRECISION, TaskType} from '../types'
3
+ import {Coordinates, TaskType} from '../types'
4
4
  import {
5
- capsHasLoaders,
6
5
  capsHasMovement,
7
6
  capsHasStorage,
8
7
  type EntityCapabilities,
@@ -14,38 +13,47 @@ import {
14
13
  RECIPE_INPUTS_INSUFFICIENT,
15
14
  RECIPE_INPUTS_INVALID,
16
15
  RECIPE_NOT_FOUND,
17
- SHIP_CARGO_NOT_LOADED,
16
+ ENTITY_CARGO_NOT_LOADED,
18
17
  } from '../errors'
19
- import {getRecipe, type RecipeInput} from '../data/recipes-runtime'
20
- import {getItem} from '../data/catalog'
21
- import {distanceBetweenCoordinates, lerp} from '../travel/travel'
18
+ import {getEntityLayout, getRecipe, type RecipeInput} from '../data/recipes-runtime'
19
+ import {ITEM_SHIP_T1_PACKED} from '../data/item-ids'
20
+ import {computeEntityCapabilities} from '../derivation/capabilities'
21
+ import {decodeCraftedItemStats, decodeStat} from '../derivation/crafting'
22
+ import {packedModulesToInstalled, type InstalledModule} from '../entities/slot-multiplier'
23
+ import {lerp} from '../travel/travel'
22
24
  import {
23
25
  calcStacksMass,
24
26
  cargoItemToStack,
25
27
  type CargoStack,
26
28
  mergeStacks,
27
- removeFromStacks,
29
+ subtractFromStacks,
28
30
  stackToCargoItem,
29
31
  } from '../capabilities/storage'
32
+ import {craftCargoOwnership} from './availability'
30
33
  import * as schedule from './schedule'
31
34
  import type {ScheduleData} from './schedule'
32
35
 
33
36
  export interface ProjectedEntity {
34
37
  location: Coordinates
35
- energy: UInt16
38
+ energy: UInt32
36
39
  cargo: CargoStack[]
37
40
  shipMass: UInt32
38
41
  capacity?: UInt64
39
42
  engines?: ServerContract.Types.movement_stats
40
- loaders?: ServerContract.Types.loader_stats
43
+ loaderLanes: ServerContract.Types.loader_lane[]
41
44
  generator?: ServerContract.Types.energy_stats
42
45
  hauler?: ServerContract.Types.hauler_stats
46
+ launcher?: ServerContract.Types.launcher_stats
43
47
  readonly cargoMass: UInt64
44
48
  readonly totalMass: UInt64
49
+ readonly gathererLanes: ServerContract.Types.gatherer_lane[]
50
+ readonly crafterLanes: ServerContract.Types.crafter_lane[]
51
+ readonly builderLanes: ServerContract.Types.builder_lane[]
45
52
 
46
53
  hasMovement(): boolean
47
54
  hasStorage(): boolean
48
55
  hasLoaders(): boolean
56
+ hasLauncher(): boolean
49
57
 
50
58
  capabilities(): EntityCapabilities
51
59
  state(): EntityState
@@ -53,53 +61,210 @@ export interface ProjectedEntity {
53
61
 
54
62
  export interface Projectable extends ScheduleData {
55
63
  coordinates: Coordinates | ServerContract.Types.coordinates
56
- energy?: UInt16
64
+ energy?: UInt32
57
65
  hullmass?: UInt32
58
66
  generator?: ServerContract.Types.energy_stats
59
67
  engines?: ServerContract.Types.movement_stats
60
- loaders?: ServerContract.Types.loader_stats
68
+ loader_lanes?: ServerContract.Types.loader_lane[]
69
+ gatherer_lanes?: ServerContract.Types.gatherer_lane[]
70
+ crafter_lanes?: ServerContract.Types.crafter_lane[]
71
+ builder_lanes?: ServerContract.Types.builder_lane[]
61
72
  hauler?: ServerContract.Types.hauler_stats
73
+ launcher?: ServerContract.Types.launcher_stats
62
74
  capacity?: UInt32
63
75
  cargo: ServerContract.Types.cargo_item[]
64
76
  cargomass: UInt32
65
77
  owner?: Name
78
+ stats?: bigint
79
+ item_id?: number | UInt16
80
+ modules?: ServerContract.Types.module_entry[] | InstalledModule[]
66
81
  }
67
82
 
68
- function getHullMass(entity: Projectable): UInt32 {
69
- return UInt32.from(entity.hullmass ?? 0)
83
+ function toInstalledModules(
84
+ modules: ServerContract.Types.module_entry[] | InstalledModule[]
85
+ ): InstalledModule[] {
86
+ if (modules.length > 0 && 'itemId' in modules[0]) {
87
+ return modules as InstalledModule[]
88
+ }
89
+ return packedModulesToInstalled(modules as ServerContract.Types.module_entry[])
90
+ }
91
+
92
+ interface ProjectedCaps {
93
+ hullmass?: UInt32
94
+ capacity?: UInt32
95
+ engines?: ServerContract.Types.movement_stats
96
+ generator?: ServerContract.Types.energy_stats
97
+ loaderLanes: ServerContract.Types.loader_lane[]
98
+ gathererLanes: ServerContract.Types.gatherer_lane[]
99
+ crafterLanes: ServerContract.Types.crafter_lane[]
100
+ builderLanes: ServerContract.Types.builder_lane[]
101
+ hauler?: ServerContract.Types.hauler_stats
102
+ launcher?: ServerContract.Types.launcher_stats
103
+ }
104
+
105
+ function recomputeCaps(entity: Projectable): ProjectedCaps | undefined {
106
+ if (
107
+ entity.item_id === undefined ||
108
+ entity.modules === undefined ||
109
+ entity.stats === undefined
110
+ ) {
111
+ return undefined
112
+ }
113
+
114
+ const itemId = Number(
115
+ typeof entity.item_id === 'number' ? entity.item_id : entity.item_id.value
116
+ )
117
+ const hullStats = decodeCraftedItemStats(itemId, entity.stats)
118
+ if (hullStats.strength === undefined) hullStats.strength = decodeStat(entity.stats, 0)
119
+ if (hullStats.hardness === undefined) hullStats.hardness = decodeStat(entity.stats, 2)
120
+ // The retired Ship T1 recipe only assigned names to its first two stat slots.
121
+ // Its on-chain entity row still carries five packed channels, and the ship hull
122
+ // formula deliberately consumes all five. Keep the unlabelled channels internal
123
+ // to projection so replay remains contract-parity until deployment replaces it.
124
+ if (itemId === ITEM_SHIP_T1_PACKED) {
125
+ hullStats.__legacyShipChannel3 = decodeStat(entity.stats, 3)
126
+ hullStats.__legacyShipChannel4 = decodeStat(entity.stats, 4)
127
+ }
128
+ const layout = getEntityLayout(itemId)?.slots ?? []
129
+ const installed = toInstalledModules(entity.modules)
130
+ const caps = computeEntityCapabilities(hullStats, itemId, installed, layout)
131
+
132
+ const toLoaderLane = (l: {
133
+ slotIndex: number
134
+ mass: number
135
+ thrust: number
136
+ outputPct: number
137
+ }): ServerContract.Types.loader_lane =>
138
+ ServerContract.Types.loader_lane.from({
139
+ slot_index: l.slotIndex,
140
+ mass: l.mass,
141
+ thrust: l.thrust,
142
+ output_pct: l.outputPct,
143
+ })
144
+
145
+ const toGathererLane = (l: {
146
+ slotIndex: number
147
+ yield: number
148
+ drain: number
149
+ depth: number
150
+ outputPct: number
151
+ }): ServerContract.Types.gatherer_lane =>
152
+ ServerContract.Types.gatherer_lane.from({
153
+ slot_index: l.slotIndex,
154
+ yield: l.yield,
155
+ drain: l.drain,
156
+ depth: l.depth,
157
+ output_pct: l.outputPct,
158
+ })
159
+
160
+ const toCrafterLane = (l: {
161
+ slotIndex: number
162
+ speed: number
163
+ drain: number
164
+ outputPct: number
165
+ }): ServerContract.Types.crafter_lane =>
166
+ ServerContract.Types.crafter_lane.from({
167
+ slot_index: l.slotIndex,
168
+ speed: l.speed,
169
+ drain: l.drain,
170
+ output_pct: l.outputPct,
171
+ })
172
+
173
+ const toBuilderLane = (l: {
174
+ slotIndex: number
175
+ speed: number
176
+ drain: number
177
+ outputPct: number
178
+ }): ServerContract.Types.builder_lane =>
179
+ ServerContract.Types.builder_lane.from({
180
+ slot_index: l.slotIndex,
181
+ speed: l.speed,
182
+ drain: l.drain,
183
+ output_pct: l.outputPct,
184
+ })
185
+
186
+ return {
187
+ hullmass: UInt32.from(caps.hullmass),
188
+ capacity: UInt32.from(caps.capacity),
189
+ engines: caps.engines ? ServerContract.Types.movement_stats.from(caps.engines) : undefined,
190
+ generator: caps.generator
191
+ ? ServerContract.Types.energy_stats.from(caps.generator)
192
+ : undefined,
193
+ loaderLanes: (caps.loaderLanes ?? []).map(toLoaderLane),
194
+ gathererLanes: (caps.gathererLanes ?? []).map(toGathererLane),
195
+ crafterLanes: (caps.crafterLanes ?? []).map(toCrafterLane),
196
+ builderLanes: (caps.builderLanes ?? []).map(toBuilderLane),
197
+ hauler: caps.hauler
198
+ ? ServerContract.Types.hauler_stats.from({
199
+ capacity: caps.hauler.capacity,
200
+ efficiency: caps.hauler.efficiency,
201
+ drain: caps.hauler.drain,
202
+ capacity_by_tier: caps.hauler.capacityByTier,
203
+ })
204
+ : undefined,
205
+ launcher: caps.launcher
206
+ ? ServerContract.Types.launcher_stats.from({
207
+ charge_rate: caps.launcher.chargeRate,
208
+ velocity: caps.launcher.velocity,
209
+ drain: caps.launcher.drain,
210
+ })
211
+ : undefined,
212
+ }
213
+ }
214
+
215
+ function loaderLanesTotalMass(lanes: ServerContract.Types.loader_lane[]): UInt64 {
216
+ let total = 0
217
+ for (const l of lanes) total += Number(l.mass)
218
+ return UInt64.from(total)
70
219
  }
71
220
 
72
221
  export function createProjectedEntity(entity: Projectable): ProjectedEntity {
73
- const shipMass = getHullMass(entity)
74
- const loaders = entity.loaders
75
- const engines = entity.engines
76
- const generator = entity.generator
77
- const hauler = entity.hauler
78
- const capacity = entity.capacity
222
+ const needsRecompute =
223
+ entity.hullmass === undefined ||
224
+ entity.loader_lanes === undefined ||
225
+ entity.engines === undefined ||
226
+ entity.generator === undefined ||
227
+ entity.hauler === undefined ||
228
+ entity.launcher === undefined ||
229
+ entity.capacity === undefined
230
+ const caps = needsRecompute ? recomputeCaps(entity) : undefined
231
+
232
+ const shipMass = UInt32.from(entity.hullmass ?? caps?.hullmass ?? 0)
233
+ const loaderLanes = entity.loader_lanes ?? caps?.loaderLanes ?? []
234
+ const gathererLanes = entity.gatherer_lanes ?? caps?.gathererLanes ?? []
235
+ const crafterLanes = entity.crafter_lanes ?? caps?.crafterLanes ?? []
236
+ const builderLanes = entity.builder_lanes ?? caps?.builderLanes ?? []
237
+ const engines = entity.engines ?? caps?.engines
238
+ const generator = entity.generator ?? caps?.generator
239
+ const hauler = entity.hauler ?? caps?.hauler
240
+ const launcher = entity.launcher ?? caps?.launcher
241
+ const capacity = entity.capacity ?? caps?.capacity
79
242
 
80
243
  const cargo: CargoStack[] = entity.cargo.map(cargoItemToStack)
81
244
 
82
245
  const projected: ProjectedEntity = {
83
246
  location: Coordinates.from(entity.coordinates),
84
- energy: UInt16.from(entity.energy ?? 0),
247
+ energy: UInt32.from(entity.energy ?? 0),
85
248
  cargo,
86
249
  shipMass,
87
250
  capacity: capacity ? UInt64.from(capacity) : undefined,
88
251
  engines,
89
252
  generator,
90
253
  hauler,
91
- loaders,
254
+ launcher,
255
+ loaderLanes,
256
+ gathererLanes,
257
+ crafterLanes,
258
+ builderLanes,
92
259
 
93
260
  get cargoMass() {
94
261
  return calcStacksMass(this.cargo)
95
262
  },
96
263
 
97
264
  get totalMass() {
98
- let mass = UInt64.from(this.shipMass).adding(this.cargoMass)
99
- if (this.loaders) {
100
- mass = mass.adding(this.loaders.mass.multiplying(this.loaders.quantity))
101
- }
102
- return mass
265
+ return UInt64.from(this.shipMass)
266
+ .adding(this.cargoMass)
267
+ .adding(loaderLanesTotalMass(this.loaderLanes))
103
268
  },
104
269
 
105
270
  hasMovement() {
@@ -111,7 +276,11 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
111
276
  },
112
277
 
113
278
  hasLoaders() {
114
- return capsHasLoaders(this.capabilities())
279
+ return this.loaderLanes.length > 0
280
+ },
281
+
282
+ hasLauncher() {
283
+ return this.launcher !== undefined
115
284
  },
116
285
 
117
286
  capabilities(): EntityCapabilities {
@@ -120,7 +289,7 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
120
289
  capacity: this.capacity ? UInt32.from(this.capacity) : undefined,
121
290
  engines: this.engines,
122
291
  generator: this.generator,
123
- loaders: this.loaders,
292
+ launcher: this.launcher,
124
293
  }
125
294
  },
126
295
 
@@ -146,12 +315,12 @@ function applyRechargeTask(
146
315
  if (!projected.generator) return
147
316
 
148
317
  if (options.complete) {
149
- projected.energy = UInt16.from(projected.generator.capacity)
318
+ projected.energy = UInt32.from(projected.generator.capacity)
150
319
  } else if (options.progress !== undefined) {
151
320
  const capacity = Number(projected.generator.capacity)
152
321
  const currentEnergy = Number(projected.energy)
153
322
  const rechargeAmount = (capacity - currentEnergy) * options.progress
154
- projected.energy = UInt16.from(Math.min(capacity, currentEnergy + rechargeAmount))
323
+ projected.energy = UInt32.from(Math.min(capacity, currentEnergy + rechargeAmount))
155
324
  }
156
325
  }
157
326
 
@@ -160,28 +329,25 @@ function applyFlightTask(
160
329
  task: ServerContract.Types.task,
161
330
  options: {complete: boolean; progress?: number}
162
331
  ): void {
163
- if (!task.coordinates || !projected.engines) return
332
+ if (!task.coordinates) return
164
333
 
165
- const origin = projected.location
166
334
  const destination = Coordinates.from(task.coordinates)
167
- const distance = distanceBetweenCoordinates(origin, task.coordinates)
168
- const energyUsage = distance.dividing(PRECISION).multiplying(projected.engines.drain)
169
335
 
170
336
  if (options.complete) {
171
- projected.energy = projected.energy.gt(energyUsage)
172
- ? UInt16.from(projected.energy.subtracting(energyUsage))
173
- : UInt16.from(0)
337
+ applyEnergyCost(projected, task)
174
338
  projected.location = destination
175
339
  } else if (options.progress !== undefined) {
176
- const interpolated = lerp(origin, destination, options.progress)
340
+ const interpolated = lerp(projected.location, destination, options.progress)
177
341
  projected.location = Coordinates.from({
178
342
  x: Math.round(interpolated.x),
179
343
  y: Math.round(interpolated.y),
180
344
  })
181
- const partialEnergy = UInt64.from(Math.floor(Number(energyUsage) * options.progress))
345
+ const partialEnergy = UInt64.from(
346
+ Math.floor(Number(task.energy_cost ?? 0) * options.progress)
347
+ )
182
348
  projected.energy = projected.energy.gt(partialEnergy)
183
- ? UInt16.from(projected.energy.subtracting(partialEnergy))
184
- : UInt16.from(0)
349
+ ? UInt32.from(projected.energy.subtracting(partialEnergy))
350
+ : UInt32.from(0)
185
351
  }
186
352
  }
187
353
 
@@ -190,7 +356,7 @@ function addCargoItem(projected: ProjectedEntity, item: ServerContract.Types.car
190
356
  }
191
357
 
192
358
  function removeCargoItem(projected: ProjectedEntity, item: ServerContract.Types.cargo_item): void {
193
- projected.cargo = removeFromStacks(projected.cargo, cargoItemToStack(item))
359
+ projected.cargo = subtractFromStacks(projected.cargo, cargoItemToStack(item))
194
360
  }
195
361
 
196
362
  function applyAddCargoTask(projected: ProjectedEntity, task: ServerContract.Types.task): void {
@@ -207,10 +373,10 @@ function applyRemoveCargoTask(projected: ProjectedEntity, task: ServerContract.T
207
373
 
208
374
  function applyEnergyCost(projected: ProjectedEntity, task: ServerContract.Types.task): void {
209
375
  if (!task.energy_cost) return
210
- const energyCost = UInt16.from(task.energy_cost)
376
+ const energyCost = UInt32.from(task.energy_cost)
211
377
  projected.energy = projected.energy.gt(energyCost)
212
- ? UInt16.from(projected.energy.subtracting(energyCost))
213
- : UInt16.from(0)
378
+ ? UInt32.from(projected.energy.subtracting(energyCost))
379
+ : UInt32.from(0)
214
380
  }
215
381
 
216
382
  function applyGatherTask(
@@ -220,7 +386,7 @@ function applyGatherTask(
220
386
  ): void {
221
387
  if (!options.complete) return
222
388
  applyEnergyCost(projected, task)
223
- if (!task.entitytarget) {
389
+ if (task.couplings.length === 0) {
224
390
  applyAddCargoTask(projected, task)
225
391
  }
226
392
  }
@@ -229,16 +395,14 @@ function applyCraftTask(projected: ProjectedEntity, task: ServerContract.Types.t
229
395
  applyEnergyCost(projected, task)
230
396
  if (task.cargo.length === 0) return
231
397
 
232
- for (let i = 0; i < task.cargo.length - 1; i++) {
233
- removeCargoItem(projected, task.cargo[i])
398
+ const {clustered, ownOutput} = craftCargoOwnership(task)
399
+ if (!clustered) {
400
+ for (let i = 0; i < task.cargo.length - 1; i++) {
401
+ removeCargoItem(projected, task.cargo[i])
402
+ }
234
403
  }
235
- addCargoItem(projected, task.cargo[task.cargo.length - 1])
236
- }
237
-
238
- function applyDeployTask(projected: ProjectedEntity, task: ServerContract.Types.task): void {
239
- applyEnergyCost(projected, task)
240
- if (task.cargo.length > 0) {
241
- removeCargoItem(projected, task.cargo[0])
404
+ if (ownOutput) {
405
+ addCargoItem(projected, task.cargo[task.cargo.length - 1])
242
406
  }
243
407
  }
244
408
 
@@ -248,6 +412,8 @@ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task):
248
412
  applyRechargeTask(projected, task, {complete: true})
249
413
  break
250
414
  case TaskType.TRAVEL:
415
+ case TaskType.WARP:
416
+ case TaskType.TRANSIT:
251
417
  applyFlightTask(projected, task, {complete: true})
252
418
  break
253
419
  case TaskType.LOAD:
@@ -255,7 +421,6 @@ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task):
255
421
  applyAddCargoTask(projected, task)
256
422
  break
257
423
  case TaskType.UNLOAD:
258
- case TaskType.WRAP:
259
424
  applyRemoveCargoTask(projected, task)
260
425
  break
261
426
  case TaskType.GATHER:
@@ -264,11 +429,7 @@ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task):
264
429
  case TaskType.CRAFT:
265
430
  applyCraftTask(projected, task)
266
431
  break
267
- case TaskType.DEPLOY:
268
- applyDeployTask(projected, task)
269
- break
270
432
  case TaskType.UNDEPLOY:
271
- case TaskType.WRAP_ENTITY:
272
433
  case TaskType.DEMOLISH:
273
434
  break
274
435
  }
@@ -280,50 +441,27 @@ export interface ProjectionOptions {
280
441
 
281
442
  export function projectEntity(entity: Projectable, options?: ProjectionOptions): ProjectedEntity {
282
443
  const projected = createProjectedEntity(entity)
283
- if (!entity.schedule || entity.schedule.tasks.length === 0) return projected
444
+ const ordered = schedule.orderedTasks(entity)
445
+ if (ordered.length === 0) return projected
284
446
 
285
- const tasks = entity.schedule.tasks
286
447
  const taskCount =
287
448
  options?.upToTaskIndex !== undefined
288
- ? Math.max(0, Math.min(options.upToTaskIndex, tasks.length))
289
- : tasks.length
449
+ ? Math.max(0, Math.min(options.upToTaskIndex, ordered.length))
450
+ : ordered.length
290
451
 
291
452
  for (let i = 0; i < taskCount; i++) {
292
- applyTask(projected, tasks[i])
453
+ applyTask(projected, ordered[i].task)
293
454
  }
294
455
  return projected
295
456
  }
296
457
 
297
- export interface ProjectableSnapshot extends Projectable {
298
- current_task?: ServerContract.Types.task
299
- pending_tasks?: ServerContract.Types.task[]
300
- }
301
-
302
- function buildRemainingProjectable(snapshot: ProjectableSnapshot): Projectable | null {
303
- if (!snapshot.schedule) return null
304
- const remainingTasks: ServerContract.Types.task[] = []
305
- if (snapshot.current_task) remainingTasks.push(snapshot.current_task)
306
- if (snapshot.pending_tasks?.length) remainingTasks.push(...snapshot.pending_tasks)
307
- if (remainingTasks.length === 0) return null
308
-
309
- const completedCount = snapshot.schedule.tasks.length - remainingTasks.length
310
- let startedMs = snapshot.schedule.started.toMilliseconds()
311
- for (let i = 0; i < completedCount; i++) {
312
- startedMs += snapshot.schedule.tasks[i].duration.toNumber() * 1000
313
- }
314
-
315
- return {
316
- ...snapshot,
317
- schedule: ServerContract.Types.schedule.from({
318
- started: TimePoint.fromMilliseconds(startedMs),
319
- tasks: remainingTasks,
320
- }),
458
+ export function projectRemainingAt(entity: Projectable, _now: Date): ProjectedEntity {
459
+ // Resolve is lazy/entity-global: completed tasks are unsettled until resolve, so replay all.
460
+ const projected = createProjectedEntity(entity)
461
+ for (const {task} of schedule.orderedTasks(entity)) {
462
+ applyTask(projected, task)
321
463
  }
322
- }
323
-
324
- export function projectFromCurrentState(snapshot: ProjectableSnapshot): ProjectedEntity {
325
- const projectable = buildRemainingProjectable(snapshot)
326
- return projectable ? projectEntity(projectable) : createProjectedEntity(snapshot)
464
+ return projected
327
465
  }
328
466
 
329
467
  function getRecipeInputsForOutput(outputItemId: number): RecipeInput[] | undefined {
@@ -346,19 +484,10 @@ function validateCraftTask(task: ServerContract.Types.task, projected: Projected
346
484
  let matched = false
347
485
  for (let ri = 0; ri < recipe.length; ri++) {
348
486
  const req = recipe[ri]
349
- if ('itemId' in req) {
350
- if (input.item_id.toNumber() === req.itemId) {
351
- groupedInputs[ri].push(input)
352
- matched = true
353
- break
354
- }
355
- } else {
356
- const item = getItem(input.item_id)
357
- if (item.category === req.category && item.tier === req.tier) {
358
- groupedInputs[ri].push(input)
359
- matched = true
360
- break
361
- }
487
+ if (input.item_id.toNumber() === req.itemId) {
488
+ groupedInputs[ri].push(input)
489
+ matched = true
490
+ break
362
491
  }
363
492
  }
364
493
  if (!matched) throw new Error(RECIPE_INPUTS_INVALID)
@@ -389,15 +518,16 @@ function validateCraftTask(task: ServerContract.Types.task, projected: Projected
389
518
  break
390
519
  }
391
520
  }
392
- if (!found) throw new Error(SHIP_CARGO_NOT_LOADED)
521
+ if (!found) throw new Error(ENTITY_CARGO_NOT_LOADED)
393
522
  }
394
523
  }
395
524
 
396
525
  export function validateSchedule(entity: Projectable): void {
397
- if (!entity.schedule || entity.schedule.tasks.length === 0) return
526
+ const ordered = schedule.orderedTasks(entity)
527
+ if (ordered.length === 0) return
398
528
 
399
529
  const projected = createProjectedEntity(entity)
400
- for (const task of entity.schedule.tasks) {
530
+ for (const {task} of ordered) {
401
531
  if (task.type.toNumber() === TaskType.CRAFT) {
402
532
  validateCraftTask(task, projected)
403
533
  }
@@ -411,28 +541,35 @@ export function validateSchedule(entity: Projectable): void {
411
541
  export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity {
412
542
  const projected = createProjectedEntity(entity)
413
543
 
414
- if (!entity.schedule || entity.schedule.tasks.length === 0) {
544
+ const ordered = schedule.orderedTasks(entity)
545
+ if (ordered.length === 0) {
415
546
  return projected
416
547
  }
417
548
 
418
- for (let i = 0; i < entity.schedule.tasks.length; i++) {
419
- const task = entity.schedule.tasks[i]
420
- const taskComplete = schedule.isTaskComplete(entity, i, now)
421
- const taskInProgress = schedule.isTaskInProgress(entity, i, now)
549
+ const nowMs = now.getTime()
550
+
551
+ for (const {task, startsAt} of ordered) {
552
+ const duration = task.duration.toNumber()
553
+ const elapsed = Math.min(
554
+ Math.max(0, Math.floor((nowMs - startsAt.getTime()) / 1000)),
555
+ duration
556
+ )
557
+ const taskComplete = elapsed >= duration
558
+ const taskInProgress = elapsed > 0 && elapsed < duration
422
559
 
423
560
  if (!taskComplete && !taskInProgress) {
424
- break
561
+ continue
425
562
  }
426
563
 
427
- const progress = taskInProgress
428
- ? schedule.getTaskElapsed(entity, i, now) / task.duration.toNumber()
429
- : undefined
564
+ const progress = taskInProgress ? elapsed / duration : undefined
430
565
 
431
566
  switch (task.type.toNumber()) {
432
567
  case TaskType.RECHARGE:
433
568
  applyRechargeTask(projected, task, {complete: taskComplete, progress})
434
569
  break
435
570
  case TaskType.TRAVEL:
571
+ case TaskType.WARP:
572
+ case TaskType.TRANSIT:
436
573
  applyFlightTask(projected, task, {complete: taskComplete, progress})
437
574
  break
438
575
  case TaskType.LOAD:
@@ -440,7 +577,6 @@ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity
440
577
  if (taskComplete) applyAddCargoTask(projected, task)
441
578
  break
442
579
  case TaskType.UNLOAD:
443
- case TaskType.WRAP:
444
580
  if (taskComplete) applyRemoveCargoTask(projected, task)
445
581
  break
446
582
  case TaskType.GATHER:
@@ -449,11 +585,7 @@ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity
449
585
  case TaskType.CRAFT:
450
586
  if (taskComplete) applyCraftTask(projected, task)
451
587
  break
452
- case TaskType.DEPLOY:
453
- if (taskComplete) applyDeployTask(projected, task)
454
- break
455
588
  case TaskType.UNDEPLOY:
456
- case TaskType.WRAP_ENTITY:
457
589
  case TaskType.DEMOLISH:
458
590
  break
459
591
  }
@@ -461,11 +593,3 @@ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity
461
593
 
462
594
  return projected
463
595
  }
464
-
465
- export function projectFromCurrentStateAt(
466
- snapshot: ProjectableSnapshot,
467
- now: Date
468
- ): ProjectedEntity {
469
- const projectable = buildRemainingProjectable(snapshot)
470
- return projectable ? projectEntityAt(projectable, now) : createProjectedEntity(snapshot)
471
- }