@shipload/sdk 1.0.0-next.6 → 1.0.0-next.60

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.
Files changed (155) hide show
  1. package/lib/scan.d.ts +52 -0
  2. package/lib/scan.js +162 -0
  3. package/lib/scan.js.map +1 -0
  4. package/lib/scan.m.js +152 -0
  5. package/lib/scan.m.js.map +1 -0
  6. package/lib/shipload.d.ts +3589 -1363
  7. package/lib/shipload.js +15700 -5454
  8. package/lib/shipload.js.map +1 -1
  9. package/lib/shipload.m.js +15423 -5402
  10. package/lib/shipload.m.js.map +1 -1
  11. package/lib/testing.d.ts +1173 -0
  12. package/lib/testing.js +4947 -0
  13. package/lib/testing.js.map +1 -0
  14. package/lib/testing.m.js +4941 -0
  15. package/lib/testing.m.js.map +1 -0
  16. package/package.json +20 -2
  17. package/src/capabilities/craftable.test.ts +82 -0
  18. package/src/capabilities/craftable.ts +82 -0
  19. package/src/capabilities/crafting.test.ts +35 -0
  20. package/src/capabilities/crafting.ts +34 -7
  21. package/src/capabilities/gathering.test.ts +29 -0
  22. package/src/capabilities/gathering.ts +37 -19
  23. package/src/capabilities/hauling.ts +0 -5
  24. package/src/capabilities/index.ts +0 -1
  25. package/src/capabilities/modules.ts +42 -0
  26. package/src/capabilities/movement.ts +1 -1
  27. package/src/capabilities/storage.ts +16 -1
  28. package/src/contracts/platform.ts +231 -3
  29. package/src/contracts/server.ts +1518 -501
  30. package/src/coordinates/address.ts +88 -0
  31. package/src/coordinates/constants.test.ts +15 -0
  32. package/src/coordinates/constants.ts +23 -0
  33. package/src/coordinates/index.ts +15 -0
  34. package/src/coordinates/memo.test.ts +47 -0
  35. package/src/coordinates/memo.ts +20 -0
  36. package/src/coordinates/permutation.ts +77 -0
  37. package/src/coordinates/regions.ts +48 -0
  38. package/src/coordinates/sectors.ts +115 -0
  39. package/src/data/capabilities.ts +49 -18
  40. package/src/data/capability-formulas.ts +26 -14
  41. package/src/data/catalog.ts +0 -5
  42. package/src/data/colors.ts +13 -47
  43. package/src/data/entities.json +379 -14
  44. package/src/data/item-ids.ts +48 -12
  45. package/src/data/items.json +301 -74
  46. package/src/data/kind-registry.json +195 -0
  47. package/src/data/kind-registry.ts +182 -0
  48. package/src/data/metadata.ts +277 -48
  49. package/src/data/recipes-runtime.ts +6 -23
  50. package/src/data/recipes.json +2074 -173
  51. package/src/derivation/build-methods.test.ts +13 -0
  52. package/src/derivation/build-methods.ts +48 -0
  53. package/src/derivation/capabilities.test.ts +348 -0
  54. package/src/derivation/capabilities.ts +692 -0
  55. package/src/derivation/capability-mappings.ts +60 -16
  56. package/src/derivation/crafting.test.ts +17 -0
  57. package/src/derivation/crafting.ts +56 -33
  58. package/src/derivation/index.ts +27 -2
  59. package/src/derivation/recipe-usage.test.ts +88 -0
  60. package/src/derivation/recipe-usage.ts +141 -0
  61. package/src/derivation/reserve-regen.ts +34 -0
  62. package/src/derivation/resources.ts +9 -1
  63. package/src/derivation/rollups.test.ts +55 -0
  64. package/src/derivation/rollups.ts +72 -0
  65. package/src/derivation/stars.test.ts +67 -0
  66. package/src/derivation/stars.ts +25 -0
  67. package/src/derivation/stat-scaling.ts +12 -0
  68. package/src/derivation/stats.ts +6 -6
  69. package/src/derivation/stratum.ts +26 -22
  70. package/src/derivation/tiers.ts +40 -7
  71. package/src/derivation/upgrades.ts +14 -0
  72. package/src/derivation/wormhole.ts +154 -0
  73. package/src/entities/entity.ts +102 -0
  74. package/src/entities/gamestate.ts +3 -28
  75. package/src/entities/makers.ts +141 -135
  76. package/src/entities/slot-multiplier.ts +43 -0
  77. package/src/errors.ts +12 -16
  78. package/src/format.ts +26 -4
  79. package/src/index-module.ts +333 -35
  80. package/src/managers/actions.ts +668 -103
  81. package/src/managers/base.ts +6 -2
  82. package/src/managers/cluster.test.ts +39 -0
  83. package/src/managers/cluster.ts +53 -0
  84. package/src/managers/construction-types.ts +80 -0
  85. package/src/managers/construction.ts +443 -0
  86. package/src/managers/context.ts +29 -1
  87. package/src/managers/coordinates.ts +14 -0
  88. package/src/managers/entities.ts +27 -66
  89. package/src/managers/epochs.ts +40 -0
  90. package/src/managers/index.ts +20 -1
  91. package/src/managers/locations.ts +25 -29
  92. package/src/managers/nft.test.ts +14 -0
  93. package/src/managers/nft.ts +70 -0
  94. package/src/managers/players.ts +25 -0
  95. package/src/managers/plot.ts +122 -0
  96. package/src/nft/atomicassets.abi.json +1342 -0
  97. package/src/nft/atomicassets.ts +237 -0
  98. package/src/nft/atomicdata.ts +130 -0
  99. package/src/nft/buildImmutableData.ts +385 -0
  100. package/src/nft/description.ts +225 -52
  101. package/src/nft/index.ts +3 -0
  102. package/src/planner/index.ts +242 -0
  103. package/src/planner/planner.test.ts +334 -0
  104. package/src/resolution/describe-module.ts +43 -25
  105. package/src/resolution/display-name.ts +38 -10
  106. package/src/resolution/resolve-item.test.ts +48 -0
  107. package/src/resolution/resolve-item.ts +112 -58
  108. package/src/scan/index.ts +244 -0
  109. package/src/scan/scan-wasm.base64.ts +2 -0
  110. package/src/scheduling/accessor.ts +65 -23
  111. package/src/scheduling/availability.test.ts +204 -0
  112. package/src/scheduling/availability.ts +211 -0
  113. package/src/scheduling/cancel.test.ts +542 -0
  114. package/src/scheduling/cancel.ts +254 -0
  115. package/src/scheduling/cluster-stock.test.ts +31 -0
  116. package/src/scheduling/cluster-stock.ts +15 -0
  117. package/src/scheduling/energy.ts +47 -0
  118. package/src/scheduling/idle-resolve.ts +45 -0
  119. package/src/scheduling/jobs.ts +71 -0
  120. package/src/scheduling/lane-core.ts +128 -0
  121. package/src/scheduling/lanes.test.ts +250 -0
  122. package/src/scheduling/lanes.ts +227 -0
  123. package/src/scheduling/projection.ts +257 -133
  124. package/src/scheduling/schedule.test.ts +119 -0
  125. package/src/scheduling/schedule.ts +257 -117
  126. package/src/scheduling/task-cargo.test.ts +44 -0
  127. package/src/scheduling/task-cargo.ts +46 -0
  128. package/src/scheduling/unwrap.test.ts +86 -0
  129. package/src/scheduling/unwrap.ts +190 -0
  130. package/src/shipload.ts +26 -1
  131. package/src/subscriptions/manager.cluster.test.ts +51 -0
  132. package/src/subscriptions/manager.ts +245 -172
  133. package/src/subscriptions/mappers.ts +5 -8
  134. package/src/subscriptions/types.ts +28 -3
  135. package/src/testing/catalog-hash.ts +19 -0
  136. package/src/testing/index.ts +2 -0
  137. package/src/testing/projection-parity.ts +167 -0
  138. package/src/travel/reach.ts +21 -0
  139. package/src/travel/route-planner.ts +288 -0
  140. package/src/travel/route-simulator.ts +170 -0
  141. package/src/travel/travel.ts +188 -122
  142. package/src/types/capabilities.ts +37 -8
  143. package/src/types/entity.ts +3 -3
  144. package/src/types/index.ts +0 -1
  145. package/src/types.ts +51 -16
  146. package/src/utils/cargo.test.ts +14 -0
  147. package/src/utils/cargo.ts +29 -0
  148. package/src/utils/display-name.ts +70 -0
  149. package/src/utils/system.ts +15 -16
  150. package/src/capabilities/loading.ts +0 -8
  151. package/src/entities/container.ts +0 -123
  152. package/src/entities/ship-deploy.ts +0 -295
  153. package/src/entities/ship.ts +0 -221
  154. package/src/entities/warehouse.ts +0 -127
  155. package/src/types/entity-traits.ts +0 -69
@@ -1,9 +1,11 @@
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,
@@ -12,69 +14,196 @@ import {
12
14
  import {
13
15
  ITEM_CONTAINER_T1_PACKED,
14
16
  ITEM_CONTAINER_T2_PACKED,
17
+ ITEM_CONSTRUCTION_DOCK_T1_PACKED,
18
+ ITEM_BUILDER_T1,
19
+ ITEM_BUILDER_T2,
15
20
  ITEM_CRAFTER_T1,
21
+ ITEM_CRAFTER_T2,
16
22
  ITEM_ENGINE_T1,
23
+ ITEM_ENGINE_T2,
24
+ ITEM_EXTRACTOR_T1_PACKED,
25
+ ITEM_FACTORY_T1_PACKED,
17
26
  ITEM_GATHERER_T1,
27
+ ITEM_GATHERER_T2,
18
28
  ITEM_GENERATOR_T1,
29
+ ITEM_GENERATOR_T2,
19
30
  ITEM_HAULER_T1,
31
+ ITEM_HAULER_T2,
32
+ ITEM_BATTERY_T1,
33
+ ITEM_LAUNCHER_T1,
20
34
  ITEM_LOADER_T1,
35
+ ITEM_LOADER_T2,
36
+ ITEM_PROSPECTOR_T1A_PACKED,
37
+ ITEM_PROSPECTOR_T2A_PACKED,
38
+ ITEM_PROSPECTOR_T2B_PACKED,
39
+ ITEM_ROUSTABOUT_T1A_PACKED,
21
40
  ITEM_SHIP_T1_PACKED,
41
+ ITEM_SMITH_T1A_PACKED,
22
42
  ITEM_STORAGE_T1,
43
+ ITEM_TENDER_T1A_PACKED,
44
+ ITEM_TUG_T1A_PACKED,
45
+ ITEM_PORTER_T1A_PACKED,
46
+ ITEM_WRIGHT_T1A_PACKED,
47
+ ITEM_DREDGER_T2A_PACKED,
23
48
  ITEM_WAREHOUSE_T1_PACKED,
24
49
  ITEM_WARP_T1,
50
+ ITEM_WARP_T2,
25
51
  } from '../data/item-ids'
26
52
  import {decodeStat} from '../derivation/crafting'
27
- import {gathererDepthForTier} from '../entities/ship-deploy'
53
+ import {
54
+ gathererDepthForTier,
55
+ computeGathererYield,
56
+ moduleTierPct,
57
+ ENGINE_THRUST_TIER_PCT,
58
+ GENERATOR_CAPACITY_TIER_PCT,
59
+ GENERATOR_RECHARGE_TIER_PCT,
60
+ CRAFTER_SPEED_TIER_PCT,
61
+ BUILDER_SPEED_TIER_PCT,
62
+ WARP_RANGE_TIER_PCT,
63
+ LOADER_THRUST_TIER_PCT,
64
+ } from '../derivation/capabilities'
28
65
  import {getItem} from '../data/catalog'
66
+ import {ENTITY_SHIP, getPackedEntityType} from '../data/kind-registry'
67
+ import {getBaseHullmassFor} from '../derivation/capabilities'
68
+ import {computeEffectiveModuleStat} from '../derivation/stat-scaling'
29
69
 
30
70
  function idiv(a: number, b: number): number {
31
71
  return Math.floor(a / b)
32
72
  }
33
73
 
34
- export function computeBaseHullmass(stats: bigint): number {
74
+ export function toWholeEnergy(milli: number): number {
75
+ return idiv(milli + 500, 1000)
76
+ }
77
+
78
+ function isShipHull(itemId: number): boolean {
79
+ return getPackedEntityType(itemId)?.equals(ENTITY_SHIP) ?? false
80
+ }
81
+
82
+ function sumPackedShipChannels(stats: bigint): number {
83
+ let sum = 0
84
+ for (let slot = 0; slot < 5; slot++) sum += decodeStat(stats, slot)
85
+ return sum
86
+ }
87
+
88
+ export function computeBaseHullmass(itemId: number, stats: bigint): number {
89
+ if (isShipHull(itemId)) {
90
+ return Math.floor(
91
+ (getBaseHullmassFor(itemId) * (10_000 - sumPackedShipChannels(stats))) / 10_000
92
+ )
93
+ }
35
94
  const density = decodeStat(stats, 1)
36
- return 25000 + 75 * density
95
+ return Math.floor((getBaseHullmassFor(itemId) * (2000 - density)) / 2000)
37
96
  }
38
97
 
39
98
  export function computeBaseCapacityShip(stats: bigint): number {
40
- const s = decodeStat(stats, 0) + decodeStat(stats, 2) + decodeStat(stats, 3)
41
- return Math.floor(1_000_000 * 10 ** (s / 2997))
99
+ return Math.floor(5_000_000 * 6 ** (sumPackedShipChannels(stats) / 4995))
100
+ }
101
+
102
+ export function computeBaseCapacityContainer(stats: bigint): number {
103
+ const s = decodeStat(stats, 0) + decodeStat(stats, 2)
104
+ return Math.floor(22_000_000 * 6 ** (s / 1998))
42
105
  }
43
106
 
44
107
  export function computeBaseCapacityWarehouse(stats: bigint): number {
45
- const s = decodeStat(stats, 0) + decodeStat(stats, 2) + decodeStat(stats, 3)
46
- return Math.floor(20_000_000 * 10 ** (s / 2997))
108
+ const s = decodeStat(stats, 0) + decodeStat(stats, 2)
109
+ return Math.floor(100_000_000 * 6 ** (s / 1998))
47
110
  }
48
111
 
49
- export const computeEngineThrust = (vol: number): number => 400 + idiv(vol * 3, 4)
50
- export const computeEngineDrain = (thm: number): number => Math.max(30, 50 - idiv(thm, 70))
51
- export const computeGeneratorCap = (com: number): number => 300 + idiv(com, 6)
52
- export const computeGeneratorRech = (fin: number): number => 1 + idiv(fin * 3, 1000)
53
- export const computeGathererYield = (str: number): number => 200 + str
112
+ export const computeEngineThrust = (vol: number, tier: number): number =>
113
+ idiv((400 + idiv(vol * 3, 4)) * moduleTierPct(ENGINE_THRUST_TIER_PCT, tier), 100)
114
+ export const computeEngineDrain = (thm: number): number => 2 * Math.max(30, 50 - idiv(thm, 70))
115
+ export const ENGINE_DRAIN_BASE = 156
116
+ export const ENGINE_DRAIN_REF_THRUST = 775
117
+ export const ENGINE_DRAIN_REF_THM = 500
118
+
119
+ export const computeTravelDrain = (totalThrust: number, avgThm: number): number => {
120
+ if (totalThrust <= 0) return 0
121
+ const thermalFactor = computeEngineDrain(avgThm) / computeEngineDrain(ENGINE_DRAIN_REF_THM)
122
+ const thrustFactor = Math.sqrt(ENGINE_DRAIN_REF_THRUST / totalThrust)
123
+ return Math.floor(ENGINE_DRAIN_BASE * 1000 * thermalFactor * thrustFactor)
124
+ }
125
+ export const computeGeneratorCap = (com: number, tier: number): number =>
126
+ idiv((1_300_000 + com * 500) * moduleTierPct(GENERATOR_CAPACITY_TIER_PCT, tier), 100)
127
+ export const computeGeneratorRech = (fin: number, tier: number): number =>
128
+ idiv((2000 + fin * 6) * moduleTierPct(GENERATOR_RECHARGE_TIER_PCT, tier), 100)
54
129
  export const computeGathererDrain = (con: number): number =>
55
- Math.max(250, 1250 - idiv(con * 25, 20))
130
+ 2 * Math.max(250_000, 1_250_000 - con * 1250)
56
131
  export const computeGathererDepth = (tol: number, tier: number): number =>
57
132
  gathererDepthForTier(tol, tier)
58
- export const computeGathererSpeed = (ref: number): number => 100 + idiv(ref * 4, 5)
59
133
  export const computeLoaderMass = (ins: number): number => Math.max(200, 2000 - ins * 2)
60
- export const computeLoaderThrust = (pla: number): number => 1 + idiv(pla, 500)
61
- export const computeCrafterSpeed = (rea: number): number => 100 + idiv(rea * 4, 5)
62
- export const computeCrafterDrain = (fin: number): number => Math.max(5, 30 - idiv(fin, 33))
63
- export const computeHaulerCapacity = (fin: number): number => Math.max(1, 1 + idiv(fin, 400))
134
+ export const computeLoaderThrust = (pla: number, tier: number): number =>
135
+ idiv((1 + idiv(pla * pla, 10000)) * moduleTierPct(LOADER_THRUST_TIER_PCT, tier), 100)
136
+ export const computeCrafterSpeed = (rea: number, tier: number): number =>
137
+ idiv((100 + idiv(rea * 4, 5)) * moduleTierPct(CRAFTER_SPEED_TIER_PCT, tier), 100)
138
+ export const computeCrafterDrain = (fin: number): number =>
139
+ Math.max(5000, 30000 - idiv(fin * 1000, 33))
140
+ export const computeBuilderSpeed = (resonance: number, tier: number): number =>
141
+ idiv((100 + idiv(resonance * 4, 5)) * moduleTierPct(BUILDER_SPEED_TIER_PCT, tier), 100)
142
+ export const computeBuilderDrain = (fineness: number): number =>
143
+ Math.max(5000, 30000 - idiv(fineness * 1000, 33))
144
+ export const computeHaulerCapacity = (fin: number, tier: number): number =>
145
+ Math.max(tier, tier + idiv(fin, 400))
64
146
  export const computeHaulerEfficiency = (con: number): number => 2000 + con * 6
65
- export const computeHaulerDrain = (com: number): number => Math.max(3, 15 - idiv(com, 80))
66
- export const computeWarpRange = (stat: number): number => 100 + stat * 3
147
+ export const supportDrainTierPercent = (tier: number): number => {
148
+ const clampedTier = Math.min(10, Math.max(1, Math.trunc(tier)))
149
+ return Math.max(50, 110 - clampedTier * 10)
150
+ }
151
+ const computeT1LogisticsDrain = (stat: number): number =>
152
+ Math.max(3000, 15000 - Math.min(12000, idiv(stat * 1000, 80)))
153
+ export const computeHaulerDrain = (conductivity: number, tier: number): number =>
154
+ idiv(computeT1LogisticsDrain(conductivity) * supportDrainTierPercent(tier), 100)
155
+ export const computeCargoBayDrain = (cohesion: number, tier: number): number =>
156
+ idiv(computeT1LogisticsDrain(cohesion) * 3 * supportDrainTierPercent(tier), 400)
157
+ export const computeWarpRange = (stat: number, tier: number): number =>
158
+ idiv((100 + stat * 3) * moduleTierPct(WARP_RANGE_TIER_PCT, tier), 100)
159
+ export const computeCargoBayCapacity = (
160
+ strength: number,
161
+ density: number,
162
+ hardness: number
163
+ ): number => 10_000_000 + idiv((strength + density + hardness) * 50_000_000, 2997)
164
+ export const computeBatteryBankCapacity = (
165
+ volatility: number,
166
+ thermal: number,
167
+ plasticity: number,
168
+ insulation: number
169
+ ): number => 2_500_000 + idiv((volatility + thermal + plasticity + insulation) * 7_500_000, 3996)
67
170
 
68
171
  export function entityDisplayName(itemId: number): string {
69
172
  switch (itemId) {
70
173
  case ITEM_SHIP_T1_PACKED:
71
174
  return 'Ship'
175
+ case ITEM_ROUSTABOUT_T1A_PACKED:
176
+ return 'Roustabout'
177
+ case ITEM_PROSPECTOR_T1A_PACKED:
178
+ return 'Prospector'
179
+ case ITEM_TENDER_T1A_PACKED:
180
+ return 'Tender'
181
+ case ITEM_TUG_T1A_PACKED:
182
+ return 'Tug'
183
+ case ITEM_PORTER_T1A_PACKED:
184
+ return 'Porter'
185
+ case ITEM_WRIGHT_T1A_PACKED:
186
+ return 'Wright'
187
+ case ITEM_SMITH_T1A_PACKED:
188
+ return 'Smith'
72
189
  case ITEM_WAREHOUSE_T1_PACKED:
73
190
  return 'Warehouse'
191
+ case ITEM_EXTRACTOR_T1_PACKED:
192
+ return 'Mining Rig'
193
+ case ITEM_FACTORY_T1_PACKED:
194
+ return 'Factory'
195
+ case ITEM_CONSTRUCTION_DOCK_T1_PACKED:
196
+ return 'Construction Dock'
74
197
  case ITEM_CONTAINER_T1_PACKED:
75
198
  return 'Container'
76
199
  case ITEM_CONTAINER_T2_PACKED:
77
200
  return 'Container'
201
+ case ITEM_PROSPECTOR_T2A_PACKED:
202
+ return 'Prospector'
203
+ case ITEM_PROSPECTOR_T2B_PACKED:
204
+ return 'Prospector'
205
+ case ITEM_DREDGER_T2A_PACKED:
206
+ return 'Dredger'
78
207
  default:
79
208
  return 'Entity'
80
209
  }
@@ -83,21 +212,35 @@ export function entityDisplayName(itemId: number): string {
83
212
  export function moduleDisplayName(itemId: number): string {
84
213
  switch (itemId) {
85
214
  case ITEM_ENGINE_T1:
215
+ case ITEM_ENGINE_T2:
86
216
  return 'Engine'
87
217
  case ITEM_GENERATOR_T1:
88
- return 'Generator'
218
+ case ITEM_GENERATOR_T2:
219
+ return 'Power Core'
89
220
  case ITEM_GATHERER_T1:
90
- return 'Gatherer'
221
+ case ITEM_GATHERER_T2:
222
+ return 'Limpet Bay'
91
223
  case ITEM_LOADER_T1:
92
- return 'Loader'
224
+ case ITEM_LOADER_T2:
225
+ return 'Shuttle Bay'
93
226
  case ITEM_CRAFTER_T1:
94
- return 'Crafter'
227
+ case ITEM_CRAFTER_T2:
228
+ return 'Fabricator'
229
+ case ITEM_BUILDER_T1:
230
+ case ITEM_BUILDER_T2:
231
+ return 'Assembly Arm'
95
232
  case ITEM_STORAGE_T1:
96
- return 'Storage'
233
+ return 'Cargo Hold'
97
234
  case ITEM_HAULER_T1:
98
- return 'Hauler'
235
+ case ITEM_HAULER_T2:
236
+ return 'Tractor Beam'
99
237
  case ITEM_WARP_T1:
100
- return 'Warp'
238
+ case ITEM_WARP_T2:
239
+ return 'Warp Drive'
240
+ case ITEM_BATTERY_T1:
241
+ return 'Battery Bank'
242
+ case ITEM_LAUNCHER_T1:
243
+ return 'Drive Coil'
101
244
  default:
102
245
  return 'Module'
103
246
  }
@@ -115,60 +258,82 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
115
258
 
116
259
  switch (subtype) {
117
260
  case MODULE_ENGINE: {
118
- const vol = decodeStat(stats, 0)
119
- const thm = decodeStat(stats, 1)
120
- out += ` Thrust ${computeEngineThrust(vol)} Drain ${computeEngineDrain(thm)}`
261
+ const vol = computeEffectiveModuleStat(decodeStat(stats, 0))
262
+ const thm = computeEffectiveModuleStat(decodeStat(stats, 1))
263
+ const tier = getItem(itemId).tier
264
+ out += ` Thrust ${computeEngineThrust(vol, tier)} Drain ${computeEngineDrain(thm)}`
121
265
  break
122
266
  }
123
267
  case MODULE_GENERATOR: {
124
- const res = decodeStat(stats, 0)
125
- const ref = decodeStat(stats, 1)
126
- out += ` Capacity ${computeGeneratorCap(res)} Recharge ${computeGeneratorRech(ref)}`
268
+ const res = computeEffectiveModuleStat(decodeStat(stats, 0))
269
+ const ref = computeEffectiveModuleStat(decodeStat(stats, 1))
270
+ const tier = getItem(itemId).tier
271
+ out += ` Capacity ${toWholeEnergy(computeGeneratorCap(res, tier))} Recharge ${toWholeEnergy(
272
+ computeGeneratorRech(ref, tier)
273
+ )}`
127
274
  break
128
275
  }
129
276
  case MODULE_GATHERER: {
130
277
  const str = decodeStat(stats, 0)
131
278
  const tol = decodeStat(stats, 1)
132
- const con = decodeStat(stats, 3)
133
- const ref = decodeStat(stats, 4)
279
+ const con = decodeStat(stats, 2)
134
280
  const tier = getItem(itemId).tier
135
- out += ` Yield ${computeGathererYield(str)} Depth ${computeGathererDepth(
281
+ out += ` Yield ${computeGathererYield(str, tier)} Depth ${computeGathererDepth(
136
282
  tol,
137
283
  tier
138
- )} Speed ${computeGathererSpeed(ref)} Drain ${computeGathererDrain(con)}`
284
+ )} Drain ${toWholeEnergy(computeGathererDrain(con))}`
139
285
  break
140
286
  }
141
287
  case MODULE_LOADER: {
142
288
  const fin = decodeStat(stats, 0)
143
289
  const pla = decodeStat(stats, 1)
144
- out += ` Mass ${computeLoaderMass(fin)} Thrust ${computeLoaderThrust(pla)}`
290
+ const tier = getItem(itemId).tier
291
+ out += ` Mass ${computeLoaderMass(fin)} Thrust ${computeLoaderThrust(pla, tier)}`
145
292
  break
146
293
  }
147
294
  case MODULE_CRAFTER: {
148
295
  const rea = decodeStat(stats, 0)
149
- const com = decodeStat(stats, 1)
150
- out += ` Speed ${computeCrafterSpeed(rea)} Drain ${computeCrafterDrain(com)}`
296
+ const con = decodeStat(stats, 1)
297
+ const tier = getItem(itemId).tier
298
+ out += ` Speed ${computeCrafterSpeed(rea, tier)} Drain ${toWholeEnergy(computeCrafterDrain(con))}`
299
+ break
300
+ }
301
+ case MODULE_BUILDER: {
302
+ const res = decodeStat(stats, 0)
303
+ const fin = decodeStat(stats, 1)
304
+ const tier = getItem(itemId).tier
305
+ out += ` Speed ${computeBuilderSpeed(res, tier)} Drain ${toWholeEnergy(computeBuilderDrain(fin))}`
151
306
  break
152
307
  }
153
308
  case MODULE_STORAGE: {
154
309
  const str = decodeStat(stats, 0)
155
- const fin = decodeStat(stats, 2)
156
- const sat = decodeStat(stats, 3)
157
- const sum = str + fin + sat
158
- const pct = 10 + idiv(sum * 10, 2997)
159
- out += ` +${pct}% capacity`
310
+ const den = decodeStat(stats, 1)
311
+ const hrd = decodeStat(stats, 2)
312
+ const coh = decodeStat(stats, 3)
313
+ const tier = getItem(itemId).tier
314
+ out += ` Cargo Capacity ${computeCargoBayCapacity(str, den, hrd)} Drain ${toWholeEnergy(computeCargoBayDrain(coh, tier))}`
160
315
  break
161
316
  }
162
317
  case MODULE_HAULER: {
163
- const fin = decodeStat(stats, 0)
164
- const con = decodeStat(stats, 1)
165
- const com = decodeStat(stats, 2)
166
- out += ` Capacity ${computeHaulerCapacity(fin)} Efficiency ${computeHaulerEfficiency(con)} Drain ${computeHaulerDrain(com)}`
318
+ const res = decodeStat(stats, 0)
319
+ const pla = decodeStat(stats, 1)
320
+ const con = decodeStat(stats, 2)
321
+ const tier = getItem(itemId).tier
322
+ out += ` Capacity ${computeHaulerCapacity(res, tier)} Efficiency ${computeHaulerEfficiency(pla)} Drain ${toWholeEnergy(computeHaulerDrain(con, tier))}`
167
323
  break
168
324
  }
169
325
  case MODULE_WARP: {
170
326
  const stat = decodeStat(stats, 0)
171
- out += ` Range ${computeWarpRange(stat)}`
327
+ const tier = getItem(itemId).tier
328
+ out += ` Range ${computeWarpRange(stat, tier)}`
329
+ break
330
+ }
331
+ case MODULE_BATTERY: {
332
+ const vol = decodeStat(stats, 0)
333
+ const thm = decodeStat(stats, 1)
334
+ const pla = decodeStat(stats, 2)
335
+ const ins = decodeStat(stats, 3)
336
+ out += ` Energy Capacity ${toWholeEnergy(computeBatteryBankCapacity(vol, thm, pla, ins))}`
172
337
  break
173
338
  }
174
339
  }
@@ -181,12 +346,20 @@ export function buildEntityDescription(
181
346
  moduleItems: number[],
182
347
  moduleStats: bigint[]
183
348
  ): string {
184
- const hullMass = computeBaseHullmass(hullStats)
349
+ const hullMass = computeBaseHullmass(itemId, hullStats)
185
350
  let baseCapacity = 0
186
- if (itemId === ITEM_SHIP_T1_PACKED) {
351
+ if (isShipHull(itemId)) {
187
352
  baseCapacity = computeBaseCapacityShip(hullStats)
188
353
  } else if (itemId === ITEM_WAREHOUSE_T1_PACKED) {
189
354
  baseCapacity = computeBaseCapacityWarehouse(hullStats)
355
+ } else if (
356
+ itemId === ITEM_EXTRACTOR_T1_PACKED ||
357
+ itemId === ITEM_FACTORY_T1_PACKED ||
358
+ itemId === ITEM_CONSTRUCTION_DOCK_T1_PACKED ||
359
+ itemId === ITEM_CONTAINER_T1_PACKED ||
360
+ itemId === ITEM_CONTAINER_T2_PACKED
361
+ ) {
362
+ baseCapacity = computeBaseCapacityContainer(hullStats)
190
363
  }
191
364
 
192
365
  let out = entityDisplayName(itemId)
package/src/nft/index.ts CHANGED
@@ -1,2 +1,5 @@
1
1
  export * from './deserializers'
2
2
  export * from './description'
3
+ export * from './atomicdata'
4
+ export * from './atomicassets'
5
+ export * from './buildImmutableData'
@@ -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
+ }