@shipload/sdk 1.0.0-next.36 → 1.0.0-next.37
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/shipload.d.ts +53 -1
- package/lib/shipload.js +120 -2
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +117 -3
- package/lib/shipload.m.js.map +1 -1
- package/package.json +1 -1
- package/src/index-module.ts +15 -0
- package/src/travel/reach.ts +23 -0
- package/src/travel/route-planner.ts +157 -0
package/package.json
CHANGED
package/src/index-module.ts
CHANGED
|
@@ -191,6 +191,21 @@ export type {
|
|
|
191
191
|
HasScheduleAndLocation,
|
|
192
192
|
} from './travel/travel'
|
|
193
193
|
|
|
194
|
+
export {planRoute, sdkSystemGraph} from './travel/route-planner'
|
|
195
|
+
export type {
|
|
196
|
+
Coord,
|
|
197
|
+
Neighbor,
|
|
198
|
+
SystemGraph,
|
|
199
|
+
RoutePlan,
|
|
200
|
+
RouteFailure,
|
|
201
|
+
RouteResult,
|
|
202
|
+
RouteFailureReason,
|
|
203
|
+
PlanRouteParams,
|
|
204
|
+
} from './travel/route-planner'
|
|
205
|
+
|
|
206
|
+
export {computePerLegReach, computeGroupPerLegReach} from './travel/reach'
|
|
207
|
+
export type {ReachStats} from './travel/reach'
|
|
208
|
+
|
|
194
209
|
export * as schedule from './scheduling/schedule'
|
|
195
210
|
export {LANE_MOBILITY, LANE_BARRIER} from './scheduling/schedule'
|
|
196
211
|
export type {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface ReachStats {
|
|
2
|
+
generator?: {capacity: bigint}
|
|
3
|
+
engines?: {drain: bigint}
|
|
4
|
+
hauler?: {drain: bigint}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function computePerLegReach(s: ReachStats, haulCount = 0): number {
|
|
8
|
+
const capacity = s.generator?.capacity
|
|
9
|
+
const drain = s.engines?.drain
|
|
10
|
+
if (capacity === undefined || drain === undefined || drain === 0n) {
|
|
11
|
+
throw new Error('entity has no usable engine/generator (cannot compute per-leg reach)')
|
|
12
|
+
}
|
|
13
|
+
const haulDrain = s.hauler && haulCount > 0 ? s.hauler.drain * BigInt(haulCount) : 0n
|
|
14
|
+
return Number(capacity) / Number(drain + haulDrain)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function computeGroupPerLegReach(participants: ReachStats[], haulCount: number): number {
|
|
18
|
+
const movers = participants.filter((p) => p.engines !== undefined && p.engines.drain !== 0n)
|
|
19
|
+
if (movers.length === 0) {
|
|
20
|
+
throw new Error('group has no moving entity (cannot compute per-leg reach)')
|
|
21
|
+
}
|
|
22
|
+
return Math.min(...movers.map((p) => computePerLegReach(p, haulCount)))
|
|
23
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import {distanceBetweenPoints, findNearbyPlanets} from './travel'
|
|
2
|
+
import {hasSystem} from '../utils/system'
|
|
3
|
+
import {PRECISION} from '../types'
|
|
4
|
+
import {Checksum256, type Checksum256Type} from '@wharfkit/antelope'
|
|
5
|
+
|
|
6
|
+
export interface Coord {
|
|
7
|
+
x: number
|
|
8
|
+
y: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface Neighbor {
|
|
12
|
+
coord: Coord
|
|
13
|
+
dist: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SystemGraph {
|
|
17
|
+
hasSystem(c: Coord): boolean
|
|
18
|
+
nearby(c: Coord, reachTiles: number): Neighbor[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface RoutePlan {
|
|
22
|
+
ok: true
|
|
23
|
+
waypoints: Coord[]
|
|
24
|
+
legs: number
|
|
25
|
+
totalDistance: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type RouteFailureReason = 'empty-destination' | 'no-path' | 'max-legs'
|
|
29
|
+
|
|
30
|
+
export interface RouteFailure {
|
|
31
|
+
ok: false
|
|
32
|
+
reason: RouteFailureReason
|
|
33
|
+
furthest?: Coord
|
|
34
|
+
legsNeeded?: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type RouteResult = RoutePlan | RouteFailure
|
|
38
|
+
|
|
39
|
+
export interface PlanRouteParams {
|
|
40
|
+
origin: Coord
|
|
41
|
+
dest: Coord
|
|
42
|
+
perLegReach: number
|
|
43
|
+
graph: SystemGraph
|
|
44
|
+
corridorSlack?: number
|
|
45
|
+
nodeBudget?: number
|
|
46
|
+
maxLegs?: number
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const key = (c: Coord): string => `${c.x},${c.y}`
|
|
50
|
+
const sameCoord = (a: Coord, b: Coord): boolean => a.x === b.x && a.y === b.y
|
|
51
|
+
const dist = (a: Coord, b: Coord): number => Math.hypot(a.x - b.x, a.y - b.y)
|
|
52
|
+
|
|
53
|
+
export function planRoute(params: PlanRouteParams): RouteResult {
|
|
54
|
+
const {origin, dest, perLegReach, graph} = params
|
|
55
|
+
const corridorSlack = params.corridorSlack ?? perLegReach
|
|
56
|
+
const nodeBudget = params.nodeBudget ?? 5000
|
|
57
|
+
const maxLegs = params.maxLegs ?? 12
|
|
58
|
+
|
|
59
|
+
if (!graph.hasSystem(dest)) {
|
|
60
|
+
return {ok: false, reason: 'empty-destination'}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const straightLine = dist(origin, dest)
|
|
64
|
+
const heuristic = (c: Coord): number => Math.ceil(dist(c, dest) / perLegReach)
|
|
65
|
+
|
|
66
|
+
const gScore = new Map<string, number>([[key(origin), 0]])
|
|
67
|
+
const cameFrom = new Map<string, Coord>()
|
|
68
|
+
const frontier: {coord: Coord; g: number; f: number; remaining: number}[] = [
|
|
69
|
+
{coord: origin, g: 0, f: heuristic(origin), remaining: straightLine},
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
let furthest = origin
|
|
73
|
+
let furthestRemaining = dist(origin, dest)
|
|
74
|
+
let expansions = 0
|
|
75
|
+
let cappedByMaxLegs = false
|
|
76
|
+
|
|
77
|
+
while (frontier.length > 0) {
|
|
78
|
+
let bestIdx = 0
|
|
79
|
+
for (let i = 1; i < frontier.length; i++) {
|
|
80
|
+
const a = frontier[i]
|
|
81
|
+
const b = frontier[bestIdx]
|
|
82
|
+
if (a.f < b.f || (a.f === b.f && a.remaining < b.remaining)) {
|
|
83
|
+
bestIdx = i
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const current = frontier.splice(bestIdx, 1)[0]
|
|
87
|
+
|
|
88
|
+
if (sameCoord(current.coord, dest)) {
|
|
89
|
+
return reconstruct(cameFrom, origin, dest)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (current.remaining < furthestRemaining) {
|
|
93
|
+
furthestRemaining = current.remaining
|
|
94
|
+
furthest = current.coord
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (++expansions > nodeBudget) break
|
|
98
|
+
|
|
99
|
+
for (const n of graph.nearby(current.coord, perLegReach)) {
|
|
100
|
+
const inCorridor =
|
|
101
|
+
dist(origin, n.coord) + dist(n.coord, dest) <= straightLine + corridorSlack
|
|
102
|
+
if (!inCorridor) continue
|
|
103
|
+
|
|
104
|
+
const tentativeG = current.g + 1
|
|
105
|
+
if (tentativeG > maxLegs) {
|
|
106
|
+
cappedByMaxLegs = true
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
const nk = key(n.coord)
|
|
110
|
+
if (tentativeG < (gScore.get(nk) ?? Infinity)) {
|
|
111
|
+
gScore.set(nk, tentativeG)
|
|
112
|
+
cameFrom.set(nk, current.coord)
|
|
113
|
+
const remaining = dist(n.coord, dest)
|
|
114
|
+
frontier.push({
|
|
115
|
+
coord: n.coord,
|
|
116
|
+
g: tentativeG,
|
|
117
|
+
f: tentativeG + Math.ceil(remaining / perLegReach),
|
|
118
|
+
remaining,
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (cappedByMaxLegs) {
|
|
125
|
+
return {ok: false, reason: 'max-legs', furthest}
|
|
126
|
+
}
|
|
127
|
+
return {ok: false, reason: 'no-path', furthest}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function reconstruct(cameFrom: Map<string, Coord>, origin: Coord, dest: Coord): RoutePlan {
|
|
131
|
+
const path: Coord[] = [dest]
|
|
132
|
+
let cur = dest
|
|
133
|
+
let totalDistance = 0
|
|
134
|
+
while (!sameCoord(cur, origin)) {
|
|
135
|
+
const prev = cameFrom.get(key(cur))
|
|
136
|
+
if (!prev) break
|
|
137
|
+
totalDistance += dist(prev, cur)
|
|
138
|
+
path.unshift(prev)
|
|
139
|
+
cur = prev
|
|
140
|
+
}
|
|
141
|
+
const waypoints = path.slice(1)
|
|
142
|
+
return {ok: true, waypoints, legs: waypoints.length, totalDistance}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function sdkSystemGraph(seed: Checksum256Type): SystemGraph {
|
|
146
|
+
const s = Checksum256.from(seed)
|
|
147
|
+
return {
|
|
148
|
+
hasSystem: (c) => hasSystem(s, {x: c.x, y: c.y}),
|
|
149
|
+
nearby: (c, reachTiles) =>
|
|
150
|
+
findNearbyPlanets(s, {x: c.x, y: c.y}, reachTiles * PRECISION)
|
|
151
|
+
.map((d) => ({
|
|
152
|
+
coord: {x: Number(d.destination.x), y: Number(d.destination.y)},
|
|
153
|
+
dist: Number(d.distance) / PRECISION,
|
|
154
|
+
}))
|
|
155
|
+
.filter((n) => !(n.coord.x === c.x && n.coord.y === c.y)),
|
|
156
|
+
}
|
|
157
|
+
}
|