@shipload/sdk 1.0.0-next.67 → 1.0.0-next.68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipload/sdk",
3
3
  "description": "SDKs for Shipload",
4
- "version": "1.0.0-next.67",
4
+ "version": "1.0.0-next.68",
5
5
  "homepage": "https://github.com/shipload/toolkit/tree/master/packages/sdk",
6
6
  "repository": {
7
7
  "type": "git",
@@ -72,7 +72,7 @@ function makeLane(laneKey: number, tasks: Task[]): Lane {
72
72
 
73
73
  test('civicShuttleBays lists both Depot bays with their queue depth', () => {
74
74
  const lanes = [makeLane(1, [shuttleTask(20, 21)])]
75
- const bays = civicShuttleBays(depotModules(), ITEM_DEPOT_T1_PACKED, lanes)
75
+ const bays = civicShuttleBays(depotModules(), ITEM_DEPOT_T1_PACKED, lanes, T0)
76
76
  expect(bays.map((b) => b.laneKey)).toEqual([1, 2])
77
77
  expect(bays.map((b) => b.slotIndex)).toEqual([0, 1])
78
78
  expect(bays[0].queued).toBe(1)
@@ -83,25 +83,68 @@ test('civicShuttleBays lists both Depot bays with their queue depth', () => {
83
83
 
84
84
  test('a bay is full at the job queue cap', () => {
85
85
  const filled = Array.from({length: JOB_QUEUE_CAP}, () => shuttleTask(20, 21))
86
- const bays = civicShuttleBays(depotModules(), ITEM_DEPOT_T1_PACKED, [makeLane(1, filled)])
86
+ const bays = civicShuttleBays(depotModules(), ITEM_DEPOT_T1_PACKED, [makeLane(1, filled)], T0)
87
87
  expect(bays[0].full).toBe(true)
88
88
  expect(bays[1].full).toBe(false)
89
89
  })
90
90
 
91
+ const AFTER = new Date(T0.getTime() + 3_600_000)
92
+
93
+ test('a landed booking leaves the bay free, since booking clears it', () => {
94
+ const filled = Array.from({length: JOB_QUEUE_CAP}, () => shuttleTask(20, 21))
95
+ const lanes = [makeLane(1, filled)]
96
+ const bays = civicShuttleBays(depotModules(), ITEM_DEPOT_T1_PACKED, lanes, AFTER)
97
+ expect(bays[0].queued).toBe(0)
98
+ expect(bays[0].full).toBe(false)
99
+ expect(selectCivicShuttleBay(depotModules(), ITEM_DEPOT_T1_PACKED, lanes, AFTER)?.laneKey).toBe(
100
+ 1
101
+ )
102
+ })
103
+
104
+ test('a bay part-way through its queue counts only what is still unfinished', () => {
105
+ const lanes = [makeLane(1, [shuttleTask(20, 21), shuttleTask(22, 23), shuttleTask(24, 25)])]
106
+ const bays = civicShuttleBays(
107
+ depotModules(),
108
+ ITEM_DEPOT_T1_PACKED,
109
+ lanes,
110
+ new Date(T0.getTime() + 90_000)
111
+ )
112
+ expect(bays[0].queued).toBe(2)
113
+ })
114
+
115
+ test('a landed transfer is listed for settling but is not callable', () => {
116
+ const lanes = [makeLane(1, [shuttleTask(20, 21)])]
117
+ const [transfer] = pendingCivicTransfers({lanes} as never, AFTER)
118
+ expect(transfer.isTail).toBe(true)
119
+ expect(transfer.complete).toBe(true)
120
+ expect(transfer.callable).toBe(false)
121
+ })
122
+
123
+ test('landed transfers do not count against the per-player cap', () => {
124
+ const mine = Array.from({length: CIVIC_TRANSFER_PER_PLAYER_CAP}, (_, i) =>
125
+ shuttleTask(20 + i, 50)
126
+ )
127
+ const senderIds = mine.map((_, i) => 20 + i)
128
+ const at = {lanes: [makeLane(1, mine)]} as never
129
+ expect(civicTransfersAtCap(at, senderIds, T0)).toBe(true)
130
+ expect(civicTransfersAtCap(at, senderIds, AFTER)).toBe(false)
131
+ expect(civicTransfersFrom(at, senderIds, AFTER).length).toBe(CIVIC_TRANSFER_PER_PLAYER_CAP)
132
+ })
133
+
91
134
  test('selectCivicShuttleBay takes the first free bay, then the lowest occupied one', () => {
92
135
  const modules = depotModules()
93
- expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, [])?.laneKey).toBe(1)
136
+ expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, [], T0)?.laneKey).toBe(1)
94
137
 
95
138
  const firstBusy = [makeLane(1, [shuttleTask(20, 21)])]
96
- expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, firstBusy)?.laneKey).toBe(2)
139
+ expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, firstBusy, T0)?.laneKey).toBe(2)
97
140
 
98
141
  const bothBusy = [makeLane(1, [shuttleTask(20, 21)]), makeLane(2, [shuttleTask(22, 23)])]
99
- expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, bothBusy)?.laneKey).toBe(1)
142
+ expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, bothBusy, T0)?.laneKey).toBe(1)
100
143
  })
101
144
 
102
145
  test('selectCivicShuttleBay returns nothing when the building has no loaders', () => {
103
146
  const modules = [makeModuleEntry(ITEM_STORAGE_T1, storageStats)]
104
- expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, [])).toBeUndefined()
147
+ expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, [], T0)).toBeUndefined()
105
148
  })
106
149
 
107
150
  test('pendingCivicTransfers reports the bay, both counterparties, and the tail', () => {
@@ -109,7 +152,7 @@ test('pendingCivicTransfers reports the bay, both counterparties, and the tail',
109
152
  makeLane(1, [shuttleTask(20, 21), shuttleTask(22, 23)]),
110
153
  makeLane(2, [shuttleTask(24, 25)]),
111
154
  ]
112
- const transfers = pendingCivicTransfers({lanes} as never)
155
+ const transfers = pendingCivicTransfers({lanes} as never, T0)
113
156
  expect(transfers.length).toBe(3)
114
157
 
115
158
  const first = transfers.find((t) => String(t.senderId) === '20')!
@@ -131,7 +174,7 @@ test('pendingCivicTransfers reports the bay, both counterparties, and the tail',
131
174
 
132
175
  test('a grouped tail transfer is not callable', () => {
133
176
  const lanes = [makeLane(1, [shuttleTask(20, 21, 7)])]
134
- const [transfer] = pendingCivicTransfers({lanes} as never)
177
+ const [transfer] = pendingCivicTransfers({lanes} as never, T0)
135
178
  expect(transfer.isTail).toBe(true)
136
179
  expect(transfer.callable).toBe(false)
137
180
  })
@@ -142,9 +185,9 @@ test('civicTransfersFrom counts only the ships a player sends from', () => {
142
185
  makeLane(2, [shuttleTask(22, 23)]),
143
186
  ]
144
187
  const entity = {lanes} as never
145
- expect(civicTransfersFrom(entity, [20, 22]).length).toBe(2)
146
- expect(civicTransfersFrom(entity, [30]).length).toBe(1)
147
- expect(civicTransfersFrom(entity, [99]).length).toBe(0)
188
+ expect(civicTransfersFrom(entity, [20, 22], T0).length).toBe(2)
189
+ expect(civicTransfersFrom(entity, [30], T0).length).toBe(1)
190
+ expect(civicTransfersFrom(entity, [99], T0).length).toBe(0)
148
191
  })
149
192
 
150
193
  test('civicTransfersAtCap trips at the per-player cap', () => {
@@ -154,6 +197,6 @@ test('civicTransfersAtCap trips at the per-player cap', () => {
154
197
  const senderIds = mine.map((_, i) => 20 + i)
155
198
  const under = {lanes: [makeLane(1, mine.slice(0, -1))]} as never
156
199
  const at = {lanes: [makeLane(1, mine)]} as never
157
- expect(civicTransfersAtCap(under, senderIds)).toBe(false)
158
- expect(civicTransfersAtCap(at, senderIds)).toBe(true)
200
+ expect(civicTransfersAtCap(under, senderIds, T0)).toBe(false)
201
+ expect(civicTransfersAtCap(at, senderIds, T0)).toBe(true)
159
202
  })
@@ -22,10 +22,25 @@ export interface CivicShuttleBay {
22
22
  full: boolean
23
23
  }
24
24
 
25
+ // a landed booking clears during the next civicshuttle, so only unfinished tasks occupy a bay
26
+ function unfinishedTasks(lane: Lane | undefined, now: Date): number {
27
+ if (!lane) return 0
28
+ const startedMs = lane.schedule.started.toDate().getTime()
29
+ const nowMs = now.getTime()
30
+ let endSec = 0
31
+ let count = 0
32
+ for (const task of lane.schedule.tasks) {
33
+ endSec += task.duration.toNumber()
34
+ if (startedMs + endSec * 1000 > nowMs) count++
35
+ }
36
+ return count
37
+ }
38
+
25
39
  export function civicShuttleBays(
26
40
  modules: ModuleEntry[],
27
41
  entityItemId: number,
28
- lanes: Lane[]
42
+ lanes: Lane[],
43
+ now: Date = new Date()
29
44
  ): CivicShuttleBay[] {
30
45
  const bays: CivicShuttleBay[] = []
31
46
  for (let slotIndex = 0; slotIndex < modules.length; slotIndex++) {
@@ -35,7 +50,7 @@ export function civicShuttleBays(
35
50
  const laneKey = laneKeyForModule(slotIndex)
36
51
  const loader = resolveLaneLoader(modules, entityItemId, laneKey)
37
52
  const lane = lanes.find((l) => l.lane_key.toNumber() === laneKey)
38
- const queued = lane ? lane.schedule.tasks.length : 0
53
+ const queued = unfinishedTasks(lane, now)
39
54
  bays.push({
40
55
  laneKey,
41
56
  slotIndex,
@@ -53,9 +68,10 @@ export function civicShuttleBays(
53
68
  export function selectCivicShuttleBay(
54
69
  modules: ModuleEntry[],
55
70
  entityItemId: number,
56
- lanes: Lane[]
71
+ lanes: Lane[],
72
+ now: Date = new Date()
57
73
  ): CivicShuttleBay | undefined {
58
- const bays = civicShuttleBays(modules, entityItemId, lanes)
74
+ const bays = civicShuttleBays(modules, entityItemId, lanes, now)
59
75
  return bays.find((bay) => bay.queued === 0) ?? bays[0]
60
76
  }
61
77
 
@@ -68,10 +84,14 @@ export interface PendingCivicTransfer {
68
84
  startsAt: Date
69
85
  completesAt: Date
70
86
  isTail: boolean
87
+ complete: boolean
71
88
  callable: boolean
72
89
  }
73
90
 
74
- export function pendingCivicTransfers(entity: ScheduleData): PendingCivicTransfer[] {
91
+ export function pendingCivicTransfers(
92
+ entity: ScheduleData,
93
+ now: Date = new Date()
94
+ ): PendingCivicTransfer[] {
75
95
  const tailIndex = new Map<number, number>()
76
96
  for (const l of entity.lanes ?? []) {
77
97
  tailIndex.set(l.lane_key.toNumber(), l.schedule.tasks.length - 1)
@@ -83,6 +103,7 @@ export function pendingCivicTransfers(entity: ScheduleData): PendingCivicTransfe
83
103
  const push = entry.task.couplings.find((c) => c.kind.toNumber() === HoldKind.PUSH)
84
104
  if (!pull || !push) continue
85
105
  const isTail = tailIndex.get(entry.laneKey) === entry.taskIndex
106
+ const complete = entry.completesAt.getTime() <= now.getTime()
86
107
  out.push({
87
108
  laneKey: entry.laneKey,
88
109
  taskIndex: entry.taskIndex,
@@ -92,7 +113,8 @@ export function pendingCivicTransfers(entity: ScheduleData): PendingCivicTransfe
92
113
  startsAt: entry.startsAt,
93
114
  completesAt: entry.completesAt,
94
115
  isTail,
95
- callable: isTail && !entry.task.entitygroup,
116
+ complete,
117
+ callable: isTail && !complete && !entry.task.entitygroup,
96
118
  })
97
119
  }
98
120
  return out
@@ -101,15 +123,20 @@ export function pendingCivicTransfers(entity: ScheduleData): PendingCivicTransfe
101
123
  // Mirrors civic_transfers_for: the sender side of every shuttle the building holds, by owner.
102
124
  export function civicTransfersFrom(
103
125
  entity: ScheduleData,
104
- senderIds: Array<UInt64 | string | number>
126
+ senderIds: Array<UInt64 | string | number>,
127
+ now: Date = new Date()
105
128
  ): PendingCivicTransfer[] {
106
129
  const owned = new Set(senderIds.map((id) => String(id)))
107
- return pendingCivicTransfers(entity).filter((t) => owned.has(String(t.senderId)))
130
+ return pendingCivicTransfers(entity, now).filter((t) => owned.has(String(t.senderId)))
108
131
  }
109
132
 
110
133
  export function civicTransfersAtCap(
111
134
  entity: ScheduleData,
112
- senderIds: Array<UInt64 | string | number>
135
+ senderIds: Array<UInt64 | string | number>,
136
+ now: Date = new Date()
113
137
  ): boolean {
114
- return civicTransfersFrom(entity, senderIds).length >= CIVIC_TRANSFER_PER_PLAYER_CAP
138
+ return (
139
+ civicTransfersFrom(entity, senderIds, now).filter((t) => !t.complete).length >=
140
+ CIVIC_TRANSFER_PER_PLAYER_CAP
141
+ )
115
142
  }