@shipload/sdk 1.0.0-next.6 → 1.0.0-next.61
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 +52 -0
- package/lib/scan.js +162 -0
- package/lib/scan.js.map +1 -0
- package/lib/scan.m.js +152 -0
- package/lib/scan.m.js.map +1 -0
- package/lib/shipload.d.ts +3601 -1364
- package/lib/shipload.js +15671 -5261
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +15392 -5209
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +1173 -0
- package/lib/testing.js +4971 -0
- package/lib/testing.js.map +1 -0
- package/lib/testing.m.js +4965 -0
- package/lib/testing.m.js.map +1 -0
- package/package.json +20 -2
- package/src/capabilities/craftable.test.ts +82 -0
- package/src/capabilities/craftable.ts +82 -0
- package/src/capabilities/crafting.test.ts +35 -0
- package/src/capabilities/crafting.ts +34 -7
- package/src/capabilities/gathering.test.ts +29 -0
- package/src/capabilities/gathering.ts +37 -19
- package/src/capabilities/hauling.ts +0 -5
- package/src/capabilities/index.ts +0 -1
- package/src/capabilities/modules.ts +27 -30
- package/src/capabilities/movement.ts +1 -1
- package/src/capabilities/storage.ts +16 -1
- package/src/contracts/platform.ts +231 -3
- package/src/contracts/server.ts +1518 -501
- 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 +49 -18
- package/src/data/capability-formulas.ts +26 -14
- package/src/data/catalog.ts +7 -8
- package/src/data/colors.ts +14 -48
- package/src/data/entities.json +380 -15
- package/src/data/item-ids.ts +51 -13
- package/src/data/items.json +318 -77
- package/src/data/kind-registry.json +195 -0
- package/src/data/kind-registry.ts +182 -0
- package/src/data/metadata.ts +291 -51
- package/src/data/recipes-runtime.ts +6 -23
- package/src/data/recipes.json +2260 -173
- package/src/data/tiers.ts +9 -6
- package/src/derivation/build-methods.test.ts +13 -0
- package/src/derivation/build-methods.ts +48 -0
- package/src/derivation/capabilities.test.ts +348 -0
- package/src/derivation/capabilities.ts +693 -0
- package/src/derivation/capability-mappings.ts +60 -16
- package/src/derivation/crafting.test.ts +17 -0
- package/src/derivation/crafting.ts +57 -34
- package/src/derivation/index.ts +27 -2
- package/src/derivation/recipe-usage.test.ts +90 -0
- package/src/derivation/recipe-usage.ts +141 -0
- package/src/derivation/reserve-regen.ts +34 -0
- package/src/derivation/resources.ts +9 -1
- package/src/derivation/rollups.test.ts +55 -0
- package/src/derivation/rollups.ts +72 -0
- package/src/derivation/stars.test.ts +67 -0
- package/src/derivation/stars.ts +25 -0
- package/src/derivation/stat-scaling.ts +12 -0
- package/src/derivation/stats.ts +6 -6
- package/src/derivation/stratum.ts +26 -22
- package/src/derivation/tiers.ts +40 -7
- package/src/derivation/upgrades.ts +14 -0
- package/src/derivation/wormhole.ts +154 -0
- package/src/entities/entity.ts +102 -0
- package/src/entities/gamestate.ts +3 -28
- package/src/entities/makers.ts +141 -135
- 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 +333 -35
- package/src/managers/actions.ts +668 -103
- package/src/managers/base.ts +6 -2
- package/src/managers/cluster.test.ts +39 -0
- package/src/managers/cluster.ts +53 -0
- package/src/managers/construction-types.ts +80 -0
- package/src/managers/construction.ts +443 -0
- package/src/managers/context.ts +29 -1
- package/src/managers/coordinates.ts +14 -0
- package/src/managers/entities.ts +27 -66
- package/src/managers/epochs.ts +40 -0
- package/src/managers/index.ts +20 -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/players.ts +25 -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 +388 -0
- package/src/nft/description.ts +215 -99
- package/src/nft/index.ts +3 -0
- package/src/planner/index.ts +242 -0
- package/src/planner/planner.test.ts +334 -0
- package/src/resolution/describe-module.ts +43 -25
- package/src/resolution/display-name.ts +38 -10
- package/src/resolution/resolve-item.test.ts +48 -0
- package/src/resolution/resolve-item.ts +112 -58
- package/src/scan/index.ts +244 -0
- package/src/scan/scan-wasm.base64.ts +2 -0
- package/src/scheduling/accessor.ts +65 -23
- package/src/scheduling/availability.test.ts +204 -0
- package/src/scheduling/availability.ts +211 -0
- package/src/scheduling/cancel.test.ts +542 -0
- package/src/scheduling/cancel.ts +254 -0
- package/src/scheduling/cluster-stock.test.ts +31 -0
- package/src/scheduling/cluster-stock.ts +15 -0
- package/src/scheduling/energy.ts +47 -0
- package/src/scheduling/idle-resolve.ts +45 -0
- package/src/scheduling/jobs.ts +71 -0
- package/src/scheduling/lane-core.ts +128 -0
- package/src/scheduling/lanes.test.ts +250 -0
- package/src/scheduling/lanes.ts +227 -0
- package/src/scheduling/projection.ts +257 -133
- package/src/scheduling/schedule.test.ts +119 -0
- package/src/scheduling/schedule.ts +257 -117
- package/src/scheduling/task-cargo.test.ts +44 -0
- package/src/scheduling/task-cargo.ts +46 -0
- package/src/scheduling/unwrap.test.ts +86 -0
- package/src/scheduling/unwrap.ts +190 -0
- package/src/shipload.ts +26 -1
- package/src/subscriptions/manager.cluster.test.ts +51 -0
- package/src/subscriptions/manager.ts +245 -172
- package/src/subscriptions/mappers.ts +5 -8
- package/src/subscriptions/types.ts +28 -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 +21 -0
- package/src/travel/route-planner.ts +288 -0
- package/src/travel/route-simulator.ts +170 -0
- package/src/travel/travel.ts +188 -122
- package/src/types/capabilities.ts +37 -8
- package/src/types/entity.ts +3 -3
- package/src/types/index.ts +0 -1
- package/src/types.ts +51 -16
- package/src/utils/cargo.test.ts +14 -0
- package/src/utils/cargo.ts +29 -0
- package/src/utils/display-name.ts +70 -0
- package/src/utils/system.ts +15 -16
- package/src/capabilities/loading.ts +0 -8
- package/src/entities/container.ts +0 -123
- package/src/entities/ship-deploy.ts +0 -295
- package/src/entities/ship.ts +0 -221
- package/src/entities/warehouse.ts +0 -127
- package/src/types/entity-traits.ts +0 -69
package/src/nft/description.ts
CHANGED
|
@@ -1,106 +1,204 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getModuleCapabilityType,
|
|
3
|
+
MODULE_BUILDER,
|
|
3
4
|
MODULE_CRAFTER,
|
|
4
5
|
MODULE_ENGINE,
|
|
5
6
|
MODULE_GATHERER,
|
|
6
7
|
MODULE_GENERATOR,
|
|
8
|
+
MODULE_BATTERY,
|
|
7
9
|
MODULE_HAULER,
|
|
8
10
|
MODULE_LOADER,
|
|
9
11
|
MODULE_STORAGE,
|
|
10
12
|
MODULE_WARP,
|
|
11
13
|
} from '../capabilities/modules'
|
|
12
|
-
import {
|
|
13
|
-
ITEM_CONTAINER_T1_PACKED,
|
|
14
|
-
ITEM_CONTAINER_T2_PACKED,
|
|
15
|
-
ITEM_CRAFTER_T1,
|
|
16
|
-
ITEM_ENGINE_T1,
|
|
17
|
-
ITEM_GATHERER_T1,
|
|
18
|
-
ITEM_GENERATOR_T1,
|
|
19
|
-
ITEM_HAULER_T1,
|
|
20
|
-
ITEM_LOADER_T1,
|
|
21
|
-
ITEM_SHIP_T1_PACKED,
|
|
22
|
-
ITEM_STORAGE_T1,
|
|
23
|
-
ITEM_WAREHOUSE_T1_PACKED,
|
|
24
|
-
ITEM_WARP_T1,
|
|
25
|
-
} from '../data/item-ids'
|
|
14
|
+
import {getKindMeta, getTemplateMeta} from '../data/kind-registry'
|
|
26
15
|
import {decodeStat} from '../derivation/crafting'
|
|
27
|
-
import {
|
|
28
|
-
|
|
16
|
+
import {
|
|
17
|
+
gathererDepthForTier,
|
|
18
|
+
computeGathererYield,
|
|
19
|
+
moduleTierPct,
|
|
20
|
+
ENGINE_THRUST_TIER_PCT,
|
|
21
|
+
GENERATOR_CAPACITY_TIER_PCT,
|
|
22
|
+
GENERATOR_RECHARGE_TIER_PCT,
|
|
23
|
+
CRAFTER_SPEED_TIER_PCT,
|
|
24
|
+
BUILDER_SPEED_TIER_PCT,
|
|
25
|
+
WARP_RANGE_TIER_PCT,
|
|
26
|
+
LOADER_THRUST_TIER_PCT,
|
|
27
|
+
CARGO_BAY_CAPACITY_TIER_PCT,
|
|
28
|
+
BATTERY_CAPACITY_TIER_PCT,
|
|
29
|
+
} from '../derivation/capabilities'
|
|
30
|
+
import {getItem, tryGetItem} from '../data/catalog'
|
|
31
|
+
import type {ModuleType} from '../types'
|
|
32
|
+
import {ENTITY_SHIP, getPackedEntityType} from '../data/kind-registry'
|
|
33
|
+
import {getBaseHullmassFor} from '../derivation/capabilities'
|
|
34
|
+
import {computeEffectiveModuleStat} from '../derivation/stat-scaling'
|
|
29
35
|
|
|
30
36
|
function idiv(a: number, b: number): number {
|
|
31
37
|
return Math.floor(a / b)
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
export function
|
|
40
|
+
export function toWholeEnergy(milli: number): number {
|
|
41
|
+
return idiv(milli + 500, 1000)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function formatMassTonnes(kg: number): string {
|
|
45
|
+
const tenths = idiv(kg + 50, 100)
|
|
46
|
+
return `${idiv(tenths, 10)}.${tenths % 10} t`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isShipHull(itemId: number): boolean {
|
|
50
|
+
return getPackedEntityType(itemId)?.equals(ENTITY_SHIP) ?? false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function sumPackedShipChannels(stats: bigint): number {
|
|
54
|
+
let sum = 0
|
|
55
|
+
for (let slot = 0; slot < 5; slot++) sum += decodeStat(stats, slot)
|
|
56
|
+
return sum
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function computeBaseHullmass(itemId: number, stats: bigint): number {
|
|
60
|
+
if (isShipHull(itemId)) {
|
|
61
|
+
return Math.floor(
|
|
62
|
+
(getBaseHullmassFor(itemId) * (10_000 - sumPackedShipChannels(stats))) / 10_000
|
|
63
|
+
)
|
|
64
|
+
}
|
|
35
65
|
const density = decodeStat(stats, 1)
|
|
36
|
-
return
|
|
66
|
+
return Math.floor((getBaseHullmassFor(itemId) * (2000 - density)) / 2000)
|
|
37
67
|
}
|
|
38
68
|
|
|
39
69
|
export function computeBaseCapacityShip(stats: bigint): number {
|
|
40
|
-
|
|
41
|
-
|
|
70
|
+
return Math.floor(5_000_000 * 6 ** (sumPackedShipChannels(stats) / 4995))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function computeBaseCapacityContainer(stats: bigint): number {
|
|
74
|
+
const s = decodeStat(stats, 0) + decodeStat(stats, 2)
|
|
75
|
+
return Math.floor(22_000_000 * 6 ** (s / 1998))
|
|
42
76
|
}
|
|
43
77
|
|
|
44
78
|
export function computeBaseCapacityWarehouse(stats: bigint): number {
|
|
45
|
-
const s = decodeStat(stats, 0) + decodeStat(stats, 2)
|
|
46
|
-
return Math.floor(
|
|
79
|
+
const s = decodeStat(stats, 0) + decodeStat(stats, 2)
|
|
80
|
+
return Math.floor(100_000_000 * 6 ** (s / 1998))
|
|
47
81
|
}
|
|
48
82
|
|
|
49
|
-
export
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
83
|
+
export function computeBaseCapacityWorkshop(stats: bigint): number {
|
|
84
|
+
const s = decodeStat(stats, 0) + decodeStat(stats, 2)
|
|
85
|
+
return Math.floor(5_000_000 * 6 ** (s / 1998))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const CAPACITY_FN_BY_KIND: Record<string, (stats: bigint) => number> = {
|
|
89
|
+
ship: computeBaseCapacityShip,
|
|
90
|
+
warehouse: computeBaseCapacityWarehouse,
|
|
91
|
+
workshop: computeBaseCapacityWorkshop,
|
|
92
|
+
extractor: computeBaseCapacityContainer,
|
|
93
|
+
factory: computeBaseCapacityContainer,
|
|
94
|
+
builddock: computeBaseCapacityContainer,
|
|
95
|
+
mdriver: computeBaseCapacityContainer,
|
|
96
|
+
mcatcher: computeBaseCapacityContainer,
|
|
97
|
+
container: computeBaseCapacityContainer,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function computeBaseCapacityForEntity(itemId: number, stats: bigint): number {
|
|
101
|
+
const kind = getTemplateMeta(itemId)?.kind
|
|
102
|
+
if (!kind) return 0
|
|
103
|
+
return CAPACITY_FN_BY_KIND[kind.toString()]?.(stats) ?? 0
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const computeEngineThrust = (vol: number, tier: number): number =>
|
|
107
|
+
idiv((400 + idiv(vol * 3, 4)) * moduleTierPct(ENGINE_THRUST_TIER_PCT, tier), 100)
|
|
108
|
+
export const computeEngineDrain = (thm: number): number => 2 * Math.max(30, 50 - idiv(thm, 70))
|
|
109
|
+
export const ENGINE_DRAIN_BASE = 156
|
|
110
|
+
export const ENGINE_DRAIN_REF_THRUST = 775
|
|
111
|
+
export const ENGINE_DRAIN_REF_THM = 500
|
|
112
|
+
|
|
113
|
+
export const computeTravelDrain = (totalThrust: number, avgThm: number): number => {
|
|
114
|
+
if (totalThrust <= 0) return 0
|
|
115
|
+
const thermalFactor = computeEngineDrain(avgThm) / computeEngineDrain(ENGINE_DRAIN_REF_THM)
|
|
116
|
+
const thrustFactor = Math.sqrt(ENGINE_DRAIN_REF_THRUST / totalThrust)
|
|
117
|
+
return Math.floor(ENGINE_DRAIN_BASE * 1000 * thermalFactor * thrustFactor)
|
|
118
|
+
}
|
|
119
|
+
export const computeGeneratorCap = (com: number, tier: number): number =>
|
|
120
|
+
idiv((1_300_000 + com * 500) * moduleTierPct(GENERATOR_CAPACITY_TIER_PCT, tier), 100)
|
|
121
|
+
export const computeGeneratorRech = (fin: number, tier: number): number =>
|
|
122
|
+
idiv((2000 + fin * 6) * moduleTierPct(GENERATOR_RECHARGE_TIER_PCT, tier), 100)
|
|
54
123
|
export const computeGathererDrain = (con: number): number =>
|
|
55
|
-
Math.max(
|
|
124
|
+
2 * Math.max(250_000, 1_250_000 - con * 1250)
|
|
56
125
|
export const computeGathererDepth = (tol: number, tier: number): number =>
|
|
57
126
|
gathererDepthForTier(tol, tier)
|
|
58
|
-
export const computeGathererSpeed = (ref: number): number => 100 + idiv(ref * 4, 5)
|
|
59
127
|
export const computeLoaderMass = (ins: number): number => Math.max(200, 2000 - ins * 2)
|
|
60
|
-
export const computeLoaderThrust = (pla: number): number =>
|
|
61
|
-
|
|
62
|
-
export const
|
|
63
|
-
|
|
128
|
+
export const computeLoaderThrust = (pla: number, tier: number): number =>
|
|
129
|
+
idiv((1 + idiv(pla * pla, 10000)) * moduleTierPct(LOADER_THRUST_TIER_PCT, tier), 100)
|
|
130
|
+
export const computeCrafterSpeed = (rea: number, tier: number): number =>
|
|
131
|
+
idiv((100 + idiv(rea * 4, 5)) * moduleTierPct(CRAFTER_SPEED_TIER_PCT, tier), 100)
|
|
132
|
+
export const computeCrafterDrain = (fin: number): number =>
|
|
133
|
+
Math.max(5000, 30000 - idiv(fin * 1000, 33))
|
|
134
|
+
export const computeBuilderSpeed = (resonance: number, tier: number): number =>
|
|
135
|
+
idiv((100 + idiv(resonance * 4, 5)) * moduleTierPct(BUILDER_SPEED_TIER_PCT, tier), 100)
|
|
136
|
+
export const computeBuilderDrain = (fineness: number): number =>
|
|
137
|
+
Math.max(5000, 30000 - idiv(fineness * 1000, 33))
|
|
138
|
+
export const computeHaulerCapacity = (fin: number, tier: number): number =>
|
|
139
|
+
Math.max(tier, tier + idiv(fin, 400))
|
|
64
140
|
export const computeHaulerEfficiency = (con: number): number => 2000 + con * 6
|
|
65
|
-
export const
|
|
66
|
-
|
|
141
|
+
export const supportDrainTierPercent = (tier: number): number => {
|
|
142
|
+
const clampedTier = Math.min(10, Math.max(1, Math.trunc(tier)))
|
|
143
|
+
return Math.max(50, 110 - clampedTier * 10)
|
|
144
|
+
}
|
|
145
|
+
const computeT1LogisticsDrain = (stat: number): number =>
|
|
146
|
+
Math.max(3000, 15000 - Math.min(12000, idiv(stat * 1000, 80)))
|
|
147
|
+
export const computeHaulerDrain = (conductivity: number, tier: number): number =>
|
|
148
|
+
idiv(computeT1LogisticsDrain(conductivity) * supportDrainTierPercent(tier), 100)
|
|
149
|
+
export const computeCargoBayDrain = (cohesion: number, tier: number): number =>
|
|
150
|
+
idiv(computeT1LogisticsDrain(cohesion) * 3 * supportDrainTierPercent(tier), 400)
|
|
151
|
+
export const computeWarpRange = (stat: number, tier: number): number =>
|
|
152
|
+
idiv((100 + stat * 3) * moduleTierPct(WARP_RANGE_TIER_PCT, tier), 100)
|
|
153
|
+
export const computeCargoBayCapacity = (
|
|
154
|
+
strength: number,
|
|
155
|
+
density: number,
|
|
156
|
+
hardness: number,
|
|
157
|
+
tier: number
|
|
158
|
+
): number =>
|
|
159
|
+
idiv(
|
|
160
|
+
(10_000_000 + idiv((strength + density + hardness) * 50_000_000, 2997)) *
|
|
161
|
+
moduleTierPct(CARGO_BAY_CAPACITY_TIER_PCT, tier),
|
|
162
|
+
100
|
|
163
|
+
)
|
|
164
|
+
export const computeBatteryBankCapacity = (
|
|
165
|
+
volatility: number,
|
|
166
|
+
thermal: number,
|
|
167
|
+
plasticity: number,
|
|
168
|
+
insulation: number,
|
|
169
|
+
tier: number
|
|
170
|
+
): number =>
|
|
171
|
+
idiv(
|
|
172
|
+
(2_500_000 + idiv((volatility + thermal + plasticity + insulation) * 7_500_000, 3996)) *
|
|
173
|
+
moduleTierPct(BATTERY_CAPACITY_TIER_PCT, tier),
|
|
174
|
+
100
|
|
175
|
+
)
|
|
67
176
|
|
|
68
177
|
export function entityDisplayName(itemId: number): string {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
178
|
+
const template = getTemplateMeta(itemId)
|
|
179
|
+
if (!template) return 'Entity'
|
|
180
|
+
if (template.displayLabel) return template.displayLabel
|
|
181
|
+
return getKindMeta(template.kind)?.defaultLabel || 'Entity'
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const MODULE_DISPLAY_NAME_BY_TYPE: Partial<Record<ModuleType, string>> = {
|
|
185
|
+
engine: 'Engine',
|
|
186
|
+
generator: 'Power Core',
|
|
187
|
+
gatherer: 'Limpet Bay',
|
|
188
|
+
loader: 'Shuttle Bay',
|
|
189
|
+
crafter: 'Fabricator',
|
|
190
|
+
storage: 'Cargo Hold',
|
|
191
|
+
hauler: 'Tractor Beam',
|
|
192
|
+
warp: 'Warp Drive',
|
|
193
|
+
battery: 'Battery Bank',
|
|
194
|
+
launcher: 'Drive Coil',
|
|
195
|
+
builder: 'Assembly Arm',
|
|
81
196
|
}
|
|
82
197
|
|
|
83
198
|
export function moduleDisplayName(itemId: number): string {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
case ITEM_GENERATOR_T1:
|
|
88
|
-
return 'Generator'
|
|
89
|
-
case ITEM_GATHERER_T1:
|
|
90
|
-
return 'Gatherer'
|
|
91
|
-
case ITEM_LOADER_T1:
|
|
92
|
-
return 'Loader'
|
|
93
|
-
case ITEM_CRAFTER_T1:
|
|
94
|
-
return 'Crafter'
|
|
95
|
-
case ITEM_STORAGE_T1:
|
|
96
|
-
return 'Storage'
|
|
97
|
-
case ITEM_HAULER_T1:
|
|
98
|
-
return 'Hauler'
|
|
99
|
-
case ITEM_WARP_T1:
|
|
100
|
-
return 'Warp'
|
|
101
|
-
default:
|
|
102
|
-
return 'Module'
|
|
103
|
-
}
|
|
199
|
+
const item = tryGetItem(itemId)
|
|
200
|
+
if (item?.type !== 'module' || !item.moduleType) return 'Module'
|
|
201
|
+
return MODULE_DISPLAY_NAME_BY_TYPE[item.moduleType] ?? 'Module'
|
|
104
202
|
}
|
|
105
203
|
|
|
106
204
|
export function formatModuleLine(slot: number, itemId: number, stats: bigint): string {
|
|
@@ -115,60 +213,83 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
|
|
|
115
213
|
|
|
116
214
|
switch (subtype) {
|
|
117
215
|
case MODULE_ENGINE: {
|
|
118
|
-
const vol = decodeStat(stats, 0)
|
|
119
|
-
const thm = decodeStat(stats, 1)
|
|
120
|
-
|
|
216
|
+
const vol = computeEffectiveModuleStat(decodeStat(stats, 0))
|
|
217
|
+
const thm = computeEffectiveModuleStat(decodeStat(stats, 1))
|
|
218
|
+
const tier = getItem(itemId).tier
|
|
219
|
+
out += ` Thrust ${computeEngineThrust(vol, tier)} Drain ${computeEngineDrain(thm)}`
|
|
121
220
|
break
|
|
122
221
|
}
|
|
123
222
|
case MODULE_GENERATOR: {
|
|
124
|
-
const res = decodeStat(stats, 0)
|
|
125
|
-
const ref = decodeStat(stats, 1)
|
|
126
|
-
|
|
223
|
+
const res = computeEffectiveModuleStat(decodeStat(stats, 0))
|
|
224
|
+
const ref = computeEffectiveModuleStat(decodeStat(stats, 1))
|
|
225
|
+
const tier = getItem(itemId).tier
|
|
226
|
+
out += ` Capacity ${toWholeEnergy(computeGeneratorCap(res, tier))} Recharge ${toWholeEnergy(
|
|
227
|
+
computeGeneratorRech(ref, tier)
|
|
228
|
+
)}`
|
|
127
229
|
break
|
|
128
230
|
}
|
|
129
231
|
case MODULE_GATHERER: {
|
|
130
232
|
const str = decodeStat(stats, 0)
|
|
131
233
|
const tol = decodeStat(stats, 1)
|
|
132
|
-
const con = decodeStat(stats,
|
|
133
|
-
const ref = decodeStat(stats, 4)
|
|
234
|
+
const con = decodeStat(stats, 2)
|
|
134
235
|
const tier = getItem(itemId).tier
|
|
135
|
-
out += ` Yield ${computeGathererYield(str)} Depth ${computeGathererDepth(
|
|
236
|
+
out += ` Yield ${computeGathererYield(str, tier)} Depth ${computeGathererDepth(
|
|
136
237
|
tol,
|
|
137
238
|
tier
|
|
138
|
-
)}
|
|
239
|
+
)} Drain ${toWholeEnergy(computeGathererDrain(con))}`
|
|
139
240
|
break
|
|
140
241
|
}
|
|
141
242
|
case MODULE_LOADER: {
|
|
142
243
|
const fin = decodeStat(stats, 0)
|
|
143
244
|
const pla = decodeStat(stats, 1)
|
|
144
|
-
|
|
245
|
+
const tier = getItem(itemId).tier
|
|
246
|
+
out += ` Mass ${formatMassTonnes(computeLoaderMass(fin))} Thrust ${computeLoaderThrust(pla, tier)}`
|
|
145
247
|
break
|
|
146
248
|
}
|
|
147
249
|
case MODULE_CRAFTER: {
|
|
148
250
|
const rea = decodeStat(stats, 0)
|
|
149
|
-
const
|
|
150
|
-
|
|
251
|
+
const con = decodeStat(stats, 1)
|
|
252
|
+
const tier = getItem(itemId).tier
|
|
253
|
+
out += ` Speed ${computeCrafterSpeed(rea, tier)} Drain ${toWholeEnergy(computeCrafterDrain(con))}`
|
|
254
|
+
break
|
|
255
|
+
}
|
|
256
|
+
case MODULE_BUILDER: {
|
|
257
|
+
const res = decodeStat(stats, 0)
|
|
258
|
+
const fin = decodeStat(stats, 1)
|
|
259
|
+
const tier = getItem(itemId).tier
|
|
260
|
+
out += ` Speed ${computeBuilderSpeed(res, tier)} Drain ${toWholeEnergy(computeBuilderDrain(fin))}`
|
|
151
261
|
break
|
|
152
262
|
}
|
|
153
263
|
case MODULE_STORAGE: {
|
|
154
264
|
const str = decodeStat(stats, 0)
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
out += `
|
|
265
|
+
const den = decodeStat(stats, 1)
|
|
266
|
+
const hrd = decodeStat(stats, 2)
|
|
267
|
+
const coh = decodeStat(stats, 3)
|
|
268
|
+
const tier = getItem(itemId).tier
|
|
269
|
+
out += ` Cargo Capacity ${formatMassTonnes(computeCargoBayCapacity(str, den, hrd, tier))} Drain ${toWholeEnergy(computeCargoBayDrain(coh, tier))}`
|
|
160
270
|
break
|
|
161
271
|
}
|
|
162
272
|
case MODULE_HAULER: {
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
|
|
273
|
+
const res = decodeStat(stats, 0)
|
|
274
|
+
const pla = decodeStat(stats, 1)
|
|
275
|
+
const con = decodeStat(stats, 2)
|
|
276
|
+
const tier = getItem(itemId).tier
|
|
277
|
+
out += ` Capacity ${computeHaulerCapacity(res, tier)} Efficiency ${computeHaulerEfficiency(pla)} Drain ${toWholeEnergy(computeHaulerDrain(con, tier))}`
|
|
167
278
|
break
|
|
168
279
|
}
|
|
169
280
|
case MODULE_WARP: {
|
|
170
281
|
const stat = decodeStat(stats, 0)
|
|
171
|
-
|
|
282
|
+
const tier = getItem(itemId).tier
|
|
283
|
+
out += ` Range ${computeWarpRange(stat, tier)}`
|
|
284
|
+
break
|
|
285
|
+
}
|
|
286
|
+
case MODULE_BATTERY: {
|
|
287
|
+
const vol = decodeStat(stats, 0)
|
|
288
|
+
const thm = decodeStat(stats, 1)
|
|
289
|
+
const pla = decodeStat(stats, 2)
|
|
290
|
+
const ins = decodeStat(stats, 3)
|
|
291
|
+
const tier = getItem(itemId).tier
|
|
292
|
+
out += ` Energy Capacity ${toWholeEnergy(computeBatteryBankCapacity(vol, thm, pla, ins, tier))}`
|
|
172
293
|
break
|
|
173
294
|
}
|
|
174
295
|
}
|
|
@@ -181,18 +302,13 @@ export function buildEntityDescription(
|
|
|
181
302
|
moduleItems: number[],
|
|
182
303
|
moduleStats: bigint[]
|
|
183
304
|
): string {
|
|
184
|
-
const hullMass = computeBaseHullmass(hullStats)
|
|
185
|
-
|
|
186
|
-
if (itemId === ITEM_SHIP_T1_PACKED) {
|
|
187
|
-
baseCapacity = computeBaseCapacityShip(hullStats)
|
|
188
|
-
} else if (itemId === ITEM_WAREHOUSE_T1_PACKED) {
|
|
189
|
-
baseCapacity = computeBaseCapacityWarehouse(hullStats)
|
|
190
|
-
}
|
|
305
|
+
const hullMass = computeBaseHullmass(itemId, hullStats)
|
|
306
|
+
const baseCapacity = computeBaseCapacityForEntity(itemId, hullStats)
|
|
191
307
|
|
|
192
308
|
let out = entityDisplayName(itemId)
|
|
193
|
-
out += ` - Hull ${hullMass}
|
|
309
|
+
out += ` - Hull ${formatMassTonnes(hullMass)}`
|
|
194
310
|
if (baseCapacity > 0) {
|
|
195
|
-
out += ` * ${baseCapacity} capacity`
|
|
311
|
+
out += ` * ${formatMassTonnes(baseCapacity)} capacity`
|
|
196
312
|
}
|
|
197
313
|
out += '\n\n'
|
|
198
314
|
|
package/src/nft/index.ts
CHANGED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import type {GathererStats} from '../types/capabilities'
|
|
2
|
+
import type {ServerContract} from '../contracts'
|
|
3
|
+
import {calc_gather_duration, calc_gather_energy} from '../capabilities/gathering'
|
|
4
|
+
import {calc_rechargetime} from '../travel/travel'
|
|
5
|
+
import {projectRemainingAt, type Projectable} from '../scheduling/projection'
|
|
6
|
+
|
|
7
|
+
export interface LanePlanEntry {
|
|
8
|
+
slot: number
|
|
9
|
+
quantity: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type PlanTarget = {quantity: number} | 'max'
|
|
13
|
+
|
|
14
|
+
export interface GatherPlanEntity extends Projectable {
|
|
15
|
+
gatherer_lanes: ServerContract.Types.gatherer_lane[]
|
|
16
|
+
loader_lanes: ServerContract.Types.loader_lane[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface GatherLimpet {
|
|
20
|
+
slot: number
|
|
21
|
+
quantity: number
|
|
22
|
+
durationSeconds: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface GatherCycle {
|
|
26
|
+
rechargeBefore: boolean
|
|
27
|
+
rechargeSeconds: number
|
|
28
|
+
limpets: GatherLimpet[]
|
|
29
|
+
gatherSeconds: number
|
|
30
|
+
batchOre: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type FillCap = 'reserve' | 'hold' | 'requested'
|
|
34
|
+
|
|
35
|
+
export interface GatherPlan {
|
|
36
|
+
cycles: GatherCycle[]
|
|
37
|
+
cycleCount: number
|
|
38
|
+
totalOre: number
|
|
39
|
+
totalSeconds: number
|
|
40
|
+
cap: FillCap
|
|
41
|
+
reachingCount: number
|
|
42
|
+
totalLimpets: number
|
|
43
|
+
warnings: string[]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface BuildGatherPlanOpts {
|
|
47
|
+
richness: number
|
|
48
|
+
itemMass: number
|
|
49
|
+
holdRoom: number
|
|
50
|
+
reserveRemaining: number
|
|
51
|
+
now: Date
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const MAX_CYCLES = 10_000
|
|
55
|
+
|
|
56
|
+
const MAX_TRANSFER_QTY = 10000
|
|
57
|
+
|
|
58
|
+
export function allocateProportional(
|
|
59
|
+
lanes: {slot: number; weight: number}[],
|
|
60
|
+
total: number
|
|
61
|
+
): LanePlanEntry[] {
|
|
62
|
+
if (lanes.length === 0) return []
|
|
63
|
+
const weightSum = lanes.reduce((s, l) => s + l.weight, 0)
|
|
64
|
+
if (weightSum === 0) return []
|
|
65
|
+
|
|
66
|
+
const entries: LanePlanEntry[] = lanes.map((l) => ({
|
|
67
|
+
slot: l.slot,
|
|
68
|
+
quantity: Math.floor((total * l.weight) / weightSum),
|
|
69
|
+
}))
|
|
70
|
+
|
|
71
|
+
let remainder = total - entries.reduce((s, e) => s + e.quantity, 0)
|
|
72
|
+
for (let i = 0; remainder > 0; i = (i + 1) % entries.length) {
|
|
73
|
+
entries[i].quantity++
|
|
74
|
+
remainder--
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return entries
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function laneStats(lane: ServerContract.Types.gatherer_lane): GathererStats {
|
|
81
|
+
return lane as unknown as GathererStats
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function gatherEnergyCost(
|
|
85
|
+
lane: ServerContract.Types.gatherer_lane,
|
|
86
|
+
quantity: number,
|
|
87
|
+
stratum: number,
|
|
88
|
+
itemMass: number,
|
|
89
|
+
richness: number
|
|
90
|
+
): number {
|
|
91
|
+
if (quantity <= 0) return 0
|
|
92
|
+
const dur = Number(calc_gather_duration(laneStats(lane), itemMass, quantity, stratum, richness))
|
|
93
|
+
return Number(calc_gather_energy(laneStats(lane), dur))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function splitCost(
|
|
97
|
+
reaching: ServerContract.Types.gatherer_lane[],
|
|
98
|
+
quantity: number,
|
|
99
|
+
stratum: number,
|
|
100
|
+
itemMass: number,
|
|
101
|
+
richness: number
|
|
102
|
+
): number {
|
|
103
|
+
if (quantity <= 0) return 0
|
|
104
|
+
const weights = reaching.map((l) => ({
|
|
105
|
+
slot: l.slot_index.toNumber(),
|
|
106
|
+
weight: l.yield.toNumber(),
|
|
107
|
+
}))
|
|
108
|
+
const split = allocateProportional(weights, quantity)
|
|
109
|
+
return split.reduce((sum, e) => {
|
|
110
|
+
const lane = reaching.find((l) => l.slot_index.toNumber() === e.slot)!
|
|
111
|
+
return sum + gatherEnergyCost(lane, e.quantity, stratum, itemMass, richness)
|
|
112
|
+
}, 0)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Binary search is exact because splitCost is monotonic non-decreasing in Q.
|
|
116
|
+
export function maxQtyForCharge(
|
|
117
|
+
reaching: ServerContract.Types.gatherer_lane[],
|
|
118
|
+
hi: number,
|
|
119
|
+
capacity: number,
|
|
120
|
+
stratum: number,
|
|
121
|
+
itemMass: number,
|
|
122
|
+
richness: number
|
|
123
|
+
): number {
|
|
124
|
+
if (hi <= 0) return 0
|
|
125
|
+
if (splitCost(reaching, hi, stratum, itemMass, richness) <= capacity) return hi
|
|
126
|
+
let lo = 0
|
|
127
|
+
let high = hi
|
|
128
|
+
while (lo < high) {
|
|
129
|
+
const mid = Math.ceil((lo + high) / 2)
|
|
130
|
+
if (splitCost(reaching, mid, stratum, itemMass, richness) <= capacity) lo = mid
|
|
131
|
+
else high = mid - 1
|
|
132
|
+
}
|
|
133
|
+
return lo
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function buildGatherPlan(
|
|
137
|
+
entity: GatherPlanEntity,
|
|
138
|
+
stratum: number,
|
|
139
|
+
target: PlanTarget,
|
|
140
|
+
opts: BuildGatherPlanOpts
|
|
141
|
+
): GatherPlan {
|
|
142
|
+
const {richness, itemMass, holdRoom, reserveRemaining, now} = opts
|
|
143
|
+
const warnings: string[] = []
|
|
144
|
+
|
|
145
|
+
const reaching = entity.gatherer_lanes.filter((l) => l.depth.toNumber() >= stratum)
|
|
146
|
+
if (reaching.length === 0) throw new Error('no gatherer reaches this stratum')
|
|
147
|
+
if (!entity.generator) throw new Error('entity has no generator')
|
|
148
|
+
|
|
149
|
+
const blocked = entity.gatherer_lanes.length - reaching.length
|
|
150
|
+
if (blocked > 0) {
|
|
151
|
+
warnings.push(
|
|
152
|
+
`${blocked} of ${entity.gatherer_lanes.length} limpets can't reach this depth`
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const capacity = entity.generator.capacity.toNumber()
|
|
157
|
+
const rechargeRate = entity.generator.recharge.toNumber()
|
|
158
|
+
let energy = Number(projectRemainingAt(entity, now).energy)
|
|
159
|
+
|
|
160
|
+
const requested = target === 'max' ? Infinity : target.quantity
|
|
161
|
+
let fillTarget = requested
|
|
162
|
+
let cap: FillCap = 'requested'
|
|
163
|
+
if (holdRoom < fillTarget) {
|
|
164
|
+
fillTarget = holdRoom
|
|
165
|
+
cap = 'hold'
|
|
166
|
+
}
|
|
167
|
+
if (reserveRemaining < fillTarget) {
|
|
168
|
+
fillTarget = reserveRemaining
|
|
169
|
+
cap = 'reserve'
|
|
170
|
+
}
|
|
171
|
+
fillTarget = Math.max(0, Math.floor(fillTarget))
|
|
172
|
+
|
|
173
|
+
const cycles: GatherCycle[] = []
|
|
174
|
+
let remaining = fillTarget
|
|
175
|
+
let guard = 0
|
|
176
|
+
while (remaining > 0 && guard++ < MAX_CYCLES) {
|
|
177
|
+
const batch = maxQtyForCharge(reaching, remaining, capacity, stratum, itemMass, richness)
|
|
178
|
+
if (batch <= 0) {
|
|
179
|
+
warnings.push('a single gather cannot fit within one full charge')
|
|
180
|
+
break
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const costFull = splitCost(reaching, batch, stratum, itemMass, richness)
|
|
184
|
+
const rechargeBefore = energy < costFull
|
|
185
|
+
const rechargeSeconds = rechargeBefore
|
|
186
|
+
? Number(calc_rechargetime(capacity, energy, rechargeRate))
|
|
187
|
+
: 0
|
|
188
|
+
if (rechargeBefore) energy = capacity
|
|
189
|
+
|
|
190
|
+
const weights = reaching.map((l) => ({
|
|
191
|
+
slot: l.slot_index.toNumber(),
|
|
192
|
+
weight: l.yield.toNumber(),
|
|
193
|
+
}))
|
|
194
|
+
const split = allocateProportional(weights, batch).filter((e) => e.quantity > 0)
|
|
195
|
+
const limpets: GatherLimpet[] = split.map((e) => {
|
|
196
|
+
const lane = reaching.find((l) => l.slot_index.toNumber() === e.slot)!
|
|
197
|
+
const dur = Number(
|
|
198
|
+
calc_gather_duration(laneStats(lane), itemMass, e.quantity, stratum, richness)
|
|
199
|
+
)
|
|
200
|
+
return {slot: e.slot, quantity: e.quantity, durationSeconds: dur}
|
|
201
|
+
})
|
|
202
|
+
const actualCost = limpets.reduce((sum, l) => {
|
|
203
|
+
const lane = reaching.find((r) => r.slot_index.toNumber() === l.slot)!
|
|
204
|
+
return sum + Number(calc_gather_energy(laneStats(lane), l.durationSeconds))
|
|
205
|
+
}, 0)
|
|
206
|
+
energy = Math.max(0, energy - actualCost)
|
|
207
|
+
|
|
208
|
+
const gatherSeconds = limpets.reduce((m, l) => Math.max(m, l.durationSeconds), 0)
|
|
209
|
+
cycles.push({rechargeBefore, rechargeSeconds, limpets, gatherSeconds, batchOre: batch})
|
|
210
|
+
remaining -= batch
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const totalOre = cycles.reduce((s, c) => s + c.batchOre, 0)
|
|
214
|
+
const totalSeconds = cycles.reduce((s, c) => s + c.rechargeSeconds + c.gatherSeconds, 0)
|
|
215
|
+
return {
|
|
216
|
+
cycles,
|
|
217
|
+
cycleCount: cycles.length,
|
|
218
|
+
totalOre,
|
|
219
|
+
totalSeconds,
|
|
220
|
+
cap,
|
|
221
|
+
reachingCount: reaching.length,
|
|
222
|
+
totalLimpets: entity.gatherer_lanes.length,
|
|
223
|
+
warnings,
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function planParallelTransfer(
|
|
228
|
+
entity: GatherPlanEntity,
|
|
229
|
+
target: PlanTarget
|
|
230
|
+
): LanePlanEntry[] {
|
|
231
|
+
const lanes = entity.loader_lanes.filter((l) => l.thrust.toNumber() > 0)
|
|
232
|
+
if (lanes.length === 0) return []
|
|
233
|
+
|
|
234
|
+
const requestedQty = target === 'max' ? MAX_TRANSFER_QTY : target.quantity
|
|
235
|
+
|
|
236
|
+
const laneWeights = lanes.map((l) => ({
|
|
237
|
+
slot: l.slot_index.toNumber(),
|
|
238
|
+
weight: l.thrust.toNumber(),
|
|
239
|
+
}))
|
|
240
|
+
|
|
241
|
+
return allocateProportional(laneWeights, requestedQty).filter((e) => e.quantity > 0)
|
|
242
|
+
}
|