@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,1455 +0,0 @@
1
- import { ModuleEngineering, LoadoutModule, LoadoutEvent, SlefHeader, Slef } from './ships/slef.js';
2
- import { TotalRangeDetails, FrameShiftDriveParams } from './ships/jump-range.js';
3
- import { BuildSlot, SlotKind } from './ships/slots.js';
4
- import { OutfittingModule } from './ships/modules.js';
5
- import { PowerBudget } from './ships/power.js';
6
- import { HeatMetrics } from './ships/heat.js';
7
- import { ShieldMetrics } from './ships/shields.js';
8
- import { ArmourMetrics } from './ships/armour.js';
9
- import { WeaponMetrics, WeaponTotals } from './ships/weapons.js';
10
- import { AmmunitionCapacity } from './ships/ammunition.js';
11
- import { WeaponsCapacitorMetrics } from './ships/weapons-capacitor.js';
12
- import { DistributorMetrics } from './ships/distributor.js';
13
- import { MobilityMetrics } from './ships/mobility.js';
14
- import { ShieldRecovery, CellBankSummary } from './ships/shield-recovery.js';
15
- import { PreEngineeredVariant } from './ships/pre-engineered.js';
16
- import { SourcePurchaseRecord } from './ships/source-purchase.js';
17
- import { CalculationResult, FuelCapacity } from './ships/loadout-calculations.js';
18
- import { ModuleFitConstraint, LoadoutIssueParams, LoadoutValidation } from './ships/loadout-validation.js';
19
-
20
- /**
21
- * Immutable fitted-module snapshots returned by {@link ShipLoadout}.
22
- *
23
- * @packageDocumentation
24
- */
25
-
26
- /**
27
- * A point-in-time, deeply frozen view of the module fitted in one slot.
28
- *
29
- * The view is detached from its {@link ShipLoadout}: later edits do not change it, and
30
- * mutating it throws. Fetch a new view with {@link ShipLoadout.fittedModuleAt} after an
31
- * state-changing edit; reads made without an intervening state change reuse the same
32
- * frozen snapshot. All mutations live on `ShipLoadout`, keyed by {@link slot}; this
33
- * avoids the stale-handle lifecycle that a live proxy would otherwise need.
34
- *
35
- * @example
36
- * ```ts
37
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
38
- *
39
- * declare const build: ShipLoadout;
40
- *
41
- * const before = build.fittedModuleAt('FrameShiftDrive')!;
42
- * build.applyBlueprint(before.slot, 'FSD_LongRange', { grade: 5 });
43
- * const after = build.fittedModuleAt(before.slot)!;
44
- * before.engineering; // unchanged
45
- * after.engineering; // the applied blueprint
46
- * ```
47
- */
48
- interface FittedModule {
49
- /** Slot key in the build's own spelling. */
50
- readonly slot: string;
51
- /** Frontier module symbol, e.g. `"Int_Hyperdrive_Size6_Class5"`. */
52
- readonly symbol: string;
53
- /** Whether the module was powered on, or `undefined` when unspecified. */
54
- readonly on: boolean | undefined;
55
- /** Zero-based power-priority group, or `undefined` when unspecified. */
56
- readonly priority: number | undefined;
57
- /** Module health in `[0, 1]`, or `undefined` when unspecified. */
58
- readonly health: number | undefined;
59
- /** Captured purchase value in credits, or `undefined` when unspecified. */
60
- readonly value: number | undefined;
61
- /** Applied engineering state; otherwise `undefined`. */
62
- readonly engineering: ModuleEngineering | undefined;
63
- /** Detached, journal-shaped fitted record. */
64
- readonly raw: LoadoutModule;
65
- /**
66
- * Snapshotted fitted-article stats before its journal modifier block is folded, or
67
- * `null` when unresolved.
68
- *
69
- * A stock or ordinarily engineered module exposes its base catalogue record. A fixed
70
- * pre-engineered variant exposes its resolved article record here as well as through
71
- * {@link effectiveStats}, so stats omitted by a journal capture still describe the
72
- * article. Clearing or replacing that fixed engineering restores the stock record
73
- * before applying the next recipe.
74
- */
75
- readonly stats: OutfittingModule | null;
76
- /**
77
- * Post-engineering module stats, or `null` when unresolved. For weapons, journal
78
- * damage per second is resolved back to per-round damage and falloff is capped at
79
- * maximum range. Exact damage components follow the engineered total and disappear
80
- * when a damage conversion replaces them with a fractional distribution.
81
- * A module engineered through {@link ShipLoadout.applyBlueprint} also retains
82
- * recipe-only burst values that its journal-shaped modifier block does not serialize.
83
- * A festive variant fitted through {@link ShipLoadout.setPreEngineeredVariant} uses
84
- * its fixed modifier block.
85
- */
86
- readonly effectiveStats: OutfittingModule | null;
87
- /** Fully rearmed ammunition capacity, or `null` for modules without ammunition. */
88
- readonly ammunition: AmmunitionCapacity | null;
89
- /** Identified fixed pre-engineered variant, or `null` when not uniquely identified. */
90
- readonly preEngineeredVariant: PreEngineeredVariant | null;
91
- }
92
-
93
- /**
94
- * Immutable slot snapshots returned by {@link ShipLoadout}.
95
- *
96
- * @packageDocumentation
97
- */
98
-
99
- /** Stable reason a hull mount cannot be emptied through {@link ShipLoadout.removeModule}. */
100
- type ImmovableReason = 'cargoHatch' | 'moduleLimit';
101
- /**
102
- * A point-in-time, deeply frozen view of one hull mount.
103
- *
104
- * The view is detached from its {@link ShipLoadout}; after an edit that changes the
105
- * build, call {@link ShipLoadout.slots} again for the current view. Reads made without an
106
- * intervening state change reuse the same frozen snapshots. 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, or `moduleLimit` when removing a fitted allowance-increasing
163
- * module would leave too many limited modules. Absent when {@link removable} is true.
164
- */
165
- readonly immovableReason?: ImmovableReason;
166
- };
167
-
168
- /**
169
- * {@link ShipLoadout} — a mutable fitted-ship model that both **answers questions**
170
- * about a build and **edits** it.
171
- *
172
- * Load one from a SLEF export (or a journal `Loadout` event) to read back the ship's
173
- * identity, mass and fuel and ask for jump range and per-jump fuel; or start an
174
- * {@link ShipLoadout.default | default} or {@link ShipLoadout.empty | empty} hull,
175
- * enumerate its {@link ShipLoadout.slots | slots}, and
176
- * {@link ShipLoadout.setModule | fit} and {@link ShipLoadout.removeModule | remove}
177
- * modules. It composes the data-free pieces of this folder — the SLEF parser
178
- * (`./slef`), the jump-range maths (`./jump-range`), the slot model (`./slots`), and
179
- * the module and ship catalogues (each record carrying its own stats).
180
- *
181
- * Instances are **mutable**: `setModule`/`removeModule` change the build in place and
182
- * return `this` for chaining. Values a SLEF export already computed (its
183
- * `UnladenMass`, `FuelCapacity`, …) are trusted verbatim; for a build assembled from
184
- * scratch those figures are computed from the fitted modules and the hull's stats.
185
- * Editing an imported build adjusts the supplied aggregate figures by the changed
186
- * module's contribution; when that contribution is unknown, the affected figure is
187
- * discarded and recomputed rather than allowed to go stale.
188
- * `setModule` snapshots the complete record it receives, including resolved
189
- * pre-engineered or caller-supplied stats, so every later metric uses the article that
190
- * was actually fitted rather than resolving its symbol back to a stock module.
191
- * Slot and fitted-module queries return deeply frozen point-in-time values; edits are
192
- * made only through this facade, then observed by querying again.
193
- *
194
- * **Slot keys are matched case-insensitively.** Frontier writes `FrameShiftDrive` and
195
- * `LargeMiningHardpoint1`, but a SLEF producer may lower-case every key as the
196
- * specification's own example does — Inara writes `frameshiftdrive` and
197
- * `largemininghardpoint1` — and both spellings name the same mount, whether you are
198
- * reading it or fitting into it. What a build already carries is never rewritten to
199
- * match, so re-exporting an import returns the producer's own spelling untouched.
200
- *
201
- * @remarks
202
- * This is the batteries-included ship facade: resolving arbitrary journal module
203
- * ids and engineering recipes requires the complete ship/module, blueprint, and
204
- * experimental-effect catalogues. Import `./slef`, `./jump-range`, or an individual
205
- * module catalogue instead when you only need one data-free operation or one
206
- * outfitting category.
207
- *
208
- * @example
209
- * ```ts
210
- * declare const slefJsonString: string;
211
- *
212
- * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
213
- *
214
- * // Read a build:
215
- * const build = ShipLoadout.fromSlef(slefJsonString);
216
- * build.maxJumpRange(); // -> 89.41 (best single jump, one jump's fuel, no cargo)
217
- *
218
- * // Assemble one:
219
- * import { getModuleBySymbol } from '@elite-dangerous-almanac/core/ships/modules';
220
- * import { CORE_MODULES } from '@elite-dangerous-almanac/core/ships/modules-core';
221
- * const conda = ShipLoadout.empty('Anaconda');
222
- * conda.setModule('FrameShiftDrive', getModuleBySymbol('Int_Hyperdrive_Size6_Class5', CORE_MODULES)!);
223
- * conda.slots('optional'); // every optional mount, occupied or empty, with size
224
- * ```
225
- *
226
- * @packageDocumentation
227
- */
228
-
229
- /**
230
- * Stable machine-readable reason a {@link ShipLoadout} edit was refused:
231
- * `immutableSlot`, an incompatible fit, a duplicate one-per-ship family, or a module
232
- * count beyond the build's current allowance.
233
- */
234
- type LoadoutEditErrorCode = 'immutableSlot' | 'incompatibleModule' | 'duplicateExclusiveModule' | 'moduleLimitExceeded';
235
- /**
236
- * An editor request that the current hull or build constraints cannot accept.
237
- *
238
- * @remarks
239
- * This remains a `TypeError`, so existing `instanceof TypeError` handling continues to
240
- * work. Localized editors should switch on {@link code}, then use {@link constraint}
241
- * and {@link params} instead of parsing the English fallback in `message`.
242
- *
243
- * @example
244
- * ```ts
245
- * import {
246
- * LoadoutEditError,
247
- * ShipLoadout,
248
- * } from '@elite-dangerous-almanac/core/ships/ship-loadout';
249
- * import { getModuleBySymbol } from '@elite-dangerous-almanac/core/ships/modules';
250
- * import { CORE_MODULES } from '@elite-dangerous-almanac/core/ships/modules-core';
251
- *
252
- * const build = ShipLoadout.empty('SideWinder');
253
- * const drive = getModuleBySymbol('Int_Hyperdrive_Size8_Class5', CORE_MODULES)!;
254
- * try {
255
- * build.setModule('FrameShiftDrive', drive);
256
- * } catch (error) {
257
- * if (error instanceof LoadoutEditError) error.constraint; // -> 'oversized'
258
- * }
259
- * ```
260
- */
261
- declare class LoadoutEditError extends TypeError {
262
- /** Stable category for the refused edit. */
263
- readonly code: LoadoutEditErrorCode;
264
- /** More specific fitting rule for an `incompatibleModule` failure. */
265
- readonly constraint?: ModuleFitConstraint;
266
- /** Language-neutral values used by the English fallback message. */
267
- readonly params: LoadoutIssueParams;
268
- /**
269
- * Construct a structured loadout-edit error.
270
- *
271
- * @param message - English fallback suitable for logs.
272
- * @param code - Stable category for the refused edit.
273
- * @param params - Language-neutral values used to render the failure.
274
- * @param constraint - Specific fitting rule, when `code` is `incompatibleModule`.
275
- */
276
- constructor(message: string, code: LoadoutEditErrorCode, params: LoadoutIssueParams, constraint?: ModuleFitConstraint);
277
- }
278
- /** Optional mass overrides for a single calculation. */
279
- interface JumpOptions {
280
- /** Finite non-negative fuel load, in tonnes. Defaults to the full main tank. */
281
- readonly fuel?: number;
282
- /** Finite non-negative cargo load, in tonnes. Defaults to `0` (unladen). */
283
- readonly cargo?: number;
284
- }
285
- /** Options for {@link ShipLoadout.applyBlueprint}. */
286
- interface ApplyBlueprintOptions {
287
- /** The blueprint grade, `1`–`5`. */
288
- readonly grade: number;
289
- /**
290
- * The engineering system's shared quality roll, `0`–`1`. Defaults to `1`
291
- * (best roll). A legacy-engineered module's independently advanced attributes cannot be
292
- * reconstructed from its single reported quality; import its stated modifiers instead.
293
- */
294
- readonly quality?: number;
295
- /** The experimental (special) effect's Frontier `fdname`, if any. */
296
- readonly experimental?: string;
297
- }
298
- /** Options for the defence figures a build reports. */
299
- interface DefenceOptions {
300
- /**
301
- * Pips to the systems capacitor, `0`–`4`, folded into the shield resistances.
302
- * Defaults to `0` for {@link ShipLoadout.shieldMetrics} and `4` for
303
- * {@link ShipLoadout.shieldRecovery}.
304
- */
305
- readonly systemsPips?: number;
306
- }
307
- /** Optional load and ENG allocation for {@link ShipLoadout.mobilityMetrics}. */
308
- interface MobilityOptions extends JumpOptions {
309
- /** Pips assigned to the engines capacitor, `0`–`4`. Defaults to `4`. */
310
- readonly enginesPips?: number;
311
- }
312
- /** Optional WEP allocation for {@link ShipLoadout.weaponsCapacitorMetrics}. */
313
- interface WeaponsOptions {
314
- /** Pips assigned to the weapons capacitor, `0`–`4`. Defaults to `4`. */
315
- readonly weaponsPips?: number;
316
- }
317
- /** Optional SYS, ENG and WEP allocations for {@link ShipLoadout.distributorMetrics}. */
318
- interface DistributorOptions {
319
- /** Pips assigned to the systems capacitor, `0`–`4`. Defaults to `4`. */
320
- readonly systemsPips?: number;
321
- /** Pips assigned to the engines capacitor, `0`–`4`. Defaults to `4`. */
322
- readonly enginesPips?: number;
323
- /** Pips assigned to the weapons capacitor, `0`–`4`. Defaults to `4`. */
324
- readonly weaponsPips?: number;
325
- }
326
- /** Retail catalogue credits for an assembled build. */
327
- interface RetailCredits {
328
- /** Bare hull list price in credits, or `null` for an unknown hull. */
329
- readonly hull: number | null;
330
- /** Sum of every priced fitted module, in credits. A lower bound when `unpriced` is non-empty. */
331
- readonly modules: number;
332
- /** Five percent of the priced hull and modules, truncated to credits, or `null` for an unknown hull. */
333
- readonly rebuy: number | null;
334
- /** Fitted modules that could not be priced from the catalogue. */
335
- readonly unpriced: readonly {
336
- readonly slot: string;
337
- readonly symbol: string;
338
- }[];
339
- }
340
- /** One fitted weapon and what it does, as {@link ShipLoadout.weaponMetrics} reports it. */
341
- interface FittedWeaponMetrics {
342
- /** The hardpoint's slot key, e.g. `"LargeHardpoint1"`. */
343
- readonly slot: string;
344
- /** The weapon's internal symbol. */
345
- readonly symbol: string;
346
- /** The weapon's display name, e.g. `"Multi-Cannon"`. */
347
- readonly name: string;
348
- /** Whether the weapon is switched on — a disabled weapon is excluded from the totals. */
349
- readonly enabled: boolean;
350
- /** What this weapon does per second, post-engineering. */
351
- readonly metrics: WeaponMetrics;
352
- /**
353
- * How many rounds it holds when fully rearmed, post-engineering — `null` for a laser,
354
- * which carries none. A capacity, not a rearm state: see {@link FittedModule.ammunition}.
355
- */
356
- readonly ammunition: AmmunitionCapacity | null;
357
- }
358
- /** A build's firepower: every fitted weapon, and the totals across the enabled ones. */
359
- interface BuildWeaponMetrics {
360
- /** Every fitted weapon, in slot order. */
361
- readonly weapons: readonly FittedWeaponMetrics[];
362
- /** The additive totals across the **enabled** weapons. */
363
- readonly total: WeaponTotals;
364
- }
365
- /**
366
- * A build's jump ranges at the loads that matter. The three single-jump values and
367
- * each total result's `range` are in light-years.
368
- */
369
- interface JumpRangeSummary {
370
- /**
371
- * Best single jump: no cargo, and only one jump's fuel aboard — the figure the game
372
- * and EDSY label "maximum jump range".
373
- */
374
- readonly max: number;
375
- /** Single jump on a full tank with an empty hold. */
376
- readonly unladen: number;
377
- /** Single jump on a full tank with a full hold. */
378
- readonly laden: number;
379
- /** Summed range and jump count on one jump's fuel, empty hold. */
380
- readonly totalMax: TotalRangeDetails;
381
- /** Summed range and jump count on one full tank, empty hold. */
382
- readonly totalUnladen: TotalRangeDetails;
383
- /** Summed range and jump count on one full tank, full hold. */
384
- readonly totalLaden: TotalRangeDetails;
385
- }
386
- /** A blueprint candidate for a module symbol, with its grades and availability route. */
387
- interface AvailableBlueprint {
388
- /** The blueprint's Frontier `fdname`, e.g. `"FSD_LongRange"`. */
389
- readonly fdname: string;
390
- /** The grades the blueprint offers, ascending (e.g. `[1, 2, 3, 4, 5]`). */
391
- readonly grades: readonly number[];
392
- /**
393
- * Why the recipe is listed: `'ordinary'` for the stock module's engineering menu,
394
- * or `'mercenary'` for a bespoke recipe attached to a Mercenary purchase.
395
- *
396
- * @remarks
397
- * A Mercenary article shares its module symbol with the stock article, so a loadout
398
- * cannot tell which one was purchased. `'mercenary'` means the recipe is available
399
- * only through that purchase route; it does not identify the fitted article as one.
400
- */
401
- readonly route: 'ordinary' | 'mercenary';
402
- }
403
- /** How to shape a build on the way out — see {@link ShipLoadout.toLoadoutEvent}. */
404
- interface LoadoutExportOptions {
405
- /**
406
- * Module order. `'fitted'` — the default — keeps the order the build carries: an
407
- * import's own `Modules[]` order, or the order modules were fitted. `'slots'`
408
- * re-orders into outfitting-panel order; a module in a slot the hull's layout does
409
- * not describe keeps its relative position at the end rather than being dropped.
410
- */
411
- readonly moduleOrder?: 'fitted' | 'slots';
412
- /**
413
- * Write `On: true` / `Priority: 0` on modules that carry neither — as a journal
414
- * always does and a build assembled here never does. Off by default, following
415
- * SLEF's "require what is necessary, do not force the rest".
416
- */
417
- readonly explicitPower?: boolean;
418
- /**
419
- * Which credits to quote. `'retail'` — the default — prices the build from the
420
- * catalogue: the bare hull's `hullCost`, every fitted module's list price, and a
421
- * `Rebuy` of 5% of the two.
422
- *
423
- * `'source'` quotes the build's {@link ShipLoadout.sourcePurchase | source purchase
424
- * record} instead — `HullValue`, `ModulesValue`, `Rebuy` and the per-module `Value`
425
- * figures exactly as the capture stated them, and nothing where it stated nothing.
426
- * An unedited capture therefore re-exports its own credits unchanged.
427
- *
428
- * Each captured figure is pinned to what it was paid for, so an edit narrows the
429
- * export rather than staling it. A slot whose module has been swapped is left
430
- * unpriced, because the figure was paid for the article that *was* fitted; and
431
- * `ModulesValue` and `Rebuy` are dropped once any priced module has been swapped or
432
- * removed, since they then cover an article no longer aboard. Removing a module the
433
- * capture listed but never priced is the one case this cannot detect: only the
434
- * capture ever knew which unpriced modules its total counted.
435
- *
436
- * `HullValue` always stands: a captured hull figure names no slot, so no edit
437
- * narrows it. Note that on a game capture it counts the hull *with* its stock
438
- * fittings, and removing one of those leaves it overstating what is aboard.
439
- *
440
- * A build with no source record — one assembled here, or a capture that quoted no
441
- * credits — exports no credit figure at all rather than falling back to retail.
442
- */
443
- readonly credits?: 'retail' | 'source';
444
- }
445
- /** As {@link LoadoutExportOptions}, plus the SLEF envelope — see {@link ShipLoadout.toSlef}. */
446
- interface SlefExportOptions extends LoadoutExportOptions {
447
- /**
448
- * The envelope header identifying the exporting application.
449
- *
450
- * SLEF attribution belongs to the application producing the export, not to this
451
- * calculation library, so callers must provide it.
452
- */
453
- readonly header: SlefHeader;
454
- /** Spaces per indent for {@link ShipLoadout.toSlefString}. `0` (the default) is compact. */
455
- readonly indent?: number;
456
- }
457
- /**
458
- * A fitted ship — read a SLEF export, or assemble a hull from scratch.
459
- *
460
- * @remarks
461
- * Jump calculations resolve the frame shift drive's constants from the drive's module
462
- * record, applying any engineering the build carries (a Long Range blueprint's
463
- * `FSDOptimalMass`, for instance). For a SLEF build, mass comes from the export's
464
- * `UnladenMass`; for an assembled build it is the hull mass plus every fitted module's
465
- * mass (armour defaults to the zero-mass lightweight alloy).
466
- *
467
- * @example
468
- * Read a build a player already flies, and ask it what an outfitting screen shows.
469
- * Every figure below is one build's — a Krait Phantom explorer. Figures the capture
470
- * already stated — `unladenMass` here — are trusted verbatim; the rest are computed
471
- * from the fit.
472
- *
473
- * ```ts
474
- * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
475
- * import type { LoadoutEvent } from '@elite-dangerous-almanac/core/ships/slef';
476
- *
477
- * // A `Loadout` line lifted from a player journal, parsed.
478
- * declare const event: LoadoutEvent;
479
- *
480
- * const build = ShipLoadout.fromLoadout(event);
481
- *
482
- * build.shipSymbol; // -> 'krait_light'
483
- * build.shipName; // -> 'Jenny Longuet'
484
- * build.unladenMass; // -> 388.830017 (tonnes)
485
- *
486
- * build.maxJumpRange(); // -> 60.5478 (ly, best single jump)
487
- * build.powerBudget().withinBudget; // -> true
488
- * build.shieldMetrics()?.strength; // -> 743.12 (MJ)
489
- * build.armourMetrics().hitPoints; // -> 307.8
490
- * ```
491
- *
492
- * @example
493
- * Assemble a hull instead. `empty` starts from the shipyard layout, `slots` enumerates
494
- * the mounts, and `setModule` fits one — chainable, because the build is mutable.
495
- *
496
- * ```ts
497
- * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
498
- * import { getModuleBySymbol } from '@elite-dangerous-almanac/core/ships/modules';
499
- * import { CORE_MODULES } from '@elite-dangerous-almanac/core/ships/modules-core';
500
- *
501
- * const conda = ShipLoadout.empty('Anaconda');
502
- * conda.slots().length; // -> 39 (every mount, occupied or not)
503
- * conda.slots('optional').length; // -> 14
504
- * conda.validation.complete; // -> false (nothing fitted yet)
505
- *
506
- * const fsd = getModuleBySymbol('Int_Hyperdrive_Size6_Class5', CORE_MODULES);
507
- * if (fsd) conda.setModule('FrameShiftDrive', fsd);
508
- * ```
509
- *
510
- * @example
511
- * Write a build back out. Retail credits are what the catalogue prices the fit at; pass
512
- * `credits: 'source'` to export the figures a capture stated it paid instead — see
513
- * {@link ShipLoadout.sourcePurchase}.
514
- *
515
- * ```ts
516
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
517
- *
518
- * declare const build: ShipLoadout;
519
- *
520
- * build.toLoadoutEvent(); // retail: hull cost plus every module's list price
521
- * build.toLoadoutEvent({ credits: 'source' }); // the capture's own figures
522
- * build.toSlefString({ header: { appName: 'MyApp', appVersion: '1.0.0' } });
523
- * ```
524
- */
525
- declare class ShipLoadout {
526
- #private;
527
- private constructor();
528
- /**
529
- * Build from a SLEF export.
530
- *
531
- * @param input - The SLEF JSON string, or an already-parsed SLEF object (see
532
- * {@link parseSlef} for accepted shapes).
533
- * @param index - Which entry to take when the export holds several builds.
534
- * Defaults to the first.
535
- * @returns The loadout for that entry.
536
- * @throws {SyntaxError} If `input` is a string that is not valid JSON.
537
- * @throws {TypeError} If the export holds no usable loadout, or `index` is out of
538
- * range.
539
- */
540
- static fromSlef(input: unknown, index?: number): ShipLoadout;
541
- /**
542
- * Build from a bare journal `Loadout` event (the `data` half of a SLEF entry).
543
- *
544
- * @param event - A `Loadout` event object.
545
- * @returns The loadout.
546
- * @remarks
547
- * Capture/instance state (`timestamp`, `ShipID`, `HullHealth`, `Hot`) and engineering
548
- * provenance (`Engineer`, `EngineerID`, `BlueprintID`) are deliberately excluded
549
- * from the durable build. See {@link LoadoutEvent} and {@link ModuleEngineering}.
550
- * A pre-engineered/reward module is identified from its reported stat signature when
551
- * the evidence uniquely matches a catalogue variant. Its complete fixed stat block is
552
- * then used as the fitted record, including values the capture omits; a separately
553
- * applied experimental effect is included when matching and remains authoritative in
554
- * the captured modifiers. Under-specified or ambiguous evidence stays unidentified.
555
- * An ordinary weapon recipe on a Guardian weapon identifies a final pre-engineered
556
- * article; the import preserves that identity, uses the catalogue's complete hand-set
557
- * stat block when the exact article is known, exposes no engineering options for it,
558
- * and refuses attempts to engineer it further. Explicit journal modifiers remain
559
- * authoritative over that stat block.
560
- *
561
- * The event's credit figures are kept twice over: as the live `hullValue` /
562
- * `modulesValue` / `rebuy`, which an edit may invalidate, and as the immutable
563
- * {@link sourcePurchase} record, which no edit touches.
564
- *
565
- * Modules are imported as one complete snapshot: their array order does not affect
566
- * per-ship count allowances, and any aggregate violation is reported by
567
- * {@link validation}. Use this factory rather than replaying a complete loadout
568
- * through the incremental {@link setModule} editor.
569
- *
570
- * @throws {TypeError} If the event is not shaped like one. What is checked is the
571
- * structure a build is assembled from, and the types of the fields naming things in
572
- * it: `event` must be an object with an array of module objects in `Modules`; each
573
- * module needs a string `Slot` and `Item`, and no two may claim the same slot; a
574
- * module's `Engineering` must be an object, and that block's `Modifiers` an array of
575
- * objects each carrying a string `Label`, whenever their key is there **at all**;
576
- * and `event.Ship`, the block's `BlueprintName` and its `ExperimentalEffect` must be
577
- * strings **when they carry a value**. Every remaining field — every number, every
578
- * flag, a modifier's value beside its label — is trusted, so use
579
- * {@link ShipLoadout.fromSlef} (or {@link parseSlef}) for input you did not produce,
580
- * which reports all of them.
581
- *
582
- * A modifier's `Label` is required rather than checked-when-present because it is
583
- * the only thing saying which stat moved: {@link fittedModuleAt} and the
584
- * pre-engineered identification both read it unconditionally, so an entry without
585
- * one would import cleanly and then break the build it produced.
586
- *
587
- * `Engineering` and its `Modifiers` are the fields where a `null` is not the same as
588
- * an omission, because a relay writing `null` for a block or list it does not have
589
- * would otherwise be read as one. An **absent** `Ship` *is* an omission, and not a
590
- * failure: it is a hull nothing can name, which {@link validation} reports as
591
- * `unknownHull`. Nor is a partial `Engineering` block — a capture may state
592
- * modifiers without naming the recipe.
593
- */
594
- static fromLoadout(event: LoadoutEvent): ShipLoadout;
595
- /**
596
- * Start a new, empty build for a hull — no modules fitted.
597
- *
598
- * @param shipSymbol - The hull's internal symbol, e.g. `"Anaconda"`
599
- * (case-insensitive).
600
- * @returns An empty loadout whose {@link slots} come from the hull's declared
601
- * layout.
602
- * @throws {TypeError} If `shipSymbol` is not a string, or no hull with that symbol
603
- * has a known slot layout.
604
- * @example
605
- * ```ts
606
- * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
607
- *
608
- * ShipLoadout.empty('Sidewinder').slots('hardpoint').length; // -> 2
609
- * ```
610
- */
611
- static empty(shipSymbol: string): ShipLoadout;
612
- /**
613
- * Start a new build with the modules supplied on a stock ship.
614
- *
615
- * @param shipSymbol - The hull's internal symbol, e.g. `"SideWinder"`
616
- * (case-insensitive).
617
- * @returns A complete, ready-to-edit stock loadout. The build is independent of the
618
- * frozen shared catalogue: edits affect this instance only.
619
- * @throws {TypeError} If `shipSymbol` is not a string, or no default loadout exists
620
- * for that hull.
621
- * @remarks
622
- * This batteries-included factory resolves calculations through the complete module
623
- * catalogue already used by `ShipLoadout`. If only the stock slot/module identities
624
- * are needed, `getDefaultLoadout` from `./default-loadouts` avoids that cost.
625
- * @example
626
- * ```ts
627
- * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
628
- *
629
- * const stock = ShipLoadout.default('SideWinder');
630
- * stock.validation.complete; // -> true
631
- * stock.fittedModuleAt('FrameShiftDrive')?.symbol;
632
- * // -> 'Int_Hyperdrive_Size2_Class1'
633
- * ```
634
- */
635
- static default(shipSymbol: string): ShipLoadout;
636
- /** The hull's internal id, e.g. `"explorer_nx"`. */
637
- get shipSymbol(): string;
638
- /** The player-given ship name, or `null` if the build has none. */
639
- get shipName(): string | null;
640
- /** The player-given ID plate, or `null` if the build has none. */
641
- get shipIdent(): string | null;
642
- /**
643
- * Hull + modules mass with an empty tank and no cargo, in tonnes, or `null` if it
644
- * cannot be determined (no `UnladenMass` in the export and either the hull or a
645
- * fitted module has no known mass).
646
- *
647
- * @remarks
648
- * A SLEF export's `UnladenMass` is trusted verbatim. Otherwise the mass is the
649
- * hull's `hullMass` plus every fitted module's mass (post-engineering), with armour
650
- * at the zero-mass lightweight default.
651
- */
652
- get unladenMass(): number | null;
653
- /**
654
- * Unladen mass with diagnostics for every missing input.
655
- *
656
- * @returns A complete imported or computed mass, otherwise `null` plus the hull or
657
- * module fields that prevented the calculation.
658
- */
659
- get unladenMassResult(): CalculationResult<number>;
660
- /**
661
- * Fuel-tank capacities, in tonnes, or `null` when a tank or the hull's reserve
662
- * capacity is unknown. A SLEF export's `FuelCapacity` is used when present;
663
- * otherwise the main capacity is the sum of the fitted fuel tanks and the reserve
664
- * comes from the hull's stats.
665
- */
666
- get fuelCapacity(): FuelCapacity | null;
667
- /** Fuel capacity with diagnostics instead of unknown tanks collapsing to zero. */
668
- get fuelCapacityResult(): CalculationResult<FuelCapacity>;
669
- /**
670
- * Cargo capacity, in tonnes, or `null` when a fitted optional module cannot be
671
- * classified. A SLEF export's `CargoCapacity` is used when present; otherwise it is
672
- * the sum of the fitted cargo racks.
673
- */
674
- get cargoCapacity(): number | null;
675
- /** Cargo capacity with diagnostics instead of unknown racks collapsing to zero. */
676
- get cargoCapacityResult(): CalculationResult<number>;
677
- /**
678
- * Hull cost in credits represented by the build, or `null` if unknown.
679
- *
680
- * @remarks
681
- * This is the live figure, kept coherent with edits: an import's own `HullValue`
682
- * until something invalidates it. For the capture's figure as captured — which no
683
- * edit changes — read {@link sourcePurchase}.
684
- */
685
- get hullValue(): number | null;
686
- /**
687
- * Fitted-modules cost in credits represented by the build, or `null` if
688
- * unknown — including after an edit discarded an import's figure, since no catalogue
689
- * records what a replaced module was bought for. {@link sourcePurchase} keeps the
690
- * captured figure regardless.
691
- */
692
- get modulesValue(): number | null;
693
- /**
694
- * Insurance rebuy cost in credits represented by the build, or `null` if
695
- * unknown. Discarded by an edit for the same reason as {@link modulesValue}, and
696
- * likewise preserved by {@link sourcePurchase}.
697
- */
698
- get rebuy(): number | null;
699
- /**
700
- * What the capture this build came from said was **paid** for it — a read-only
701
- * {@link SourcePurchaseRecord}, or `null` for a build assembled here or imported
702
- * from a capture that quoted no credits at all.
703
- *
704
- * @remarks
705
- * The record is provenance about the source, so it is fixed at import and **survives
706
- * every edit**: fit, remove or engineer whatever you like and it still reports the
707
- * figures the capture carried, for the modules the capture carried them for. That is
708
- * what {@link hullValue}, {@link modulesValue} and {@link rebuy} cannot do — they
709
- * describe the build in hand, so an edit that invalidates one drops it.
710
- *
711
- * The two answer different questions and neither substitutes for the other. A
712
- * captured price belongs to one commander's purchase history, discounts included;
713
- * the library's own figures are catalogue retail. Export picks between them
714
- * explicitly, and quotes retail unless asked otherwise — see
715
- * {@link LoadoutExportOptions.credits}.
716
- *
717
- * @example
718
- * ```ts
719
- * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
720
- * import { getSourceModuleValue } from '@elite-dangerous-almanac/core/ships/source-purchase';
721
- *
722
- * declare const slefJson: string;
723
- *
724
- * const build = ShipLoadout.fromSlef(slefJson);
725
- * const paid = build.sourcePurchase!;
726
- * paid.hullValue; // -> 189326510, as captured
727
- * getSourceModuleValue(paid, 'powerplant')?.value; // -> what that plant cost its owner
728
- *
729
- * build.removeModule('Slot05_Size4');
730
- * build.modulesValue; // -> null unavailable after the edit
731
- * paid.modulesValue; // -> 192625195, the captured figure
732
- * ```
733
- */
734
- get sourcePurchase(): SourcePurchaseRecord | null;
735
- /**
736
- * Structural validity and operational completeness of this build.
737
- *
738
- * @remarks
739
- * Optional, hardpoint and utility mounts may be empty. Armour and all seven core
740
- * mounts must be filled for `complete` to be true. An unknown hull or module is
741
- * incomplete; a module in a nonexistent or incompatible slot is invalid. Exclusive
742
- * families and per-ship module-count allowances must also be satisfied.
743
- */
744
- get validation(): LoadoutValidation;
745
- /**
746
- * Frozen point-in-time views of the hull's mounts in outfitting-panel order.
747
- *
748
- * @param kind - Optionally keep only one mount kind. Omit it for every mount.
749
- * @returns Detached slot views. Repeated reads at the same build version reuse the
750
- * same frozen array and records; every state-changing edit makes the next read produce
751
- * new snapshots.
752
- * @throws {TypeError} If the hull has no known slot layout.
753
- * @example
754
- * ```ts
755
- * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
756
- *
757
- * const emptyHardpoints = ShipLoadout.empty('Sidewinder').slots('hardpoint');
758
- * emptyHardpoints.every((slot) => slot.module === null); // true
759
- * ```
760
- */
761
- slots(kind?: SlotKind): readonly LoadoutSlot[];
762
- /**
763
- * A deeply frozen, point-in-time view of the module in a slot.
764
- *
765
- * @param slotKey - Slot key, matched case-insensitively.
766
- * @returns A detached view, or `null` when the slot is empty or unknown. Repeated
767
- * reads at the same build version reuse the same frozen record; every state-changing
768
- * edit makes the next read produce a new snapshot.
769
- * @throws {TypeError} If `slotKey` is not a string.
770
- */
771
- fittedModuleAt(slotKey: string): FittedModule | null;
772
- /**
773
- * Every fitted module as a deeply frozen point-in-time view.
774
- *
775
- * @returns Detached module snapshots in the order the build carries them. The array
776
- * and every nested record are frozen; query again after an edit for current state.
777
- * @example
778
- * ```ts
779
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
780
- *
781
- * declare const build: ShipLoadout;
782
- *
783
- * build.fittedModules().map((module) => `${module.slot}: ${module.symbol}`);
784
- * ```
785
- */
786
- fittedModules(): readonly FittedModule[];
787
- /**
788
- * Return the computable blueprint candidates for a fitted module symbol.
789
- *
790
- * @param slotKey - Slot key, matched case-insensitively.
791
- * @returns Frozen blueprint descriptors: the ordinary engineering menu first, then
792
- * bespoke Mercenary upgrade recipes. An `'ordinary'` candidate is available to the
793
- * stock module; a `'mercenary'` candidate requires the caller to confirm that the
794
- * fitted article is the matching Mercenary purchase, because its symbol alone cannot.
795
- * Returns an empty array when the slot is empty, unresolved or final, or the module
796
- * symbol has neither route.
797
- * @throws {TypeError} If `slotKey` is not a string.
798
- * @example
799
- * ```ts
800
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
801
- *
802
- * declare const build: ShipLoadout;
803
- *
804
- * build.availableBlueprints('FrameShiftDrive').map(({ fdname }) => fdname);
805
- * ```
806
- */
807
- availableBlueprints(slotKey: string): readonly AvailableBlueprint[];
808
- /**
809
- * Return the computable experimental effects offered to a fitted module.
810
- *
811
- * @param slotKey - Slot key, matched case-insensitively.
812
- * @returns Frozen Frontier effect ids in engineering-menu order, or an empty array
813
- * when the slot is empty, unresolved, final, or has no experimental menu.
814
- * @throws {TypeError} If `slotKey` is not a string.
815
- * @example
816
- * ```ts
817
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
818
- *
819
- * declare const build: ShipLoadout;
820
- *
821
- * build.availableExperimentalEffects('FrameShiftDrive');
822
- * // -> ['special_fsd_heavy', ...]
823
- * ```
824
- */
825
- availableExperimentalEffects(slotKey: string): readonly string[];
826
- /**
827
- * The modules that fit a given slot — its size, kind and any restriction all
828
- * satisfied, with candidates that would worsen a one-per-ship or module-count limit
829
- * omitted.
830
- *
831
- * @param slotKey - The slot key to fit, matched case-insensitively (journal spelling).
832
- * @returns The fitting modules, in complete-catalogue order.
833
- * @throws {RangeError} If the hull has no slot with that key.
834
- * @throws {TypeError} If `slotKey` is not a string, or the hull has no known slot
835
- * layout (a SLEF build on an unrecognised hull).
836
- * @example
837
- * ```ts
838
- * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
839
- *
840
- * ShipLoadout.empty('Anaconda').modulesForSlot('FrameShiftDrive');
841
- * ```
842
- */
843
- modulesForSlot(slotKey: string): OutfittingModule[];
844
- /**
845
- * Fit a module into a slot, replacing whatever is there.
846
- *
847
- * @remarks
848
- * This is an incremental editor: every call must avoid worsening the current
849
- * build's module-count excess. Fit an allowance-increasing module before the weapons
850
- * it permits. To consume a complete order-independent snapshot, use
851
- * {@link ShipLoadout.fromLoadout}.
852
- *
853
- * @param slotKey - The slot key to fit into, matched case-insensitively (journal
854
- * spelling). An occupied slot keeps the key the build already spells it with, so
855
- * fitting into an import never renames one of its mounts.
856
- * @param module - The module to fit (resolve it from a catalogue first, e.g. with
857
- * {@link getModuleBySymbol}). The complete record is snapshotted, so a result from
858
- * `getPreEngineeredStats` or a caller-supplied catalogue keeps its resolved stats.
859
- * @returns `this`, for chaining.
860
- * @throws {RangeError} If the hull has no slot with that key.
861
- * @throws {TypeError} If `slotKey` is not a string; `module` is null/undefined (e.g. a
862
- * `getModuleBySymbol` miss) or is not an outfitting module at all; or the hull has no
863
- * known slot layout (a SLEF build on an unrecognised hull).
864
- * @throws {LoadoutEditError} If the module does not fit the slot (wrong kind, too
865
- * large, or a restriction the module does not satisfy), conflicts with a one-per-ship
866
- * family already fitted elsewhere, or worsens a per-ship module-count excess.
867
- * @example
868
- * ```ts
869
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
870
- *
871
- * declare const build: ShipLoadout;
872
- *
873
- * import { getModuleBySymbol } from '@elite-dangerous-almanac/core/ships/modules';
874
- * import { CORE_MODULES } from '@elite-dangerous-almanac/core/ships/modules-core';
875
- * const fsd = getModuleBySymbol('Int_Hyperdrive_Size6_Class5', CORE_MODULES)!;
876
- * const tank = getModuleBySymbol('Int_FuelTank_Size6_Class3', CORE_MODULES)!;
877
- * build.setModule('FrameShiftDrive', fsd).setModule('Slot01_Size7', tank);
878
- * ```
879
- */
880
- setModule(slotKey: string, module: OutfittingModule): this;
881
- /**
882
- * Empty a slot.
883
- *
884
- * @param slotKey - The slot key to clear, matched case-insensitively (journal
885
- * spelling).
886
- * @returns `this`, for chaining. Clearing an already-empty slot is a no-op.
887
- * @throws {TypeError} If `slotKey` is not a string.
888
- * @throws {LoadoutEditError} If the slot is the built-in cargo hatch, which cannot
889
- * be removed or replaced, or removing the module would worsen a per-ship
890
- * module-count excess.
891
- */
892
- removeModule(slotKey: string): this;
893
- /**
894
- * Engineer the module in a slot — apply a blueprint (with a grade and quality) and
895
- * an optional experimental effect, computing the resulting stat modifiers.
896
- *
897
- * The modifiers are stored with journal-equivalent labels and numeric values on the
898
- * fitted module, so the build's jump-range and mass calculations pick them up
899
- * automatically. The optional journal display-direction hint `LessIsGood` is omitted.
900
- * The block keeps the `BlueprintName` you passed, so it reads back the way the build
901
- * declared it. Values use Frontier's float32 arithmetic, and weapon recipe internals
902
- * such as `BurstInterval` are exposed as the derived `RateOfFire` and
903
- * `DamagePerSecond` labels a journal writes. Module-specific aliases likewise use the
904
- * journal spelling (`MaximumRange` for a module's maximum range and `Range` for a
905
- * scanner range).
906
- * Recipe-only values remain available through {@link FittedModule.effectiveStats} and
907
- * build calculations even though a journal does not serialize their labels; this is
908
- * what keeps burst and reload-cycle calculations faithful after applying a recipe.
909
- *
910
- * **Which recipe an id names can depend on the module.** The game writes
911
- * `Sensor_LongRange` and `Sensor_WideAngle` for both a sensor suite's modification and a
912
- * utility scanner's, and the two roll different stats in opposite directions — Long
913
- * Range costs the suite mass and the scanner power draw. So the id is resolved against
914
- * the module's menu before anything is computed, and a wake scanner engineered
915
- * `Sensor_LongRange` gets the scanner's numbers, which `BLUEPRINTS` keys
916
- * `Scanner_LongRange`. Reading a stored block back the same way means resolving it the
917
- * same way: `resolveBlueprintForModule` in `ships/blueprint-journal` is that lookup.
918
- *
919
- * @param slotKey - The slot whose module to engineer, matched case-insensitively
920
- * (journal spelling).
921
- * @param fdname - The blueprint recipe's Frontier `fdname`, e.g. `"FSD_LongRange"`.
922
- * @param options - {@link ApplyBlueprintOptions}: `grade` (1–5), optional `quality`
923
- * (0–1, default 1), and optional `experimental` effect `fdname`. A nullish
924
- * `experimental` means no effect, the same as leaving it out. Each is read once,
925
- * before anything is checked, so an accessor cannot answer the check and the use
926
- * differently.
927
- * @returns `this`, for chaining.
928
- * @throws {RangeError} If the slot is empty, or the blueprint/grade/experimental is
929
- * unknown, or `quality` is outside `[0, 1]`.
930
- * @throws {TypeError} If `slotKey` or `fdname` is not a string, `options` is not an
931
- * object, or `options.experimental` carries a value that is not a string — a nullish
932
- * one is no effect, not a wrong type; the fitted module has no stats to engineer; or the id names a
933
- * fixed event-reward identity, which names no craftable recipe; or the module is not offered the blueprint — by
934
- * its engineering menu, by the journal spelling of an entry on that menu, by the
935
- * generic spelling of a recipe that menu lists under a family's name, or by being a
936
- * Mercenary article sold at grade 1 with that bespoke recipe; the fitted article is
937
- * final and accepts no further engineering;
938
- * or the module is not offered the experimental effect, which its
939
- * menu alone decides; or the catalogue does not carry every base stat the recipe
940
- * modifies. Incomplete engineering is rejected rather than stored as a partial journal
941
- * modifier block.
942
- * @example
943
- * ```ts
944
- * import { getModuleBySymbol } from '@elite-dangerous-almanac/core/ships/modules';
945
- * import { CORE_MODULES } from '@elite-dangerous-almanac/core/ships/modules-core';
946
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
947
- *
948
- * declare const build: ShipLoadout;
949
- *
950
- * const fsd = getModuleBySymbol('Int_Hyperdrive_Size6_Class5', CORE_MODULES)!;
951
- *
952
- * build.setModule('FrameShiftDrive', fsd)
953
- * .applyBlueprint('FrameShiftDrive', 'FSD_LongRange', {
954
- * grade: 5,
955
- * experimental: 'special_fsd_heavy',
956
- * });
957
- * build.maxJumpRange(); // uses the engineered optimal mass
958
- * ```
959
- */
960
- applyBlueprint(slotKey: string, fdname: string, options: ApplyBlueprintOptions): this;
961
- /**
962
- * Fit a pre-engineered variant into a slot, replacing whatever is there.
963
- *
964
- * The variant's fixed stats and journal engineering block are resolved together.
965
- * Articles carry `Level`, `Quality: 1`, any baked experimental effect and their fixed
966
- * modifiers. Because the variant names its base module, a decorative identity cannot
967
- * be applied to an unrelated damage-bearing module.
968
- * A Mercenary variant whose fixed modifier block has not been published retains the
969
- * stock catalogue stats and omits `Modifiers` rather than claiming it changes none.
970
- *
971
- * @param slotKey - The slot key to fit into, matched case-insensitively.
972
- * @param variant - The pre-engineered catalogue variant to fit.
973
- * @returns `this`, for chaining.
974
- * @throws {TypeError} If `variant` is not a pre-engineered variant or one of its
975
- * authored modifier labels cannot be resolved for its base module.
976
- * @throws {RangeError} If no catalogue row matches the supplied module, blueprint,
977
- * grade, experimental effect and acquisition route.
978
- * @throws {LoadoutEditError} If the variant's base module does not fit the slot or
979
- * violates a fitted-module limit.
980
- * @example
981
- * ```ts
982
- * import { getPreEngineeredVariants } from '@elite-dangerous-almanac/core/ships/pre-engineered';
983
- * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
984
- *
985
- * const festive = getPreEngineeredVariants('Hpt_FlakMortar_Turret_Medium')
986
- * .find((variant) => variant.blueprint === 'Decorative_Red')!;
987
- * const build = ShipLoadout.empty('Krait_MkII')
988
- * .setPreEngineeredVariant('MediumHardpoint1', festive);
989
- * build.fittedModuleAt('MediumHardpoint1')?.effectiveStats?.damage; // -> 0.34
990
- * ```
991
- */
992
- setPreEngineeredVariant(slotKey: string, variant: PreEngineeredVariant): this;
993
- /**
994
- * Strip engineering from a slot's module,
995
- * restoring its base stats.
996
- *
997
- * @param slotKey - The slot to de-engineer, matched case-insensitively (journal
998
- * spelling).
999
- * @returns `this`, for chaining. A no-op if the slot is empty or unmodified.
1000
- * @throws {TypeError} If `slotKey` is not a string, or the fitted article is final
1001
- * pre-engineered and its baked engineering cannot be removed.
1002
- */
1003
- clearEngineering(slotKey: string): this;
1004
- /**
1005
- * Switch a fitted module on or off.
1006
- *
1007
- * @param slotKey - The slot's journal key, e.g. `"PowerPlant"`, matched
1008
- * case-insensitively.
1009
- * @param on - `true` to power it, `false` to switch it off.
1010
- * @returns `this`, for chaining.
1011
- * @throws {RangeError} If the slot is empty.
1012
- * @throws {TypeError} If `slotKey` is not a string.
1013
- * @example
1014
- * ```ts
1015
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1016
- *
1017
- * declare const build: ShipLoadout;
1018
- *
1019
- * build.setModuleEnabled('TinyHardpoint6', false); // an unpowered heat sink
1020
- * ```
1021
- */
1022
- setModuleEnabled(slotKey: string, on: boolean): this;
1023
- /**
1024
- * Set a fitted module's power-priority group.
1025
- *
1026
- * @param slotKey - The slot's journal key, matched case-insensitively.
1027
- * @param priority - The journal's **zero-based** group, `0`–`4`. Note that the
1028
- * outfitting panel — and {@link powerBudget}'s `bands[].priority` — number the same
1029
- * five groups `1`–`5`.
1030
- * @returns `this`, for chaining.
1031
- * @throws {RangeError} If the slot is empty, or `priority` is not an integer in `[0, 4]`.
1032
- * @throws {TypeError} If `slotKey` is not a string.
1033
- */
1034
- setModulePriority(slotKey: string, priority: number): this;
1035
- /**
1036
- * This build as a journal `Loadout` event — the `data` half of a SLEF entry.
1037
- *
1038
- * @param options - Module ordering and how sparse to be about power state.
1039
- * @returns A fresh event. Every top-level figure is **recomputed** from the hull and
1040
- * the fitted modules rather than echoed from whatever an import supplied — the one
1041
- * exception being the credits, when `credits: 'source'` asks for the capture's own.
1042
- * Any figure that cannot be worked out is **left out** rather than emitted as a stale
1043
- * or zero value — SLEF requires nothing beyond `Ship` and `Modules`.
1044
- *
1045
- * Credits are quoted at **retail** by default: the bare hull's `hullCost` plus every
1046
- * fitted module's catalogue list price, with `Rebuy` 5% of the two. A source's own
1047
- * `HullValue` / `ModulesValue` / `Value` figures are deliberately not quoted here,
1048
- * because they record one commander's purchase history — the Deep Black's modules
1049
- * are all 12.25% off list — and purchase discounts are not a property of the build.
1050
- * They are not lost either: pass `credits: 'source'` to export the
1051
- * {@link sourcePurchase} record instead, as provenance rather than as a price.
1052
- * @example
1053
- * ```ts
1054
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1055
- *
1056
- * declare const build: ShipLoadout;
1057
- *
1058
- * const event = build.toLoadoutEvent();
1059
- * event.MaxJumpRange; // recomputed, not the exporter's claim
1060
- * event.HullValue; // the catalogue's list price
1061
- *
1062
- * build.toLoadoutEvent({ credits: 'source' }).HullValue; // what the capture paid
1063
- * ```
1064
- */
1065
- toLoadoutEvent(options?: LoadoutExportOptions): LoadoutEvent;
1066
- /**
1067
- * This build as a one-entry SLEF export.
1068
- *
1069
- * @param options - Ordering, power state, and the envelope header.
1070
- * @returns The export. Several builds travel together as
1071
- * `toSlef([a.toLoadoutEvent(), b.toLoadoutEvent()])` using the function of the same
1072
- * name from `./slef`.
1073
- */
1074
- toSlef(options: SlefExportOptions): Slef;
1075
- /**
1076
- * This build as SLEF JSON — ready to write to a file or put on the clipboard.
1077
- *
1078
- * @param options - As {@link toSlef}, plus `indent` (compact by default).
1079
- * @example
1080
- * ```ts
1081
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1082
- *
1083
- * declare const build: ShipLoadout;
1084
- *
1085
- * build.toSlefString({ header: { appName: 'MyApp', appVersion: '1.0.0' } });
1086
- * ```
1087
- */
1088
- toSlefString(options: SlefExportOptions): string;
1089
- /**
1090
- * The resolved frame-shift-drive constants for this build — post-engineering,
1091
- * with any Guardian FSD Booster folded into `jumpBoost`.
1092
- *
1093
- * @throws {TypeError} If the build has no frame shift drive, or its required jump
1094
- * constants are missing from the stats catalogue.
1095
- */
1096
- get frameShiftDrive(): FrameShiftDriveParams;
1097
- /**
1098
- * The fitted frame shift drive's dimensionless mass factor at a chosen load.
1099
- *
1100
- * @param options - {@link JumpOptions}. `fuel` defaults to a full main tank and
1101
- * `cargo` to `0`.
1102
- * @returns `optMass / loadedMass`: `1` at the drive's optimised mass, below `1`
1103
- * above it and above `1` below it.
1104
- * @remarks
1105
- * This is the mass term used by the jump equation, not the three-point performance
1106
- * curve used by thrusters and shield generators. Main-tank fuel contributes to the
1107
- * loaded mass; the Guardian FSD Booster's additive range does not contribute to the
1108
- * factor.
1109
- * @throws {TypeError} If the build has no usable frame shift drive or its mass
1110
- * cannot be determined; also if fuel capacity is unknown and `options.fuel` is
1111
- * omitted.
1112
- * @throws {RangeError} If fuel or cargo is not finite and non-negative, or loaded
1113
- * mass is zero.
1114
- * @example
1115
- * ```ts
1116
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1117
- *
1118
- * declare const build: ShipLoadout;
1119
- * build.frameShiftDriveMassFactor({ fuel: 8, cargo: 32 }); // dimensionless
1120
- * ```
1121
- */
1122
- frameShiftDriveMassFactor(options?: JumpOptions): number;
1123
- /**
1124
- * Best single-jump range, in light-years — no cargo, and exactly one jump's fuel
1125
- * aboard (the lightest the ship jumps). This is the figure the game and EDSY label
1126
- * "maximum jump range".
1127
- *
1128
- * @remarks
1129
- * Returns `0` when no fuel is available — an assembled build with no fuel tank
1130
- * fitted has an empty main tank, so there is nothing to jump on.
1131
- * @returns The best single jump, in light-years.
1132
- * @throws {TypeError} If the build has no usable frame shift drive, or its mass or
1133
- * fuel capacity cannot be determined.
1134
- */
1135
- maxJumpRange(): number;
1136
- /**
1137
- * The range of a single jump for a chosen fuel and cargo load, in light-years.
1138
- *
1139
- * @param options - {@link JumpOptions}. `fuel` defaults to a full main tank,
1140
- * `cargo` to `0`.
1141
- * @returns The jump's range, in light-years.
1142
- * @throws {TypeError} If the build has no usable frame shift drive or its mass
1143
- * cannot be determined; also if fuel capacity is unknown and `options.fuel` is
1144
- * omitted.
1145
- * @throws {RangeError} If fuel or cargo is not finite and non-negative.
1146
- */
1147
- jumpRange(options?: JumpOptions): number;
1148
- /**
1149
- * Single-jump range on a full tank with a full cargo hold, in light-years.
1150
- *
1151
- * @returns The jump's range, in light-years.
1152
- * @throws {TypeError} If the build has no usable frame shift drive, or its mass,
1153
- * fuel capacity or cargo capacity cannot be determined.
1154
- */
1155
- ladenJumpRange(): number;
1156
- /**
1157
- * The fuel a single jump of a given distance costs, in tonnes.
1158
- *
1159
- * @param distance - The jump distance, in light-years.
1160
- * @param options - {@link JumpOptions}. `fuel` defaults to a full main tank,
1161
- * `cargo` to `0`.
1162
- * @returns Fuel used, in tonnes (capped at the drive's max fuel per jump).
1163
- * @throws {TypeError} If the build has no usable frame shift drive or its mass
1164
- * cannot be determined; also if fuel capacity is unknown and `options.fuel` is
1165
- * omitted.
1166
- * @throws {RangeError} If fuel or cargo is not finite and non-negative.
1167
- */
1168
- fuelPerJump(distance: number, options?: JumpOptions): number;
1169
- /**
1170
- * Total range and jump count for a chosen fuel and cargo load.
1171
- *
1172
- * @param options - {@link JumpOptions}. `fuel` defaults to a full main tank,
1173
- * `cargo` to `0`.
1174
- * @returns Summed range in light-years and the jumps made before the tank is empty.
1175
- * @throws {TypeError} If the build has no usable frame shift drive, or its mass or
1176
- * fuel capacity cannot be determined; fuel capacity is not required when
1177
- * `options.fuel` is supplied.
1178
- * @throws {RangeError} If fuel or cargo is not finite and non-negative, or the
1179
- * fuel load would require more than 100,000 jumps.
1180
- * @example
1181
- * ```ts
1182
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1183
- *
1184
- * declare const build: ShipLoadout;
1185
- * build.totalRange().jumps; // jumps available from one full main tank
1186
- * build.totalRange({ fuel: 8, cargo: 32 }).range; // range for that partial load
1187
- * ```
1188
- */
1189
- totalRange(options?: JumpOptions): TotalRangeDetails;
1190
- /**
1191
- * Every jump figure at once — best, unladen, laden, and each load's total.
1192
- *
1193
- * @returns The {@link JumpRangeSummary}. Single-jump figures and each total's
1194
- * `range` are in light-years. For a partial load, call {@link jumpRange} for one
1195
- * jump or {@link totalRange} for every jump with the `fuel` and `cargo` you
1196
- * actually have.
1197
- * @throws {TypeError} If the build has no usable frame shift drive, or its mass,
1198
- * fuel capacity or cargo capacity cannot be determined.
1199
- * @example
1200
- * ```ts
1201
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1202
- *
1203
- * declare const build: ShipLoadout;
1204
- *
1205
- * const jumps = build.jumpRangeSummary();
1206
- * jumps.max; // -> 89.41 (one jump's fuel, empty hold)
1207
- * jumps.laden; // -> the range with the hold full
1208
- * jumps.totalMax.jumps; // the best jump expressed as a total
1209
- * // Half a tank and 32 t aboard, once the tank is known:
1210
- * const fuel = build.fuelCapacityResult;
1211
- * if (fuel.complete) build.jumpRange({ fuel: fuel.value.main / 2, cargo: 32 });
1212
- * ```
1213
- */
1214
- jumpRangeSummary(): JumpRangeSummary;
1215
- /**
1216
- * The build's power budget: what the plant makes, what the modules draw with
1217
- * hardpoints retracted and deployed, and which priority groups stay lit.
1218
- *
1219
- * Draws are post-engineering, modules switched off in the journal are skipped, and
1220
- * weapons (plus the utility fittings that are not always powered) count only
1221
- * towards the deployed total.
1222
- *
1223
- * @returns The {@link PowerBudget}. With no power plant fitted, `available` is `0`
1224
- * and nothing is powered. A fitted module whose draw the catalogue cannot supply is
1225
- * named in {@link PowerBudget.unknownDraws} rather than counted as drawing nothing,
1226
- * which makes every total a lower bound while that list is non-empty.
1227
- * @example
1228
- * ```ts
1229
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1230
- *
1231
- * declare const build: ShipLoadout;
1232
- *
1233
- * const power = build.powerBudget();
1234
- * power.available; // -> 20.4 MW generated
1235
- * power.deployed; // -> 19.02 MW drawn, hardpoints out
1236
- * power.withinBudget; // -> true
1237
- * power.bands[4]?.poweredDeployed; // -> is priority group 5 still lit?
1238
- * ```
1239
- */
1240
- powerBudget(): PowerBudget;
1241
- /**
1242
- * The build's heat: what it idles at, what it runs at flying and jumping, and
1243
- * whether firing everything cooks it.
1244
- *
1245
- * Every figure is post-engineering. The heat a build makes follows what the plant
1246
- * actually feeds, so a module switched off — or one in a priority group the plant
1247
- * cannot keep lit — contributes nothing.
1248
- *
1249
- * @returns The {@link HeatMetrics}, or `null` when the build has no powered power
1250
- * plant or its hull is unknown. A build carrying a module the catalogues cannot
1251
- * resolve is answered rather than refused, with that module named in
1252
- * {@link HeatMetrics.unknownDraws}: read what that entry says about the figures
1253
- * before showing them, because they are then a projection over the rest of the build
1254
- * rather than an answer for it.
1255
- * @example
1256
- * ```ts
1257
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1258
- *
1259
- * declare const build: ShipLoadout;
1260
- *
1261
- * const heat = build.heatMetrics();
1262
- * heat?.idle.gauge; // -> 0.23, i.e. the gauge reads 23%
1263
- * heat?.firingSustained.overheats; // -> false: the guns run cool enough to hold
1264
- * heat?.firingDrained.secondsToOverheat; // -> how long an alpha strike has on an empty WEP
1265
- * ```
1266
- */
1267
- heatMetrics(): HeatMetrics | null;
1268
- /**
1269
- * The build's speed, boost and rotation rates at a chosen load and ENG allocation.
1270
- *
1271
- * @remarks
1272
- * Main-tank fuel contributes to the flight model's loaded mass. Reserve-tank fuel
1273
- * does not: although the statistics panel includes it in the displayed current
1274
- * mass, ten observed builds reproduce their angular rates only when the reserve is
1275
- * excluded from the thruster mass curve.
1276
- *
1277
- * @param options - Fuel defaults to a full main tank, cargo to `0`, and ENG pips to `4`.
1278
- * @returns Loaded {@link MobilityMetrics}, or `null` when no powered, fully described
1279
- * thrusters are fitted.
1280
- * @throws {TypeError} If mass or an omitted main-tank fuel load cannot be determined.
1281
- * @throws {RangeError} If fuel or cargo is not finite and non-negative, or
1282
- * `enginesPips` is outside `[0, 4]`.
1283
- * @example
1284
- * ```ts
1285
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1286
- *
1287
- * declare const build: ShipLoadout;
1288
- * build.mobilityMetrics({ cargo: 32, fuel: 8, enginesPips: 2 })?.speed; // -> m/s
1289
- * ```
1290
- */
1291
- mobilityMetrics(options?: MobilityOptions): MobilityMetrics | null;
1292
- /**
1293
- * The build's shields: strength in megajoules, where it comes from, and the
1294
- * effective resistances.
1295
- *
1296
- * Shield strength scales with the **hull's** mass, not the build's, so fitting
1297
- * more modules never weakens it. Boosters, Guardian shield reinforcement and any
1298
- * engineering are all folded in; switched-off modules are ignored.
1299
- *
1300
- * @param options - {@link DefenceOptions}. `systemsPips` (0–4) folds the SYS
1301
- * capacitor's own resistance into the reported figures; it defaults to `0`, which
1302
- * is what an outfitting screen shows.
1303
- * @returns The {@link ShieldMetrics}, or `null` when the build has no shield
1304
- * generator fitted (or has one switched off).
1305
- * @example
1306
- * ```ts
1307
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1308
- *
1309
- * declare const build: ShipLoadout;
1310
- *
1311
- * const shields = build.shieldMetrics();
1312
- * shields?.strength; // -> MJ
1313
- * shields?.resistances.thermal; // -> negative on a stock generator
1314
- * build.shieldMetrics({ systemsPips: 4 })?.resistances.thermal; // -> with 4 pips to SYS
1315
- * ```
1316
- */
1317
- shieldMetrics(options?: DefenceOptions): ShieldMetrics | null;
1318
- /**
1319
- * Time for this build's shield to rise after collapse and then regenerate to full.
1320
- *
1321
- * @param options - SYS pips in `[0, 4]`, defaulting to `4`.
1322
- * @returns Recovery rates and seconds, or `null` with no powered shield generator.
1323
- * A missing distributor or insufficient zero-pip recharge produces `Infinity`.
1324
- * @throws {RangeError} If `systemsPips` is outside `[0, 4]` or not finite.
1325
- * @example
1326
- * ```ts
1327
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1328
- *
1329
- * declare const build: ShipLoadout;
1330
- * build.shieldRecovery({ systemsPips: 4 })?.recoveryTime; // -> seconds from collapse to 50%
1331
- * ```
1332
- */
1333
- shieldRecovery(options?: DefenceOptions): ShieldRecovery | null;
1334
- /**
1335
- * Every fitted shield cell bank and the complete rearmed reinforcement pool.
1336
- *
1337
- * @returns A frozen {@link CellBankSummary}; no banks is an empty list and zero totals.
1338
- * @example
1339
- * ```ts
1340
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1341
- *
1342
- * declare const build: ShipLoadout;
1343
- * build.cellBanks().totalRestorable; // -> MJ across every fitted cell
1344
- * ```
1345
- */
1346
- cellBanks(): CellBankSummary;
1347
- /**
1348
- * Price this build from current catalogue list prices without creating a journal event.
1349
- *
1350
- * @returns Hull, module and five-percent rebuy credits. `modules` and `rebuy` remain
1351
- * lower bounds when {@link RetailCredits.unpriced} is non-empty; built-in hull fittings are free.
1352
- * @example
1353
- * ```ts
1354
- * import { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1355
- *
1356
- * ShipLoadout.default('Anaconda').retailCredits().hull; // -> 142456440
1357
- * ```
1358
- */
1359
- retailCredits(): RetailCredits;
1360
- /**
1361
- * The build's armour: hull hit points, the bulkhead and reinforcement each
1362
- * contribute, and the effective resistances.
1363
- *
1364
- * @returns The {@link ArmourMetrics}. A build with no armour module fitted is
1365
- * reported on the stock lightweight alloy the hull leaves the shipyard with, which
1366
- * is what the game does.
1367
- * @example
1368
- * ```ts
1369
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1370
- *
1371
- * declare const build: ShipLoadout;
1372
- *
1373
- * const hull = build.armourMetrics();
1374
- * hull.hitPoints; // -> total hull points
1375
- * hull.resistances.explosive; // -> lightweight alloy is explosively weak
1376
- * hull.effectiveHitPoints.thermal; // -> thermal damage the hull can soak
1377
- * ```
1378
- */
1379
- armourMetrics(): ArmourMetrics;
1380
- /**
1381
- * The build's firepower: DPS, sustained DPS, weapons-capacitor draw, heat and power
1382
- * draw for every fitted weapon, plus the totals.
1383
- *
1384
- * Every figure is post-engineering. A weapon switched off in the journal is still
1385
- * listed — with its own metrics — but left out of the totals.
1386
- *
1387
- * @returns The {@link BuildWeaponMetrics}.
1388
- * @example
1389
- * ```ts
1390
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1391
- *
1392
- * declare const build: ShipLoadout;
1393
- *
1394
- * const guns = build.weaponMetrics();
1395
- * guns.total.damagePerSecond; // -> burst DPS across the hardpoints
1396
- * guns.total.sustainedDamagePerSecond; // -> with reloads folded in
1397
- * guns.total.energyPerSecond; // -> MW asked of the WEP capacitor
1398
- * guns.total.powerDraw; // -> MW asked of the power plant when deployed
1399
- * guns.weapons[0]?.metrics.damageByType.thermal;
1400
- * guns.weapons[0]?.ammunition?.total; // -> rounds aboard when fully rearmed
1401
- * ```
1402
- */
1403
- weaponMetrics(): BuildWeaponMetrics;
1404
- /**
1405
- * WEP-capacitor recharge and endurance while every powered weapon fires.
1406
- *
1407
- * @param options - WEP pips in `[0, 4]`, defaulting to `4`.
1408
- * @returns Actual recharge, sustained draw, net drain and seconds from full to
1409
- * empty. The deployed power budget is applied to the distributor and weapons, so a
1410
- * module the plant sheds contributes nothing. A module with an unresolved power
1411
- * draw is assumed powered, consistently with {@link powerBudget}; inspect its
1412
- * `unknownDraws` when that distinction matters. With no powered distributor,
1413
- * capacity and recharge are zero. A load that draws no more than recharge reports
1414
- * `Infinity` for `timeToDrain`.
1415
- * @throws {RangeError} If `weaponsPips` is outside `[0, 4]` or not finite.
1416
- * @example
1417
- * ```ts
1418
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1419
- *
1420
- * declare const build: ShipLoadout;
1421
- * build.weaponsCapacitorMetrics({ weaponsPips: 2 }).timeToDrain; // seconds
1422
- * ```
1423
- */
1424
- weaponsCapacitorMetrics(options?: WeaponsOptions): WeaponsCapacitorMetrics;
1425
- /**
1426
- * All three power-distributor capacitors at selected pip allocations.
1427
- *
1428
- * @param options - SYS, ENG and WEP pips in `[0, 4]`, each defaulting
1429
- * independently to `4`. The allocations need not sum to six, which permits
1430
- * independent comparisons of the three maxima.
1431
- * @returns Capacity, rated four-pip recharge and actual pip-scaled recharge for
1432
- * SYS, ENG and WEP, or `null` when no distributor is fitted, it is switched off,
1433
- * its six capacitor stats cannot be resolved, or the retracted power budget sheds
1434
- * it. The retracted state represents the distributor itself; firing endurance in
1435
- * {@link weaponsCapacitorMetrics} separately applies the deployed state. A module
1436
- * with unresolved power draw is assumed powered, consistently with
1437
- * {@link powerBudget}; inspect its `unknownDraws` when that distinction matters.
1438
- * @throws {RangeError} If any pip allocation is outside `[0, 4]` or not finite.
1439
- * @example
1440
- * ```ts
1441
- * import type { ShipLoadout } from '@elite-dangerous-almanac/core/ships/ship-loadout';
1442
- *
1443
- * declare const build: ShipLoadout;
1444
- * const distributor = build.distributorMetrics({
1445
- * systemsPips: 2,
1446
- * enginesPips: 2,
1447
- * weaponsPips: 2,
1448
- * });
1449
- * distributor?.engines.rechargeRate; // MJ/s
1450
- * ```
1451
- */
1452
- distributorMetrics(options?: DistributorOptions): DistributorMetrics | null;
1453
- }
1454
-
1455
- export { type ApplyBlueprintOptions as A, type BuildWeaponMetrics as B, type DefenceOptions as D, type FittedModule as F, type ImmovableReason as I, type JumpOptions as J, LoadoutEditError as L, type MobilityOptions as M, type RetailCredits as R, ShipLoadout as S, type WeaponsOptions as W, type AvailableBlueprint as a, type DistributorOptions as b, type FittedWeaponMetrics as c, type JumpRangeSummary as d, type LoadoutEditErrorCode as e, type LoadoutExportOptions as f, type LoadoutSlot as g, type SlefExportOptions as h };