@shipload/sdk 1.0.0-next.45 → 1.0.0-next.47
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 +236 -39
- package/lib/shipload.js +1465 -191
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +1450 -192
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +81 -15
- package/lib/testing.js +377 -44
- package/lib/testing.js.map +1 -1
- package/lib/testing.m.js +378 -45
- 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 +13 -0
- package/src/contracts/server.ts +242 -28
- package/src/data/capabilities.ts +1 -1
- 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 +75 -7
- package/src/data/recipes-runtime.ts +1 -0
- package/src/data/recipes.json +736 -0
- package/src/derivation/capabilities.test.ts +11 -11
- package/src/derivation/capabilities.ts +42 -27
- package/src/derivation/crafting.ts +13 -9
- 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 +25 -12
- package/src/resolution/describe-module.ts +21 -8
- package/src/resolution/resolve-item.ts +31 -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
|
-
}
|
|
@@ -141,16 +141,20 @@ export function computeEntityStats(
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
return recipe.statSlots.map((slot) => {
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
144
|
+
const slotKey = keyForStatSlot(recipe, slot)
|
|
145
|
+
if (slot.sources.length === 0) return {key: slotKey, value: 1}
|
|
146
|
+
let weightedSum = 0
|
|
147
|
+
let totalWeight = 0
|
|
148
|
+
for (const src of slot.sources) {
|
|
149
|
+
const key = keyForRecipeInputStat(recipe, src.inputIndex, src.statIndex)
|
|
150
|
+
const input = recipe.inputs[src.inputIndex]
|
|
151
|
+
if (!input) continue
|
|
152
|
+
const weight = recipe.blendWeights?.[src.inputIndex] ?? 1
|
|
153
|
+
weightedSum += (blendedByComponent[input.itemId]?.[key] ?? 0) * weight
|
|
154
|
+
totalWeight += weight
|
|
150
155
|
}
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
return {key, value: Math.max(1, Math.min(999, value))}
|
|
156
|
+
const value = totalWeight > 0 ? Math.floor(weightedSum / totalWeight) : 0
|
|
157
|
+
return {key: slotKey, value: Math.max(1, Math.min(999, value))}
|
|
154
158
|
})
|
|
155
159
|
}
|
|
156
160
|
|
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,15 @@ 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,
|
|
25
|
+
ITEM_HAULER_T2,
|
|
23
26
|
ITEM_BATTERY_T1,
|
|
27
|
+
ITEM_LAUNCHER_T1,
|
|
24
28
|
ITEM_LOADER_T1,
|
|
29
|
+
ITEM_PROSPECTOR_T2_PACKED,
|
|
25
30
|
ITEM_SHIP_T1_PACKED,
|
|
26
31
|
ITEM_STORAGE_T1,
|
|
27
32
|
ITEM_WAREHOUSE_T1_PACKED,
|
|
@@ -50,11 +55,6 @@ export function computeBaseCapacityContainer(stats: bigint): number {
|
|
|
50
55
|
return Math.floor(22_000_000 * 6 ** (s / 1998))
|
|
51
56
|
}
|
|
52
57
|
|
|
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
58
|
export function computeBaseCapacityWarehouse(stats: bigint): number {
|
|
59
59
|
const s = decodeStat(stats, 0) + decodeStat(stats, 2)
|
|
60
60
|
return Math.floor(100_000_000 * 6 ** (s / 1998))
|
|
@@ -83,7 +83,8 @@ export const computeLoaderMass = (ins: number): number => Math.max(200, 2000 - i
|
|
|
83
83
|
export const computeLoaderThrust = (pla: number): number => 1 + idiv(pla * pla, 10000)
|
|
84
84
|
export const computeCrafterSpeed = (rea: number): number => 100 + idiv(rea * 4, 5)
|
|
85
85
|
export const computeCrafterDrain = (fin: number): number => Math.max(5, 30 - idiv(fin, 33))
|
|
86
|
-
export const computeHaulerCapacity = (fin: number): number =>
|
|
86
|
+
export const computeHaulerCapacity = (fin: number, tier: number): number =>
|
|
87
|
+
Math.max(tier, tier + idiv(fin, 400))
|
|
87
88
|
export const computeHaulerEfficiency = (con: number): number => 2000 + con * 6
|
|
88
89
|
export const computeHaulerDrain = (com: number): number => Math.max(3, 15 - idiv(com, 80))
|
|
89
90
|
export const computeWarpRange = (stat: number): number => 100 + stat * 3
|
|
@@ -114,6 +115,10 @@ export function entityDisplayName(itemId: number): string {
|
|
|
114
115
|
return 'Container'
|
|
115
116
|
case ITEM_CONTAINER_T2_PACKED:
|
|
116
117
|
return 'Container'
|
|
118
|
+
case ITEM_PROSPECTOR_T2_PACKED:
|
|
119
|
+
return 'Prospector'
|
|
120
|
+
case ITEM_HAULER_SHIP_T2_PACKED:
|
|
121
|
+
return 'Hauler'
|
|
117
122
|
default:
|
|
118
123
|
return 'Entity'
|
|
119
124
|
}
|
|
@@ -124,8 +129,9 @@ export function moduleDisplayName(itemId: number): string {
|
|
|
124
129
|
case ITEM_ENGINE_T1:
|
|
125
130
|
return 'Engine'
|
|
126
131
|
case ITEM_GENERATOR_T1:
|
|
127
|
-
return '
|
|
132
|
+
return 'Power Core'
|
|
128
133
|
case ITEM_GATHERER_T1:
|
|
134
|
+
case ITEM_GATHERER_T2:
|
|
129
135
|
return 'Limpet Bay'
|
|
130
136
|
case ITEM_LOADER_T1:
|
|
131
137
|
return 'Shuttle Bay'
|
|
@@ -134,11 +140,14 @@ export function moduleDisplayName(itemId: number): string {
|
|
|
134
140
|
case ITEM_STORAGE_T1:
|
|
135
141
|
return 'Cargo Hold'
|
|
136
142
|
case ITEM_HAULER_T1:
|
|
143
|
+
case ITEM_HAULER_T2:
|
|
137
144
|
return 'Tractor Beam'
|
|
138
145
|
case ITEM_WARP_T1:
|
|
139
146
|
return 'Warp Drive'
|
|
140
147
|
case ITEM_BATTERY_T1:
|
|
141
148
|
return 'Battery Bank'
|
|
149
|
+
case ITEM_LAUNCHER_T1:
|
|
150
|
+
return 'Drive Coil'
|
|
142
151
|
default:
|
|
143
152
|
return 'Module'
|
|
144
153
|
}
|
|
@@ -202,7 +211,8 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
|
|
|
202
211
|
const res = decodeStat(stats, 0)
|
|
203
212
|
const pla = decodeStat(stats, 1)
|
|
204
213
|
const ref = decodeStat(stats, 2)
|
|
205
|
-
|
|
214
|
+
const tier = getItem(itemId).tier
|
|
215
|
+
out += ` Capacity ${computeHaulerCapacity(res, tier)} Efficiency ${computeHaulerEfficiency(pla)} Drain ${computeHaulerDrain(ref)}`
|
|
206
216
|
break
|
|
207
217
|
}
|
|
208
218
|
case MODULE_WARP: {
|
|
@@ -230,18 +240,21 @@ export function buildEntityDescription(
|
|
|
230
240
|
): string {
|
|
231
241
|
const hullMass = computeBaseHullmass(hullStats)
|
|
232
242
|
let baseCapacity = 0
|
|
233
|
-
if (
|
|
243
|
+
if (
|
|
244
|
+
itemId === ITEM_SHIP_T1_PACKED ||
|
|
245
|
+
itemId === ITEM_PROSPECTOR_T2_PACKED ||
|
|
246
|
+
itemId === ITEM_HAULER_SHIP_T2_PACKED
|
|
247
|
+
) {
|
|
234
248
|
baseCapacity = computeBaseCapacityShip(hullStats)
|
|
235
249
|
} else if (itemId === ITEM_WAREHOUSE_T1_PACKED) {
|
|
236
250
|
baseCapacity = computeBaseCapacityWarehouse(hullStats)
|
|
237
251
|
} else if (
|
|
238
252
|
itemId === ITEM_EXTRACTOR_T1_PACKED ||
|
|
239
253
|
itemId === ITEM_FACTORY_T1_PACKED ||
|
|
240
|
-
itemId === ITEM_CONTAINER_T1_PACKED
|
|
254
|
+
itemId === ITEM_CONTAINER_T1_PACKED ||
|
|
255
|
+
itemId === ITEM_CONTAINER_T2_PACKED
|
|
241
256
|
) {
|
|
242
257
|
baseCapacity = computeBaseCapacityContainer(hullStats)
|
|
243
|
-
} else if (itemId === ITEM_CONTAINER_T2_PACKED) {
|
|
244
|
-
baseCapacity = computeBaseCapacityContainerT2(hullStats)
|
|
245
258
|
}
|
|
246
259
|
|
|
247
260
|
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
|
}
|