@shipload/sdk 1.0.0-next.51 → 1.0.0-next.52
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 +470 -157
- package/lib/shipload.js +2309 -492
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +2279 -491
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +123 -8
- package/lib/testing.js +450 -19
- package/lib/testing.js.map +1 -1
- package/lib/testing.m.js +451 -20
- package/lib/testing.m.js.map +1 -1
- package/package.json +1 -1
- package/src/capabilities/crafting.ts +10 -1
- package/src/capabilities/gathering.ts +1 -1
- package/src/capabilities/hauling.ts +0 -5
- package/src/capabilities/modules.ts +6 -0
- package/src/capabilities/movement.ts +1 -1
- package/src/contracts/server.ts +293 -13
- package/src/data/capabilities.ts +11 -2
- package/src/data/entities.json +235 -20
- package/src/data/item-ids.ts +10 -0
- package/src/data/items.json +61 -0
- package/src/data/kind-registry.json +53 -1
- package/src/data/kind-registry.ts +5 -0
- package/src/data/metadata.ts +74 -5
- package/src/data/recipes-runtime.ts +1 -0
- package/src/data/recipes.json +887 -118
- package/src/derivation/capabilities.test.ts +81 -4
- package/src/derivation/capabilities.ts +162 -57
- package/src/derivation/crafting.ts +2 -0
- package/src/derivation/recipe-usage.test.ts +10 -7
- package/src/derivation/rollups.ts +16 -0
- package/src/derivation/stat-scaling.ts +12 -0
- package/src/entities/makers.ts +10 -1
- package/src/index-module.ts +29 -3
- package/src/managers/actions.ts +77 -46
- package/src/managers/construction-types.ts +2 -2
- package/src/managers/construction.ts +15 -15
- package/src/managers/plot.ts +1 -1
- package/src/nft/buildImmutableData.ts +55 -9
- package/src/nft/description.ts +99 -36
- package/src/planner/planner.test.ts +50 -41
- package/src/resolution/resolve-item.test.ts +1 -1
- package/src/resolution/resolve-item.ts +26 -11
- package/src/scheduling/availability.ts +7 -6
- package/src/scheduling/cancel.test.ts +40 -6
- package/src/scheduling/cancel.ts +21 -29
- package/src/scheduling/jobs.ts +71 -0
- package/src/scheduling/lanes.ts +29 -0
- package/src/scheduling/projection.ts +42 -25
- package/src/scheduling/task-cargo.ts +1 -1
- package/src/testing/projection-parity.ts +2 -2
- package/src/travel/reach.ts +4 -6
- package/src/travel/route-planner.ts +60 -49
- package/src/travel/route-simulator.ts +3 -7
- package/src/travel/travel.ts +14 -5
- package/src/types/capabilities.ts +9 -3
- package/src/types.ts +5 -3
- package/src/managers/flatten-gather-plan.test.ts +0 -80
package/src/nft/description.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getModuleCapabilityType,
|
|
3
|
+
MODULE_BUILDER,
|
|
3
4
|
MODULE_CRAFTER,
|
|
4
5
|
MODULE_ENGINE,
|
|
5
6
|
MODULE_GATHERER,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
import {
|
|
14
15
|
ITEM_CONTAINER_T1_PACKED,
|
|
15
16
|
ITEM_CONTAINER_T2_PACKED,
|
|
17
|
+
ITEM_BUILDER_T1,
|
|
16
18
|
ITEM_CRAFTER_T1,
|
|
17
19
|
ITEM_ENGINE_T1,
|
|
18
20
|
ITEM_EXTRACTOR_T1_PACKED,
|
|
@@ -26,28 +28,56 @@ import {
|
|
|
26
28
|
ITEM_BATTERY_T1,
|
|
27
29
|
ITEM_LAUNCHER_T1,
|
|
28
30
|
ITEM_LOADER_T1,
|
|
31
|
+
ITEM_PROSPECTOR_T1_PACKED,
|
|
29
32
|
ITEM_PROSPECTOR_T2_PACKED,
|
|
33
|
+
ITEM_ROUSTABOUT_T1_PACKED,
|
|
30
34
|
ITEM_SHIP_T1_PACKED,
|
|
31
35
|
ITEM_STORAGE_T1,
|
|
36
|
+
ITEM_TENDER_T1_PACKED,
|
|
37
|
+
ITEM_TUG_T1_PACKED,
|
|
38
|
+
ITEM_PORTER_T1_PACKED,
|
|
39
|
+
ITEM_WRANGLER_T1_PACKED,
|
|
40
|
+
ITEM_DREDGER_T1_PACKED,
|
|
32
41
|
ITEM_WAREHOUSE_T1_PACKED,
|
|
33
42
|
ITEM_WARP_T1,
|
|
34
43
|
} from '../data/item-ids'
|
|
35
44
|
import {decodeStat} from '../derivation/crafting'
|
|
36
45
|
import {gathererDepthForTier} from '../derivation/capabilities'
|
|
37
46
|
import {getItem} from '../data/catalog'
|
|
47
|
+
import {ENTITY_SHIP, getPackedEntityType} from '../data/kind-registry'
|
|
48
|
+
import {getBaseHullmassFor} from '../derivation/capabilities'
|
|
49
|
+
import {computeEffectiveModuleStat} from '../derivation/stat-scaling'
|
|
38
50
|
|
|
39
51
|
function idiv(a: number, b: number): number {
|
|
40
52
|
return Math.floor(a / b)
|
|
41
53
|
}
|
|
42
54
|
|
|
43
|
-
export function
|
|
55
|
+
export function toWholeEnergy(milli: number): number {
|
|
56
|
+
return idiv(milli + 500, 1000)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isShipHull(itemId: number): boolean {
|
|
60
|
+
return getPackedEntityType(itemId)?.equals(ENTITY_SHIP) ?? false
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function sumPackedShipChannels(stats: bigint): number {
|
|
64
|
+
let sum = 0
|
|
65
|
+
for (let slot = 0; slot < 5; slot++) sum += decodeStat(stats, slot)
|
|
66
|
+
return sum
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function computeBaseHullmass(itemId: number, stats: bigint): number {
|
|
70
|
+
if (isShipHull(itemId)) {
|
|
71
|
+
return Math.floor(
|
|
72
|
+
(getBaseHullmassFor(itemId) * (10_000 - sumPackedShipChannels(stats))) / 10_000
|
|
73
|
+
)
|
|
74
|
+
}
|
|
44
75
|
const density = decodeStat(stats, 1)
|
|
45
|
-
return
|
|
76
|
+
return Math.floor((getBaseHullmassFor(itemId) * (2000 - density)) / 2000)
|
|
46
77
|
}
|
|
47
78
|
|
|
48
79
|
export function computeBaseCapacityShip(stats: bigint): number {
|
|
49
|
-
|
|
50
|
-
return Math.floor(5_000_000 * 6 ** (s / 1998))
|
|
80
|
+
return Math.floor(5_000_000 * 6 ** (sumPackedShipChannels(stats) / 4995))
|
|
51
81
|
}
|
|
52
82
|
|
|
53
83
|
export function computeBaseCapacityContainer(stats: bigint): number {
|
|
@@ -62,49 +92,75 @@ export function computeBaseCapacityWarehouse(stats: bigint): number {
|
|
|
62
92
|
|
|
63
93
|
export const computeEngineThrust = (vol: number): number => 400 + idiv(vol * 3, 4)
|
|
64
94
|
export const computeEngineDrain = (thm: number): number => 2 * Math.max(30, 50 - idiv(thm, 70))
|
|
65
|
-
export const ENGINE_DRAIN_BASE =
|
|
95
|
+
export const ENGINE_DRAIN_BASE = 156
|
|
66
96
|
export const ENGINE_DRAIN_REF_THRUST = 775
|
|
67
97
|
export const ENGINE_DRAIN_REF_THM = 500
|
|
68
98
|
|
|
69
99
|
export const computeTravelDrain = (totalThrust: number, avgThm: number): number => {
|
|
70
100
|
if (totalThrust <= 0) return 0
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
return
|
|
101
|
+
const thermalFactor = computeEngineDrain(avgThm) / computeEngineDrain(ENGINE_DRAIN_REF_THM)
|
|
102
|
+
const thrustFactor = Math.sqrt(ENGINE_DRAIN_REF_THRUST / totalThrust)
|
|
103
|
+
return Math.floor(ENGINE_DRAIN_BASE * 1000 * thermalFactor * thrustFactor)
|
|
74
104
|
}
|
|
75
|
-
export const computeGeneratorCap = (com: number): number =>
|
|
76
|
-
export const computeGeneratorRech = (fin: number): number =>
|
|
105
|
+
export const computeGeneratorCap = (com: number): number => 1_300_000 + com * 500
|
|
106
|
+
export const computeGeneratorRech = (fin: number): number => 2000 + fin * 6
|
|
77
107
|
export const computeGathererYield = (str: number): number => 200 + str
|
|
78
108
|
export const computeGathererDrain = (con: number): number =>
|
|
79
|
-
2 * Math.max(
|
|
109
|
+
2 * Math.max(250_000, 1_250_000 - con * 1250)
|
|
80
110
|
export const computeGathererDepth = (tol: number, tier: number): number =>
|
|
81
111
|
gathererDepthForTier(tol, tier)
|
|
82
112
|
export const computeLoaderMass = (ins: number): number => Math.max(200, 2000 - ins * 2)
|
|
83
113
|
export const computeLoaderThrust = (pla: number): number => 1 + idiv(pla * pla, 10000)
|
|
84
114
|
export const computeCrafterSpeed = (rea: number): number => 100 + idiv(rea * 4, 5)
|
|
85
|
-
export const computeCrafterDrain = (fin: number): number =>
|
|
115
|
+
export const computeCrafterDrain = (fin: number): number =>
|
|
116
|
+
Math.max(5000, 30000 - idiv(fin * 1000, 33))
|
|
117
|
+
export const computeBuilderSpeed = (coh: number): number => 100 + idiv(coh * 4, 5)
|
|
118
|
+
export const computeBuilderDrain = (tol: number): number =>
|
|
119
|
+
Math.max(5000, 30000 - idiv(tol * 1000, 33))
|
|
86
120
|
export const computeHaulerCapacity = (fin: number, tier: number): number =>
|
|
87
121
|
Math.max(tier, tier + idiv(fin, 400))
|
|
88
122
|
export const computeHaulerEfficiency = (con: number): number => 2000 + con * 6
|
|
89
|
-
export const
|
|
123
|
+
export const supportDrainTierPercent = (tier: number): number => {
|
|
124
|
+
const clampedTier = Math.min(10, Math.max(1, Math.trunc(tier)))
|
|
125
|
+
return Math.max(50, 110 - clampedTier * 10)
|
|
126
|
+
}
|
|
127
|
+
const computeT1LogisticsDrain = (stat: number): number =>
|
|
128
|
+
Math.max(3000, 15000 - Math.min(12000, idiv(stat * 1000, 80)))
|
|
129
|
+
export const computeHaulerDrain = (conductivity: number, tier: number): number =>
|
|
130
|
+
idiv(computeT1LogisticsDrain(conductivity) * supportDrainTierPercent(tier), 100)
|
|
131
|
+
export const computeCargoBayDrain = (cohesion: number, tier: number): number =>
|
|
132
|
+
idiv(computeT1LogisticsDrain(cohesion) * 3 * supportDrainTierPercent(tier), 400)
|
|
90
133
|
export const computeWarpRange = (stat: number): number => 100 + stat * 3
|
|
91
134
|
export const computeCargoBayCapacity = (
|
|
92
135
|
strength: number,
|
|
93
136
|
density: number,
|
|
94
|
-
hardness: number
|
|
95
|
-
|
|
96
|
-
): number => 10_000_000 + idiv((strength + density + hardness + cohesion) * 50_000_000, 3996)
|
|
137
|
+
hardness: number
|
|
138
|
+
): number => 10_000_000 + idiv((strength + density + hardness) * 50_000_000, 2997)
|
|
97
139
|
export const computeBatteryBankCapacity = (
|
|
98
140
|
volatility: number,
|
|
99
141
|
thermal: number,
|
|
100
142
|
plasticity: number,
|
|
101
143
|
insulation: number
|
|
102
|
-
): number =>
|
|
144
|
+
): number => 2_500_000 + idiv((volatility + thermal + plasticity + insulation) * 7_500_000, 3996)
|
|
103
145
|
|
|
104
146
|
export function entityDisplayName(itemId: number): string {
|
|
105
147
|
switch (itemId) {
|
|
106
148
|
case ITEM_SHIP_T1_PACKED:
|
|
149
|
+
return 'Ship'
|
|
150
|
+
case ITEM_ROUSTABOUT_T1_PACKED:
|
|
107
151
|
return 'Roustabout'
|
|
152
|
+
case ITEM_PROSPECTOR_T1_PACKED:
|
|
153
|
+
return 'Prospector'
|
|
154
|
+
case ITEM_TENDER_T1_PACKED:
|
|
155
|
+
return 'Tender'
|
|
156
|
+
case ITEM_TUG_T1_PACKED:
|
|
157
|
+
return 'Tug'
|
|
158
|
+
case ITEM_PORTER_T1_PACKED:
|
|
159
|
+
return 'Porter'
|
|
160
|
+
case ITEM_WRANGLER_T1_PACKED:
|
|
161
|
+
return 'Wrangler'
|
|
162
|
+
case ITEM_DREDGER_T1_PACKED:
|
|
163
|
+
return 'Dredger'
|
|
108
164
|
case ITEM_WAREHOUSE_T1_PACKED:
|
|
109
165
|
return 'Warehouse'
|
|
110
166
|
case ITEM_EXTRACTOR_T1_PACKED:
|
|
@@ -118,7 +174,7 @@ export function entityDisplayName(itemId: number): string {
|
|
|
118
174
|
case ITEM_PROSPECTOR_T2_PACKED:
|
|
119
175
|
return 'Prospector'
|
|
120
176
|
case ITEM_HAULER_SHIP_T2_PACKED:
|
|
121
|
-
return '
|
|
177
|
+
return 'Tug'
|
|
122
178
|
default:
|
|
123
179
|
return 'Entity'
|
|
124
180
|
}
|
|
@@ -137,6 +193,8 @@ export function moduleDisplayName(itemId: number): string {
|
|
|
137
193
|
return 'Shuttle Bay'
|
|
138
194
|
case ITEM_CRAFTER_T1:
|
|
139
195
|
return 'Fabricator'
|
|
196
|
+
case ITEM_BUILDER_T1:
|
|
197
|
+
return 'Assembly Arm'
|
|
140
198
|
case ITEM_STORAGE_T1:
|
|
141
199
|
return 'Cargo Hold'
|
|
142
200
|
case ITEM_HAULER_T1:
|
|
@@ -165,15 +223,17 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
|
|
|
165
223
|
|
|
166
224
|
switch (subtype) {
|
|
167
225
|
case MODULE_ENGINE: {
|
|
168
|
-
const vol = decodeStat(stats, 0)
|
|
169
|
-
const thm = decodeStat(stats, 1)
|
|
226
|
+
const vol = computeEffectiveModuleStat(decodeStat(stats, 0))
|
|
227
|
+
const thm = computeEffectiveModuleStat(decodeStat(stats, 1))
|
|
170
228
|
out += ` Thrust ${computeEngineThrust(vol)} Drain ${computeEngineDrain(thm)}`
|
|
171
229
|
break
|
|
172
230
|
}
|
|
173
231
|
case MODULE_GENERATOR: {
|
|
174
|
-
const res = decodeStat(stats, 0)
|
|
175
|
-
const ref = decodeStat(stats, 1)
|
|
176
|
-
out += ` Capacity ${computeGeneratorCap(res)} Recharge ${
|
|
232
|
+
const res = computeEffectiveModuleStat(decodeStat(stats, 0))
|
|
233
|
+
const ref = computeEffectiveModuleStat(decodeStat(stats, 1))
|
|
234
|
+
out += ` Capacity ${toWholeEnergy(computeGeneratorCap(res))} Recharge ${toWholeEnergy(
|
|
235
|
+
computeGeneratorRech(ref)
|
|
236
|
+
)}`
|
|
177
237
|
break
|
|
178
238
|
}
|
|
179
239
|
case MODULE_GATHERER: {
|
|
@@ -184,7 +244,7 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
|
|
|
184
244
|
out += ` Yield ${computeGathererYield(str)} Depth ${computeGathererDepth(
|
|
185
245
|
tol,
|
|
186
246
|
tier
|
|
187
|
-
)} Drain ${computeGathererDrain(con)}`
|
|
247
|
+
)} Drain ${toWholeEnergy(computeGathererDrain(con))}`
|
|
188
248
|
break
|
|
189
249
|
}
|
|
190
250
|
case MODULE_LOADER: {
|
|
@@ -196,23 +256,30 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
|
|
|
196
256
|
case MODULE_CRAFTER: {
|
|
197
257
|
const rea = decodeStat(stats, 0)
|
|
198
258
|
const con = decodeStat(stats, 1)
|
|
199
|
-
out += ` Speed ${computeCrafterSpeed(rea)} Drain ${computeCrafterDrain(con)}`
|
|
259
|
+
out += ` Speed ${computeCrafterSpeed(rea)} Drain ${toWholeEnergy(computeCrafterDrain(con))}`
|
|
260
|
+
break
|
|
261
|
+
}
|
|
262
|
+
case MODULE_BUILDER: {
|
|
263
|
+
const coh = decodeStat(stats, 0)
|
|
264
|
+
const tol = decodeStat(stats, 1)
|
|
265
|
+
out += ` Speed ${computeBuilderSpeed(coh)} Drain ${toWholeEnergy(computeBuilderDrain(tol))}`
|
|
200
266
|
break
|
|
201
267
|
}
|
|
202
268
|
case MODULE_STORAGE: {
|
|
203
269
|
const str = decodeStat(stats, 0)
|
|
204
270
|
const den = decodeStat(stats, 1)
|
|
205
271
|
const hrd = decodeStat(stats, 2)
|
|
206
|
-
const
|
|
207
|
-
|
|
272
|
+
const coh = decodeStat(stats, 3)
|
|
273
|
+
const tier = getItem(itemId).tier
|
|
274
|
+
out += ` Cargo Capacity ${computeCargoBayCapacity(str, den, hrd)} Drain ${toWholeEnergy(computeCargoBayDrain(coh, tier))}`
|
|
208
275
|
break
|
|
209
276
|
}
|
|
210
277
|
case MODULE_HAULER: {
|
|
211
278
|
const res = decodeStat(stats, 0)
|
|
212
279
|
const pla = decodeStat(stats, 1)
|
|
213
|
-
const
|
|
280
|
+
const con = decodeStat(stats, 2)
|
|
214
281
|
const tier = getItem(itemId).tier
|
|
215
|
-
out += ` Capacity ${computeHaulerCapacity(res, tier)} Efficiency ${computeHaulerEfficiency(pla)} Drain ${computeHaulerDrain(
|
|
282
|
+
out += ` Capacity ${computeHaulerCapacity(res, tier)} Efficiency ${computeHaulerEfficiency(pla)} Drain ${toWholeEnergy(computeHaulerDrain(con, tier))}`
|
|
216
283
|
break
|
|
217
284
|
}
|
|
218
285
|
case MODULE_WARP: {
|
|
@@ -225,7 +292,7 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
|
|
|
225
292
|
const thm = decodeStat(stats, 1)
|
|
226
293
|
const pla = decodeStat(stats, 2)
|
|
227
294
|
const ins = decodeStat(stats, 3)
|
|
228
|
-
out += ` Energy Capacity ${computeBatteryBankCapacity(vol, thm, pla, ins)}`
|
|
295
|
+
out += ` Energy Capacity ${toWholeEnergy(computeBatteryBankCapacity(vol, thm, pla, ins))}`
|
|
229
296
|
break
|
|
230
297
|
}
|
|
231
298
|
}
|
|
@@ -238,13 +305,9 @@ export function buildEntityDescription(
|
|
|
238
305
|
moduleItems: number[],
|
|
239
306
|
moduleStats: bigint[]
|
|
240
307
|
): string {
|
|
241
|
-
const hullMass = computeBaseHullmass(hullStats)
|
|
308
|
+
const hullMass = computeBaseHullmass(itemId, hullStats)
|
|
242
309
|
let baseCapacity = 0
|
|
243
|
-
if (
|
|
244
|
-
itemId === ITEM_SHIP_T1_PACKED ||
|
|
245
|
-
itemId === ITEM_PROSPECTOR_T2_PACKED ||
|
|
246
|
-
itemId === ITEM_HAULER_SHIP_T2_PACKED
|
|
247
|
-
) {
|
|
310
|
+
if (isShipHull(itemId)) {
|
|
248
311
|
baseCapacity = computeBaseCapacityShip(hullStats)
|
|
249
312
|
} else if (itemId === ITEM_WAREHOUSE_T1_PACKED) {
|
|
250
313
|
baseCapacity = computeBaseCapacityWarehouse(hullStats)
|
|
@@ -58,7 +58,7 @@ function entity(overrides: EntityOverrides = {}): GatherPlanEntity {
|
|
|
58
58
|
gatherer_lanes: overrides.gatherer_lanes ?? [],
|
|
59
59
|
loader_lanes: overrides.loader_lanes ?? [],
|
|
60
60
|
generator: overrides.generator,
|
|
61
|
-
energy: overrides.energy !== undefined ?
|
|
61
|
+
energy: overrides.energy !== undefined ? UInt32.from(overrides.energy) : undefined,
|
|
62
62
|
lanes: overrides.lanes ?? [],
|
|
63
63
|
coordinates: ServerContract.Types.coordinates.from({x: 0, y: 0}),
|
|
64
64
|
cargo: [],
|
|
@@ -118,27 +118,30 @@ describe('cost helpers', () => {
|
|
|
118
118
|
const RICHNESS = 500
|
|
119
119
|
|
|
120
120
|
test('gatherEnergyCost is 0 for non-positive quantity and positive otherwise', () => {
|
|
121
|
-
const lane = gathererLane(0, 700,
|
|
121
|
+
const lane = gathererLane(0, 700, 1_250_000, 5000)
|
|
122
122
|
expect(gatherEnergyCost(lane, 0, STRATUM, ITEM_MASS, RICHNESS)).toBe(0)
|
|
123
123
|
expect(gatherEnergyCost(lane, 100, STRATUM, ITEM_MASS, RICHNESS)).toBeGreaterThan(0)
|
|
124
124
|
})
|
|
125
125
|
|
|
126
126
|
test('splitCost across two lanes is monotonic non-decreasing in quantity', () => {
|
|
127
|
-
const reaching = [
|
|
127
|
+
const reaching = [
|
|
128
|
+
gathererLane(0, 700, 1_250_000, 5000),
|
|
129
|
+
gathererLane(1, 700, 1_250_000, 5000),
|
|
130
|
+
]
|
|
128
131
|
const c100 = splitCost(reaching, 100, STRATUM, ITEM_MASS, RICHNESS)
|
|
129
132
|
const c200 = splitCost(reaching, 200, STRATUM, ITEM_MASS, RICHNESS)
|
|
130
133
|
expect(c200).toBeGreaterThanOrEqual(c100)
|
|
131
134
|
})
|
|
132
135
|
|
|
133
136
|
test('maxQtyForCharge returns hi when the whole batch fits the charge', () => {
|
|
134
|
-
const reaching = [gathererLane(0, 700,
|
|
137
|
+
const reaching = [gathererLane(0, 700, 1_250_000, 5000)]
|
|
135
138
|
// capacity huge → the full 500 fits
|
|
136
139
|
expect(maxQtyForCharge(reaching, 500, 1_000_000, STRATUM, ITEM_MASS, RICHNESS)).toBe(500)
|
|
137
140
|
})
|
|
138
141
|
|
|
139
142
|
test('maxQtyForCharge finds the boundary: result fits, result+1 does not', () => {
|
|
140
|
-
const reaching = [gathererLane(0, 700,
|
|
141
|
-
const capacity =
|
|
143
|
+
const reaching = [gathererLane(0, 700, 1_250_000, 5000)]
|
|
144
|
+
const capacity = 40_000
|
|
142
145
|
const q = maxQtyForCharge(reaching, 100_000, capacity, STRATUM, ITEM_MASS, RICHNESS)
|
|
143
146
|
expect(splitCost(reaching, q, STRATUM, ITEM_MASS, RICHNESS)).toBeLessThanOrEqual(capacity)
|
|
144
147
|
expect(splitCost(reaching, q + 1, STRATUM, ITEM_MASS, RICHNESS)).toBeGreaterThan(capacity)
|
|
@@ -157,9 +160,9 @@ describe('buildGatherPlan', () => {
|
|
|
157
160
|
|
|
158
161
|
test('single cycle when the target fits one charge and energy is full', () => {
|
|
159
162
|
const e = entity({
|
|
160
|
-
gatherer_lanes: [gathererLane(0, 700,
|
|
161
|
-
generator: energyStats(
|
|
162
|
-
energy:
|
|
163
|
+
gatherer_lanes: [gathererLane(0, 700, 1_250_000, 5000)],
|
|
164
|
+
generator: energyStats(1_200_000, 2000),
|
|
165
|
+
energy: 1_200_000,
|
|
163
166
|
})
|
|
164
167
|
const plan = buildGatherPlan(e, STRATUM, {quantity: 100}, OPTS)
|
|
165
168
|
expect(plan.cycleCount).toBe(1)
|
|
@@ -172,9 +175,12 @@ describe('buildGatherPlan', () => {
|
|
|
172
175
|
|
|
173
176
|
test('two limpets split one charge proportional to yield, finish within 1s of each other', () => {
|
|
174
177
|
const e = entity({
|
|
175
|
-
gatherer_lanes: [
|
|
176
|
-
|
|
177
|
-
|
|
178
|
+
gatherer_lanes: [
|
|
179
|
+
gathererLane(0, 200, 500_000, 5000),
|
|
180
|
+
gathererLane(1, 400, 500_000, 5000),
|
|
181
|
+
],
|
|
182
|
+
generator: energyStats(60_000_000, 2000),
|
|
183
|
+
energy: 60_000_000,
|
|
178
184
|
})
|
|
179
185
|
const plan = buildGatherPlan(e, STRATUM, {quantity: 60}, OPTS)
|
|
180
186
|
expect(plan.cycleCount).toBe(1)
|
|
@@ -185,9 +191,9 @@ describe('buildGatherPlan', () => {
|
|
|
185
191
|
})
|
|
186
192
|
|
|
187
193
|
test('multi-cycle: fills beyond one charge; totalOre equals fill target; each cycle fits the charge', () => {
|
|
188
|
-
const CAP =
|
|
194
|
+
const CAP = 40_000
|
|
189
195
|
const e = entity({
|
|
190
|
-
gatherer_lanes: [gathererLane(0, 700,
|
|
196
|
+
gatherer_lanes: [gathererLane(0, 700, 1_250_000, 5000)],
|
|
191
197
|
generator: energyStats(CAP, 2),
|
|
192
198
|
energy: CAP,
|
|
193
199
|
})
|
|
@@ -214,9 +220,9 @@ describe('buildGatherPlan', () => {
|
|
|
214
220
|
|
|
215
221
|
test('cap reasons: hold binds, reserve binds, requested binds', () => {
|
|
216
222
|
const e = entity({
|
|
217
|
-
gatherer_lanes: [gathererLane(0, 700,
|
|
218
|
-
generator: energyStats(
|
|
219
|
-
energy:
|
|
223
|
+
gatherer_lanes: [gathererLane(0, 700, 1_250_000, 5000)],
|
|
224
|
+
generator: energyStats(60_000_000, 2000),
|
|
225
|
+
energy: 60_000_000,
|
|
220
226
|
})
|
|
221
227
|
expect(
|
|
222
228
|
buildGatherPlan(e, STRATUM, 'max', {...OPTS, holdRoom: 300, reserveRemaining: 9999}).cap
|
|
@@ -228,9 +234,9 @@ describe('buildGatherPlan', () => {
|
|
|
228
234
|
})
|
|
229
235
|
|
|
230
236
|
test('total ETA sums per-cycle recharge + gather seconds', () => {
|
|
231
|
-
const CAP =
|
|
237
|
+
const CAP = 40_000
|
|
232
238
|
const e = entity({
|
|
233
|
-
gatherer_lanes: [gathererLane(0, 700,
|
|
239
|
+
gatherer_lanes: [gathererLane(0, 700, 1_250_000, 5000)],
|
|
234
240
|
generator: energyStats(CAP, 2),
|
|
235
241
|
energy: CAP,
|
|
236
242
|
})
|
|
@@ -242,11 +248,11 @@ describe('buildGatherPlan', () => {
|
|
|
242
248
|
test('excludes limpets that cannot reach the stratum and warns', () => {
|
|
243
249
|
const e = entity({
|
|
244
250
|
gatherer_lanes: [
|
|
245
|
-
gathererLane(0, 700,
|
|
246
|
-
gathererLane(1, 700,
|
|
251
|
+
gathererLane(0, 700, 1_250_000, 5000), // reaches 100
|
|
252
|
+
gathererLane(1, 700, 1_250_000, 50), // does not reach 100
|
|
247
253
|
],
|
|
248
|
-
generator: energyStats(
|
|
249
|
-
energy:
|
|
254
|
+
generator: energyStats(60_000_000, 2000),
|
|
255
|
+
energy: 60_000_000,
|
|
250
256
|
})
|
|
251
257
|
const plan = buildGatherPlan(e, STRATUM, {quantity: 100}, OPTS)
|
|
252
258
|
expect(plan.cycles[0].limpets).toHaveLength(1)
|
|
@@ -256,9 +262,12 @@ describe('buildGatherPlan', () => {
|
|
|
256
262
|
|
|
257
263
|
test('reports structured limpet reach counts (all reaching)', () => {
|
|
258
264
|
const e = entity({
|
|
259
|
-
gatherer_lanes: [
|
|
260
|
-
|
|
261
|
-
|
|
265
|
+
gatherer_lanes: [
|
|
266
|
+
gathererLane(0, 700, 1_250_000, 5000),
|
|
267
|
+
gathererLane(1, 700, 1_250_000, 5000),
|
|
268
|
+
],
|
|
269
|
+
generator: energyStats(60_000_000, 2000),
|
|
270
|
+
energy: 60_000_000,
|
|
262
271
|
})
|
|
263
272
|
const plan = buildGatherPlan(e, STRATUM, {quantity: 50}, OPTS)
|
|
264
273
|
expect(plan.totalLimpets).toBe(2)
|
|
@@ -268,11 +277,11 @@ describe('buildGatherPlan', () => {
|
|
|
268
277
|
test('reachingCount excludes limpets that cannot reach the stratum', () => {
|
|
269
278
|
const e = entity({
|
|
270
279
|
gatherer_lanes: [
|
|
271
|
-
gathererLane(0, 700,
|
|
272
|
-
gathererLane(1, 700,
|
|
280
|
+
gathererLane(0, 700, 1_250_000, 5000), // reaches 100
|
|
281
|
+
gathererLane(1, 700, 1_250_000, 50), // does not reach 100
|
|
273
282
|
],
|
|
274
|
-
generator: energyStats(
|
|
275
|
-
energy:
|
|
283
|
+
generator: energyStats(60_000_000, 2000),
|
|
284
|
+
energy: 60_000_000,
|
|
276
285
|
})
|
|
277
286
|
const plan = buildGatherPlan(e, STRATUM, {quantity: 50}, OPTS)
|
|
278
287
|
expect(plan.totalLimpets).toBe(2)
|
|
@@ -280,9 +289,9 @@ describe('buildGatherPlan', () => {
|
|
|
280
289
|
})
|
|
281
290
|
|
|
282
291
|
test('single-limpet ship still fills across cycles', () => {
|
|
283
|
-
const CAP =
|
|
292
|
+
const CAP = 40_000
|
|
284
293
|
const e = entity({
|
|
285
|
-
gatherer_lanes: [gathererLane(0, 700,
|
|
294
|
+
gatherer_lanes: [gathererLane(0, 700, 1_250_000, 5000)],
|
|
286
295
|
generator: energyStats(CAP, 2),
|
|
287
296
|
energy: CAP,
|
|
288
297
|
})
|
|
@@ -293,18 +302,18 @@ describe('buildGatherPlan', () => {
|
|
|
293
302
|
|
|
294
303
|
test('throws when no gatherer reaches the stratum', () => {
|
|
295
304
|
const e = entity({
|
|
296
|
-
gatherer_lanes: [gathererLane(0, 700,
|
|
297
|
-
generator: energyStats(
|
|
298
|
-
energy:
|
|
305
|
+
gatherer_lanes: [gathererLane(0, 700, 1_250_000, 50)],
|
|
306
|
+
generator: energyStats(1_200_000, 2000),
|
|
307
|
+
energy: 1_200_000,
|
|
299
308
|
})
|
|
300
309
|
expect(() => buildGatherPlan(e, STRATUM, 'max', OPTS)).toThrow('no gatherer reaches')
|
|
301
310
|
})
|
|
302
311
|
|
|
303
312
|
test('zero fill target yields an empty plan (no cycles)', () => {
|
|
304
313
|
const e = entity({
|
|
305
|
-
gatherer_lanes: [gathererLane(0, 700,
|
|
306
|
-
generator: energyStats(
|
|
307
|
-
energy:
|
|
314
|
+
gatherer_lanes: [gathererLane(0, 700, 1_250_000, 5000)],
|
|
315
|
+
generator: energyStats(1_200_000, 2000),
|
|
316
|
+
energy: 1_200_000,
|
|
308
317
|
})
|
|
309
318
|
const plan = buildGatherPlan(e, STRATUM, 'max', {...OPTS, holdRoom: 0})
|
|
310
319
|
expect(plan.cycleCount).toBe(0)
|
|
@@ -314,9 +323,9 @@ describe('buildGatherPlan', () => {
|
|
|
314
323
|
|
|
315
324
|
test('first cycle skips recharge when current energy already covers the small batch', () => {
|
|
316
325
|
const e = entity({
|
|
317
|
-
gatherer_lanes: [gathererLane(0, 700,
|
|
318
|
-
generator: energyStats(
|
|
319
|
-
energy:
|
|
326
|
+
gatherer_lanes: [gathererLane(0, 700, 1_250_000, 5000)],
|
|
327
|
+
generator: energyStats(60_000_000, 2000),
|
|
328
|
+
energy: 60_000_000, // full
|
|
320
329
|
})
|
|
321
330
|
const plan = buildGatherPlan(e, STRATUM, {quantity: 50}, OPTS)
|
|
322
331
|
expect(plan.cycles[0].rechargeBefore).toBe(false)
|
|
@@ -42,7 +42,7 @@ test('resolveItem resolves the station hub with hullmass and zero cargo capacity
|
|
|
42
42
|
const resolved = resolveItem(UInt16.from(ITEM_HUB_T1_PACKED), stats)
|
|
43
43
|
const hull = resolved.attributes?.find((g) => g.capability === 'Hull')
|
|
44
44
|
expect(hull?.attributes.find((a) => a.label === 'Mass')?.value).toBe(
|
|
45
|
-
computeBaseHullmass({density: 100})
|
|
45
|
+
computeBaseHullmass(ITEM_HUB_T1_PACKED, {density: 100})
|
|
46
46
|
)
|
|
47
47
|
expect(hull?.attributes.find((a) => a.label === 'Capacity')?.value).toBe(0)
|
|
48
48
|
})
|
|
@@ -2,6 +2,7 @@ import {UInt16, UInt64} from '@wharfkit/antelope'
|
|
|
2
2
|
import type {UInt16Type, UInt64Type} from '@wharfkit/antelope'
|
|
3
3
|
import type {ResourceCategory} from '../types'
|
|
4
4
|
import {getItem, getModules} from '../data/catalog'
|
|
5
|
+
import {ENTITY_SHIP, getPackedEntityType} from '../data/kind-registry'
|
|
5
6
|
import {getEntityLayout} from '../data/recipes-runtime'
|
|
6
7
|
import {entityMetadata, itemMetadata} from '../data/metadata'
|
|
7
8
|
import {
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
computeStorageCapabilities,
|
|
32
33
|
} from '../derivation/capabilities'
|
|
33
34
|
import {applySlotMultiplierUint32} from '../entities/slot-multiplier'
|
|
35
|
+
import {toWholeEnergy} from '../nft/description'
|
|
34
36
|
import {categoryColors, componentIcon, itemAbbreviations, moduleIcon} from '../data/colors'
|
|
35
37
|
import type {ServerContract} from '../contracts'
|
|
36
38
|
|
|
@@ -171,8 +173,8 @@ function computeCapabilityGroup(
|
|
|
171
173
|
return {
|
|
172
174
|
capability: 'Generator',
|
|
173
175
|
attributes: [
|
|
174
|
-
{label: 'Capacity', value: caps.capacity},
|
|
175
|
-
{label: 'Recharge', value: caps.recharge},
|
|
176
|
+
{label: 'Capacity', value: toWholeEnergy(caps.capacity)},
|
|
177
|
+
{label: 'Recharge', value: toWholeEnergy(caps.recharge)},
|
|
176
178
|
],
|
|
177
179
|
}
|
|
178
180
|
}
|
|
@@ -182,7 +184,7 @@ function computeCapabilityGroup(
|
|
|
182
184
|
capability: 'Gatherer',
|
|
183
185
|
attributes: [
|
|
184
186
|
{label: 'Yield', value: caps.yield},
|
|
185
|
-
{label: 'Drain', value: caps.drain},
|
|
187
|
+
{label: 'Drain', value: toWholeEnergy(caps.drain)},
|
|
186
188
|
{label: 'Depth', value: caps.depth},
|
|
187
189
|
],
|
|
188
190
|
}
|
|
@@ -204,7 +206,7 @@ function computeCapabilityGroup(
|
|
|
204
206
|
capability: 'Crafting',
|
|
205
207
|
attributes: [
|
|
206
208
|
{label: 'Speed', value: caps.speed},
|
|
207
|
-
{label: 'Drain', value: caps.drain},
|
|
209
|
+
{label: 'Drain', value: toWholeEnergy(caps.drain)},
|
|
208
210
|
],
|
|
209
211
|
}
|
|
210
212
|
}
|
|
@@ -215,12 +217,12 @@ function computeCapabilityGroup(
|
|
|
215
217
|
attributes: [
|
|
216
218
|
{label: 'Capacity', value: caps.capacity},
|
|
217
219
|
{label: 'Efficiency', value: caps.efficiency},
|
|
218
|
-
{label: 'Drain', value: caps.drain},
|
|
220
|
+
{label: 'Drain', value: toWholeEnergy(caps.drain)},
|
|
219
221
|
],
|
|
220
222
|
}
|
|
221
223
|
}
|
|
222
224
|
case MODULE_STORAGE: {
|
|
223
|
-
const caps = computeStorageCapabilities(stats)
|
|
225
|
+
const caps = computeStorageCapabilities(stats, tier)
|
|
224
226
|
return {
|
|
225
227
|
capability: 'Storage',
|
|
226
228
|
attributes: [
|
|
@@ -228,6 +230,7 @@ function computeCapabilityGroup(
|
|
|
228
230
|
label: 'Cargo Capacity',
|
|
229
231
|
value: applySlotMultiplierUint32(caps.capacity, outputPct),
|
|
230
232
|
},
|
|
233
|
+
{label: 'Drain', value: toWholeEnergy(caps.drain)},
|
|
231
234
|
],
|
|
232
235
|
}
|
|
233
236
|
}
|
|
@@ -238,7 +241,7 @@ function computeCapabilityGroup(
|
|
|
238
241
|
attributes: [
|
|
239
242
|
{
|
|
240
243
|
label: 'Energy Capacity',
|
|
241
|
-
value: applySlotMultiplierUint32(caps.capacity, outputPct),
|
|
244
|
+
value: toWholeEnergy(applySlotMultiplierUint32(caps.capacity, outputPct)),
|
|
242
245
|
},
|
|
243
246
|
],
|
|
244
247
|
}
|
|
@@ -282,14 +285,26 @@ function resolveModule(id: number, stats?: UInt64Type): ResolvedItem {
|
|
|
282
285
|
|
|
283
286
|
function hullCapsForEntity(
|
|
284
287
|
itemId: number,
|
|
285
|
-
decoded: Record<string, number
|
|
288
|
+
decoded: Record<string, number>,
|
|
289
|
+
packedStats: bigint
|
|
286
290
|
): {
|
|
287
291
|
hullmass: number
|
|
288
292
|
capacity: number
|
|
289
293
|
} {
|
|
294
|
+
// A ship hull consumes all five positional packed channels. This is separate
|
|
295
|
+
// from the recipe-facing stat labels, which remain historical for Ship T1.
|
|
296
|
+
const hullStats = getPackedEntityType(itemId)?.equals(ENTITY_SHIP)
|
|
297
|
+
? {
|
|
298
|
+
strength: decodeStat(packedStats, 0),
|
|
299
|
+
hardness: decodeStat(packedStats, 1),
|
|
300
|
+
plasticity: decodeStat(packedStats, 2),
|
|
301
|
+
volatility: decodeStat(packedStats, 3),
|
|
302
|
+
conductivity: decodeStat(packedStats, 4),
|
|
303
|
+
}
|
|
304
|
+
: decoded
|
|
290
305
|
return {
|
|
291
|
-
hullmass: computeBaseHullmass(
|
|
292
|
-
capacity: computeBaseCapacity(itemId,
|
|
306
|
+
hullmass: computeBaseHullmass(itemId, hullStats),
|
|
307
|
+
capacity: computeBaseCapacity(itemId, hullStats),
|
|
293
308
|
}
|
|
294
309
|
}
|
|
295
310
|
|
|
@@ -309,7 +324,7 @@ function resolveEntity(
|
|
|
309
324
|
if (decoded.strength === undefined) decoded.strength = decodeStat(bigStats, 0)
|
|
310
325
|
if (decoded.density === undefined) decoded.density = decodeStat(bigStats, 1)
|
|
311
326
|
if (decoded.hardness === undefined) decoded.hardness = decodeStat(bigStats, 2)
|
|
312
|
-
const hullCaps = hullCapsForEntity(id, decoded)
|
|
327
|
+
const hullCaps = hullCapsForEntity(id, decoded, bigStats)
|
|
313
328
|
attributes = [
|
|
314
329
|
{
|
|
315
330
|
capability: 'Hull',
|
|
@@ -23,14 +23,15 @@ export function taskCargoEffect(task: Task): CargoEffect {
|
|
|
23
23
|
case TaskType.UNLOAD:
|
|
24
24
|
return {added: [], removed: task.cargo}
|
|
25
25
|
case TaskType.GATHER:
|
|
26
|
-
return task.
|
|
26
|
+
return task.couplings.length > 0
|
|
27
|
+
? {added: [], removed: []}
|
|
28
|
+
: {added: task.cargo, removed: []}
|
|
27
29
|
case TaskType.CRAFT:
|
|
28
30
|
if (task.cargo.length === 0) return {added: [], removed: []}
|
|
29
|
-
return {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
: {added: [], removed: []}
|
|
31
|
+
return {
|
|
32
|
+
added: task.couplings.length === 0 ? [task.cargo[task.cargo.length - 1]] : [],
|
|
33
|
+
removed: task.cargo.slice(0, -1),
|
|
34
|
+
}
|
|
34
35
|
default:
|
|
35
36
|
return {added: [], removed: []}
|
|
36
37
|
}
|