@shipload/sdk 1.0.0-next.4 → 1.0.0-next.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/scan.d.ts +34 -0
- package/lib/scan.js +136 -0
- package/lib/scan.js.map +1 -0
- package/lib/scan.m.js +129 -0
- package/lib/scan.m.js.map +1 -0
- package/lib/shipload.d.ts +2473 -973
- package/lib/shipload.js +11529 -5211
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +11338 -5162
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +970 -0
- package/lib/testing.js +4013 -0
- package/lib/testing.js.map +1 -0
- package/lib/testing.m.js +4007 -0
- package/lib/testing.m.js.map +1 -0
- package/package.json +20 -2
- package/src/capabilities/craftable.ts +51 -0
- package/src/capabilities/crafting.test.ts +7 -0
- package/src/capabilities/crafting.ts +5 -6
- package/src/capabilities/gathering.test.ts +16 -0
- package/src/capabilities/gathering.ts +35 -18
- package/src/capabilities/index.ts +0 -1
- package/src/capabilities/modules.ts +9 -0
- package/src/capabilities/storage.ts +16 -1
- package/src/contracts/platform.ts +231 -3
- package/src/contracts/server.ts +1021 -481
- package/src/coordinates/address.ts +88 -0
- package/src/coordinates/constants.test.ts +15 -0
- package/src/coordinates/constants.ts +23 -0
- package/src/coordinates/index.ts +15 -0
- package/src/coordinates/memo.test.ts +47 -0
- package/src/coordinates/memo.ts +20 -0
- package/src/coordinates/permutation.ts +77 -0
- package/src/coordinates/regions.ts +48 -0
- package/src/coordinates/sectors.ts +115 -0
- package/src/data/capabilities.ts +12 -5
- package/src/data/capability-formulas.ts +14 -7
- package/src/data/catalog.ts +0 -5
- package/src/data/colors.ts +14 -47
- package/src/data/entities.json +76 -10
- package/src/data/item-ids.ts +18 -12
- package/src/data/items.json +321 -38
- package/src/data/kind-registry.json +109 -0
- package/src/data/kind-registry.ts +165 -0
- package/src/data/metadata.ts +119 -33
- package/src/data/recipes-runtime.ts +3 -23
- package/src/data/recipes.json +238 -117
- package/src/derivation/build-methods.ts +45 -0
- package/src/derivation/capabilities.test.ts +151 -0
- package/src/derivation/capabilities.ts +512 -0
- package/src/derivation/capability-mappings.ts +9 -12
- package/src/derivation/crafting.ts +23 -24
- package/src/derivation/index.ts +25 -2
- package/src/derivation/recipe-usage.test.ts +78 -0
- package/src/derivation/recipe-usage.ts +141 -0
- package/src/derivation/reserve-regen.ts +34 -0
- package/src/derivation/resources.ts +125 -38
- package/src/derivation/rollups.test.ts +55 -0
- package/src/derivation/rollups.ts +56 -0
- package/src/derivation/stars.test.ts +51 -0
- package/src/derivation/stars.ts +15 -0
- package/src/derivation/stats.ts +6 -6
- package/src/derivation/stratum.ts +17 -20
- package/src/derivation/tiers.ts +40 -7
- package/src/derivation/wormhole.ts +136 -0
- package/src/entities/entity.ts +98 -0
- package/src/entities/gamestate.ts +3 -28
- package/src/entities/makers.ts +124 -134
- package/src/entities/slot-multiplier.ts +43 -0
- package/src/errors.ts +12 -16
- package/src/format.ts +26 -4
- package/src/index-module.ts +267 -47
- package/src/managers/actions.ts +528 -95
- package/src/managers/base.ts +6 -2
- package/src/managers/construction-types.ts +80 -0
- package/src/managers/construction.ts +412 -0
- package/src/managers/context.ts +20 -1
- package/src/managers/coordinates.ts +14 -0
- package/src/managers/entities.ts +18 -66
- package/src/managers/epochs.ts +40 -0
- package/src/managers/index.ts +17 -1
- package/src/managers/locations.ts +25 -29
- package/src/managers/nft.test.ts +14 -0
- package/src/managers/nft.ts +70 -0
- package/src/managers/plot.ts +122 -0
- package/src/nft/atomicassets.abi.json +1342 -0
- package/src/nft/atomicassets.ts +237 -0
- package/src/nft/atomicdata.ts +130 -0
- package/src/nft/buildImmutableData.ts +338 -0
- package/src/nft/description.ts +98 -24
- package/src/nft/index.ts +3 -0
- package/src/planner/index.ts +127 -0
- package/src/planner/planner.test.ts +319 -0
- package/src/resolution/describe-module.ts +18 -13
- package/src/resolution/display-name.ts +38 -10
- package/src/resolution/resolve-item.test.ts +37 -0
- package/src/resolution/resolve-item.ts +55 -24
- package/src/scan/index.ts +180 -0
- package/src/scan/scan-wasm.base64.ts +2 -0
- package/src/scheduling/accessor.ts +68 -22
- package/src/scheduling/availability.ts +108 -0
- package/src/scheduling/cancel.test.ts +348 -0
- package/src/scheduling/cancel.ts +209 -0
- package/src/scheduling/energy.ts +47 -0
- package/src/scheduling/idle-resolve.ts +45 -0
- package/src/scheduling/lane-core.ts +128 -0
- package/src/scheduling/lanes.test.ts +249 -0
- package/src/scheduling/lanes.ts +198 -0
- package/src/scheduling/projection.ts +209 -105
- package/src/scheduling/schedule.ts +241 -104
- package/src/scheduling/task-cargo.ts +46 -0
- package/src/shipload.ts +21 -1
- package/src/subscriptions/manager.ts +229 -142
- package/src/subscriptions/mappers.ts +5 -8
- package/src/subscriptions/types.ts +11 -3
- package/src/testing/catalog-hash.ts +19 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/projection-parity.ts +167 -0
- package/src/travel/reach.ts +23 -0
- package/src/travel/route-planner.ts +196 -0
- package/src/travel/travel.ts +200 -112
- package/src/types/capabilities.ts +29 -6
- package/src/types/entity.ts +3 -3
- package/src/types/index.ts +0 -1
- package/src/types.ts +28 -13
- package/src/utils/cargo.ts +27 -0
- package/src/utils/display-name.ts +70 -0
- package/src/utils/system.ts +36 -24
- package/src/capabilities/loading.ts +0 -8
- package/src/entities/container.ts +0 -108
- package/src/entities/ship-deploy.ts +0 -259
- package/src/entities/ship.ts +0 -204
- package/src/entities/warehouse.ts +0 -119
- package/src/types/entity-traits.ts +0 -69
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import {SLOT_FORMULAS, type SlotConsumerKind} from '../data/capability-formulas'
|
|
2
2
|
import {getStatDefinitions, type StatDefinition} from './stats'
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
type Recipe,
|
|
6
|
-
type RecipeInput,
|
|
7
|
-
type RecipeInputCategory,
|
|
8
|
-
} from '../data/recipes-runtime'
|
|
3
|
+
import {getRecipe, type Recipe} from '../data/recipes-runtime'
|
|
4
|
+
import {getItem} from '../data/catalog'
|
|
9
5
|
import {
|
|
10
6
|
ITEM_ENGINE_T1,
|
|
7
|
+
ITEM_EXTRACTOR_T1_PACKED,
|
|
11
8
|
ITEM_GENERATOR_T1,
|
|
12
9
|
ITEM_GATHERER_T1,
|
|
13
10
|
ITEM_LOADER_T1,
|
|
@@ -15,6 +12,7 @@ import {
|
|
|
15
12
|
ITEM_STORAGE_T1,
|
|
16
13
|
ITEM_HAULER_T1,
|
|
17
14
|
ITEM_WARP_T1,
|
|
15
|
+
ITEM_BATTERY_T1,
|
|
18
16
|
ITEM_SHIP_T1_PACKED,
|
|
19
17
|
ITEM_CONTAINER_T1_PACKED,
|
|
20
18
|
ITEM_WAREHOUSE_T1_PACKED,
|
|
@@ -31,16 +29,14 @@ export const KIND_TO_ITEM_ID: Record<SlotConsumerKind, number> = {
|
|
|
31
29
|
storage: ITEM_STORAGE_T1,
|
|
32
30
|
hauler: ITEM_HAULER_T1,
|
|
33
31
|
warp: ITEM_WARP_T1,
|
|
32
|
+
battery: ITEM_BATTERY_T1,
|
|
34
33
|
'ship-t1': ITEM_SHIP_T1_PACKED,
|
|
35
34
|
'container-t1': ITEM_CONTAINER_T1_PACKED,
|
|
36
35
|
'warehouse-t1': ITEM_WAREHOUSE_T1_PACKED,
|
|
36
|
+
'extractor-t1': ITEM_EXTRACTOR_T1_PACKED,
|
|
37
37
|
'container-t2': ITEM_CONTAINER_T2_PACKED,
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
function isCategoryInput(input: RecipeInput): input is RecipeInputCategory {
|
|
41
|
-
return 'category' in input
|
|
42
|
-
}
|
|
43
|
-
|
|
44
40
|
/**
|
|
45
41
|
* Walk a recipe's slot source down to the raw category stat that ultimately
|
|
46
42
|
* lands in that slot. Returns the StatDefinition or undefined if the trace
|
|
@@ -56,8 +52,9 @@ function traceToRawCategoryStat(
|
|
|
56
52
|
): StatDefinition | undefined {
|
|
57
53
|
const input = recipe.inputs[source.inputIndex]
|
|
58
54
|
if (!input) return undefined
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
const inputItem = getItem(input.itemId)
|
|
56
|
+
if (inputItem.type === 'resource' && inputItem.category) {
|
|
57
|
+
const defs = getStatDefinitions(inputItem.category)
|
|
61
58
|
return defs[source.statIndex]
|
|
62
59
|
}
|
|
63
60
|
if (visited.has(input.itemId)) return undefined
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {UInt64} from '@wharfkit/antelope'
|
|
2
2
|
import type {ResourceCategory} from '../types'
|
|
3
|
-
import {
|
|
3
|
+
import {getRecipe, type Recipe} from '../data/recipes-runtime'
|
|
4
4
|
import {getItem} from '../data/catalog'
|
|
5
5
|
import {getStatDefinitions} from './stats'
|
|
6
6
|
import {deriveResourceStats} from './stratum'
|
|
@@ -58,11 +58,8 @@ function keyForStatSlot(
|
|
|
58
58
|
function keyForRecipeInputStat(recipe: Recipe, inputIndex: number, statIndex: number): string {
|
|
59
59
|
const input = recipe.inputs[inputIndex]
|
|
60
60
|
if (!input) return ''
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return defs[statIndex]?.key ?? ''
|
|
64
|
-
}
|
|
65
|
-
// itemId-typed input — its stats follow that item's own statSlots layout.
|
|
61
|
+
// Every input names an item by id; its stats follow that item's own layout
|
|
62
|
+
// (resource stat definitions for resources, statSlots for crafted items).
|
|
66
63
|
const innerKeys = getItemStatKeys(input.itemId)
|
|
67
64
|
return innerKeys[statIndex] ?? ''
|
|
68
65
|
}
|
|
@@ -117,10 +114,11 @@ export function computeComponentStats(
|
|
|
117
114
|
const src = slot.sources[0]
|
|
118
115
|
const key = keyForStatSlot(recipe, slot)
|
|
119
116
|
const input = src ? recipe.inputs[src.inputIndex] : undefined
|
|
120
|
-
|
|
117
|
+
const inputItem = input ? getItem(input.itemId) : undefined
|
|
118
|
+
if (!inputItem || inputItem.type !== 'resource' || !inputItem.category) {
|
|
121
119
|
return {key, value: Math.max(1, Math.min(999, 0))}
|
|
122
120
|
}
|
|
123
|
-
const matching = categoryStacks.find((cs) => cs.category ===
|
|
121
|
+
const matching = categoryStacks.find((cs) => cs.category === inputItem.category)
|
|
124
122
|
const value = matching ? blendStacks(matching.stacks, key) : 0
|
|
125
123
|
return {key, value: Math.max(1, Math.min(999, value))}
|
|
126
124
|
})
|
|
@@ -147,7 +145,7 @@ export function computeEntityStats(
|
|
|
147
145
|
const key = keyForStatSlot(recipe, slot)
|
|
148
146
|
if (!src) return {key, value: 1}
|
|
149
147
|
const input = recipe.inputs[src.inputIndex]
|
|
150
|
-
if (!input
|
|
148
|
+
if (!input) {
|
|
151
149
|
return {key, value: 1}
|
|
152
150
|
}
|
|
153
151
|
const blended = blendedByComponent[input.itemId] ?? {}
|
|
@@ -185,12 +183,7 @@ export function computeInputMass(itemId: number): number {
|
|
|
185
183
|
|
|
186
184
|
let total = 0
|
|
187
185
|
for (const input of recipe.inputs) {
|
|
188
|
-
|
|
189
|
-
total += getItem(input.itemId).mass * input.quantity
|
|
190
|
-
} else {
|
|
191
|
-
const item = findItemByCategoryAndTier(input.category, input.tier)
|
|
192
|
-
total += item.mass * input.quantity
|
|
193
|
-
}
|
|
186
|
+
total += getItem(input.itemId).mass * input.quantity
|
|
194
187
|
}
|
|
195
188
|
return total
|
|
196
189
|
}
|
|
@@ -305,10 +298,13 @@ export function computeCraftedOutputStats(
|
|
|
305
298
|
const key = keyForRecipeInputStat(recipe, src.inputIndex, src.statIndex)
|
|
306
299
|
const input = recipe.inputs[src.inputIndex]
|
|
307
300
|
let value = 0
|
|
308
|
-
if (input
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
301
|
+
if (input) {
|
|
302
|
+
const inputItem = getItem(input.itemId)
|
|
303
|
+
if (inputItem.type === 'resource' && inputItem.category) {
|
|
304
|
+
value = blendStacks(decodedByCategory[inputItem.category] ?? [], key)
|
|
305
|
+
} else {
|
|
306
|
+
value = blendedByItem[input.itemId]?.[key] ?? 0
|
|
307
|
+
}
|
|
312
308
|
}
|
|
313
309
|
out.push(Math.max(1, Math.min(999, value)))
|
|
314
310
|
} else {
|
|
@@ -319,10 +315,13 @@ export function computeCraftedOutputStats(
|
|
|
319
315
|
const input = recipe.inputs[src.inputIndex]
|
|
320
316
|
const weight = recipe.blendWeights[src.inputIndex] ?? 1
|
|
321
317
|
let value = 0
|
|
322
|
-
if (input
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
318
|
+
if (input) {
|
|
319
|
+
const inputItem = getItem(input.itemId)
|
|
320
|
+
if (inputItem.type === 'resource' && inputItem.category) {
|
|
321
|
+
value = blendStacks(decodedByCategory[inputItem.category] ?? [], key)
|
|
322
|
+
} else {
|
|
323
|
+
value = blendedByItem[input.itemId]?.[key] ?? 0
|
|
324
|
+
}
|
|
326
325
|
}
|
|
327
326
|
weightedSum += value * weight
|
|
328
327
|
totalWeight += weight
|
|
@@ -340,7 +339,7 @@ export function computeCraftedOutputStats(
|
|
|
340
339
|
* returns a UInt64 whose bit-packed form matches what the contract writes
|
|
341
340
|
* to cargo_item.stats on gather.
|
|
342
341
|
*
|
|
343
|
-
* Use this whenever off-chain code simulates a gather (
|
|
342
|
+
* Use this whenever off-chain code simulates a gather (webapp, player
|
|
344
343
|
* scanners that project cargo outcomes) and needs a value that matches
|
|
345
344
|
* what on-chain cargo would carry.
|
|
346
345
|
*/
|
package/src/derivation/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export {
|
|
|
7
7
|
getEligibleResources,
|
|
8
8
|
getResourceWeight,
|
|
9
9
|
getLocationCandidates,
|
|
10
|
+
getLocationProfile,
|
|
10
11
|
getDepthThreshold,
|
|
11
12
|
getResourceTier,
|
|
12
13
|
DEPTH_THRESHOLD_T1,
|
|
@@ -16,7 +17,9 @@ export {
|
|
|
16
17
|
DEPTH_THRESHOLD_T5,
|
|
17
18
|
LOCATION_MIN_DEPTH,
|
|
18
19
|
LOCATION_MAX_DEPTH,
|
|
19
|
-
|
|
20
|
+
yieldThresholdAt,
|
|
21
|
+
YIELD_FRACTION_SHALLOW,
|
|
22
|
+
YIELD_FRACTION_DEEP,
|
|
20
23
|
PLANET_SUBTYPE_GAS_GIANT,
|
|
21
24
|
PLANET_SUBTYPE_ROCKY,
|
|
22
25
|
PLANET_SUBTYPE_TERRESTRIAL,
|
|
@@ -25,8 +28,28 @@ export {
|
|
|
25
28
|
PLANET_SUBTYPE_INDUSTRIAL,
|
|
26
29
|
} from './resources'
|
|
27
30
|
|
|
28
|
-
export {
|
|
31
|
+
export {
|
|
32
|
+
RESERVE_TIERS,
|
|
33
|
+
TIER_ROLL_MAX,
|
|
34
|
+
tierOfReserve,
|
|
35
|
+
rollTier,
|
|
36
|
+
rollWithinTier,
|
|
37
|
+
RESOURCE_TIER_MULT_TENTHS,
|
|
38
|
+
applyResourceTierMultiplier,
|
|
39
|
+
} from './tiers'
|
|
29
40
|
export type {ReserveTier, TierRange} from './tiers'
|
|
30
41
|
|
|
42
|
+
export {getEffectiveReserve} from './reserve-regen'
|
|
43
|
+
export type {EffectiveReserveInput} from './reserve-regen'
|
|
44
|
+
|
|
31
45
|
export * from './stats'
|
|
32
46
|
export * from './crafting'
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
STAR_STEP,
|
|
50
|
+
MAX_STARS_PER_STAT,
|
|
51
|
+
MAX_STAR_RATING,
|
|
52
|
+
starsForStat,
|
|
53
|
+
starRating,
|
|
54
|
+
statMagnitude,
|
|
55
|
+
} from './stars'
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {expect, test} from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
getAllRecipes,
|
|
4
|
+
getRecipeConsumers,
|
|
5
|
+
getComponentDemand,
|
|
6
|
+
getResourceDemand,
|
|
7
|
+
} from './recipe-usage'
|
|
8
|
+
import {
|
|
9
|
+
ITEM_SENSOR,
|
|
10
|
+
ITEM_RESIN,
|
|
11
|
+
ITEM_PLATE,
|
|
12
|
+
ITEM_BEAM,
|
|
13
|
+
ITEM_GATHERER_T1,
|
|
14
|
+
ITEM_CRAFTER_T1,
|
|
15
|
+
ITEM_EXTRACTOR_T1_PACKED,
|
|
16
|
+
ITEM_SHIP_T1_PACKED,
|
|
17
|
+
} from '../data/item-ids'
|
|
18
|
+
|
|
19
|
+
test('getAllRecipes returns the full catalog including the gatherer', () => {
|
|
20
|
+
const all = getAllRecipes()
|
|
21
|
+
expect(all.length).toBeGreaterThan(20)
|
|
22
|
+
expect(all.some((r) => r.outputItemId === ITEM_GATHERER_T1)).toBe(true)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('getRecipeConsumers lists every recipe that consumes Sensor', () => {
|
|
26
|
+
const consumers = getRecipeConsumers(ITEM_SENSOR)
|
|
27
|
+
const ids = consumers.map((c) => c.outputItemId).sort((a, b) => a - b)
|
|
28
|
+
expect(ids).toEqual(
|
|
29
|
+
[ITEM_GATHERER_T1, ITEM_CRAFTER_T1, ITEM_SHIP_T1_PACKED, ITEM_EXTRACTOR_T1_PACKED].sort(
|
|
30
|
+
(a, b) => a - b
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('Sensor feeds the gatherer drain stat', () => {
|
|
36
|
+
const consumers = getRecipeConsumers(ITEM_SENSOR)
|
|
37
|
+
const gatherer = consumers.find((c) => c.outputItemId === ITEM_GATHERER_T1)
|
|
38
|
+
expect(gatherer).toBeDefined()
|
|
39
|
+
const drain = gatherer?.statFlows.find(
|
|
40
|
+
(f) => f.capability === 'Gathering' && f.attribute === 'drain'
|
|
41
|
+
)
|
|
42
|
+
expect(drain).toBeDefined()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('Sensor is a mass-only sink in the Extractor recipe', () => {
|
|
46
|
+
const consumers = getRecipeConsumers(ITEM_SENSOR)
|
|
47
|
+
const extractor = consumers.find((c) => c.outputItemId === ITEM_EXTRACTOR_T1_PACKED)
|
|
48
|
+
expect(extractor).toBeDefined()
|
|
49
|
+
expect(extractor?.statFlows).toHaveLength(0)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('getResourceDemand returns the resource tonnage for a single-resource component', () => {
|
|
53
|
+
expect(getResourceDemand(ITEM_PLATE)).toEqual({ore: 10})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('getResourceDemand traces a dual-resource component to both resources', () => {
|
|
57
|
+
expect(getResourceDemand(ITEM_BEAM)).toEqual({ore: 5, gas: 5})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('getResourceDemand recurses through a module to raw resources', () => {
|
|
61
|
+
// Gatherer = 300 Beam (5 ore + 5 gas each) + 300 Sensor (10 crystal each)
|
|
62
|
+
expect(getResourceDemand(ITEM_GATHERER_T1)).toEqual({
|
|
63
|
+
ore: 1500,
|
|
64
|
+
gas: 1500,
|
|
65
|
+
crystal: 3000,
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('getResourceDemand scales by quantity', () => {
|
|
70
|
+
expect(getResourceDemand(ITEM_PLATE, 3)).toEqual({ore: 30})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('getComponentDemand reports Resin as consumed by exactly one recipe', () => {
|
|
74
|
+
const demand = getComponentDemand()
|
|
75
|
+
const resin = demand.find((d) => d.itemId === ITEM_RESIN)
|
|
76
|
+
expect(resin).toBeDefined()
|
|
77
|
+
expect(resin?.consumerCount).toBe(1)
|
|
78
|
+
})
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import recipes from '../data/recipes.json'
|
|
2
|
+
import {getRecipe, type Recipe} from '../data/recipes-runtime'
|
|
3
|
+
import {getItem} from '../data/catalog'
|
|
4
|
+
import {getStatDefinitions} from './stats'
|
|
5
|
+
import {SLOT_FORMULAS, type SlotConsumerKind} from '../data/capability-formulas'
|
|
6
|
+
import {KIND_TO_ITEM_ID} from './capability-mappings'
|
|
7
|
+
import type {ResourceCategory} from '../types'
|
|
8
|
+
|
|
9
|
+
export function getAllRecipes(): Recipe[] {
|
|
10
|
+
return recipes as unknown as Recipe[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type ResourceDemand = Partial<Record<ResourceCategory, number>>
|
|
14
|
+
|
|
15
|
+
function accumulateResourceDemand(itemId: number, quantity: number, out: ResourceDemand): void {
|
|
16
|
+
const item = getItem(itemId)
|
|
17
|
+
if (item.type === 'resource' && item.category) {
|
|
18
|
+
out[item.category] = (out[item.category] ?? 0) + quantity
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
const recipe = getRecipe(itemId)
|
|
22
|
+
if (!recipe) return
|
|
23
|
+
for (const input of recipe.inputs) {
|
|
24
|
+
accumulateResourceDemand(input.itemId, input.quantity * quantity, out)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Raw-resource tonnage to craft `quantity` of an item, tracing recipes to the resource leaves.
|
|
29
|
+
export function getResourceDemand(itemId: number, quantity = 1): ResourceDemand {
|
|
30
|
+
const out: ResourceDemand = {}
|
|
31
|
+
accumulateResourceDemand(itemId, quantity, out)
|
|
32
|
+
return out
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const ITEM_ID_TO_KIND = new Map<number, SlotConsumerKind>()
|
|
36
|
+
for (const [kind, itemId] of Object.entries(KIND_TO_ITEM_ID) as [SlotConsumerKind, number][]) {
|
|
37
|
+
ITEM_ID_TO_KIND.set(itemId, kind)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Traces a stat index down to the raw category stat label it carries (Sensor stat 0 → "Conductivity").
|
|
41
|
+
function resolveComponentStatLabel(itemId: number, statIndex: number): string | undefined {
|
|
42
|
+
let item: ReturnType<typeof getItem>
|
|
43
|
+
try {
|
|
44
|
+
item = getItem(itemId)
|
|
45
|
+
} catch {
|
|
46
|
+
return undefined
|
|
47
|
+
}
|
|
48
|
+
if (item.type === 'resource' && item.category) {
|
|
49
|
+
return getStatDefinitions(item.category)[statIndex]?.label
|
|
50
|
+
}
|
|
51
|
+
const recipe = getRecipe(itemId)
|
|
52
|
+
const slot = recipe?.statSlots[statIndex]
|
|
53
|
+
const source = slot?.sources[0]
|
|
54
|
+
if (!recipe || !source) return undefined
|
|
55
|
+
const input = recipe.inputs[source.inputIndex]
|
|
56
|
+
if (!input) return undefined
|
|
57
|
+
return resolveComponentStatLabel(input.itemId, source.statIndex)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface StatFlow {
|
|
61
|
+
slotIndex: number
|
|
62
|
+
capability?: string
|
|
63
|
+
attribute?: string
|
|
64
|
+
sourceStatIndex: number
|
|
65
|
+
sourceStatLabel?: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface RecipeConsumer {
|
|
69
|
+
outputItemId: number
|
|
70
|
+
quantity: number
|
|
71
|
+
statFlows: StatFlow[]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Every recipe that consumes `componentItemId`, with how its stats flow through. */
|
|
75
|
+
export function getRecipeConsumers(componentItemId: number): RecipeConsumer[] {
|
|
76
|
+
const out: RecipeConsumer[] = []
|
|
77
|
+
for (const recipe of getAllRecipes()) {
|
|
78
|
+
for (let inputIndex = 0; inputIndex < recipe.inputs.length; inputIndex++) {
|
|
79
|
+
if (recipe.inputs[inputIndex].itemId !== componentItemId) continue
|
|
80
|
+
const kind = ITEM_ID_TO_KIND.get(recipe.outputItemId)
|
|
81
|
+
const formulas = kind ? SLOT_FORMULAS[kind] : undefined
|
|
82
|
+
const statFlows: StatFlow[] = []
|
|
83
|
+
for (let slotIndex = 0; slotIndex < recipe.statSlots.length; slotIndex++) {
|
|
84
|
+
for (const source of recipe.statSlots[slotIndex].sources) {
|
|
85
|
+
if (source.inputIndex !== inputIndex) continue
|
|
86
|
+
const consumer = formulas?.[slotIndex]
|
|
87
|
+
statFlows.push({
|
|
88
|
+
slotIndex,
|
|
89
|
+
capability: consumer?.capability,
|
|
90
|
+
attribute: consumer?.attribute,
|
|
91
|
+
sourceStatIndex: source.statIndex,
|
|
92
|
+
sourceStatLabel: resolveComponentStatLabel(
|
|
93
|
+
componentItemId,
|
|
94
|
+
source.statIndex
|
|
95
|
+
),
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
out.push({
|
|
100
|
+
outputItemId: recipe.outputItemId,
|
|
101
|
+
quantity: recipe.inputs[inputIndex].quantity,
|
|
102
|
+
statFlows,
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return out
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface DemandRow {
|
|
110
|
+
itemId: number
|
|
111
|
+
consumerCount: number
|
|
112
|
+
statSourceCount: number
|
|
113
|
+
sinkOnlyCount: number
|
|
114
|
+
consumers: number[]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Demand tally for every item consumed as a recipe input, ascending by usage. */
|
|
118
|
+
export function getComponentDemand(): DemandRow[] {
|
|
119
|
+
const inputIds = new Set<number>()
|
|
120
|
+
for (const recipe of getAllRecipes()) {
|
|
121
|
+
for (const input of recipe.inputs) inputIds.add(input.itemId)
|
|
122
|
+
}
|
|
123
|
+
const rows: DemandRow[] = []
|
|
124
|
+
for (const itemId of inputIds) {
|
|
125
|
+
const consumers = getRecipeConsumers(itemId)
|
|
126
|
+
let statSourceCount = 0
|
|
127
|
+
let sinkOnlyCount = 0
|
|
128
|
+
for (const c of consumers) {
|
|
129
|
+
if (c.statFlows.length > 0) statSourceCount++
|
|
130
|
+
else sinkOnlyCount++
|
|
131
|
+
}
|
|
132
|
+
rows.push({
|
|
133
|
+
itemId,
|
|
134
|
+
consumerCount: consumers.length,
|
|
135
|
+
statSourceCount,
|
|
136
|
+
sinkOnlyCount,
|
|
137
|
+
consumers: consumers.map((c) => c.outputItemId),
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
return rows.sort((a, b) => a.consumerCount - b.consumerCount || a.itemId - b.itemId)
|
|
141
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type {BlockTimestamp, UInt32} from '@wharfkit/antelope'
|
|
2
|
+
|
|
3
|
+
export interface EffectiveReserveInput {
|
|
4
|
+
remaining: UInt32 | number
|
|
5
|
+
max_reserve: UInt32 | number
|
|
6
|
+
last_block: BlockTimestamp
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function toNumber(value: UInt32 | number): number {
|
|
10
|
+
return typeof value === 'number' ? value : Number(value)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function slotsBetween(now: BlockTimestamp, last: BlockTimestamp): number {
|
|
14
|
+
const nowMs = now.toMilliseconds()
|
|
15
|
+
const lastMs = last.toMilliseconds()
|
|
16
|
+
if (nowMs <= lastMs) return 0
|
|
17
|
+
return Math.floor((nowMs - lastMs) / 500)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getEffectiveReserve(
|
|
21
|
+
row: EffectiveReserveInput,
|
|
22
|
+
now: BlockTimestamp,
|
|
23
|
+
epochSeconds: number
|
|
24
|
+
): number {
|
|
25
|
+
const remaining = toNumber(row.remaining)
|
|
26
|
+
const max = toNumber(row.max_reserve)
|
|
27
|
+
if (remaining >= max) return max
|
|
28
|
+
const epochSlots = epochSeconds * 2
|
|
29
|
+
if (epochSlots === 0) return remaining
|
|
30
|
+
const elapsed = slotsBetween(now, row.last_block)
|
|
31
|
+
const regen = Math.floor((max * elapsed) / epochSlots)
|
|
32
|
+
const effective = remaining + regen
|
|
33
|
+
return effective >= max ? max : effective
|
|
34
|
+
}
|
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
import {getItem} from '../data/catalog'
|
|
2
|
+
import {LocationType} from '../types'
|
|
2
3
|
|
|
3
4
|
export const DEPTH_THRESHOLD_T1 = 0
|
|
4
|
-
export const DEPTH_THRESHOLD_T2 =
|
|
5
|
-
export const DEPTH_THRESHOLD_T3 =
|
|
6
|
-
export const DEPTH_THRESHOLD_T4 =
|
|
7
|
-
export const DEPTH_THRESHOLD_T5 =
|
|
5
|
+
export const DEPTH_THRESHOLD_T2 = 1500
|
|
6
|
+
export const DEPTH_THRESHOLD_T3 = 5000
|
|
7
|
+
export const DEPTH_THRESHOLD_T4 = 12000
|
|
8
|
+
export const DEPTH_THRESHOLD_T5 = 22000
|
|
9
|
+
export const DEPTH_THRESHOLD_T6 = 32000
|
|
10
|
+
export const DEPTH_THRESHOLD_T7 = 42000
|
|
11
|
+
export const DEPTH_THRESHOLD_T8 = 50000
|
|
12
|
+
export const DEPTH_THRESHOLD_T9 = 57000
|
|
13
|
+
export const DEPTH_THRESHOLD_T10 = 63000
|
|
8
14
|
|
|
9
15
|
export const LOCATION_MIN_DEPTH = 500
|
|
10
16
|
export const LOCATION_MAX_DEPTH = 65535
|
|
11
17
|
|
|
12
|
-
export const
|
|
18
|
+
export const YIELD_FRACTION_SHALLOW = 0.002
|
|
19
|
+
export const YIELD_FRACTION_DEEP = 0.0004
|
|
20
|
+
|
|
21
|
+
export function yieldThresholdAt(stratum: number): number {
|
|
22
|
+
const clamped = stratum > 65535 ? 65535 : stratum
|
|
23
|
+
const t = clamped / 65535
|
|
24
|
+
const fraction = YIELD_FRACTION_SHALLOW + (YIELD_FRACTION_DEEP - YIELD_FRACTION_SHALLOW) * t
|
|
25
|
+
return Math.floor(fraction * 0xffffffff)
|
|
26
|
+
}
|
|
13
27
|
|
|
14
28
|
export const PLANET_SUBTYPE_GAS_GIANT = 0
|
|
15
29
|
export const PLANET_SUBTYPE_ROCKY = 1
|
|
@@ -18,19 +32,22 @@ export const PLANET_SUBTYPE_ICY = 3
|
|
|
18
32
|
export const PLANET_SUBTYPE_OCEAN = 4
|
|
19
33
|
export const PLANET_SUBTYPE_INDUSTRIAL = 5
|
|
20
34
|
|
|
35
|
+
const DEPTH_THRESHOLD_TABLE = [
|
|
36
|
+
DEPTH_THRESHOLD_T1,
|
|
37
|
+
DEPTH_THRESHOLD_T2,
|
|
38
|
+
DEPTH_THRESHOLD_T3,
|
|
39
|
+
DEPTH_THRESHOLD_T4,
|
|
40
|
+
DEPTH_THRESHOLD_T5,
|
|
41
|
+
DEPTH_THRESHOLD_T6,
|
|
42
|
+
DEPTH_THRESHOLD_T7,
|
|
43
|
+
DEPTH_THRESHOLD_T8,
|
|
44
|
+
DEPTH_THRESHOLD_T9,
|
|
45
|
+
DEPTH_THRESHOLD_T10,
|
|
46
|
+
]
|
|
47
|
+
|
|
21
48
|
export function getDepthThreshold(tier: number): number {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return DEPTH_THRESHOLD_T1
|
|
25
|
-
case 2:
|
|
26
|
-
return DEPTH_THRESHOLD_T2
|
|
27
|
-
case 3:
|
|
28
|
-
return DEPTH_THRESHOLD_T3
|
|
29
|
-
case 4:
|
|
30
|
-
return DEPTH_THRESHOLD_T4
|
|
31
|
-
default:
|
|
32
|
-
return DEPTH_THRESHOLD_T5
|
|
33
|
-
}
|
|
49
|
+
if (tier < 1 || tier > 10) return 65535
|
|
50
|
+
return DEPTH_THRESHOLD_TABLE[tier - 1]
|
|
34
51
|
}
|
|
35
52
|
|
|
36
53
|
export function getResourceTier(itemId: number): number {
|
|
@@ -46,9 +63,9 @@ export function getResourceWeight(itemId: number, stratum: number): number {
|
|
|
46
63
|
|
|
47
64
|
switch (tier) {
|
|
48
65
|
case 1:
|
|
49
|
-
if (stratum <
|
|
50
|
-
if (stratum <
|
|
51
|
-
if (stratum <
|
|
66
|
+
if (stratum < DEPTH_THRESHOLD_T2) return 100
|
|
67
|
+
if (stratum < DEPTH_THRESHOLD_T3) return 80
|
|
68
|
+
if (stratum < DEPTH_THRESHOLD_T4) return 50
|
|
52
69
|
return 30
|
|
53
70
|
case 2:
|
|
54
71
|
if (depthAbove < 3000) return 40
|
|
@@ -67,37 +84,107 @@ export function getResourceWeight(itemId: number, stratum: number): number {
|
|
|
67
84
|
}
|
|
68
85
|
}
|
|
69
86
|
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
const ICY_RESOURCES = [101, 301, 302, 401, 403, 501, 502]
|
|
76
|
-
const OCEAN_RESOURCES = [201, 203, 301, 303, 501, 502, 503]
|
|
77
|
-
const INDUSTRIAL_RESOURCES = [101, 102, 103, 201, 203, 402, 403]
|
|
87
|
+
const RESOURCE_ORE = 0
|
|
88
|
+
const RESOURCE_GAS = 1
|
|
89
|
+
const RESOURCE_REGOLITH = 2
|
|
90
|
+
const RESOURCE_BIOMASS = 3
|
|
91
|
+
const RESOURCE_CRYSTAL = 4
|
|
78
92
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
93
|
+
interface LocationProfileEntry {
|
|
94
|
+
category: number
|
|
95
|
+
maxTier: number
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function categoryBaseId(category: number): number {
|
|
99
|
+
switch (category) {
|
|
100
|
+
case RESOURCE_ORE:
|
|
101
|
+
return 100
|
|
102
|
+
case RESOURCE_CRYSTAL:
|
|
103
|
+
return 200
|
|
104
|
+
case RESOURCE_GAS:
|
|
105
|
+
return 300
|
|
106
|
+
case RESOURCE_REGOLITH:
|
|
107
|
+
return 400
|
|
108
|
+
case RESOURCE_BIOMASS:
|
|
109
|
+
return 500
|
|
110
|
+
default:
|
|
111
|
+
return 0
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function resourceId(category: number, tier: number): number {
|
|
116
|
+
return categoryBaseId(category) + tier
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function getLocationProfile(locationType: number, subtype: number): LocationProfileEntry[] {
|
|
120
|
+
if (locationType === LocationType.ASTEROID) {
|
|
121
|
+
return [
|
|
122
|
+
{category: RESOURCE_ORE, maxTier: 5},
|
|
123
|
+
{category: RESOURCE_CRYSTAL, maxTier: 5},
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
if (locationType === LocationType.NEBULA) {
|
|
127
|
+
return [
|
|
128
|
+
{category: RESOURCE_GAS, maxTier: 5},
|
|
129
|
+
{category: RESOURCE_REGOLITH, maxTier: 5},
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
if (locationType === LocationType.ICE_FIELD) {
|
|
133
|
+
return [
|
|
134
|
+
{category: RESOURCE_GAS, maxTier: 5},
|
|
135
|
+
{category: RESOURCE_BIOMASS, maxTier: 5},
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
if (locationType === LocationType.PLANET) {
|
|
83
139
|
switch (subtype) {
|
|
84
140
|
case PLANET_SUBTYPE_GAS_GIANT:
|
|
85
|
-
return
|
|
141
|
+
return [
|
|
142
|
+
{category: RESOURCE_GAS, maxTier: 10},
|
|
143
|
+
{category: RESOURCE_CRYSTAL, maxTier: 3},
|
|
144
|
+
]
|
|
86
145
|
case PLANET_SUBTYPE_ROCKY:
|
|
87
|
-
return
|
|
146
|
+
return [
|
|
147
|
+
{category: RESOURCE_REGOLITH, maxTier: 10},
|
|
148
|
+
{category: RESOURCE_ORE, maxTier: 3},
|
|
149
|
+
]
|
|
88
150
|
case PLANET_SUBTYPE_TERRESTRIAL:
|
|
89
|
-
return
|
|
151
|
+
return [
|
|
152
|
+
{category: RESOURCE_ORE, maxTier: 10},
|
|
153
|
+
{category: RESOURCE_BIOMASS, maxTier: 3},
|
|
154
|
+
]
|
|
90
155
|
case PLANET_SUBTYPE_ICY:
|
|
91
|
-
return
|
|
156
|
+
return [
|
|
157
|
+
{category: RESOURCE_CRYSTAL, maxTier: 10},
|
|
158
|
+
{category: RESOURCE_REGOLITH, maxTier: 3},
|
|
159
|
+
]
|
|
92
160
|
case PLANET_SUBTYPE_OCEAN:
|
|
93
|
-
return
|
|
161
|
+
return [
|
|
162
|
+
{category: RESOURCE_BIOMASS, maxTier: 10},
|
|
163
|
+
{category: RESOURCE_GAS, maxTier: 3},
|
|
164
|
+
]
|
|
94
165
|
case PLANET_SUBTYPE_INDUSTRIAL:
|
|
95
|
-
return
|
|
166
|
+
return [
|
|
167
|
+
{category: RESOURCE_ORE, maxTier: 3},
|
|
168
|
+
{category: RESOURCE_CRYSTAL, maxTier: 3},
|
|
169
|
+
{category: RESOURCE_REGOLITH, maxTier: 3},
|
|
170
|
+
{category: RESOURCE_BIOMASS, maxTier: 3},
|
|
171
|
+
]
|
|
96
172
|
}
|
|
97
173
|
}
|
|
98
174
|
return []
|
|
99
175
|
}
|
|
100
176
|
|
|
177
|
+
export function getLocationCandidates(locationType: number, subtype: number): number[] {
|
|
178
|
+
const profile = getLocationProfile(locationType, subtype)
|
|
179
|
+
const ids: number[] = []
|
|
180
|
+
for (const {category, maxTier} of profile) {
|
|
181
|
+
for (let tier = 1; tier <= maxTier; tier++) {
|
|
182
|
+
ids.push(resourceId(category, tier))
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return ids
|
|
186
|
+
}
|
|
187
|
+
|
|
101
188
|
export function getEligibleResources(
|
|
102
189
|
locationType: number,
|
|
103
190
|
subtype: number,
|