@shipload/sdk 1.0.0-next.5 → 1.0.0-next.50
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/lib/scan.d.ts +52 -0
- package/lib/scan.js +162 -0
- package/lib/scan.js.map +1 -0
- package/lib/scan.m.js +152 -0
- package/lib/scan.m.js.map +1 -0
- package/lib/shipload.d.ts +2881 -1149
- package/lib/shipload.js +13031 -5511
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +12807 -5460
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +1028 -0
- package/lib/testing.js +4336 -0
- package/lib/testing.js.map +1 -0
- package/lib/testing.m.js +4330 -0
- package/lib/testing.m.js.map +1 -0
- package/package.json +20 -2
- package/src/capabilities/craftable.ts +51 -0
- package/src/capabilities/crafting.test.ts +7 -0
- package/src/capabilities/crafting.ts +6 -7
- package/src/capabilities/gathering.test.ts +29 -0
- package/src/capabilities/gathering.ts +37 -19
- package/src/capabilities/index.ts +0 -1
- package/src/capabilities/modules.ts +22 -0
- package/src/capabilities/storage.ts +16 -1
- package/src/contracts/platform.ts +231 -3
- package/src/contracts/server.ts +1160 -520
- package/src/coordinates/address.ts +88 -0
- package/src/coordinates/constants.test.ts +15 -0
- package/src/coordinates/constants.ts +23 -0
- package/src/coordinates/index.ts +15 -0
- package/src/coordinates/memo.test.ts +47 -0
- package/src/coordinates/memo.ts +20 -0
- package/src/coordinates/permutation.ts +77 -0
- package/src/coordinates/regions.ts +48 -0
- package/src/coordinates/sectors.ts +115 -0
- package/src/data/capabilities.ts +31 -16
- package/src/data/capability-formulas.ts +21 -14
- package/src/data/catalog.ts +0 -5
- package/src/data/colors.ts +13 -47
- package/src/data/entities.json +154 -10
- package/src/data/item-ids.ts +31 -12
- package/src/data/items.json +192 -74
- package/src/data/kind-registry.json +131 -0
- package/src/data/kind-registry.ts +172 -0
- package/src/data/metadata.ts +166 -48
- package/src/data/recipes-runtime.ts +5 -23
- package/src/data/recipes.json +1003 -163
- package/src/derivation/build-methods.test.ts +13 -0
- package/src/derivation/build-methods.ts +48 -0
- package/src/derivation/capabilities.test.ts +151 -0
- package/src/derivation/capabilities.ts +527 -0
- package/src/derivation/capability-mappings.ts +58 -16
- package/src/derivation/crafting.ts +36 -33
- package/src/derivation/index.ts +28 -2
- package/src/derivation/recipe-usage.test.ts +81 -0
- package/src/derivation/recipe-usage.ts +141 -0
- package/src/derivation/reserve-regen.ts +34 -0
- package/src/derivation/resources.ts +98 -19
- package/src/derivation/rollups.test.ts +55 -0
- package/src/derivation/rollups.ts +56 -0
- package/src/derivation/stars.test.ts +67 -0
- package/src/derivation/stars.ts +25 -0
- package/src/derivation/stats.ts +6 -6
- package/src/derivation/stratum.ts +26 -22
- package/src/derivation/tiers.ts +40 -7
- package/src/derivation/upgrades.ts +14 -0
- package/src/derivation/wormhole.ts +141 -0
- package/src/entities/entity.ts +98 -0
- package/src/entities/gamestate.ts +3 -28
- package/src/entities/makers.ts +131 -134
- package/src/entities/slot-multiplier.ts +43 -0
- package/src/errors.ts +12 -16
- package/src/format.ts +26 -4
- package/src/index-module.ts +290 -34
- package/src/managers/actions.ts +602 -105
- package/src/managers/base.ts +6 -2
- package/src/managers/cluster.test.ts +39 -0
- package/src/managers/cluster.ts +53 -0
- package/src/managers/construction-types.ts +80 -0
- package/src/managers/construction.ts +412 -0
- package/src/managers/context.ts +29 -1
- package/src/managers/coordinates.ts +14 -0
- package/src/managers/entities.ts +27 -66
- package/src/managers/epochs.ts +40 -0
- package/src/managers/flatten-gather-plan.test.ts +80 -0
- package/src/managers/index.ts +20 -1
- package/src/managers/locations.ts +25 -29
- package/src/managers/nft.test.ts +14 -0
- package/src/managers/nft.ts +70 -0
- package/src/managers/players.ts +25 -0
- package/src/managers/plot.ts +122 -0
- package/src/nft/atomicassets.abi.json +1342 -0
- package/src/nft/atomicassets.ts +237 -0
- package/src/nft/atomicdata.ts +130 -0
- package/src/nft/buildImmutableData.ts +336 -0
- package/src/nft/description.ts +104 -36
- package/src/nft/index.ts +3 -0
- package/src/planner/index.ts +242 -0
- package/src/planner/planner.test.ts +325 -0
- package/src/resolution/describe-module.ts +43 -25
- package/src/resolution/display-name.ts +38 -10
- package/src/resolution/resolve-item.test.ts +48 -0
- package/src/resolution/resolve-item.ts +74 -47
- package/src/scan/index.ts +244 -0
- package/src/scan/scan-wasm.base64.ts +2 -0
- package/src/scheduling/accessor.ts +65 -23
- package/src/scheduling/availability.ts +108 -0
- package/src/scheduling/cancel.test.ts +348 -0
- package/src/scheduling/cancel.ts +209 -0
- package/src/scheduling/energy.ts +47 -0
- package/src/scheduling/idle-resolve.ts +45 -0
- package/src/scheduling/lane-core.ts +128 -0
- package/src/scheduling/lanes.test.ts +249 -0
- package/src/scheduling/lanes.ts +198 -0
- package/src/scheduling/projection.ts +210 -107
- package/src/scheduling/schedule.ts +240 -117
- package/src/scheduling/task-cargo.ts +46 -0
- package/src/scheduling/unwrap.test.ts +86 -0
- package/src/scheduling/unwrap.ts +190 -0
- package/src/shipload.ts +26 -1
- package/src/subscriptions/manager.cluster.test.ts +51 -0
- package/src/subscriptions/manager.ts +245 -172
- package/src/subscriptions/mappers.ts +5 -8
- package/src/subscriptions/types.ts +28 -3
- package/src/testing/catalog-hash.ts +19 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/projection-parity.ts +167 -0
- package/src/travel/reach.ts +23 -0
- package/src/travel/route-planner.ts +277 -0
- package/src/travel/travel.ts +148 -117
- package/src/types/capabilities.ts +29 -6
- package/src/types/entity.ts +3 -3
- package/src/types/index.ts +0 -1
- package/src/types.ts +36 -14
- package/src/utils/cargo.test.ts +14 -0
- package/src/utils/cargo.ts +29 -0
- package/src/utils/display-name.ts +70 -0
- package/src/utils/system.ts +36 -24
- package/src/capabilities/loading.ts +0 -8
- package/src/entities/container.ts +0 -123
- package/src/entities/ship-deploy.ts +0 -295
- package/src/entities/ship.ts +0 -221
- package/src/entities/warehouse.ts +0 -127
- package/src/types/entity-traits.ts +0 -69
|
@@ -1,193 +1,316 @@
|
|
|
1
1
|
import type {ServerContract} from '../contracts'
|
|
2
2
|
import {TaskType} from '../types'
|
|
3
|
+
import * as core from './lane-core'
|
|
3
4
|
|
|
4
5
|
type Schedule = ServerContract.Types.schedule
|
|
5
6
|
type Task = ServerContract.Types.task
|
|
7
|
+
type Lane = ServerContract.Types.lane
|
|
8
|
+
type Hold = ServerContract.Types.hold
|
|
9
|
+
|
|
10
|
+
export const LANE_MOBILITY = 0
|
|
11
|
+
export const LANE_BARRIER = 255
|
|
6
12
|
|
|
7
13
|
export interface ScheduleData {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
14
|
+
lanes?: Lane[]
|
|
15
|
+
holds?: Hold[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface LaneView {
|
|
19
|
+
laneKey: number
|
|
20
|
+
schedule: Schedule
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
laneStartsIn,
|
|
25
|
+
currentTaskIndexForLane,
|
|
26
|
+
laneTaskComplete,
|
|
27
|
+
laneTaskInProgress,
|
|
28
|
+
laneCompletesAt,
|
|
29
|
+
currentTaskProgressFloatForLane,
|
|
30
|
+
} from './lane-core'
|
|
31
|
+
|
|
32
|
+
export function getLanes(entity: ScheduleData): LaneView[] {
|
|
33
|
+
const lanes = entity.lanes
|
|
34
|
+
if (!lanes || lanes.length === 0) return []
|
|
35
|
+
return lanes.map((l) => ({laneKey: l.lane_key.toNumber(), schedule: l.schedule}))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getLane(entity: ScheduleData, laneKey: number): LaneView | undefined {
|
|
39
|
+
const lanes = entity.lanes
|
|
40
|
+
if (!lanes) return undefined
|
|
41
|
+
for (const l of lanes) {
|
|
42
|
+
if (l.lane_key.toNumber() === laneKey) return {laneKey, schedule: l.schedule}
|
|
43
|
+
}
|
|
44
|
+
return undefined
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function mobilityLane(entity: ScheduleData): LaneView | undefined {
|
|
48
|
+
return getLane(entity, LANE_MOBILITY)
|
|
29
49
|
}
|
|
30
50
|
|
|
31
51
|
export function hasSchedule(entity: ScheduleData): boolean {
|
|
32
|
-
|
|
52
|
+
const lanes = entity.lanes
|
|
53
|
+
if (!lanes) return false
|
|
54
|
+
return lanes.some((l) => l.schedule.tasks.length > 0)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function hasHolds(entity: ScheduleData): boolean {
|
|
58
|
+
const holds = entity.holds
|
|
59
|
+
return !!holds && holds.length > 0
|
|
33
60
|
}
|
|
34
61
|
|
|
35
62
|
export function isIdle(entity: ScheduleData): boolean {
|
|
36
|
-
return !hasSchedule(entity)
|
|
63
|
+
return !hasSchedule(entity) && !hasHolds(entity)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function isEntityIdle(entity: ScheduleData, now: Date): boolean {
|
|
67
|
+
if (hasHolds(entity)) return false
|
|
68
|
+
const lanes = entity.lanes
|
|
69
|
+
if (!lanes) return true
|
|
70
|
+
return lanes.every((l) => core.currentTaskIndexForLane(l.schedule, now) < 0)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function entityIdleAt(entity: ScheduleData, _now: Date): Date | undefined {
|
|
74
|
+
const lanes = entity.lanes
|
|
75
|
+
if (!lanes) return undefined
|
|
76
|
+
let maxMs: number | undefined
|
|
77
|
+
for (const l of lanes) {
|
|
78
|
+
if (l.schedule.tasks.length === 0) continue
|
|
79
|
+
const endMs = l.schedule.started.toDate().getTime() + core.laneDuration(l.schedule) * 1000
|
|
80
|
+
if (maxMs === undefined || endMs > maxMs) maxMs = endMs
|
|
81
|
+
}
|
|
82
|
+
return maxMs === undefined ? undefined : new Date(maxMs)
|
|
37
83
|
}
|
|
38
84
|
|
|
39
85
|
export function getTasks(entity: ScheduleData): Task[] {
|
|
40
|
-
|
|
86
|
+
const lanes = entity.lanes
|
|
87
|
+
if (!lanes) return []
|
|
88
|
+
return lanes.flatMap((l) => l.schedule.tasks)
|
|
41
89
|
}
|
|
42
90
|
|
|
43
91
|
export function scheduleDuration(entity: ScheduleData): number {
|
|
44
|
-
|
|
45
|
-
|
|
92
|
+
let max = 0
|
|
93
|
+
for (const l of entity.lanes ?? []) max = Math.max(max, core.laneDuration(l.schedule))
|
|
94
|
+
return max
|
|
46
95
|
}
|
|
47
96
|
|
|
48
97
|
export function scheduleElapsed(entity: ScheduleData, now: Date): number {
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
return Math.max(0, elapsed)
|
|
98
|
+
let max = 0
|
|
99
|
+
for (const l of entity.lanes ?? []) max = Math.max(max, core.laneElapsed(l.schedule, now))
|
|
100
|
+
return max
|
|
53
101
|
}
|
|
54
102
|
|
|
55
103
|
export function scheduleRemaining(entity: ScheduleData, now: Date): number {
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
104
|
+
let remaining = 0
|
|
105
|
+
for (const l of entity.lanes ?? []) {
|
|
106
|
+
remaining = Math.max(remaining, core.laneRemaining(l.schedule, now))
|
|
107
|
+
}
|
|
108
|
+
return remaining
|
|
60
109
|
}
|
|
61
110
|
|
|
62
111
|
export function scheduleComplete(entity: ScheduleData, now: Date): boolean {
|
|
63
|
-
|
|
112
|
+
const lanes = entity.lanes
|
|
113
|
+
if (!lanes) return false
|
|
114
|
+
let hasAnyTask = false
|
|
115
|
+
let remaining = 0
|
|
116
|
+
for (const l of lanes) {
|
|
117
|
+
if (l.schedule.tasks.length > 0) hasAnyTask = true
|
|
118
|
+
remaining = Math.max(remaining, core.laneRemaining(l.schedule, now))
|
|
119
|
+
}
|
|
120
|
+
if (!hasAnyTask) return false
|
|
121
|
+
return remaining === 0
|
|
64
122
|
}
|
|
65
123
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
let timeAccum = 0
|
|
71
|
-
|
|
72
|
-
for (let i = 0; i < entity.schedule.tasks.length; i++) {
|
|
73
|
-
const taskDuration = entity.schedule.tasks[i].duration.toNumber()
|
|
74
|
-
if (elapsed < timeAccum + taskDuration) {
|
|
75
|
-
return i
|
|
76
|
-
}
|
|
77
|
-
timeAccum += taskDuration
|
|
124
|
+
// Mirrors contract lane_front_complete: any lane whose front task is complete and non-reserved.
|
|
125
|
+
export function hasResolvable(entity: ScheduleData, now: Date): boolean {
|
|
126
|
+
for (const l of entity.lanes ?? []) {
|
|
127
|
+
if (core.laneTaskComplete(l.schedule, 0, now)) return true
|
|
78
128
|
}
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
79
131
|
|
|
80
|
-
|
|
132
|
+
export function currentTaskForLane(
|
|
133
|
+
entity: ScheduleData,
|
|
134
|
+
laneKey: number,
|
|
135
|
+
now: Date
|
|
136
|
+
): Task | undefined {
|
|
137
|
+
const lane = getLane(entity, laneKey)
|
|
138
|
+
return lane ? core.currentTask(lane.schedule, now) : undefined
|
|
81
139
|
}
|
|
82
140
|
|
|
83
|
-
export function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
141
|
+
export function currentTaskTypeForLane(
|
|
142
|
+
entity: ScheduleData,
|
|
143
|
+
laneKey: number,
|
|
144
|
+
now: Date
|
|
145
|
+
): TaskType | undefined {
|
|
146
|
+
const lane = getLane(entity, laneKey)
|
|
147
|
+
return lane ? core.currentTaskType(lane.schedule, now) : undefined
|
|
87
148
|
}
|
|
88
149
|
|
|
89
|
-
export function
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
150
|
+
export function activeTasks(entity: ScheduleData, now: Date): Task[] {
|
|
151
|
+
const out: Task[] = []
|
|
152
|
+
for (const l of entity.lanes ?? []) {
|
|
153
|
+
const idx = core.currentTaskIndexForLane(l.schedule, now)
|
|
154
|
+
if (idx >= 0) out.push(l.schedule.tasks[idx])
|
|
155
|
+
}
|
|
156
|
+
return out
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface ResolvedEvent {
|
|
160
|
+
laneKey: number
|
|
161
|
+
taskIndex: number
|
|
162
|
+
task: Task
|
|
163
|
+
completesAt: Date
|
|
93
164
|
}
|
|
94
165
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
166
|
+
// Canonical lane-front order (mirrors contract front_precedes): completion, then RECHARGE-last, then lane key.
|
|
167
|
+
function frontPrecedes(
|
|
168
|
+
a: {completesAt: Date; task: Task; laneKey: number},
|
|
169
|
+
b: {completesAt: Date; task: Task; laneKey: number}
|
|
170
|
+
): number {
|
|
171
|
+
if (a.completesAt.getTime() !== b.completesAt.getTime()) {
|
|
172
|
+
return a.completesAt.getTime() - b.completesAt.getTime()
|
|
100
173
|
}
|
|
101
|
-
|
|
174
|
+
const aRecharge = a.task.type.toNumber() === TaskType.RECHARGE
|
|
175
|
+
const bRecharge = b.task.type.toNumber() === TaskType.RECHARGE
|
|
176
|
+
if (aRecharge !== bRecharge) return aRecharge ? 1 : -1
|
|
177
|
+
return a.laneKey - b.laneKey
|
|
102
178
|
}
|
|
103
179
|
|
|
104
|
-
|
|
105
|
-
|
|
180
|
+
// Completed lane-fronts in canonical order (mirrors contract front_precedes).
|
|
181
|
+
export function resolveOrder(entity: ScheduleData, now: Date): ResolvedEvent[] {
|
|
182
|
+
const events: ResolvedEvent[] = []
|
|
183
|
+
for (const l of entity.lanes ?? []) {
|
|
184
|
+
const laneKey = l.lane_key.toNumber()
|
|
185
|
+
const startedMs = l.schedule.started.toDate().getTime()
|
|
186
|
+
let endSec = 0
|
|
187
|
+
for (let i = 0; i < l.schedule.tasks.length; i++) {
|
|
188
|
+
const task = l.schedule.tasks[i]
|
|
189
|
+
endSec += task.duration.toNumber()
|
|
190
|
+
const completesAt = new Date(startedMs + endSec * 1000)
|
|
191
|
+
if (completesAt.getTime() > now.getTime()) break
|
|
192
|
+
events.push({laneKey, taskIndex: i, task, completesAt})
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
events.sort(frontPrecedes)
|
|
196
|
+
return events
|
|
197
|
+
}
|
|
106
198
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
199
|
+
export interface OrderedTask {
|
|
200
|
+
laneKey: number
|
|
201
|
+
taskIndex: number
|
|
202
|
+
task: Task
|
|
203
|
+
startsAt: Date
|
|
204
|
+
completesAt: Date
|
|
205
|
+
}
|
|
110
206
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
207
|
+
// Every task across all lanes in canonical order (mirrors contract front_precedes).
|
|
208
|
+
export function orderedTasks(entity: ScheduleData): OrderedTask[] {
|
|
209
|
+
const out: OrderedTask[] = []
|
|
210
|
+
for (const l of entity.lanes ?? []) {
|
|
211
|
+
const laneKey = l.lane_key.toNumber()
|
|
212
|
+
const startedMs = l.schedule.started.toDate().getTime()
|
|
213
|
+
let endSec = 0
|
|
214
|
+
for (let i = 0; i < l.schedule.tasks.length; i++) {
|
|
215
|
+
const task = l.schedule.tasks[i]
|
|
216
|
+
const startsAt = new Date(startedMs + endSec * 1000)
|
|
217
|
+
endSec += task.duration.toNumber()
|
|
218
|
+
const completesAt = new Date(startedMs + endSec * 1000)
|
|
219
|
+
out.push({laneKey, taskIndex: i, task, startsAt, completesAt})
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
out.sort(frontPrecedes)
|
|
223
|
+
return out
|
|
114
224
|
}
|
|
115
225
|
|
|
116
|
-
export function
|
|
117
|
-
|
|
226
|
+
export function laneRemainingOf(entity: ScheduleData, laneKey: number, now: Date): number {
|
|
227
|
+
const lane = getLane(entity, laneKey)
|
|
228
|
+
return lane ? core.laneRemaining(lane.schedule, now) : 0
|
|
229
|
+
}
|
|
118
230
|
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
return
|
|
231
|
+
export function laneStartsInOf(entity: ScheduleData, laneKey: number, now: Date): number {
|
|
232
|
+
const lane = getLane(entity, laneKey)
|
|
233
|
+
return lane ? core.laneStartsIn(lane.schedule, now) : 0
|
|
122
234
|
}
|
|
123
235
|
|
|
124
|
-
export function
|
|
125
|
-
|
|
236
|
+
export function laneCompleteOf(entity: ScheduleData, laneKey: number, now: Date): boolean {
|
|
237
|
+
const lane = getLane(entity, laneKey)
|
|
238
|
+
return lane ? core.laneComplete(lane.schedule, now) : false
|
|
239
|
+
}
|
|
126
240
|
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
return
|
|
241
|
+
export function laneProgressOf(entity: ScheduleData, laneKey: number, now: Date): number {
|
|
242
|
+
const lane = getLane(entity, laneKey)
|
|
243
|
+
return lane ? core.laneProgress(lane.schedule, now) : 0
|
|
130
244
|
}
|
|
131
245
|
|
|
132
|
-
export function
|
|
133
|
-
|
|
246
|
+
export function laneTaskElapsedOf(
|
|
247
|
+
entity: ScheduleData,
|
|
248
|
+
laneKey: number,
|
|
249
|
+
index: number,
|
|
250
|
+
now: Date
|
|
251
|
+
): number {
|
|
252
|
+
const lane = getLane(entity, laneKey)
|
|
253
|
+
return lane ? core.laneTaskElapsed(lane.schedule, index, now) : 0
|
|
254
|
+
}
|
|
134
255
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
256
|
+
export function laneTaskRemainingOf(
|
|
257
|
+
entity: ScheduleData,
|
|
258
|
+
laneKey: number,
|
|
259
|
+
index: number,
|
|
260
|
+
now: Date
|
|
261
|
+
): number {
|
|
262
|
+
const lane = getLane(entity, laneKey)
|
|
263
|
+
return lane ? core.laneTaskRemaining(lane.schedule, index, now) : 0
|
|
138
264
|
}
|
|
139
265
|
|
|
140
|
-
export function
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return
|
|
266
|
+
export function laneTaskCompleteOf(
|
|
267
|
+
entity: ScheduleData,
|
|
268
|
+
laneKey: number,
|
|
269
|
+
index: number,
|
|
270
|
+
now: Date
|
|
271
|
+
): boolean {
|
|
272
|
+
const lane = getLane(entity, laneKey)
|
|
273
|
+
return lane ? core.laneTaskComplete(lane.schedule, index, now) : false
|
|
148
274
|
}
|
|
149
275
|
|
|
150
|
-
export function
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const taskStartMs = startedMs + getTaskStartTime(entity, index) * 1000
|
|
159
|
-
const elapsedMs = now.getTime() - taskStartMs
|
|
160
|
-
if (elapsedMs <= 0) return 0
|
|
161
|
-
return Math.min(1, elapsedMs / durationMs)
|
|
276
|
+
export function laneTaskInProgressOf(
|
|
277
|
+
entity: ScheduleData,
|
|
278
|
+
laneKey: number,
|
|
279
|
+
index: number,
|
|
280
|
+
now: Date
|
|
281
|
+
): boolean {
|
|
282
|
+
const lane = getLane(entity, laneKey)
|
|
283
|
+
return lane ? core.laneTaskInProgress(lane.schedule, index, now) : false
|
|
162
284
|
}
|
|
163
285
|
|
|
164
|
-
export function
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
const elapsed = scheduleElapsed(entity, now)
|
|
168
|
-
return Math.min(1, elapsed / duration)
|
|
286
|
+
export function currentTaskIndexOf(entity: ScheduleData, laneKey: number, now: Date): number {
|
|
287
|
+
const lane = getLane(entity, laneKey)
|
|
288
|
+
return lane ? core.currentTaskIndexForLane(lane.schedule, now) : -1
|
|
169
289
|
}
|
|
170
290
|
|
|
171
|
-
|
|
172
|
-
return
|
|
291
|
+
function entityDoesTaskType(entity: ScheduleData, taskType: TaskType, now: Date): boolean {
|
|
292
|
+
return activeTasks(entity, now).some((t) => t.type.toNumber() === taskType)
|
|
173
293
|
}
|
|
174
294
|
|
|
175
295
|
export function isInFlight(entity: ScheduleData, now: Date): boolean {
|
|
176
|
-
|
|
296
|
+
const lane = mobilityLane(entity)
|
|
297
|
+
if (!lane) return false
|
|
298
|
+
const t = core.currentTaskType(lane.schedule, now)
|
|
299
|
+
return t === TaskType.TRAVEL || t === TaskType.TRANSIT
|
|
177
300
|
}
|
|
178
301
|
|
|
179
302
|
export function isRecharging(entity: ScheduleData, now: Date): boolean {
|
|
180
|
-
return
|
|
303
|
+
return entityDoesTaskType(entity, TaskType.RECHARGE, now)
|
|
181
304
|
}
|
|
182
305
|
|
|
183
306
|
export function isLoading(entity: ScheduleData, now: Date): boolean {
|
|
184
|
-
return
|
|
307
|
+
return entityDoesTaskType(entity, TaskType.LOAD, now)
|
|
185
308
|
}
|
|
186
309
|
|
|
187
310
|
export function isUnloading(entity: ScheduleData, now: Date): boolean {
|
|
188
|
-
return
|
|
311
|
+
return entityDoesTaskType(entity, TaskType.UNLOAD, now)
|
|
189
312
|
}
|
|
190
313
|
|
|
191
314
|
export function isGathering(entity: ScheduleData, now: Date): boolean {
|
|
192
|
-
return
|
|
315
|
+
return entityDoesTaskType(entity, TaskType.GATHER, now)
|
|
193
316
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type {ServerContract} from '../contracts'
|
|
2
|
+
import {TaskType} from '../types'
|
|
3
|
+
|
|
4
|
+
export type TaskCargoDirection = 'in' | 'out'
|
|
5
|
+
|
|
6
|
+
export interface TaskCargoChange {
|
|
7
|
+
direction: TaskCargoDirection
|
|
8
|
+
item_id: number
|
|
9
|
+
stats: bigint
|
|
10
|
+
modules: ServerContract.Types.module_entry[]
|
|
11
|
+
quantity: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function toChange(
|
|
15
|
+
item: ServerContract.Types.cargo_item,
|
|
16
|
+
direction: TaskCargoDirection
|
|
17
|
+
): TaskCargoChange {
|
|
18
|
+
return {
|
|
19
|
+
direction,
|
|
20
|
+
item_id: Number(item.item_id),
|
|
21
|
+
stats: BigInt(item.stats.toString()),
|
|
22
|
+
modules: item.modules ?? [],
|
|
23
|
+
quantity: Number(item.quantity),
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function taskCargoChanges(task: ServerContract.Types.task): TaskCargoChange[] {
|
|
28
|
+
const items = task.cargo ?? []
|
|
29
|
+
if (items.length === 0) return []
|
|
30
|
+
switch (Number(task.type)) {
|
|
31
|
+
case TaskType.LOAD:
|
|
32
|
+
case TaskType.UNWRAP:
|
|
33
|
+
return items.map((i) => toChange(i, 'in'))
|
|
34
|
+
case TaskType.GATHER:
|
|
35
|
+
return task.entitytarget ? [] : items.map((i) => toChange(i, 'in'))
|
|
36
|
+
case TaskType.UNLOAD:
|
|
37
|
+
return items.map((i) => toChange(i, 'out'))
|
|
38
|
+
case TaskType.CRAFT:
|
|
39
|
+
return [
|
|
40
|
+
...items.slice(0, -1).map((i) => toChange(i, 'out')),
|
|
41
|
+
toChange(items[items.length - 1], 'in'),
|
|
42
|
+
]
|
|
43
|
+
default:
|
|
44
|
+
return []
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {describe, expect, test} from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
derivedLoaders,
|
|
4
|
+
unwrapTransitDuration,
|
|
5
|
+
unwrapLoadDuration,
|
|
6
|
+
estimateUnwrapDuration,
|
|
7
|
+
incomingHoldMass,
|
|
8
|
+
projectedPeakCargomass,
|
|
9
|
+
} from './unwrap'
|
|
10
|
+
|
|
11
|
+
describe('unwrap duration mirror', () => {
|
|
12
|
+
test('derivedLoaders aggregates lanes like derived_loaders()', () => {
|
|
13
|
+
expect(derivedLoaders([])).toBeNull()
|
|
14
|
+
expect(
|
|
15
|
+
derivedLoaders([
|
|
16
|
+
{mass: 1000, thrust: 10},
|
|
17
|
+
{mass: 1400, thrust: 20},
|
|
18
|
+
])
|
|
19
|
+
).toEqual({mass: 1200, thrust: 30, quantity: 2}) // floor(2400/2)=1200, sum thrust, count
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('transit floors distance then flight time', () => {
|
|
23
|
+
// distance = floor(sqrt(3^2+4^2)*10000)=50000; accel=400/mass*10000; flight=floor(2*sqrt(d/accel))
|
|
24
|
+
const mass = 1000
|
|
25
|
+
const accel = (400 / mass) * 10000
|
|
26
|
+
const expected = Math.floor(2 * Math.sqrt(50000 / accel))
|
|
27
|
+
expect(unwrapTransitDuration(mass, {x: 0, y: 0}, {x: 3, y: 4})).toBe(expected)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('load uses altitude z, adds loader mass, divides by quantity', () => {
|
|
31
|
+
const loaders = {mass: 1200, thrust: 30, quantity: 2}
|
|
32
|
+
const itemMass = 800
|
|
33
|
+
const accel = (30 / (itemMass + 1200)) * 10000
|
|
34
|
+
const flight = Math.floor(2 * Math.sqrt(3000 / accel))
|
|
35
|
+
expect(unwrapLoadDuration(loaders, itemMass, 3000)).toBe(Math.floor(flight / 2))
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('zero item mass and no loaders are safe', () => {
|
|
39
|
+
expect(unwrapTransitDuration(0, {x: 0, y: 0}, {x: 9, y: 9})).toBe(0)
|
|
40
|
+
expect(unwrapLoadDuration(null, 500, 3000)).toBe(0)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('load altitude is floored so ground-level (z=0) destinations are not instant', () => {
|
|
44
|
+
const loaders = {mass: 500, thrust: 200, quantity: 1}
|
|
45
|
+
const atZero = unwrapLoadDuration(loaders, 1000, 0)
|
|
46
|
+
const at800 = unwrapLoadDuration(loaders, 1000, 800)
|
|
47
|
+
expect(atZero).toBeGreaterThan(0)
|
|
48
|
+
expect(atZero).toBe(at800)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('estimateUnwrapDuration uses worst-loader baseline when dest has no loaders', () => {
|
|
52
|
+
// IRON (101); same coords so transit is 0 and the estimate is baseline load alone
|
|
53
|
+
const item = {itemId: 101, quantity: 10, modules: [], originX: 0, originY: 0}
|
|
54
|
+
const bareDest = {loader_lanes: [], coordinates: {x: 0, y: 0, z: 800}}
|
|
55
|
+
|
|
56
|
+
const bare = estimateUnwrapDuration(bareDest, item)
|
|
57
|
+
expect(bare).toBeGreaterThan(0)
|
|
58
|
+
|
|
59
|
+
const loadedDest = {
|
|
60
|
+
loader_lanes: [{mass: 500, thrust: 200}],
|
|
61
|
+
coordinates: {x: 0, y: 0, z: 800},
|
|
62
|
+
}
|
|
63
|
+
const loaded = estimateUnwrapDuration(loadedDest, item)
|
|
64
|
+
expect(loaded).toBeLessThanOrEqual(bare)
|
|
65
|
+
|
|
66
|
+
expect(unwrapLoadDuration(null, 500, 3000)).toBe(0)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('incomingHoldMass sums incoming-kind hold mass', () => {
|
|
71
|
+
expect(incomingHoldMass([])).toBe(0)
|
|
72
|
+
// PUSH(2) + FLIGHT(5) count; BUILD(4) does not
|
|
73
|
+
expect(
|
|
74
|
+
incomingHoldMass([
|
|
75
|
+
{kind: 2, incoming_mass: 100},
|
|
76
|
+
{kind: 4, incoming_mass: 999},
|
|
77
|
+
{kind: 5, incoming_mass: 50},
|
|
78
|
+
])
|
|
79
|
+
).toBe(150)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('projectedPeakCargomass tracks the running peak from cargomass', () => {
|
|
83
|
+
const entity = {cargomass: 1000, lanes: [], cargo: [], schedule: undefined} as never
|
|
84
|
+
// No pending tasks: peak = base + candidate add.
|
|
85
|
+
expect(projectedPeakCargomass(entity, new Date(0), 500)).toBe(1500)
|
|
86
|
+
})
|