@shipload/sdk 1.0.0-next.4 → 1.0.0-next.40

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 (127) hide show
  1. package/lib/shipload.d.ts +2473 -973
  2. package/lib/shipload.js +11529 -5211
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +11338 -5162
  5. package/lib/shipload.m.js.map +1 -1
  6. package/lib/testing.d.ts +970 -0
  7. package/lib/testing.js +4013 -0
  8. package/lib/testing.js.map +1 -0
  9. package/lib/testing.m.js +4007 -0
  10. package/lib/testing.m.js.map +1 -0
  11. package/package.json +15 -2
  12. package/src/capabilities/craftable.ts +51 -0
  13. package/src/capabilities/crafting.test.ts +7 -0
  14. package/src/capabilities/crafting.ts +5 -6
  15. package/src/capabilities/gathering.test.ts +16 -0
  16. package/src/capabilities/gathering.ts +35 -18
  17. package/src/capabilities/index.ts +0 -1
  18. package/src/capabilities/modules.ts +9 -0
  19. package/src/capabilities/storage.ts +16 -1
  20. package/src/contracts/platform.ts +231 -3
  21. package/src/contracts/server.ts +1021 -481
  22. package/src/coordinates/address.ts +88 -0
  23. package/src/coordinates/constants.test.ts +15 -0
  24. package/src/coordinates/constants.ts +23 -0
  25. package/src/coordinates/index.ts +15 -0
  26. package/src/coordinates/memo.test.ts +47 -0
  27. package/src/coordinates/memo.ts +20 -0
  28. package/src/coordinates/permutation.ts +77 -0
  29. package/src/coordinates/regions.ts +48 -0
  30. package/src/coordinates/sectors.ts +115 -0
  31. package/src/data/capabilities.ts +12 -5
  32. package/src/data/capability-formulas.ts +14 -7
  33. package/src/data/catalog.ts +0 -5
  34. package/src/data/colors.ts +14 -47
  35. package/src/data/entities.json +76 -10
  36. package/src/data/item-ids.ts +18 -12
  37. package/src/data/items.json +321 -38
  38. package/src/data/kind-registry.json +109 -0
  39. package/src/data/kind-registry.ts +165 -0
  40. package/src/data/metadata.ts +119 -33
  41. package/src/data/recipes-runtime.ts +3 -23
  42. package/src/data/recipes.json +238 -117
  43. package/src/derivation/build-methods.ts +45 -0
  44. package/src/derivation/capabilities.test.ts +151 -0
  45. package/src/derivation/capabilities.ts +512 -0
  46. package/src/derivation/capability-mappings.ts +9 -12
  47. package/src/derivation/crafting.ts +23 -24
  48. package/src/derivation/index.ts +25 -2
  49. package/src/derivation/recipe-usage.test.ts +78 -0
  50. package/src/derivation/recipe-usage.ts +141 -0
  51. package/src/derivation/reserve-regen.ts +34 -0
  52. package/src/derivation/resources.ts +125 -38
  53. package/src/derivation/rollups.test.ts +55 -0
  54. package/src/derivation/rollups.ts +56 -0
  55. package/src/derivation/stars.test.ts +51 -0
  56. package/src/derivation/stars.ts +15 -0
  57. package/src/derivation/stats.ts +6 -6
  58. package/src/derivation/stratum.ts +17 -20
  59. package/src/derivation/tiers.ts +40 -7
  60. package/src/derivation/wormhole.ts +136 -0
  61. package/src/entities/entity.ts +98 -0
  62. package/src/entities/gamestate.ts +3 -28
  63. package/src/entities/makers.ts +124 -134
  64. package/src/entities/slot-multiplier.ts +43 -0
  65. package/src/errors.ts +12 -16
  66. package/src/format.ts +26 -4
  67. package/src/index-module.ts +267 -47
  68. package/src/managers/actions.ts +528 -95
  69. package/src/managers/base.ts +6 -2
  70. package/src/managers/construction-types.ts +80 -0
  71. package/src/managers/construction.ts +412 -0
  72. package/src/managers/context.ts +20 -1
  73. package/src/managers/coordinates.ts +14 -0
  74. package/src/managers/entities.ts +18 -66
  75. package/src/managers/epochs.ts +40 -0
  76. package/src/managers/index.ts +17 -1
  77. package/src/managers/locations.ts +25 -29
  78. package/src/managers/nft.test.ts +14 -0
  79. package/src/managers/nft.ts +70 -0
  80. package/src/managers/plot.ts +122 -0
  81. package/src/nft/atomicassets.abi.json +1342 -0
  82. package/src/nft/atomicassets.ts +237 -0
  83. package/src/nft/atomicdata.ts +130 -0
  84. package/src/nft/buildImmutableData.ts +338 -0
  85. package/src/nft/description.ts +98 -24
  86. package/src/nft/index.ts +3 -0
  87. package/src/planner/index.ts +127 -0
  88. package/src/planner/planner.test.ts +319 -0
  89. package/src/resolution/describe-module.ts +18 -13
  90. package/src/resolution/display-name.ts +38 -10
  91. package/src/resolution/resolve-item.test.ts +37 -0
  92. package/src/resolution/resolve-item.ts +55 -24
  93. package/src/scheduling/accessor.ts +68 -22
  94. package/src/scheduling/availability.ts +108 -0
  95. package/src/scheduling/cancel.test.ts +348 -0
  96. package/src/scheduling/cancel.ts +209 -0
  97. package/src/scheduling/energy.ts +47 -0
  98. package/src/scheduling/idle-resolve.ts +45 -0
  99. package/src/scheduling/lane-core.ts +128 -0
  100. package/src/scheduling/lanes.test.ts +249 -0
  101. package/src/scheduling/lanes.ts +198 -0
  102. package/src/scheduling/projection.ts +209 -105
  103. package/src/scheduling/schedule.ts +241 -104
  104. package/src/scheduling/task-cargo.ts +46 -0
  105. package/src/shipload.ts +21 -1
  106. package/src/subscriptions/manager.ts +229 -142
  107. package/src/subscriptions/mappers.ts +5 -8
  108. package/src/subscriptions/types.ts +11 -3
  109. package/src/testing/catalog-hash.ts +19 -0
  110. package/src/testing/index.ts +2 -0
  111. package/src/testing/projection-parity.ts +167 -0
  112. package/src/travel/reach.ts +23 -0
  113. package/src/travel/route-planner.ts +196 -0
  114. package/src/travel/travel.ts +200 -112
  115. package/src/types/capabilities.ts +29 -6
  116. package/src/types/entity.ts +3 -3
  117. package/src/types/index.ts +0 -1
  118. package/src/types.ts +28 -13
  119. package/src/utils/cargo.ts +27 -0
  120. package/src/utils/display-name.ts +70 -0
  121. package/src/utils/system.ts +36 -24
  122. package/src/capabilities/loading.ts +0 -8
  123. package/src/entities/container.ts +0 -108
  124. package/src/entities/ship-deploy.ts +0 -259
  125. package/src/entities/ship.ts +0 -204
  126. package/src/entities/warehouse.ts +0 -119
  127. package/src/types/entity-traits.ts +0 -69
@@ -53,25 +53,22 @@ const TEMPLATES: Record<string, TemplateSpec> = {
53
53
  gatherer: {
54
54
  id: 'module.gatherer.description',
55
55
  template:
56
- 'mines resources at {yield} speed to a max depth of {depth} with {speed} gather speed while draining {drain} energy per second',
56
+ 'mines resources at {yield} yield to a max depth of {depth} while draining {drain} energy per second',
57
57
  params: [
58
58
  ['yield', 'Yield'],
59
- ['drain', 'Drain'],
60
59
  ['depth', 'Depth'],
61
- ['speed', 'Speed'],
60
+ ['drain', 'Drain'],
62
61
  ],
63
- highlightKeys: ['yield', 'depth', 'speed', 'drain'],
62
+ highlightKeys: ['yield', 'depth', 'drain'],
64
63
  },
65
64
  loader: {
66
65
  id: 'module.loader.description',
67
- template:
68
- '{quantity} loader that generates {thrust} thrust with a weight of {mass} per unit',
66
+ template: 'generates {thrust} thrust with a weight of {mass} per unit',
69
67
  params: [
70
- ['quantity', 'Quantity'],
71
68
  ['thrust', 'Thrust'],
72
69
  ['mass', 'Mass'],
73
70
  ],
74
- highlightKeys: ['quantity', 'thrust', 'mass'],
71
+ highlightKeys: ['thrust', 'mass'],
75
72
  },
76
73
  crafter: {
77
74
  id: 'module.crafter.description',
@@ -84,9 +81,15 @@ const TEMPLATES: Record<string, TemplateSpec> = {
84
81
  },
85
82
  storage: {
86
83
  id: 'module.storage.description',
87
- template: 'boosts cargo capacity by {bonus}%',
88
- params: [['bonus', 'Capacity Bonus']],
89
- highlightKeys: ['bonus'],
84
+ template: 'adds {capacity} cargo capacity',
85
+ params: [['capacity', 'Cargo Capacity']],
86
+ highlightKeys: ['capacity'],
87
+ },
88
+ energy: {
89
+ id: 'module.energy-capacity.description',
90
+ template: 'adds {capacity} energy capacity',
91
+ params: [['capacity', 'Energy Capacity']],
92
+ highlightKeys: ['capacity'],
90
93
  },
91
94
  hauler: {
92
95
  id: 'module.hauler.description',
@@ -127,8 +130,10 @@ export function describeModuleForItem(resolved: ResolvedItem): ModuleDescription
127
130
  }
128
131
 
129
132
  export function describeModuleForSlot(slot: ResolvedModuleSlot): ModuleDescription | null {
130
- if (!slot.installed || !slot.name || !slot.attributes) return null
131
- return describeModule({capability: slot.name, attributes: slot.attributes})
133
+ if (!slot.installed || !slot.attributes) return null
134
+ const capability = slot.capability ?? slot.name
135
+ if (!capability) return null
136
+ return describeModule({capability, attributes: slot.attributes})
132
137
  }
133
138
 
134
139
  export function renderDescription(
@@ -1,22 +1,50 @@
1
1
  import type {ResolvedItem} from './resolve-item'
2
2
  import type {ResourceCategory} from '../types'
3
- import {CATEGORY_LABELS, TIER_ADJECTIVES} from '../types'
3
+ import {
4
+ CATEGORY_LABELS,
5
+ RESOURCE_TIER_ADJECTIVES,
6
+ COMPONENT_TIER_PREFIXES,
7
+ MODULE_TIER_PREFIXES,
8
+ } from '../types'
4
9
  import {formatMass as defaultFormatMass} from '../format'
5
10
 
6
- export interface DisplayNameInput {
7
- itemType: 'resource' | 'component' | 'module' | 'entity' | string
11
+ interface DisplayNameInputCommon {
8
12
  tier: number
9
13
  category?: ResourceCategory
10
14
  name: string
11
15
  }
12
16
 
13
- export function displayName(resolved: DisplayNameInput): string {
14
- if (resolved.itemType === 'resource') {
15
- const adj = TIER_ADJECTIVES[resolved.tier] ?? 'Unknown'
16
- const cat = resolved.category ? CATEGORY_LABELS[resolved.category] : 'Resource'
17
- return `${adj} ${cat}`
18
- }
19
- return resolved.name
17
+ export type DisplayNameInput =
18
+ | (DisplayNameInputCommon & {itemType: 'resource' | 'component' | 'module' | 'entity' | string})
19
+ | (DisplayNameInputCommon & {type: string})
20
+
21
+ function itemTypeOf(item: DisplayNameInput): string {
22
+ return 'itemType' in item ? item.itemType : item.type
23
+ }
24
+
25
+ function tierPrefix(item: DisplayNameInput): string | null {
26
+ const t = itemTypeOf(item)
27
+ if (t === 'resource') return RESOURCE_TIER_ADJECTIVES[item.tier] ?? null
28
+ if (t === 'component') return COMPONENT_TIER_PREFIXES[item.tier] ?? null
29
+ if (t === 'module') return MODULE_TIER_PREFIXES[item.tier] ?? null
30
+ return null
31
+ }
32
+
33
+ function rootName(item: DisplayNameInput): string {
34
+ if (itemTypeOf(item) !== 'resource') return item.name
35
+ return item.category ? CATEGORY_LABELS[item.category] : 'Resource'
36
+ }
37
+
38
+ // Tier-free display name: includes the resource tier adjective / component-module
39
+ // prefix, but no "(T#)" suffix. Use this when the tier is shown separately.
40
+ export function baseName(item: DisplayNameInput): string {
41
+ const prefix = tierPrefix(item)
42
+ const root = rootName(item)
43
+ return prefix ? `${prefix} ${root}` : root
44
+ }
45
+
46
+ export function displayName(item: DisplayNameInput): string {
47
+ return `${baseName(item)} (T${item.tier})`
20
48
  }
21
49
 
22
50
  export interface DescribeOptions {
@@ -0,0 +1,37 @@
1
+ import {expect, test} from 'bun:test'
2
+ import {UInt16, UInt64} from '@wharfkit/antelope'
3
+ import {resolveItem} from './resolve-item'
4
+ import {encodeStats} from '../derivation/crafting'
5
+ import {computeContainerCapabilities} from '../derivation/capabilities'
6
+ import {
7
+ ITEM_EXTRACTOR_T1_PACKED,
8
+ ITEM_FACTORY_T1_PACKED,
9
+ ITEM_MASS_DRIVER_T1_PACKED,
10
+ ITEM_MASS_CATCHER_T1_PACKED,
11
+ } from '../data/item-ids'
12
+
13
+ function hullStats(strength: number, density: number, hardness: number): UInt64 {
14
+ return UInt64.from(encodeStats([strength, density, hardness]).toString())
15
+ }
16
+
17
+ const CONTAINER_ENTITIES = [
18
+ ['factory', ITEM_FACTORY_T1_PACKED],
19
+ ['extractor', ITEM_EXTRACTOR_T1_PACKED],
20
+ ['mass driver', ITEM_MASS_DRIVER_T1_PACKED],
21
+ ['mass catcher', ITEM_MASS_CATCHER_T1_PACKED],
22
+ ] as const
23
+
24
+ for (const [label, itemId] of CONTAINER_ENTITIES) {
25
+ test(`resolveItem resolves ${label} hull capacity via container formula`, () => {
26
+ const stats = hullStats(300, 100, 400)
27
+ const resolved = resolveItem(UInt16.from(itemId), stats)
28
+ const hull = resolved.attributes?.find((g) => g.capability === 'Hull')
29
+ const capacity = hull?.attributes.find((a) => a.label === 'Capacity')?.value
30
+ const expected = computeContainerCapabilities({
31
+ strength: 300,
32
+ hardness: 400,
33
+ density: 100,
34
+ }).capacity
35
+ expect(capacity).toBe(expected)
36
+ })
37
+ }
@@ -9,6 +9,7 @@ import {
9
9
  isModuleItem,
10
10
  MODULE_CRAFTER,
11
11
  MODULE_ENGINE,
12
+ MODULE_BATTERY,
12
13
  MODULE_GATHERER,
13
14
  MODULE_GENERATOR,
14
15
  MODULE_HAULER,
@@ -19,6 +20,7 @@ import {decodeCraftedItemStats, decodeStat} from '../derivation/crafting'
19
20
  import {getStatDefinitions} from '../derivation/stats'
20
21
  import {
21
22
  computeCrafterCapabilities,
23
+ computeBatteryCapabilities,
22
24
  computeEngineCapabilities,
23
25
  computeGathererCapabilities,
24
26
  computeGeneratorCapabilities,
@@ -26,19 +28,20 @@ import {
26
28
  computeLoaderCapabilities,
27
29
  computeShipHullCapabilities,
28
30
  computeWarehouseHullCapabilities,
29
- } from '../entities/ship-deploy'
30
- import {computeContainerCapabilities, computeContainerT2Capabilities} from '../entities/container'
31
- import {
32
- categoryColors,
33
- categoryIcons,
34
- componentIcon,
35
- itemAbbreviations,
36
- moduleIcon,
37
- } from '../data/colors'
31
+ computeContainerCapabilities,
32
+ computeContainerT2Capabilities,
33
+ computeStorageCapabilities,
34
+ } from '../derivation/capabilities'
35
+ import {applySlotMultiplierUint32} from '../entities/slot-multiplier'
36
+ import {categoryColors, componentIcon, itemAbbreviations, moduleIcon} from '../data/colors'
38
37
  import type {ServerContract} from '../contracts'
39
38
  import {
40
39
  ITEM_CONTAINER_T1_PACKED,
41
40
  ITEM_CONTAINER_T2_PACKED,
41
+ ITEM_EXTRACTOR_T1_PACKED,
42
+ ITEM_FACTORY_T1_PACKED,
43
+ ITEM_MASS_CATCHER_T1_PACKED,
44
+ ITEM_MASS_DRIVER_T1_PACKED,
42
45
  ITEM_SHIP_T1_PACKED,
43
46
  ITEM_WAREHOUSE_T1_PACKED,
44
47
  } from '../data/item-ids'
@@ -62,6 +65,7 @@ export type ResolvedItemType = 'resource' | 'component' | 'module' | 'entity'
62
65
 
63
66
  export interface ResolvedModuleSlot {
64
67
  name?: string
68
+ capability?: string
65
69
  installed: boolean
66
70
  attributes?: {label: string; value: number}[]
67
71
  }
@@ -109,7 +113,7 @@ function resolveResource(id: number, stats?: UInt64Type): ResolvedItem {
109
113
  return {
110
114
  itemId: id,
111
115
  name: item.name,
112
- icon: cat ? categoryIcons[cat] : '',
116
+ icon: '',
113
117
  abbreviation: null,
114
118
  category: cat,
115
119
  tier: item.tier,
@@ -155,7 +159,9 @@ function resolveComponent(id: number, stats?: UInt64Type): ResolvedItem {
155
159
 
156
160
  function computeCapabilityGroup(
157
161
  moduleType: number,
158
- stats: Record<string, number>
162
+ stats: Record<string, number>,
163
+ tier: number,
164
+ outputPct = 100
159
165
  ): ResolvedAttributeGroup | undefined {
160
166
  switch (moduleType) {
161
167
  case MODULE_ENGINE: {
@@ -179,14 +185,13 @@ function computeCapabilityGroup(
179
185
  }
180
186
  }
181
187
  case MODULE_GATHERER: {
182
- const caps = computeGathererCapabilities(stats)
188
+ const caps = computeGathererCapabilities(stats, tier)
183
189
  return {
184
190
  capability: 'Gatherer',
185
191
  attributes: [
186
192
  {label: 'Yield', value: caps.yield},
187
193
  {label: 'Drain', value: caps.drain},
188
194
  {label: 'Depth', value: caps.depth},
189
- {label: 'Speed', value: caps.speed},
190
195
  ],
191
196
  }
192
197
  }
@@ -223,13 +228,28 @@ function computeCapabilityGroup(
223
228
  }
224
229
  }
225
230
  case MODULE_STORAGE: {
226
- const str = stats.strength
227
- const den = stats.density
228
- const hrd = stats.hardness
229
- const sat = stats.saturation
230
- const statSum = str + den + hrd + sat
231
- const pct = 10 + Math.floor((statSum * 10) / 2997)
232
- return {capability: 'Storage', attributes: [{label: 'Capacity Bonus', value: pct}]}
231
+ const caps = computeStorageCapabilities(stats)
232
+ return {
233
+ capability: 'Storage',
234
+ attributes: [
235
+ {
236
+ label: 'Cargo Capacity',
237
+ value: applySlotMultiplierUint32(caps.capacity, outputPct),
238
+ },
239
+ ],
240
+ }
241
+ }
242
+ case MODULE_BATTERY: {
243
+ const caps = computeBatteryCapabilities(stats)
244
+ return {
245
+ capability: 'Energy',
246
+ attributes: [
247
+ {
248
+ label: 'Energy Capacity',
249
+ value: applySlotMultiplierUint32(caps.capacity, outputPct),
250
+ },
251
+ ],
252
+ }
233
253
  }
234
254
  default:
235
255
  return undefined
@@ -242,7 +262,7 @@ function resolveModule(id: number, stats?: UInt64Type): ResolvedItem {
242
262
  if (stats !== undefined) {
243
263
  const decoded = decodeCraftedItemStats(id, toBigStats(stats))
244
264
  const modType = getModuleCapabilityType(id)
245
- const group = computeCapabilityGroup(modType, decoded)
265
+ const group = computeCapabilityGroup(modType, decoded, item.tier)
246
266
  if (group) attributes = [group]
247
267
  }
248
268
  return {
@@ -269,6 +289,10 @@ function hullCapsForEntity(
269
289
  return computeShipHullCapabilities(decoded)
270
290
  case ITEM_WAREHOUSE_T1_PACKED:
271
291
  return computeWarehouseHullCapabilities(decoded)
292
+ case ITEM_EXTRACTOR_T1_PACKED:
293
+ case ITEM_FACTORY_T1_PACKED:
294
+ case ITEM_MASS_DRIVER_T1_PACKED:
295
+ case ITEM_MASS_CATCHER_T1_PACKED:
272
296
  case ITEM_CONTAINER_T1_PACKED:
273
297
  return computeContainerCapabilities(decoded)
274
298
  case ITEM_CONTAINER_T2_PACKED:
@@ -289,7 +313,10 @@ function resolveEntity(
289
313
  let moduleSlots: ResolvedModuleSlot[] | undefined
290
314
 
291
315
  if (stats !== undefined) {
292
- const decoded = decodeCraftedItemStats(id, toBigStats(stats))
316
+ const bigStats = toBigStats(stats)
317
+ const decoded = decodeCraftedItemStats(id, bigStats)
318
+ if (decoded.strength === undefined) decoded.strength = decodeStat(bigStats, 0)
319
+ if (decoded.hardness === undefined) decoded.hardness = decodeStat(bigStats, 2)
293
320
  const hullCaps = hullCapsForEntity(id, decoded)
294
321
  attributes = [
295
322
  {
@@ -311,15 +338,19 @@ function resolveEntity(
311
338
  const modStats = BigInt(mod.installed.stats.toString())
312
339
  const decodedStats = decodeCraftedItemStats(modItemId, modStats)
313
340
  const modType = getModuleCapabilityType(modItemId)
314
- const group = computeCapabilityGroup(modType, decodedStats)
315
341
  let modName = 'Module'
342
+ let modTier = 1
316
343
  try {
317
- modName = getItem(modItemId).name
344
+ const modItem = getItem(modItemId)
345
+ modName = modItem.name
346
+ modTier = modItem.tier
318
347
  } catch {
319
348
  modName = itemMetadata[modItemId]?.name ?? 'Module'
320
349
  }
350
+ const group = computeCapabilityGroup(modType, decodedStats, modTier, slot.outputPct)
321
351
  return {
322
352
  name: modName,
353
+ capability: group?.capability,
323
354
  installed: true,
324
355
  attributes: group?.attributes,
325
356
  }
@@ -1,82 +1,128 @@
1
1
  import type {ServerContract} from '../contracts'
2
2
  import type {TaskType} from '../types'
3
- import type {ScheduleData} from './schedule'
4
- import * as schedule from './schedule'
3
+ import * as core from './lane-core'
4
+ import {
5
+ activeTasks,
6
+ getLane,
7
+ getLanes,
8
+ hasSchedule,
9
+ isIdle,
10
+ LANE_MOBILITY,
11
+ type LaneView,
12
+ type ScheduleData,
13
+ } from './schedule'
5
14
 
6
15
  type Task = ServerContract.Types.task
7
16
 
8
17
  export class ScheduleAccessor {
9
- constructor(private entity: ScheduleData) {}
18
+ private _laneResolved = false
19
+ private _lane: LaneView | undefined
20
+
21
+ constructor(
22
+ private entity: ScheduleData,
23
+ private laneKey: number = LANE_MOBILITY
24
+ ) {}
25
+
26
+ private get lane(): LaneView | undefined {
27
+ if (!this._laneResolved) {
28
+ this._lane = getLane(this.entity, this.laneKey)
29
+ this._laneResolved = true
30
+ }
31
+ return this._lane
32
+ }
33
+
34
+ forLane(laneKey: number): ScheduleAccessor {
35
+ return new ScheduleAccessor(this.entity, laneKey)
36
+ }
37
+
38
+ get lanes(): LaneView[] {
39
+ return getLanes(this.entity)
40
+ }
10
41
 
11
42
  get hasSchedule(): boolean {
12
- return schedule.hasSchedule(this.entity)
43
+ return hasSchedule(this.entity)
13
44
  }
14
45
 
15
46
  get isIdle(): boolean {
16
- return schedule.isIdle(this.entity)
47
+ return isIdle(this.entity)
17
48
  }
18
49
 
19
50
  get tasks(): Task[] {
20
- return schedule.getTasks(this.entity)
51
+ return this.lane?.schedule.tasks ?? []
52
+ }
53
+
54
+ activeTasks(now: Date): Task[] {
55
+ return activeTasks(this.entity, now)
21
56
  }
22
57
 
23
58
  duration(): number {
24
- return schedule.scheduleDuration(this.entity)
59
+ return this.lane ? core.laneDuration(this.lane.schedule) : 0
25
60
  }
26
61
 
27
62
  elapsed(now: Date): number {
28
- return schedule.scheduleElapsed(this.entity, now)
63
+ return this.lane ? core.laneElapsed(this.lane.schedule, now) : 0
29
64
  }
30
65
 
31
66
  remaining(now: Date): number {
32
- return schedule.scheduleRemaining(this.entity, now)
67
+ return this.lane ? core.laneRemaining(this.lane.schedule, now) : 0
68
+ }
69
+
70
+ startsIn(now: Date): number {
71
+ return this.lane ? core.laneStartsIn(this.lane.schedule, now) : 0
33
72
  }
34
73
 
35
74
  complete(now: Date): boolean {
36
- return schedule.scheduleComplete(this.entity, now)
75
+ return this.lane ? core.laneComplete(this.lane.schedule, now) : false
37
76
  }
38
77
 
39
78
  currentTaskIndex(now: Date): number {
40
- return schedule.currentTaskIndex(this.entity, now)
79
+ return this.lane ? core.currentTaskIndexForLane(this.lane.schedule, now) : -1
41
80
  }
42
81
 
43
82
  currentTask(now: Date): Task | undefined {
44
- return schedule.currentTask(this.entity, now)
83
+ return this.lane ? core.currentTask(this.lane.schedule, now) : undefined
45
84
  }
46
85
 
47
86
  currentTaskType(now: Date): TaskType | undefined {
48
- return schedule.currentTaskType(this.entity, now)
87
+ return this.lane ? core.currentTaskType(this.lane.schedule, now) : undefined
49
88
  }
50
89
 
51
90
  taskStartTime(index: number): number {
52
- return schedule.getTaskStartTime(this.entity, index)
91
+ return this.lane ? core.laneTaskStartTime(this.lane.schedule, index) : 0
53
92
  }
54
93
 
55
94
  taskElapsed(index: number, now: Date): number {
56
- return schedule.getTaskElapsed(this.entity, index, now)
95
+ return this.lane ? core.laneTaskElapsed(this.lane.schedule, index, now) : 0
57
96
  }
58
97
 
59
98
  taskRemaining(index: number, now: Date): number {
60
- return schedule.getTaskRemaining(this.entity, index, now)
99
+ return this.lane ? core.laneTaskRemaining(this.lane.schedule, index, now) : 0
61
100
  }
62
101
 
63
102
  taskComplete(index: number, now: Date): boolean {
64
- return schedule.isTaskComplete(this.entity, index, now)
103
+ return this.lane ? core.laneTaskComplete(this.lane.schedule, index, now) : false
65
104
  }
66
105
 
67
106
  taskInProgress(index: number, now: Date): boolean {
68
- return schedule.isTaskInProgress(this.entity, index, now)
107
+ return this.lane ? core.laneTaskInProgress(this.lane.schedule, index, now) : false
69
108
  }
70
109
 
71
110
  currentTaskProgress(now: Date): number {
72
- return schedule.currentTaskProgress(this.entity, now)
111
+ return this.lane ? core.currentTaskProgress(this.lane.schedule, now) : 0
112
+ }
113
+
114
+ currentTaskProgressFloat(now: Date): number {
115
+ return this.lane ? core.currentTaskProgressFloatForLane(this.lane.schedule, now) : 0
73
116
  }
74
117
 
75
118
  progress(now: Date): number {
76
- return schedule.scheduleProgress(this.entity, now)
119
+ return this.lane ? core.laneProgress(this.lane.schedule, now) : 0
77
120
  }
78
121
  }
79
122
 
80
- export function createScheduleAccessor(entity: ScheduleData): ScheduleAccessor {
81
- return new ScheduleAccessor(entity)
123
+ export function createScheduleAccessor(
124
+ entity: ScheduleData,
125
+ laneKey: number = LANE_MOBILITY
126
+ ): ScheduleAccessor {
127
+ return new ScheduleAccessor(entity, laneKey)
82
128
  }
@@ -0,0 +1,108 @@
1
+ import type {ServerContract} from '../contracts'
2
+ import {TaskType} from '../types'
3
+ import * as schedule from './schedule'
4
+
5
+ type Task = ServerContract.Types.task
6
+ type CargoItem = ServerContract.Types.cargo_item
7
+
8
+ export interface CargoEffect {
9
+ added: CargoItem[]
10
+ removed: CargoItem[]
11
+ }
12
+
13
+ export interface AvailabilityInput extends schedule.ScheduleData {
14
+ cargo: CargoItem[]
15
+ }
16
+
17
+ export function taskCargoEffect(task: Task): CargoEffect {
18
+ switch (task.type.toNumber()) {
19
+ case TaskType.LOAD:
20
+ case TaskType.UNWRAP:
21
+ case TaskType.UNDEPLOY:
22
+ return {added: task.cargo, removed: []}
23
+ case TaskType.UNLOAD:
24
+ return {added: [], removed: task.cargo}
25
+ case TaskType.GATHER:
26
+ return task.entitytarget ? {added: [], removed: []} : {added: task.cargo, removed: []}
27
+ case TaskType.CRAFT:
28
+ if (task.cargo.length === 0) return {added: [], removed: []}
29
+ return {added: [task.cargo[task.cargo.length - 1]], removed: task.cargo.slice(0, -1)}
30
+ case TaskType.DEPLOY:
31
+ return task.cargo.length > 0
32
+ ? {added: [], removed: [task.cargo[0]]}
33
+ : {added: [], removed: []}
34
+ default:
35
+ return {added: [], removed: []}
36
+ }
37
+ }
38
+
39
+ export function cargoKey(item: CargoItem): string {
40
+ const base = `${item.item_id.toNumber()}:${item.stats.toString()}`
41
+ const modules = item.modules ?? []
42
+ const entityId = item.entity_id?.toString()
43
+ const normalizedEntityId = entityId && entityId !== '0' ? entityId : ''
44
+ if (modules.length === 0 && normalizedEntityId === '') return base
45
+ return `${base}:modules=${JSON.stringify(modules)}:entity=${normalizedEntityId}`
46
+ }
47
+
48
+ function cargoQuantity(item: CargoItem): bigint {
49
+ return BigInt(item.quantity.toString())
50
+ }
51
+
52
+ export function projectedCargoAvailableAt(
53
+ entity: AvailabilityInput,
54
+ at: Date
55
+ ): Map<string, bigint> {
56
+ const avail = new Map<string, bigint>()
57
+
58
+ for (const item of entity.cargo) {
59
+ const key = cargoKey(item)
60
+ avail.set(key, (avail.get(key) ?? 0n) + cargoQuantity(item))
61
+ }
62
+
63
+ // Every scheduled task reserves inputs against the unsettled cargo base, even already-elapsed ones.
64
+ const tasks = schedule.orderedTasks(entity)
65
+
66
+ for (const ordered of tasks) {
67
+ if (ordered.completesAt.getTime() >= at.getTime()) continue
68
+
69
+ for (const item of taskCargoEffect(ordered.task).added) {
70
+ const key = cargoKey(item)
71
+ avail.set(key, (avail.get(key) ?? 0n) + cargoQuantity(item))
72
+ }
73
+ }
74
+
75
+ for (const ordered of tasks) {
76
+ for (const item of taskCargoEffect(ordered.task).removed) {
77
+ const key = cargoKey(item)
78
+ const current = avail.get(key) ?? 0n
79
+ const quantity = cargoQuantity(item)
80
+ avail.set(key, current > quantity ? current - quantity : 0n)
81
+ }
82
+ }
83
+
84
+ return avail
85
+ }
86
+
87
+ // Latest completion among scheduled tasks producing any of the given inputs (a craft starts no earlier).
88
+ export function cargoReadyAt(entity: AvailabilityInput, inputItemIds: readonly number[]): Date {
89
+ let readyMs = 0
90
+ for (const ordered of schedule.orderedTasks(entity)) {
91
+ for (const item of taskCargoEffect(ordered.task).added) {
92
+ if (inputItemIds.includes(item.item_id.toNumber())) {
93
+ readyMs = Math.max(readyMs, ordered.completesAt.getTime())
94
+ break
95
+ }
96
+ }
97
+ }
98
+ return new Date(readyMs)
99
+ }
100
+
101
+ export function availableForItem(avail: Map<string, bigint>, itemId: number): bigint {
102
+ const prefix = `${itemId}:`
103
+ let total = 0n
104
+ for (const [key, quantity] of avail) {
105
+ if (key.startsWith(prefix)) total += quantity
106
+ }
107
+ return total
108
+ }