@shipload/sdk 1.0.0-next.70 → 1.0.0-next.72

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 (42) hide show
  1. package/lib/shipload.d.ts +266 -101
  2. package/lib/shipload.js +898 -404
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +880 -398
  5. package/lib/shipload.m.js.map +1 -1
  6. package/lib/testing.d.ts +51 -13
  7. package/lib/testing.js +209 -64
  8. package/lib/testing.js.map +1 -1
  9. package/lib/testing.m.js +209 -64
  10. package/lib/testing.m.js.map +1 -1
  11. package/package.json +1 -1
  12. package/src/contracts/server.ts +140 -34
  13. package/src/data/capability-formulas.ts +4 -4
  14. package/src/data/entities.json +84 -68
  15. package/src/data/item-ids.ts +5 -4
  16. package/src/data/items.json +30 -24
  17. package/src/data/kind-registry.json +75 -39
  18. package/src/data/kind-registry.ts +13 -0
  19. package/src/data/metadata.ts +35 -0
  20. package/src/data/recipes.json +194 -155
  21. package/src/derivation/build-methods.test.ts +4 -4
  22. package/src/derivation/capabilities.test.ts +14 -10
  23. package/src/derivation/capabilities.ts +8 -10
  24. package/src/derivation/capability-mappings.ts +4 -4
  25. package/src/derivation/capacity-fn-registry.test.ts +9 -0
  26. package/src/derivation/capacity-fn-registry.ts +18 -0
  27. package/src/derivation/recipe-usage.test.ts +7 -11
  28. package/src/index-module.ts +28 -8
  29. package/src/influence/influence.test.ts +1 -1
  30. package/src/managers/actions.ts +19 -4
  31. package/src/managers/context.ts +9 -0
  32. package/src/managers/fund.ts +49 -0
  33. package/src/managers/index.ts +3 -0
  34. package/src/managers/jobs.test.ts +25 -9
  35. package/src/managers/jobs.ts +8 -4
  36. package/src/nft/description.ts +5 -10
  37. package/src/receipts.ts +9 -9
  38. package/src/resolution/resolve-item.test.ts +15 -10
  39. package/src/scheduling/jobs.test.ts +417 -18
  40. package/src/scheduling/jobs.ts +222 -16
  41. package/src/scheduling/lanes.ts +53 -1
  42. package/src/shipload.ts +5 -0
@@ -35,13 +35,9 @@ export type cargo_item = ServerContract.Types.cargo_item
35
35
  export type entity_row = ServerContract.Types.entity_row
36
36
  export type craftjob_row = ServerContract.Types.craftjob_row
37
37
  export type buildjob_row = ServerContract.Types.buildjob_row
38
- export type booking_receipt = ServerContract.Types.booking_receipt
39
- export {
40
- decodeBookingReceipt,
41
- bookingReceiptLandsAtMs,
42
- bookingReceiptRuntimeSeconds,
43
- } from './receipts'
44
- export type {BookingReceipt} from './receipts'
38
+ export type window_receipt = ServerContract.Types.window_receipt
39
+ export {decodeWindowReceipt, windowReceiptStartsAtMs, windowReceiptCompletesAtMs} from './receipts'
40
+ export type {WindowReceipt} from './receipts'
45
41
 
46
42
  export type location_static = ServerContract.Types.location_static
47
43
  export type location_derived = ServerContract.Types.location_derived
@@ -61,6 +57,7 @@ export {
61
57
  computeFreeCells,
62
58
  NftManager,
63
59
  BalancesManager,
60
+ FundManager,
64
61
  ConstructionManager,
65
62
  JobsManager,
66
63
  InfluenceManager,
@@ -84,6 +81,9 @@ export type {
84
81
  NftConfigForItem,
85
82
  WrapConfig,
86
83
  WrapGate,
84
+ FundBeneficiary,
85
+ FundToken,
86
+ FundAccrued,
87
87
  DepositConfig,
88
88
  PlatformBalance,
89
89
  TokenBalance,
@@ -289,6 +289,7 @@ export type {
289
289
  } from './scheduling/schedule'
290
290
  export {
291
291
  candidateLaneCompletesAt,
292
+ candidateCivicDepositWindow,
292
293
  laneKeyForModule,
293
294
  rawScheduleEnd,
294
295
  resolveLaneGatherer,
@@ -335,10 +336,27 @@ export {
335
336
  JOB_QUEUE_CAP,
336
337
  jobStatus,
337
338
  jobStatusLabel,
339
+ jobDropoffTask,
340
+ jobCancelRoute,
341
+ buildJobCancelRoute,
342
+ jobCancellable,
343
+ jobCancellationBlockReason,
338
344
  jobDeposited,
339
345
  splitJobCargo,
346
+ workshopAvailability,
347
+ pickupsInFlight,
348
+ } from './scheduling/jobs'
349
+ export type {
350
+ JobWindow,
351
+ JobLane,
352
+ JobLaneEntry,
353
+ JobStatus,
354
+ JobStatusInput,
355
+ JobCancelRoute,
356
+ JobCancellationBlockReason,
357
+ OwnedJob,
358
+ PickupInFlight,
340
359
  } from './scheduling/jobs'
341
- export type {JobWindow, JobLane, JobLaneEntry, JobStatus, OwnedJob} from './scheduling/jobs'
342
360
 
343
361
  export {composeIdleResolve} from './scheduling/idle-resolve'
344
362
  export type {CounterpartLookup, IdleResolveTarget} from './scheduling/idle-resolve'
@@ -399,6 +417,7 @@ export {
399
417
  ENTITY_NEXUS,
400
418
  ENTITY_HUB,
401
419
  ENTITY_DEPOT,
420
+ ENTITY_ASSEMBLY_YARD,
402
421
  getEntityClass,
403
422
  getPackedEntityType,
404
423
  getKindMeta,
@@ -424,6 +443,7 @@ export {
424
443
  isPlot,
425
444
  isHub,
426
445
  isDepot,
446
+ isAssemblyYard,
427
447
  } from './data/kind-registry'
428
448
  export type {EntityTypeName, KindMeta, TemplateMeta} from './data/kind-registry'
429
449
  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(56)
139
+ expect(DECOMP_REGISTRY.length).toBe(57)
140
140
  })
141
141
 
142
142
  test('the retired Workshop has no decomposition entry', () => {
@@ -24,6 +24,7 @@ import {Coordinates, PRECISION, type ClusterSlotType, type CoordinatesType} from
24
24
  import {ServerContract, TokenContract} from '../contracts'
25
25
  import {ATOMICASSETS_ABI, SHIPLOAD_COLLECTION} from '../nft/atomicassets'
26
26
  import {getItem} from '../data/catalog'
27
+ import type {JobCancelRoute} from '../scheduling/jobs'
27
28
 
28
29
  const CHARGE_K = 100n
29
30
  const ENERGY_DIVISOR = 10n
@@ -515,7 +516,6 @@ export class ActionsManager extends BaseManager {
515
516
  craftjob(
516
517
  shipId: UInt64Type,
517
518
  workshopId: UInt64Type,
518
- slot: UInt8Type,
519
519
  recipeId: UInt16Type,
520
520
  quantity: UInt32Type,
521
521
  inputs: ServerContract.ActionParams.Type.cargo_item[]
@@ -523,7 +523,6 @@ export class ActionsManager extends BaseManager {
523
523
  const params: ServerContract.ActionParams.craftjob = {
524
524
  ship_id: UInt64.from(shipId),
525
525
  workshop_id: UInt64.from(workshopId),
526
- slot: UInt8.from(slot),
527
526
  recipe_id: UInt16.from(recipeId),
528
527
  quantity: UInt32.from(quantity),
529
528
  inputs,
@@ -531,6 +530,19 @@ export class ActionsManager extends BaseManager {
531
530
  return this.server.action('craftjob', params)
532
531
  }
533
532
 
533
+ cancelcraft(jobId: UInt64Type): Action {
534
+ const params: ServerContract.ActionParams.cancelcraft = {
535
+ job_id: UInt64.from(jobId),
536
+ }
537
+ return this.server.action('cancelcraft', params)
538
+ }
539
+
540
+ canceljob(route: JobCancelRoute): Action {
541
+ if (route.kind === 'dropoff') return this.cancel(route.shipId, route.laneKey, route.count)
542
+ if (route.kind === 'build') return this.cancelbuild(route.jobId)
543
+ return this.cancelcraft(route.jobId)
544
+ }
545
+
534
546
  clustercraft(
535
547
  entityId: UInt64Type,
536
548
  recipeId: number,
@@ -565,14 +577,12 @@ export class ActionsManager extends BaseManager {
565
577
  buildjob(
566
578
  targetId: UInt64Type,
567
579
  dockId: UInt64Type,
568
- slot: UInt8Type,
569
580
  targetItemId: UInt16Type,
570
581
  inputs: ServerContract.ActionParams.Type.cargo_item[]
571
582
  ): Action {
572
583
  const params: ServerContract.ActionParams.buildjob = {
573
584
  target_id: UInt64.from(targetId),
574
585
  dock_id: UInt64.from(dockId),
575
- slot: UInt8.from(slot),
576
586
  target_item_id: UInt16.from(targetItemId),
577
587
  inputs,
578
588
  }
@@ -1028,6 +1038,11 @@ export class ActionsManager extends BaseManager {
1028
1038
  })
1029
1039
  }
1030
1040
 
1041
+ /** Pays every nonzero accrued token balance to the beneficiary. */
1042
+ claimFund(beneficiary: NameType): Action {
1043
+ return this.fund.action('claim', {beneficiary_account: Name.from(beneficiary)})
1044
+ }
1045
+
1031
1046
  collectFund(): Action {
1032
1047
  return this.fund.action('collect', {})
1033
1048
  }
@@ -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
+ }
@@ -44,3 +44,6 @@ export type {
44
44
  ScheduledBuild,
45
45
  Reservation,
46
46
  } from './construction-types'
47
+
48
+ export {FundManager} from './fund'
49
+ export type {FundBeneficiary, FundToken, FundAccrued} from './fund'
@@ -12,6 +12,7 @@ const row = (over: Record<string, unknown> = {}) => ({
12
12
  coords: {x: {toNumber: () => 12}, y: {toNumber: () => 34}},
13
13
  starts_at: {toDate: () => new Date('2026-07-26T10:00:00Z')},
14
14
  completes_at: {toDate: () => new Date('2026-07-26T11:00:00Z')},
15
+ arrives_at: {toDate: () => new Date('2026-07-26T09:45:00Z')},
15
16
  recipe_id: {toNumber: () => 10001},
16
17
  quantity: {toNumber: () => 5},
17
18
  energy_paid: {toNumber: () => 0},
@@ -26,37 +27,52 @@ function managerWith(queryImpl: () => Promise<unknown[]>, allImpl: () => Promise
26
27
  }
27
28
 
28
29
  describe('JobsManager.getOwnedJobs', () => {
29
- it('parses a landed row: cargo is the output, no inputs, status derived', async () => {
30
+ it('parses a deposited row: inputs then the output tail, status derived', async () => {
30
31
  const m = managerWith(
31
- async () => [row()],
32
+ async () => [row({cargo: [{item: 'in'}, {item: 'out'}]})],
32
33
  async () => []
33
34
  )
34
35
  const jobs = await m.getOwnedJobs(OWNER, {now: new Date('2026-07-26T11:30:00Z')})
35
36
  expect(jobs).toHaveLength(1)
36
37
  expect(jobs[0]).toMatchObject({id: 7, building: 42, quantity: 5, status: 'ready'})
38
+ expect(jobs[0].arrivesAt).toEqual(new Date('2026-07-26T09:45:00Z'))
37
39
  expect(jobs[0].coords).toEqual({x: 12, y: 34})
38
40
  expect(jobs[0].output).toEqual({item: 'out'} as never)
39
- expect(jobs[0].inputs).toEqual([] as never)
41
+ expect(jobs[0].inputs).toEqual([{item: 'in'}] as never)
40
42
  })
41
43
 
42
- it('reports a job In Line rather than ready, whatever its zero window says', async () => {
43
- const inLine = row({
44
- starts_at: {toDate: () => new Date(0)},
45
- completes_at: {toDate: () => new Date(0)},
44
+ it('reports an undeposited row as dropping off, whatever its window says', async () => {
45
+ const inFlight = row({
46
46
  deposited: false,
47
47
  cargo: [{item: 'in'}],
48
48
  })
49
49
  const m = managerWith(
50
- async () => [inLine],
50
+ async () => [inFlight],
51
51
  async () => []
52
52
  )
53
53
  const jobs = await m.getOwnedJobs(OWNER, {now: new Date('2026-07-26T11:30:00Z')})
54
- expect(jobs[0].status).toBe('inline')
54
+ expect(jobs[0].status).toBe('dropping')
55
55
  expect(jobs[0].deposited).toBe(false)
56
56
  expect(jobs[0].output).toBeNull()
57
57
  expect(jobs[0].inputs).toEqual([{item: 'in'}] as never)
58
58
  })
59
59
 
60
+ it('reports a cancelled row as ready with its inputs held', async () => {
61
+ const cancelled = row({
62
+ quantity: {toNumber: () => 0},
63
+ deposited: true,
64
+ cargo: [{item: 'in'}],
65
+ })
66
+ const m = managerWith(
67
+ async () => [cancelled],
68
+ async () => []
69
+ )
70
+ const jobs = await m.getOwnedJobs(OWNER, {now: new Date('2026-07-26T09:00:00Z')})
71
+ expect(jobs[0].status).toBe('ready')
72
+ expect(jobs[0].output).toBeNull()
73
+ expect(jobs[0].inputs).toEqual([{item: 'in'}] as never)
74
+ })
75
+
60
76
  it('drops rows whose owner does not match (positional-index safety re-filter)', async () => {
61
77
  const other = row({owner: Name.from('someoneelse.gm')})
62
78
  const m = managerWith(
@@ -32,19 +32,23 @@ export class JobsManager extends BaseManager {
32
32
  private parseOwnedJob(r: JobRow, now: Date): OwnedJob {
33
33
  const startsAt = r.starts_at.toDate()
34
34
  const completesAt = r.completes_at.toDate()
35
+ const arrivesAt = r.arrives_at.toDate()
35
36
  const deposited = jobDeposited(r.deposited)
36
- const {output, inputs} = splitJobCargo(r.cargo, deposited)
37
+ const quantity = r.quantity.toNumber()
38
+ const building = r.building.toNumber()
39
+ const {output, inputs} = splitJobCargo(r.cargo, deposited, quantity)
37
40
  return {
38
41
  id: r.id.toNumber(),
39
- building: r.building.toNumber(),
42
+ building,
40
43
  socket: r.socket.toNumber(),
41
44
  shipId: r.ship_id.toNumber(),
42
45
  coords: {x: r.coords.x.toNumber(), y: r.coords.y.toNumber()},
43
46
  startsAt,
44
47
  completesAt,
48
+ arrivesAt,
45
49
  recipeId: r.recipe_id.toNumber(),
46
- quantity: r.quantity.toNumber(),
47
- status: jobStatus({startsAt, completesAt, deposited}, now),
50
+ quantity,
51
+ status: jobStatus({startsAt, completesAt, deposited, quantity, building, inputs}, now),
48
52
  deposited,
49
53
  output,
50
54
  inputs,
@@ -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 CAPACITY_FN_BY_KIND: Record<string, (stats: bigint) => number> = {
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 kind = getTemplateMeta(itemId)?.kind
108
- if (!kind) return 0
109
- return CAPACITY_FN_BY_KIND[kind.toString()]?.(stats) ?? 0
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 =>
package/src/receipts.ts CHANGED
@@ -1,22 +1,22 @@
1
1
  import {Serializer} from '@wharfkit/antelope'
2
2
  import {ServerContract} from './contracts'
3
3
 
4
- export type BookingReceipt = ServerContract.Types.booking_receipt
4
+ export type WindowReceipt = ServerContract.Types.window_receipt
5
5
 
6
- export function decodeBookingReceipt(data: unknown): BookingReceipt {
6
+ export function decodeWindowReceipt(data: unknown): WindowReceipt {
7
7
  if (data === null || data === undefined) {
8
- throw new Error('decodeBookingReceipt: no return value')
8
+ throw new Error('decodeWindowReceipt: no return value')
9
9
  }
10
10
  if (data instanceof Uint8Array || typeof data === 'string') {
11
- return Serializer.decode({data, type: ServerContract.Types.booking_receipt})
11
+ return Serializer.decode({data, type: ServerContract.Types.window_receipt})
12
12
  }
13
- return ServerContract.Types.booking_receipt.from(data as ServerContract.Types.booking_receipt)
13
+ return ServerContract.Types.window_receipt.from(data as ServerContract.Types.window_receipt)
14
14
  }
15
15
 
16
- export function bookingReceiptLandsAtMs(receipt: BookingReceipt): number {
17
- return receipt.lands_at.toMilliseconds()
16
+ export function windowReceiptStartsAtMs(receipt: WindowReceipt): number {
17
+ return receipt.starts_at.toMilliseconds()
18
18
  }
19
19
 
20
- export function bookingReceiptRuntimeSeconds(receipt: BookingReceipt): number {
21
- return Number(receipt.runtime)
20
+ export function windowReceiptCompletesAtMs(receipt: WindowReceipt): number {
21
+ return receipt.completes_at.toMilliseconds()
22
22
  }
@@ -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
- ITEM_EXTRACTOR_T1_PACKED,
8
- ITEM_FACTORY_T1_PACKED,
9
- ITEM_HUB_T1_PACKED,
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', ITEM_FACTORY_T1_PACKED],
20
- ['extractor', ITEM_EXTRACTOR_T1_PACKED],
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 expected = computeContainerCapabilities({
36
+ const base = computeContainerCapabilities({
32
37
  strength: 300,
33
38
  hardness: 400,
34
39
  density: 100,
35
40
  }).capacity
36
- expect(capacity).toBe(expected)
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(ITEM_HUB_T1_PACKED), stats)
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(ITEM_HUB_T1_PACKED, {density: 100})
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
  })