@shipload/sdk 1.0.0-next.26 → 1.0.0-next.28

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 (40) hide show
  1. package/lib/shipload.d.ts +201 -86
  2. package/lib/shipload.js +843 -425
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +825 -423
  5. package/lib/shipload.m.js.map +1 -1
  6. package/lib/testing.d.ts +13 -8
  7. package/lib/testing.js +41 -26
  8. package/lib/testing.js.map +1 -1
  9. package/lib/testing.m.js +41 -26
  10. package/lib/testing.m.js.map +1 -1
  11. package/package.json +1 -1
  12. package/src/capabilities/craftable.ts +51 -0
  13. package/src/contracts/server.ts +39 -18
  14. package/src/data/capabilities.ts +5 -0
  15. package/src/data/colors.ts +1 -1
  16. package/src/data/item-ids.ts +1 -1
  17. package/src/data/metadata.ts +3 -3
  18. package/src/data/recipes.json +10 -10
  19. package/src/derivation/capabilities.ts +11 -11
  20. package/src/derivation/index.ts +9 -0
  21. package/src/derivation/stars.test.ts +51 -0
  22. package/src/derivation/stars.ts +15 -0
  23. package/src/derivation/stats.ts +5 -4
  24. package/src/entities/entity.ts +1 -1
  25. package/src/entities/makers.ts +15 -6
  26. package/src/index-module.ts +33 -4
  27. package/src/managers/actions.ts +10 -1
  28. package/src/managers/construction.ts +67 -65
  29. package/src/nft/buildImmutableData.ts +13 -11
  30. package/src/nft/description.ts +2 -2
  31. package/src/resolution/resolve-item.ts +2 -2
  32. package/src/scheduling/accessor.ts +65 -23
  33. package/src/scheduling/availability.ts +108 -0
  34. package/src/scheduling/energy.ts +18 -11
  35. package/src/scheduling/lane-core.ts +130 -0
  36. package/src/scheduling/lanes.ts +60 -0
  37. package/src/scheduling/projection.ts +30 -54
  38. package/src/scheduling/schedule.ts +236 -121
  39. package/src/travel/travel.ts +21 -16
  40. package/src/types/capabilities.ts +1 -0
@@ -0,0 +1,51 @@
1
+ import {expect, test} from 'bun:test'
2
+ import {
3
+ MAX_STARS_PER_STAT,
4
+ MAX_STAR_RATING,
5
+ starRating,
6
+ starsForStat,
7
+ statMagnitude,
8
+ STAR_STEP,
9
+ } from './stars'
10
+
11
+ test('starsForStat bands at the 250 boundaries', () => {
12
+ expect(starsForStat(0)).toBe(0)
13
+ expect(starsForStat(1)).toBe(0)
14
+ expect(starsForStat(249)).toBe(0)
15
+ expect(starsForStat(250)).toBe(1)
16
+ expect(starsForStat(499)).toBe(1)
17
+ expect(starsForStat(500)).toBe(2)
18
+ expect(starsForStat(749)).toBe(2)
19
+ expect(starsForStat(750)).toBe(3)
20
+ })
21
+
22
+ test('starsForStat clamps to MAX_STARS_PER_STAT', () => {
23
+ expect(starsForStat(999)).toBe(MAX_STARS_PER_STAT)
24
+ expect(starsForStat(1000)).toBe(MAX_STARS_PER_STAT)
25
+ expect(starsForStat(10_000)).toBe(MAX_STARS_PER_STAT)
26
+ })
27
+
28
+ test('starsForStat never goes negative', () => {
29
+ expect(starsForStat(-50)).toBe(0)
30
+ })
31
+
32
+ test('starRating sums per-stat stars to a 0-9 grade', () => {
33
+ expect(starRating(0, 0, 0)).toBe(0)
34
+ expect(starRating(250, 0, 0)).toBe(1)
35
+ expect(starRating(750, 750, 750)).toBe(MAX_STAR_RATING)
36
+ expect(starRating(999, 999, 999)).toBe(MAX_STAR_RATING)
37
+ expect(starRating(599, 599, 599)).toBe(6)
38
+ expect(starRating(251, 251, 251)).toBe(3)
39
+ })
40
+
41
+ test('statMagnitude sums raw values for tiebreaking', () => {
42
+ expect(statMagnitude(599, 599, 599)).toBe(1797)
43
+ expect(statMagnitude(251, 251, 251)).toBe(753)
44
+ expect(statMagnitude(599, 599, 599)).toBeGreaterThan(statMagnitude(251, 251, 251))
45
+ })
46
+
47
+ test('constants hold their documented values', () => {
48
+ expect(STAR_STEP).toBe(250)
49
+ expect(MAX_STARS_PER_STAT).toBe(3)
50
+ expect(MAX_STAR_RATING).toBe(9)
51
+ })
@@ -0,0 +1,15 @@
1
+ export const STAR_STEP = 250
2
+ export const MAX_STARS_PER_STAT = 3
3
+ export const MAX_STAR_RATING = MAX_STARS_PER_STAT * 3
4
+
5
+ export function starsForStat(value: number): number {
6
+ return Math.max(0, Math.min(MAX_STARS_PER_STAT, Math.floor(value / STAR_STEP)))
7
+ }
8
+
9
+ export function starRating(stat1: number, stat2: number, stat3: number): number {
10
+ return starsForStat(stat1) + starsForStat(stat2) + starsForStat(stat3)
11
+ }
12
+
13
+ export function statMagnitude(stat1: number, stat2: number, stat3: number): number {
14
+ return stat1 + stat2 + stat3
15
+ }
@@ -73,10 +73,11 @@ const GAS_STATS: StatDefinition[] = [
73
73
 
74
74
  const REGOLITH_STATS: StatDefinition[] = [
75
75
  {
76
- key: 'composition',
77
- label: 'Composition',
78
- abbreviation: 'COM',
79
- purpose: 'Mineral/metal mix — drives sensor, chip, and optic crafting quality',
76
+ key: 'cohesion',
77
+ label: 'Cohesion',
78
+ abbreviation: 'COH',
79
+ purpose:
80
+ 'Binding strength of the loose aggregate; higher cohesion yields more rigid frames and hulls',
80
81
  },
81
82
  {
82
83
  key: 'hardness',
@@ -28,7 +28,7 @@ export class Entity extends ServerContract.Types.entity_info {
28
28
  }
29
29
 
30
30
  get isIdle(): boolean {
31
- return this.is_idle
31
+ return schedule.isIdle(this)
32
32
  }
33
33
 
34
34
  get sched(): ScheduleAccessor {
@@ -11,6 +11,7 @@ import {getItem} from '../data/catalog'
11
11
  import {getModuleCapabilityType, moduleAccepts, moduleSlotTypeToCode} from '../capabilities/modules'
12
12
  import {computeEntityCapabilities} from '../derivation/capabilities'
13
13
  import {packedModulesToInstalled} from './slot-multiplier'
14
+ import {LANE_MOBILITY} from '../scheduling/schedule'
14
15
 
15
16
  export interface PackedModuleInput {
16
17
  itemId: number
@@ -29,6 +30,7 @@ export interface EntityStateInput {
29
30
  energy?: number
30
31
  modules?: PackedModuleInput[]
31
32
  schedule?: ServerContract.Types.schedule
33
+ lanes?: ServerContract.Types.lane[]
32
34
  cargo?: ServerContract.Types.cargo_item[]
33
35
  }
34
36
 
@@ -73,7 +75,7 @@ const ZERO_HULL_STATS: Record<string, number> = {
73
75
  density: 0,
74
76
  strength: 0,
75
77
  hardness: 0,
76
- saturation: 0,
78
+ cohesion: 0,
77
79
  }
78
80
 
79
81
  export function makeEntity(packedItemId: number, state: EntityStateInput): Entity {
@@ -86,6 +88,17 @@ export function makeEntity(packedItemId: number, state: EntityStateInput): Entit
86
88
  const layout = getEntityLayout(packedItemId)?.slots ?? []
87
89
  const mods = state.modules ?? []
88
90
 
91
+ const lanes =
92
+ state.lanes ??
93
+ (state.schedule
94
+ ? [
95
+ ServerContract.Types.lane.from({
96
+ lane_key: UInt8.from(LANE_MOBILITY),
97
+ schedule: state.schedule,
98
+ }),
99
+ ]
100
+ : [])
101
+
89
102
  const info: Record<string, unknown> = {
90
103
  type: template.kind,
91
104
  id: UInt64.from(state.id),
@@ -95,14 +108,10 @@ export function makeEntity(packedItemId: number, state: EntityStateInput): Entit
95
108
  item_id: UInt16.from(state.itemId ?? template.itemId),
96
109
  cargomass: UInt32.from(state.cargomass ?? 0),
97
110
  cargo: state.cargo || [],
98
- is_idle: !state.schedule,
99
- current_task_elapsed: UInt32.from(0),
100
- current_task_remaining: UInt32.from(0),
101
- pending_tasks: [],
111
+ lanes,
102
112
  }
103
113
 
104
114
  if (state.energy !== undefined) info.energy = UInt16.from(state.energy)
105
- if (state.schedule) info.schedule = state.schedule
106
115
 
107
116
  if (kind === 'container') {
108
117
  info.modules = []
@@ -28,6 +28,7 @@ export type movement_stats = ServerContract.Types.movement_stats
28
28
  export type energy_stats = ServerContract.Types.energy_stats
29
29
  export type loader_stats = ServerContract.Types.loader_stats
30
30
  export type schedule = ServerContract.Types.schedule
31
+ export type lane = ServerContract.Types.lane
31
32
  export type task = ServerContract.Types.task
32
33
  export type cargo_item = ServerContract.Types.cargo_item
33
34
  export type entity_row = ServerContract.Types.entity_row
@@ -132,6 +133,15 @@ export type {EffectiveReserveInput} from './derivation'
132
133
  export {getStatDefinitions, getStatName, resolveStats} from './derivation'
133
134
  export type {StatDefinition, NamedStats} from './derivation'
134
135
 
136
+ export {
137
+ STAR_STEP,
138
+ MAX_STARS_PER_STAT,
139
+ MAX_STAR_RATING,
140
+ starsForStat,
141
+ starRating,
142
+ statMagnitude,
143
+ } from './derivation'
144
+
135
145
  export {hash, hash512} from './utils/hash'
136
146
 
137
147
  export {
@@ -177,7 +187,19 @@ export type {
177
187
  } from './travel/travel'
178
188
 
179
189
  export * as schedule from './scheduling/schedule'
180
- export type {Scheduleable, ScheduleData} from './scheduling/schedule'
190
+ export {LANE_MOBILITY, LANE_BARRIER} from './scheduling/schedule'
191
+ export type {
192
+ ScheduleData,
193
+ LaneView,
194
+ OrderedTask,
195
+ ResolvedEvent,
196
+ } from './scheduling/schedule'
197
+ export {
198
+ candidateLaneCompletesAt,
199
+ laneKeyForModule,
200
+ rawScheduleEnd,
201
+ workerLaneKey,
202
+ } from './scheduling/lanes'
181
203
  export {ScheduleAccessor, createScheduleAccessor} from './scheduling/accessor'
182
204
  export {InventoryAccessor, createInventoryAccessor} from './entities/inventory-accessor'
183
205
  export type {HasCargo} from './entities/inventory-accessor'
@@ -191,13 +213,11 @@ export {
191
213
  createProjectedEntity,
192
214
  projectEntity,
193
215
  projectEntityAt,
194
- projectFromCurrentState,
195
- projectFromCurrentStateAt,
216
+ projectRemainingAt,
196
217
  validateSchedule,
197
218
  } from './scheduling/projection'
198
219
  export type {
199
220
  Projectable,
200
- ProjectableSnapshot,
201
221
  ProjectedEntity,
202
222
  ProjectionOptions,
203
223
  } from './scheduling/projection'
@@ -205,6 +225,15 @@ export type {
205
225
  export {taskCargoChanges} from './scheduling/task-cargo'
206
226
  export type {TaskCargoChange, TaskCargoDirection} from './scheduling/task-cargo'
207
227
 
228
+ export {
229
+ projectedCargoAvailableAt,
230
+ availableForItem,
231
+ cargoReadyAt,
232
+ taskCargoEffect,
233
+ } from './scheduling/availability'
234
+
235
+ export {maxCraftable} from './capabilities/craftable'
236
+
208
237
  export {energyAtTime} from './scheduling/energy'
209
238
 
210
239
  export * from './types/capabilities'
@@ -65,13 +65,22 @@ export class ActionsManager extends BaseManager {
65
65
  return this.server.action('resolve', params)
66
66
  }
67
67
 
68
- cancel(entityId: UInt64Type, count: UInt64Type): Action {
68
+ cancel(entityId: UInt64Type, laneKey: number, count: UInt64Type): Action {
69
69
  return this.server.action('cancel', {
70
70
  id: UInt64.from(entityId),
71
+ lane_key: UInt8.from(laneKey),
71
72
  count: UInt64.from(count),
72
73
  })
73
74
  }
74
75
 
76
+ retarget(sourceId: UInt64Type, taskIndex: UInt64Type, newDestId: UInt64Type): Action {
77
+ return this.server.action('retarget', {
78
+ source_id: UInt64.from(sourceId),
79
+ task_index: UInt64.from(taskIndex),
80
+ new_dest_id: UInt64.from(newDestId),
81
+ })
82
+ }
83
+
75
84
  recharge(entityId: UInt64Type): Action {
76
85
  return this.server.action('recharge', {
77
86
  id: UInt64.from(entityId),
@@ -4,6 +4,7 @@ import type {ServerContract} from '../contracts'
4
4
  import {PlotManager} from './plot'
5
5
  import {getItem} from '../data/catalog'
6
6
  import {calc_craft_duration} from '../capabilities/crafting'
7
+ import {getLanes, getTasks} from '../scheduling/schedule'
7
8
  import {TaskType} from '../types'
8
9
  import type {
9
10
  BuildableTarget,
@@ -94,43 +95,43 @@ export class ConstructionManager extends BaseManager {
94
95
  const buckets = new Map<string, Map<string, InboundTransfer>>()
95
96
  const nowMs = now.getTime()
96
97
  for (const entity of entities) {
97
- const schedule = entity.schedule
98
- if (!schedule) continue
99
98
  const entityIdStr = entity.id.toString()
100
99
  const sourceName = entity.entity_name || entityIdStr
101
- const startedMs = schedule.started.toDate().getTime()
102
- let cumulativeSec = 0
103
- for (const task of schedule.tasks) {
104
- cumulativeSec += task.duration.toNumber()
105
- if (!isTransferTask(task)) continue
106
- if (!task.entitytarget) continue
107
- const projectedEndMs = startedMs + cumulativeSec * 1000
108
- if (projectedEndMs < nowMs) continue
109
- const targetIdStr = task.entitytarget.entity_id.toString()
110
- const etaSeconds = Math.max(0, Math.round((projectedEndMs - nowMs) / 1000))
111
- let perTarget = buckets.get(targetIdStr)
112
- if (!perTarget) {
113
- perTarget = new Map()
114
- buckets.set(targetIdStr, perTarget)
115
- }
116
- for (const c of task.cargo) {
117
- const itemId = c.item_id.toNumber()
118
- const quantity = c.quantity.toNumber()
119
- if (quantity === 0) continue
120
- const key = `${entityIdStr}#${itemId}`
121
- const existing = perTarget.get(key)
122
- if (existing) {
123
- existing.quantity += quantity
124
- existing.etaSeconds = Math.min(existing.etaSeconds, etaSeconds)
125
- } else {
126
- perTarget.set(key, {
127
- sourceEntityId: entity.id,
128
- sourceEntityType: entity.type,
129
- sourceName,
130
- itemId,
131
- quantity,
132
- etaSeconds,
133
- })
100
+ for (const lane of getLanes(entity)) {
101
+ const startedMs = lane.schedule.started.toDate().getTime()
102
+ let cumulativeSec = 0
103
+ for (const task of lane.schedule.tasks) {
104
+ cumulativeSec += task.duration.toNumber()
105
+ if (!isTransferTask(task)) continue
106
+ if (!task.entitytarget) continue
107
+ const projectedEndMs = startedMs + cumulativeSec * 1000
108
+ if (projectedEndMs < nowMs) continue
109
+ const targetIdStr = task.entitytarget.entity_id.toString()
110
+ const etaSeconds = Math.max(0, Math.round((projectedEndMs - nowMs) / 1000))
111
+ let perTarget = buckets.get(targetIdStr)
112
+ if (!perTarget) {
113
+ perTarget = new Map()
114
+ buckets.set(targetIdStr, perTarget)
115
+ }
116
+ for (const c of task.cargo) {
117
+ const itemId = c.item_id.toNumber()
118
+ const quantity = c.quantity.toNumber()
119
+ if (quantity === 0) continue
120
+ const key = `${entityIdStr}#${itemId}`
121
+ const existing = perTarget.get(key)
122
+ if (existing) {
123
+ existing.quantity += quantity
124
+ existing.etaSeconds = Math.min(existing.etaSeconds, etaSeconds)
125
+ } else {
126
+ perTarget.set(key, {
127
+ sourceEntityId: entity.id,
128
+ sourceEntityType: entity.type,
129
+ sourceName,
130
+ itemId,
131
+ quantity,
132
+ etaSeconds,
133
+ })
134
+ }
134
135
  }
135
136
  }
136
137
  }
@@ -152,25 +153,24 @@ export class ConstructionManager extends BaseManager {
152
153
  completesAt: number
153
154
  hasStarted: boolean
154
155
  } | null {
155
- const schedule = plot.schedule
156
- if (!schedule) return null
157
- const tasks = schedule.tasks
158
- const startedMs = schedule.started.toDate().getTime()
159
- let startSec = 0
160
- for (const task of tasks) {
161
- if (task.type.toNumber() === TaskType.RESERVED) {
162
- if (!task.entitytarget) return null
163
- const startsAt = startedMs + startSec * 1000
164
- const completesAt = startsAt + task.duration.toNumber() * 1000
165
- return {
166
- builderId: task.entitytarget.entity_id,
167
- group: task.entitygroup ?? undefined,
168
- startsAt,
169
- completesAt,
170
- hasStarted: startsAt <= now.getTime(),
156
+ for (const lane of getLanes(plot)) {
157
+ const startedMs = lane.schedule.started.toDate().getTime()
158
+ let startSec = 0
159
+ for (const task of lane.schedule.tasks) {
160
+ if (task.type.toNumber() === TaskType.RESERVED) {
161
+ if (!task.entitytarget) return null
162
+ const startsAt = startedMs + startSec * 1000
163
+ const completesAt = startsAt + task.duration.toNumber() * 1000
164
+ return {
165
+ builderId: task.entitytarget.entity_id,
166
+ group: task.entitygroup ?? undefined,
167
+ startsAt,
168
+ completesAt,
169
+ hasStarted: startsAt <= now.getTime(),
170
+ }
171
171
  }
172
+ startSec += task.duration.toNumber()
172
173
  }
173
- startSec += task.duration.toNumber()
174
174
  }
175
175
  return null
176
176
  }
@@ -179,19 +179,22 @@ export class ConstructionManager extends BaseManager {
179
179
  builder: ServerContract.Types.entity_info | undefined,
180
180
  group: UInt64 | undefined
181
181
  ): {cancelable: boolean; blockingTaskCount: number} {
182
- if (!builder?.schedule || group === undefined) {
182
+ if (!builder || group === undefined) {
183
183
  return {cancelable: false, blockingTaskCount: 0}
184
184
  }
185
- const tasks = builder.schedule.tasks
186
- const buildIdx = tasks.findIndex(
187
- (t) =>
188
- t.type.toNumber() === TaskType.BUILDPLOT &&
189
- t.entitygroup !== undefined &&
190
- t.entitygroup.equals(group)
191
- )
192
- if (buildIdx < 0) return {cancelable: false, blockingTaskCount: 0}
193
- const trailing = tasks.length - 1 - buildIdx
194
- return {cancelable: trailing === 0, blockingTaskCount: trailing}
185
+ for (const lane of getLanes(builder)) {
186
+ const tasks = lane.schedule.tasks
187
+ const buildIdx = tasks.findIndex(
188
+ (t) =>
189
+ t.type.toNumber() === TaskType.BUILDPLOT &&
190
+ t.entitygroup !== undefined &&
191
+ t.entitygroup.equals(group)
192
+ )
193
+ if (buildIdx < 0) continue
194
+ const trailing = tasks.length - 1 - buildIdx
195
+ return {cancelable: trailing === 0, blockingTaskCount: trailing}
196
+ }
197
+ return {cancelable: false, blockingTaskCount: 0}
195
198
  }
196
199
 
197
200
  private buildFromReservation(
@@ -357,9 +360,8 @@ function isTransferTask(task: ServerContract.Types.task): boolean {
357
360
  }
358
361
 
359
362
  function reservationsOf(source: ServerContract.Types.entity_info): Reservation[] {
360
- if (!source.schedule) return []
361
363
  const out = new Map<string, Reservation>()
362
- for (const task of source.schedule.tasks) {
364
+ for (const task of getTasks(source)) {
363
365
  if (!isTransferTask(task)) continue
364
366
  if (!task.entitytarget) continue
365
367
  const targetType = task.entitytarget.entity_type
@@ -224,21 +224,23 @@ export function buildModuleImmutable(
224
224
  }
225
225
  case MODULE_CRAFTER: {
226
226
  const rea = decodeStat(stats, 0)
227
- const com = decodeStat(stats, 1)
227
+ const fin = decodeStat(stats, 1)
228
228
  base.push({first: 'reactivity', second: ['uint16', rea]})
229
- base.push({first: 'composition', second: ['uint16', com]})
229
+ base.push({first: 'fineness', second: ['uint16', fin]})
230
230
  base.push({first: 'speed', second: ['uint16', computeCrafterSpeed(rea)]})
231
- base.push({first: 'drain', second: ['uint16', computeCrafterDrain(com)]})
231
+ base.push({first: 'drain', second: ['uint16', computeCrafterDrain(fin)]})
232
232
  break
233
233
  }
234
234
  case MODULE_STORAGE: {
235
235
  const str = decodeStat(stats, 0)
236
- const fin = decodeStat(stats, 1)
237
- const sat = decodeStat(stats, 2)
238
- const sum = str + fin + sat
236
+ const den = decodeStat(stats, 1)
237
+ const hrd = decodeStat(stats, 2)
238
+ const com = decodeStat(stats, 3)
239
+ const sum = str + den + hrd + com
239
240
  base.push({first: 'strength', second: ['uint16', str]})
240
- base.push({first: 'fineness', second: ['uint16', fin]})
241
- base.push({first: 'saturation', second: ['uint16', sat]})
241
+ base.push({first: 'density', second: ['uint16', den]})
242
+ base.push({first: 'hardness', second: ['uint16', hrd]})
243
+ base.push({first: 'cohesion', second: ['uint16', com]})
242
244
  base.push({
243
245
  first: 'capacity_bonus_pct',
244
246
  second: ['uint16', 10 + Math.floor((sum * 10) / 2997)],
@@ -247,13 +249,13 @@ export function buildModuleImmutable(
247
249
  }
248
250
  case MODULE_HAULER: {
249
251
  const res = decodeStat(stats, 0)
250
- const con = decodeStat(stats, 1)
252
+ const pla = decodeStat(stats, 1)
251
253
  const ref = decodeStat(stats, 2)
252
254
  base.push({first: 'resonance', second: ['uint16', res]})
253
- base.push({first: 'conductivity', second: ['uint16', con]})
255
+ base.push({first: 'plasticity', second: ['uint16', pla]})
254
256
  base.push({first: 'reflectivity', second: ['uint16', ref]})
255
257
  base.push({first: 'capacity', second: ['uint8', computeHaulerCapacity(res)]})
256
- base.push({first: 'efficiency', second: ['uint16', computeHaulerEfficiency(con)]})
258
+ base.push({first: 'efficiency', second: ['uint16', computeHaulerEfficiency(pla)]})
257
259
  base.push({first: 'drain', second: ['uint16', computeHaulerDrain(ref)]})
258
260
  break
259
261
  }
@@ -162,9 +162,9 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
162
162
  }
163
163
  case MODULE_HAULER: {
164
164
  const res = decodeStat(stats, 0)
165
- const con = decodeStat(stats, 1)
165
+ const pla = decodeStat(stats, 1)
166
166
  const ref = decodeStat(stats, 2)
167
- out += ` Capacity ${computeHaulerCapacity(res)} Efficiency ${computeHaulerEfficiency(con)} Drain ${computeHaulerDrain(ref)}`
167
+ out += ` Capacity ${computeHaulerCapacity(res)} Efficiency ${computeHaulerEfficiency(pla)} Drain ${computeHaulerDrain(ref)}`
168
168
  break
169
169
  }
170
170
  case MODULE_WARP: {
@@ -222,8 +222,8 @@ function computeCapabilityGroup(
222
222
  const str = stats.strength
223
223
  const den = stats.density
224
224
  const hrd = stats.hardness
225
- const sat = stats.saturation
226
- const statSum = str + den + hrd + sat
225
+ const com = stats.cohesion
226
+ const statSum = str + den + hrd + com
227
227
  const pct = 10 + Math.floor((statSum * 10) / 2997)
228
228
  return {capability: 'Storage', attributes: [{label: 'Capacity Bonus', value: pct}]}
229
229
  }
@@ -1,86 +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
73
112
  }
74
113
 
75
114
  currentTaskProgressFloat(now: Date): number {
76
- return schedule.currentTaskProgressFloat(this.entity, now)
115
+ return this.lane ? core.currentTaskProgressFloatForLane(this.lane.schedule, now) : 0
77
116
  }
78
117
 
79
118
  progress(now: Date): number {
80
- return schedule.scheduleProgress(this.entity, now)
119
+ return this.lane ? core.laneProgress(this.lane.schedule, now) : 0
81
120
  }
82
121
  }
83
122
 
84
- export function createScheduleAccessor(entity: ScheduleData): ScheduleAccessor {
85
- return new ScheduleAccessor(entity)
123
+ export function createScheduleAccessor(
124
+ entity: ScheduleData,
125
+ laneKey: number = LANE_MOBILITY
126
+ ): ScheduleAccessor {
127
+ return new ScheduleAccessor(entity, laneKey)
86
128
  }