@shipload/sdk 1.0.0-next.47 → 1.0.0-next.49
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.
- package/lib/shipload.d.ts +141 -73
- package/lib/shipload.js +355 -233
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +350 -233
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +9 -0
- package/lib/testing.js +43 -13
- package/lib/testing.js.map +1 -1
- package/lib/testing.m.js +43 -13
- package/lib/testing.m.js.map +1 -1
- package/package.json +1 -1
- package/src/capabilities/crafting.ts +2 -2
- package/src/contracts/server.ts +28 -1
- package/src/data/items.json +11 -11
- package/src/data/kind-registry.json +1 -1
- package/src/data/metadata.ts +1 -1
- package/src/data/recipes-runtime.ts +1 -0
- package/src/data/recipes.json +154 -180
- package/src/derivation/crafting.ts +1 -1
- package/src/derivation/index.ts +1 -0
- package/src/derivation/upgrades.ts +14 -0
- package/src/index-module.ts +20 -2
- package/src/managers/actions.ts +43 -0
- package/src/managers/flatten-gather-plan.test.ts +80 -0
- package/src/nft/description.ts +1 -1
- package/src/planner/index.ts +169 -54
- package/src/planner/planner.test.ts +201 -195
- package/src/scheduling/unwrap.test.ts +26 -0
- package/src/scheduling/unwrap.ts +5 -2
- package/src/types.ts +3 -0
package/src/index-module.ts
CHANGED
|
@@ -150,6 +150,8 @@ export type {ReserveTier, TierRange} from './derivation'
|
|
|
150
150
|
export {getEffectiveReserve} from './derivation'
|
|
151
151
|
export type {EffectiveReserveInput} from './derivation'
|
|
152
152
|
|
|
153
|
+
export {eligibleUpgrades} from './derivation'
|
|
154
|
+
|
|
153
155
|
export {getStatDefinitions, getStatName, resolveStats} from './derivation'
|
|
154
156
|
export type {StatDefinition, NamedStats} from './derivation'
|
|
155
157
|
|
|
@@ -568,5 +570,21 @@ export type {DescribeOptions} from './resolution/display-name'
|
|
|
568
570
|
|
|
569
571
|
export * from './subscriptions'
|
|
570
572
|
|
|
571
|
-
export {
|
|
572
|
-
|
|
573
|
+
export {
|
|
574
|
+
allocateProportional,
|
|
575
|
+
buildGatherPlan,
|
|
576
|
+
gatherEnergyCost,
|
|
577
|
+
maxQtyForCharge,
|
|
578
|
+
planParallelTransfer,
|
|
579
|
+
splitCost,
|
|
580
|
+
} from './planner'
|
|
581
|
+
export type {
|
|
582
|
+
BuildGatherPlanOpts,
|
|
583
|
+
FillCap,
|
|
584
|
+
GatherCycle,
|
|
585
|
+
GatherLimpet,
|
|
586
|
+
GatherPlan,
|
|
587
|
+
GatherPlanEntity,
|
|
588
|
+
LanePlanEntry,
|
|
589
|
+
PlanTarget,
|
|
590
|
+
} from './planner'
|
package/src/managers/actions.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {Coordinates, PRECISION, type ClusterSlotType, type CoordinatesType} from
|
|
|
21
21
|
import {ServerContract} from '../contracts'
|
|
22
22
|
import {ATOMICASSETS_ABI, SHIPLOAD_COLLECTION} from '../nft/atomicassets'
|
|
23
23
|
import {getItem} from '../data/catalog'
|
|
24
|
+
import type {GatherPlan} from '../planner'
|
|
24
25
|
|
|
25
26
|
const CHARGE_K = 1n
|
|
26
27
|
const ENERGY_DIVISOR = 1_000_000n
|
|
@@ -394,6 +395,29 @@ export class ActionsManager extends BaseManager {
|
|
|
394
395
|
})
|
|
395
396
|
}
|
|
396
397
|
|
|
398
|
+
// Mirrors the contract's projected_energy walk: recharge (if due) then gathers, per cycle.
|
|
399
|
+
flattenGatherPlan(
|
|
400
|
+
plan: GatherPlan,
|
|
401
|
+
ctx: {sourceId: UInt64Type; destinationId: UInt64Type; stratum: UInt16Type}
|
|
402
|
+
): Action[] {
|
|
403
|
+
const actions: Action[] = []
|
|
404
|
+
for (const cycle of plan.cycles) {
|
|
405
|
+
if (cycle.rechargeBefore) actions.push(this.recharge(ctx.sourceId))
|
|
406
|
+
for (const limpet of cycle.limpets) {
|
|
407
|
+
actions.push(
|
|
408
|
+
this.gather(
|
|
409
|
+
ctx.sourceId,
|
|
410
|
+
ctx.destinationId,
|
|
411
|
+
ctx.stratum,
|
|
412
|
+
limpet.quantity,
|
|
413
|
+
limpet.slot
|
|
414
|
+
)
|
|
415
|
+
)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return actions
|
|
419
|
+
}
|
|
420
|
+
|
|
397
421
|
warp(entityId: UInt64Type, destination: CoordinatesType): Action {
|
|
398
422
|
const x = Int64.from(destination.x)
|
|
399
423
|
const y = Int64.from(destination.y)
|
|
@@ -447,6 +471,25 @@ export class ActionsManager extends BaseManager {
|
|
|
447
471
|
})
|
|
448
472
|
}
|
|
449
473
|
|
|
474
|
+
upgrade(
|
|
475
|
+
builderId: UInt64Type,
|
|
476
|
+
targetId: UInt64Type,
|
|
477
|
+
targetItemId: UInt16Type,
|
|
478
|
+
inputs: ServerContract.ActionParams.Type.cargo_item[],
|
|
479
|
+
slot?: UInt8Type
|
|
480
|
+
): Action {
|
|
481
|
+
const params: ServerContract.ActionParams.upgrade = {
|
|
482
|
+
builder_id: UInt64.from(builderId),
|
|
483
|
+
target_id: UInt64.from(targetId),
|
|
484
|
+
target_item_id: UInt16.from(targetItemId),
|
|
485
|
+
inputs,
|
|
486
|
+
}
|
|
487
|
+
if (slot !== undefined) {
|
|
488
|
+
params.slot = UInt8.from(slot)
|
|
489
|
+
}
|
|
490
|
+
return this.server.action('upgrade', params)
|
|
491
|
+
}
|
|
492
|
+
|
|
450
493
|
claimplot(entityId: UInt64Type, targetItemId: UInt16Type, slot: ClusterSlotType): Action {
|
|
451
494
|
return this.server.action('claimplot', {
|
|
452
495
|
builder_id: UInt64.from(entityId),
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {expect, test, describe} from 'bun:test'
|
|
2
|
+
import {makeClient} from '@wharfkit/mock-data'
|
|
3
|
+
import {Serializer} from '@wharfkit/antelope'
|
|
4
|
+
import type {GatherPlan} from '../planner'
|
|
5
|
+
import {ActionsManager} from './actions'
|
|
6
|
+
import {ServerContract} from '../contracts'
|
|
7
|
+
|
|
8
|
+
const client = makeClient('https://jungle4.greymass.com')
|
|
9
|
+
|
|
10
|
+
function makeActions() {
|
|
11
|
+
const realServer = new ServerContract.Contract({client})
|
|
12
|
+
const stubServer = {
|
|
13
|
+
account: realServer.account,
|
|
14
|
+
action: realServer.action.bind(realServer),
|
|
15
|
+
}
|
|
16
|
+
const context = {server: stubServer} as any
|
|
17
|
+
return new ActionsManager(context)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function fakePlan(): GatherPlan {
|
|
21
|
+
return {
|
|
22
|
+
cycles: [
|
|
23
|
+
{
|
|
24
|
+
rechargeBefore: false,
|
|
25
|
+
rechargeSeconds: 0,
|
|
26
|
+
gatherSeconds: 10,
|
|
27
|
+
batchOre: 30,
|
|
28
|
+
limpets: [
|
|
29
|
+
{slot: 0, quantity: 20, durationSeconds: 10},
|
|
30
|
+
{slot: 1, quantity: 10, durationSeconds: 8},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
rechargeBefore: true,
|
|
35
|
+
rechargeSeconds: 5,
|
|
36
|
+
gatherSeconds: 10,
|
|
37
|
+
batchOre: 30,
|
|
38
|
+
limpets: [{slot: 0, quantity: 30, durationSeconds: 10}],
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
cycleCount: 2,
|
|
42
|
+
totalOre: 60,
|
|
43
|
+
totalSeconds: 25,
|
|
44
|
+
cap: 'requested',
|
|
45
|
+
reachingCount: 2,
|
|
46
|
+
totalLimpets: 2,
|
|
47
|
+
warnings: [],
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe('flattenGatherPlan', () => {
|
|
52
|
+
test('flattens cycles to [recharge?, gather x limpets] in order with slots', () => {
|
|
53
|
+
const actions = makeActions().flattenGatherPlan(fakePlan(), {
|
|
54
|
+
sourceId: 1,
|
|
55
|
+
destinationId: 1,
|
|
56
|
+
stratum: 100,
|
|
57
|
+
})
|
|
58
|
+
expect(actions.map((a) => a.name.toString())).toEqual([
|
|
59
|
+
'gather',
|
|
60
|
+
'gather',
|
|
61
|
+
'recharge',
|
|
62
|
+
'gather',
|
|
63
|
+
])
|
|
64
|
+
expect(actions.filter((a) => a.name.toString() === 'recharge').length).toBe(1)
|
|
65
|
+
const gatherSlots = actions
|
|
66
|
+
.filter((a) => a.name.toString() === 'gather')
|
|
67
|
+
.map((a) => {
|
|
68
|
+
const decoded = Serializer.decode({data: a.data, type: ServerContract.Types.gather})
|
|
69
|
+
return Number(decoded.slot)
|
|
70
|
+
})
|
|
71
|
+
expect(gatherSlots).toEqual([0, 1, 0])
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('empty plan produces no actions', () => {
|
|
75
|
+
const empty = {...fakePlan(), cycles: [], cycleCount: 0, totalOre: 0}
|
|
76
|
+
expect(
|
|
77
|
+
makeActions().flattenGatherPlan(empty, {sourceId: 1, destinationId: 1, stratum: 100})
|
|
78
|
+
).toHaveLength(0)
|
|
79
|
+
})
|
|
80
|
+
})
|
package/src/nft/description.ts
CHANGED
|
@@ -104,7 +104,7 @@ export const computeBatteryBankCapacity = (
|
|
|
104
104
|
export function entityDisplayName(itemId: number): string {
|
|
105
105
|
switch (itemId) {
|
|
106
106
|
case ITEM_SHIP_T1_PACKED:
|
|
107
|
-
return '
|
|
107
|
+
return 'Roustabout'
|
|
108
108
|
case ITEM_WAREHOUSE_T1_PACKED:
|
|
109
109
|
return 'Warehouse'
|
|
110
110
|
case ITEM_EXTRACTOR_T1_PACKED:
|
package/src/planner/index.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import type {GathererStats} from '../types/capabilities'
|
|
2
2
|
import type {ServerContract} from '../contracts'
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
calc_gather_energy,
|
|
6
|
-
GATHER_MASS_DIVISOR,
|
|
7
|
-
} from '../capabilities/gathering'
|
|
3
|
+
import {calc_gather_duration, calc_gather_energy} from '../capabilities/gathering'
|
|
4
|
+
import {calc_rechargetime} from '../travel/travel'
|
|
8
5
|
import {projectRemainingAt, type Projectable} from '../scheduling/projection'
|
|
9
6
|
|
|
10
7
|
export interface LanePlanEntry {
|
|
@@ -19,26 +16,46 @@ export interface GatherPlanEntity extends Projectable {
|
|
|
19
16
|
loader_lanes: ServerContract.Types.loader_lane[]
|
|
20
17
|
}
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
export interface GatherLimpet {
|
|
20
|
+
slot: number
|
|
21
|
+
quantity: number
|
|
22
|
+
durationSeconds: number
|
|
23
|
+
}
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
export interface GatherCycle {
|
|
26
|
+
rechargeBefore: boolean
|
|
27
|
+
rechargeSeconds: number
|
|
28
|
+
limpets: GatherLimpet[]
|
|
29
|
+
gatherSeconds: number
|
|
30
|
+
batchOre: number
|
|
31
|
+
}
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
export type FillCap = 'reserve' | 'hold' | 'requested'
|
|
34
|
+
|
|
35
|
+
export interface GatherPlan {
|
|
36
|
+
cycles: GatherCycle[]
|
|
37
|
+
cycleCount: number
|
|
38
|
+
totalOre: number
|
|
39
|
+
totalSeconds: number
|
|
40
|
+
cap: FillCap
|
|
41
|
+
reachingCount: number
|
|
42
|
+
totalLimpets: number
|
|
43
|
+
warnings: string[]
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
|
|
46
|
+
export interface BuildGatherPlanOpts {
|
|
47
|
+
richness: number
|
|
48
|
+
itemMass: number
|
|
49
|
+
holdRoom: number
|
|
50
|
+
reserveRemaining: number
|
|
51
|
+
now: Date
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const MAX_CYCLES = 10_000
|
|
55
|
+
|
|
56
|
+
const MAX_TRANSFER_QTY = 10000
|
|
57
|
+
|
|
58
|
+
export function allocateProportional(
|
|
42
59
|
lanes: {slot: number; weight: number}[],
|
|
43
60
|
total: number
|
|
44
61
|
): LanePlanEntry[] {
|
|
@@ -60,53 +77,151 @@ function allocateProportional(
|
|
|
60
77
|
return entries
|
|
61
78
|
}
|
|
62
79
|
|
|
63
|
-
|
|
80
|
+
function laneStats(lane: ServerContract.Types.gatherer_lane): GathererStats {
|
|
81
|
+
return lane as unknown as GathererStats
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function gatherEnergyCost(
|
|
85
|
+
lane: ServerContract.Types.gatherer_lane,
|
|
86
|
+
quantity: number,
|
|
87
|
+
stratum: number,
|
|
88
|
+
itemMass: number,
|
|
89
|
+
richness: number
|
|
90
|
+
): number {
|
|
91
|
+
if (quantity <= 0) return 0
|
|
92
|
+
const dur = Number(calc_gather_duration(laneStats(lane), itemMass, quantity, stratum, richness))
|
|
93
|
+
return Number(calc_gather_energy(laneStats(lane), dur))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function splitCost(
|
|
97
|
+
reaching: ServerContract.Types.gatherer_lane[],
|
|
98
|
+
quantity: number,
|
|
99
|
+
stratum: number,
|
|
100
|
+
itemMass: number,
|
|
101
|
+
richness: number
|
|
102
|
+
): number {
|
|
103
|
+
if (quantity <= 0) return 0
|
|
104
|
+
const weights = reaching.map((l) => ({
|
|
105
|
+
slot: l.slot_index.toNumber(),
|
|
106
|
+
weight: l.yield.toNumber(),
|
|
107
|
+
}))
|
|
108
|
+
const split = allocateProportional(weights, quantity)
|
|
109
|
+
return split.reduce((sum, e) => {
|
|
110
|
+
const lane = reaching.find((l) => l.slot_index.toNumber() === e.slot)!
|
|
111
|
+
return sum + gatherEnergyCost(lane, e.quantity, stratum, itemMass, richness)
|
|
112
|
+
}, 0)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Binary search is exact because splitCost is monotonic non-decreasing in Q.
|
|
116
|
+
export function maxQtyForCharge(
|
|
117
|
+
reaching: ServerContract.Types.gatherer_lane[],
|
|
118
|
+
hi: number,
|
|
119
|
+
capacity: number,
|
|
120
|
+
stratum: number,
|
|
121
|
+
itemMass: number,
|
|
122
|
+
richness: number
|
|
123
|
+
): number {
|
|
124
|
+
if (hi <= 0) return 0
|
|
125
|
+
if (splitCost(reaching, hi, stratum, itemMass, richness) <= capacity) return hi
|
|
126
|
+
let lo = 0
|
|
127
|
+
let high = hi
|
|
128
|
+
while (lo < high) {
|
|
129
|
+
const mid = Math.ceil((lo + high) / 2)
|
|
130
|
+
if (splitCost(reaching, mid, stratum, itemMass, richness) <= capacity) lo = mid
|
|
131
|
+
else high = mid - 1
|
|
132
|
+
}
|
|
133
|
+
return lo
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function buildGatherPlan(
|
|
64
137
|
entity: GatherPlanEntity,
|
|
65
|
-
target: PlanTarget,
|
|
66
138
|
stratum: number,
|
|
67
|
-
|
|
68
|
-
|
|
139
|
+
target: PlanTarget,
|
|
140
|
+
opts: BuildGatherPlanOpts
|
|
141
|
+
): GatherPlan {
|
|
142
|
+
const {richness, itemMass, holdRoom, reserveRemaining, now} = opts
|
|
143
|
+
const warnings: string[] = []
|
|
144
|
+
|
|
69
145
|
const reaching = entity.gatherer_lanes.filter((l) => l.depth.toNumber() >= stratum)
|
|
70
146
|
if (reaching.length === 0) throw new Error('no gatherer reaches this stratum')
|
|
147
|
+
if (!entity.generator) throw new Error('entity has no generator')
|
|
71
148
|
|
|
72
|
-
|
|
73
|
-
|
|
149
|
+
const blocked = entity.gatherer_lanes.length - reaching.length
|
|
150
|
+
if (blocked > 0) {
|
|
151
|
+
warnings.push(
|
|
152
|
+
`${blocked} of ${entity.gatherer_lanes.length} limpets can't reach this depth`
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const capacity = entity.generator.capacity.toNumber()
|
|
157
|
+
const rechargeRate = entity.generator.recharge.toNumber()
|
|
158
|
+
let energy = Number(projectRemainingAt(entity, now).energy)
|
|
74
159
|
|
|
75
|
-
const
|
|
160
|
+
const requested = target === 'max' ? Infinity : target.quantity
|
|
161
|
+
let fillTarget = requested
|
|
162
|
+
let cap: FillCap = 'requested'
|
|
163
|
+
if (holdRoom < fillTarget) {
|
|
164
|
+
fillTarget = holdRoom
|
|
165
|
+
cap = 'hold'
|
|
166
|
+
}
|
|
167
|
+
if (reserveRemaining < fillTarget) {
|
|
168
|
+
fillTarget = reserveRemaining
|
|
169
|
+
cap = 'reserve'
|
|
170
|
+
}
|
|
171
|
+
fillTarget = Math.max(0, Math.floor(fillTarget))
|
|
172
|
+
|
|
173
|
+
const cycles: GatherCycle[] = []
|
|
174
|
+
let remaining = fillTarget
|
|
175
|
+
let guard = 0
|
|
176
|
+
while (remaining > 0 && guard++ < MAX_CYCLES) {
|
|
177
|
+
const batch = maxQtyForCharge(reaching, remaining, capacity, stratum, itemMass, richness)
|
|
178
|
+
if (batch <= 0) {
|
|
179
|
+
warnings.push('a single gather cannot fit within one full charge')
|
|
180
|
+
break
|
|
181
|
+
}
|
|
76
182
|
|
|
77
|
-
|
|
78
|
-
|
|
183
|
+
const costFull = splitCost(reaching, batch, stratum, itemMass, richness)
|
|
184
|
+
const rechargeBefore = energy < costFull
|
|
185
|
+
const rechargeSeconds = rechargeBefore
|
|
186
|
+
? Number(calc_rechargetime(capacity, energy, rechargeRate))
|
|
187
|
+
: 0
|
|
188
|
+
if (rechargeBefore) energy = capacity
|
|
79
189
|
|
|
80
|
-
|
|
81
|
-
const laneWeights = activeLanes.map((l) => ({
|
|
190
|
+
const weights = reaching.map((l) => ({
|
|
82
191
|
slot: l.slot_index.toNumber(),
|
|
83
192
|
weight: l.yield.toNumber(),
|
|
84
193
|
}))
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
194
|
+
const split = allocateProportional(weights, batch).filter((e) => e.quantity > 0)
|
|
195
|
+
const limpets: GatherLimpet[] = split.map((e) => {
|
|
196
|
+
const lane = reaching.find((l) => l.slot_index.toNumber() === e.slot)!
|
|
197
|
+
const dur = Number(
|
|
198
|
+
calc_gather_duration(laneStats(lane), itemMass, e.quantity, stratum, richness)
|
|
199
|
+
)
|
|
200
|
+
return {slot: e.slot, quantity: e.quantity, durationSeconds: dur}
|
|
201
|
+
})
|
|
202
|
+
const actualCost = limpets.reduce((sum, l) => {
|
|
203
|
+
const lane = reaching.find((r) => r.slot_index.toNumber() === l.slot)!
|
|
204
|
+
return sum + Number(calc_gather_energy(laneStats(lane), l.durationSeconds))
|
|
91
205
|
}, 0)
|
|
206
|
+
energy = Math.max(0, energy - actualCost)
|
|
92
207
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (activeLanes.length === 1) {
|
|
98
|
-
const lane = activeLanes[0]
|
|
99
|
-
const energyPerUnit = gatherEnergyCost(lane, 1, stratum)
|
|
100
|
-
if (energyPerUnit === 0) return proposed.filter((e) => e.quantity > 0)
|
|
101
|
-
const maxQty = Math.min(requestedQty, Math.floor(energy / energyPerUnit))
|
|
102
|
-
if (maxQty <= 0) return []
|
|
103
|
-
return [{slot: lane.slot_index.toNumber(), quantity: maxQty}]
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
activeLanes = activeLanes.slice(1)
|
|
208
|
+
const gatherSeconds = limpets.reduce((m, l) => Math.max(m, l.durationSeconds), 0)
|
|
209
|
+
cycles.push({rechargeBefore, rechargeSeconds, limpets, gatherSeconds, batchOre: batch})
|
|
210
|
+
remaining -= batch
|
|
107
211
|
}
|
|
108
212
|
|
|
109
|
-
|
|
213
|
+
const totalOre = cycles.reduce((s, c) => s + c.batchOre, 0)
|
|
214
|
+
const totalSeconds = cycles.reduce((s, c) => s + c.rechargeSeconds + c.gatherSeconds, 0)
|
|
215
|
+
return {
|
|
216
|
+
cycles,
|
|
217
|
+
cycleCount: cycles.length,
|
|
218
|
+
totalOre,
|
|
219
|
+
totalSeconds,
|
|
220
|
+
cap,
|
|
221
|
+
reachingCount: reaching.length,
|
|
222
|
+
totalLimpets: entity.gatherer_lanes.length,
|
|
223
|
+
warnings,
|
|
224
|
+
}
|
|
110
225
|
}
|
|
111
226
|
|
|
112
227
|
export function planParallelTransfer(
|
|
@@ -116,7 +231,7 @@ export function planParallelTransfer(
|
|
|
116
231
|
const lanes = entity.loader_lanes.filter((l) => l.thrust.toNumber() > 0)
|
|
117
232
|
if (lanes.length === 0) return []
|
|
118
233
|
|
|
119
|
-
const requestedQty = target === 'max' ?
|
|
234
|
+
const requestedQty = target === 'max' ? MAX_TRANSFER_QTY : target.quantity
|
|
120
235
|
|
|
121
236
|
const laneWeights = lanes.map((l) => ({
|
|
122
237
|
slot: l.slot_index.toNumber(),
|