@shipload/sdk 1.0.0-next.5 → 1.0.0-next.51

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 (145) 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 +3167 -1353
  7. package/lib/shipload.js +13319 -5640
  8. package/lib/shipload.js.map +1 -1
  9. package/lib/shipload.m.js +13091 -5589
  10. package/lib/shipload.m.js.map +1 -1
  11. package/lib/testing.d.ts +1037 -0
  12. package/lib/testing.js +4363 -0
  13. package/lib/testing.js.map +1 -0
  14. package/lib/testing.m.js +4357 -0
  15. package/lib/testing.m.js.map +1 -0
  16. package/package.json +20 -2
  17. package/src/capabilities/craftable.ts +51 -0
  18. package/src/capabilities/crafting.test.ts +7 -0
  19. package/src/capabilities/crafting.ts +6 -7
  20. package/src/capabilities/gathering.test.ts +29 -0
  21. package/src/capabilities/gathering.ts +37 -19
  22. package/src/capabilities/index.ts +0 -1
  23. package/src/capabilities/modules.ts +22 -0
  24. package/src/capabilities/storage.ts +16 -1
  25. package/src/contracts/platform.ts +231 -3
  26. package/src/contracts/server.ts +1178 -511
  27. package/src/coordinates/address.ts +88 -0
  28. package/src/coordinates/constants.test.ts +15 -0
  29. package/src/coordinates/constants.ts +23 -0
  30. package/src/coordinates/index.ts +15 -0
  31. package/src/coordinates/memo.test.ts +47 -0
  32. package/src/coordinates/memo.ts +20 -0
  33. package/src/coordinates/permutation.ts +77 -0
  34. package/src/coordinates/regions.ts +48 -0
  35. package/src/coordinates/sectors.ts +115 -0
  36. package/src/data/capabilities.ts +31 -16
  37. package/src/data/capability-formulas.ts +21 -14
  38. package/src/data/catalog.ts +0 -5
  39. package/src/data/colors.ts +13 -47
  40. package/src/data/entities.json +154 -10
  41. package/src/data/item-ids.ts +31 -12
  42. package/src/data/items.json +192 -74
  43. package/src/data/kind-registry.json +131 -0
  44. package/src/data/kind-registry.ts +172 -0
  45. package/src/data/metadata.ts +166 -48
  46. package/src/data/recipes-runtime.ts +5 -23
  47. package/src/data/recipes.json +1003 -163
  48. package/src/derivation/build-methods.test.ts +13 -0
  49. package/src/derivation/build-methods.ts +48 -0
  50. package/src/derivation/capabilities.test.ts +151 -0
  51. package/src/derivation/capabilities.ts +527 -0
  52. package/src/derivation/capability-mappings.ts +58 -16
  53. package/src/derivation/crafting.ts +36 -33
  54. package/src/derivation/index.ts +28 -2
  55. package/src/derivation/recipe-usage.test.ts +81 -0
  56. package/src/derivation/recipe-usage.ts +141 -0
  57. package/src/derivation/reserve-regen.ts +34 -0
  58. package/src/derivation/resources.ts +98 -19
  59. package/src/derivation/rollups.test.ts +55 -0
  60. package/src/derivation/rollups.ts +56 -0
  61. package/src/derivation/stars.test.ts +67 -0
  62. package/src/derivation/stars.ts +25 -0
  63. package/src/derivation/stats.ts +6 -6
  64. package/src/derivation/stratum.ts +26 -22
  65. package/src/derivation/tiers.ts +40 -7
  66. package/src/derivation/upgrades.ts +14 -0
  67. package/src/derivation/wormhole.ts +141 -0
  68. package/src/entities/entity.ts +98 -0
  69. package/src/entities/gamestate.ts +3 -28
  70. package/src/entities/makers.ts +131 -134
  71. package/src/entities/slot-multiplier.ts +43 -0
  72. package/src/errors.ts +12 -16
  73. package/src/format.ts +26 -4
  74. package/src/index-module.ts +294 -34
  75. package/src/managers/actions.ts +616 -105
  76. package/src/managers/base.ts +6 -2
  77. package/src/managers/cluster.test.ts +39 -0
  78. package/src/managers/cluster.ts +53 -0
  79. package/src/managers/construction-types.ts +80 -0
  80. package/src/managers/construction.ts +412 -0
  81. package/src/managers/context.ts +29 -1
  82. package/src/managers/coordinates.ts +14 -0
  83. package/src/managers/entities.ts +27 -66
  84. package/src/managers/epochs.ts +40 -0
  85. package/src/managers/flatten-gather-plan.test.ts +80 -0
  86. package/src/managers/index.ts +20 -1
  87. package/src/managers/locations.ts +25 -29
  88. package/src/managers/nft.test.ts +14 -0
  89. package/src/managers/nft.ts +70 -0
  90. package/src/managers/players.ts +25 -0
  91. package/src/managers/plot.ts +122 -0
  92. package/src/nft/atomicassets.abi.json +1342 -0
  93. package/src/nft/atomicassets.ts +237 -0
  94. package/src/nft/atomicdata.ts +130 -0
  95. package/src/nft/buildImmutableData.ts +336 -0
  96. package/src/nft/description.ts +104 -36
  97. package/src/nft/index.ts +3 -0
  98. package/src/planner/index.ts +242 -0
  99. package/src/planner/planner.test.ts +325 -0
  100. package/src/resolution/describe-module.ts +43 -25
  101. package/src/resolution/display-name.ts +38 -10
  102. package/src/resolution/resolve-item.test.ts +48 -0
  103. package/src/resolution/resolve-item.ts +74 -47
  104. package/src/scan/index.ts +244 -0
  105. package/src/scan/scan-wasm.base64.ts +2 -0
  106. package/src/scheduling/accessor.ts +65 -23
  107. package/src/scheduling/availability.ts +108 -0
  108. package/src/scheduling/cancel.test.ts +348 -0
  109. package/src/scheduling/cancel.ts +209 -0
  110. package/src/scheduling/energy.ts +47 -0
  111. package/src/scheduling/idle-resolve.ts +45 -0
  112. package/src/scheduling/lane-core.ts +128 -0
  113. package/src/scheduling/lanes.test.ts +249 -0
  114. package/src/scheduling/lanes.ts +198 -0
  115. package/src/scheduling/projection.ts +210 -107
  116. package/src/scheduling/schedule.ts +240 -117
  117. package/src/scheduling/task-cargo.ts +46 -0
  118. package/src/scheduling/unwrap.test.ts +86 -0
  119. package/src/scheduling/unwrap.ts +190 -0
  120. package/src/shipload.ts +26 -1
  121. package/src/subscriptions/manager.cluster.test.ts +51 -0
  122. package/src/subscriptions/manager.ts +245 -172
  123. package/src/subscriptions/mappers.ts +5 -8
  124. package/src/subscriptions/types.ts +28 -3
  125. package/src/testing/catalog-hash.ts +19 -0
  126. package/src/testing/index.ts +2 -0
  127. package/src/testing/projection-parity.ts +167 -0
  128. package/src/travel/reach.ts +23 -0
  129. package/src/travel/route-planner.ts +277 -0
  130. package/src/travel/route-simulator.ts +174 -0
  131. package/src/travel/travel.ts +175 -118
  132. package/src/types/capabilities.ts +29 -6
  133. package/src/types/entity.ts +3 -3
  134. package/src/types/index.ts +0 -1
  135. package/src/types.ts +39 -14
  136. package/src/utils/cargo.test.ts +14 -0
  137. package/src/utils/cargo.ts +29 -0
  138. package/src/utils/display-name.ts +70 -0
  139. package/src/utils/system.ts +36 -24
  140. package/src/capabilities/loading.ts +0 -8
  141. package/src/entities/container.ts +0 -123
  142. package/src/entities/ship-deploy.ts +0 -295
  143. package/src/entities/ship.ts +0 -221
  144. package/src/entities/warehouse.ts +0 -127
  145. 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, 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,17 +13,19 @@ 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 {computeEntityCapabilities} from '../derivation/capabilities'
20
+ import {decodeCraftedItemStats, decodeStat} from '../derivation/crafting'
21
+ import {packedModulesToInstalled, type InstalledModule} from '../entities/slot-multiplier'
22
+ import {lerp} from '../travel/travel'
22
23
  import {
23
24
  calcStacksMass,
24
25
  cargoItemToStack,
25
26
  type CargoStack,
26
27
  mergeStacks,
27
- removeFromStacks,
28
+ subtractFromStacks,
28
29
  stackToCargoItem,
29
30
  } from '../capabilities/storage'
30
31
  import * as schedule from './schedule'
@@ -37,15 +38,19 @@ export interface ProjectedEntity {
37
38
  shipMass: UInt32
38
39
  capacity?: UInt64
39
40
  engines?: ServerContract.Types.movement_stats
40
- loaders?: ServerContract.Types.loader_stats
41
+ loaderLanes: ServerContract.Types.loader_lane[]
41
42
  generator?: ServerContract.Types.energy_stats
42
43
  hauler?: ServerContract.Types.hauler_stats
44
+ launcher?: ServerContract.Types.launcher_stats
43
45
  readonly cargoMass: UInt64
44
46
  readonly totalMass: UInt64
47
+ readonly gathererLanes: ServerContract.Types.gatherer_lane[]
48
+ readonly crafterLanes: ServerContract.Types.crafter_lane[]
45
49
 
46
50
  hasMovement(): boolean
47
51
  hasStorage(): boolean
48
52
  hasLoaders(): boolean
53
+ hasLauncher(): boolean
49
54
 
50
55
  capabilities(): EntityCapabilities
51
56
  state(): EntityState
@@ -57,25 +62,155 @@ export interface Projectable extends ScheduleData {
57
62
  hullmass?: UInt32
58
63
  generator?: ServerContract.Types.energy_stats
59
64
  engines?: ServerContract.Types.movement_stats
60
- loaders?: ServerContract.Types.loader_stats
65
+ loader_lanes?: ServerContract.Types.loader_lane[]
66
+ gatherer_lanes?: ServerContract.Types.gatherer_lane[]
67
+ crafter_lanes?: ServerContract.Types.crafter_lane[]
61
68
  hauler?: ServerContract.Types.hauler_stats
69
+ launcher?: ServerContract.Types.launcher_stats
62
70
  capacity?: UInt32
63
71
  cargo: ServerContract.Types.cargo_item[]
64
72
  cargomass: UInt32
65
73
  owner?: Name
74
+ stats?: bigint
75
+ item_id?: number | UInt16
76
+ modules?: ServerContract.Types.module_entry[] | InstalledModule[]
66
77
  }
67
78
 
68
- function getHullMass(entity: Projectable): UInt32 {
69
- return UInt32.from(entity.hullmass ?? 0)
79
+ function toInstalledModules(
80
+ modules: ServerContract.Types.module_entry[] | InstalledModule[]
81
+ ): InstalledModule[] {
82
+ if (modules.length > 0 && 'itemId' in modules[0]) {
83
+ return modules as InstalledModule[]
84
+ }
85
+ return packedModulesToInstalled(modules as ServerContract.Types.module_entry[])
86
+ }
87
+
88
+ interface ProjectedCaps {
89
+ hullmass?: UInt32
90
+ capacity?: UInt32
91
+ engines?: ServerContract.Types.movement_stats
92
+ generator?: ServerContract.Types.energy_stats
93
+ loaderLanes: ServerContract.Types.loader_lane[]
94
+ gathererLanes: ServerContract.Types.gatherer_lane[]
95
+ crafterLanes: ServerContract.Types.crafter_lane[]
96
+ hauler?: ServerContract.Types.hauler_stats
97
+ launcher?: ServerContract.Types.launcher_stats
98
+ }
99
+
100
+ function recomputeCaps(entity: Projectable): ProjectedCaps | undefined {
101
+ if (
102
+ entity.item_id === undefined ||
103
+ entity.modules === undefined ||
104
+ entity.stats === undefined
105
+ ) {
106
+ return undefined
107
+ }
108
+
109
+ const itemId = Number(
110
+ typeof entity.item_id === 'number' ? entity.item_id : entity.item_id.value
111
+ )
112
+ const hullStats = decodeCraftedItemStats(itemId, entity.stats)
113
+ if (hullStats.strength === undefined) hullStats.strength = decodeStat(entity.stats, 0)
114
+ if (hullStats.hardness === undefined) hullStats.hardness = decodeStat(entity.stats, 2)
115
+ const layout = getEntityLayout(itemId)?.slots ?? []
116
+ const installed = toInstalledModules(entity.modules)
117
+ const caps = computeEntityCapabilities(hullStats, itemId, installed, layout)
118
+
119
+ const toLoaderLane = (l: {
120
+ slotIndex: number
121
+ mass: number
122
+ thrust: number
123
+ outputPct: number
124
+ }): ServerContract.Types.loader_lane =>
125
+ ServerContract.Types.loader_lane.from({
126
+ slot_index: l.slotIndex,
127
+ mass: l.mass,
128
+ thrust: l.thrust,
129
+ output_pct: l.outputPct,
130
+ })
131
+
132
+ const toGathererLane = (l: {
133
+ slotIndex: number
134
+ yield: number
135
+ drain: number
136
+ depth: number
137
+ outputPct: number
138
+ }): ServerContract.Types.gatherer_lane =>
139
+ ServerContract.Types.gatherer_lane.from({
140
+ slot_index: l.slotIndex,
141
+ yield: l.yield,
142
+ drain: l.drain,
143
+ depth: l.depth,
144
+ output_pct: l.outputPct,
145
+ })
146
+
147
+ const toCrafterLane = (l: {
148
+ slotIndex: number
149
+ speed: number
150
+ drain: number
151
+ outputPct: number
152
+ }): ServerContract.Types.crafter_lane =>
153
+ ServerContract.Types.crafter_lane.from({
154
+ slot_index: l.slotIndex,
155
+ speed: l.speed,
156
+ drain: l.drain,
157
+ output_pct: l.outputPct,
158
+ })
159
+
160
+ return {
161
+ hullmass: UInt32.from(caps.hullmass),
162
+ capacity: UInt32.from(caps.capacity),
163
+ engines: caps.engines ? ServerContract.Types.movement_stats.from(caps.engines) : undefined,
164
+ generator: caps.generator
165
+ ? ServerContract.Types.energy_stats.from(caps.generator)
166
+ : undefined,
167
+ loaderLanes: (caps.loaderLanes ?? []).map(toLoaderLane),
168
+ gathererLanes: (caps.gathererLanes ?? []).map(toGathererLane),
169
+ crafterLanes: (caps.crafterLanes ?? []).map(toCrafterLane),
170
+ hauler: caps.hauler
171
+ ? ServerContract.Types.hauler_stats.from({
172
+ capacity: caps.hauler.capacity,
173
+ efficiency: caps.hauler.efficiency,
174
+ drain: caps.hauler.drain,
175
+ capacity_by_tier: caps.hauler.capacityByTier,
176
+ })
177
+ : undefined,
178
+ launcher: caps.launcher
179
+ ? ServerContract.Types.launcher_stats.from({
180
+ charge_rate: caps.launcher.chargeRate,
181
+ velocity: caps.launcher.velocity,
182
+ drain: caps.launcher.drain,
183
+ })
184
+ : undefined,
185
+ }
186
+ }
187
+
188
+ function loaderLanesTotalMass(lanes: ServerContract.Types.loader_lane[]): UInt64 {
189
+ let total = 0
190
+ for (const l of lanes) total += Number(l.mass)
191
+ return UInt64.from(total)
70
192
  }
71
193
 
72
194
  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
195
+ const needsRecompute =
196
+ entity.hullmass === undefined ||
197
+ entity.loader_lanes === undefined ||
198
+ entity.engines === undefined ||
199
+ entity.generator === undefined ||
200
+ entity.hauler === undefined ||
201
+ entity.launcher === undefined ||
202
+ entity.capacity === undefined
203
+ const caps = needsRecompute ? recomputeCaps(entity) : undefined
204
+
205
+ const shipMass = UInt32.from(entity.hullmass ?? caps?.hullmass ?? 0)
206
+ const loaderLanes = entity.loader_lanes ?? caps?.loaderLanes ?? []
207
+ const gathererLanes = entity.gatherer_lanes ?? caps?.gathererLanes ?? []
208
+ const crafterLanes = entity.crafter_lanes ?? caps?.crafterLanes ?? []
209
+ const engines = entity.engines ?? caps?.engines
210
+ const generator = entity.generator ?? caps?.generator
211
+ const hauler = entity.hauler ?? caps?.hauler
212
+ const launcher = entity.launcher ?? caps?.launcher
213
+ const capacity = entity.capacity ?? caps?.capacity
79
214
 
80
215
  const cargo: CargoStack[] = entity.cargo.map(cargoItemToStack)
81
216
 
@@ -88,18 +223,19 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
88
223
  engines,
89
224
  generator,
90
225
  hauler,
91
- loaders,
226
+ launcher,
227
+ loaderLanes,
228
+ gathererLanes,
229
+ crafterLanes,
92
230
 
93
231
  get cargoMass() {
94
232
  return calcStacksMass(this.cargo)
95
233
  },
96
234
 
97
235
  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
236
+ return UInt64.from(this.shipMass)
237
+ .adding(this.cargoMass)
238
+ .adding(loaderLanesTotalMass(this.loaderLanes))
103
239
  },
104
240
 
105
241
  hasMovement() {
@@ -111,7 +247,11 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
111
247
  },
112
248
 
113
249
  hasLoaders() {
114
- return capsHasLoaders(this.capabilities())
250
+ return this.loaderLanes.length > 0
251
+ },
252
+
253
+ hasLauncher() {
254
+ return this.launcher !== undefined
115
255
  },
116
256
 
117
257
  capabilities(): EntityCapabilities {
@@ -120,7 +260,7 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
120
260
  capacity: this.capacity ? UInt32.from(this.capacity) : undefined,
121
261
  engines: this.engines,
122
262
  generator: this.generator,
123
- loaders: this.loaders,
263
+ launcher: this.launcher,
124
264
  }
125
265
  },
126
266
 
@@ -160,25 +300,22 @@ function applyFlightTask(
160
300
  task: ServerContract.Types.task,
161
301
  options: {complete: boolean; progress?: number}
162
302
  ): void {
163
- if (!task.coordinates || !projected.engines) return
303
+ if (!task.coordinates) return
164
304
 
165
- const origin = projected.location
166
305
  const destination = Coordinates.from(task.coordinates)
167
- const distance = distanceBetweenCoordinates(origin, task.coordinates)
168
- const energyUsage = distance.dividing(PRECISION).multiplying(projected.engines.drain)
169
306
 
170
307
  if (options.complete) {
171
- projected.energy = projected.energy.gt(energyUsage)
172
- ? UInt16.from(projected.energy.subtracting(energyUsage))
173
- : UInt16.from(0)
308
+ applyEnergyCost(projected, task)
174
309
  projected.location = destination
175
310
  } else if (options.progress !== undefined) {
176
- const interpolated = lerp(origin, destination, options.progress)
311
+ const interpolated = lerp(projected.location, destination, options.progress)
177
312
  projected.location = Coordinates.from({
178
313
  x: Math.round(interpolated.x),
179
314
  y: Math.round(interpolated.y),
180
315
  })
181
- const partialEnergy = UInt64.from(Math.floor(Number(energyUsage) * options.progress))
316
+ const partialEnergy = UInt64.from(
317
+ Math.floor(Number(task.energy_cost ?? 0) * options.progress)
318
+ )
182
319
  projected.energy = projected.energy.gt(partialEnergy)
183
320
  ? UInt16.from(projected.energy.subtracting(partialEnergy))
184
321
  : UInt16.from(0)
@@ -190,7 +327,7 @@ function addCargoItem(projected: ProjectedEntity, item: ServerContract.Types.car
190
327
  }
191
328
 
192
329
  function removeCargoItem(projected: ProjectedEntity, item: ServerContract.Types.cargo_item): void {
193
- projected.cargo = removeFromStacks(projected.cargo, cargoItemToStack(item))
330
+ projected.cargo = subtractFromStacks(projected.cargo, cargoItemToStack(item))
194
331
  }
195
332
 
196
333
  function applyAddCargoTask(projected: ProjectedEntity, task: ServerContract.Types.task): void {
@@ -248,6 +385,8 @@ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task):
248
385
  applyRechargeTask(projected, task, {complete: true})
249
386
  break
250
387
  case TaskType.TRAVEL:
388
+ case TaskType.WARP:
389
+ case TaskType.TRANSIT:
251
390
  applyFlightTask(projected, task, {complete: true})
252
391
  break
253
392
  case TaskType.LOAD:
@@ -255,7 +394,6 @@ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task):
255
394
  applyAddCargoTask(projected, task)
256
395
  break
257
396
  case TaskType.UNLOAD:
258
- case TaskType.WRAP:
259
397
  applyRemoveCargoTask(projected, task)
260
398
  break
261
399
  case TaskType.GATHER:
@@ -268,7 +406,6 @@ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task):
268
406
  applyDeployTask(projected, task)
269
407
  break
270
408
  case TaskType.UNDEPLOY:
271
- case TaskType.WRAP_ENTITY:
272
409
  case TaskType.DEMOLISH:
273
410
  break
274
411
  }
@@ -280,50 +417,27 @@ export interface ProjectionOptions {
280
417
 
281
418
  export function projectEntity(entity: Projectable, options?: ProjectionOptions): ProjectedEntity {
282
419
  const projected = createProjectedEntity(entity)
283
- if (!entity.schedule || entity.schedule.tasks.length === 0) return projected
420
+ const ordered = schedule.orderedTasks(entity)
421
+ if (ordered.length === 0) return projected
284
422
 
285
- const tasks = entity.schedule.tasks
286
423
  const taskCount =
287
424
  options?.upToTaskIndex !== undefined
288
- ? Math.max(0, Math.min(options.upToTaskIndex, tasks.length))
289
- : tasks.length
425
+ ? Math.max(0, Math.min(options.upToTaskIndex, ordered.length))
426
+ : ordered.length
290
427
 
291
428
  for (let i = 0; i < taskCount; i++) {
292
- applyTask(projected, tasks[i])
429
+ applyTask(projected, ordered[i].task)
293
430
  }
294
431
  return projected
295
432
  }
296
433
 
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
- }),
434
+ export function projectRemainingAt(entity: Projectable, _now: Date): ProjectedEntity {
435
+ // Resolve is lazy/entity-global: completed tasks are unsettled until resolve, so replay all.
436
+ const projected = createProjectedEntity(entity)
437
+ for (const {task} of schedule.orderedTasks(entity)) {
438
+ applyTask(projected, task)
321
439
  }
322
- }
323
-
324
- export function projectFromCurrentState(snapshot: ProjectableSnapshot): ProjectedEntity {
325
- const projectable = buildRemainingProjectable(snapshot)
326
- return projectable ? projectEntity(projectable) : createProjectedEntity(snapshot)
440
+ return projected
327
441
  }
328
442
 
329
443
  function getRecipeInputsForOutput(outputItemId: number): RecipeInput[] | undefined {
@@ -346,19 +460,10 @@ function validateCraftTask(task: ServerContract.Types.task, projected: Projected
346
460
  let matched = false
347
461
  for (let ri = 0; ri < recipe.length; ri++) {
348
462
  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
- }
463
+ if (input.item_id.toNumber() === req.itemId) {
464
+ groupedInputs[ri].push(input)
465
+ matched = true
466
+ break
362
467
  }
363
468
  }
364
469
  if (!matched) throw new Error(RECIPE_INPUTS_INVALID)
@@ -389,15 +494,16 @@ function validateCraftTask(task: ServerContract.Types.task, projected: Projected
389
494
  break
390
495
  }
391
496
  }
392
- if (!found) throw new Error(SHIP_CARGO_NOT_LOADED)
497
+ if (!found) throw new Error(ENTITY_CARGO_NOT_LOADED)
393
498
  }
394
499
  }
395
500
 
396
501
  export function validateSchedule(entity: Projectable): void {
397
- if (!entity.schedule || entity.schedule.tasks.length === 0) return
502
+ const ordered = schedule.orderedTasks(entity)
503
+ if (ordered.length === 0) return
398
504
 
399
505
  const projected = createProjectedEntity(entity)
400
- for (const task of entity.schedule.tasks) {
506
+ for (const {task} of ordered) {
401
507
  if (task.type.toNumber() === TaskType.CRAFT) {
402
508
  validateCraftTask(task, projected)
403
509
  }
@@ -411,28 +517,35 @@ export function validateSchedule(entity: Projectable): void {
411
517
  export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity {
412
518
  const projected = createProjectedEntity(entity)
413
519
 
414
- if (!entity.schedule || entity.schedule.tasks.length === 0) {
520
+ const ordered = schedule.orderedTasks(entity)
521
+ if (ordered.length === 0) {
415
522
  return projected
416
523
  }
417
524
 
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)
525
+ const nowMs = now.getTime()
526
+
527
+ for (const {task, startsAt} of ordered) {
528
+ const duration = task.duration.toNumber()
529
+ const elapsed = Math.min(
530
+ Math.max(0, Math.floor((nowMs - startsAt.getTime()) / 1000)),
531
+ duration
532
+ )
533
+ const taskComplete = elapsed >= duration
534
+ const taskInProgress = elapsed > 0 && elapsed < duration
422
535
 
423
536
  if (!taskComplete && !taskInProgress) {
424
- break
537
+ continue
425
538
  }
426
539
 
427
- const progress = taskInProgress
428
- ? schedule.getTaskElapsed(entity, i, now) / task.duration.toNumber()
429
- : undefined
540
+ const progress = taskInProgress ? elapsed / duration : undefined
430
541
 
431
542
  switch (task.type.toNumber()) {
432
543
  case TaskType.RECHARGE:
433
544
  applyRechargeTask(projected, task, {complete: taskComplete, progress})
434
545
  break
435
546
  case TaskType.TRAVEL:
547
+ case TaskType.WARP:
548
+ case TaskType.TRANSIT:
436
549
  applyFlightTask(projected, task, {complete: taskComplete, progress})
437
550
  break
438
551
  case TaskType.LOAD:
@@ -440,7 +553,6 @@ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity
440
553
  if (taskComplete) applyAddCargoTask(projected, task)
441
554
  break
442
555
  case TaskType.UNLOAD:
443
- case TaskType.WRAP:
444
556
  if (taskComplete) applyRemoveCargoTask(projected, task)
445
557
  break
446
558
  case TaskType.GATHER:
@@ -453,7 +565,6 @@ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity
453
565
  if (taskComplete) applyDeployTask(projected, task)
454
566
  break
455
567
  case TaskType.UNDEPLOY:
456
- case TaskType.WRAP_ENTITY:
457
568
  case TaskType.DEMOLISH:
458
569
  break
459
570
  }
@@ -461,11 +572,3 @@ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity
461
572
 
462
573
  return projected
463
574
  }
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
- }