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

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 (60) hide show
  1. package/lib/shipload.d.ts +471 -158
  2. package/lib/shipload.js +2320 -485
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +2290 -484
  5. package/lib/shipload.m.js.map +1 -1
  6. package/lib/testing.d.ts +123 -8
  7. package/lib/testing.js +450 -19
  8. package/lib/testing.js.map +1 -1
  9. package/lib/testing.m.js +451 -20
  10. package/lib/testing.m.js.map +1 -1
  11. package/package.json +1 -1
  12. package/src/capabilities/crafting.ts +10 -1
  13. package/src/capabilities/gathering.ts +1 -1
  14. package/src/capabilities/hauling.ts +0 -5
  15. package/src/capabilities/modules.ts +6 -0
  16. package/src/capabilities/movement.ts +1 -1
  17. package/src/contracts/server.ts +293 -13
  18. package/src/data/capabilities.ts +18 -2
  19. package/src/data/capability-formulas.ts +5 -0
  20. package/src/data/entities.json +235 -20
  21. package/src/data/item-ids.ts +10 -0
  22. package/src/data/items.json +61 -0
  23. package/src/data/kind-registry.json +53 -1
  24. package/src/data/kind-registry.ts +5 -0
  25. package/src/data/metadata.ts +74 -5
  26. package/src/data/recipes-runtime.ts +1 -0
  27. package/src/data/recipes.json +895 -119
  28. package/src/derivation/capabilities.test.ts +105 -4
  29. package/src/derivation/capabilities.ts +162 -57
  30. package/src/derivation/capability-mappings.ts +2 -0
  31. package/src/derivation/crafting.ts +2 -0
  32. package/src/derivation/recipe-usage.test.ts +10 -7
  33. package/src/derivation/rollups.ts +16 -0
  34. package/src/derivation/stat-scaling.ts +12 -0
  35. package/src/entities/makers.ts +10 -1
  36. package/src/index-module.ts +29 -3
  37. package/src/managers/actions.ts +77 -46
  38. package/src/managers/construction-types.ts +2 -2
  39. package/src/managers/construction.ts +15 -15
  40. package/src/managers/plot.ts +1 -1
  41. package/src/nft/buildImmutableData.ts +55 -9
  42. package/src/nft/description.ts +99 -36
  43. package/src/planner/planner.test.ts +50 -41
  44. package/src/resolution/resolve-item.test.ts +1 -1
  45. package/src/resolution/resolve-item.ts +26 -11
  46. package/src/scheduling/availability.ts +7 -6
  47. package/src/scheduling/cancel.test.ts +40 -6
  48. package/src/scheduling/cancel.ts +21 -29
  49. package/src/scheduling/jobs.ts +71 -0
  50. package/src/scheduling/lanes.ts +29 -0
  51. package/src/scheduling/projection.ts +42 -25
  52. package/src/scheduling/task-cargo.ts +1 -1
  53. package/src/testing/projection-parity.ts +2 -2
  54. package/src/travel/reach.ts +4 -6
  55. package/src/travel/route-planner.ts +60 -49
  56. package/src/travel/route-simulator.ts +3 -7
  57. package/src/travel/travel.ts +14 -5
  58. package/src/types/capabilities.ts +9 -3
  59. package/src/types.ts +5 -3
  60. package/src/managers/flatten-gather-plan.test.ts +0 -80
@@ -10,6 +10,7 @@ function task(over: Partial<{type: number; duration: number; cancelable: number;
10
10
  duration: over.duration ?? 100,
11
11
  cancelable: over.cancelable ?? TaskCancelable.BEFORE_START,
12
12
  cargo: [],
13
+ couplings: [],
13
14
  ...(over.group ? {entitygroup: over.group} : {}),
14
15
  })
15
16
  }
@@ -28,6 +29,7 @@ function entity(tasks: ReturnType<typeof task>[], startedISO = T0) {
28
29
  lanes: [{lane_key: 0, schedule: {started: startedISO, tasks}}],
29
30
  gatherer_lanes: [],
30
31
  crafter_lanes: [],
32
+ builder_lanes: [],
31
33
  loader_lanes: [],
32
34
  holds: [],
33
35
  })
@@ -101,14 +103,20 @@ describe('cancelEligibility — linked tasks', () => {
101
103
  })
102
104
 
103
105
  const HOLD_PULL = 1
106
+ const HOLD_BUILD = 4
104
107
  function loadTask(giverType: string, giverId: number, holdId: number, qty: number) {
105
108
  return ServerContract.Types.task.from({
106
109
  type: TaskType.LOAD,
107
110
  duration: 100,
108
111
  cancelable: TaskCancelable.ALWAYS,
109
112
  cargo: [{item_id: 7, stats: 0, modules: [], quantity: qty}],
110
- entitytarget: {entity_type: giverType, entity_id: giverId},
111
- hold: holdId,
113
+ couplings: [
114
+ {
115
+ counterpart: {entity_type: giverType, entity_id: giverId},
116
+ hold: holdId,
117
+ kind: HOLD_PULL,
118
+ },
119
+ ],
112
120
  })
113
121
  }
114
122
 
@@ -129,7 +137,13 @@ describe('cancelEligibility — effects', () => {
129
137
  duration: 100,
130
138
  cancelable: TaskCancelable.ALWAYS,
131
139
  cargo: [],
132
- entitytarget: {entity_type: 'plot', entity_id: 55},
140
+ couplings: [
141
+ {
142
+ counterpart: {entity_type: 'plot', entity_id: 55},
143
+ hold: 0,
144
+ kind: HOLD_BUILD,
145
+ },
146
+ ],
133
147
  }),
134
148
  ])
135
149
  const plan = cancelEligibility(e, 0, 0, {now})
@@ -152,6 +166,7 @@ describe('cancelEligibility — effects', () => {
152
166
  lanes: [{lane_key: 0, schedule: {started: T0, tasks: [lt]}}],
153
167
  gatherer_lanes: [],
154
168
  crafter_lanes: [],
169
+ builder_lanes: [],
155
170
  loader_lanes: [],
156
171
  holds: [
157
172
  {
@@ -184,6 +199,7 @@ describe('cancelEligibility — effects', () => {
184
199
  lanes: [{lane_key: 0, schedule: {started: T0, tasks: [lt]}}],
185
200
  gatherer_lanes: [],
186
201
  crafter_lanes: [],
202
+ builder_lanes: [],
187
203
  loader_lanes: [],
188
204
  holds: [
189
205
  {
@@ -200,8 +216,7 @@ describe('cancelEligibility — effects', () => {
200
216
  expect(plan.effects.releasedHolds[0]?.counterpart.entity_id.toNumber()).toBe(6)
201
217
  })
202
218
 
203
- test('unresolvable hold emits no releasedHolds entry', () => {
204
- const lt = loadTask('warehouse', 6, 99, 4)
219
+ test('uncoupled task emits no releasedHolds entry', () => {
205
220
  const upcoming = new Date('2026-06-18T23:59:50.000Z')
206
221
  const e = ServerContract.Types.entity_info.from({
207
222
  type: 'ship',
@@ -213,14 +228,24 @@ describe('cancelEligibility — effects', () => {
213
228
  cargomass: 0,
214
229
  cargo: [],
215
230
  modules: [],
216
- lanes: [{lane_key: 0, schedule: {started: T0, tasks: [lt]}}],
231
+ lanes: [
232
+ {
233
+ lane_key: 0,
234
+ schedule: {
235
+ started: T0,
236
+ tasks: [task({cancelable: TaskCancelable.ALWAYS, type: TaskType.LOAD})],
237
+ },
238
+ },
239
+ ],
217
240
  gatherer_lanes: [],
218
241
  crafter_lanes: [],
242
+ builder_lanes: [],
219
243
  loader_lanes: [],
220
244
  holds: [],
221
245
  })
222
246
  const plan = cancelEligibility(e, 0, 0, {now: upcoming})
223
247
  expect(plan.effects.releasedHolds).toHaveLength(0)
248
+ expect(plan.effects.refunds).toHaveLength(0)
224
249
  })
225
250
  })
226
251
 
@@ -233,6 +258,7 @@ describe('cancelEligibility — feasibility', () => {
233
258
  duration: 50,
234
259
  cancelable: TaskCancelable.ALWAYS,
235
260
  cargo: [{item_id: 7, stats: 0, modules: [], quantity: 2}],
261
+ couplings: [],
236
262
  })
237
263
  const consumer = ServerContract.Types.task.from({
238
264
  type: TaskType.CRAFT,
@@ -242,6 +268,7 @@ describe('cancelEligibility — feasibility', () => {
242
268
  {item_id: 7, stats: 0, modules: [], quantity: 2},
243
269
  {item_id: 9, stats: 0, modules: [], quantity: 1},
244
270
  ],
271
+ couplings: [],
245
272
  })
246
273
  const e = ServerContract.Types.entity_info.from({
247
274
  type: 'ship',
@@ -265,6 +292,7 @@ describe('cancelEligibility — feasibility', () => {
265
292
  ],
266
293
  gatherer_lanes: [],
267
294
  crafter_lanes: [],
295
+ builder_lanes: [],
268
296
  loader_lanes: [],
269
297
  holds: [],
270
298
  })
@@ -279,12 +307,14 @@ describe('cancelEligibility — feasibility', () => {
279
307
  duration: 50,
280
308
  cancelable: TaskCancelable.ALWAYS,
281
309
  cargo: [{item_id: 7, stats: 0, modules: [], quantity: 2}],
310
+ couplings: [],
282
311
  })
283
312
  const independent = ServerContract.Types.task.from({
284
313
  type: TaskType.TRAVEL,
285
314
  duration: 50,
286
315
  cancelable: TaskCancelable.ALWAYS,
287
316
  cargo: [],
317
+ couplings: [],
288
318
  })
289
319
  const e = ServerContract.Types.entity_info.from({
290
320
  type: 'ship',
@@ -302,6 +332,7 @@ describe('cancelEligibility — feasibility', () => {
302
332
  ],
303
333
  gatherer_lanes: [],
304
334
  crafter_lanes: [],
335
+ builder_lanes: [],
305
336
  loader_lanes: [],
306
337
  holds: [],
307
338
  })
@@ -315,12 +346,14 @@ describe('cancelEligibility — feasibility', () => {
315
346
  duration: 50,
316
347
  cancelable: TaskCancelable.ALWAYS,
317
348
  cargo: [moduledCargo],
349
+ couplings: [],
318
350
  })
319
351
  const consumer = ServerContract.Types.task.from({
320
352
  type: TaskType.CRAFT,
321
353
  duration: 50,
322
354
  cancelable: TaskCancelable.ALWAYS,
323
355
  cargo: [moduledCargo, {item_id: 9, stats: 0, modules: [], quantity: 1}],
356
+ couplings: [],
324
357
  })
325
358
  const e = ServerContract.Types.entity_info.from({
326
359
  type: 'ship',
@@ -338,6 +371,7 @@ describe('cancelEligibility — feasibility', () => {
338
371
  ],
339
372
  gatherer_lanes: [],
340
373
  crafter_lanes: [],
374
+ builder_lanes: [],
341
375
  loader_lanes: [],
342
376
  holds: [],
343
377
  })
@@ -1,5 +1,5 @@
1
1
  import type {ServerContract} from '../contracts'
2
- import {TaskCancelable, TaskType} from '../types'
2
+ import {HoldKind, TaskCancelable, TaskType} from '../types'
3
3
  import {calcCargoItemMass} from '../capabilities/storage'
4
4
  import {taskCargoEffect, cargoKey} from './availability'
5
5
  import * as schedule from './schedule'
@@ -169,34 +169,26 @@ export function cancelEligibility(
169
169
  if (timing[i].running && t.cancelable.equals(TaskCancelable.ALWAYS))
170
170
  effects.abandonsRunning = true
171
171
  if (t.energy_cost) energyForfeited += t.energy_cost.toNumber()
172
- if (t.type.equals(TaskType.BUILDPLOT) && t.entitytarget)
173
- effects.keepsPlotDeposits = {plot: t.entitytarget}
174
- if (t.hold && t.entitytarget) {
175
- const counterpart = input.counterparts?.get(t.entitytarget.entity_id.toString())
176
- const hold =
177
- (counterpart?.holds ?? []).find((h) => h.id.equals(t.hold!)) ??
178
- (entity.holds ?? []).find((h) => h.id.equals(t.hold!))
179
- if (hold) {
180
- const kind = hold.kind.toNumber()
181
- effects.releasedHolds.push({counterpart: t.entitytarget, kind})
182
- if (kind === 1 /* HOLD_PULL */) {
183
- effects.refunds.push({giver: t.entitytarget, cargo: t.cargo})
184
- const giver = counterpart
185
- if (giver) {
186
- const returned = t.cargo.reduce(
187
- (s, c) => s + calcCargoItemMass(c).toNumber(),
188
- 0
189
- )
190
- const cap = giver.capacity
191
- ? giver.capacity.toNumber()
192
- : Number.MAX_SAFE_INTEGER
193
- if (giver.cargomass.toNumber() + returned > cap) {
194
- return {
195
- ok: false,
196
- blockedReason: CancelBlockReason.WOULD_OVERFILL,
197
- range,
198
- effects: {...EMPTY_EFFECTS},
199
- }
172
+ if (t.type.equals(TaskType.BUILDPLOT) && t.couplings.length > 0)
173
+ effects.keepsPlotDeposits = {plot: t.couplings[0].counterpart}
174
+ for (const c of t.couplings) {
175
+ const kind = c.kind.toNumber()
176
+ effects.releasedHolds.push({counterpart: c.counterpart, kind})
177
+ if (kind === HoldKind.PULL) {
178
+ effects.refunds.push({giver: c.counterpart, cargo: t.cargo})
179
+ const giver = input.counterparts?.get(c.counterpart.entity_id.toString())
180
+ if (giver) {
181
+ const returned = t.cargo.reduce(
182
+ (s, c) => s + calcCargoItemMass(c).toNumber(),
183
+ 0
184
+ )
185
+ const cap = giver.capacity ? giver.capacity.toNumber() : Number.MAX_SAFE_INTEGER
186
+ if (giver.cargomass.toNumber() + returned > cap) {
187
+ return {
188
+ ok: false,
189
+ blockedReason: CancelBlockReason.WOULD_OVERFILL,
190
+ range,
191
+ effects: {...EMPTY_EFFECTS},
200
192
  }
201
193
  }
202
194
  }
@@ -0,0 +1,71 @@
1
+ export interface JobWindow {
2
+ id: number
3
+ socket: number
4
+ owner: string
5
+ startsAt: Date
6
+ completesAt: Date
7
+ recipeId: number
8
+ quantity: number
9
+ }
10
+
11
+ export interface JobLaneEntry {
12
+ kind: 'idle' | 'job'
13
+ startsAt: Date
14
+ completesAt: Date
15
+ job?: JobWindow
16
+ }
17
+
18
+ export interface JobLane {
19
+ socket: number
20
+ entries: JobLaneEntry[]
21
+ }
22
+
23
+ export const JOB_QUEUE_CAP = 25
24
+
25
+ function activeOn(jobs: JobWindow[], socket: number, now: Date): JobWindow[] {
26
+ return jobs
27
+ .filter((j) => j.socket === socket && j.completesAt > now)
28
+ .sort((a, b) => a.startsAt.getTime() - b.startsAt.getTime())
29
+ }
30
+
31
+ export function jobsToLanes(jobs: JobWindow[], socketCount: number, now: Date): JobLane[] {
32
+ const lanes: JobLane[] = []
33
+ for (let socket = 0; socket < socketCount; socket++) {
34
+ const entries: JobLaneEntry[] = []
35
+ let cursor: Date | null = null
36
+ for (const j of activeOn(jobs, socket, now)) {
37
+ if (cursor && j.startsAt > cursor) {
38
+ entries.push({kind: 'idle', startsAt: cursor, completesAt: j.startsAt})
39
+ }
40
+ entries.push({kind: 'job', startsAt: j.startsAt, completesAt: j.completesAt, job: j})
41
+ cursor = j.completesAt
42
+ }
43
+ lanes.push({socket, entries})
44
+ }
45
+ return lanes
46
+ }
47
+
48
+ export function socketTail(jobs: JobWindow[], socket: number, now: Date): Date {
49
+ const active = activeOn(jobs, socket, now)
50
+ const last = active[active.length - 1]
51
+ return last && last.completesAt > now ? last.completesAt : now
52
+ }
53
+
54
+ export function pickFabricator(
55
+ jobs: JobWindow[],
56
+ sockets: Array<{open: boolean}>,
57
+ durationBySocketMinutes: number[],
58
+ now: Date
59
+ ): {slot: number; startsAt: Date; completesAt: Date} | null {
60
+ let best: {slot: number; startsAt: Date; completesAt: Date} | null = null
61
+ for (let slot = 0; slot < sockets.length; slot++) {
62
+ if (!sockets[slot].open) continue
63
+ if (activeOn(jobs, slot, now).length >= JOB_QUEUE_CAP) continue
64
+ const startsAt = socketTail(jobs, slot, now)
65
+ const completesAt = new Date(startsAt.getTime() + durationBySocketMinutes[slot] * 60_000)
66
+ if (!best || completesAt < best.completesAt) {
67
+ best = {slot, startsAt, completesAt}
68
+ }
69
+ }
70
+ return best
71
+ }
@@ -11,6 +11,8 @@ import {
11
11
  computeLoaderMass,
12
12
  computeCrafterSpeed,
13
13
  computeCrafterDrain,
14
+ computeBuilderSpeed,
15
+ computeBuilderDrain,
14
16
  } from '../nft/description'
15
17
  import type {ModuleType} from '../types'
16
18
  import {getLane, LANE_MOBILITY, type ScheduleData} from './schedule'
@@ -27,6 +29,13 @@ export interface ResolvedGathererLane {
27
29
  outputPct: number
28
30
  }
29
31
 
32
+ export interface ResolvedBuilderLane {
33
+ slotIndex: number
34
+ speed: number
35
+ drain: number
36
+ outputPct: number
37
+ }
38
+
30
39
  export interface ResolvedCrafterLane {
31
40
  slotIndex: number
32
41
  speed: number
@@ -110,6 +119,26 @@ export function resolveLaneCrafter(
110
119
  return {slotIndex: idx, speed, drain, outputPct: amp}
111
120
  }
112
121
 
122
+ export function resolveLaneBuilder(
123
+ modules: ModuleEntry[],
124
+ entityItemId: number,
125
+ laneKey: number
126
+ ): ResolvedBuilderLane {
127
+ const idx = laneKey - 1
128
+ const installed = idx >= 0 && idx < modules.length ? modules[idx].installed : undefined
129
+ if (!installed) throw new Error('builder lane has no module')
130
+ const item = getItem(Number(installed.item_id.value ?? installed.item_id))
131
+ if (item.moduleType !== 'builder') throw new Error('lane module is not a builder')
132
+ const stats = BigInt(installed.stats.toString())
133
+ const res = decodeStat(stats, 0)
134
+ const fin = decodeStat(stats, 1)
135
+ const layout = getEntityLayout(entityItemId)?.slots ?? []
136
+ const amp = getSlotAmp(layout, idx)
137
+ const speed = applySlotMultiplier(computeBuilderSpeed(res), amp)
138
+ const drain = computeBuilderDrain(fin)
139
+ return {slotIndex: idx, speed, drain, outputPct: amp}
140
+ }
141
+
113
142
  // LANE_MOBILITY or a missing module soft-returns valid=false (never throws); callers check `valid`.
114
143
  export function resolveLaneLoader(
115
144
  modules: ModuleEntry[],
@@ -1,4 +1,4 @@
1
- import {Name, UInt16, UInt32, UInt64} from '@wharfkit/antelope'
1
+ import {Name, type UInt16, UInt32, UInt64} from '@wharfkit/antelope'
2
2
  import {ServerContract} from '../contracts'
3
3
  import {Coordinates, TaskType} from '../types'
4
4
  import {
@@ -16,6 +16,7 @@ import {
16
16
  ENTITY_CARGO_NOT_LOADED,
17
17
  } from '../errors'
18
18
  import {getEntityLayout, getRecipe, type RecipeInput} from '../data/recipes-runtime'
19
+ import {ITEM_SHIP_T1_PACKED} from '../data/item-ids'
19
20
  import {computeEntityCapabilities} from '../derivation/capabilities'
20
21
  import {decodeCraftedItemStats, decodeStat} from '../derivation/crafting'
21
22
  import {packedModulesToInstalled, type InstalledModule} from '../entities/slot-multiplier'
@@ -33,7 +34,7 @@ import type {ScheduleData} from './schedule'
33
34
 
34
35
  export interface ProjectedEntity {
35
36
  location: Coordinates
36
- energy: UInt16
37
+ energy: UInt32
37
38
  cargo: CargoStack[]
38
39
  shipMass: UInt32
39
40
  capacity?: UInt64
@@ -46,6 +47,7 @@ export interface ProjectedEntity {
46
47
  readonly totalMass: UInt64
47
48
  readonly gathererLanes: ServerContract.Types.gatherer_lane[]
48
49
  readonly crafterLanes: ServerContract.Types.crafter_lane[]
50
+ readonly builderLanes: ServerContract.Types.builder_lane[]
49
51
 
50
52
  hasMovement(): boolean
51
53
  hasStorage(): boolean
@@ -58,13 +60,14 @@ export interface ProjectedEntity {
58
60
 
59
61
  export interface Projectable extends ScheduleData {
60
62
  coordinates: Coordinates | ServerContract.Types.coordinates
61
- energy?: UInt16
63
+ energy?: UInt32
62
64
  hullmass?: UInt32
63
65
  generator?: ServerContract.Types.energy_stats
64
66
  engines?: ServerContract.Types.movement_stats
65
67
  loader_lanes?: ServerContract.Types.loader_lane[]
66
68
  gatherer_lanes?: ServerContract.Types.gatherer_lane[]
67
69
  crafter_lanes?: ServerContract.Types.crafter_lane[]
70
+ builder_lanes?: ServerContract.Types.builder_lane[]
68
71
  hauler?: ServerContract.Types.hauler_stats
69
72
  launcher?: ServerContract.Types.launcher_stats
70
73
  capacity?: UInt32
@@ -93,6 +96,7 @@ interface ProjectedCaps {
93
96
  loaderLanes: ServerContract.Types.loader_lane[]
94
97
  gathererLanes: ServerContract.Types.gatherer_lane[]
95
98
  crafterLanes: ServerContract.Types.crafter_lane[]
99
+ builderLanes: ServerContract.Types.builder_lane[]
96
100
  hauler?: ServerContract.Types.hauler_stats
97
101
  launcher?: ServerContract.Types.launcher_stats
98
102
  }
@@ -112,6 +116,14 @@ function recomputeCaps(entity: Projectable): ProjectedCaps | undefined {
112
116
  const hullStats = decodeCraftedItemStats(itemId, entity.stats)
113
117
  if (hullStats.strength === undefined) hullStats.strength = decodeStat(entity.stats, 0)
114
118
  if (hullStats.hardness === undefined) hullStats.hardness = decodeStat(entity.stats, 2)
119
+ // The retired Ship T1 recipe only assigned names to its first two stat slots.
120
+ // Its on-chain entity row still carries five packed channels, and the ship hull
121
+ // formula deliberately consumes all five. Keep the unlabelled channels internal
122
+ // to projection so replay remains contract-parity until deployment replaces it.
123
+ if (itemId === ITEM_SHIP_T1_PACKED) {
124
+ hullStats.__legacyShipChannel3 = decodeStat(entity.stats, 3)
125
+ hullStats.__legacyShipChannel4 = decodeStat(entity.stats, 4)
126
+ }
115
127
  const layout = getEntityLayout(itemId)?.slots ?? []
116
128
  const installed = toInstalledModules(entity.modules)
117
129
  const caps = computeEntityCapabilities(hullStats, itemId, installed, layout)
@@ -157,6 +169,19 @@ function recomputeCaps(entity: Projectable): ProjectedCaps | undefined {
157
169
  output_pct: l.outputPct,
158
170
  })
159
171
 
172
+ const toBuilderLane = (l: {
173
+ slotIndex: number
174
+ speed: number
175
+ drain: number
176
+ outputPct: number
177
+ }): ServerContract.Types.builder_lane =>
178
+ ServerContract.Types.builder_lane.from({
179
+ slot_index: l.slotIndex,
180
+ speed: l.speed,
181
+ drain: l.drain,
182
+ output_pct: l.outputPct,
183
+ })
184
+
160
185
  return {
161
186
  hullmass: UInt32.from(caps.hullmass),
162
187
  capacity: UInt32.from(caps.capacity),
@@ -167,6 +192,7 @@ function recomputeCaps(entity: Projectable): ProjectedCaps | undefined {
167
192
  loaderLanes: (caps.loaderLanes ?? []).map(toLoaderLane),
168
193
  gathererLanes: (caps.gathererLanes ?? []).map(toGathererLane),
169
194
  crafterLanes: (caps.crafterLanes ?? []).map(toCrafterLane),
195
+ builderLanes: (caps.builderLanes ?? []).map(toBuilderLane),
170
196
  hauler: caps.hauler
171
197
  ? ServerContract.Types.hauler_stats.from({
172
198
  capacity: caps.hauler.capacity,
@@ -206,6 +232,7 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
206
232
  const loaderLanes = entity.loader_lanes ?? caps?.loaderLanes ?? []
207
233
  const gathererLanes = entity.gatherer_lanes ?? caps?.gathererLanes ?? []
208
234
  const crafterLanes = entity.crafter_lanes ?? caps?.crafterLanes ?? []
235
+ const builderLanes = entity.builder_lanes ?? caps?.builderLanes ?? []
209
236
  const engines = entity.engines ?? caps?.engines
210
237
  const generator = entity.generator ?? caps?.generator
211
238
  const hauler = entity.hauler ?? caps?.hauler
@@ -216,7 +243,7 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
216
243
 
217
244
  const projected: ProjectedEntity = {
218
245
  location: Coordinates.from(entity.coordinates),
219
- energy: UInt16.from(entity.energy ?? 0),
246
+ energy: UInt32.from(entity.energy ?? 0),
220
247
  cargo,
221
248
  shipMass,
222
249
  capacity: capacity ? UInt64.from(capacity) : undefined,
@@ -227,6 +254,7 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
227
254
  loaderLanes,
228
255
  gathererLanes,
229
256
  crafterLanes,
257
+ builderLanes,
230
258
 
231
259
  get cargoMass() {
232
260
  return calcStacksMass(this.cargo)
@@ -286,12 +314,12 @@ function applyRechargeTask(
286
314
  if (!projected.generator) return
287
315
 
288
316
  if (options.complete) {
289
- projected.energy = UInt16.from(projected.generator.capacity)
317
+ projected.energy = UInt32.from(projected.generator.capacity)
290
318
  } else if (options.progress !== undefined) {
291
319
  const capacity = Number(projected.generator.capacity)
292
320
  const currentEnergy = Number(projected.energy)
293
321
  const rechargeAmount = (capacity - currentEnergy) * options.progress
294
- projected.energy = UInt16.from(Math.min(capacity, currentEnergy + rechargeAmount))
322
+ projected.energy = UInt32.from(Math.min(capacity, currentEnergy + rechargeAmount))
295
323
  }
296
324
  }
297
325
 
@@ -317,8 +345,8 @@ function applyFlightTask(
317
345
  Math.floor(Number(task.energy_cost ?? 0) * options.progress)
318
346
  )
319
347
  projected.energy = projected.energy.gt(partialEnergy)
320
- ? UInt16.from(projected.energy.subtracting(partialEnergy))
321
- : UInt16.from(0)
348
+ ? UInt32.from(projected.energy.subtracting(partialEnergy))
349
+ : UInt32.from(0)
322
350
  }
323
351
  }
324
352
 
@@ -344,10 +372,10 @@ function applyRemoveCargoTask(projected: ProjectedEntity, task: ServerContract.T
344
372
 
345
373
  function applyEnergyCost(projected: ProjectedEntity, task: ServerContract.Types.task): void {
346
374
  if (!task.energy_cost) return
347
- const energyCost = UInt16.from(task.energy_cost)
375
+ const energyCost = UInt32.from(task.energy_cost)
348
376
  projected.energy = projected.energy.gt(energyCost)
349
- ? UInt16.from(projected.energy.subtracting(energyCost))
350
- : UInt16.from(0)
377
+ ? UInt32.from(projected.energy.subtracting(energyCost))
378
+ : UInt32.from(0)
351
379
  }
352
380
 
353
381
  function applyGatherTask(
@@ -357,7 +385,7 @@ function applyGatherTask(
357
385
  ): void {
358
386
  if (!options.complete) return
359
387
  applyEnergyCost(projected, task)
360
- if (!task.entitytarget) {
388
+ if (task.couplings.length === 0) {
361
389
  applyAddCargoTask(projected, task)
362
390
  }
363
391
  }
@@ -369,13 +397,8 @@ function applyCraftTask(projected: ProjectedEntity, task: ServerContract.Types.t
369
397
  for (let i = 0; i < task.cargo.length - 1; i++) {
370
398
  removeCargoItem(projected, task.cargo[i])
371
399
  }
372
- addCargoItem(projected, task.cargo[task.cargo.length - 1])
373
- }
374
-
375
- function applyDeployTask(projected: ProjectedEntity, task: ServerContract.Types.task): void {
376
- applyEnergyCost(projected, task)
377
- if (task.cargo.length > 0) {
378
- removeCargoItem(projected, task.cargo[0])
400
+ if (task.couplings.length === 0) {
401
+ addCargoItem(projected, task.cargo[task.cargo.length - 1])
379
402
  }
380
403
  }
381
404
 
@@ -402,9 +425,6 @@ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task):
402
425
  case TaskType.CRAFT:
403
426
  applyCraftTask(projected, task)
404
427
  break
405
- case TaskType.DEPLOY:
406
- applyDeployTask(projected, task)
407
- break
408
428
  case TaskType.UNDEPLOY:
409
429
  case TaskType.DEMOLISH:
410
430
  break
@@ -561,9 +581,6 @@ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity
561
581
  case TaskType.CRAFT:
562
582
  if (taskComplete) applyCraftTask(projected, task)
563
583
  break
564
- case TaskType.DEPLOY:
565
- if (taskComplete) applyDeployTask(projected, task)
566
- break
567
584
  case TaskType.UNDEPLOY:
568
585
  case TaskType.DEMOLISH:
569
586
  break
@@ -32,7 +32,7 @@ export function taskCargoChanges(task: ServerContract.Types.task): TaskCargoChan
32
32
  case TaskType.UNWRAP:
33
33
  return items.map((i) => toChange(i, 'in'))
34
34
  case TaskType.GATHER:
35
- return task.entitytarget ? [] : items.map((i) => toChange(i, 'in'))
35
+ return task.couplings.length > 0 ? [] : items.map((i) => toChange(i, 'in'))
36
36
  case TaskType.UNLOAD:
37
37
  return items.map((i) => toChange(i, 'out'))
38
38
  case TaskType.CRAFT:
@@ -1,4 +1,4 @@
1
- import type {UInt16, UInt32} from '@wharfkit/antelope'
1
+ import type {UInt32} from '@wharfkit/antelope'
2
2
  import type {ServerContract} from '../contracts'
3
3
  import type {ProjectedEntity} from '../scheduling/projection'
4
4
  import {type CargoStack, cargoItemToStack, mergeStacks} from '../capabilities/storage'
@@ -6,7 +6,7 @@ import {type CargoStack, cargoItemToStack, mergeStacks} from '../capabilities/st
6
6
  export interface ContractProjectedState {
7
7
  owner: {toString(): string}
8
8
  coordinates: ServerContract.Types.coordinates
9
- energy?: UInt16
9
+ energy?: UInt32
10
10
  cargomass: UInt32
11
11
  cargo: ServerContract.Types.cargo_view[]
12
12
  hullmass?: UInt32
@@ -1,23 +1,21 @@
1
1
  export interface ReachStats {
2
2
  generator?: {capacity: bigint}
3
3
  engines?: {drain: bigint}
4
- hauler?: {drain: bigint}
5
4
  }
6
5
 
7
- export function computePerLegReach(s: ReachStats, haulCount = 0): number {
6
+ export function computePerLegReach(s: ReachStats): number {
8
7
  const capacity = s.generator?.capacity
9
8
  const drain = s.engines?.drain
10
9
  if (capacity === undefined || drain === undefined || drain === 0n) {
11
10
  throw new Error('entity has no usable engine/generator (cannot compute per-leg reach)')
12
11
  }
13
- const haulDrain = s.hauler && haulCount > 0 ? s.hauler.drain * BigInt(haulCount) : 0n
14
- return Number(capacity) / Number(drain + haulDrain)
12
+ return Number(capacity) / Number(drain)
15
13
  }
16
14
 
17
- export function computeGroupPerLegReach(participants: ReachStats[], haulCount: number): number {
15
+ export function computeGroupPerLegReach(participants: ReachStats[]): number {
18
16
  const movers = participants.filter((p) => p.engines !== undefined && p.engines.drain !== 0n)
19
17
  if (movers.length === 0) {
20
18
  throw new Error('group has no moving entity (cannot compute per-leg reach)')
21
19
  }
22
- return Math.min(...movers.map((p) => computePerLegReach(p, haulCount)))
20
+ return Math.min(...movers.map(computePerLegReach))
23
21
  }