@shipload/sdk 1.0.0-next.44 → 1.0.0-next.46
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 +235 -39
- package/lib/shipload.js +1401 -157
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +1387 -158
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +81 -15
- package/lib/testing.js +374 -41
- package/lib/testing.js.map +1 -1
- package/lib/testing.m.js +375 -42
- package/lib/testing.m.js.map +1 -1
- package/package.json +1 -1
- package/src/capabilities/gathering.test.ts +14 -1
- package/src/capabilities/gathering.ts +6 -5
- package/src/capabilities/modules.ts +4 -0
- package/src/contracts/server.ts +242 -28
- package/src/data/entities.json +93 -19
- package/src/data/item-ids.ts +12 -0
- package/src/data/items.json +76 -2
- package/src/data/kind-registry.json +10 -0
- package/src/data/metadata.ts +68 -0
- package/src/data/recipes-runtime.ts +1 -0
- package/src/data/recipes.json +720 -0
- package/src/derivation/capabilities.test.ts +11 -11
- package/src/derivation/capabilities.ts +42 -27
- package/src/derivation/index.ts +2 -0
- package/src/derivation/recipe-usage.test.ts +11 -7
- package/src/derivation/stars.test.ts +16 -0
- package/src/derivation/stars.ts +10 -0
- package/src/derivation/stratum.ts +11 -4
- package/src/entities/makers.ts +8 -1
- package/src/index-module.ts +5 -1
- package/src/managers/actions.ts +10 -0
- package/src/nft/buildImmutableData.ts +1 -1
- package/src/nft/description.ts +20 -11
- package/src/resolution/describe-module.ts +21 -8
- package/src/resolution/resolve-item.test.ts +12 -1
- package/src/resolution/resolve-item.ts +33 -33
- package/src/scheduling/projection.ts +8 -1
- package/src/subscriptions/manager.cluster.test.ts +7 -2
- package/src/subscriptions/types.ts +1 -0
- package/src/utils/cargo.test.ts +14 -0
- package/src/utils/cargo.ts +2 -0
|
@@ -21,12 +21,12 @@ import {
|
|
|
21
21
|
import type {InstalledModule} from '../entities/slot-multiplier'
|
|
22
22
|
import type {EntitySlot} from '../data/recipes-runtime'
|
|
23
23
|
|
|
24
|
-
function makeGathererStats(strength: number,
|
|
25
|
-
return encodeStats([strength,
|
|
24
|
+
function makeGathererStats(strength: number, hardness: number, saturation: number): bigint {
|
|
25
|
+
return encodeStats([strength, hardness, saturation, 0])
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function makeCrafterStats(
|
|
29
|
-
return encodeStats([
|
|
28
|
+
function makeCrafterStats(fineness: number, conductivity: number): bigint {
|
|
29
|
+
return encodeStats([fineness, conductivity])
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
function makeLoaderStats(insulation: number, plasticity: number): bigint {
|
|
@@ -57,8 +57,8 @@ test('computeEntityCapabilities emits gathererLanes alongside legacy gatherer su
|
|
|
57
57
|
]
|
|
58
58
|
|
|
59
59
|
const layout: EntitySlot[] = [
|
|
60
|
-
{type: 'gatherer', outputPct: 100},
|
|
61
|
-
{type: 'gatherer', outputPct: 100},
|
|
60
|
+
{type: 'gatherer', outputPct: 100, maxTier: 1},
|
|
61
|
+
{type: 'gatherer', outputPct: 100, maxTier: 1},
|
|
62
62
|
]
|
|
63
63
|
|
|
64
64
|
const result = computeEntityCapabilities({}, ITEM_EXTRACTOR_T1_PACKED, modules, layout)
|
|
@@ -72,8 +72,8 @@ test('computeEntityCapabilities emits gathererLanes alongside legacy gatherer su
|
|
|
72
72
|
expect(result.gathererLanes![1].slotIndex).toBe(1)
|
|
73
73
|
|
|
74
74
|
// Yields are amp-scaled and distinct
|
|
75
|
-
const caps1 = computeGathererCapabilities({strength: 300,
|
|
76
|
-
const caps2 = computeGathererCapabilities({strength: 500,
|
|
75
|
+
const caps1 = computeGathererCapabilities({strength: 300, hardness: 200, saturation: 400}, 1)
|
|
76
|
+
const caps2 = computeGathererCapabilities({strength: 500, hardness: 100, saturation: 300}, 1)
|
|
77
77
|
const expectedYield1 = applySlotMultiplier(caps1.yield, 100)
|
|
78
78
|
const expectedYield2 = applySlotMultiplier(caps2.yield, 100)
|
|
79
79
|
expect(result.gathererLanes![0].yield).toBe(expectedYield1)
|
|
@@ -102,7 +102,7 @@ test('computeEntityCapabilities emits crafterLanes alongside legacy crafter sum'
|
|
|
102
102
|
{slotIndex: 0, itemId: ITEM_CRAFTER_T1, stats: crafterStats},
|
|
103
103
|
]
|
|
104
104
|
|
|
105
|
-
const layout: EntitySlot[] = [{type: 'crafter', outputPct: 120}]
|
|
105
|
+
const layout: EntitySlot[] = [{type: 'crafter', outputPct: 120, maxTier: 1}]
|
|
106
106
|
|
|
107
107
|
const result = computeEntityCapabilities({}, ITEM_EXTRACTOR_T1_PACKED, modules, layout)
|
|
108
108
|
|
|
@@ -110,7 +110,7 @@ test('computeEntityCapabilities emits crafterLanes alongside legacy crafter sum'
|
|
|
110
110
|
expect(result.crafterLanes!.length).toBe(1)
|
|
111
111
|
expect(result.crafterLanes![0].slotIndex).toBe(0)
|
|
112
112
|
|
|
113
|
-
const caps = computeCrafterCapabilities({
|
|
113
|
+
const caps = computeCrafterCapabilities({fineness: 400, conductivity: 300})
|
|
114
114
|
const expectedSpeed = applySlotMultiplier(caps.speed, 120)
|
|
115
115
|
expect(result.crafterLanes![0].speed).toBe(expectedSpeed)
|
|
116
116
|
expect(result.crafterLanes![0].drain).toBe(caps.drain)
|
|
@@ -126,7 +126,7 @@ test('computeEntityCapabilities emits loaderLanes alongside legacy loaders sum',
|
|
|
126
126
|
|
|
127
127
|
const modules: InstalledModule[] = [{slotIndex: 0, itemId: ITEM_LOADER_T1, stats: loaderStats}]
|
|
128
128
|
|
|
129
|
-
const layout: EntitySlot[] = [{type: 'loader', outputPct: 80}]
|
|
129
|
+
const layout: EntitySlot[] = [{type: 'loader', outputPct: 80, maxTier: 1}]
|
|
130
130
|
|
|
131
131
|
const result = computeEntityCapabilities({}, ITEM_EXTRACTOR_T1_PACKED, modules, layout)
|
|
132
132
|
|
|
@@ -115,7 +115,10 @@ export function computeCrafterCapabilities(stats: Record<string, number>): {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
export function computeHaulerCapabilities(
|
|
118
|
+
export function computeHaulerCapabilities(
|
|
119
|
+
stats: Record<string, number>,
|
|
120
|
+
tier: number
|
|
121
|
+
): {
|
|
119
122
|
capacity: number
|
|
120
123
|
efficiency: number
|
|
121
124
|
drain: number
|
|
@@ -125,7 +128,7 @@ export function computeHaulerCapabilities(stats: Record<string, number>): {
|
|
|
125
128
|
const conductivity = stats.conductivity
|
|
126
129
|
|
|
127
130
|
return {
|
|
128
|
-
capacity:
|
|
131
|
+
capacity: computeHaulerCapacity(resonance, tier),
|
|
129
132
|
efficiency: 2000 + plasticity * 6,
|
|
130
133
|
drain: Math.max(3, 15 - Math.floor(conductivity / 80)),
|
|
131
134
|
}
|
|
@@ -171,8 +174,10 @@ import {
|
|
|
171
174
|
ITEM_CONTAINER_T2_PACKED,
|
|
172
175
|
ITEM_EXTRACTOR_T1_PACKED,
|
|
173
176
|
ITEM_FACTORY_T1_PACKED,
|
|
177
|
+
ITEM_HAULER_SHIP_T2_PACKED,
|
|
174
178
|
ITEM_MASS_CATCHER_T1_PACKED,
|
|
175
179
|
ITEM_MASS_DRIVER_T1_PACKED,
|
|
180
|
+
ITEM_PROSPECTOR_T2_PACKED,
|
|
176
181
|
ITEM_SHIP_T1_PACKED,
|
|
177
182
|
ITEM_WAREHOUSE_T1_PACKED,
|
|
178
183
|
} from '../data/item-ids'
|
|
@@ -200,25 +205,42 @@ import {
|
|
|
200
205
|
type InstalledModule,
|
|
201
206
|
} from '../entities/slot-multiplier'
|
|
202
207
|
import type {EntitySlot} from '../data/recipes-runtime'
|
|
203
|
-
import {computeTravelDrain} from '../nft/description'
|
|
208
|
+
import {computeHaulerCapacity, computeTravelDrain} from '../nft/description'
|
|
209
|
+
|
|
210
|
+
export const CAPACITY_TIER_TABLE = [1.0, 1.4, 1.8, 2.2, 2.6, 3.0, 3.4, 3.8, 4.2, 4.6]
|
|
211
|
+
|
|
212
|
+
export function capacityTierMultiplier(tier: number): number {
|
|
213
|
+
const clampedTier = tier >= 1 && tier <= 10 ? tier : 1
|
|
214
|
+
return CAPACITY_TIER_TABLE[clampedTier - 1]
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function applyCapacityTier(baseCapacity: number, tier: number): number {
|
|
218
|
+
return clampUint32(Math.floor(baseCapacity * capacityTierMultiplier(tier)))
|
|
219
|
+
}
|
|
204
220
|
|
|
205
221
|
export function computeBaseCapacity(itemId: number, stats: Record<string, number>): number {
|
|
222
|
+
let base: number
|
|
206
223
|
switch (itemId) {
|
|
207
224
|
case ITEM_SHIP_T1_PACKED:
|
|
208
|
-
|
|
225
|
+
case ITEM_PROSPECTOR_T2_PACKED:
|
|
226
|
+
case ITEM_HAULER_SHIP_T2_PACKED:
|
|
227
|
+
base = computeShipHullCapabilities(stats).capacity
|
|
228
|
+
break
|
|
209
229
|
case ITEM_EXTRACTOR_T1_PACKED:
|
|
210
230
|
case ITEM_FACTORY_T1_PACKED:
|
|
211
231
|
case ITEM_MASS_DRIVER_T1_PACKED:
|
|
212
232
|
case ITEM_MASS_CATCHER_T1_PACKED:
|
|
213
233
|
case ITEM_CONTAINER_T1_PACKED:
|
|
214
|
-
return computeContainerCapabilities(stats).capacity
|
|
215
|
-
case ITEM_WAREHOUSE_T1_PACKED:
|
|
216
|
-
return computeWarehouseHullCapabilities(stats).capacity
|
|
217
234
|
case ITEM_CONTAINER_T2_PACKED:
|
|
218
|
-
|
|
235
|
+
base = computeContainerCapabilities(stats).capacity
|
|
236
|
+
break
|
|
237
|
+
case ITEM_WAREHOUSE_T1_PACKED:
|
|
238
|
+
base = computeWarehouseHullCapabilities(stats).capacity
|
|
239
|
+
break
|
|
219
240
|
default:
|
|
220
241
|
return 0
|
|
221
242
|
}
|
|
243
|
+
return applyCapacityTier(base, getItem(itemId).tier)
|
|
222
244
|
}
|
|
223
245
|
|
|
224
246
|
export function computeWarpCapabilities(stats: Record<string, number>): {
|
|
@@ -273,7 +295,12 @@ export interface ComputedCapabilities {
|
|
|
273
295
|
loaderLanes?: LoaderLaneEntry[]
|
|
274
296
|
crafter?: {speed: number; drain: number}
|
|
275
297
|
crafterLanes?: CrafterLaneEntry[]
|
|
276
|
-
hauler?: {
|
|
298
|
+
hauler?: {
|
|
299
|
+
capacity: number
|
|
300
|
+
efficiency: number
|
|
301
|
+
drain: number
|
|
302
|
+
capacityByTier: {tier: number; capacity: number}[]
|
|
303
|
+
}
|
|
277
304
|
warp?: {range: number}
|
|
278
305
|
launcher?: {chargeRate: number; velocity: number; drain: number}
|
|
279
306
|
}
|
|
@@ -315,6 +342,7 @@ export function computeEntityCapabilities(
|
|
|
315
342
|
let weightedHaulerEffNum = 0n
|
|
316
343
|
let totalHaulerDrain = 0
|
|
317
344
|
let hasHauler = false
|
|
345
|
+
const haulerCapByTier = new Map<number, number>()
|
|
318
346
|
|
|
319
347
|
let totalWarpRange = 0
|
|
320
348
|
let hasWarp = false
|
|
@@ -392,9 +420,10 @@ export function computeEntityCapabilities(
|
|
|
392
420
|
})
|
|
393
421
|
} else if (modType === MODULE_HAULER) {
|
|
394
422
|
hasHauler = true
|
|
395
|
-
const caps = computeHaulerCapabilities(decodedStats)
|
|
423
|
+
const caps = computeHaulerCapabilities(decodedStats, item.tier)
|
|
396
424
|
const eff = applySlotMultiplier(caps.efficiency, amp)
|
|
397
425
|
totalHaulerCapacity += caps.capacity
|
|
426
|
+
haulerCapByTier.set(item.tier, (haulerCapByTier.get(item.tier) ?? 0) + caps.capacity)
|
|
398
427
|
weightedHaulerEffNum += BigInt(eff) * BigInt(caps.capacity)
|
|
399
428
|
totalHaulerDrain += caps.drain
|
|
400
429
|
} else if (modType === MODULE_WARP) {
|
|
@@ -466,6 +495,9 @@ export function computeEntityCapabilities(
|
|
|
466
495
|
capacity: totalHaulerCapacity,
|
|
467
496
|
efficiency: clampUint16(efficiency),
|
|
468
497
|
drain: totalHaulerDrain,
|
|
498
|
+
capacityByTier: [...haulerCapByTier.entries()]
|
|
499
|
+
.sort((a, b) => a[0] - b[0])
|
|
500
|
+
.map(([tier, capacity]) => ({tier, capacity})),
|
|
469
501
|
}
|
|
470
502
|
}
|
|
471
503
|
if (hasWarp) {
|
|
@@ -493,20 +525,3 @@ export function computeContainerCapabilities(stats: Record<string, number>): {
|
|
|
493
525
|
capacity: Math.floor(22000000 * 6 ** exponent),
|
|
494
526
|
}
|
|
495
527
|
}
|
|
496
|
-
|
|
497
|
-
export function computeContainerT2Capabilities(stats: Record<string, number>): {
|
|
498
|
-
hullmass: number
|
|
499
|
-
capacity: number
|
|
500
|
-
} {
|
|
501
|
-
const strength = stats.strength ?? 0
|
|
502
|
-
const density = stats.density ?? 0
|
|
503
|
-
const hardness = stats.hardness ?? 0
|
|
504
|
-
|
|
505
|
-
const hullmass = 70000 - 50 * density
|
|
506
|
-
|
|
507
|
-
const statSum = strength + hardness
|
|
508
|
-
const exponent = statSum / 2947
|
|
509
|
-
const capacity = Math.floor(24000000 * 6 ** exponent)
|
|
510
|
-
|
|
511
|
-
return {hullmass, capacity}
|
|
512
|
-
}
|
package/src/derivation/index.ts
CHANGED
|
@@ -7,7 +7,9 @@ import {
|
|
|
7
7
|
} from './recipe-usage'
|
|
8
8
|
import {
|
|
9
9
|
ITEM_SENSOR,
|
|
10
|
+
ITEM_SENSOR_T2,
|
|
10
11
|
ITEM_RESIN,
|
|
12
|
+
ITEM_FRAME,
|
|
11
13
|
ITEM_PLATE,
|
|
12
14
|
ITEM_BEAM,
|
|
13
15
|
ITEM_GATHERER_T1,
|
|
@@ -26,12 +28,14 @@ test('getRecipeConsumers lists every recipe that consumes Sensor', () => {
|
|
|
26
28
|
const consumers = getRecipeConsumers(ITEM_SENSOR)
|
|
27
29
|
const ids = consumers.map((c) => c.outputItemId).sort((a, b) => a - b)
|
|
28
30
|
expect(ids).toEqual(
|
|
29
|
-
[ITEM_CRAFTER_T1, ITEM_SHIP_T1_PACKED, ITEM_EXTRACTOR_T1_PACKED].sort(
|
|
31
|
+
[ITEM_CRAFTER_T1, ITEM_SHIP_T1_PACKED, ITEM_EXTRACTOR_T1_PACKED, ITEM_SENSOR_T2].sort(
|
|
32
|
+
(a, b) => a - b
|
|
33
|
+
)
|
|
30
34
|
)
|
|
31
35
|
})
|
|
32
36
|
|
|
33
|
-
test('
|
|
34
|
-
const consumers = getRecipeConsumers(
|
|
37
|
+
test('Frame feeds the gatherer drain stat', () => {
|
|
38
|
+
const consumers = getRecipeConsumers(ITEM_FRAME)
|
|
35
39
|
const gatherer = consumers.find((c) => c.outputItemId === ITEM_GATHERER_T1)
|
|
36
40
|
expect(gatherer).toBeDefined()
|
|
37
41
|
const drain = gatherer?.statFlows.find(
|
|
@@ -56,12 +60,12 @@ test('getResourceDemand traces a dual-resource component to both resources', ()
|
|
|
56
60
|
})
|
|
57
61
|
|
|
58
62
|
test('getResourceDemand recurses through a module to raw resources', () => {
|
|
59
|
-
// Gatherer = 300 Beam (5 ore + 5 gas each) + 300
|
|
63
|
+
// Gatherer = 300 Beam (5 ore + 5 gas each) + 300 Frame (5 regolith + 5 biomass each)
|
|
60
64
|
expect(getResourceDemand(ITEM_GATHERER_T1)).toEqual({
|
|
61
65
|
ore: 1500,
|
|
62
66
|
gas: 1500,
|
|
67
|
+
regolith: 1500,
|
|
63
68
|
biomass: 1500,
|
|
64
|
-
crystal: 1500,
|
|
65
69
|
})
|
|
66
70
|
})
|
|
67
71
|
|
|
@@ -69,9 +73,9 @@ test('getResourceDemand scales by quantity', () => {
|
|
|
69
73
|
expect(getResourceDemand(ITEM_PLATE, 3)).toEqual({ore: 30})
|
|
70
74
|
})
|
|
71
75
|
|
|
72
|
-
test('getComponentDemand reports Resin as consumed by
|
|
76
|
+
test('getComponentDemand reports Resin as consumed by three recipes', () => {
|
|
73
77
|
const demand = getComponentDemand()
|
|
74
78
|
const resin = demand.find((d) => d.itemId === ITEM_RESIN)
|
|
75
79
|
expect(resin).toBeDefined()
|
|
76
|
-
expect(resin?.consumerCount).toBe(
|
|
80
|
+
expect(resin?.consumerCount).toBe(3)
|
|
77
81
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {expect, test} from 'bun:test'
|
|
2
2
|
import {
|
|
3
|
+
compareByStars,
|
|
3
4
|
MAX_STARS_PER_STAT,
|
|
4
5
|
MAX_STAR_RATING,
|
|
5
6
|
starRating,
|
|
@@ -44,6 +45,21 @@ test('statMagnitude sums raw values for tiebreaking', () => {
|
|
|
44
45
|
expect(statMagnitude(599, 599, 599)).toBeGreaterThan(statMagnitude(251, 251, 251))
|
|
45
46
|
})
|
|
46
47
|
|
|
48
|
+
test('compareByStars orders by rating desc, then magnitude desc', () => {
|
|
49
|
+
const rows = [
|
|
50
|
+
{key: 'low-rating', rating: 3, magnitude: 9000},
|
|
51
|
+
{key: 'high-rating', rating: 6, magnitude: 300},
|
|
52
|
+
{key: 'tie-small', rating: 6, magnitude: 600},
|
|
53
|
+
{key: 'tie-big', rating: 6, magnitude: 900},
|
|
54
|
+
]
|
|
55
|
+
const ordered = [...rows].sort(compareByStars).map((r) => r.key)
|
|
56
|
+
expect(ordered).toEqual(['tie-big', 'tie-small', 'high-rating', 'low-rating'])
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('compareByStars returns 0 when rating and magnitude are equal', () => {
|
|
60
|
+
expect(compareByStars({rating: 4, magnitude: 500}, {rating: 4, magnitude: 500})).toBe(0)
|
|
61
|
+
})
|
|
62
|
+
|
|
47
63
|
test('constants hold their documented values', () => {
|
|
48
64
|
expect(STAR_STEP).toBe(250)
|
|
49
65
|
expect(MAX_STARS_PER_STAT).toBe(3)
|
package/src/derivation/stars.ts
CHANGED
|
@@ -13,3 +13,13 @@ export function starRating(stat1: number, stat2: number, stat3: number): number
|
|
|
13
13
|
export function statMagnitude(stat1: number, stat2: number, stat3: number): number {
|
|
14
14
|
return stat1 + stat2 + stat3
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
export interface StarSortable {
|
|
18
|
+
rating: number
|
|
19
|
+
magnitude: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Star-quality ordering shared by every "sort by stars" surface: rating desc, then total sum of stats desc.
|
|
23
|
+
export function compareByStars(a: StarSortable, b: StarSortable): number {
|
|
24
|
+
return b.rating - a.rating || b.magnitude - a.magnitude
|
|
25
|
+
}
|
|
@@ -3,13 +3,20 @@ import {hash512} from '../utils/hash'
|
|
|
3
3
|
import {Coordinates, type CoordinatesType} from '../types'
|
|
4
4
|
import {getItem} from '../data/catalog'
|
|
5
5
|
import {getEligibleResources, getResourceWeight, yieldThresholdAt} from './resources'
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
RESERVE_TIERS,
|
|
8
|
+
rollTier,
|
|
9
|
+
rollWithinTier,
|
|
10
|
+
applyResourceTierMultiplier,
|
|
11
|
+
type ReserveTier,
|
|
12
|
+
} from './tiers'
|
|
7
13
|
|
|
8
14
|
export interface StratumInfo {
|
|
9
15
|
itemId: number
|
|
10
16
|
seed: bigint
|
|
11
17
|
richness: number
|
|
12
18
|
reserve: number
|
|
19
|
+
tier: ReserveTier
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
export interface ResourceStats {
|
|
@@ -35,10 +42,10 @@ export function deriveStratum(
|
|
|
35
42
|
const rawReserve = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0
|
|
36
43
|
|
|
37
44
|
if (rawReserve > yieldThresholdAt(stratum))
|
|
38
|
-
return {itemId: 0, seed: 0n, richness: 0, reserve: 0}
|
|
45
|
+
return {itemId: 0, seed: 0n, richness: 0, reserve: 0, tier: 'small'}
|
|
39
46
|
|
|
40
47
|
const eligible = getEligibleResources(locationType, subtype, stratum)
|
|
41
|
-
if (eligible.length === 0) return {itemId: 0, seed: 0n, richness: 0, reserve: 0}
|
|
48
|
+
if (eligible.length === 0) return {itemId: 0, seed: 0n, richness: 0, reserve: 0, tier: 'small'}
|
|
42
49
|
|
|
43
50
|
const resourceRoll = ((bytes[4] << 24) | (bytes[5] << 16) | (bytes[6] << 8) | bytes[7]) >>> 0
|
|
44
51
|
|
|
@@ -83,7 +90,7 @@ export function deriveStratum(
|
|
|
83
90
|
const roll = 500 + 100 * z
|
|
84
91
|
const richness = Math.max(1, Math.min(999, Math.round(roll)))
|
|
85
92
|
|
|
86
|
-
return {itemId: selectedItemId, seed: seedBigInt, richness, reserve}
|
|
93
|
+
return {itemId: selectedItemId, seed: seedBigInt, richness, reserve, tier}
|
|
87
94
|
}
|
|
88
95
|
|
|
89
96
|
/**
|
package/src/entities/makers.ts
CHANGED
|
@@ -140,7 +140,14 @@ export function makeEntity(packedItemId: number, state: EntityStateInput): Entit
|
|
|
140
140
|
|
|
141
141
|
if (caps.engines) info.engines = caps.engines
|
|
142
142
|
if (caps.generator) info.generator = caps.generator
|
|
143
|
-
if (caps.hauler)
|
|
143
|
+
if (caps.hauler) {
|
|
144
|
+
info.hauler = ServerContract.Types.hauler_stats.from({
|
|
145
|
+
capacity: caps.hauler.capacity,
|
|
146
|
+
efficiency: caps.hauler.efficiency,
|
|
147
|
+
drain: caps.hauler.drain,
|
|
148
|
+
capacity_by_tier: caps.hauler.capacityByTier,
|
|
149
|
+
})
|
|
150
|
+
}
|
|
144
151
|
if (caps.warp) info.warp = caps.warp
|
|
145
152
|
if (caps.launcher) {
|
|
146
153
|
info.launcher = ServerContract.Types.launcher_stats.from({
|
package/src/index-module.ts
CHANGED
|
@@ -160,7 +160,9 @@ export {
|
|
|
160
160
|
starsForStat,
|
|
161
161
|
starRating,
|
|
162
162
|
statMagnitude,
|
|
163
|
+
compareByStars,
|
|
163
164
|
} from './derivation'
|
|
165
|
+
export type {StarSortable} from './derivation'
|
|
164
166
|
|
|
165
167
|
export {hash, hash512} from './utils/hash'
|
|
166
168
|
export {validateDisplayName, normalizeDisplayName} from './utils/display-name'
|
|
@@ -424,11 +426,13 @@ export {
|
|
|
424
426
|
computeStorageCapabilities,
|
|
425
427
|
computeBatteryCapabilities,
|
|
426
428
|
computeContainerCapabilities,
|
|
427
|
-
computeContainerT2Capabilities,
|
|
428
429
|
computeWarpCapabilities,
|
|
429
430
|
computeLauncherCapabilities,
|
|
430
431
|
computeBaseCapacity,
|
|
431
432
|
computeEntityCapabilities,
|
|
433
|
+
CAPACITY_TIER_TABLE,
|
|
434
|
+
capacityTierMultiplier,
|
|
435
|
+
applyCapacityTier,
|
|
432
436
|
GATHERER_DEPTH_TABLE,
|
|
433
437
|
GATHERER_DEPTH_MAX_TIER,
|
|
434
438
|
gathererDepthForTier,
|
package/src/managers/actions.ts
CHANGED
|
@@ -471,6 +471,16 @@ export class ActionsManager extends BaseManager {
|
|
|
471
471
|
})
|
|
472
472
|
}
|
|
473
473
|
|
|
474
|
+
swaptile(hubId: UInt64Type, aGx: number, aGy: number, bGx: number, bGy: number): Action {
|
|
475
|
+
return this.server.action('swaptile', {
|
|
476
|
+
hub_id: UInt64.from(hubId),
|
|
477
|
+
a_gx: aGx,
|
|
478
|
+
a_gy: aGy,
|
|
479
|
+
b_gx: bGx,
|
|
480
|
+
b_gy: bGy,
|
|
481
|
+
})
|
|
482
|
+
}
|
|
483
|
+
|
|
474
484
|
buildplot(entityId: UInt64Type, plotId: UInt64Type): Action {
|
|
475
485
|
return this.server.action('buildplot', {
|
|
476
486
|
builder_id: UInt64.from(entityId),
|
|
@@ -269,7 +269,7 @@ export function buildModuleImmutable(
|
|
|
269
269
|
base.push({first: 'resonance', second: ['uint16', res]})
|
|
270
270
|
base.push({first: 'plasticity', second: ['uint16', pla]})
|
|
271
271
|
base.push({first: 'conductivity', second: ['uint16', con]})
|
|
272
|
-
base.push({first: 'capacity', second: ['uint8', computeHaulerCapacity(res)]})
|
|
272
|
+
base.push({first: 'capacity', second: ['uint8', computeHaulerCapacity(res, item.tier)]})
|
|
273
273
|
base.push({first: 'efficiency', second: ['uint16', computeHaulerEfficiency(pla)]})
|
|
274
274
|
base.push({first: 'drain', second: ['uint16', computeHaulerDrain(con)]})
|
|
275
275
|
break
|
package/src/nft/description.ts
CHANGED
|
@@ -18,10 +18,13 @@ import {
|
|
|
18
18
|
ITEM_EXTRACTOR_T1_PACKED,
|
|
19
19
|
ITEM_FACTORY_T1_PACKED,
|
|
20
20
|
ITEM_GATHERER_T1,
|
|
21
|
+
ITEM_GATHERER_T2,
|
|
21
22
|
ITEM_GENERATOR_T1,
|
|
23
|
+
ITEM_HAULER_SHIP_T2_PACKED,
|
|
22
24
|
ITEM_HAULER_T1,
|
|
23
25
|
ITEM_BATTERY_T1,
|
|
24
26
|
ITEM_LOADER_T1,
|
|
27
|
+
ITEM_PROSPECTOR_T2_PACKED,
|
|
25
28
|
ITEM_SHIP_T1_PACKED,
|
|
26
29
|
ITEM_STORAGE_T1,
|
|
27
30
|
ITEM_WAREHOUSE_T1_PACKED,
|
|
@@ -50,11 +53,6 @@ export function computeBaseCapacityContainer(stats: bigint): number {
|
|
|
50
53
|
return Math.floor(22_000_000 * 6 ** (s / 1998))
|
|
51
54
|
}
|
|
52
55
|
|
|
53
|
-
export function computeBaseCapacityContainerT2(stats: bigint): number {
|
|
54
|
-
const s = decodeStat(stats, 0) + decodeStat(stats, 2)
|
|
55
|
-
return Math.floor(24_000_000 * 6 ** (s / 2947))
|
|
56
|
-
}
|
|
57
|
-
|
|
58
56
|
export function computeBaseCapacityWarehouse(stats: bigint): number {
|
|
59
57
|
const s = decodeStat(stats, 0) + decodeStat(stats, 2)
|
|
60
58
|
return Math.floor(100_000_000 * 6 ** (s / 1998))
|
|
@@ -83,7 +81,8 @@ export const computeLoaderMass = (ins: number): number => Math.max(200, 2000 - i
|
|
|
83
81
|
export const computeLoaderThrust = (pla: number): number => 1 + idiv(pla * pla, 10000)
|
|
84
82
|
export const computeCrafterSpeed = (rea: number): number => 100 + idiv(rea * 4, 5)
|
|
85
83
|
export const computeCrafterDrain = (fin: number): number => Math.max(5, 30 - idiv(fin, 33))
|
|
86
|
-
export const computeHaulerCapacity = (fin: number): number =>
|
|
84
|
+
export const computeHaulerCapacity = (fin: number, tier: number): number =>
|
|
85
|
+
Math.max(tier, tier + idiv(fin, 400))
|
|
87
86
|
export const computeHaulerEfficiency = (con: number): number => 2000 + con * 6
|
|
88
87
|
export const computeHaulerDrain = (com: number): number => Math.max(3, 15 - idiv(com, 80))
|
|
89
88
|
export const computeWarpRange = (stat: number): number => 100 + stat * 3
|
|
@@ -114,6 +113,10 @@ export function entityDisplayName(itemId: number): string {
|
|
|
114
113
|
return 'Container'
|
|
115
114
|
case ITEM_CONTAINER_T2_PACKED:
|
|
116
115
|
return 'Container'
|
|
116
|
+
case ITEM_PROSPECTOR_T2_PACKED:
|
|
117
|
+
return 'Prospector'
|
|
118
|
+
case ITEM_HAULER_SHIP_T2_PACKED:
|
|
119
|
+
return 'Hauler'
|
|
117
120
|
default:
|
|
118
121
|
return 'Entity'
|
|
119
122
|
}
|
|
@@ -127,6 +130,8 @@ export function moduleDisplayName(itemId: number): string {
|
|
|
127
130
|
return 'Reactor'
|
|
128
131
|
case ITEM_GATHERER_T1:
|
|
129
132
|
return 'Limpet Bay'
|
|
133
|
+
case ITEM_GATHERER_T2:
|
|
134
|
+
return 'Gatherer (T2)'
|
|
130
135
|
case ITEM_LOADER_T1:
|
|
131
136
|
return 'Shuttle Bay'
|
|
132
137
|
case ITEM_CRAFTER_T1:
|
|
@@ -202,7 +207,8 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
|
|
|
202
207
|
const res = decodeStat(stats, 0)
|
|
203
208
|
const pla = decodeStat(stats, 1)
|
|
204
209
|
const ref = decodeStat(stats, 2)
|
|
205
|
-
|
|
210
|
+
const tier = getItem(itemId).tier
|
|
211
|
+
out += ` Capacity ${computeHaulerCapacity(res, tier)} Efficiency ${computeHaulerEfficiency(pla)} Drain ${computeHaulerDrain(ref)}`
|
|
206
212
|
break
|
|
207
213
|
}
|
|
208
214
|
case MODULE_WARP: {
|
|
@@ -230,18 +236,21 @@ export function buildEntityDescription(
|
|
|
230
236
|
): string {
|
|
231
237
|
const hullMass = computeBaseHullmass(hullStats)
|
|
232
238
|
let baseCapacity = 0
|
|
233
|
-
if (
|
|
239
|
+
if (
|
|
240
|
+
itemId === ITEM_SHIP_T1_PACKED ||
|
|
241
|
+
itemId === ITEM_PROSPECTOR_T2_PACKED ||
|
|
242
|
+
itemId === ITEM_HAULER_SHIP_T2_PACKED
|
|
243
|
+
) {
|
|
234
244
|
baseCapacity = computeBaseCapacityShip(hullStats)
|
|
235
245
|
} else if (itemId === ITEM_WAREHOUSE_T1_PACKED) {
|
|
236
246
|
baseCapacity = computeBaseCapacityWarehouse(hullStats)
|
|
237
247
|
} else if (
|
|
238
248
|
itemId === ITEM_EXTRACTOR_T1_PACKED ||
|
|
239
249
|
itemId === ITEM_FACTORY_T1_PACKED ||
|
|
240
|
-
itemId === ITEM_CONTAINER_T1_PACKED
|
|
250
|
+
itemId === ITEM_CONTAINER_T1_PACKED ||
|
|
251
|
+
itemId === ITEM_CONTAINER_T2_PACKED
|
|
241
252
|
) {
|
|
242
253
|
baseCapacity = computeBaseCapacityContainer(hullStats)
|
|
243
|
-
} else if (itemId === ITEM_CONTAINER_T2_PACKED) {
|
|
244
|
-
baseCapacity = computeBaseCapacityContainerT2(hullStats)
|
|
245
254
|
}
|
|
246
255
|
|
|
247
256
|
let out = entityDisplayName(itemId)
|
|
@@ -20,6 +20,7 @@ export interface ModuleDescription {
|
|
|
20
20
|
export interface RenderDescriptionOptions {
|
|
21
21
|
translate?: (id: string, fallback: string) => string
|
|
22
22
|
formatNumber?: (n: number) => string
|
|
23
|
+
formatParam?: (paramName: string, value: number, descId: string) => string | undefined
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
interface TemplateSpec {
|
|
@@ -27,6 +28,7 @@ interface TemplateSpec {
|
|
|
27
28
|
template: string
|
|
28
29
|
params: readonly [string, string][]
|
|
29
30
|
highlightKeys: readonly string[]
|
|
31
|
+
derive?: Record<string, (get: (label: string) => number) => number>
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
const TEMPLATES: Record<string, TemplateSpec> = {
|
|
@@ -53,13 +55,15 @@ const TEMPLATES: Record<string, TemplateSpec> = {
|
|
|
53
55
|
gatherer: {
|
|
54
56
|
id: 'module.gatherer.description',
|
|
55
57
|
template:
|
|
56
|
-
'mines resources at {yield} yield to a max depth of {depth} while draining {drain} energy per
|
|
58
|
+
'mines resources at {yield} yield to a max depth of {depth} while draining {drain} energy per minute',
|
|
57
59
|
params: [
|
|
58
60
|
['yield', 'Yield'],
|
|
59
61
|
['depth', 'Depth'],
|
|
60
|
-
['drain', 'Drain'],
|
|
61
62
|
],
|
|
62
63
|
highlightKeys: ['yield', 'depth', 'drain'],
|
|
64
|
+
derive: {
|
|
65
|
+
drain: (get) => Math.round((get('Drain') / 10000) * 60 * 10) / 10,
|
|
66
|
+
},
|
|
63
67
|
},
|
|
64
68
|
loading: {
|
|
65
69
|
id: 'module.loading.description',
|
|
@@ -72,12 +76,12 @@ const TEMPLATES: Record<string, TemplateSpec> = {
|
|
|
72
76
|
},
|
|
73
77
|
crafting: {
|
|
74
78
|
id: 'module.crafting.description',
|
|
75
|
-
template: 'manufactures items at {speed} speed while draining {drain} energy per
|
|
76
|
-
params: [
|
|
77
|
-
['speed', 'Speed'],
|
|
78
|
-
['drain', 'Drain'],
|
|
79
|
-
],
|
|
79
|
+
template: 'manufactures items at {speed} speed while draining {drain} energy per minute',
|
|
80
|
+
params: [['speed', 'Speed']],
|
|
80
81
|
highlightKeys: ['speed', 'drain'],
|
|
82
|
+
derive: {
|
|
83
|
+
drain: (get) => Math.round(((get('Drain') * get('Speed')) / 150000) * 60 * 10) / 10,
|
|
84
|
+
},
|
|
81
85
|
},
|
|
82
86
|
storage: {
|
|
83
87
|
id: 'module.storage.description',
|
|
@@ -114,6 +118,12 @@ export function describeModule(input: CapabilityInput): ModuleDescription | null
|
|
|
114
118
|
const attr = input.attributes.find((a) => a.label === attrLabel)
|
|
115
119
|
if (attr) params[paramName] = attr.value
|
|
116
120
|
}
|
|
121
|
+
if (spec.derive) {
|
|
122
|
+
const get = (label: string) => input.attributes.find((a) => a.label === label)?.value ?? 0
|
|
123
|
+
for (const [paramName, fn] of Object.entries(spec.derive)) {
|
|
124
|
+
params[paramName] = fn(get)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
117
127
|
return {
|
|
118
128
|
id: spec.id,
|
|
119
129
|
template: spec.template,
|
|
@@ -158,7 +168,10 @@ export function renderDescription(
|
|
|
158
168
|
if (raw === undefined) {
|
|
159
169
|
spans.push({text: `{${paramName}}`})
|
|
160
170
|
} else {
|
|
161
|
-
const formatted =
|
|
171
|
+
const formatted =
|
|
172
|
+
typeof raw === 'number'
|
|
173
|
+
? (options?.formatParam?.(paramName, raw, desc.id) ?? formatNumber(raw))
|
|
174
|
+
: raw
|
|
162
175
|
const highlight = (desc.highlightKeys as readonly string[]).includes(paramName)
|
|
163
176
|
spans.push(highlight ? {text: formatted, highlight: true} : {text: formatted})
|
|
164
177
|
}
|
|
@@ -2,10 +2,11 @@ import {expect, test} from 'bun:test'
|
|
|
2
2
|
import {UInt16, UInt64} from '@wharfkit/antelope'
|
|
3
3
|
import {resolveItem} from './resolve-item'
|
|
4
4
|
import {encodeStats} from '../derivation/crafting'
|
|
5
|
-
import {computeContainerCapabilities} from '../derivation/capabilities'
|
|
5
|
+
import {computeBaseHullmass, computeContainerCapabilities} from '../derivation/capabilities'
|
|
6
6
|
import {
|
|
7
7
|
ITEM_EXTRACTOR_T1_PACKED,
|
|
8
8
|
ITEM_FACTORY_T1_PACKED,
|
|
9
|
+
ITEM_HUB_T1_PACKED,
|
|
9
10
|
ITEM_MASS_DRIVER_T1_PACKED,
|
|
10
11
|
ITEM_MASS_CATCHER_T1_PACKED,
|
|
11
12
|
} from '../data/item-ids'
|
|
@@ -35,3 +36,13 @@ for (const [label, itemId] of CONTAINER_ENTITIES) {
|
|
|
35
36
|
expect(capacity).toBe(expected)
|
|
36
37
|
})
|
|
37
38
|
}
|
|
39
|
+
|
|
40
|
+
test('resolveItem resolves the station hub with hullmass and zero cargo capacity', () => {
|
|
41
|
+
const stats = hullStats(300, 100, 400)
|
|
42
|
+
const resolved = resolveItem(UInt16.from(ITEM_HUB_T1_PACKED), stats)
|
|
43
|
+
const hull = resolved.attributes?.find((g) => g.capability === 'Hull')
|
|
44
|
+
expect(hull?.attributes.find((a) => a.label === 'Mass')?.value).toBe(
|
|
45
|
+
computeBaseHullmass({density: 100})
|
|
46
|
+
)
|
|
47
|
+
expect(hull?.attributes.find((a) => a.label === 'Capacity')?.value).toBe(0)
|
|
48
|
+
})
|