@shipload/sdk 1.0.0-next.42 → 1.0.0-next.43
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 +82 -1
- package/lib/shipload.js +180 -146
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +170 -147
- package/lib/shipload.m.js.map +1 -1
- package/package.json +1 -1
- package/src/data/capabilities.ts +8 -0
- package/src/data/recipes.json +0 -142
- package/src/derivation/capability-mappings.ts +49 -4
- package/src/index-module.ts +16 -1
- package/src/managers/actions.ts +17 -0
- package/src/scheduling/unwrap.test.ts +60 -0
- package/src/scheduling/unwrap.ts +187 -0
package/package.json
CHANGED
package/src/data/capabilities.ts
CHANGED
|
@@ -8,6 +8,14 @@ export interface StatMapping {
|
|
|
8
8
|
stat: string
|
|
9
9
|
capability: string
|
|
10
10
|
attribute: string
|
|
11
|
+
source: string // producing module/role, always present
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CapabilityAttributeRow {
|
|
15
|
+
capability: string
|
|
16
|
+
attribute: string
|
|
17
|
+
description: string
|
|
18
|
+
source?: string // producing module/role; absent when no formula-derived producer exists
|
|
11
19
|
}
|
|
12
20
|
|
|
13
21
|
export const capabilityNames: string[] = [
|
package/src/data/recipes.json
CHANGED
|
@@ -875,147 +875,5 @@
|
|
|
875
875
|
}
|
|
876
876
|
],
|
|
877
877
|
"blendWeights": []
|
|
878
|
-
},
|
|
879
|
-
{
|
|
880
|
-
"outputItemId": 20001,
|
|
881
|
-
"outputMass": 500,
|
|
882
|
-
"inputs": [
|
|
883
|
-
{
|
|
884
|
-
"itemId": 10001,
|
|
885
|
-
"quantity": 200
|
|
886
|
-
},
|
|
887
|
-
{
|
|
888
|
-
"itemId": 102,
|
|
889
|
-
"quantity": 15
|
|
890
|
-
}
|
|
891
|
-
],
|
|
892
|
-
"statSlots": [
|
|
893
|
-
{
|
|
894
|
-
"sources": [
|
|
895
|
-
{
|
|
896
|
-
"inputIndex": 0,
|
|
897
|
-
"statIndex": 0
|
|
898
|
-
},
|
|
899
|
-
{
|
|
900
|
-
"inputIndex": 1,
|
|
901
|
-
"statIndex": 0
|
|
902
|
-
}
|
|
903
|
-
]
|
|
904
|
-
},
|
|
905
|
-
{
|
|
906
|
-
"sources": [
|
|
907
|
-
{
|
|
908
|
-
"inputIndex": 0,
|
|
909
|
-
"statIndex": 1
|
|
910
|
-
},
|
|
911
|
-
{
|
|
912
|
-
"inputIndex": 1,
|
|
913
|
-
"statIndex": 2
|
|
914
|
-
}
|
|
915
|
-
]
|
|
916
|
-
}
|
|
917
|
-
],
|
|
918
|
-
"blendWeights": [
|
|
919
|
-
1,
|
|
920
|
-
1
|
|
921
|
-
]
|
|
922
|
-
},
|
|
923
|
-
{
|
|
924
|
-
"outputItemId": 20002,
|
|
925
|
-
"outputMass": 300,
|
|
926
|
-
"inputs": [
|
|
927
|
-
{
|
|
928
|
-
"itemId": 10002,
|
|
929
|
-
"quantity": 200
|
|
930
|
-
},
|
|
931
|
-
{
|
|
932
|
-
"itemId": 402,
|
|
933
|
-
"quantity": 10
|
|
934
|
-
},
|
|
935
|
-
{
|
|
936
|
-
"itemId": 502,
|
|
937
|
-
"quantity": 20
|
|
938
|
-
}
|
|
939
|
-
],
|
|
940
|
-
"statSlots": [
|
|
941
|
-
{
|
|
942
|
-
"sources": [
|
|
943
|
-
{
|
|
944
|
-
"inputIndex": 0,
|
|
945
|
-
"statIndex": 0
|
|
946
|
-
},
|
|
947
|
-
{
|
|
948
|
-
"inputIndex": 1,
|
|
949
|
-
"statIndex": 1
|
|
950
|
-
}
|
|
951
|
-
]
|
|
952
|
-
},
|
|
953
|
-
{
|
|
954
|
-
"sources": [
|
|
955
|
-
{
|
|
956
|
-
"inputIndex": 0,
|
|
957
|
-
"statIndex": 1
|
|
958
|
-
},
|
|
959
|
-
{
|
|
960
|
-
"inputIndex": 2,
|
|
961
|
-
"statIndex": 2
|
|
962
|
-
}
|
|
963
|
-
]
|
|
964
|
-
}
|
|
965
|
-
],
|
|
966
|
-
"blendWeights": [
|
|
967
|
-
1,
|
|
968
|
-
1,
|
|
969
|
-
1
|
|
970
|
-
]
|
|
971
|
-
},
|
|
972
|
-
{
|
|
973
|
-
"outputItemId": 20200,
|
|
974
|
-
"outputMass": 80000,
|
|
975
|
-
"inputs": [
|
|
976
|
-
{
|
|
977
|
-
"itemId": 20001,
|
|
978
|
-
"quantity": 600
|
|
979
|
-
},
|
|
980
|
-
{
|
|
981
|
-
"itemId": 20002,
|
|
982
|
-
"quantity": 200
|
|
983
|
-
}
|
|
984
|
-
],
|
|
985
|
-
"statSlots": [
|
|
986
|
-
{
|
|
987
|
-
"sources": [
|
|
988
|
-
{
|
|
989
|
-
"inputIndex": 0,
|
|
990
|
-
"statIndex": 0
|
|
991
|
-
}
|
|
992
|
-
]
|
|
993
|
-
},
|
|
994
|
-
{
|
|
995
|
-
"sources": [
|
|
996
|
-
{
|
|
997
|
-
"inputIndex": 0,
|
|
998
|
-
"statIndex": 1
|
|
999
|
-
}
|
|
1000
|
-
]
|
|
1001
|
-
},
|
|
1002
|
-
{
|
|
1003
|
-
"sources": [
|
|
1004
|
-
{
|
|
1005
|
-
"inputIndex": 1,
|
|
1006
|
-
"statIndex": 0
|
|
1007
|
-
}
|
|
1008
|
-
]
|
|
1009
|
-
},
|
|
1010
|
-
{
|
|
1011
|
-
"sources": [
|
|
1012
|
-
{
|
|
1013
|
-
"inputIndex": 1,
|
|
1014
|
-
"statIndex": 1
|
|
1015
|
-
}
|
|
1016
|
-
]
|
|
1017
|
-
}
|
|
1018
|
-
],
|
|
1019
|
-
"blendWeights": []
|
|
1020
878
|
}
|
|
1021
879
|
]
|
|
@@ -18,7 +18,8 @@ import {
|
|
|
18
18
|
ITEM_WAREHOUSE_T1_PACKED,
|
|
19
19
|
ITEM_CONTAINER_T2_PACKED,
|
|
20
20
|
} from '../data/item-ids'
|
|
21
|
-
import type {StatMapping} from '../data/capabilities'
|
|
21
|
+
import type {StatMapping, CapabilityAttributeRow} from '../data/capabilities'
|
|
22
|
+
import {capabilityAttributes} from '../data/capabilities'
|
|
22
23
|
|
|
23
24
|
export const KIND_TO_ITEM_ID: Record<SlotConsumerKind, number> = {
|
|
24
25
|
engine: ITEM_ENGINE_T1,
|
|
@@ -69,6 +70,12 @@ function traceToRawCategoryStat(
|
|
|
69
70
|
return traceToRawCategoryStat(subRecipe, subSource, nextVisited)
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
// Producing role for a capability·attribute: entity hull slots all roll up to "Hull"; modules use their own name.
|
|
74
|
+
export function sourceLabelForOutput(itemId: number): string {
|
|
75
|
+
const item = getItem(itemId)
|
|
76
|
+
return item.type === 'entity' ? 'Hull' : item.name
|
|
77
|
+
}
|
|
78
|
+
|
|
72
79
|
let cached: StatMapping[] | undefined
|
|
73
80
|
|
|
74
81
|
export function deriveStatMappings(): StatMapping[] {
|
|
@@ -82,20 +89,22 @@ export function deriveStatMappings(): StatMapping[] {
|
|
|
82
89
|
const itemId = KIND_TO_ITEM_ID[kind]
|
|
83
90
|
const recipe = getRecipe(itemId)
|
|
84
91
|
if (!recipe) continue
|
|
92
|
+
const source = sourceLabelForOutput(itemId)
|
|
85
93
|
for (const [slotIdxStr, consumer] of Object.entries(slots)) {
|
|
86
94
|
const slotIdx = Number(slotIdxStr)
|
|
87
95
|
const slot = recipe.statSlots[slotIdx]
|
|
88
96
|
if (!slot) continue
|
|
89
|
-
for (const
|
|
90
|
-
const stat = traceToRawCategoryStat(recipe,
|
|
97
|
+
for (const src of slot.sources) {
|
|
98
|
+
const stat = traceToRawCategoryStat(recipe, src)
|
|
91
99
|
if (!stat) continue
|
|
92
|
-
const key = `${stat.label}|${consumer.capability}|${consumer.attribute}`
|
|
100
|
+
const key = `${stat.label}|${consumer.capability}|${consumer.attribute}|${source}`
|
|
93
101
|
if (seen.has(key)) continue
|
|
94
102
|
seen.add(key)
|
|
95
103
|
out.push({
|
|
96
104
|
stat: stat.label,
|
|
97
105
|
capability: consumer.capability,
|
|
98
106
|
attribute: consumer.attribute,
|
|
107
|
+
source,
|
|
99
108
|
})
|
|
100
109
|
}
|
|
101
110
|
}
|
|
@@ -115,3 +124,39 @@ export function getStatMappingsForStat(stat: string): StatMapping[] {
|
|
|
115
124
|
export function getStatMappingsForCapability(capability: string): StatMapping[] {
|
|
116
125
|
return deriveStatMappings().filter((m) => m.capability === capability)
|
|
117
126
|
}
|
|
127
|
+
|
|
128
|
+
export function getProducersForAttribute(capability: string, attribute: string): string[] {
|
|
129
|
+
const seen = new Set<string>()
|
|
130
|
+
const out: string[] = []
|
|
131
|
+
for (const m of deriveStatMappings()) {
|
|
132
|
+
if (m.capability !== capability || m.attribute !== attribute) continue
|
|
133
|
+
if (seen.has(m.source)) continue
|
|
134
|
+
seen.add(m.source)
|
|
135
|
+
out.push(m.source)
|
|
136
|
+
}
|
|
137
|
+
return out
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function getCapabilityAttributeRows(): CapabilityAttributeRow[] {
|
|
141
|
+
const rows: CapabilityAttributeRow[] = []
|
|
142
|
+
for (const ca of capabilityAttributes) {
|
|
143
|
+
const producers = getProducersForAttribute(ca.capability, ca.attribute)
|
|
144
|
+
if (producers.length === 0) {
|
|
145
|
+
rows.push({
|
|
146
|
+
capability: ca.capability,
|
|
147
|
+
attribute: ca.attribute,
|
|
148
|
+
description: ca.description,
|
|
149
|
+
})
|
|
150
|
+
continue
|
|
151
|
+
}
|
|
152
|
+
for (const source of producers) {
|
|
153
|
+
rows.push({
|
|
154
|
+
capability: ca.capability,
|
|
155
|
+
attribute: ca.attribute,
|
|
156
|
+
description: ca.description,
|
|
157
|
+
source,
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return rows
|
|
162
|
+
}
|
package/src/index-module.ts
CHANGED
|
@@ -283,6 +283,17 @@ export type {
|
|
|
283
283
|
CancelEligibilityInput,
|
|
284
284
|
} from './scheduling/cancel'
|
|
285
285
|
|
|
286
|
+
export {
|
|
287
|
+
derivedLoaders,
|
|
288
|
+
estimateUnwrapDuration,
|
|
289
|
+
incomingHoldMass,
|
|
290
|
+
projectedPeakCargomass,
|
|
291
|
+
receiveFits,
|
|
292
|
+
unwrapLoadDuration,
|
|
293
|
+
unwrapTransitDuration,
|
|
294
|
+
} from './scheduling/unwrap'
|
|
295
|
+
export type {DerivedLoaders, UnwrapDestination, UnwrapItem} from './scheduling/unwrap'
|
|
296
|
+
|
|
286
297
|
export {
|
|
287
298
|
projectedCargoAvailableAt,
|
|
288
299
|
availableForItem,
|
|
@@ -348,13 +359,16 @@ export {
|
|
|
348
359
|
isInvertedAttribute,
|
|
349
360
|
getCapabilityAttributes,
|
|
350
361
|
} from './data/capabilities'
|
|
351
|
-
export type {CapabilityAttribute, StatMapping} from './data/capabilities'
|
|
362
|
+
export type {CapabilityAttribute, StatMapping, CapabilityAttributeRow} from './data/capabilities'
|
|
352
363
|
|
|
353
364
|
export {
|
|
354
365
|
deriveStatMappings,
|
|
355
366
|
getStatMappings,
|
|
356
367
|
getStatMappingsForStat,
|
|
357
368
|
getStatMappingsForCapability,
|
|
369
|
+
getProducersForAttribute,
|
|
370
|
+
getCapabilityAttributeRows,
|
|
371
|
+
sourceLabelForOutput,
|
|
358
372
|
} from './derivation/capability-mappings'
|
|
359
373
|
export {SLOT_FORMULAS} from './data/capability-formulas'
|
|
360
374
|
export type {SlotConsumer, SlotConsumerKind} from './data/capability-formulas'
|
|
@@ -422,6 +436,7 @@ export {
|
|
|
422
436
|
feistelInv,
|
|
423
437
|
regionOf,
|
|
424
438
|
partnerRegion,
|
|
439
|
+
nearbyWormholes,
|
|
425
440
|
wormholeAt,
|
|
426
441
|
wormholeAtRegionEndpoint,
|
|
427
442
|
isValidWormholePair,
|
package/src/managers/actions.ts
CHANGED
|
@@ -593,6 +593,23 @@ export class ActionsManager extends BaseManager {
|
|
|
593
593
|
)
|
|
594
594
|
}
|
|
595
595
|
|
|
596
|
+
sendAsset(owner: NameType, recipient: NameType, assetId: UInt64Type, memo = ''): Action {
|
|
597
|
+
return Action.from(
|
|
598
|
+
{
|
|
599
|
+
account: this.atomicAssetsAccount,
|
|
600
|
+
name: 'transfer',
|
|
601
|
+
authorization: [{actor: Name.from(owner), permission: 'active'}],
|
|
602
|
+
data: {
|
|
603
|
+
from: Name.from(owner),
|
|
604
|
+
to: Name.from(recipient),
|
|
605
|
+
asset_ids: [UInt64.from(assetId)],
|
|
606
|
+
memo,
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
ATOMICASSETS_ABI
|
|
610
|
+
)
|
|
611
|
+
}
|
|
612
|
+
|
|
596
613
|
// Two top-level actions the wallet signs to unwrap an NFT into a host's cargo.
|
|
597
614
|
unwrapCargoTx(owner: NameType, assetId: UInt64Type, hostId: UInt64Type): Action[] {
|
|
598
615
|
return [this.transferForUnwrap(owner, assetId), this.placecargo(owner, hostId, assetId)]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {describe, expect, test} from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
derivedLoaders,
|
|
4
|
+
unwrapTransitDuration,
|
|
5
|
+
unwrapLoadDuration,
|
|
6
|
+
estimateUnwrapDuration,
|
|
7
|
+
incomingHoldMass,
|
|
8
|
+
projectedPeakCargomass,
|
|
9
|
+
} from './unwrap'
|
|
10
|
+
|
|
11
|
+
describe('unwrap duration mirror', () => {
|
|
12
|
+
test('derivedLoaders aggregates lanes like derived_loaders()', () => {
|
|
13
|
+
expect(derivedLoaders([])).toBeNull()
|
|
14
|
+
expect(
|
|
15
|
+
derivedLoaders([
|
|
16
|
+
{mass: 1000, thrust: 10},
|
|
17
|
+
{mass: 1400, thrust: 20},
|
|
18
|
+
])
|
|
19
|
+
).toEqual({mass: 1200, thrust: 30, quantity: 2}) // floor(2400/2)=1200, sum thrust, count
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('transit floors distance then flight time', () => {
|
|
23
|
+
// distance = floor(sqrt(3^2+4^2)*10000)=50000; accel=400/mass*10000; flight=floor(2*sqrt(d/accel))
|
|
24
|
+
const mass = 1000
|
|
25
|
+
const accel = (400 / mass) * 10000
|
|
26
|
+
const expected = Math.floor(2 * Math.sqrt(50000 / accel))
|
|
27
|
+
expect(unwrapTransitDuration(mass, {x: 0, y: 0}, {x: 3, y: 4})).toBe(expected)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('load uses altitude z, adds loader mass, divides by quantity', () => {
|
|
31
|
+
const loaders = {mass: 1200, thrust: 30, quantity: 2}
|
|
32
|
+
const itemMass = 800
|
|
33
|
+
const accel = (30 / (itemMass + 1200)) * 10000
|
|
34
|
+
const flight = Math.floor(2 * Math.sqrt(3000 / accel))
|
|
35
|
+
expect(unwrapLoadDuration(loaders, itemMass, 3000)).toBe(Math.floor(flight / 2))
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('zero item mass and no loaders are safe', () => {
|
|
39
|
+
expect(unwrapTransitDuration(0, {x: 0, y: 0}, {x: 9, y: 9})).toBe(0)
|
|
40
|
+
expect(unwrapLoadDuration(null, 500, 3000)).toBe(0)
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('incomingHoldMass sums incoming-kind hold mass', () => {
|
|
45
|
+
expect(incomingHoldMass([])).toBe(0)
|
|
46
|
+
// PUSH(2) + FLIGHT(5) count; BUILD(4) does not
|
|
47
|
+
expect(
|
|
48
|
+
incomingHoldMass([
|
|
49
|
+
{kind: 2, incoming_mass: 100},
|
|
50
|
+
{kind: 4, incoming_mass: 999},
|
|
51
|
+
{kind: 5, incoming_mass: 50},
|
|
52
|
+
])
|
|
53
|
+
).toBe(150)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('projectedPeakCargomass tracks the running peak from cargomass', () => {
|
|
57
|
+
const entity = {cargomass: 1000, lanes: [], cargo: [], schedule: undefined} as never
|
|
58
|
+
// No pending tasks: peak = base + candidate add.
|
|
59
|
+
expect(projectedPeakCargomass(entity, new Date(0), 500)).toBe(1500)
|
|
60
|
+
})
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import type {UInt16Type, UInt32Type} from '@wharfkit/antelope'
|
|
2
|
+
import {calcCargoItemMass} from '../capabilities/storage'
|
|
3
|
+
import type {ServerContract} from '../contracts'
|
|
4
|
+
import {PRECISION} from '../types'
|
|
5
|
+
import * as sched from './schedule'
|
|
6
|
+
import {taskCargoEffect} from './availability'
|
|
7
|
+
import {candidateLaneCompletesAt} from './lanes'
|
|
8
|
+
|
|
9
|
+
const NFT_TRANSIT_THRUST = 400
|
|
10
|
+
|
|
11
|
+
export interface DerivedLoaders {
|
|
12
|
+
mass: number
|
|
13
|
+
thrust: number
|
|
14
|
+
quantity: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function derivedLoaders(
|
|
18
|
+
lanes: {mass: UInt32Type | number; thrust: UInt16Type | number}[] | undefined
|
|
19
|
+
): DerivedLoaders | null {
|
|
20
|
+
if (!lanes || lanes.length === 0) return null
|
|
21
|
+
let totalMass = 0
|
|
22
|
+
let totalThrust = 0
|
|
23
|
+
for (const l of lanes) {
|
|
24
|
+
totalMass += Number(l.mass)
|
|
25
|
+
totalThrust += Number(l.thrust)
|
|
26
|
+
}
|
|
27
|
+
const count = lanes.length
|
|
28
|
+
return {
|
|
29
|
+
mass: Math.floor(totalMass / count),
|
|
30
|
+
thrust: Math.min(totalThrust, 65_535),
|
|
31
|
+
quantity: count,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function acceleration(thrust: number, mass: number): number {
|
|
36
|
+
if (mass <= 0) return 0
|
|
37
|
+
return (thrust / mass) * PRECISION
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function flightTime(distance: number, accel: number): number {
|
|
41
|
+
if (accel <= 0 || distance <= 0) return 0
|
|
42
|
+
return Math.floor(2 * Math.sqrt(distance / accel))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function distance2d(ax: number, ay: number, bx: number, by: number): number {
|
|
46
|
+
const dx = ax - bx
|
|
47
|
+
const dy = ay - by
|
|
48
|
+
return Math.floor(Math.sqrt(dx * dx + dy * dy) * PRECISION)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function unwrapTransitDuration(
|
|
52
|
+
itemMass: number,
|
|
53
|
+
origin: {x: number; y: number},
|
|
54
|
+
dest: {x: number; y: number}
|
|
55
|
+
): number {
|
|
56
|
+
if (itemMass <= 0) return 0
|
|
57
|
+
return flightTime(
|
|
58
|
+
distance2d(origin.x, origin.y, dest.x, dest.y),
|
|
59
|
+
acceleration(NFT_TRANSIT_THRUST, itemMass)
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function unwrapLoadDuration(
|
|
64
|
+
loaders: DerivedLoaders | null,
|
|
65
|
+
itemMass: number,
|
|
66
|
+
destZ: number
|
|
67
|
+
): number {
|
|
68
|
+
if (!loaders || itemMass <= 0) return 0
|
|
69
|
+
const total = itemMass + loaders.mass
|
|
70
|
+
const flight = flightTime(destZ, acceleration(loaders.thrust, total))
|
|
71
|
+
return Math.floor(flight / loaders.quantity)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface UnwrapItem {
|
|
75
|
+
itemId: number
|
|
76
|
+
quantity: number
|
|
77
|
+
modules: ServerContract.Types.module_entry[]
|
|
78
|
+
originX: number
|
|
79
|
+
originY: number
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface UnwrapDestination {
|
|
83
|
+
loader_lanes?: {mass: UInt32Type | number; thrust: UInt16Type | number}[]
|
|
84
|
+
coordinates: {x: UInt32Type | number; y: UInt32Type | number; z?: UInt32Type | number}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function estimateUnwrapDuration(dest: UnwrapDestination, item: UnwrapItem): number {
|
|
88
|
+
const itemMass = Number(
|
|
89
|
+
calcCargoItemMass({
|
|
90
|
+
item_id: item.itemId as never,
|
|
91
|
+
quantity: item.quantity as never,
|
|
92
|
+
modules: item.modules,
|
|
93
|
+
})
|
|
94
|
+
)
|
|
95
|
+
const loaders = derivedLoaders(dest.loader_lanes)
|
|
96
|
+
const dz = Number(dest.coordinates.z ?? 0)
|
|
97
|
+
const load = unwrapLoadDuration(loaders, itemMass, dz)
|
|
98
|
+
const transit = unwrapTransitDuration(
|
|
99
|
+
itemMass,
|
|
100
|
+
{x: item.originX, y: item.originY},
|
|
101
|
+
{
|
|
102
|
+
x: Number(dest.coordinates.x),
|
|
103
|
+
y: Number(dest.coordinates.y),
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
return load + transit
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Hold kinds that count as incoming (mirror is_incoming_hold_kind in holds.hpp).
|
|
110
|
+
const INCOMING_HOLD_KINDS = new Set<number>([2, 3, 5])
|
|
111
|
+
|
|
112
|
+
export function incomingHoldMass(
|
|
113
|
+
holds:
|
|
114
|
+
| {kind: number | {toNumber(): number}; incoming_mass: number | {toNumber(): number}}[]
|
|
115
|
+
| undefined
|
|
116
|
+
): number {
|
|
117
|
+
if (!holds) return 0
|
|
118
|
+
let total = 0
|
|
119
|
+
for (const h of holds) {
|
|
120
|
+
if (INCOMING_HOLD_KINDS.has(Number(h.kind))) total += Number(h.incoming_mass)
|
|
121
|
+
}
|
|
122
|
+
return total
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
type CargoItem = ServerContract.Types.cargo_item
|
|
126
|
+
|
|
127
|
+
function cargoListMass(items: CargoItem[]): number {
|
|
128
|
+
let m = 0
|
|
129
|
+
for (const it of items) m += Number(calcCargoItemMass(it))
|
|
130
|
+
return m
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function projectedPeakCargomass(
|
|
134
|
+
entity: sched.ScheduleData & {cargomass: number | {toNumber(): number}},
|
|
135
|
+
at: Date,
|
|
136
|
+
addMass: number,
|
|
137
|
+
removeMass = 0
|
|
138
|
+
): number {
|
|
139
|
+
const events: {t: number; delta: number}[] = []
|
|
140
|
+
for (const ordered of sched.orderedTasks(entity)) {
|
|
141
|
+
const eff = taskCargoEffect(ordered.task)
|
|
142
|
+
const delta = cargoListMass(eff.added) - cargoListMass(eff.removed)
|
|
143
|
+
events.push({t: ordered.completesAt.getTime(), delta})
|
|
144
|
+
}
|
|
145
|
+
events.push({t: at.getTime(), delta: addMass - removeMass})
|
|
146
|
+
events.sort((a, b) => (a.t !== b.t ? a.t - b.t : b.delta - a.delta))
|
|
147
|
+
let running = Number(entity.cargomass)
|
|
148
|
+
let peak = running
|
|
149
|
+
for (const e of events) {
|
|
150
|
+
running += e.delta
|
|
151
|
+
if (running < 0) running = 0
|
|
152
|
+
if (running > peak) peak = running
|
|
153
|
+
}
|
|
154
|
+
return Math.min(peak, 0xffff_ffff)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function receiveFits(
|
|
158
|
+
dest: UnwrapDestination &
|
|
159
|
+
sched.ScheduleData & {
|
|
160
|
+
cargomass: number | {toNumber(): number}
|
|
161
|
+
capacity?: number | {toNumber(): number}
|
|
162
|
+
holds?: {
|
|
163
|
+
kind: number | {toNumber(): number}
|
|
164
|
+
incoming_mass: number | {toNumber(): number}
|
|
165
|
+
}[]
|
|
166
|
+
},
|
|
167
|
+
item: UnwrapItem,
|
|
168
|
+
now: Date
|
|
169
|
+
): boolean {
|
|
170
|
+
const capacity = Number(dest.capacity ?? 0)
|
|
171
|
+
if (capacity <= 0) return false
|
|
172
|
+
const itemMass = Number(
|
|
173
|
+
calcCargoItemMass({
|
|
174
|
+
item_id: item.itemId as never,
|
|
175
|
+
quantity: item.quantity as never,
|
|
176
|
+
modules: item.modules,
|
|
177
|
+
})
|
|
178
|
+
)
|
|
179
|
+
const duration = estimateUnwrapDuration(dest, item)
|
|
180
|
+
const candidateCompletes = candidateLaneCompletesAt(dest, sched.LANE_MOBILITY, duration, now)
|
|
181
|
+
const peak = projectedPeakCargomass(
|
|
182
|
+
dest,
|
|
183
|
+
candidateCompletes,
|
|
184
|
+
itemMass + incomingHoldMass(dest.holds)
|
|
185
|
+
)
|
|
186
|
+
return peak <= capacity
|
|
187
|
+
}
|