@shipload/sdk 1.0.0-next.71 → 1.0.0-next.73
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 +115 -27
- package/lib/shipload.js +806 -697
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +795 -694
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +21 -11
- package/lib/testing.js +272 -339
- package/lib/testing.js.map +1 -1
- package/lib/testing.m.js +272 -339
- package/lib/testing.m.js.map +1 -1
- package/package.json +1 -1
- package/src/contracts/server.ts +69 -26
- package/src/data/capability-formulas.ts +4 -4
- package/src/data/catalog.test.ts +81 -1
- package/src/data/catalog.ts +6 -4
- package/src/data/entities.json +84 -68
- package/src/data/item-ids.ts +5 -4
- package/src/data/items.json +30 -24
- package/src/data/kind-registry.json +75 -39
- package/src/data/kind-registry.ts +13 -0
- package/src/data/metadata.ts +203 -334
- package/src/data/recipes.json +194 -155
- package/src/data/task-types.json +184 -0
- package/src/derivation/build-methods.test.ts +4 -4
- package/src/derivation/capabilities.test.ts +14 -10
- package/src/derivation/capabilities.ts +8 -10
- package/src/derivation/capability-mappings.ts +4 -4
- package/src/derivation/capacity-fn-registry.test.ts +9 -0
- package/src/derivation/capacity-fn-registry.ts +18 -0
- package/src/derivation/recipe-usage.test.ts +7 -11
- package/src/entities/makers.ts +2 -2
- package/src/index-module.ts +15 -1
- package/src/influence/influence.test.ts +1 -1
- package/src/managers/actions.ts +17 -0
- package/src/managers/context.ts +9 -0
- package/src/managers/fund.ts +49 -0
- package/src/managers/index.ts +3 -0
- package/src/nft/description.ts +5 -10
- package/src/resolution/resolve-item.test.ts +15 -10
- package/src/resolution/resolve-item.ts +2 -2
- package/src/scheduling/unwrap.test.ts +60 -0
- package/src/scheduling/unwrap.ts +24 -8
- package/src/shipload.ts +5 -0
- package/src/types.ts +1 -0
|
@@ -4,7 +4,7 @@ import {getRecipe, type Recipe} from '../data/recipes-runtime'
|
|
|
4
4
|
import {getItem} from '../data/catalog'
|
|
5
5
|
import {
|
|
6
6
|
ITEM_ENGINE_T1,
|
|
7
|
-
|
|
7
|
+
ITEM_EXTRACTOR_T2_PACKED,
|
|
8
8
|
ITEM_GENERATOR_T1,
|
|
9
9
|
ITEM_GATHERER_T1,
|
|
10
10
|
ITEM_LOADER_T1,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
ITEM_BATTERY_T1,
|
|
17
17
|
ITEM_SHIP_T1_PACKED,
|
|
18
18
|
ITEM_CONTAINER_T1_PACKED,
|
|
19
|
-
|
|
19
|
+
ITEM_WAREHOUSE_T2_PACKED,
|
|
20
20
|
ITEM_CONTAINER_T2_PACKED,
|
|
21
21
|
} from '../data/item-ids'
|
|
22
22
|
import type {StatMapping, CapabilityAttributeRow} from '../data/capabilities'
|
|
@@ -35,8 +35,8 @@ export const KIND_TO_ITEM_ID: Record<SlotConsumerKind, number> = {
|
|
|
35
35
|
battery: ITEM_BATTERY_T1,
|
|
36
36
|
'ship-t1': ITEM_SHIP_T1_PACKED,
|
|
37
37
|
'container-t1': ITEM_CONTAINER_T1_PACKED,
|
|
38
|
-
'warehouse-
|
|
39
|
-
'extractor-
|
|
38
|
+
'warehouse-t2': ITEM_WAREHOUSE_T2_PACKED,
|
|
39
|
+
'extractor-t2': ITEM_EXTRACTOR_T2_PACKED,
|
|
40
40
|
'container-t2': ITEM_CONTAINER_T2_PACKED,
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import {expect, test} from 'bun:test'
|
|
2
|
+
import {resolveCapacityFnName} from './capacity-fn-registry'
|
|
3
|
+
import kindRegistryJson from '../data/kind-registry.json'
|
|
4
|
+
|
|
5
|
+
test('every template in the catalog resolves to a known capacity function', () => {
|
|
6
|
+
for (const t of kindRegistryJson.templates as {itemId: number}[]) {
|
|
7
|
+
expect(() => resolveCapacityFnName(t.itemId)).not.toThrow()
|
|
8
|
+
}
|
|
9
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {getBaseCapacityFnName} from '../data/kind-registry'
|
|
2
|
+
|
|
3
|
+
export const CAPACITY_FN_NAMES = ['ship', 'warehouse', 'depot', 'container', 'workshop'] as const
|
|
4
|
+
|
|
5
|
+
export type CapacityFnName = (typeof CAPACITY_FN_NAMES)[number]
|
|
6
|
+
|
|
7
|
+
function isCapacityFnName(name: string): name is CapacityFnName {
|
|
8
|
+
return (CAPACITY_FN_NAMES as readonly string[]).includes(name)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function resolveCapacityFnName(itemId: number): CapacityFnName | undefined {
|
|
12
|
+
const name = getBaseCapacityFnName(itemId)
|
|
13
|
+
if (!name) return undefined
|
|
14
|
+
if (!isCapacityFnName(name)) {
|
|
15
|
+
throw new Error(`unrecognized base capacity function "${name}" for item ${itemId}`)
|
|
16
|
+
}
|
|
17
|
+
return name
|
|
18
|
+
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from './recipe-usage'
|
|
8
8
|
import {
|
|
9
9
|
ITEM_SENSOR,
|
|
10
|
+
ITEM_SENSOR_T2,
|
|
10
11
|
ITEM_RESIN,
|
|
11
12
|
ITEM_FRAME,
|
|
12
13
|
ITEM_PLATE,
|
|
@@ -14,7 +15,7 @@ import {
|
|
|
14
15
|
ITEM_GATHERER_T1,
|
|
15
16
|
ITEM_CRAFTER_T1,
|
|
16
17
|
ITEM_BUILDER_T1,
|
|
17
|
-
|
|
18
|
+
ITEM_EXTRACTOR_T2_PACKED,
|
|
18
19
|
ITEM_ROUSTABOUT_T1A_PACKED,
|
|
19
20
|
ITEM_PROSPECTOR_T1A_PACKED,
|
|
20
21
|
ITEM_TENDER_T1A_PACKED,
|
|
@@ -32,12 +33,7 @@ test('getRecipeConsumers lists every recipe that consumes Sensor', () => {
|
|
|
32
33
|
const consumers = getRecipeConsumers(ITEM_SENSOR)
|
|
33
34
|
const ids = consumers.map((c) => c.outputItemId).sort((a, b) => a - b)
|
|
34
35
|
expect(ids).toEqual(
|
|
35
|
-
[
|
|
36
|
-
ITEM_CRAFTER_T1,
|
|
37
|
-
ITEM_BUILDER_T1,
|
|
38
|
-
ITEM_EXTRACTOR_T1_PACKED,
|
|
39
|
-
ITEM_ROUSTABOUT_T1A_PACKED,
|
|
40
|
-
].sort((a, b) => a - b)
|
|
36
|
+
[ITEM_CRAFTER_T1, ITEM_BUILDER_T1, ITEM_ROUSTABOUT_T1A_PACKED].sort((a, b) => a - b)
|
|
41
37
|
)
|
|
42
38
|
})
|
|
43
39
|
|
|
@@ -52,8 +48,8 @@ test('Frame feeds the gatherer drain stat', () => {
|
|
|
52
48
|
})
|
|
53
49
|
|
|
54
50
|
test('Sensor is a mass-only sink in the Extractor recipe', () => {
|
|
55
|
-
const consumers = getRecipeConsumers(
|
|
56
|
-
const extractor = consumers.find((c) => c.outputItemId ===
|
|
51
|
+
const consumers = getRecipeConsumers(ITEM_SENSOR_T2)
|
|
52
|
+
const extractor = consumers.find((c) => c.outputItemId === ITEM_EXTRACTOR_T2_PACKED)
|
|
57
53
|
expect(extractor).toBeDefined()
|
|
58
54
|
expect(extractor?.statFlows).toHaveLength(0)
|
|
59
55
|
})
|
|
@@ -80,9 +76,9 @@ test('getResourceDemand scales by quantity', () => {
|
|
|
80
76
|
expect(getResourceDemand(ITEM_PLATE, 3)).toEqual({ore: 30})
|
|
81
77
|
})
|
|
82
78
|
|
|
83
|
-
test('getComponentDemand reports Resin as consumed by
|
|
79
|
+
test('getComponentDemand reports Resin as consumed by seven recipes', () => {
|
|
84
80
|
const demand = getComponentDemand()
|
|
85
81
|
const resin = demand.find((d) => d.itemId === ITEM_RESIN)
|
|
86
82
|
expect(resin).toBeDefined()
|
|
87
|
-
expect(resin?.consumerCount).toBe(
|
|
83
|
+
expect(resin?.consumerCount).toBe(7)
|
|
88
84
|
})
|
package/src/entities/makers.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {getKindMeta, getTemplateMeta} from '../data/kind-registry'
|
|
|
6
6
|
import type {EntityTypeName} from '../data/kind-registry'
|
|
7
7
|
import {getEntityLayout} from '../data/recipes-runtime'
|
|
8
8
|
import type {EntitySlot} from '../data/recipes-runtime'
|
|
9
|
-
import {
|
|
9
|
+
import {getItemFamily} from '../data/metadata'
|
|
10
10
|
import {getItem} from '../data/catalog'
|
|
11
11
|
import {getModuleCapabilityType, moduleAccepts, moduleSlotTypeToCode} from '../capabilities/modules'
|
|
12
12
|
import {computeEntityCapabilities} from '../derivation/capabilities'
|
|
@@ -52,7 +52,7 @@ function assignModulesToSlots(
|
|
|
52
52
|
try {
|
|
53
53
|
modName = getItem(mod.itemId).name
|
|
54
54
|
} catch {
|
|
55
|
-
modName =
|
|
55
|
+
modName = getItemFamily(mod.itemId)?.name ?? `item ${mod.itemId}`
|
|
56
56
|
}
|
|
57
57
|
throw new Error(
|
|
58
58
|
`No compatible slot for module ${modName} (type ${modType}) on ${entityLabel}`
|
package/src/index-module.ts
CHANGED
|
@@ -57,6 +57,7 @@ export {
|
|
|
57
57
|
computeFreeCells,
|
|
58
58
|
NftManager,
|
|
59
59
|
BalancesManager,
|
|
60
|
+
FundManager,
|
|
60
61
|
ConstructionManager,
|
|
61
62
|
JobsManager,
|
|
62
63
|
InfluenceManager,
|
|
@@ -80,6 +81,9 @@ export type {
|
|
|
80
81
|
NftConfigForItem,
|
|
81
82
|
WrapConfig,
|
|
82
83
|
WrapGate,
|
|
84
|
+
FundBeneficiary,
|
|
85
|
+
FundToken,
|
|
86
|
+
FundAccrued,
|
|
83
87
|
DepositConfig,
|
|
84
88
|
PlatformBalance,
|
|
85
89
|
TokenBalance,
|
|
@@ -120,6 +124,8 @@ export {
|
|
|
120
124
|
categoryFromIndex,
|
|
121
125
|
categoryLabelFromIndex,
|
|
122
126
|
} from './data/catalog'
|
|
127
|
+
export {getItemFamily, itemFamilies, itemFamilyKey} from './data/metadata'
|
|
128
|
+
export type {ItemFamily} from './data/metadata'
|
|
123
129
|
export {getCurrentEpoch, getEpochInfo} from './scheduling/epoch'
|
|
124
130
|
export type {EpochInfo} from './scheduling/epoch'
|
|
125
131
|
export {
|
|
@@ -370,12 +376,18 @@ export {
|
|
|
370
376
|
derivedLoaders,
|
|
371
377
|
estimateUnwrapDuration,
|
|
372
378
|
incomingHoldMass,
|
|
379
|
+
projectCargomass,
|
|
373
380
|
projectedPeakCargomass,
|
|
374
381
|
receiveFits,
|
|
375
382
|
unwrapLoadDuration,
|
|
376
383
|
unwrapTransitDuration,
|
|
377
384
|
} from './scheduling/unwrap'
|
|
378
|
-
export type {
|
|
385
|
+
export type {
|
|
386
|
+
CargomassProjection,
|
|
387
|
+
DerivedLoaders,
|
|
388
|
+
UnwrapDestination,
|
|
389
|
+
UnwrapItem,
|
|
390
|
+
} from './scheduling/unwrap'
|
|
379
391
|
|
|
380
392
|
export {
|
|
381
393
|
projectedCargoAvailableAt,
|
|
@@ -413,6 +425,7 @@ export {
|
|
|
413
425
|
ENTITY_NEXUS,
|
|
414
426
|
ENTITY_HUB,
|
|
415
427
|
ENTITY_DEPOT,
|
|
428
|
+
ENTITY_ASSEMBLY_YARD,
|
|
416
429
|
getEntityClass,
|
|
417
430
|
getPackedEntityType,
|
|
418
431
|
getKindMeta,
|
|
@@ -438,6 +451,7 @@ export {
|
|
|
438
451
|
isPlot,
|
|
439
452
|
isHub,
|
|
440
453
|
isDepot,
|
|
454
|
+
isAssemblyYard,
|
|
441
455
|
} from './data/kind-registry'
|
|
442
456
|
export type {EntityTypeName, KindMeta, TemplateMeta} from './data/kind-registry'
|
|
443
457
|
export * from './capabilities'
|
|
@@ -136,7 +136,7 @@ describe('decomposition', () => {
|
|
|
136
136
|
for (let i = 1; i < DECOMP_REGISTRY.length; i++) {
|
|
137
137
|
expect(DECOMP_REGISTRY[i].itemId).toBeGreaterThan(DECOMP_REGISTRY[i - 1].itemId)
|
|
138
138
|
}
|
|
139
|
-
expect(DECOMP_REGISTRY.length).toBe(
|
|
139
|
+
expect(DECOMP_REGISTRY.length).toBe(57)
|
|
140
140
|
})
|
|
141
141
|
|
|
142
142
|
test('the retired Workshop has no decomposition entry', () => {
|
package/src/managers/actions.ts
CHANGED
|
@@ -610,6 +610,18 @@ export class ActionsManager extends BaseManager {
|
|
|
610
610
|
})
|
|
611
611
|
}
|
|
612
612
|
|
|
613
|
+
blenddepot(
|
|
614
|
+
depotId: UInt64Type,
|
|
615
|
+
owner: NameType,
|
|
616
|
+
inputs: ServerContract.ActionParams.Type.cargo_item[]
|
|
617
|
+
): Action {
|
|
618
|
+
return this.server.action('blenddepot', {
|
|
619
|
+
depot_id: UInt64.from(depotId),
|
|
620
|
+
owner: Name.from(owner),
|
|
621
|
+
inputs,
|
|
622
|
+
})
|
|
623
|
+
}
|
|
624
|
+
|
|
613
625
|
deploy(
|
|
614
626
|
entityId: UInt64Type,
|
|
615
627
|
ref: ServerContract.ActionParams.Type.cargo_ref,
|
|
@@ -1038,6 +1050,11 @@ export class ActionsManager extends BaseManager {
|
|
|
1038
1050
|
})
|
|
1039
1051
|
}
|
|
1040
1052
|
|
|
1053
|
+
/** Pays every nonzero accrued token balance to the beneficiary. */
|
|
1054
|
+
claimFund(beneficiary: NameType): Action {
|
|
1055
|
+
return this.fund.action('claim', {beneficiary_account: Name.from(beneficiary)})
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1041
1058
|
collectFund(): Action {
|
|
1042
1059
|
return this.fund.action('collect', {})
|
|
1043
1060
|
}
|
package/src/managers/context.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {ActionsManager} from './actions'
|
|
|
13
13
|
import {ClusterManager} from './cluster'
|
|
14
14
|
import {NftManager} from './nft'
|
|
15
15
|
import {BalancesManager} from './balances'
|
|
16
|
+
import {FundManager} from './fund'
|
|
16
17
|
import {JobsManager} from './jobs'
|
|
17
18
|
import {InfluenceManager} from './influence'
|
|
18
19
|
import {SubscriptionsManager} from '../subscriptions/manager'
|
|
@@ -27,6 +28,7 @@ export class GameContext {
|
|
|
27
28
|
private _clusters?: ClusterManager
|
|
28
29
|
private _nft?: NftManager
|
|
29
30
|
private _balances?: BalancesManager
|
|
31
|
+
private _funds?: FundManager
|
|
30
32
|
private _jobs?: JobsManager
|
|
31
33
|
private _influence?: InfluenceManager
|
|
32
34
|
private _subscriptions?: SubscriptionsManager
|
|
@@ -106,6 +108,13 @@ export class GameContext {
|
|
|
106
108
|
return this._balances
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
get funds(): FundManager {
|
|
112
|
+
if (!this._funds) {
|
|
113
|
+
this._funds = new FundManager(this)
|
|
114
|
+
}
|
|
115
|
+
return this._funds
|
|
116
|
+
}
|
|
117
|
+
|
|
109
118
|
get jobs(): JobsManager {
|
|
110
119
|
if (!this._jobs) {
|
|
111
120
|
this._jobs = new JobsManager(this)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {type Asset, Name, type NameType, type UInt16} from '@wharfkit/antelope'
|
|
2
|
+
import type {FundContract} from '../contracts'
|
|
3
|
+
import type {PlatformBalance} from './balances'
|
|
4
|
+
import {BaseManager} from './base'
|
|
5
|
+
|
|
6
|
+
export interface FundBeneficiary {
|
|
7
|
+
account: Name
|
|
8
|
+
bps: UInt16
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface FundToken {
|
|
12
|
+
tokenContract: Name
|
|
13
|
+
symbol: Asset.Symbol
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface FundAccrued {
|
|
17
|
+
tokenContract: Name
|
|
18
|
+
balance: Asset
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Live fund ledger reads. Token custody can exceed the amounts accrued to beneficiaries. */
|
|
22
|
+
export class FundManager extends BaseManager {
|
|
23
|
+
async getBeneficiaries(): Promise<FundBeneficiary[]> {
|
|
24
|
+
const rows = (await this.fund
|
|
25
|
+
.table('benefs', this.fund.account)
|
|
26
|
+
.all()) as FundContract.Types.benef_row[]
|
|
27
|
+
return rows.map((row) => ({account: row.account, bps: row.bps}))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async getTokens(): Promise<FundToken[]> {
|
|
31
|
+
const rows = (await this.fund
|
|
32
|
+
.table('tokens', this.fund.account)
|
|
33
|
+
.all()) as FundContract.Types.token_row[]
|
|
34
|
+
return rows.map((row) => ({tokenContract: row.token_contract, symbol: row.token_symbol}))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Includes zero rows and claims for tokens no longer accepted; no rows returns []. */
|
|
38
|
+
async getAccrued(beneficiary: NameType): Promise<FundAccrued[]> {
|
|
39
|
+
const rows = (await this.fund
|
|
40
|
+
.table('accrued', Name.from(beneficiary))
|
|
41
|
+
.all()) as FundContract.Types.accrued_row[]
|
|
42
|
+
return rows.map((row) => ({tokenContract: row.token_contract, balance: row.balance}))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Platform balances that have not reached the fund's accrual ledger yet. */
|
|
46
|
+
async getUncollected(): Promise<PlatformBalance[]> {
|
|
47
|
+
return this.context.balances.getPlatformBalances(this.fund.account)
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/managers/index.ts
CHANGED
package/src/nft/description.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {MASS_UNITS_PER_TONNE, type ModuleType} from '../types'
|
|
|
32
32
|
import {ENTITY_SHIP, getPackedEntityType} from '../data/kind-registry'
|
|
33
33
|
import {getBaseHullmassFor} from '../derivation/capabilities'
|
|
34
34
|
import {computeEffectiveModuleStat} from '../derivation/stat-scaling'
|
|
35
|
+
import {resolveCapacityFnName, type CapacityFnName} from '../derivation/capacity-fn-registry'
|
|
35
36
|
|
|
36
37
|
function idiv(a: number, b: number): number {
|
|
37
38
|
return Math.floor(a / b)
|
|
@@ -89,24 +90,18 @@ export function computeBaseCapacityDepot(stats: bigint): number {
|
|
|
89
90
|
return Math.floor(500_000 * 6 ** (s / 1998))
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
const
|
|
93
|
+
const CAPACITY_FN_BY_NAME: Record<CapacityFnName, (stats: bigint) => number> = {
|
|
93
94
|
ship: computeBaseCapacityShip,
|
|
94
95
|
warehouse: computeBaseCapacityWarehouse,
|
|
95
96
|
workshop: computeBaseCapacityWorkshop,
|
|
96
|
-
extractor: computeBaseCapacityContainer,
|
|
97
|
-
factory: computeBaseCapacityContainer,
|
|
98
|
-
builddock: computeBaseCapacityContainer,
|
|
99
|
-
mdriver: computeBaseCapacityContainer,
|
|
100
|
-
mcatcher: computeBaseCapacityContainer,
|
|
101
97
|
container: computeBaseCapacityContainer,
|
|
102
|
-
nexus: computeBaseCapacityContainer,
|
|
103
98
|
depot: computeBaseCapacityDepot,
|
|
104
99
|
}
|
|
105
100
|
|
|
106
101
|
export function computeBaseCapacityForEntity(itemId: number, stats: bigint): number {
|
|
107
|
-
const
|
|
108
|
-
if (!
|
|
109
|
-
return
|
|
102
|
+
const fnName = resolveCapacityFnName(itemId)
|
|
103
|
+
if (!fnName) return 0
|
|
104
|
+
return CAPACITY_FN_BY_NAME[fnName](stats)
|
|
110
105
|
}
|
|
111
106
|
|
|
112
107
|
export const computeEngineThrust = (vol: number, tier: number): number =>
|
|
@@ -2,11 +2,16 @@ import {expect, test} from 'bun:test'
|
|
|
2
2
|
import {UInt16, UInt64} from '@wharfkit/antelope'
|
|
3
3
|
import {resolveItem} from './resolve-item'
|
|
4
4
|
import {encodeStats} from '../derivation/crafting'
|
|
5
|
-
import {computeBaseHullmass, computeContainerCapabilities} from '../derivation/capabilities'
|
|
6
5
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
computeBaseHullmass,
|
|
7
|
+
computeContainerCapabilities,
|
|
8
|
+
applyCapacityTier,
|
|
9
|
+
} from '../derivation/capabilities'
|
|
10
|
+
import {getItem} from '../data/catalog'
|
|
11
|
+
import {
|
|
12
|
+
ITEM_EXTRACTOR_T2_PACKED,
|
|
13
|
+
ITEM_FACTORY_T2_PACKED,
|
|
14
|
+
ITEM_HUB_T2_PACKED,
|
|
10
15
|
ITEM_MASS_DRIVER_T1_PACKED,
|
|
11
16
|
ITEM_MASS_CATCHER_T1_PACKED,
|
|
12
17
|
} from '../data/item-ids'
|
|
@@ -16,8 +21,8 @@ function hullStats(strength: number, density: number, hardness: number): UInt64
|
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
const CONTAINER_ENTITIES = [
|
|
19
|
-
['factory',
|
|
20
|
-
['extractor',
|
|
24
|
+
['factory', ITEM_FACTORY_T2_PACKED],
|
|
25
|
+
['extractor', ITEM_EXTRACTOR_T2_PACKED],
|
|
21
26
|
['mass driver', ITEM_MASS_DRIVER_T1_PACKED],
|
|
22
27
|
['mass catcher', ITEM_MASS_CATCHER_T1_PACKED],
|
|
23
28
|
] as const
|
|
@@ -28,21 +33,21 @@ for (const [label, itemId] of CONTAINER_ENTITIES) {
|
|
|
28
33
|
const resolved = resolveItem(UInt16.from(itemId), stats)
|
|
29
34
|
const hull = resolved.attributes?.find((g) => g.capability === 'Hull')
|
|
30
35
|
const capacity = hull?.attributes.find((a) => a.label === 'Capacity')?.value
|
|
31
|
-
const
|
|
36
|
+
const base = computeContainerCapabilities({
|
|
32
37
|
strength: 300,
|
|
33
38
|
hardness: 400,
|
|
34
39
|
density: 100,
|
|
35
40
|
}).capacity
|
|
36
|
-
expect(capacity).toBe(
|
|
41
|
+
expect(capacity).toBe(applyCapacityTier(base, getItem(itemId).tier))
|
|
37
42
|
})
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
test('resolveItem resolves the station hub with hullmass and zero cargo capacity', () => {
|
|
41
46
|
const stats = hullStats(300, 100, 400)
|
|
42
|
-
const resolved = resolveItem(UInt16.from(
|
|
47
|
+
const resolved = resolveItem(UInt16.from(ITEM_HUB_T2_PACKED), stats)
|
|
43
48
|
const hull = resolved.attributes?.find((g) => g.capability === 'Hull')
|
|
44
49
|
expect(hull?.attributes.find((a) => a.label === 'Mass')?.value).toBe(
|
|
45
|
-
computeBaseHullmass(
|
|
50
|
+
computeBaseHullmass(ITEM_HUB_T2_PACKED, {density: 100})
|
|
46
51
|
)
|
|
47
52
|
expect(hull?.attributes.find((a) => a.label === 'Capacity')?.value).toBe(0)
|
|
48
53
|
})
|
|
@@ -4,7 +4,7 @@ import type {ResourceCategory} from '../types'
|
|
|
4
4
|
import {getItem, getModules} from '../data/catalog'
|
|
5
5
|
import {ENTITY_SHIP, getPackedEntityType} from '../data/kind-registry'
|
|
6
6
|
import {getEntityLayout} from '../data/recipes-runtime'
|
|
7
|
-
import {entityMetadata,
|
|
7
|
+
import {entityMetadata, getItemFamily} from '../data/metadata'
|
|
8
8
|
import {
|
|
9
9
|
getModuleCapabilityType,
|
|
10
10
|
isModuleItem,
|
|
@@ -370,7 +370,7 @@ function resolveEntity(
|
|
|
370
370
|
modName = modItem.name
|
|
371
371
|
modTier = modItem.tier
|
|
372
372
|
} catch {
|
|
373
|
-
modName =
|
|
373
|
+
modName = getItemFamily(modItemId)?.name ?? 'Module'
|
|
374
374
|
}
|
|
375
375
|
const group = computeCapabilityGroup(modType, decodedStats, modTier, slot.outputPct)
|
|
376
376
|
return {
|
|
@@ -5,8 +5,14 @@ import {
|
|
|
5
5
|
unwrapLoadDuration,
|
|
6
6
|
estimateUnwrapDuration,
|
|
7
7
|
incomingHoldMass,
|
|
8
|
+
projectCargomass,
|
|
8
9
|
projectedPeakCargomass,
|
|
9
10
|
} from './unwrap'
|
|
11
|
+
import {TimePoint, UInt8, UInt16, UInt32, UInt64} from '@wharfkit/antelope'
|
|
12
|
+
import {ServerContract} from '../contracts'
|
|
13
|
+
import {getItem} from '../data/catalog'
|
|
14
|
+
import {ITEM_BEAM} from '../data/item-ids'
|
|
15
|
+
import {TaskType} from '../types'
|
|
10
16
|
|
|
11
17
|
describe('unwrap duration mirror', () => {
|
|
12
18
|
test('derivedLoaders aggregates lanes like derived_loaders()', () => {
|
|
@@ -84,3 +90,57 @@ test('projectedPeakCargomass tracks the running peak from cargomass', () => {
|
|
|
84
90
|
// No pending tasks: peak = base + candidate add.
|
|
85
91
|
expect(projectedPeakCargomass(entity, new Date(0), 500)).toBe(1500)
|
|
86
92
|
})
|
|
93
|
+
|
|
94
|
+
const DEPOSIT_START = new Date('2026-09-20T07:48:37.000Z')
|
|
95
|
+
const BEAM_QTY = 2_500
|
|
96
|
+
const BEAM_MASS = getItem(ITEM_BEAM).mass
|
|
97
|
+
|
|
98
|
+
function unloadingShip(projectedAtMs?: number) {
|
|
99
|
+
const cargo = ServerContract.Types.cargo_item.from({
|
|
100
|
+
item_id: UInt16.from(ITEM_BEAM),
|
|
101
|
+
quantity: UInt32.from(BEAM_QTY),
|
|
102
|
+
stats: UInt64.from(0),
|
|
103
|
+
modules: [],
|
|
104
|
+
})
|
|
105
|
+
const lane = ServerContract.Types.lane.from({
|
|
106
|
+
lane_key: UInt8.from(0),
|
|
107
|
+
schedule: ServerContract.Types.schedule.from({
|
|
108
|
+
started: TimePoint.fromMilliseconds(DEPOSIT_START.getTime()),
|
|
109
|
+
tasks: [
|
|
110
|
+
ServerContract.Types.task.from({
|
|
111
|
+
type: UInt8.from(TaskType.CIVIC_DEPOSIT),
|
|
112
|
+
duration: UInt32.from(1_309),
|
|
113
|
+
cancelable: UInt8.from(2),
|
|
114
|
+
cargo: [cargo],
|
|
115
|
+
couplings: [],
|
|
116
|
+
}),
|
|
117
|
+
],
|
|
118
|
+
}),
|
|
119
|
+
})
|
|
120
|
+
return {
|
|
121
|
+
cargomass: BEAM_QTY * BEAM_MASS,
|
|
122
|
+
lanes: [lane],
|
|
123
|
+
holds: [],
|
|
124
|
+
projected_at:
|
|
125
|
+
projectedAtMs === undefined ? undefined : TimePoint.fromMilliseconds(projectedAtMs),
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const UNLOAD_ENDS = new Date(DEPOSIT_START.getTime() + 1_309_000)
|
|
130
|
+
const AFTER_UNLOAD = new Date(UNLOAD_ENDS.getTime() + 60_000)
|
|
131
|
+
|
|
132
|
+
test('projectCargomass reports the mass held at the candidate and the peak around it', () => {
|
|
133
|
+
const plan = projectCargomass(unloadingShip(), AFTER_UNLOAD, 400 * BEAM_MASS)
|
|
134
|
+
// The queued unload empties the hold before the candidate lands.
|
|
135
|
+
expect(plan.used).toBe(400 * BEAM_MASS)
|
|
136
|
+
expect(plan.peak).toBe(BEAM_QTY * BEAM_MASS)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test('a task already applied to a projected row is not replayed', () => {
|
|
140
|
+
const applied = unloadingShip(UNLOAD_ENDS.getTime())
|
|
141
|
+
applied.cargomass = 0
|
|
142
|
+
const plan = projectCargomass(applied, AFTER_UNLOAD, 400 * BEAM_MASS)
|
|
143
|
+
expect(plan.used).toBe(400 * BEAM_MASS)
|
|
144
|
+
expect(plan.peak).toBe(400 * BEAM_MASS)
|
|
145
|
+
expect(projectedPeakCargomass(applied, AFTER_UNLOAD, 400 * BEAM_MASS)).toBe(plan.peak)
|
|
146
|
+
})
|
package/src/scheduling/unwrap.ts
CHANGED
|
@@ -133,28 +133,44 @@ function cargoListMass(items: CargoItem[]): number {
|
|
|
133
133
|
return m
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
export
|
|
137
|
-
|
|
136
|
+
export interface CargomassProjection {
|
|
137
|
+
used: number
|
|
138
|
+
peak: number
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function projectCargomass(
|
|
142
|
+
entity: sched.AnchoredScheduleData & {cargomass: number | {toNumber(): number}},
|
|
138
143
|
at: Date,
|
|
139
144
|
addMass: number,
|
|
140
145
|
removeMass = 0
|
|
141
|
-
):
|
|
142
|
-
const events: {t: number; delta: number}[] = []
|
|
143
|
-
for (const ordered of sched.
|
|
146
|
+
): CargomassProjection {
|
|
147
|
+
const events: {t: number; delta: number; candidate: boolean}[] = []
|
|
148
|
+
for (const ordered of sched.unappliedTasks(entity)) {
|
|
144
149
|
const eff = taskCargoEffect(ordered.task)
|
|
145
150
|
const delta = cargoListMass(eff.added) - cargoListMass(eff.removed)
|
|
146
|
-
events.push({t: ordered.completesAt.getTime(), delta})
|
|
151
|
+
events.push({t: ordered.completesAt.getTime(), delta, candidate: false})
|
|
147
152
|
}
|
|
148
|
-
events.push({t: at.getTime(), delta: addMass - removeMass})
|
|
153
|
+
events.push({t: at.getTime(), delta: addMass - removeMass, candidate: true})
|
|
149
154
|
events.sort((a, b) => (a.t !== b.t ? a.t - b.t : b.delta - a.delta))
|
|
150
155
|
let running = Number(entity.cargomass)
|
|
151
156
|
let peak = running
|
|
157
|
+
let used = running
|
|
152
158
|
for (const e of events) {
|
|
153
159
|
running += e.delta
|
|
154
160
|
if (running < 0) running = 0
|
|
155
161
|
if (running > peak) peak = running
|
|
162
|
+
if (e.candidate) used = running
|
|
156
163
|
}
|
|
157
|
-
return Math.min(peak, 0xffff_ffff)
|
|
164
|
+
return {used: Math.min(used, 0xffff_ffff), peak: Math.min(peak, 0xffff_ffff)}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function projectedPeakCargomass(
|
|
168
|
+
entity: sched.AnchoredScheduleData & {cargomass: number | {toNumber(): number}},
|
|
169
|
+
at: Date,
|
|
170
|
+
addMass: number,
|
|
171
|
+
removeMass = 0
|
|
172
|
+
): number {
|
|
173
|
+
return projectCargomass(entity, at, addMass, removeMass).peak
|
|
158
174
|
}
|
|
159
175
|
|
|
160
176
|
export function receiveFits(
|
package/src/shipload.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type {ActionsManager} from './managers/actions'
|
|
|
13
13
|
import type {ClusterManager} from './managers/cluster'
|
|
14
14
|
import type {NftManager} from './managers/nft'
|
|
15
15
|
import type {BalancesManager} from './managers/balances'
|
|
16
|
+
import type {FundManager} from './managers/fund'
|
|
16
17
|
import type {JobsManager} from './managers/jobs'
|
|
17
18
|
import type {InfluenceManager} from './managers/influence'
|
|
18
19
|
import type {SubscriptionsManager} from './subscriptions/manager'
|
|
@@ -154,6 +155,10 @@ export class Shipload {
|
|
|
154
155
|
return this._context.nft
|
|
155
156
|
}
|
|
156
157
|
|
|
158
|
+
get funds(): FundManager {
|
|
159
|
+
return this._context.funds
|
|
160
|
+
}
|
|
161
|
+
|
|
157
162
|
get balances(): BalancesManager {
|
|
158
163
|
return this._context.balances
|
|
159
164
|
}
|