@shipload/sdk 1.0.0-next.54 → 1.0.0-next.56
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 +7 -2
- package/lib/shipload.js +127 -25
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +125 -26
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.js +11 -0
- package/lib/testing.js.map +1 -1
- package/lib/testing.m.js +11 -0
- package/lib/testing.m.js.map +1 -1
- package/package.json +1 -1
- package/src/data/entities.json +16 -0
- package/src/data/item-ids.ts +1 -0
- package/src/data/items.json +6 -0
- package/src/data/kind-registry.json +12 -0
- package/src/data/kind-registry.ts +5 -0
- package/src/data/metadata.ts +7 -0
- package/src/data/recipes.json +39 -0
- package/src/derivation/capabilities.ts +2 -0
- package/src/derivation/recipe-usage.test.ts +7 -1
- package/src/index-module.ts +2 -0
- package/src/managers/construction.ts +41 -10
- package/src/nft/description.ts +4 -0
- package/src/scheduling/projection.ts +1 -14
- package/src/scheduling/schedule.test.ts +12 -9
- package/src/scheduling/schedule.ts +2 -6
package/package.json
CHANGED
package/src/data/entities.json
CHANGED
|
@@ -171,6 +171,22 @@
|
|
|
171
171
|
],
|
|
172
172
|
"baseHullmass": 100000
|
|
173
173
|
},
|
|
174
|
+
{
|
|
175
|
+
"entityItemId": 10209,
|
|
176
|
+
"slots": [
|
|
177
|
+
{
|
|
178
|
+
"type": "generator",
|
|
179
|
+
"outputPct": 200,
|
|
180
|
+
"maxTier": 1
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"type": "builder",
|
|
184
|
+
"outputPct": 200,
|
|
185
|
+
"maxTier": 1
|
|
186
|
+
}
|
|
187
|
+
],
|
|
188
|
+
"baseHullmass": 100000
|
|
189
|
+
},
|
|
174
190
|
{
|
|
175
191
|
"entityItemId": 10210,
|
|
176
192
|
"slots": [
|
package/src/data/item-ids.ts
CHANGED
|
@@ -80,6 +80,7 @@ export const ITEM_MASS_DRIVER_T1_PACKED = 10205
|
|
|
80
80
|
export const ITEM_MASS_CATCHER_T1_PACKED = 10206
|
|
81
81
|
export const ITEM_HUB_T1_PACKED = 10207
|
|
82
82
|
export const ITEM_WORKSHOP_T1_PACKED = 10208
|
|
83
|
+
export const ITEM_CONSTRUCTION_DOCK_T1_PACKED = 10209
|
|
83
84
|
export const ITEM_ROUSTABOUT_T1_PACKED = 10210
|
|
84
85
|
export const ITEM_PROSPECTOR_T1_PACKED = 10211
|
|
85
86
|
export const ITEM_TENDER_T1_PACKED = 10212
|
package/src/data/items.json
CHANGED
|
@@ -76,6 +76,13 @@
|
|
|
76
76
|
"capabilityFlags": 20,
|
|
77
77
|
"zCoord": 0,
|
|
78
78
|
"defaultLabel": "Workshop"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"kind": "builddock",
|
|
82
|
+
"classification": "OrbitalStructure",
|
|
83
|
+
"capabilityFlags": 20,
|
|
84
|
+
"zCoord": 0,
|
|
85
|
+
"defaultLabel": "Construction Dock"
|
|
79
86
|
}
|
|
80
87
|
],
|
|
81
88
|
"templates": [
|
|
@@ -169,6 +176,11 @@
|
|
|
169
176
|
"kind": "workshop",
|
|
170
177
|
"displayLabel": ""
|
|
171
178
|
},
|
|
179
|
+
{
|
|
180
|
+
"itemId": 10209,
|
|
181
|
+
"kind": "builddock",
|
|
182
|
+
"displayLabel": ""
|
|
183
|
+
},
|
|
172
184
|
{
|
|
173
185
|
"itemId": 20210,
|
|
174
186
|
"kind": "ship",
|
|
@@ -26,6 +26,7 @@ export type EntityTypeName =
|
|
|
26
26
|
| 'warehouse'
|
|
27
27
|
| 'extractor'
|
|
28
28
|
| 'factory'
|
|
29
|
+
| 'builddock'
|
|
29
30
|
| 'workshop'
|
|
30
31
|
| 'container'
|
|
31
32
|
| 'nexus'
|
|
@@ -134,6 +135,7 @@ export const ENTITY_SHIP = Name.from('ship')
|
|
|
134
135
|
export const ENTITY_WAREHOUSE = Name.from('warehouse')
|
|
135
136
|
export const ENTITY_EXTRACTOR = Name.from('extractor')
|
|
136
137
|
export const ENTITY_FACTORY = Name.from('factory')
|
|
138
|
+
export const ENTITY_CONSTRUCTION_DOCK = Name.from('builddock')
|
|
137
139
|
export const ENTITY_WORKSHOP = Name.from('workshop')
|
|
138
140
|
export const ENTITY_CONTAINER = Name.from('container')
|
|
139
141
|
export const ENTITY_NEXUS = Name.from('nexus')
|
|
@@ -154,6 +156,9 @@ export function isExtractor(entity: {type?: Name}): boolean {
|
|
|
154
156
|
export function isFactory(entity: {type?: Name}): boolean {
|
|
155
157
|
return entity.type?.equals(ENTITY_FACTORY) ?? false
|
|
156
158
|
}
|
|
159
|
+
export function isConstructionDock(entity: {type?: Name}): boolean {
|
|
160
|
+
return entity.type?.equals(ENTITY_CONSTRUCTION_DOCK) ?? false
|
|
161
|
+
}
|
|
157
162
|
export function isWorkshop(entity: {type?: Name}): boolean {
|
|
158
163
|
return entity.type?.equals(ENTITY_WORKSHOP) ?? false
|
|
159
164
|
}
|
package/src/data/metadata.ts
CHANGED
|
@@ -279,6 +279,12 @@ export const itemMetadata: Record<number, ItemMetadata> = {
|
|
|
279
279
|
'A station workshop with five independent workers. Visiting ships bring materials and power, and the workshop does the crafting.',
|
|
280
280
|
color: '#B877FF',
|
|
281
281
|
},
|
|
282
|
+
10209: {
|
|
283
|
+
name: 'Construction Dock',
|
|
284
|
+
description:
|
|
285
|
+
'An immobile station construction facility with amplified power core and assembly arm slots.',
|
|
286
|
+
color: '#FFB347',
|
|
287
|
+
},
|
|
282
288
|
10210: {
|
|
283
289
|
name: 'Roustabout',
|
|
284
290
|
description:
|
|
@@ -433,6 +439,7 @@ export const entityMetadata: Record<number, EntityMetadata> = {
|
|
|
433
439
|
10208: {
|
|
434
440
|
moduleSlotLabels: ['Fabricator', 'Fabricator', 'Fabricator', 'Fabricator', 'Fabricator'],
|
|
435
441
|
},
|
|
442
|
+
10209: {moduleSlotLabels: ['Power Core', 'Assembly Arm']},
|
|
436
443
|
20210: {moduleSlotLabels: ['Power Core', 'Power Core', 'Engine', 'Limpet Bay']},
|
|
437
444
|
20211: {moduleSlotLabels: ['Power Core', 'Engine', 'Tractor Beam', 'Tractor Beam']},
|
|
438
445
|
}
|
package/src/data/recipes.json
CHANGED
|
@@ -1016,6 +1016,45 @@
|
|
|
1016
1016
|
],
|
|
1017
1017
|
"blendWeights": []
|
|
1018
1018
|
},
|
|
1019
|
+
{
|
|
1020
|
+
"outputItemId": 10209,
|
|
1021
|
+
"outputMass": 1900000,
|
|
1022
|
+
"inputs": [
|
|
1023
|
+
{
|
|
1024
|
+
"itemId": 10001,
|
|
1025
|
+
"quantity": 600
|
|
1026
|
+
},
|
|
1027
|
+
{
|
|
1028
|
+
"itemId": 10003,
|
|
1029
|
+
"quantity": 600
|
|
1030
|
+
}
|
|
1031
|
+
],
|
|
1032
|
+
"statSlots": [
|
|
1033
|
+
{
|
|
1034
|
+
"sources": [
|
|
1035
|
+
{
|
|
1036
|
+
"inputIndex": 0,
|
|
1037
|
+
"statIndex": 0
|
|
1038
|
+
}
|
|
1039
|
+
]
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
"sources": [
|
|
1043
|
+
{
|
|
1044
|
+
"inputIndex": 0,
|
|
1045
|
+
"statIndex": 1
|
|
1046
|
+
}
|
|
1047
|
+
]
|
|
1048
|
+
},
|
|
1049
|
+
{
|
|
1050
|
+
"sources": []
|
|
1051
|
+
},
|
|
1052
|
+
{
|
|
1053
|
+
"sources": []
|
|
1054
|
+
}
|
|
1055
|
+
],
|
|
1056
|
+
"blendWeights": []
|
|
1057
|
+
},
|
|
1019
1058
|
{
|
|
1020
1059
|
"outputItemId": 10210,
|
|
1021
1060
|
"outputMass": 1600000,
|
|
@@ -218,6 +218,7 @@ export function computeBatteryCapabilities(stats: Record<string, number>): {
|
|
|
218
218
|
import {
|
|
219
219
|
ITEM_CONTAINER_T1_PACKED,
|
|
220
220
|
ITEM_CONTAINER_T2_PACKED,
|
|
221
|
+
ITEM_CONSTRUCTION_DOCK_T1_PACKED,
|
|
221
222
|
ITEM_EXTRACTOR_T1_PACKED,
|
|
222
223
|
ITEM_FACTORY_T1_PACKED,
|
|
223
224
|
ITEM_MASS_CATCHER_T1_PACKED,
|
|
@@ -277,6 +278,7 @@ export function computeBaseCapacity(itemId: number, stats: Record<string, number
|
|
|
277
278
|
switch (itemId) {
|
|
278
279
|
case ITEM_EXTRACTOR_T1_PACKED:
|
|
279
280
|
case ITEM_FACTORY_T1_PACKED:
|
|
281
|
+
case ITEM_CONSTRUCTION_DOCK_T1_PACKED:
|
|
280
282
|
case ITEM_MASS_DRIVER_T1_PACKED:
|
|
281
283
|
case ITEM_MASS_CATCHER_T1_PACKED:
|
|
282
284
|
case ITEM_CONTAINER_T1_PACKED:
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
ITEM_BEAM,
|
|
14
14
|
ITEM_GATHERER_T1,
|
|
15
15
|
ITEM_CRAFTER_T1,
|
|
16
|
+
ITEM_BUILDER_T1,
|
|
16
17
|
ITEM_EXTRACTOR_T1_PACKED,
|
|
17
18
|
ITEM_ROUSTABOUT_T1_PACKED,
|
|
18
19
|
ITEM_PROSPECTOR_T1_PACKED,
|
|
@@ -33,7 +34,12 @@ test('getRecipeConsumers lists every recipe that consumes Sensor', () => {
|
|
|
33
34
|
const consumers = getRecipeConsumers(ITEM_SENSOR)
|
|
34
35
|
const ids = consumers.map((c) => c.outputItemId).sort((a, b) => a - b)
|
|
35
36
|
expect(ids).toEqual(
|
|
36
|
-
[
|
|
37
|
+
[
|
|
38
|
+
ITEM_CRAFTER_T1,
|
|
39
|
+
ITEM_BUILDER_T1,
|
|
40
|
+
ITEM_EXTRACTOR_T1_PACKED,
|
|
41
|
+
ITEM_ROUSTABOUT_T1_PACKED,
|
|
42
|
+
].sort((a, b) => a - b)
|
|
37
43
|
)
|
|
38
44
|
})
|
|
39
45
|
|
package/src/index-module.ts
CHANGED
|
@@ -333,6 +333,7 @@ export {
|
|
|
333
333
|
ENTITY_WAREHOUSE,
|
|
334
334
|
ENTITY_EXTRACTOR,
|
|
335
335
|
ENTITY_FACTORY,
|
|
336
|
+
ENTITY_CONSTRUCTION_DOCK,
|
|
336
337
|
ENTITY_WORKSHOP,
|
|
337
338
|
ENTITY_CONTAINER,
|
|
338
339
|
ENTITY_NEXUS,
|
|
@@ -351,6 +352,7 @@ export {
|
|
|
351
352
|
isWarehouse,
|
|
352
353
|
isExtractor,
|
|
353
354
|
isFactory,
|
|
355
|
+
isConstructionDock,
|
|
354
356
|
isWorkshop,
|
|
355
357
|
isContainer,
|
|
356
358
|
isNexus,
|
|
@@ -95,20 +95,24 @@ export class ConstructionManager extends BaseManager {
|
|
|
95
95
|
now: Date
|
|
96
96
|
): Map<string, InboundTransfer[]> {
|
|
97
97
|
const buckets = new Map<string, Map<string, InboundTransfer>>()
|
|
98
|
+
const entitiesById = new Map(entities.map((entity) => [entity.id.toString(), entity]))
|
|
98
99
|
const nowMs = now.getTime()
|
|
99
100
|
for (const entity of entities) {
|
|
100
|
-
const entityIdStr = entity.id.toString()
|
|
101
|
-
const sourceName = entity.entity_name || entityIdStr
|
|
102
101
|
for (const lane of getLanes(entity)) {
|
|
103
102
|
const startedMs = lane.schedule.started.toDate().getTime()
|
|
104
103
|
let cumulativeSec = 0
|
|
105
104
|
for (const task of lane.schedule.tasks) {
|
|
106
105
|
cumulativeSec += task.duration.toNumber()
|
|
107
|
-
if (!
|
|
108
|
-
|
|
106
|
+
if (!isDeliveryTask(task)) continue
|
|
107
|
+
const target = deliveryTarget(task)
|
|
108
|
+
if (!target) continue
|
|
109
|
+
const source = deliverySource(task, entity, entitiesById)
|
|
110
|
+
if (!source) continue
|
|
109
111
|
const projectedEndMs = startedMs + cumulativeSec * 1000
|
|
110
112
|
if (projectedEndMs < nowMs) continue
|
|
111
|
-
const targetIdStr =
|
|
113
|
+
const targetIdStr = target.entity_id.toString()
|
|
114
|
+
const sourceIdStr = source.id.toString()
|
|
115
|
+
const sourceName = source.entity_name || sourceIdStr
|
|
112
116
|
const etaSeconds = Math.max(0, Math.round((projectedEndMs - nowMs) / 1000))
|
|
113
117
|
let perTarget = buckets.get(targetIdStr)
|
|
114
118
|
if (!perTarget) {
|
|
@@ -119,15 +123,15 @@ export class ConstructionManager extends BaseManager {
|
|
|
119
123
|
const itemId = c.item_id.toNumber()
|
|
120
124
|
const quantity = c.quantity.toNumber()
|
|
121
125
|
if (quantity === 0) continue
|
|
122
|
-
const key = `${
|
|
126
|
+
const key = `${sourceIdStr}#${itemId}`
|
|
123
127
|
const existing = perTarget.get(key)
|
|
124
128
|
if (existing) {
|
|
125
129
|
existing.quantity += quantity
|
|
126
130
|
existing.etaSeconds = Math.min(existing.etaSeconds, etaSeconds)
|
|
127
131
|
} else {
|
|
128
132
|
perTarget.set(key, {
|
|
129
|
-
sourceEntityId:
|
|
130
|
-
sourceEntityType:
|
|
133
|
+
sourceEntityId: source.id,
|
|
134
|
+
sourceEntityType: source.type,
|
|
131
135
|
sourceName,
|
|
132
136
|
itemId,
|
|
133
137
|
quantity,
|
|
@@ -363,7 +367,34 @@ function partitionSources(
|
|
|
363
367
|
return {eligible, unreachable}
|
|
364
368
|
}
|
|
365
369
|
|
|
366
|
-
function
|
|
370
|
+
function isDeliveryTask(task: ServerContract.Types.task): boolean {
|
|
371
|
+
const type = task.type.toNumber()
|
|
372
|
+
return type === TaskType.UNLOAD || type === TaskType.SHUTTLE
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function deliveryTarget(
|
|
376
|
+
task: ServerContract.Types.task
|
|
377
|
+
): ServerContract.Types.entity_ref | undefined {
|
|
378
|
+
if (task.type.toNumber() === TaskType.SHUTTLE) {
|
|
379
|
+
return task.couplings.find((coupling) => coupling.kind.toNumber() === HoldKind.PUSH)
|
|
380
|
+
?.counterpart
|
|
381
|
+
}
|
|
382
|
+
return task.couplings[0]?.counterpart
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function deliverySource(
|
|
386
|
+
task: ServerContract.Types.task,
|
|
387
|
+
transporter: ServerContract.Types.entity_info,
|
|
388
|
+
entitiesById: ReadonlyMap<string, ServerContract.Types.entity_info>
|
|
389
|
+
): ServerContract.Types.entity_info | undefined {
|
|
390
|
+
if (task.type.toNumber() !== TaskType.SHUTTLE) return transporter
|
|
391
|
+
const source = task.couplings.find(
|
|
392
|
+
(coupling) => coupling.kind.toNumber() === HoldKind.PULL
|
|
393
|
+
)?.counterpart
|
|
394
|
+
return source ? entitiesById.get(source.entity_id.toString()) : undefined
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function isReservingPushTask(task: ServerContract.Types.task): boolean {
|
|
367
398
|
return task.type.toNumber() === TaskType.UNLOAD
|
|
368
399
|
}
|
|
369
400
|
|
|
@@ -378,7 +409,7 @@ function isBuildOfPlot(task: ServerContract.Types.task, plotId: UInt64): boolean
|
|
|
378
409
|
function reservationsOf(source: ServerContract.Types.entity_info): Reservation[] {
|
|
379
410
|
const out = new Map<string, Reservation>()
|
|
380
411
|
for (const task of getTasks(source)) {
|
|
381
|
-
if (!
|
|
412
|
+
if (!isReservingPushTask(task)) continue
|
|
382
413
|
if (task.couplings.length === 0) continue
|
|
383
414
|
const targetType = task.couplings[0].counterpart.entity_type
|
|
384
415
|
const targetId = task.couplings[0].counterpart.entity_id
|
package/src/nft/description.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import {
|
|
15
15
|
ITEM_CONTAINER_T1_PACKED,
|
|
16
16
|
ITEM_CONTAINER_T2_PACKED,
|
|
17
|
+
ITEM_CONSTRUCTION_DOCK_T1_PACKED,
|
|
17
18
|
ITEM_BUILDER_T1,
|
|
18
19
|
ITEM_CRAFTER_T1,
|
|
19
20
|
ITEM_ENGINE_T1,
|
|
@@ -170,6 +171,8 @@ export function entityDisplayName(itemId: number): string {
|
|
|
170
171
|
return 'Mining Rig'
|
|
171
172
|
case ITEM_FACTORY_T1_PACKED:
|
|
172
173
|
return 'Factory'
|
|
174
|
+
case ITEM_CONSTRUCTION_DOCK_T1_PACKED:
|
|
175
|
+
return 'Construction Dock'
|
|
173
176
|
case ITEM_CONTAINER_T1_PACKED:
|
|
174
177
|
return 'Container'
|
|
175
178
|
case ITEM_CONTAINER_T2_PACKED:
|
|
@@ -317,6 +320,7 @@ export function buildEntityDescription(
|
|
|
317
320
|
} else if (
|
|
318
321
|
itemId === ITEM_EXTRACTOR_T1_PACKED ||
|
|
319
322
|
itemId === ITEM_FACTORY_T1_PACKED ||
|
|
323
|
+
itemId === ITEM_CONSTRUCTION_DOCK_T1_PACKED ||
|
|
320
324
|
itemId === ITEM_CONTAINER_T1_PACKED ||
|
|
321
325
|
itemId === ITEM_CONTAINER_T2_PACKED
|
|
322
326
|
) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {Name, type UInt16, UInt32, UInt64} from '@wharfkit/antelope'
|
|
2
2
|
import {ServerContract} from '../contracts'
|
|
3
|
-
import {Coordinates,
|
|
3
|
+
import {Coordinates, TaskType} from '../types'
|
|
4
4
|
import {
|
|
5
5
|
capsHasMovement,
|
|
6
6
|
capsHasStorage,
|
|
@@ -402,13 +402,6 @@ function applyCraftTask(projected: ProjectedEntity, task: ServerContract.Types.t
|
|
|
402
402
|
}
|
|
403
403
|
}
|
|
404
404
|
|
|
405
|
-
// Mirrors calc_task_effect's TASK_REFIT: only ADD has a projectable cargo effect (REMOVE/SWAP return mass the entity row holds, invisible to the task itself).
|
|
406
|
-
function applyRefitTask(projected: ProjectedEntity, task: ServerContract.Types.task): void {
|
|
407
|
-
if (task.refit?.op.toNumber() === RefitOp.ADD) {
|
|
408
|
-
removeCargoItem(projected, task.cargo[0])
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
405
|
function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task): void {
|
|
413
406
|
switch (task.type.toNumber()) {
|
|
414
407
|
case TaskType.RECHARGE:
|
|
@@ -432,9 +425,6 @@ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task):
|
|
|
432
425
|
case TaskType.CRAFT:
|
|
433
426
|
applyCraftTask(projected, task)
|
|
434
427
|
break
|
|
435
|
-
case TaskType.REFIT:
|
|
436
|
-
applyRefitTask(projected, task)
|
|
437
|
-
break
|
|
438
428
|
case TaskType.UNDEPLOY:
|
|
439
429
|
case TaskType.DEMOLISH:
|
|
440
430
|
break
|
|
@@ -591,9 +581,6 @@ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity
|
|
|
591
581
|
case TaskType.CRAFT:
|
|
592
582
|
if (taskComplete) applyCraftTask(projected, task)
|
|
593
583
|
break
|
|
594
|
-
case TaskType.REFIT:
|
|
595
|
-
if (taskComplete) applyRefitTask(projected, task)
|
|
596
|
-
break
|
|
597
584
|
case TaskType.UNDEPLOY:
|
|
598
585
|
case TaskType.DEMOLISH:
|
|
599
586
|
break
|
|
@@ -50,10 +50,13 @@ function hold(kind: number) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
describe('isCapperTaskType', () => {
|
|
53
|
-
test('UNDEPLOY, DEMOLISH
|
|
53
|
+
test('UNDEPLOY, DEMOLISH are cappers', () => {
|
|
54
54
|
expect(isCapperTaskType(TaskType.UNDEPLOY)).toBe(true)
|
|
55
55
|
expect(isCapperTaskType(TaskType.DEMOLISH)).toBe(true)
|
|
56
|
-
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('REFIT is not a capper', () => {
|
|
59
|
+
expect(isCapperTaskType(TaskType.REFIT)).toBe(false)
|
|
57
60
|
})
|
|
58
61
|
|
|
59
62
|
test('non-capper task types are not cappers', () => {
|
|
@@ -71,8 +74,8 @@ describe('hasPendingCapper', () => {
|
|
|
71
74
|
expect(hasPendingCapper(entity([task({type: TaskType.TRAVEL})]))).toBe(false)
|
|
72
75
|
})
|
|
73
76
|
|
|
74
|
-
test('true when a
|
|
75
|
-
const e = entity([task({type: TaskType.TRAVEL}), task({type: TaskType.
|
|
77
|
+
test('true when a DEMOLISH task is queued anywhere in the lane', () => {
|
|
78
|
+
const e = entity([task({type: TaskType.TRAVEL}), task({type: TaskType.DEMOLISH})])
|
|
76
79
|
expect(hasPendingCapper(e)).toBe(true)
|
|
77
80
|
})
|
|
78
81
|
|
|
@@ -88,18 +91,18 @@ describe('hasResolvable — capper gating', () => {
|
|
|
88
91
|
})
|
|
89
92
|
|
|
90
93
|
test('a completed capper front with no holds is resolvable', () => {
|
|
91
|
-
const e = entity([task({type: TaskType.
|
|
94
|
+
const e = entity([task({type: TaskType.DEMOLISH, duration: 30})])
|
|
92
95
|
expect(hasResolvable(e, NOW)).toBe(true)
|
|
93
96
|
})
|
|
94
97
|
|
|
95
98
|
test('a completed capper front with a live hold is gated', () => {
|
|
96
|
-
const e = entity([task({type: TaskType.
|
|
99
|
+
const e = entity([task({type: TaskType.DEMOLISH, duration: 30})], [hold(2)])
|
|
97
100
|
expect(hasResolvable(e, NOW)).toBe(false)
|
|
98
101
|
})
|
|
99
102
|
|
|
100
103
|
test('a capper front clears once holds empty', () => {
|
|
101
|
-
const withHold = entity([task({type: TaskType.
|
|
102
|
-
const cleared = entity([task({type: TaskType.
|
|
104
|
+
const withHold = entity([task({type: TaskType.DEMOLISH, duration: 30})], [hold(2)])
|
|
105
|
+
const cleared = entity([task({type: TaskType.DEMOLISH, duration: 30})], [])
|
|
103
106
|
expect(hasResolvable(withHold, NOW)).toBe(false)
|
|
104
107
|
expect(hasResolvable(cleared, NOW)).toBe(true)
|
|
105
108
|
})
|
|
@@ -110,7 +113,7 @@ describe('hasResolvable — capper gating', () => {
|
|
|
110
113
|
})
|
|
111
114
|
|
|
112
115
|
test('an in-progress front is not resolvable regardless of holds', () => {
|
|
113
|
-
const e = entity([task({type: TaskType.
|
|
116
|
+
const e = entity([task({type: TaskType.DEMOLISH, duration: 3600})])
|
|
114
117
|
expect(hasResolvable(e, NOW)).toBe(false)
|
|
115
118
|
})
|
|
116
119
|
})
|
|
@@ -63,13 +63,9 @@ export function isIdle(entity: ScheduleData): boolean {
|
|
|
63
63
|
return !hasSchedule(entity) && !hasHolds(entity)
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
// Mirrors is_capper_task_type: demolish/undeploy
|
|
66
|
+
// Mirrors is_capper_task_type: demolish/undeploy cap a plan — no further appends once queued.
|
|
67
67
|
export function isCapperTaskType(taskType: number): boolean {
|
|
68
|
-
return
|
|
69
|
-
taskType === TaskType.UNDEPLOY ||
|
|
70
|
-
taskType === TaskType.DEMOLISH ||
|
|
71
|
-
taskType === TaskType.REFIT
|
|
72
|
-
)
|
|
68
|
+
return taskType === TaskType.UNDEPLOY || taskType === TaskType.DEMOLISH
|
|
73
69
|
}
|
|
74
70
|
|
|
75
71
|
export function hasPendingCapper(entity: ScheduleData): boolean {
|