@shipload/sdk 1.0.0-next.3 → 1.0.0-next.30

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 (99) hide show
  1. package/lib/shipload.d.ts +1847 -962
  2. package/lib/shipload.js +9088 -4854
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +8957 -4805
  5. package/lib/shipload.m.js.map +1 -1
  6. package/lib/testing.d.ts +856 -0
  7. package/lib/testing.js +3739 -0
  8. package/lib/testing.js.map +1 -0
  9. package/lib/testing.m.js +3733 -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 +3 -3
  15. package/src/capabilities/gathering.ts +17 -7
  16. package/src/capabilities/index.ts +0 -1
  17. package/src/capabilities/modules.ts +6 -0
  18. package/src/capabilities/storage.ts +16 -1
  19. package/src/contracts/platform.ts +231 -3
  20. package/src/contracts/server.ts +816 -471
  21. package/src/data/capabilities.ts +14 -329
  22. package/src/data/capability-formulas.ts +76 -0
  23. package/src/data/catalog.ts +0 -5
  24. package/src/data/colors.ts +14 -47
  25. package/src/data/entities.json +46 -10
  26. package/src/data/item-ids.ts +15 -12
  27. package/src/data/items.json +302 -38
  28. package/src/data/kind-registry.json +85 -0
  29. package/src/data/kind-registry.ts +150 -0
  30. package/src/data/metadata.ts +100 -31
  31. package/src/data/recipes-runtime.ts +3 -23
  32. package/src/data/recipes.json +250 -113
  33. package/src/derivation/build-methods.ts +45 -0
  34. package/src/derivation/capabilities.ts +415 -0
  35. package/src/derivation/capability-mappings.ts +117 -0
  36. package/src/derivation/crafting.ts +23 -24
  37. package/src/derivation/index.ts +17 -2
  38. package/src/derivation/reserve-regen.ts +34 -0
  39. package/src/derivation/resources.ts +125 -38
  40. package/src/derivation/stars.test.ts +51 -0
  41. package/src/derivation/stars.ts +15 -0
  42. package/src/derivation/stats.ts +6 -6
  43. package/src/derivation/stratum.ts +15 -19
  44. package/src/derivation/tiers.ts +28 -7
  45. package/src/entities/entity.ts +98 -0
  46. package/src/entities/gamestate.ts +3 -28
  47. package/src/entities/makers.ts +91 -136
  48. package/src/entities/slot-multiplier.ts +39 -0
  49. package/src/errors.ts +10 -15
  50. package/src/format.ts +26 -4
  51. package/src/index-module.ts +189 -47
  52. package/src/managers/actions.ts +252 -83
  53. package/src/managers/base.ts +6 -2
  54. package/src/managers/construction-types.ts +79 -0
  55. package/src/managers/construction.ts +396 -0
  56. package/src/managers/context.ts +11 -1
  57. package/src/managers/entities.ts +18 -66
  58. package/src/managers/epochs.ts +40 -0
  59. package/src/managers/index.ts +17 -1
  60. package/src/managers/locations.ts +25 -29
  61. package/src/managers/nft.ts +28 -0
  62. package/src/managers/plot.ts +127 -0
  63. package/src/nft/atomicassets.abi.json +1342 -0
  64. package/src/nft/atomicassets.ts +237 -0
  65. package/src/nft/atomicdata.ts +130 -0
  66. package/src/nft/buildImmutableData.ts +321 -0
  67. package/src/nft/description.ts +37 -15
  68. package/src/nft/index.ts +3 -0
  69. package/src/resolution/describe-module.ts +5 -8
  70. package/src/resolution/display-name.ts +38 -10
  71. package/src/resolution/resolve-item.ts +22 -20
  72. package/src/scheduling/accessor.ts +68 -22
  73. package/src/scheduling/availability.ts +108 -0
  74. package/src/scheduling/energy.ts +48 -0
  75. package/src/scheduling/lane-core.ts +130 -0
  76. package/src/scheduling/lanes.ts +60 -0
  77. package/src/scheduling/projection.ts +121 -94
  78. package/src/scheduling/schedule.ts +237 -103
  79. package/src/scheduling/task-cargo.ts +46 -0
  80. package/src/shipload.ts +16 -1
  81. package/src/subscriptions/manager.ts +40 -6
  82. package/src/subscriptions/mappers.ts +3 -8
  83. package/src/subscriptions/types.ts +3 -2
  84. package/src/testing/catalog-hash.ts +19 -0
  85. package/src/testing/index.ts +2 -0
  86. package/src/testing/projection-parity.ts +143 -0
  87. package/src/travel/travel.ts +90 -13
  88. package/src/types/capabilities.ts +1 -0
  89. package/src/types/index.ts +0 -1
  90. package/src/types.ts +19 -12
  91. package/src/utils/cargo.ts +27 -0
  92. package/src/utils/display-name.ts +61 -0
  93. package/src/utils/system.ts +25 -24
  94. package/src/capabilities/loading.ts +0 -8
  95. package/src/entities/container.ts +0 -108
  96. package/src/entities/ship-deploy.ts +0 -258
  97. package/src/entities/ship.ts +0 -204
  98. package/src/entities/warehouse.ts +0 -119
  99. package/src/types/entity-traits.ts +0 -69
@@ -4,6 +4,7 @@ import {
4
4
  MODULE_ENGINE,
5
5
  MODULE_GATHERER,
6
6
  MODULE_GENERATOR,
7
+ MODULE_HAULER,
7
8
  MODULE_LOADER,
8
9
  MODULE_STORAGE,
9
10
  MODULE_WARP,
@@ -13,8 +14,10 @@ import {
13
14
  ITEM_CONTAINER_T2_PACKED,
14
15
  ITEM_CRAFTER_T1,
15
16
  ITEM_ENGINE_T1,
17
+ ITEM_EXTRACTOR_T1_PACKED,
16
18
  ITEM_GATHERER_T1,
17
19
  ITEM_GENERATOR_T1,
20
+ ITEM_HAULER_T1,
18
21
  ITEM_LOADER_T1,
19
22
  ITEM_SHIP_T1_PACKED,
20
23
  ITEM_STORAGE_T1,
@@ -22,6 +25,8 @@ import {
22
25
  ITEM_WARP_T1,
23
26
  } from '../data/item-ids'
24
27
  import {decodeStat} from '../derivation/crafting'
28
+ import {gathererDepthForTier} from '../derivation/capabilities'
29
+ import {getItem} from '../data/catalog'
25
30
 
26
31
  function idiv(a: number, b: number): number {
27
32
  return Math.floor(a / b)
@@ -29,32 +34,35 @@ function idiv(a: number, b: number): number {
29
34
 
30
35
  export function computeBaseHullmass(stats: bigint): number {
31
36
  const density = decodeStat(stats, 1)
32
- return 25000 + 75 * density
37
+ return 100000 - 75 * density
33
38
  }
34
39
 
35
40
  export function computeBaseCapacityShip(stats: bigint): number {
36
41
  const s = decodeStat(stats, 0) + decodeStat(stats, 2) + decodeStat(stats, 3)
37
- return Math.floor(1_000_000 * 10 ** (s / 2997))
42
+ return Math.floor(5_000_000 * 6 ** (s / 2997))
38
43
  }
39
44
 
40
45
  export function computeBaseCapacityWarehouse(stats: bigint): number {
41
46
  const s = decodeStat(stats, 0) + decodeStat(stats, 2) + decodeStat(stats, 3)
42
- return Math.floor(20_000_000 * 10 ** (s / 2997))
47
+ return Math.floor(100_000_000 * 6 ** (s / 2997))
43
48
  }
44
49
 
45
50
  export const computeEngineThrust = (vol: number): number => 400 + idiv(vol * 3, 4)
46
- export const computeEngineDrain = (thm: number): number => Math.max(30, 50 - idiv(thm, 70))
47
- export const computeGeneratorCap = (res: number): number => 300 + idiv(res, 6)
48
- export const computeGeneratorRech = (ref: number): number => 1 + idiv(ref * 3, 1000)
51
+ export const computeEngineDrain = (thm: number): number => 2 * Math.max(30, 50 - idiv(thm, 70))
52
+ export const computeGeneratorCap = (com: number): number => 950 + idiv(com, 2)
53
+ export const computeGeneratorRech = (fin: number): number => 2 * (1 + idiv(fin * 3, 1000))
49
54
  export const computeGathererYield = (str: number): number => 200 + str
50
55
  export const computeGathererDrain = (con: number): number =>
51
- Math.max(250, 1250 - idiv(con * 25, 20))
52
- export const computeGathererDepth = (tol: number): number => 200 + idiv(tol * 3, 2)
53
- export const computeGathererSpeed = (ref: number): number => 100 + idiv(ref * 4, 5)
54
- export const computeLoaderMass = (fin: number): number => Math.max(200, 2000 - fin * 2)
56
+ 2 * Math.max(250, 1250 - idiv(con * 25, 20))
57
+ export const computeGathererDepth = (tol: number, tier: number): number =>
58
+ gathererDepthForTier(tol, tier)
59
+ export const computeLoaderMass = (ins: number): number => Math.max(200, 2000 - ins * 2)
55
60
  export const computeLoaderThrust = (pla: number): number => 1 + idiv(pla, 500)
56
61
  export const computeCrafterSpeed = (rea: number): number => 100 + idiv(rea * 4, 5)
57
- export const computeCrafterDrain = (com: number): number => Math.max(5, 30 - idiv(com, 33))
62
+ export const computeCrafterDrain = (fin: number): number => Math.max(5, 30 - idiv(fin, 33))
63
+ export const computeHaulerCapacity = (fin: number): number => Math.max(1, 1 + idiv(fin, 400))
64
+ export const computeHaulerEfficiency = (con: number): number => 2000 + con * 6
65
+ export const computeHaulerDrain = (com: number): number => Math.max(3, 15 - idiv(com, 80))
58
66
  export const computeWarpRange = (stat: number): number => 100 + stat * 3
59
67
 
60
68
  export function entityDisplayName(itemId: number): string {
@@ -63,6 +71,8 @@ export function entityDisplayName(itemId: number): string {
63
71
  return 'Ship'
64
72
  case ITEM_WAREHOUSE_T1_PACKED:
65
73
  return 'Warehouse'
74
+ case ITEM_EXTRACTOR_T1_PACKED:
75
+ return 'Extractor'
66
76
  case ITEM_CONTAINER_T1_PACKED:
67
77
  return 'Container'
68
78
  case ITEM_CONTAINER_T2_PACKED:
@@ -86,6 +96,8 @@ export function moduleDisplayName(itemId: number): string {
86
96
  return 'Crafter'
87
97
  case ITEM_STORAGE_T1:
88
98
  return 'Storage'
99
+ case ITEM_HAULER_T1:
100
+ return 'Hauler'
89
101
  case ITEM_WARP_T1:
90
102
  return 'Warp'
91
103
  default:
@@ -119,11 +131,12 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
119
131
  case MODULE_GATHERER: {
120
132
  const str = decodeStat(stats, 0)
121
133
  const tol = decodeStat(stats, 1)
122
- const con = decodeStat(stats, 3)
123
- const ref = decodeStat(stats, 4)
134
+ const con = decodeStat(stats, 2)
135
+ const tier = getItem(itemId).tier
124
136
  out += ` Yield ${computeGathererYield(str)} Depth ${computeGathererDepth(
125
- tol
126
- )} Speed ${computeGathererSpeed(ref)} Drain ${computeGathererDrain(con)}`
137
+ tol,
138
+ tier
139
+ )} Drain ${computeGathererDrain(con)}`
127
140
  break
128
141
  }
129
142
  case MODULE_LOADER: {
@@ -147,6 +160,13 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
147
160
  out += ` +${pct}% capacity`
148
161
  break
149
162
  }
163
+ case MODULE_HAULER: {
164
+ const res = decodeStat(stats, 0)
165
+ const pla = decodeStat(stats, 1)
166
+ const ref = decodeStat(stats, 2)
167
+ out += ` Capacity ${computeHaulerCapacity(res)} Efficiency ${computeHaulerEfficiency(pla)} Drain ${computeHaulerDrain(ref)}`
168
+ break
169
+ }
150
170
  case MODULE_WARP: {
151
171
  const stat = decodeStat(stats, 0)
152
172
  out += ` Range ${computeWarpRange(stat)}`
@@ -168,6 +188,8 @@ export function buildEntityDescription(
168
188
  baseCapacity = computeBaseCapacityShip(hullStats)
169
189
  } else if (itemId === ITEM_WAREHOUSE_T1_PACKED) {
170
190
  baseCapacity = computeBaseCapacityWarehouse(hullStats)
191
+ } else if (itemId === ITEM_EXTRACTOR_T1_PACKED) {
192
+ baseCapacity = computeBaseCapacityShip(hullStats)
171
193
  }
172
194
 
173
195
  let out = entityDisplayName(itemId)
package/src/nft/index.ts CHANGED
@@ -1,2 +1,5 @@
1
1
  export * from './deserializers'
2
2
  export * from './description'
3
+ export * from './atomicdata'
4
+ export * from './atomicassets'
5
+ export * from './buildImmutableData'
@@ -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',
@@ -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 {
@@ -26,19 +26,15 @@ import {
26
26
  computeLoaderCapabilities,
27
27
  computeShipHullCapabilities,
28
28
  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'
29
+ computeContainerCapabilities,
30
+ computeContainerT2Capabilities,
31
+ } from '../derivation/capabilities'
32
+ import {categoryColors, componentIcon, itemAbbreviations, moduleIcon} from '../data/colors'
38
33
  import type {ServerContract} from '../contracts'
39
34
  import {
40
35
  ITEM_CONTAINER_T1_PACKED,
41
36
  ITEM_CONTAINER_T2_PACKED,
37
+ ITEM_EXTRACTOR_T1_PACKED,
42
38
  ITEM_SHIP_T1_PACKED,
43
39
  ITEM_WAREHOUSE_T1_PACKED,
44
40
  } from '../data/item-ids'
@@ -109,7 +105,7 @@ function resolveResource(id: number, stats?: UInt64Type): ResolvedItem {
109
105
  return {
110
106
  itemId: id,
111
107
  name: item.name,
112
- icon: cat ? categoryIcons[cat] : '',
108
+ icon: '',
113
109
  abbreviation: null,
114
110
  category: cat,
115
111
  tier: item.tier,
@@ -155,7 +151,8 @@ function resolveComponent(id: number, stats?: UInt64Type): ResolvedItem {
155
151
 
156
152
  function computeCapabilityGroup(
157
153
  moduleType: number,
158
- stats: Record<string, number>
154
+ stats: Record<string, number>,
155
+ tier: number
159
156
  ): ResolvedAttributeGroup | undefined {
160
157
  switch (moduleType) {
161
158
  case MODULE_ENGINE: {
@@ -179,14 +176,13 @@ function computeCapabilityGroup(
179
176
  }
180
177
  }
181
178
  case MODULE_GATHERER: {
182
- const caps = computeGathererCapabilities(stats)
179
+ const caps = computeGathererCapabilities(stats, tier)
183
180
  return {
184
181
  capability: 'Gatherer',
185
182
  attributes: [
186
183
  {label: 'Yield', value: caps.yield},
187
184
  {label: 'Drain', value: caps.drain},
188
185
  {label: 'Depth', value: caps.depth},
189
- {label: 'Speed', value: caps.speed},
190
186
  ],
191
187
  }
192
188
  }
@@ -223,10 +219,11 @@ function computeCapabilityGroup(
223
219
  }
224
220
  }
225
221
  case MODULE_STORAGE: {
226
- const str = stats.strength ?? 500
227
- const hrd = stats.hardness ?? 500
228
- const sat = stats.saturation ?? 500
229
- const statSum = str + hrd + sat
222
+ const str = stats.strength
223
+ const den = stats.density
224
+ const hrd = stats.hardness
225
+ const com = stats.cohesion
226
+ const statSum = str + den + hrd + com
230
227
  const pct = 10 + Math.floor((statSum * 10) / 2997)
231
228
  return {capability: 'Storage', attributes: [{label: 'Capacity Bonus', value: pct}]}
232
229
  }
@@ -241,7 +238,7 @@ function resolveModule(id: number, stats?: UInt64Type): ResolvedItem {
241
238
  if (stats !== undefined) {
242
239
  const decoded = decodeCraftedItemStats(id, toBigStats(stats))
243
240
  const modType = getModuleCapabilityType(id)
244
- const group = computeCapabilityGroup(modType, decoded)
241
+ const group = computeCapabilityGroup(modType, decoded, item.tier)
245
242
  if (group) attributes = [group]
246
243
  }
247
244
  return {
@@ -268,6 +265,8 @@ function hullCapsForEntity(
268
265
  return computeShipHullCapabilities(decoded)
269
266
  case ITEM_WAREHOUSE_T1_PACKED:
270
267
  return computeWarehouseHullCapabilities(decoded)
268
+ case ITEM_EXTRACTOR_T1_PACKED:
269
+ return computeShipHullCapabilities(decoded)
271
270
  case ITEM_CONTAINER_T1_PACKED:
272
271
  return computeContainerCapabilities(decoded)
273
272
  case ITEM_CONTAINER_T2_PACKED:
@@ -310,13 +309,16 @@ function resolveEntity(
310
309
  const modStats = BigInt(mod.installed.stats.toString())
311
310
  const decodedStats = decodeCraftedItemStats(modItemId, modStats)
312
311
  const modType = getModuleCapabilityType(modItemId)
313
- const group = computeCapabilityGroup(modType, decodedStats)
314
312
  let modName = 'Module'
313
+ let modTier = 1
315
314
  try {
316
- modName = getItem(modItemId).name
315
+ const modItem = getItem(modItemId)
316
+ modName = modItem.name
317
+ modTier = modItem.tier
317
318
  } catch {
318
319
  modName = itemMetadata[modItemId]?.name ?? 'Module'
319
320
  }
321
+ const group = computeCapabilityGroup(modType, decodedStats, modTier)
320
322
  return {
321
323
  name: modName,
322
324
  installed: true,
@@ -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
+ 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
+ }
@@ -0,0 +1,48 @@
1
+ import {TaskType} from '../types'
2
+ import {createProjectedEntity, type Projectable} from './projection'
3
+ import {orderedTasks} from './schedule'
4
+
5
+ export function energyAtTime(entity: Projectable, now: Date): number {
6
+ const projected = createProjectedEntity(entity)
7
+ const capacity = projected.generator ? Number(projected.generator.capacity) : undefined
8
+
9
+ const clamp = (value: number): number => {
10
+ const floored = Math.max(0, value)
11
+ return capacity !== undefined ? Math.min(capacity, floored) : floored
12
+ }
13
+
14
+ let running = Number(projected.energy)
15
+
16
+ const ordered = orderedTasks(entity)
17
+ if (ordered.length === 0) return clamp(running)
18
+
19
+ const nowMs = now.getTime()
20
+
21
+ for (const {task, startsAt} of ordered) {
22
+ const duration = task.duration.toNumber()
23
+ const isReserved = task.type.toNumber() === TaskType.RESERVED
24
+ const elapsed = Math.min(
25
+ Math.max(0, Math.floor((nowMs - startsAt.getTime()) / 1000)),
26
+ duration
27
+ )
28
+ const complete = !isReserved && elapsed >= duration
29
+ const inProgress = !complete && elapsed > 0 && elapsed < duration
30
+
31
+ if (!complete && !inProgress) continue
32
+
33
+ const fraction = complete ? 1 : duration === 0 ? 1 : elapsed / duration
34
+
35
+ if (task.type.toNumber() === TaskType.RECHARGE) {
36
+ if (capacity !== undefined) {
37
+ running = complete ? capacity : running + (capacity - running) * fraction
38
+ }
39
+ } else {
40
+ const cost = Number(task.energy_cost ?? 0)
41
+ running -= cost * fraction
42
+ }
43
+
44
+ running = clamp(running)
45
+ }
46
+
47
+ return clamp(running)
48
+ }