@elite-dangerous-almanac/core 0.1.0-beta.9 → 0.1.2

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 (239) hide show
  1. package/PROVENANCE/i18n/SOURCES.md +71 -12
  2. package/PROVENANCE/ships/SOURCES.md +74 -48
  3. package/README.md +34 -2
  4. package/THIRD_PARTY_NOTICES.md +3 -3
  5. package/dist/astro/codex-region-lookup.js +1 -1
  6. package/dist/astro/codex-region.js +1 -1
  7. package/dist/astro/galaxy-grid.js +1 -1
  8. package/dist/astro/index.js +1 -1
  9. package/dist/astro/mass-code.js +1 -1
  10. package/dist/astro/naming-region-origins.js +1 -1
  11. package/dist/astro/nebulae.js +1 -1
  12. package/dist/astro/permit-locked-regions.js +1 -1
  13. package/dist/astro/permit-locked-systems.js +1 -1
  14. package/dist/astro/permit-locks.js +1 -1
  15. package/dist/astro/procedural-system.js +1 -1
  16. package/dist/astro/sector-name.js +1 -1
  17. package/dist/astro/system-address-input.js +1 -1
  18. package/dist/astro/system-address.js +1 -1
  19. package/dist/astro/system-name.js +1 -1
  20. package/dist/{chunk-KQDJTDLV.js → chunk-2J2UV4ZX.js} +1 -1
  21. package/dist/{chunk-DB54PH3X.js → chunk-6EPYVBAX.js} +1 -1
  22. package/dist/{chunk-U6TMCYA6.js → chunk-6EUVIZAS.js} +1 -1
  23. package/dist/chunk-6RLMBSXE.js +1 -0
  24. package/dist/chunk-6RLMBSXE.js.map +1 -0
  25. package/dist/{chunk-NWPK6Q3S.js → chunk-ADLVPKUV.js} +1 -1
  26. package/dist/{chunk-S4DBNX2B.js → chunk-BKWLI44T.js} +1 -1
  27. package/dist/chunk-BMM6GQAU.js +1 -0
  28. package/dist/chunk-BMM6GQAU.js.map +1 -0
  29. package/dist/{chunk-WV5YM7H5.js → chunk-BMT3U46U.js} +1 -1
  30. package/dist/chunk-CTJQPQRA.js +1 -0
  31. package/dist/{chunk-NSSWYRHN.js.map → chunk-CTJQPQRA.js.map} +1 -1
  32. package/dist/chunk-DAQ3PJ4R.js +1 -0
  33. package/dist/chunk-DAQ3PJ4R.js.map +1 -0
  34. package/dist/{chunk-BTPV5CIF.js → chunk-DI5RXSJR.js} +1 -1
  35. package/dist/{chunk-YDBRGW4G.js → chunk-DLLBARN5.js} +1 -1
  36. package/dist/{chunk-J2PNZQY4.js → chunk-DVL5RBKX.js} +1 -1
  37. package/dist/{chunk-VXVUEF5U.js → chunk-E3BRVGJJ.js} +1 -1
  38. package/dist/{chunk-MFV4VZFP.js → chunk-EBAHNZ36.js} +1 -1
  39. package/dist/{chunk-BSOIPL4A.js → chunk-EJMNNQIS.js} +1 -1
  40. package/dist/{chunk-BSOIPL4A.js.map → chunk-EJMNNQIS.js.map} +1 -1
  41. package/dist/{chunk-QF3NYGUJ.js → chunk-EKWLAHRG.js} +1 -1
  42. package/dist/chunk-EU42PAV4.js +1 -0
  43. package/dist/{chunk-32PVERNH.js.map → chunk-EU42PAV4.js.map} +1 -1
  44. package/dist/{chunk-Q22LXT53.js → chunk-FEER4ERG.js} +1 -1
  45. package/dist/{chunk-G3AMKCOE.js → chunk-FGNJ7EVM.js} +1 -1
  46. package/dist/{chunk-JWJ7RSZC.js → chunk-G3265B27.js} +1 -1
  47. package/dist/chunk-GKDEI2SQ.js +1 -0
  48. package/dist/chunk-GKDEI2SQ.js.map +1 -0
  49. package/dist/chunk-GOMBLS2T.js +1 -0
  50. package/dist/chunk-GOMBLS2T.js.map +1 -0
  51. package/dist/{chunk-V3DW67W2.js → chunk-GRIK3EDH.js} +1 -1
  52. package/dist/chunk-H3HMVGEY.js +1 -0
  53. package/dist/chunk-H3HMVGEY.js.map +1 -0
  54. package/dist/{chunk-UXZJMZEU.js → chunk-HRVIHQ4A.js} +1 -1
  55. package/dist/{chunk-QTDBO7R2.js → chunk-IYU4WLFM.js} +1 -1
  56. package/dist/{chunk-5EUX3L76.js → chunk-JWWYAMGD.js} +1 -1
  57. package/dist/chunk-KAA4VOOB.js +1 -0
  58. package/dist/chunk-KAA4VOOB.js.map +1 -0
  59. package/dist/{chunk-ZFT56QFR.js → chunk-L7CLUYZ6.js} +1 -1
  60. package/dist/{chunk-4AE22CHV.js → chunk-M5IBENCW.js} +1 -1
  61. package/dist/chunk-M5IBENCW.js.map +1 -0
  62. package/dist/{chunk-IH4NXKVW.js → chunk-NEEFT7WH.js} +1 -1
  63. package/dist/{chunk-VZXL5KBR.js → chunk-NLBUKXNV.js} +1 -1
  64. package/dist/{chunk-4OSCSACF.js → chunk-OCND33TO.js} +1 -1
  65. package/dist/{chunk-PMJG7PHU.js → chunk-PHPLZEFS.js} +1 -1
  66. package/dist/chunk-PKWVG2JY.js +1 -0
  67. package/dist/chunk-PKWVG2JY.js.map +1 -0
  68. package/dist/chunk-PNZCLQDJ.js +1 -0
  69. package/dist/{chunk-53ERRGPM.js.map → chunk-PNZCLQDJ.js.map} +1 -1
  70. package/dist/{chunk-DACZE3HX.js → chunk-POZTXBSN.js} +1 -1
  71. package/dist/chunk-Q4E2V6RE.js +1 -0
  72. package/dist/chunk-Q4E2V6RE.js.map +1 -0
  73. package/dist/{chunk-DNTN34M4.js → chunk-QI22FKBG.js} +1 -1
  74. package/dist/chunk-QTZPA2FR.js +1 -0
  75. package/dist/{chunk-TH4S5MVD.js.map → chunk-QTZPA2FR.js.map} +1 -1
  76. package/dist/{chunk-MNXWK7PA.js → chunk-R26GVRMI.js} +1 -1
  77. package/dist/{chunk-I7PIDRMU.js → chunk-RAO35XDF.js} +1 -1
  78. package/dist/chunk-TFPIPIMU.js +1 -0
  79. package/dist/chunk-TFPIPIMU.js.map +1 -0
  80. package/dist/chunk-TLA6LHLN.js +1 -0
  81. package/dist/{chunk-V4C6FIE2.js.map → chunk-TLA6LHLN.js.map} +1 -1
  82. package/dist/{chunk-JYQE53FL.js → chunk-TMMGS6TC.js} +1 -1
  83. package/dist/{chunk-ZAHNOQGG.js → chunk-UBYO7VZR.js} +1 -1
  84. package/dist/{chunk-Z4GTTB7I.js → chunk-UXBCLFGU.js} +1 -1
  85. package/dist/{chunk-RKLJ7EEE.js → chunk-UXFMVZAY.js} +1 -1
  86. package/dist/chunk-VB3XZGIB.js +1 -0
  87. package/dist/chunk-VB3XZGIB.js.map +1 -0
  88. package/dist/chunk-VKN65H2U.js +1 -0
  89. package/dist/chunk-VKN65H2U.js.map +1 -0
  90. package/dist/chunk-VSNS7Y3M.js +1 -0
  91. package/dist/chunk-VSNS7Y3M.js.map +1 -0
  92. package/dist/{chunk-MPZGCTRG.js → chunk-VZZUNSDF.js} +1 -1
  93. package/dist/chunk-VZZUNSDF.js.map +1 -0
  94. package/dist/{chunk-ZJB5UYNE.js → chunk-W5G2NUCZ.js} +1 -1
  95. package/dist/{chunk-ZJB5UYNE.js.map → chunk-W5G2NUCZ.js.map} +1 -1
  96. package/dist/{chunk-IJRKEODR.js → chunk-WNGQ3IAM.js} +1 -1
  97. package/dist/{chunk-VARPO47V.js → chunk-XN6Q7LWU.js} +1 -1
  98. package/dist/chunk-XT6KKGSE.js +1 -0
  99. package/dist/chunk-XT6KKGSE.js.map +1 -0
  100. package/dist/{chunk-KMFUCORC.js → chunk-Y6UZWNP4.js} +1 -1
  101. package/dist/{chunk-QMQHIZGS.js → chunk-YU5GND2Q.js} +1 -1
  102. package/dist/chunk-YU5GND2Q.js.map +1 -0
  103. package/dist/commodities/commodities.js +1 -1
  104. package/dist/commodities/index.js +1 -1
  105. package/dist/equipment/index.js +1 -1
  106. package/dist/equipment/modification-costs.js +1 -1
  107. package/dist/equipment/modification-journal.js +1 -1
  108. package/dist/equipment/modifications.js +1 -1
  109. package/dist/equipment/suits.js +1 -1
  110. package/dist/equipment/upgrade-costs.js +1 -1
  111. package/dist/equipment/weapons.js +1 -1
  112. package/dist/i18n/blueprints.js +1 -1
  113. package/dist/i18n/diagnostics.d.ts +62 -0
  114. package/dist/i18n/diagnostics.js +1 -0
  115. package/dist/i18n/diagnostics.js.map +1 -0
  116. package/dist/i18n/engineering-groups.d.ts +20 -0
  117. package/dist/i18n/engineering-groups.js +1 -0
  118. package/dist/i18n/engineering-groups.js.map +1 -0
  119. package/dist/i18n/experimental-effect-descriptions.d.ts +20 -0
  120. package/dist/i18n/experimental-effect-descriptions.js +1 -0
  121. package/dist/i18n/experimental-effect-descriptions.js.map +1 -0
  122. package/dist/i18n/experimental-effects.js +1 -1
  123. package/dist/i18n/index.d.ts +27 -0
  124. package/dist/i18n/index.js +1 -1
  125. package/dist/i18n/materials.js +1 -1
  126. package/dist/i18n/micro-resources.js +1 -1
  127. package/dist/i18n/modules.js +1 -1
  128. package/dist/i18n/pre-engineered.d.ts +39 -0
  129. package/dist/i18n/pre-engineered.js +1 -0
  130. package/dist/i18n/pre-engineered.js.map +1 -0
  131. package/dist/i18n/ships.d.ts +36 -0
  132. package/dist/i18n/ships.js +1 -0
  133. package/dist/i18n/ships.js.map +1 -0
  134. package/dist/i18n/slots.d.ts +42 -0
  135. package/dist/i18n/slots.js +1 -0
  136. package/dist/i18n/slots.js.map +1 -0
  137. package/dist/materials/index.js +1 -1
  138. package/dist/materials/materials-all.js +1 -1
  139. package/dist/materials/materials.js +1 -1
  140. package/dist/materials/micro-resources-all.js +1 -1
  141. package/dist/materials/micro-resources.js +1 -1
  142. package/dist/ships/blueprint-costs.d.ts +13 -11
  143. package/dist/ships/blueprint-costs.js +1 -1
  144. package/dist/ships/blueprint-costs.js.map +1 -1
  145. package/dist/ships/blueprint-journal.d.ts +6 -7
  146. package/dist/ships/blueprint-journal.js +1 -1
  147. package/dist/ships/blueprints.js +1 -1
  148. package/dist/ships/default-loadouts.js +1 -1
  149. package/dist/ships/engineering-options.d.ts +28 -29
  150. package/dist/ships/engineering-options.js +1 -1
  151. package/dist/ships/experimental-effect-costs.js +1 -1
  152. package/dist/ships/experimental-effects.js +1 -1
  153. package/dist/ships/gunsights.js +1 -1
  154. package/dist/ships/heat.d.ts +22 -0
  155. package/dist/ships/heat.js +1 -1
  156. package/dist/ships/index.d.ts +6 -5
  157. package/dist/ships/index.js +1 -1
  158. package/dist/ships/loadout-calculations.d.ts +21 -11
  159. package/dist/ships/loadout-calculations.js +1 -1
  160. package/dist/ships/loadout-validation.d.ts +7 -6
  161. package/dist/ships/loadout-validation.js +1 -1
  162. package/dist/ships/modules-all.js +1 -1
  163. package/dist/ships/modules-hardpoint.js +1 -1
  164. package/dist/ships/modules-internal.js +1 -1
  165. package/dist/ships/modules.d.ts +5 -5
  166. package/dist/ships/modules.js +1 -1
  167. package/dist/ships/power.d.ts +39 -11
  168. package/dist/ships/power.js +1 -1
  169. package/dist/ships/pre-engineered-stats.d.ts +20 -4
  170. package/dist/ships/pre-engineered-stats.js +1 -1
  171. package/dist/ships/pre-engineered.d.ts +7 -8
  172. package/dist/ships/pre-engineered.js +1 -1
  173. package/dist/ships/shield-recovery.d.ts +12 -6
  174. package/dist/ships/shield-recovery.js +1 -1
  175. package/dist/ships/ship-loadout.d.ts +1793 -19
  176. package/dist/ships/ship-loadout.js +1 -1
  177. package/dist/ships/ships.js +1 -1
  178. package/dist/ships/slef.js +1 -1
  179. package/dist/ships/slots.js +1 -1
  180. package/dist/ships/source-purchase.js +1 -1
  181. package/package.json +26 -2
  182. package/dist/chunk-32PVERNH.js +0 -1
  183. package/dist/chunk-4AE22CHV.js.map +0 -1
  184. package/dist/chunk-4WC5NHBJ.js +0 -1
  185. package/dist/chunk-4WC5NHBJ.js.map +0 -1
  186. package/dist/chunk-53ERRGPM.js +0 -1
  187. package/dist/chunk-5W43EQEQ.js +0 -1
  188. package/dist/chunk-5W43EQEQ.js.map +0 -1
  189. package/dist/chunk-76XKY2Y2.js +0 -1
  190. package/dist/chunk-76XKY2Y2.js.map +0 -1
  191. package/dist/chunk-B5TT26XE.js +0 -1
  192. package/dist/chunk-B5TT26XE.js.map +0 -1
  193. package/dist/chunk-BYBT5XSW.js +0 -1
  194. package/dist/chunk-BYBT5XSW.js.map +0 -1
  195. package/dist/chunk-KLIYDSYF.js +0 -1
  196. package/dist/chunk-KLIYDSYF.js.map +0 -1
  197. package/dist/chunk-MPZGCTRG.js.map +0 -1
  198. package/dist/chunk-NSSWYRHN.js +0 -1
  199. package/dist/chunk-QMQHIZGS.js.map +0 -1
  200. package/dist/chunk-RF2IKLWQ.js +0 -1
  201. package/dist/chunk-RF2IKLWQ.js.map +0 -1
  202. package/dist/chunk-TH4S5MVD.js +0 -1
  203. package/dist/chunk-V4C6FIE2.js +0 -1
  204. package/dist/ship-loadout-BsfKbvKe.d.ts +0 -1455
  205. /package/dist/{chunk-KQDJTDLV.js.map → chunk-2J2UV4ZX.js.map} +0 -0
  206. /package/dist/{chunk-DB54PH3X.js.map → chunk-6EPYVBAX.js.map} +0 -0
  207. /package/dist/{chunk-U6TMCYA6.js.map → chunk-6EUVIZAS.js.map} +0 -0
  208. /package/dist/{chunk-NWPK6Q3S.js.map → chunk-ADLVPKUV.js.map} +0 -0
  209. /package/dist/{chunk-S4DBNX2B.js.map → chunk-BKWLI44T.js.map} +0 -0
  210. /package/dist/{chunk-WV5YM7H5.js.map → chunk-BMT3U46U.js.map} +0 -0
  211. /package/dist/{chunk-BTPV5CIF.js.map → chunk-DI5RXSJR.js.map} +0 -0
  212. /package/dist/{chunk-YDBRGW4G.js.map → chunk-DLLBARN5.js.map} +0 -0
  213. /package/dist/{chunk-J2PNZQY4.js.map → chunk-DVL5RBKX.js.map} +0 -0
  214. /package/dist/{chunk-VXVUEF5U.js.map → chunk-E3BRVGJJ.js.map} +0 -0
  215. /package/dist/{chunk-MFV4VZFP.js.map → chunk-EBAHNZ36.js.map} +0 -0
  216. /package/dist/{chunk-QF3NYGUJ.js.map → chunk-EKWLAHRG.js.map} +0 -0
  217. /package/dist/{chunk-Q22LXT53.js.map → chunk-FEER4ERG.js.map} +0 -0
  218. /package/dist/{chunk-G3AMKCOE.js.map → chunk-FGNJ7EVM.js.map} +0 -0
  219. /package/dist/{chunk-JWJ7RSZC.js.map → chunk-G3265B27.js.map} +0 -0
  220. /package/dist/{chunk-V3DW67W2.js.map → chunk-GRIK3EDH.js.map} +0 -0
  221. /package/dist/{chunk-UXZJMZEU.js.map → chunk-HRVIHQ4A.js.map} +0 -0
  222. /package/dist/{chunk-QTDBO7R2.js.map → chunk-IYU4WLFM.js.map} +0 -0
  223. /package/dist/{chunk-5EUX3L76.js.map → chunk-JWWYAMGD.js.map} +0 -0
  224. /package/dist/{chunk-ZFT56QFR.js.map → chunk-L7CLUYZ6.js.map} +0 -0
  225. /package/dist/{chunk-IH4NXKVW.js.map → chunk-NEEFT7WH.js.map} +0 -0
  226. /package/dist/{chunk-VZXL5KBR.js.map → chunk-NLBUKXNV.js.map} +0 -0
  227. /package/dist/{chunk-4OSCSACF.js.map → chunk-OCND33TO.js.map} +0 -0
  228. /package/dist/{chunk-PMJG7PHU.js.map → chunk-PHPLZEFS.js.map} +0 -0
  229. /package/dist/{chunk-DACZE3HX.js.map → chunk-POZTXBSN.js.map} +0 -0
  230. /package/dist/{chunk-DNTN34M4.js.map → chunk-QI22FKBG.js.map} +0 -0
  231. /package/dist/{chunk-MNXWK7PA.js.map → chunk-R26GVRMI.js.map} +0 -0
  232. /package/dist/{chunk-I7PIDRMU.js.map → chunk-RAO35XDF.js.map} +0 -0
  233. /package/dist/{chunk-JYQE53FL.js.map → chunk-TMMGS6TC.js.map} +0 -0
  234. /package/dist/{chunk-ZAHNOQGG.js.map → chunk-UBYO7VZR.js.map} +0 -0
  235. /package/dist/{chunk-Z4GTTB7I.js.map → chunk-UXBCLFGU.js.map} +0 -0
  236. /package/dist/{chunk-RKLJ7EEE.js.map → chunk-UXFMVZAY.js.map} +0 -0
  237. /package/dist/{chunk-IJRKEODR.js.map → chunk-WNGQ3IAM.js.map} +0 -0
  238. /package/dist/{chunk-VARPO47V.js.map → chunk-XN6Q7LWU.js.map} +0 -0
  239. /package/dist/{chunk-KMFUCORC.js.map → chunk-Y6UZWNP4.js.map} +0 -0
@@ -1,21 +1,1795 @@
1
- import './slef.js';
2
- import './jump-range.js';
3
- import './slots.js';
4
- import './modules.js';
5
- import './power.js';
6
- import './heat.js';
7
- import './shields.js';
8
- import './armour.js';
9
- import './weapons.js';
10
- import './ammunition.js';
11
- import './weapons-capacitor.js';
12
- import './distributor.js';
13
- import './mobility.js';
14
- import './shield-recovery.js';
15
- import './pre-engineered.js';
16
- export { A as ApplyBlueprintOptions, a as AvailableBlueprint, B as BuildWeaponMetrics, D as DefenceOptions, b as DistributorOptions, c as FittedWeaponMetrics, I as ImmovableReason, J as JumpOptions, d as JumpRangeSummary, L as LoadoutEditError, e as LoadoutEditErrorCode, f as LoadoutExportOptions, g as LoadoutSlot, M as MobilityOptions, R as RetailCredits, S as ShipLoadout, h as SlefExportOptions, W as WeaponsOptions } from '../ship-loadout-BsfKbvKe.js';
17
- import './source-purchase.js';
18
- import './loadout-calculations.js';
19
- import './loadout-validation.js';
1
+ import { ModuleEngineering, LoadoutModule, LoadoutEvent, SlefHeader, Slef } from './slef.js';
2
+ import { TotalRangeDetails, FrameShiftDriveParams } from './jump-range.js';
3
+ import { BuildSlot, SlotKind } from './slots.js';
4
+ import { OutfittingModule, ProjectileRangeBoundaries } from './modules.js';
5
+ import { PowerBudget } from './power.js';
6
+ import { HeatMetrics } from './heat.js';
7
+ import { ShieldMetrics } from './shields.js';
8
+ import { ArmourMetrics } from './armour.js';
9
+ import { WeaponMetrics, WeaponTotals } from './weapons.js';
10
+ import { AmmunitionCapacity } from './ammunition.js';
11
+ import { WeaponsCapacitorMetrics } from './weapons-capacitor.js';
12
+ import { DistributorMetrics } from './distributor.js';
13
+ import { MobilityMetrics } from './mobility.js';
14
+ import { ShieldRecovery, CellBankSummary } from './shield-recovery.js';
15
+ import { PreEngineeredVariant } from './pre-engineered.js';
16
+ import { SourcePurchaseRecord } from './source-purchase.js';
17
+ import { CalculationResult, FuelCapacity } from './loadout-calculations.js';
18
+ import { ModuleFitConstraint, LoadoutIssueParams, LoadoutValidation } from './loadout-validation.js';
20
19
  import './engineering-options.js';
21
20
  import './resistances.js';
21
+
22
+ /**
23
+ * Immutable fitted-module snapshots returned by {@link ShipLoadout}.
24
+ *
25
+ * @packageDocumentation
26
+ */
27
+
28
+ /**
29
+ * A point-in-time, deeply frozen view of the module fitted in one slot.
30
+ *
31
+ * The view is detached from its {@link ShipLoadout}: later edits do not change it, and
32
+ * mutating it throws. Fetch a new view with {@link ShipLoadout.fittedModuleAt} after a
33
+ * state-changing edit. All mutations live on `ShipLoadout`, keyed by {@link slot}; this
34
+ * avoids the stale-handle lifecycle that a live proxy would otherwise need.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
39
+ *
40
+ * declare const build: ShipLoadout;
41
+ *
42
+ * const before = build.fittedModuleAt('FrameShiftDrive')!;
43
+ * build.applyBlueprint(before.slot, 'FSD_LongRange', { grade: 5 });
44
+ * const after = build.fittedModuleAt(before.slot)!;
45
+ * before.engineering; // unchanged
46
+ * after.engineering; // the applied blueprint
47
+ * ```
48
+ */
49
+ interface FittedModule {
50
+ /** Slot key in the build's own spelling. */
51
+ readonly slot: string;
52
+ /** Frontier module symbol, e.g. `"Int_Hyperdrive_Size6_Class5"`. */
53
+ readonly symbol: string;
54
+ /** Whether the module was powered on, or `undefined` when unspecified. */
55
+ readonly on: boolean | undefined;
56
+ /** Zero-based power-priority group, or `undefined` when unspecified. */
57
+ readonly priority: number | undefined;
58
+ /** Module health in `[0, 1]`, or `undefined` when unspecified. */
59
+ readonly health: number | undefined;
60
+ /** Captured purchase value in credits, or `undefined` when unspecified. */
61
+ readonly value: number | undefined;
62
+ /** Applied engineering state; otherwise `undefined`. */
63
+ readonly engineering: ModuleEngineering | undefined;
64
+ /** Detached, journal-shaped fitted record. */
65
+ readonly raw: LoadoutModule;
66
+ /**
67
+ * Snapshotted fitted-article stats before its journal modifier block is folded, or
68
+ * `null` when unresolved.
69
+ *
70
+ * A stock or ordinarily engineered module exposes its base catalogue record. A fixed
71
+ * pre-engineered variant exposes its resolved article record here as well as through
72
+ * {@link effectiveStats}, so stats omitted by a journal capture still describe the
73
+ * article. Clearing or replacing that fixed engineering restores the stock record
74
+ * before applying the next recipe.
75
+ */
76
+ readonly stats: OutfittingModule | null;
77
+ /**
78
+ * Post-engineering module stats, or `null` when unresolved. For weapons, journal
79
+ * damage per second is resolved back to per-round damage and falloff is capped at
80
+ * maximum range. Exact damage components follow the engineered total and disappear
81
+ * when a damage conversion replaces them with a fractional distribution.
82
+ * A module engineered through {@link ShipLoadout.applyBlueprint} also retains
83
+ * recipe-only burst values that its journal-shaped modifier block does not serialize.
84
+ * A festive variant fitted through {@link ShipLoadout.setPreEngineeredVariant} uses
85
+ * its fixed modifier block.
86
+ */
87
+ readonly effectiveStats: OutfittingModule | null;
88
+ /** Fully rearmed ammunition capacity, or `null` for modules without ammunition. */
89
+ readonly ammunition: AmmunitionCapacity | null;
90
+ /** Identified fixed pre-engineered variant, or `null` when not uniquely identified. */
91
+ readonly preEngineeredVariant: PreEngineeredVariant | null;
92
+ }
93
+
94
+ /**
95
+ * Immutable slot snapshots returned by {@link ShipLoadout}.
96
+ *
97
+ * @packageDocumentation
98
+ */
99
+
100
+ /** Stable reason a hull mount cannot be emptied through {@link ShipLoadout.removeModule}. */
101
+ type ImmovableReason = 'cargoHatch' | 'moduleLimit' | 'requiredSlot';
102
+ /**
103
+ * A point-in-time, deeply frozen view of one hull mount.
104
+ *
105
+ * The view is detached from its {@link ShipLoadout}; after an edit that changes the
106
+ * build, call {@link ShipLoadout.slots} again for the current view. Mutations and candidate
107
+ * filtering stay on `ShipLoadout` and take the slot `key`, leaving this value serializable
108
+ * and free of lifecycle rules.
109
+ *
110
+ * @example
111
+ * Walking a build's mounts. Slot keys come from the game and are not derivable from
112
+ * position, so read the slot's `key` rather than composing one.
113
+ *
114
+ * ```ts
115
+ * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
116
+ * import type { LoadoutEvent } from '@elite-dangerous-almanac/core/ships/slef';
117
+ *
118
+ * declare const event: LoadoutEvent;
119
+ *
120
+ * // Figures below are one build's — a Federal Corvette.
121
+ * const build = ShipLoadout.fromLoadout(event);
122
+ *
123
+ * build.slots().length; // -> 38 every mount on the hull
124
+ * build.slots('hardpoint').length; // -> 7
125
+ *
126
+ * const first = build.slots('hardpoint')[0];
127
+ * first?.key; // -> 'HugeHardpoint1' what ShipLoadout.setModule takes
128
+ * first?.name; // -> 'Huge Hardpoint 1' what a UI shows
129
+ * first?.size; // -> 4
130
+ * first?.module?.symbol; // -> 'hpt_beamlaser_gimbal_huge'; undefined when the mount is empty
131
+ * ```
132
+ *
133
+ * @example
134
+ * The view is a snapshot, not a handle — re-read it after an edit.
135
+ *
136
+ * ```ts
137
+ * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
138
+ * import { HARDPOINT_MODULES } from '@elite-dangerous-almanac/core/ships/modules-hardpoint';
139
+ * import { getModuleBySymbol } from '@elite-dangerous-almanac/core/ships/modules';
140
+ *
141
+ * const build = ShipLoadout.empty('Sidewinder');
142
+ * const before = build.slots('hardpoint')[0];
143
+ * before?.key; // -> 'SmallHardpoint1'
144
+ * before?.module; // -> null
145
+ *
146
+ * const pulse = getModuleBySymbol('Hpt_PulseLaser_Fixed_Small', HARDPOINT_MODULES);
147
+ * if (pulse) build.setModule('SmallHardpoint1', pulse);
148
+ *
149
+ * before?.module; // -> still null — `before` describes the build as it was
150
+ * build.slots('hardpoint')[0]?.module?.symbol; // -> 'Hpt_PulseLaser_Fixed_Small'
151
+ * ```
152
+ */
153
+ type LoadoutSlot = BuildSlot & {
154
+ /** Human-readable label, e.g. `"Frame Shift Drive"`. */
155
+ readonly name: string;
156
+ /** Frozen fitted-module snapshot, or `null` when this mount is empty. */
157
+ readonly module: FittedModule | null;
158
+ /** Whether {@link ShipLoadout.removeModule} may empty this mount. */
159
+ readonly removable: boolean;
160
+ /**
161
+ * Machine-readable reason the mount cannot currently be emptied: `cargoHatch` for
162
+ * the built-in hatch, `requiredSlot` for a core or armour mount, or `moduleLimit`
163
+ * when removing a fitted allowance-increasing module would leave too many limited
164
+ * modules. Absent when {@link removable} is true.
165
+ */
166
+ readonly immovableReason?: ImmovableReason;
167
+ };
168
+
169
+ /**
170
+ * {@link ShipLoadout} — a mutable fitted-ship model that both **answers questions**
171
+ * about a build and **edits** it.
172
+ *
173
+ * Load one from a SLEF export (or a journal `Loadout` event) to read back the ship's
174
+ * identity, mass and fuel and ask for jump range and per-jump fuel; or start an
175
+ * {@link ShipLoadout.default | default} or {@link ShipLoadout.empty | empty} hull,
176
+ * enumerate its {@link ShipLoadout.slots | slots}, and
177
+ * {@link ShipLoadout.setModule | fit} and {@link ShipLoadout.removeModule | remove}
178
+ * modules. It composes the data-free pieces of this folder — the SLEF parser
179
+ * (`./slef`), the jump-range maths (`./jump-range`), the slot model (`./slots`), and
180
+ * the module and ship catalogues (each record carrying its own stats).
181
+ *
182
+ * Instances are **mutable**: `setModule`/`removeModule` change the build in place and
183
+ * return `this` for chaining. Values a SLEF export already computed (its
184
+ * `UnladenMass`, `FuelCapacity`, …) are trusted verbatim; for a build assembled from
185
+ * scratch those figures are computed from the fitted modules and the hull's stats.
186
+ * Editing an imported build adjusts the supplied aggregate figures by the changed
187
+ * module's contribution; when that contribution is unknown, the affected figure is
188
+ * discarded and recomputed rather than allowed to go stale.
189
+ * `setModule` snapshots the complete record it receives, including resolved
190
+ * pre-engineered or caller-supplied stats, so every later metric uses the article that
191
+ * was actually fitted rather than resolving its symbol back to a stock module.
192
+ * Slot and fitted-module queries return deeply frozen point-in-time values; edits are
193
+ * made only through this facade, then observed by querying again.
194
+ *
195
+ * **Slot keys are matched case-insensitively.** Frontier writes `FrameShiftDrive` and
196
+ * `LargeMiningHardpoint1`, but a SLEF producer may lower-case every key as the
197
+ * specification's own example does — Inara writes `frameshiftdrive` and
198
+ * `largemininghardpoint1` — and both spellings name the same mount, whether you are
199
+ * reading it or fitting into it. What a build already carries is never rewritten to
200
+ * match, so re-exporting an import returns the producer's own spelling untouched.
201
+ *
202
+ * @remarks
203
+ * This is the batteries-included ship facade: resolving arbitrary journal module
204
+ * ids and engineering recipes requires the complete ship/module, blueprint, and
205
+ * experimental-effect catalogues. Import `./slef`, `./jump-range`, or an individual
206
+ * module catalogue instead when you only need one data-free operation or one
207
+ * outfitting category.
208
+ *
209
+ * @example
210
+ * ```ts
211
+ * declare const slefJsonString: string;
212
+ *
213
+ * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
214
+ *
215
+ * // Read a build:
216
+ * const build = ShipLoadout.fromSlef(slefJsonString);
217
+ * build.maxJumpRange(); // -> 89.41 (best single jump, one jump's fuel, no cargo)
218
+ *
219
+ * // Assemble one:
220
+ * import { getModuleBySymbol } from '@elite-dangerous-almanac/core/ships/modules';
221
+ * import { CORE_MODULES } from '@elite-dangerous-almanac/core/ships/modules-core';
222
+ * const conda = ShipLoadout.empty('Anaconda');
223
+ * conda.setModule('FrameShiftDrive', getModuleBySymbol('Int_Hyperdrive_Size6_Class5', CORE_MODULES)!);
224
+ * conda.slots('optional'); // every optional mount, occupied or empty, with size
225
+ * ```
226
+ *
227
+ * @packageDocumentation
228
+ */
229
+
230
+ /**
231
+ * Stable machine-readable reason a {@link ShipLoadout} edit was refused. `immutableSlot`
232
+ * identifies a mount that cannot be changed at all; `requiredSlot` identifies a core or
233
+ * armour mount that must remain occupied. A required mount can be replaced but not
234
+ * emptied.
235
+ * The remaining codes identify an incompatible fit, a duplicate one-per-ship family,
236
+ * or a module count beyond the build's current allowance.
237
+ */
238
+ type LoadoutEditErrorCode = 'immutableSlot' | 'requiredSlot' | 'incompatibleModule' | 'duplicateExclusiveModule' | 'moduleLimitExceeded';
239
+ /**
240
+ * An editor request that the current hull or build constraints cannot accept.
241
+ *
242
+ * @remarks
243
+ * This remains a `TypeError`, so existing `instanceof TypeError` handling continues to
244
+ * work. Localized editors should switch on {@link code}, then use {@link constraint}
245
+ * and {@link params} instead of parsing the English fallback in `message`.
246
+ *
247
+ * @example
248
+ * ```ts
249
+ * import {
250
+ * LoadoutEditError,
251
+ * ShipLoadout,
252
+ * } from '@elite-dangerous-almanac/core/ships/ship-loadout';
253
+ * import { getModuleBySymbol } from '@elite-dangerous-almanac/core/ships/modules';
254
+ * import { CORE_MODULES } from '@elite-dangerous-almanac/core/ships/modules-core';
255
+ *
256
+ * const build = ShipLoadout.empty('SideWinder');
257
+ * const drive = getModuleBySymbol('Int_Hyperdrive_Size8_Class5', CORE_MODULES)!;
258
+ * try {
259
+ * build.setModule('FrameShiftDrive', drive);
260
+ * } catch (error) {
261
+ * if (error instanceof LoadoutEditError) error.constraint; // -> 'oversized'
262
+ * }
263
+ * ```
264
+ */
265
+ declare class LoadoutEditError extends TypeError {
266
+ /** Stable category for the refused edit. */
267
+ readonly code: LoadoutEditErrorCode;
268
+ /** More specific fitting rule for an `incompatibleModule` failure. */
269
+ readonly constraint?: ModuleFitConstraint;
270
+ /** Language-neutral values used by the English fallback message. */
271
+ readonly params: LoadoutIssueParams;
272
+ /**
273
+ * Construct a structured loadout-edit error.
274
+ *
275
+ * @param message - English fallback suitable for logs.
276
+ * @param code - Stable category for the refused edit.
277
+ * @param params - Language-neutral values used to render the failure.
278
+ * @param constraint - Specific fitting rule, when `code` is `incompatibleModule`.
279
+ */
280
+ constructor(message: string, code: LoadoutEditErrorCode, params: LoadoutIssueParams, constraint?: ModuleFitConstraint);
281
+ }
282
+ /** Result of {@link ShipLoadout.repairFixedMount}, discriminated by `status`. */
283
+ type FixedMountRepairResult = {
284
+ /** The hull default was installed. */
285
+ readonly status: 'repaired';
286
+ /** Slot key in the build's own spelling. */
287
+ readonly slot: string;
288
+ /** Installed module symbol. */
289
+ readonly symbol: string;
290
+ } | {
291
+ /** The fixed mount already held a valid module. */
292
+ readonly status: 'unchanged';
293
+ /** Slot key in the build's own spelling. */
294
+ readonly slot: string;
295
+ /** Current module symbol. */
296
+ readonly symbol: string;
297
+ } | {
298
+ /** No resolvable hull default exists for the fixed mount. */
299
+ readonly status: 'defaultUnavailable';
300
+ /** Slot key in the build's own spelling when one exists, otherwise the request. */
301
+ readonly slot: string;
302
+ /** Default module symbol, when the loadout names one but its stats are unavailable. */
303
+ readonly symbol?: string;
304
+ } | {
305
+ /** The requested mount is not fixed. */
306
+ readonly status: 'refused';
307
+ /** Slot key in the build's own spelling when one exists, otherwise the request. */
308
+ readonly slot: string;
309
+ /** Stable refusal reason. */
310
+ readonly reason: 'notFixedMount';
311
+ };
312
+ /** Optional mass overrides for a single calculation. */
313
+ interface JumpOptions {
314
+ /** Finite non-negative fuel load, in tonnes. Defaults to the full main tank. */
315
+ readonly fuel?: number;
316
+ /** Finite non-negative cargo load, in tonnes. Defaults to `0` (unladen). */
317
+ readonly cargo?: number;
318
+ }
319
+ /** Options for {@link ShipLoadout.applyBlueprint}. */
320
+ interface ApplyBlueprintOptions {
321
+ /** The blueprint grade, `1`–`5`. */
322
+ readonly grade: number;
323
+ /**
324
+ * The engineering system's shared quality roll, `0`–`1`. Defaults to `1`
325
+ * (best roll). A legacy-engineered module's independently advanced attributes cannot be
326
+ * reconstructed from its single reported quality; import its stated modifiers instead.
327
+ */
328
+ readonly quality?: number;
329
+ /** The experimental (special) effect's Frontier `fdname`, if any. */
330
+ readonly experimental?: string;
331
+ }
332
+ /** Stable reason {@link ShipLoadout.setExperimentalEffect} cannot perform an edit. */
333
+ type ExperimentalEffectMutationCode = 'emptySlot' | 'unknownModule' | 'notEngineered' | 'unknownExperimentalEffect' | 'unsupportedExperimentalEffect' | 'finalArticle' | 'unsupportedEngineering' | 'unidentifiedPreEngineeredVariant' | 'unresolvedModifiers';
334
+ /** Successful effect-only engineering edit. */
335
+ interface ExperimentalEffectUpdated {
336
+ /** Discriminator for an edit that changed the fitted module. */
337
+ readonly kind: 'updated';
338
+ /** Effect id before the edit, or `null` when none was present. */
339
+ readonly previousExperimental: string | null;
340
+ /** Effect id after the edit, or `null` when it was removed. */
341
+ readonly experimental: string | null;
342
+ }
343
+ /** Effect-only edit that requested the fitted module's current effect. */
344
+ interface ExperimentalEffectUnchanged {
345
+ /** Discriminator for a no-op edit. */
346
+ readonly kind: 'unchanged';
347
+ /** Current effect id, or `null` when none is present. */
348
+ readonly experimental: string | null;
349
+ }
350
+ /** Effect-only edit that cannot be performed losslessly. */
351
+ interface ExperimentalEffectUnsupported {
352
+ /** Discriminator for a refused edit. */
353
+ readonly kind: 'unsupported';
354
+ /** Stable machine-readable reason for the refusal. */
355
+ readonly code: ExperimentalEffectMutationCode;
356
+ /** Language-neutral values describing the fitted article and requested edit. */
357
+ readonly params: LoadoutIssueParams;
358
+ }
359
+ /** Result of {@link ShipLoadout.setExperimentalEffect}. */
360
+ type ExperimentalEffectMutationResult = ExperimentalEffectUpdated | ExperimentalEffectUnchanged | ExperimentalEffectUnsupported;
361
+ /** Stable reason {@link ShipLoadout.completeEngineeringGrade} cannot normalize a grade. */
362
+ type EngineeringNormalizationCode = 'emptySlot' | 'unknownModule' | 'notEngineered' | 'invalidQuality' | 'unknownExperimentalEffect' | 'unsupportedExperimentalEffect' | 'finalArticle' | 'unsupportedEngineering' | 'unidentifiedPreEngineeredVariant' | 'unresolvedModifiers';
363
+ /** Engineering state that already needs no normalization. */
364
+ interface EngineeringNormalizationUnchanged {
365
+ /** Discriminator for a no-op normalization. */
366
+ readonly kind: 'unchanged';
367
+ }
368
+ /** Engineering state recomputed at completed quality. */
369
+ interface EngineeringNormalized {
370
+ /** Discriminator for a successful normalization. */
371
+ readonly kind: 'normalized';
372
+ /** Quality reported by the fitted module before normalization, in `[0, 1)`. */
373
+ readonly previousQuality: number;
374
+ /** Completed engineering quality. Always `1`. */
375
+ readonly quality: 1;
376
+ }
377
+ /** Engineering state that cannot be normalized losslessly. */
378
+ interface EngineeringNormalizationUnsupported {
379
+ /** Discriminator for a refused normalization. */
380
+ readonly kind: 'unsupported';
381
+ /** Stable machine-readable reason for the refusal. */
382
+ readonly code: EngineeringNormalizationCode;
383
+ /** Language-neutral values describing the fitted engineering identity. */
384
+ readonly params: LoadoutIssueParams;
385
+ }
386
+ /** Result of {@link ShipLoadout.completeEngineeringGrade}. */
387
+ type EngineeringNormalizationResult = EngineeringNormalizationUnchanged | EngineeringNormalized | EngineeringNormalizationUnsupported;
388
+ /** Options for the defence figures a build reports. */
389
+ interface DefenceOptions {
390
+ /**
391
+ * Pips to the systems capacitor, `0`–`4`, folded into the shield resistances.
392
+ * Defaults to `0` for {@link ShipLoadout.shieldMetrics} and `4` for
393
+ * {@link ShipLoadout.shieldRecovery}.
394
+ */
395
+ readonly systemsPips?: number;
396
+ }
397
+ /** Optional load and ENG allocation for {@link ShipLoadout.mobilityMetrics}. */
398
+ interface MobilityOptions extends JumpOptions {
399
+ /** Pips assigned to the engines capacitor, `0`–`4`. Defaults to `4`. */
400
+ readonly enginesPips?: number;
401
+ }
402
+ /** A standard fuel-and-cargo condition shared by jump and mobility views. */
403
+ type StandardLoad = 'maximum' | 'unladen' | 'laden';
404
+ /** Fuel and cargo carried for a {@link StandardLoad}, both in tonnes. */
405
+ interface StandardLoadInputs {
406
+ /** Main-tank fuel carried, in tonnes. */
407
+ readonly fuel: number;
408
+ /** Cargo carried, in tonnes. */
409
+ readonly cargo: number;
410
+ }
411
+ /** Optional WEP allocation for {@link ShipLoadout.weaponsCapacitorMetrics}. */
412
+ interface WeaponsOptions {
413
+ /** Pips assigned to the weapons capacitor, `0`–`4`. Defaults to `4`. */
414
+ readonly weaponsPips?: number;
415
+ }
416
+ /** Optional SYS, ENG and WEP allocations for {@link ShipLoadout.distributorMetrics}. */
417
+ interface DistributorOptions {
418
+ /** Pips assigned to the systems capacitor, `0`–`4`. Defaults to `4`. */
419
+ readonly systemsPips?: number;
420
+ /** Pips assigned to the engines capacitor, `0`–`4`. Defaults to `4`. */
421
+ readonly enginesPips?: number;
422
+ /** Pips assigned to the weapons capacitor, `0`–`4`. Defaults to `4`. */
423
+ readonly weaponsPips?: number;
424
+ }
425
+ /**
426
+ * Retail catalogue credits for an assembled build.
427
+ *
428
+ * @see {@link ShipLoadout.mercCoinCost} for Mercenary purchase prices.
429
+ */
430
+ interface RetailCredits {
431
+ /** Bare hull list price in credits. */
432
+ readonly hull: number;
433
+ /** Sum of every priced fitted module, in credits. A lower bound when `unpriced` is non-empty. */
434
+ readonly modules: number;
435
+ /** Five percent of the priced hull and modules, truncated to credits. */
436
+ readonly rebuy: number;
437
+ /** Fitted modules that could not be priced from the catalogue. */
438
+ readonly unpriced: readonly {
439
+ readonly slot: string;
440
+ readonly symbol: string;
441
+ }[];
442
+ }
443
+ /** One fitted weapon and what it does, as {@link ShipLoadout.weaponMetrics} reports it. */
444
+ interface FittedWeaponMetrics {
445
+ /** The hardpoint's slot key, e.g. `"LargeHardpoint1"`. */
446
+ readonly slot: string;
447
+ /** The weapon's internal symbol. */
448
+ readonly symbol: string;
449
+ /** The weapon's display name, e.g. `"Multi-Cannon"`. */
450
+ readonly name: string;
451
+ /** Whether the weapon is switched on — a disabled weapon is excluded from the totals. */
452
+ readonly enabled: boolean;
453
+ /** What this weapon does per second, post-engineering. */
454
+ readonly metrics: WeaponMetrics;
455
+ /**
456
+ * How many rounds it holds when fully rearmed, post-engineering — `null` for a laser,
457
+ * which carries none. A capacity, not a rearm state: see {@link FittedModule.ammunition}.
458
+ */
459
+ readonly ammunition: AmmunitionCapacity | null;
460
+ /** Maximum effective range in metres, absent when the fitted weapon does not state one. */
461
+ readonly maximumRange?: number;
462
+ /** Damage-falloff start in metres, absent when the fitted weapon does not state one. */
463
+ readonly falloffRange?: number;
464
+ /**
465
+ * Exact projectile boundary metadata, absent when unavailable. These are not
466
+ * effective distances and remain separate from {@link maximumRange} and
467
+ * {@link falloffRange}.
468
+ */
469
+ readonly projectileRange?: ProjectileRangeBoundaries;
470
+ /** Armour-piercing rating, absent when unavailable. */
471
+ readonly armourPiercing?: number;
472
+ }
473
+ /** A build's firepower: every fitted weapon, and the totals across the enabled ones. */
474
+ interface BuildWeaponMetrics {
475
+ /**
476
+ * Every fitted weapon in hull slot order. Weapons in unknown or unmapped slots
477
+ * follow the known slots in their original source order.
478
+ */
479
+ readonly weapons: readonly FittedWeaponMetrics[];
480
+ /** The additive totals across the **enabled** weapons. */
481
+ readonly total: WeaponTotals;
482
+ }
483
+ /**
484
+ * A build's jump ranges at the loads that matter. The three single-jump values and
485
+ * each total result's `range` are in light-years.
486
+ */
487
+ interface JumpRangeSummary {
488
+ /**
489
+ * Best single jump: no cargo, and only one jump's fuel aboard — the figure the game
490
+ * and EDSY label "maximum jump range".
491
+ */
492
+ readonly max: number;
493
+ /** Single jump on a full tank with an empty hold. */
494
+ readonly unladen: number;
495
+ /** Single jump on a full tank with a full hold. */
496
+ readonly laden: number;
497
+ /** Summed range and jump count on one jump's fuel, empty hold. */
498
+ readonly totalMax: TotalRangeDetails;
499
+ /** Summed range and jump count on one full tank, empty hold. */
500
+ readonly totalUnladen: TotalRangeDetails;
501
+ /** Summed range and jump count on one full tank, full hold. */
502
+ readonly totalLaden: TotalRangeDetails;
503
+ }
504
+ /** A blueprint candidate for a module symbol, with its grades and availability route. */
505
+ interface AvailableBlueprint {
506
+ /** The blueprint's Frontier `fdname`, e.g. `"FSD_LongRange"`. */
507
+ readonly fdname: string;
508
+ /** The grades the blueprint offers, ascending (e.g. `[1, 2, 3, 4, 5]`). */
509
+ readonly grades: readonly number[];
510
+ /**
511
+ * Why the recipe is listed: `'ordinary'` for the stock module's engineering menu,
512
+ * or `'mercenary'` for a bespoke recipe attached to a Mercenary purchase.
513
+ *
514
+ * @remarks
515
+ * A Mercenary article shares its module symbol with the stock article. Its bespoke
516
+ * blueprint is nevertheless available only through that purchase, so a fitted module
517
+ * carrying the blueprint identifies the article even after a later grade upgrade.
518
+ * `'mercenary'` means the recipe requires that purchase route.
519
+ */
520
+ readonly route: 'ordinary' | 'mercenary';
521
+ }
522
+ /** How to shape a build on the way out — see {@link ShipLoadout.toLoadoutEvent}. */
523
+ interface LoadoutExportOptions {
524
+ /**
525
+ * Module order. `'fitted'` — the default — keeps the order the build carries: an
526
+ * import's own `Modules[]` order, or the order modules were fitted. `'slots'`
527
+ * re-orders into outfitting-panel order; a module in a slot the hull's layout does
528
+ * not describe keeps its relative position at the end rather than being dropped.
529
+ */
530
+ readonly moduleOrder?: 'fitted' | 'slots';
531
+ /**
532
+ * Write `On: true` / `Priority: 0` on modules that carry neither — as a journal
533
+ * always does and a build assembled here never does. Off by default, following
534
+ * SLEF's "require what is necessary, do not force the rest".
535
+ */
536
+ readonly explicitPower?: boolean;
537
+ /**
538
+ * Which credits to quote. `'retail'` — the default — prices the build from the
539
+ * catalogue: the bare hull's `hullCost`, every fitted module's list price, and a
540
+ * `Rebuy` of 5% of the two.
541
+ *
542
+ * `'source'` quotes the build's {@link ShipLoadout.sourcePurchase | source purchase
543
+ * record} instead — `HullValue`, `ModulesValue`, `Rebuy` and the per-module `Value`
544
+ * figures exactly as the capture stated them, and nothing where it stated nothing.
545
+ * An unedited capture therefore re-exports its own credits unchanged.
546
+ *
547
+ * Each captured figure is pinned to what it was paid for, so an edit narrows the
548
+ * export rather than staling it. A slot whose module has been swapped is left
549
+ * unpriced, because the figure was paid for the article that *was* fitted; and
550
+ * `ModulesValue` and `Rebuy` are dropped once any priced module has been swapped or
551
+ * removed, since they then cover an article no longer aboard. Removing a module the
552
+ * capture listed but never priced is the one case this cannot detect: only the
553
+ * capture ever knew which unpriced modules its total counted. The built-in cargo hatch
554
+ * is never purchasable, so normalising an unpriced or zero-priced captured hatch does
555
+ * not invalidate the totals. A non-zero captured hatch value is treated like any
556
+ * other priced replaced article.
557
+ *
558
+ * `HullValue` always stands: a captured hull figure names no slot, so no edit
559
+ * narrows it. Note that on a game capture it counts the hull *with* its stock
560
+ * fittings, and removing one of those leaves it overstating what is aboard.
561
+ *
562
+ * A build with no source record — one assembled here, or a capture that quoted no
563
+ * credits — exports no credit figure at all rather than falling back to retail.
564
+ */
565
+ readonly credits?: 'retail' | 'source';
566
+ }
567
+ /** As {@link LoadoutExportOptions}, plus the SLEF envelope — see {@link ShipLoadout.toSlef}. */
568
+ interface SlefExportOptions extends LoadoutExportOptions {
569
+ /**
570
+ * The envelope header identifying the exporting application.
571
+ *
572
+ * SLEF attribution belongs to the application producing the export, not to this
573
+ * calculation library, so callers must provide it.
574
+ */
575
+ readonly header: SlefHeader;
576
+ /** Spaces per indent for {@link ShipLoadout.toSlefString}. `0` (the default) is compact. */
577
+ readonly indent?: number;
578
+ }
579
+ /**
580
+ * A fitted ship — read a SLEF export, or assemble a hull from scratch.
581
+ *
582
+ * @remarks
583
+ * Jump calculations resolve the frame shift drive's constants from the drive's module
584
+ * record, applying any engineering the build carries (a Long Range blueprint's
585
+ * `FSDOptimalMass`, for instance). For a SLEF build, mass comes from the export's
586
+ * `UnladenMass`; for an assembled build it is the hull mass plus every fitted module's
587
+ * mass (armour defaults to the zero-mass lightweight alloy).
588
+ *
589
+ * @example
590
+ * Read a build a player already flies, and ask it what an outfitting screen shows.
591
+ * Every figure below is one build's — a Krait Phantom explorer. Figures the capture
592
+ * already stated — `unladenMass` here — are trusted verbatim; the rest are computed
593
+ * from the fit.
594
+ *
595
+ * ```ts
596
+ * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
597
+ * import type { LoadoutEvent } from '@elite-dangerous-almanac/core/ships/slef';
598
+ *
599
+ * // A `Loadout` line lifted from a player journal, parsed.
600
+ * declare const event: LoadoutEvent;
601
+ *
602
+ * const build = ShipLoadout.fromLoadout(event);
603
+ *
604
+ * build.shipSymbol; // -> 'krait_light'
605
+ * build.shipName; // -> 'Jenny Longuet'
606
+ * build.unladenMass; // -> 388.830017 (tonnes)
607
+ *
608
+ * build.maxJumpRange(); // -> 60.5478 (ly, best single jump)
609
+ * build.powerBudget().withinBudget; // -> true
610
+ * build.shieldMetrics()?.strength; // -> 743.12 (MJ)
611
+ * build.armourMetrics().hitPoints; // -> 307.8
612
+ * ```
613
+ *
614
+ * @example
615
+ * Assemble a hull instead. `empty` starts from the shipyard layout, `slots` enumerates
616
+ * the mounts, and `setModule` fits one — chainable, because the build is mutable.
617
+ *
618
+ * ```ts
619
+ * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
620
+ * import { getModuleBySymbol } from '@elite-dangerous-almanac/core/ships/modules';
621
+ * import { CORE_MODULES } from '@elite-dangerous-almanac/core/ships/modules-core';
622
+ *
623
+ * const conda = ShipLoadout.empty('Anaconda');
624
+ * conda.slots().length; // -> 39 (every mount, occupied or not)
625
+ * conda.slots('optional').length; // -> 14
626
+ * conda.validation.complete; // -> false (nothing fitted yet)
627
+ *
628
+ * const fsd = getModuleBySymbol('Int_Hyperdrive_Size6_Class5', CORE_MODULES);
629
+ * if (fsd) conda.setModule('FrameShiftDrive', fsd);
630
+ * ```
631
+ *
632
+ * @example
633
+ * Write a build back out. Retail credits are what the catalogue prices the fit at; pass
634
+ * `credits: 'source'` to export the figures a capture stated it paid instead — see
635
+ * {@link ShipLoadout.sourcePurchase}.
636
+ *
637
+ * ```ts
638
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
639
+ *
640
+ * declare const build: ShipLoadout;
641
+ *
642
+ * build.toLoadoutEvent(); // retail: hull cost plus every module's list price
643
+ * build.toLoadoutEvent({ credits: 'source' }); // the capture's own figures
644
+ * build.toSlefString({ header: { appName: 'MyApp', appVersion: '1.0.0' } });
645
+ * ```
646
+ */
647
+ declare class ShipLoadout {
648
+ #private;
649
+ private constructor();
650
+ /**
651
+ * Build from a SLEF export.
652
+ *
653
+ * @param input - The SLEF JSON string, or an already-parsed SLEF object (see
654
+ * {@link parseSlef} for accepted shapes).
655
+ * @param index - Which entry to take when the export holds several builds.
656
+ * Defaults to the first.
657
+ * @returns The loadout for that entry.
658
+ * @throws {SyntaxError} If `input` is a string that is not valid JSON.
659
+ * @throws {TypeError} If the export holds no usable loadout, `index` is out of range,
660
+ * or the selected entry names a hull absent from the catalogue.
661
+ */
662
+ static fromSlef(input: unknown, index?: number): ShipLoadout;
663
+ /**
664
+ * Build from a bare journal `Loadout` event (the `data` half of a SLEF entry).
665
+ *
666
+ * @param event - A `Loadout` event object.
667
+ * @returns The loadout.
668
+ * @remarks
669
+ * Capture/instance state (`timestamp`, `ShipID`, `HullHealth`, `Hot`) and engineering
670
+ * provenance (`Engineer`, `EngineerID`, `BlueprintID`) are deliberately excluded
671
+ * from the durable build. See {@link LoadoutEvent} and {@link ModuleEngineering}.
672
+ * A reward module is identified when its reported stat signature uniquely matches a
673
+ * catalogue variant. That reward's complete fixed stat block is then used as the fitted
674
+ * record, including values the capture omits; a separately applied experimental effect
675
+ * is included when matching and remains authoritative in the captured modifiers. A
676
+ * Mercenary module is instead identified by the bespoke blueprint available only to its
677
+ * purchase, including after a later grade upgrade. Its unpublished purchase modifiers
678
+ * are not inferred; the capture's current engineering remains authoritative. A fixed
679
+ * catalogue article can also be identified by its complete
680
+ * symbol/blueprint/grade/experimental identity when a SLEF capture omits the
681
+ * `Modifiers` key entirely. A present modifier array must pass the stat-signature
682
+ * match instead, so an older exported stock roll sharing that identity remains stock.
683
+ * A third-party export could also omit the array from an ordinary historical AX roll;
684
+ * that record is indistinguishable from the fixed reward, so the catalogue identity
685
+ * wins. Other under-specified or ambiguous evidence stays unidentified.
686
+ * An ordinary weapon recipe on a Guardian weapon identifies a final pre-engineered
687
+ * article; the import preserves that identity, uses the catalogue's complete hand-set
688
+ * stat block when the exact article is known, exposes no engineering options for it,
689
+ * and refuses attempts to engineer it further. Explicit journal modifiers remain
690
+ * authoritative over that stat block.
691
+ *
692
+ * The event's credit figures are kept twice over: as the live `hullValue` /
693
+ * `modulesValue` / `rebuy`, which an edit may invalidate, and as the immutable
694
+ * {@link sourcePurchase} record, which no edit touches.
695
+ *
696
+ * Modules are imported as one complete snapshot: their array order does not affect
697
+ * per-ship count allowances, and any aggregate violation is reported by
698
+ * {@link validation}. Use this factory rather than replaying a complete loadout
699
+ * through the incremental {@link setModule} editor.
700
+ * A known hull's non-removable cargo hatch is restored from its default loadout when
701
+ * the capture omits it or supplies an unresolved item for that mount. Because that
702
+ * hatch has zero mass, capacity and price, restoration preserves the build's live
703
+ * aggregate mass, capacity and credit figures. Source-credit export retains its
704
+ * totals only when the captured hatch was unpriced or valued at zero.
705
+ *
706
+ * @throws {TypeError} If the event is not shaped like one. What is checked is the
707
+ * structure a build is assembled from, and the types of the fields naming things in
708
+ * it: `event` must be an object with an array of module objects in `Modules`; each
709
+ * module needs a string `Slot` and `Item`, and no two may claim the same slot; a
710
+ * module's `Engineering` must be an object, and that block's `Modifiers` an array of
711
+ * objects each carrying a string `Label`, whenever their key is there **at all**;
712
+ * `event.Ship` must name a known hull; the block's `BlueprintName` and its
713
+ * `ExperimentalEffect` must be strings **when they carry a value**. Every remaining
714
+ * field — every number, every
715
+ * flag, a modifier's value beside its label — is trusted, so use
716
+ * {@link ShipLoadout.fromSlef} (or {@link parseSlef}) for input you did not produce,
717
+ * which reports all of them.
718
+ *
719
+ * A modifier's `Label` is required rather than checked-when-present because it is
720
+ * the only thing saying which stat moved: {@link fittedModuleAt} and the
721
+ * pre-engineered identification both read it unconditionally, so an entry without
722
+ * one would import cleanly and then break the build it produced.
723
+ *
724
+ * `Ship`, `Engineering` and `Modifiers` are fields where `null` is not an omission:
725
+ * `Ship` is required, while a relay writing `null` for an optional block or list it
726
+ * does not have would otherwise be read as one. A partial `Engineering` block is
727
+ * accepted because a capture may state modifiers without naming the recipe.
728
+ */
729
+ static fromLoadout(event: LoadoutEvent): ShipLoadout;
730
+ /**
731
+ * Start a new build for a hull with no editable modules fitted.
732
+ *
733
+ * @param shipSymbol - The hull's internal symbol, e.g. `"Anaconda"`
734
+ * (case-insensitive).
735
+ * @returns An otherwise empty loadout whose {@link slots} come from the hull's
736
+ * declared layout. The hull's immutable default cargo hatch is fitted because it
737
+ * is part of the ship rather than an outfitting choice.
738
+ * @throws {TypeError} If `shipSymbol` is not a string, or no hull with that symbol
739
+ * has a known slot layout.
740
+ * @example
741
+ * ```ts
742
+ * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
743
+ *
744
+ * ShipLoadout.empty('Sidewinder').slots('hardpoint').length; // -> 2
745
+ * ```
746
+ */
747
+ static empty(shipSymbol: string): ShipLoadout;
748
+ /**
749
+ * Start a new build with the modules supplied on a stock ship.
750
+ *
751
+ * @param shipSymbol - The hull's internal symbol, e.g. `"SideWinder"`
752
+ * (case-insensitive).
753
+ * @returns A complete, ready-to-edit stock loadout. The build is independent of the
754
+ * frozen shared catalogue: edits affect this instance only.
755
+ * @throws {TypeError} If `shipSymbol` is not a string, or no default loadout exists
756
+ * for that hull.
757
+ * @remarks
758
+ * This batteries-included factory resolves calculations through the complete module
759
+ * catalogue already used by `ShipLoadout`. If only the stock slot/module identities
760
+ * are needed, `getDefaultLoadout` from `./default-loadouts` avoids that cost.
761
+ * @example
762
+ * ```ts
763
+ * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
764
+ *
765
+ * const stock = ShipLoadout.default('SideWinder');
766
+ * stock.validation.complete; // -> true
767
+ * stock.fittedModuleAt('FrameShiftDrive')?.symbol;
768
+ * // -> 'Int_Hyperdrive_Size2_Class1'
769
+ * ```
770
+ */
771
+ static default(shipSymbol: string): ShipLoadout;
772
+ /** The hull's internal id, e.g. `"explorer_nx"`. */
773
+ get shipSymbol(): string;
774
+ /** The player-given ship name, or `null` if the build has none. */
775
+ get shipName(): string | null;
776
+ /** The player-given ID plate, or `null` if the build has none. */
777
+ get shipIdent(): string | null;
778
+ /**
779
+ * Hull + modules mass with an empty tank and no cargo, in tonnes, or `null` if it
780
+ * cannot be determined (no `UnladenMass` in the export and a fitted module has no
781
+ * known mass).
782
+ *
783
+ * @remarks
784
+ * A SLEF export's `UnladenMass` is trusted verbatim. Otherwise the mass is the
785
+ * hull's `hullMass` plus every fitted module's mass (post-engineering), with armour
786
+ * at the zero-mass lightweight default.
787
+ */
788
+ get unladenMass(): number | null;
789
+ /**
790
+ * Unladen mass with diagnostics for every missing input.
791
+ *
792
+ * @returns A complete imported or computed mass, otherwise `null` plus the module
793
+ * fields that prevented the calculation.
794
+ */
795
+ get unladenMassResult(): CalculationResult<number>;
796
+ /**
797
+ * Fuel-tank capacities, in tonnes, or `null` when a tank's capacity is unknown. A
798
+ * SLEF export's `FuelCapacity` is used when present;
799
+ * otherwise the main capacity is the sum of the fitted fuel tanks and the reserve
800
+ * comes from the hull's stats.
801
+ */
802
+ get fuelCapacity(): FuelCapacity | null;
803
+ /** Fuel capacity with diagnostics instead of unknown tanks collapsing to zero. */
804
+ get fuelCapacityResult(): CalculationResult<FuelCapacity>;
805
+ /**
806
+ * Cargo capacity, in tonnes, or `null` when a fitted optional module cannot be
807
+ * classified. A SLEF export's `CargoCapacity` is used when present; otherwise it is
808
+ * the sum of the fitted cargo racks.
809
+ */
810
+ get cargoCapacity(): number | null;
811
+ /** Cargo capacity with diagnostics instead of unknown racks collapsing to zero. */
812
+ get cargoCapacityResult(): CalculationResult<number>;
813
+ /**
814
+ * Hull cost in credits represented by the build, or `null` if unknown.
815
+ *
816
+ * @remarks
817
+ * This is the live figure, kept coherent with edits: an import's own `HullValue`
818
+ * until something invalidates it. For the capture's figure as captured — which no
819
+ * edit changes — read {@link sourcePurchase}.
820
+ */
821
+ get hullValue(): number | null;
822
+ /**
823
+ * Fitted-modules cost in credits represented by the build, or `null` if
824
+ * unknown — including after an edit discarded an import's figure, since no catalogue
825
+ * records what a replaced module was bought for. {@link sourcePurchase} keeps the
826
+ * captured figure regardless.
827
+ */
828
+ get modulesValue(): number | null;
829
+ /**
830
+ * Insurance rebuy cost in credits represented by the build, or `null` if
831
+ * unknown. Discarded by an edit for the same reason as {@link modulesValue}, and
832
+ * likewise preserved by {@link sourcePurchase}.
833
+ */
834
+ get rebuy(): number | null;
835
+ /**
836
+ * What the capture this build came from said was **paid** for it — a read-only
837
+ * {@link SourcePurchaseRecord}, or `null` for a build assembled here or imported
838
+ * from a capture that quoted no credits at all.
839
+ *
840
+ * @remarks
841
+ * The record is provenance about the source, so it is fixed at import and **survives
842
+ * every edit**: fit, remove or engineer whatever you like and it still reports the
843
+ * figures the capture carried, for the modules the capture carried them for. That is
844
+ * what {@link hullValue}, {@link modulesValue} and {@link rebuy} cannot do — they
845
+ * describe the build in hand, so an edit that invalidates one drops it.
846
+ *
847
+ * The two answer different questions and neither substitutes for the other. A
848
+ * captured price belongs to one commander's purchase history, discounts included;
849
+ * the library's own figures are catalogue retail. Export picks between them
850
+ * explicitly, and quotes retail unless asked otherwise — see
851
+ * {@link LoadoutExportOptions.credits}.
852
+ *
853
+ * @example
854
+ * ```ts
855
+ * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
856
+ * import { getSourceModuleValue } from '@elite-dangerous-almanac/core/ships/source-purchase';
857
+ *
858
+ * declare const slefJson: string;
859
+ *
860
+ * const build = ShipLoadout.fromSlef(slefJson);
861
+ * const paid = build.sourcePurchase!;
862
+ * paid.hullValue; // -> 189326510, as captured
863
+ * getSourceModuleValue(paid, 'powerplant')?.value; // -> what that plant cost its owner
864
+ *
865
+ * build.removeModule('Slot05_Size4');
866
+ * build.modulesValue; // -> null unavailable after the edit
867
+ * paid.modulesValue; // -> 192625195, the captured figure
868
+ * ```
869
+ */
870
+ get sourcePurchase(): SourcePurchaseRecord | null;
871
+ /**
872
+ * Structural validity and operational completeness of this build.
873
+ *
874
+ * @remarks
875
+ * Optional, hardpoint and utility mounts may be empty. Armour and all seven core
876
+ * mounts must be filled for `complete` to be true. An unknown module is incomplete;
877
+ * a module in a nonexistent or incompatible slot is invalid. Exclusive
878
+ * families and per-ship module-count allowances must also be satisfied.
879
+ */
880
+ get validation(): LoadoutValidation;
881
+ /**
882
+ * Frozen point-in-time views of the hull's mounts in outfitting-panel order.
883
+ *
884
+ * @param kind - Optionally keep only one mount kind. Omit it for every mount.
885
+ * @returns Detached, frozen slot views. Repeated reads for the same `kind` reuse the
886
+ * same snapshots until a state-changing edit.
887
+ * @example
888
+ * ```ts
889
+ * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
890
+ *
891
+ * const emptyHardpoints = ShipLoadout.empty('Sidewinder').slots('hardpoint');
892
+ * emptyHardpoints.every((slot) => slot.module === null); // true
893
+ * ```
894
+ */
895
+ slots(kind?: SlotKind): readonly LoadoutSlot[];
896
+ /**
897
+ * A deeply frozen, point-in-time view of the module in a slot.
898
+ *
899
+ * @param slotKey - Slot key, matched case-insensitively.
900
+ * @returns A detached, frozen view, or `null` when the slot is empty or unknown.
901
+ * @throws {TypeError} If `slotKey` is not a string.
902
+ */
903
+ fittedModuleAt(slotKey: string): FittedModule | null;
904
+ /**
905
+ * Every fitted module as a deeply frozen point-in-time view.
906
+ *
907
+ * @returns Detached module snapshots in the order the build carries them. The array
908
+ * and every nested record are frozen; query again after an edit for current state.
909
+ * @example
910
+ * ```ts
911
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
912
+ *
913
+ * declare const build: ShipLoadout;
914
+ *
915
+ * build.fittedModules().map((module) => `${module.slot}: ${module.symbol}`);
916
+ * ```
917
+ */
918
+ fittedModules(): readonly FittedModule[];
919
+ /**
920
+ * Return the computable blueprint candidates for a fitted module symbol.
921
+ *
922
+ * @param slotKey - Slot key, matched case-insensitively.
923
+ * @returns Frozen blueprint descriptors: the ordinary engineering menu first, then
924
+ * bespoke Mercenary upgrade recipes. An `'ordinary'` candidate is available to the
925
+ * stock module; a `'mercenary'` candidate is purchase-specific. Applying that bespoke
926
+ * blueprint identifies the matching Mercenary article even though its bare module
927
+ * symbol does not.
928
+ * Returns an empty array when the slot is empty, unresolved or final, or the module
929
+ * symbol has neither route.
930
+ * @throws {TypeError} If `slotKey` is not a string.
931
+ * @example
932
+ * ```ts
933
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
934
+ *
935
+ * declare const build: ShipLoadout;
936
+ *
937
+ * build.availableBlueprints('FrameShiftDrive').map(({ fdname }) => fdname);
938
+ * ```
939
+ */
940
+ availableBlueprints(slotKey: string): readonly AvailableBlueprint[];
941
+ /**
942
+ * Return the computable experimental effects offered to a fitted module.
943
+ *
944
+ * @param slotKey - Slot key, matched case-insensitively.
945
+ * @returns Frozen Frontier effect ids in engineering-menu order, or an empty array
946
+ * when the slot is empty, unresolved, final, or has no experimental menu.
947
+ * @throws {TypeError} If `slotKey` is not a string.
948
+ * @example
949
+ * ```ts
950
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
951
+ *
952
+ * declare const build: ShipLoadout;
953
+ *
954
+ * build.availableExperimentalEffects('FrameShiftDrive');
955
+ * // -> ['special_fsd_heavy', ...]
956
+ * ```
957
+ */
958
+ availableExperimentalEffects(slotKey: string): readonly string[];
959
+ /**
960
+ * The modules that fit a given slot — its size, kind and any restriction all
961
+ * satisfied, with candidates that would worsen a one-per-ship or module-count limit
962
+ * omitted.
963
+ *
964
+ * @param slotKey - The slot key to fit, matched case-insensitively (journal spelling).
965
+ * @returns The fitting modules, in complete-catalogue order.
966
+ * @throws {RangeError} If the hull has no slot with that key.
967
+ * @throws {TypeError} If `slotKey` is not a string.
968
+ * @example
969
+ * ```ts
970
+ * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
971
+ *
972
+ * ShipLoadout.empty('Anaconda').modulesForSlot('FrameShiftDrive');
973
+ * ```
974
+ */
975
+ modulesForSlot(slotKey: string): OutfittingModule[];
976
+ /**
977
+ * Restore a missing, unresolved or invalid fixed mount from this hull's stock loadout.
978
+ *
979
+ * @remarks
980
+ * This is the narrow repair path for mounts that {@link setModule} deliberately does
981
+ * not expose as ordinary edits, including the built-in cargo hatch. Live aggregates
982
+ * are updated by the same rules as every package-owned refit. The immutable
983
+ * {@link sourcePurchase} record is unchanged; source-credit export leaves a replaced
984
+ * slot unpriced, while its aggregate totals remain valid for an unpriced or
985
+ * zero-priced cargo hatch. Resolved valid core and armour alternatives are left
986
+ * unchanged.
987
+ *
988
+ * @param slotKey - Fixed slot key, matched case-insensitively.
989
+ * @returns A frozen {@link FixedMountRepairResult}. Refusals leave the build unchanged.
990
+ * @throws {TypeError} If `slotKey` is not a string.
991
+ * @throws {RangeError} If a known hull has no slot with that key.
992
+ * @example
993
+ * ```ts
994
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
995
+ *
996
+ * declare const imported: ShipLoadout;
997
+ * const result = imported.repairFixedMount('CargoHatch');
998
+ * if (result.status === 'repaired') console.log(result.symbol.toLowerCase());
999
+ * ```
1000
+ */
1001
+ repairFixedMount(slotKey: string): FixedMountRepairResult;
1002
+ /**
1003
+ * Fit a module into a slot, replacing whatever is there.
1004
+ *
1005
+ * @remarks
1006
+ * This is an incremental editor: every call must avoid worsening the current
1007
+ * build's module-count excess. Fit an allowance-increasing module before the weapons
1008
+ * it permits. To consume a complete order-independent snapshot, use
1009
+ * {@link ShipLoadout.fromLoadout}.
1010
+ *
1011
+ * @param slotKey - The slot key to fit into, matched case-insensitively (journal
1012
+ * spelling). An occupied slot keeps the key the build already spells it with, so
1013
+ * fitting into an import never renames one of its mounts.
1014
+ * @param module - The module to fit (resolve it from a catalogue first, e.g. with
1015
+ * {@link getModuleBySymbol}). The complete record is snapshotted, so a result from
1016
+ * `getPreEngineeredStats` or a caller-supplied catalogue keeps its resolved stats.
1017
+ * @returns `this`, for chaining.
1018
+ * @throws {RangeError} If the hull has no slot with that key.
1019
+ * @throws {TypeError} If `slotKey` is not a string; `module` is null/undefined (e.g. a
1020
+ * `getModuleBySymbol` miss) or is not an outfitting module at all.
1021
+ * @throws {LoadoutEditError} If the module does not fit the slot (wrong kind, too
1022
+ * large, or a restriction the module does not satisfy), conflicts with a one-per-ship
1023
+ * family already fitted elsewhere, or worsens a per-ship module-count excess.
1024
+ * @example
1025
+ * ```ts
1026
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1027
+ *
1028
+ * declare const build: ShipLoadout;
1029
+ *
1030
+ * import { getModuleBySymbol } from '@elite-dangerous-almanac/core/ships/modules';
1031
+ * import { CORE_MODULES } from '@elite-dangerous-almanac/core/ships/modules-core';
1032
+ * const fsd = getModuleBySymbol('Int_Hyperdrive_Size6_Class5', CORE_MODULES)!;
1033
+ * const tank = getModuleBySymbol('Int_FuelTank_Size6_Class3', CORE_MODULES)!;
1034
+ * build.setModule('FrameShiftDrive', fsd).setModule('Slot01_Size7', tank);
1035
+ * ```
1036
+ */
1037
+ setModule(slotKey: string, module: OutfittingModule): this;
1038
+ /**
1039
+ * Empty a slot.
1040
+ *
1041
+ * @param slotKey - The slot key to clear, matched case-insensitively (journal
1042
+ * spelling).
1043
+ * @returns `this`, for chaining. Clearing an already-empty removable slot is a no-op.
1044
+ * @throws {TypeError} If `slotKey` is not a string.
1045
+ * @throws {LoadoutEditError} If the slot is the built-in cargo hatch; is a required
1046
+ * core or armour mount; or removing the module would worsen a per-ship module-count
1047
+ * excess. Required mounts may be replaced with {@link setModule} but cannot be
1048
+ * emptied.
1049
+ */
1050
+ removeModule(slotKey: string): this;
1051
+ /**
1052
+ * Engineer the module in a slot — apply a blueprint (with a grade and quality) and
1053
+ * an optional experimental effect, computing the resulting stat modifiers.
1054
+ *
1055
+ * The modifiers are stored with journal-equivalent labels and numeric values on the
1056
+ * fitted module, so the build's jump-range and mass calculations pick them up
1057
+ * automatically. The optional journal display-direction hint `LessIsGood` is omitted.
1058
+ * The block keeps the `BlueprintName` you passed, so it reads back the way the build
1059
+ * declared it. Values use Frontier's float32 arithmetic, and weapon recipe internals
1060
+ * such as `BurstInterval` are exposed as the derived `RateOfFire` and
1061
+ * `DamagePerSecond` labels a journal writes. Module-specific aliases likewise use the
1062
+ * journal spelling (`MaximumRange` for a module's maximum range and `Range` for a
1063
+ * scanner range).
1064
+ * Recipe-only values remain available through {@link FittedModule.effectiveStats} and
1065
+ * build calculations even though a journal does not serialize their labels; this is
1066
+ * what keeps burst and reload-cycle calculations faithful after applying a recipe.
1067
+ *
1068
+ * **Which recipe an id names can depend on the module.** The game writes
1069
+ * `Sensor_LongRange` and `Sensor_WideAngle` for both a sensor suite's modification and a
1070
+ * utility scanner's, and the two roll different stats in opposite directions — Long
1071
+ * Range costs the suite mass and the scanner power draw. So the id is resolved against
1072
+ * the module's menu before anything is computed, and a wake scanner engineered
1073
+ * `Sensor_LongRange` gets the scanner's numbers, which `BLUEPRINTS` keys
1074
+ * `Scanner_LongRange`. Reading a stored block back the same way means resolving it the
1075
+ * same way: `resolveBlueprintForModule` in `ships/blueprint-journal` is that lookup.
1076
+ *
1077
+ * @param slotKey - The slot whose module to engineer, matched case-insensitively
1078
+ * (journal spelling).
1079
+ * @param fdname - The blueprint recipe's Frontier `fdname`, e.g. `"FSD_LongRange"`.
1080
+ * @param options - {@link ApplyBlueprintOptions}: `grade` (1–5), optional `quality`
1081
+ * (0–1, default 1), and optional `experimental` effect `fdname`. A nullish
1082
+ * `experimental` means no effect, the same as leaving it out. Each is read once,
1083
+ * before anything is checked, so an accessor cannot answer the check and the use
1084
+ * differently.
1085
+ * @returns `this`, for chaining.
1086
+ * @throws {RangeError} If the slot is empty, or the blueprint/grade/experimental is
1087
+ * unknown, or `quality` is outside `[0, 1]`.
1088
+ * @throws {TypeError} If `slotKey` or `fdname` is not a string, `options` is not an
1089
+ * object, or `options.experimental` carries a value that is not a string — a nullish
1090
+ * one is no effect, not a wrong type; the fitted module has no stats to engineer; or the id names a
1091
+ * fixed event-reward identity, which names no craftable recipe; or the module is not offered the blueprint — by
1092
+ * its engineering menu, by the journal spelling of an entry on that menu, by the
1093
+ * generic spelling of a recipe that menu lists under a family's name, or by being a
1094
+ * Mercenary article sold at grade 1 with that bespoke recipe; the fitted article is
1095
+ * final and accepts no further engineering;
1096
+ * or the module is not offered the experimental effect, which its
1097
+ * menu alone decides; or the catalogue does not carry every base stat the recipe
1098
+ * modifies. Incomplete engineering is rejected rather than stored as a partial journal
1099
+ * modifier block.
1100
+ * @example
1101
+ * ```ts
1102
+ * import { getModuleBySymbol } from '@elite-dangerous-almanac/core/ships/modules';
1103
+ * import { CORE_MODULES } from '@elite-dangerous-almanac/core/ships/modules-core';
1104
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1105
+ *
1106
+ * declare const build: ShipLoadout;
1107
+ *
1108
+ * const fsd = getModuleBySymbol('Int_Hyperdrive_Size6_Class5', CORE_MODULES)!;
1109
+ *
1110
+ * build.setModule('FrameShiftDrive', fsd)
1111
+ * .applyBlueprint('FrameShiftDrive', 'FSD_LongRange', {
1112
+ * grade: 5,
1113
+ * experimental: 'special_fsd_heavy',
1114
+ * });
1115
+ * build.maxJumpRange(); // uses the engineered optimal mass
1116
+ * ```
1117
+ */
1118
+ applyBlueprint(slotKey: string, fdname: string, options: ApplyBlueprintOptions): this;
1119
+ /**
1120
+ * Add, replace or remove only the fitted module's experimental effect.
1121
+ *
1122
+ * Ordinary and Mercenary engineering is recomputed at its current blueprint, grade
1123
+ * and quality. A fixed reward instead retains its hand-authored modifiers and
1124
+ * purchase identity while the requested effect is composed with them. Refused edits
1125
+ * leave the build unchanged and return stable structured data.
1126
+ *
1127
+ * @param slotKey - The engineered slot, matched case-insensitively.
1128
+ * @param experimental - Experimental-effect `fdname`, or `null` to remove the effect.
1129
+ * @returns A frozen result identifying an update, no-op or lossless refusal.
1130
+ * @throws {TypeError} If `slotKey` or a non-null `experimental` is not a string.
1131
+ * @example
1132
+ * ```ts
1133
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1134
+ *
1135
+ * declare const build: ShipLoadout;
1136
+ *
1137
+ * build.setExperimentalEffect('FrameShiftDrive', 'special_fsd_heavy');
1138
+ * build.setExperimentalEffect('FrameShiftDrive', null);
1139
+ * ```
1140
+ */
1141
+ setExperimentalEffect(slotKey: string, experimental: string | null): ExperimentalEffectMutationResult;
1142
+ /**
1143
+ * Recompute the fitted module's current engineering identity at quality `1`.
1144
+ *
1145
+ * Imported modifier values remain authoritative until this method is called. An
1146
+ * ordinary or Mercenary recipe is rerolled through the package calculator; a fixed
1147
+ * reward rebuilds its hand-authored modifiers and optional effect without losing its
1148
+ * purchase identity. A refusal never changes the loadout.
1149
+ *
1150
+ * @param slotKey - The engineered slot, matched case-insensitively.
1151
+ * @returns A frozen result identifying a normalized, unchanged or unsupported state.
1152
+ * @throws {TypeError} If `slotKey` is not a string.
1153
+ * @example
1154
+ * ```ts
1155
+ * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1156
+ *
1157
+ * const partial = ShipLoadout.default('SideWinder').applyBlueprint(
1158
+ * 'FrameShiftDrive',
1159
+ * 'FSD_LongRange',
1160
+ * { grade: 5, quality: 0.42 },
1161
+ * );
1162
+ * const imported = ShipLoadout.fromLoadout(partial.toLoadoutEvent());
1163
+ * imported.completeEngineeringGrade('FrameShiftDrive').kind; // -> 'normalized'
1164
+ * imported.fittedModuleAt('FrameShiftDrive')?.engineering?.Quality; // -> 1
1165
+ * ```
1166
+ */
1167
+ completeEngineeringGrade(slotKey: string): EngineeringNormalizationResult;
1168
+ /**
1169
+ * Fit a pre-engineered variant into a slot, replacing whatever is there.
1170
+ *
1171
+ * The variant's fixed stats and journal engineering block are resolved together.
1172
+ * Articles carry `Level`, `Quality: 1`, any baked experimental effect and their fixed
1173
+ * modifiers. Because the variant names its base module, a decorative identity cannot
1174
+ * be applied to an unrelated damage-bearing module.
1175
+ * A Mercenary variant whose fixed modifier block has not been published retains the
1176
+ * stock catalogue stats and omits `Modifiers` rather than claiming it changes none.
1177
+ *
1178
+ * @param slotKey - The slot key to fit into, matched case-insensitively.
1179
+ * @param variant - The pre-engineered catalogue variant to fit.
1180
+ * @returns `this`, for chaining.
1181
+ * @throws {TypeError} If `variant` is not a pre-engineered variant or one of its
1182
+ * authored modifier labels cannot be resolved for its base module.
1183
+ * @throws {RangeError} If no catalogue row matches the supplied module, blueprint,
1184
+ * grade, experimental effect and acquisition route.
1185
+ * @throws {LoadoutEditError} If the variant's base module does not fit the slot or
1186
+ * violates a fitted-module limit.
1187
+ * @example
1188
+ * ```ts
1189
+ * import { getPreEngineeredVariants } from '@elite-dangerous-almanac/core/ships/pre-engineered';
1190
+ * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1191
+ *
1192
+ * const festive = getPreEngineeredVariants('Hpt_FlakMortar_Turret_Medium')
1193
+ * .find((variant) => variant.blueprint === 'Decorative_Red')!;
1194
+ * const build = ShipLoadout.empty('Krait_MkII')
1195
+ * .setPreEngineeredVariant('MediumHardpoint1', festive);
1196
+ * build.fittedModuleAt('MediumHardpoint1')?.effectiveStats?.damage; // -> 0.34
1197
+ * ```
1198
+ */
1199
+ setPreEngineeredVariant(slotKey: string, variant: PreEngineeredVariant): this;
1200
+ /**
1201
+ * Strip engineering from a slot's module,
1202
+ * restoring its base stats.
1203
+ *
1204
+ * @param slotKey - The slot to de-engineer, matched case-insensitively (journal
1205
+ * spelling).
1206
+ * @returns `this`, for chaining. A no-op if the slot is empty or unmodified.
1207
+ * @throws {TypeError} If `slotKey` is not a string, or the fitted article is final
1208
+ * pre-engineered and its baked engineering cannot be removed.
1209
+ * @remarks
1210
+ * Clearing a Mercenary article removes its purchase-exclusive blueprint identity.
1211
+ * Its {@link FittedModule.preEngineeredVariant} then reads `null`.
1212
+ */
1213
+ clearEngineering(slotKey: string): this;
1214
+ /**
1215
+ * Switch a fitted module on or off.
1216
+ *
1217
+ * @param slotKey - The slot's journal key, e.g. `"PowerPlant"`, matched
1218
+ * case-insensitively.
1219
+ * @param on - `true` to power it, `false` to switch it off.
1220
+ * @returns `this`, for chaining.
1221
+ * @throws {RangeError} If the slot is empty.
1222
+ * @throws {TypeError} If `slotKey` is not a string.
1223
+ * @example
1224
+ * ```ts
1225
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1226
+ *
1227
+ * declare const build: ShipLoadout;
1228
+ *
1229
+ * build.setModuleEnabled('TinyHardpoint6', false); // an unpowered heat sink
1230
+ * ```
1231
+ */
1232
+ setModuleEnabled(slotKey: string, on: boolean): this;
1233
+ /**
1234
+ * Set a fitted module's power-priority group.
1235
+ *
1236
+ * @param slotKey - The slot's journal key, matched case-insensitively.
1237
+ * @param priority - The journal's **zero-based** group, `0`–`4`. Note that the
1238
+ * outfitting panel — and {@link powerBudget}'s `bands[].priority` — number the same
1239
+ * five groups `1`–`5`.
1240
+ * @returns `this`, for chaining.
1241
+ * @throws {RangeError} If the slot is empty, or `priority` is not an integer in `[0, 4]`.
1242
+ * @throws {TypeError} If `slotKey` is not a string.
1243
+ */
1244
+ setModulePriority(slotKey: string, priority: number): this;
1245
+ /**
1246
+ * This build as a journal `Loadout` event — the `data` half of a SLEF entry.
1247
+ *
1248
+ * @param options - Module ordering and how sparse to be about power state.
1249
+ * @returns A fresh event. Every top-level figure is **recomputed** from the hull and
1250
+ * the fitted modules rather than echoed from whatever an import supplied — the one
1251
+ * exception being the credits, when `credits: 'source'` asks for the capture's own.
1252
+ * Any figure that cannot be worked out is **left out** rather than emitted as a stale
1253
+ * or zero value — SLEF requires nothing beyond `Ship` and `Modules`.
1254
+ *
1255
+ * Credits are quoted at **retail** by default: the bare hull's `hullCost` plus every
1256
+ * fitted module's catalogue list price, with `Rebuy` 5% of the two. A source's own
1257
+ * `HullValue` / `ModulesValue` / `Value` figures are deliberately not quoted here,
1258
+ * because they record one commander's purchase history — the Deep Black's modules
1259
+ * are all 12.25% off list — and purchase discounts are not a property of the build.
1260
+ * They are not lost either: pass `credits: 'source'` to export the
1261
+ * {@link sourcePurchase} record instead, as provenance rather than as a price.
1262
+ * @example
1263
+ * ```ts
1264
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1265
+ *
1266
+ * declare const build: ShipLoadout;
1267
+ *
1268
+ * const event = build.toLoadoutEvent();
1269
+ * event.MaxJumpRange; // recomputed, not the exporter's claim
1270
+ * event.HullValue; // the catalogue's list price
1271
+ *
1272
+ * build.toLoadoutEvent({ credits: 'source' }).HullValue; // what the capture paid
1273
+ * ```
1274
+ */
1275
+ toLoadoutEvent(options?: LoadoutExportOptions): LoadoutEvent;
1276
+ /**
1277
+ * This build as a one-entry SLEF export.
1278
+ *
1279
+ * @param options - Ordering, power state, and the envelope header.
1280
+ * @returns The export. Several builds travel together as
1281
+ * `toSlef([a.toLoadoutEvent(), b.toLoadoutEvent()])` using the function of the same
1282
+ * name from `./slef`.
1283
+ */
1284
+ toSlef(options: SlefExportOptions): Slef;
1285
+ /**
1286
+ * This build as SLEF JSON — ready to write to a file or put on the clipboard.
1287
+ *
1288
+ * @param options - As {@link toSlef}, plus `indent` (compact by default).
1289
+ * @example
1290
+ * ```ts
1291
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1292
+ *
1293
+ * declare const build: ShipLoadout;
1294
+ *
1295
+ * build.toSlefString({ header: { appName: 'MyApp', appVersion: '1.0.0' } });
1296
+ * ```
1297
+ */
1298
+ toSlefString(options: SlefExportOptions): string;
1299
+ /**
1300
+ * The resolved frame-shift-drive constants for this build — post-engineering,
1301
+ * with any Guardian FSD Booster folded into `jumpBoost`.
1302
+ *
1303
+ * @throws {TypeError} If the build has no frame shift drive, or its required jump
1304
+ * constants are missing from the stats catalogue.
1305
+ */
1306
+ get frameShiftDrive(): FrameShiftDriveParams;
1307
+ /**
1308
+ * The fitted frame shift drive's dimensionless mass factor at a chosen load.
1309
+ *
1310
+ * @param options - {@link JumpOptions}. `fuel` defaults to a full main tank and
1311
+ * `cargo` to `0`.
1312
+ * @returns `optMass / loadedMass`: `1` at the drive's optimised mass, below `1`
1313
+ * above it and above `1` below it.
1314
+ * @remarks
1315
+ * This is the mass term used by the jump equation, not the three-point performance
1316
+ * curve used by thrusters and shield generators. Main-tank fuel contributes to the
1317
+ * loaded mass; the Guardian FSD Booster's additive range does not contribute to the
1318
+ * factor.
1319
+ * @throws {TypeError} If the build has no usable frame shift drive or its mass
1320
+ * cannot be determined; also if fuel capacity is unknown and `options.fuel` is
1321
+ * omitted.
1322
+ * @throws {RangeError} If fuel or cargo is not finite and non-negative, or loaded
1323
+ * mass is zero.
1324
+ * @example
1325
+ * ```ts
1326
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1327
+ *
1328
+ * declare const build: ShipLoadout;
1329
+ * build.frameShiftDriveMassFactor({ fuel: 8, cargo: 32 }); // dimensionless
1330
+ * ```
1331
+ */
1332
+ frameShiftDriveMassFactor(options?: JumpOptions): number;
1333
+ /**
1334
+ * Best single-jump range, in light-years — no cargo, and exactly one jump's fuel
1335
+ * aboard (the lightest the ship jumps). This is the figure the game and EDSY label
1336
+ * "maximum jump range".
1337
+ *
1338
+ * @remarks
1339
+ * Returns `0` when no fuel is available — an assembled build with no fuel tank
1340
+ * fitted has an empty main tank, so there is nothing to jump on.
1341
+ * @returns The best single jump, in light-years.
1342
+ * @throws {TypeError} If the build has no usable frame shift drive, or its mass or
1343
+ * fuel capacity cannot be determined.
1344
+ */
1345
+ maxJumpRange(): number;
1346
+ /**
1347
+ * The range of a single jump for a chosen fuel and cargo load, in light-years.
1348
+ *
1349
+ * @param options - {@link JumpOptions}. `fuel` defaults to a full main tank,
1350
+ * `cargo` to `0`.
1351
+ * @returns The jump's range, in light-years.
1352
+ * @throws {TypeError} If the build has no usable frame shift drive or its mass
1353
+ * cannot be determined; also if fuel capacity is unknown and `options.fuel` is
1354
+ * omitted.
1355
+ * @throws {RangeError} If fuel or cargo is not finite and non-negative.
1356
+ */
1357
+ jumpRange(options?: JumpOptions): number;
1358
+ /**
1359
+ * Single-jump range on a full tank with a full cargo hold, in light-years.
1360
+ *
1361
+ * @returns The jump's range, in light-years.
1362
+ * @throws {TypeError} If the build has no usable frame shift drive, or its mass,
1363
+ * fuel capacity or cargo capacity cannot be determined.
1364
+ */
1365
+ ladenJumpRange(): number;
1366
+ /**
1367
+ * The fuel a single jump of a given distance costs, in tonnes.
1368
+ *
1369
+ * @param distance - The jump distance, in light-years.
1370
+ * @param options - {@link JumpOptions}. `fuel` defaults to a full main tank,
1371
+ * `cargo` to `0`.
1372
+ * @returns Fuel used, in tonnes (capped at the drive's max fuel per jump).
1373
+ * @throws {TypeError} If the build has no usable frame shift drive or its mass
1374
+ * cannot be determined; also if fuel capacity is unknown and `options.fuel` is
1375
+ * omitted.
1376
+ * @throws {RangeError} If fuel or cargo is not finite and non-negative.
1377
+ */
1378
+ fuelPerJump(distance: number, options?: JumpOptions): number;
1379
+ /**
1380
+ * Total range and jump count for a chosen fuel and cargo load.
1381
+ *
1382
+ * @param options - {@link JumpOptions}. `fuel` defaults to a full main tank,
1383
+ * `cargo` to `0`.
1384
+ * @returns Summed range in light-years and the jumps made before the tank is empty.
1385
+ * @throws {TypeError} If the build has no usable frame shift drive, or its mass or
1386
+ * fuel capacity cannot be determined; fuel capacity is not required when
1387
+ * `options.fuel` is supplied.
1388
+ * @throws {RangeError} If fuel or cargo is not finite and non-negative, or the
1389
+ * fuel load would require more than 100,000 jumps.
1390
+ * @example
1391
+ * ```ts
1392
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1393
+ *
1394
+ * declare const build: ShipLoadout;
1395
+ * build.totalRange().jumps; // jumps available from one full main tank
1396
+ * build.totalRange({ fuel: 8, cargo: 32 }).range; // range for that partial load
1397
+ * ```
1398
+ */
1399
+ totalRange(options?: JumpOptions): TotalRangeDetails;
1400
+ /**
1401
+ * Resolve one of the package's standard load conditions for jump and mobility views.
1402
+ *
1403
+ * @param load - `'maximum'` for one jump's fuel and no cargo, `'unladen'` for a
1404
+ * full main tank and no cargo, or `'laden'` for a full main tank and full hold.
1405
+ * @returns Fuel and cargo in tonnes, or structured diagnostics for unavailable
1406
+ * capacities. `'maximum'` also reports every mass or frame-shift-drive fact needed
1407
+ * to use that load in a jump calculation. A complete maximum result can therefore
1408
+ * be passed safely to {@link jumpRange}; it validates the whole fitted drive,
1409
+ * including an active jump booster, even though only the drive's maximum fuel
1410
+ * determines the returned load.
1411
+ * @throws {RangeError} If `load` is not a recognised standard load.
1412
+ * @example
1413
+ * ```ts
1414
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1415
+ *
1416
+ * declare const build: ShipLoadout;
1417
+ * const load = build.standardLoadResult('maximum');
1418
+ * if (load.complete) build.mobilityMetrics({ ...load.value, enginesPips: 2 });
1419
+ * ```
1420
+ */
1421
+ standardLoadResult(load: StandardLoad): CalculationResult<StandardLoadInputs>;
1422
+ /**
1423
+ * Every jump figure at once — best, unladen, laden, and each load's total.
1424
+ *
1425
+ * @returns The {@link JumpRangeSummary}. Single-jump figures and each total's
1426
+ * `range` are in light-years. For a partial load, call {@link jumpRange} for one
1427
+ * jump or {@link totalRange} for every jump with the `fuel` and `cargo` you
1428
+ * actually have.
1429
+ * @throws {TypeError} If the build has no usable frame shift drive, or its mass,
1430
+ * fuel capacity or cargo capacity cannot be determined.
1431
+ * @example
1432
+ * ```ts
1433
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1434
+ *
1435
+ * declare const build: ShipLoadout;
1436
+ *
1437
+ * const jumps = build.jumpRangeSummary();
1438
+ * jumps.max; // -> 89.41 (one jump's fuel, empty hold)
1439
+ * jumps.laden; // -> the range with the hold full
1440
+ * jumps.totalMax.jumps; // the best jump expressed as a total
1441
+ * // Half a tank and 32 t aboard, once the tank is known:
1442
+ * const fuel = build.fuelCapacityResult;
1443
+ * if (fuel.complete) build.jumpRange({ fuel: fuel.value.main / 2, cargo: 32 });
1444
+ * ```
1445
+ */
1446
+ jumpRangeSummary(): JumpRangeSummary;
1447
+ /**
1448
+ * The build's power budget: what the plant makes, what the modules draw with
1449
+ * hardpoints retracted and deployed, and which priority groups stay lit.
1450
+ *
1451
+ * Draws are post-engineering, modules switched off in the journal are skipped, and
1452
+ * weapons (plus the utility fittings that are not always powered) count only
1453
+ * towards the deployed total.
1454
+ *
1455
+ * @returns The {@link PowerBudget}. With no power plant fitted, `available` is `0`
1456
+ * and nothing is powered. An unresolved plant also reports `available: 0`; it is
1457
+ * identified as `powerCapacity` by the mobility, shield and recovery result methods,
1458
+ * not {@link PowerBudget.unknownDraws}. When the entire plant record is unresolved,
1459
+ * {@link validation} also reports `unknownModule`. A fitted module whose draw the
1460
+ * catalogue cannot supply is
1461
+ * named in {@link PowerBudget.unknownDraws} rather than counted as drawing nothing,
1462
+ * which makes every total a lower bound while that list is non-empty. `consumers`
1463
+ * includes modules with positive or unknown draw; passive and zero-draw fittings are
1464
+ * absent.
1465
+ * @throws {RangeError} If a known power capacity or module draw is negative or not
1466
+ * finite.
1467
+ * @example
1468
+ * ```ts
1469
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1470
+ *
1471
+ * declare const build: ShipLoadout;
1472
+ *
1473
+ * const power = build.powerBudget();
1474
+ * power.available; // -> 20.4 MW generated
1475
+ * power.deployed; // -> 19.02 MW drawn, hardpoints out
1476
+ * power.withinBudget; // -> true
1477
+ * power.bands[4]?.poweredDeployed; // -> is priority group 5 still lit?
1478
+ * ```
1479
+ */
1480
+ powerBudget(): PowerBudget;
1481
+ /**
1482
+ * The build's heat: what it idles at, what it runs at flying and jumping, and
1483
+ * whether firing everything cooks it.
1484
+ *
1485
+ * Every figure is post-engineering. The heat a build makes follows what the plant
1486
+ * actually feeds, so a module switched off — or one in a priority group the plant
1487
+ * cannot keep lit — contributes nothing.
1488
+ *
1489
+ * @returns The {@link HeatMetrics}, or `null` when the build has no powered power
1490
+ * plant. A build carrying a module the catalogues cannot resolve is answered rather
1491
+ * than refused. {@link HeatMetrics.unknownDraws} names unknown power contributions;
1492
+ * {@link HeatMetrics.unknownWeaponHeat} names unresolved hardpoints omitted from the
1493
+ * firing scenarios. Read those entries before showing the figures, because a
1494
+ * non-empty diagnostic makes the affected results projections over the rest of the
1495
+ * build rather than complete answers.
1496
+ * @example
1497
+ * ```ts
1498
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1499
+ *
1500
+ * declare const build: ShipLoadout;
1501
+ *
1502
+ * const heat = build.heatMetrics();
1503
+ * heat?.idle.gauge; // -> 0.23, i.e. the gauge reads 23%
1504
+ * heat?.firingSustained.overheats; // -> false: the guns run cool enough to hold
1505
+ * heat?.firingDrained.secondsToOverheat; // -> how long an alpha strike has on an empty WEP
1506
+ * ```
1507
+ */
1508
+ heatMetrics(): HeatMetrics | null;
1509
+ /**
1510
+ * The build's speed, boost and rotation rates at a chosen load and ENG allocation.
1511
+ *
1512
+ * @remarks
1513
+ * Main-tank fuel contributes to the flight model's loaded mass. Reserve-tank fuel
1514
+ * does not: although the statistics panel includes it in the displayed current
1515
+ * mass, ten observed builds reproduce their angular rates only when the reserve is
1516
+ * excluded from the thruster mass curve.
1517
+ *
1518
+ * @param options - Fuel defaults to a full main tank, cargo to `0`, and ENG pips to `4`.
1519
+ * @returns Loaded {@link MobilityMetrics}, or `null` when no fully described
1520
+ * thrusters are powered with hardpoints retracted. Use
1521
+ * {@link mobilityMetricsResult} to distinguish the unavailable conditions.
1522
+ * @throws {TypeError} If mass or an omitted main-tank fuel load cannot be determined.
1523
+ * @throws {RangeError} If fuel or cargo is not finite and non-negative, or
1524
+ * `enginesPips` is outside `[0, 4]`.
1525
+ * @example
1526
+ * ```ts
1527
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1528
+ *
1529
+ * declare const build: ShipLoadout;
1530
+ * build.mobilityMetrics({ cargo: 32, fuel: 8, enginesPips: 2 })?.speed; // -> m/s
1531
+ * ```
1532
+ */
1533
+ mobilityMetrics(options?: MobilityOptions): MobilityMetrics | null;
1534
+ /**
1535
+ * The build's mobility with a diagnostic when its thrusters or retracted power
1536
+ * supply is unavailable.
1537
+ *
1538
+ * @param options - Fuel defaults to a full main tank, cargo to `0`, and ENG pips to `4`.
1539
+ * @returns A complete {@link MobilityMetrics} value, otherwise `null` plus the input
1540
+ * or fitted-module state that prevented the calculation.
1541
+ * @throws {TypeError} If mass or an omitted main-tank fuel load cannot be determined.
1542
+ * @throws {RangeError} If fuel or cargo is not finite and non-negative, or
1543
+ * `enginesPips` is outside `[0, 4]`.
1544
+ * @example
1545
+ * ```ts
1546
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1547
+ *
1548
+ * declare const build: ShipLoadout;
1549
+ * const result = build.mobilityMetricsResult({ enginesPips: 2 });
1550
+ * if (result.complete) result.value.speed; // metres per second
1551
+ * else result.issues[0].reason; // unavailable-state discriminator
1552
+ * ```
1553
+ */
1554
+ mobilityMetricsResult(options?: MobilityOptions): CalculationResult<MobilityMetrics>;
1555
+ /**
1556
+ * The build's shields: strength in megajoules, where it comes from, and the
1557
+ * effective resistances.
1558
+ *
1559
+ * Shield strength scales with the **hull's** mass, not the build's, so fitting
1560
+ * more modules never weakens it. Boosters, Guardian shield reinforcement and any
1561
+ * engineering are all folded in; switched-off or shed boosters and reinforcement
1562
+ * are ignored, while a switched-off or shed generator makes the metric unavailable.
1563
+ *
1564
+ * @param options - {@link DefenceOptions}. `systemsPips` (0–4) folds the SYS
1565
+ * capacitor's own resistance into the reported figures; it defaults to `0`, which
1566
+ * is what an outfitting screen shows.
1567
+ * @returns The {@link ShieldMetrics}, or `null` when the build has no shield
1568
+ * generator powered with hardpoints retracted. Use
1569
+ * {@link shieldMetricsResult} to distinguish the unavailable conditions.
1570
+ * @throws {RangeError} If `systemsPips` is outside `[0, 4]` or not finite.
1571
+ * @example
1572
+ * ```ts
1573
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1574
+ *
1575
+ * declare const build: ShipLoadout;
1576
+ *
1577
+ * const shields = build.shieldMetrics();
1578
+ * shields?.strength; // -> MJ
1579
+ * shields?.resistances.thermal; // -> negative on a stock generator
1580
+ * build.shieldMetrics({ systemsPips: 4 })?.resistances.thermal; // -> with 4 pips to SYS
1581
+ * ```
1582
+ */
1583
+ shieldMetrics(options?: DefenceOptions): ShieldMetrics | null;
1584
+ /**
1585
+ * The build's shields with a diagnostic when its hull, generator or retracted
1586
+ * power supply is unavailable.
1587
+ *
1588
+ * @param options - {@link DefenceOptions}. `systemsPips` defaults to `0`.
1589
+ * @returns A complete {@link ShieldMetrics} value, otherwise `null` plus the input
1590
+ * or fitted-module state that prevented the calculation.
1591
+ * @throws {RangeError} If `systemsPips` is outside `[0, 4]` or not finite.
1592
+ * @example
1593
+ * ```ts
1594
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1595
+ *
1596
+ * declare const build: ShipLoadout;
1597
+ * const result = build.shieldMetricsResult();
1598
+ * if (result.complete) result.value.strength; // megajoules
1599
+ * else result.issues[0].reason; // unavailable-state discriminator
1600
+ * ```
1601
+ */
1602
+ shieldMetricsResult(options?: DefenceOptions): CalculationResult<ShieldMetrics>;
1603
+ /**
1604
+ * Time for this build's shield to rise after collapse and then regenerate to full.
1605
+ *
1606
+ * @param options - SYS pips in `[0, 4]`, defaulting to `4`.
1607
+ * @returns Recovery rates and seconds, or `null` when no shield generator is powered
1608
+ * with hardpoints retracted. Use
1609
+ * {@link shieldRecoveryResult} to distinguish the unavailable conditions.
1610
+ * A missing distributor or insufficient zero-pip recharge produces `Infinity`.
1611
+ * @throws {RangeError} If `systemsPips` is outside `[0, 4]` or not finite.
1612
+ * @example
1613
+ * ```ts
1614
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1615
+ *
1616
+ * declare const build: ShipLoadout;
1617
+ * build.shieldRecovery({ systemsPips: 4 })?.recoveryTime; // -> seconds from collapse to 50%
1618
+ * ```
1619
+ */
1620
+ shieldRecovery(options?: DefenceOptions): ShieldRecovery | null;
1621
+ /**
1622
+ * The build's shield recovery with a diagnostic when its hull, generator or
1623
+ * retracted power supply is unavailable.
1624
+ *
1625
+ * @param options - SYS pips in `[0, 4]`, defaulting to `4`.
1626
+ * @returns A complete {@link ShieldRecovery} value, otherwise `null` plus the input
1627
+ * or fitted-module state that prevented the calculation.
1628
+ * @throws {RangeError} If `systemsPips` is outside `[0, 4]` or not finite.
1629
+ * @example
1630
+ * ```ts
1631
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1632
+ *
1633
+ * declare const build: ShipLoadout;
1634
+ * const result = build.shieldRecoveryResult();
1635
+ * if (result.complete) result.value.recoveryTime; // seconds
1636
+ * else result.issues[0].reason; // unavailable-state discriminator
1637
+ * ```
1638
+ */
1639
+ shieldRecoveryResult(options?: DefenceOptions): CalculationResult<ShieldRecovery>;
1640
+ /**
1641
+ * Every fitted shield cell bank and the usable rearmed reinforcement pool.
1642
+ *
1643
+ * Every fitted bank remains in `banks`, where `powered` says whether it is switched
1644
+ * on and its priority group is fed with hardpoints deployed. The totals include
1645
+ * only those powered banks. A bank whose draw is unresolved is assumed powered,
1646
+ * consistently with {@link powerBudget}; inspect that result's `unknownDraws` when
1647
+ * the distinction matters. A build with no powered power plant — including one
1648
+ * created by {@link empty} before a plant is fitted — reports every bank as
1649
+ * unpowered and returns zero totals.
1650
+ *
1651
+ * @returns A frozen {@link CellBankSummary}; no banks is an empty list and zero totals.
1652
+ * @example
1653
+ * ```ts
1654
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1655
+ *
1656
+ * declare const build: ShipLoadout;
1657
+ * build.cellBanks().totalRestorable; // -> MJ across every powered fitted cell
1658
+ * ```
1659
+ */
1660
+ cellBanks(): CellBankSummary;
1661
+ /**
1662
+ * Price this build from current catalogue list prices without creating a journal event.
1663
+ *
1664
+ * @returns Hull, module and five-percent rebuy credits. `modules` and `rebuy` remain
1665
+ * lower bounds when {@link RetailCredits.unpriced} is non-empty; built-in hull fittings are free.
1666
+ * @example
1667
+ * ```ts
1668
+ * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1669
+ *
1670
+ * ShipLoadout.default('Anaconda').retailCredits().hull; // -> 142456440
1671
+ * ```
1672
+ * @see {@link mercCoinCost} for Mercenary purchase prices.
1673
+ */
1674
+ retailCredits(): RetailCredits;
1675
+ /**
1676
+ * Total the Merc Coin prices of the Mercenary articles fitted to this build.
1677
+ *
1678
+ * @returns The total in Merc Coin, or `0` when no fitted article is a Mercenary
1679
+ * purchase. Credit prices and rebuy remain available from {@link retailCredits}.
1680
+ * @remarks
1681
+ * The total counts both articles fitted through {@link setPreEngineeredVariant} and
1682
+ * purchases implied by applying their Mercenary-only blueprint through
1683
+ * {@link applyBlueprint}. The blueprint identifies the purchase at grade 1 and after
1684
+ * later upgrades; the current grade does not change the original shop price.
1685
+ * @example
1686
+ * ```ts
1687
+ * import { getPreEngineeredVariants } from '@elite-dangerous-almanac/core/ships/pre-engineered';
1688
+ * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1689
+ *
1690
+ * const variant = getPreEngineeredVariants('Hpt_Railgun_Fixed_Medium')
1691
+ * .find((candidate) => candidate.acquisition === 'mercenary')!;
1692
+ * const build = ShipLoadout.default('Python')
1693
+ * .setPreEngineeredVariant('MediumHardpoint1', variant);
1694
+ * build.mercCoinCost(); // -> 950
1695
+ * ```
1696
+ */
1697
+ mercCoinCost(): number;
1698
+ /**
1699
+ * The build's armour: hull hit points, the bulkhead and reinforcement each
1700
+ * contribute, and the effective resistances.
1701
+ *
1702
+ * @returns The {@link ArmourMetrics}. A build with no armour module fitted is
1703
+ * reported on the stock lightweight alloy the hull
1704
+ * leaves the shipyard with, which is what the game does.
1705
+ * @example
1706
+ * ```ts
1707
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1708
+ *
1709
+ * declare const build: ShipLoadout;
1710
+ *
1711
+ * const hull = build.armourMetrics();
1712
+ * hull.hitPoints; // -> total hull points
1713
+ * hull.resistances.explosive; // -> lightweight alloy is explosively weak
1714
+ * hull.effectiveHitPoints.thermal; // -> thermal damage the hull can soak
1715
+ * ```
1716
+ */
1717
+ armourMetrics(): ArmourMetrics;
1718
+ /**
1719
+ * The build's firepower: DPS, sustained DPS, weapons-capacitor draw, heat and power
1720
+ * draw for every fitted weapon, plus the totals.
1721
+ *
1722
+ * Every figure is post-engineering. A weapon switched off in the journal is still
1723
+ * listed — with its own metrics — but left out of the totals.
1724
+ *
1725
+ * @returns The {@link BuildWeaponMetrics}.
1726
+ * @example
1727
+ * ```ts
1728
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1729
+ *
1730
+ * declare const build: ShipLoadout;
1731
+ *
1732
+ * const guns = build.weaponMetrics();
1733
+ * guns.total.damagePerSecond; // -> burst DPS across the hardpoints
1734
+ * guns.total.sustainedDamagePerSecond; // -> with reloads folded in
1735
+ * guns.total.energyPerSecond; // -> MW asked of the WEP capacitor
1736
+ * guns.total.powerDraw; // -> MW asked of the power plant when deployed
1737
+ * guns.weapons[0]?.metrics.damageByType.thermal;
1738
+ * guns.weapons[0]?.maximumRange; // post-engineering metres, when known
1739
+ * guns.weapons[0]?.armourPiercing; // post-engineering rating, when known
1740
+ * guns.weapons[0]?.ammunition?.total; // -> rounds aboard when fully rearmed
1741
+ * ```
1742
+ */
1743
+ weaponMetrics(): BuildWeaponMetrics;
1744
+ /**
1745
+ * WEP-capacitor recharge and endurance while every powered weapon fires.
1746
+ *
1747
+ * @param options - WEP pips in `[0, 4]`, defaulting to `4`.
1748
+ * @returns Actual recharge, sustained draw, net drain and seconds from full to
1749
+ * empty. The deployed power budget is applied to the distributor and weapons, so a
1750
+ * module the plant sheds contributes nothing. A module with an unresolved power
1751
+ * draw is assumed powered, consistently with {@link powerBudget}; inspect its
1752
+ * `unknownDraws` when that distinction matters. With no powered distributor,
1753
+ * capacity and recharge are zero. A load that draws no more than recharge reports
1754
+ * `Infinity` for `timeToDrain`.
1755
+ * @throws {RangeError} If `weaponsPips` is outside `[0, 4]` or not finite.
1756
+ * @example
1757
+ * ```ts
1758
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1759
+ *
1760
+ * declare const build: ShipLoadout;
1761
+ * build.weaponsCapacitorMetrics({ weaponsPips: 2 }).timeToDrain; // seconds
1762
+ * ```
1763
+ */
1764
+ weaponsCapacitorMetrics(options?: WeaponsOptions): WeaponsCapacitorMetrics;
1765
+ /**
1766
+ * All three power-distributor capacitors at selected pip allocations.
1767
+ *
1768
+ * @param options - SYS, ENG and WEP pips in `[0, 4]`, each defaulting
1769
+ * independently to `4`. The allocations need not sum to six, which permits
1770
+ * independent comparisons of the three maxima.
1771
+ * @returns Capacity, rated four-pip recharge and actual pip-scaled recharge for
1772
+ * SYS, ENG and WEP, or `null` when no distributor is fitted, it is switched off,
1773
+ * its six capacitor stats cannot be resolved, or the retracted power budget sheds
1774
+ * it. The retracted state represents the distributor itself; firing endurance in
1775
+ * {@link weaponsCapacitorMetrics} separately applies the deployed state. A module
1776
+ * with unresolved power draw is assumed powered, consistently with
1777
+ * {@link powerBudget}; inspect its `unknownDraws` when that distinction matters.
1778
+ * @throws {RangeError} If any pip allocation is outside `[0, 4]` or not finite.
1779
+ * @example
1780
+ * ```ts
1781
+ * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1782
+ *
1783
+ * declare const build: ShipLoadout;
1784
+ * const distributor = build.distributorMetrics({
1785
+ * systemsPips: 2,
1786
+ * enginesPips: 2,
1787
+ * weaponsPips: 2,
1788
+ * });
1789
+ * distributor?.engines.rechargeRate; // MJ/s
1790
+ * ```
1791
+ */
1792
+ distributorMetrics(options?: DistributorOptions): DistributorMetrics | null;
1793
+ }
1794
+
1795
+ export { type ApplyBlueprintOptions, type AvailableBlueprint, type BuildWeaponMetrics, type DefenceOptions, type DistributorOptions, type EngineeringNormalizationCode, type EngineeringNormalizationResult, type EngineeringNormalizationUnchanged, type EngineeringNormalizationUnsupported, type EngineeringNormalized, type ExperimentalEffectMutationCode, type ExperimentalEffectMutationResult, type ExperimentalEffectUnchanged, type ExperimentalEffectUnsupported, type ExperimentalEffectUpdated, type FittedModule, type FittedWeaponMetrics, type FixedMountRepairResult, type ImmovableReason, type JumpOptions, type JumpRangeSummary, LoadoutEditError, type LoadoutEditErrorCode, type LoadoutExportOptions, type LoadoutSlot, type MobilityOptions, type RetailCredits, ShipLoadout, type SlefExportOptions, type StandardLoad, type StandardLoadInputs, type WeaponsOptions };