@elite-dangerous-almanac/core 0.2.6 → 0.2.8

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 (149) hide show
  1. package/PROVENANCE/equipment/SOURCES.md +149 -27
  2. package/PROVENANCE/i18n/SOURCES.md +251 -40
  3. package/PROVENANCE/ships/SOURCES.md +232 -63
  4. package/README.md +21 -6
  5. package/THIRD_PARTY_NOTICES.md +4 -1
  6. package/dist/{chunk-AAU7OM47.js → chunk-2OBUSQA7.js} +1 -1
  7. package/dist/{chunk-AAU7OM47.js.map → chunk-2OBUSQA7.js.map} +1 -1
  8. package/dist/chunk-3GNYPM6J.js +1 -0
  9. package/dist/chunk-3GNYPM6J.js.map +1 -0
  10. package/dist/{chunk-A6OINYNP.js → chunk-3WGXGBW5.js} +1 -1
  11. package/dist/{chunk-ABSDZINM.js → chunk-5YEHJM6V.js} +1 -1
  12. package/dist/{chunk-ABSDZINM.js.map → chunk-5YEHJM6V.js.map} +1 -1
  13. package/dist/{chunk-7S7ZTZZY.js → chunk-7EWZGDWY.js} +1 -1
  14. package/dist/chunk-A7IVMYNF.js +1 -0
  15. package/dist/chunk-A7IVMYNF.js.map +1 -0
  16. package/dist/{chunk-KPJ77VPS.js → chunk-BJU27V6C.js} +1 -1
  17. package/dist/{chunk-KPJ77VPS.js.map → chunk-BJU27V6C.js.map} +1 -1
  18. package/dist/chunk-C3LLPLLD.js +1 -0
  19. package/dist/chunk-C3LLPLLD.js.map +1 -0
  20. package/dist/chunk-DAQ3PJ4R.js +1 -0
  21. package/dist/chunk-DAQ3PJ4R.js.map +1 -0
  22. package/dist/chunk-DHACRCLI.js +1 -0
  23. package/dist/chunk-DHACRCLI.js.map +1 -0
  24. package/dist/chunk-EHFHRQEP.js +1 -0
  25. package/dist/{chunk-LOWDDWDT.js.map → chunk-EHFHRQEP.js.map} +1 -1
  26. package/dist/{chunk-YCGISPDR.js → chunk-ETWMPMZP.js} +1 -1
  27. package/dist/chunk-FJIWJYKO.js +1 -0
  28. package/dist/chunk-FJIWJYKO.js.map +1 -0
  29. package/dist/chunk-GIGRBFIJ.js +1 -0
  30. package/dist/{chunk-MGRDB2BZ.js.map → chunk-GIGRBFIJ.js.map} +1 -1
  31. package/dist/{chunk-4CBMQULR.js → chunk-IMNOEFUE.js} +1 -1
  32. package/dist/{chunk-4CBMQULR.js.map → chunk-IMNOEFUE.js.map} +1 -1
  33. package/dist/chunk-IN36YSS4.js +1 -0
  34. package/dist/chunk-IN36YSS4.js.map +1 -0
  35. package/dist/chunk-IUSMAX5T.js +1 -0
  36. package/dist/{chunk-6GJHUWMO.js.map → chunk-IUSMAX5T.js.map} +1 -1
  37. package/dist/{chunk-XQJERZUS.js → chunk-JCYOE3C4.js} +1 -1
  38. package/dist/{chunk-XQJERZUS.js.map → chunk-JCYOE3C4.js.map} +1 -1
  39. package/dist/chunk-LWNBWRKL.js +1 -0
  40. package/dist/{chunk-B7EYTZRK.js.map → chunk-LWNBWRKL.js.map} +1 -1
  41. package/dist/chunk-M6J7KP7G.js +1 -0
  42. package/dist/chunk-M6J7KP7G.js.map +1 -0
  43. package/dist/chunk-MT5AHPNP.js +1 -0
  44. package/dist/{chunk-3CKWT2F3.js.map → chunk-MT5AHPNP.js.map} +1 -1
  45. package/dist/chunk-OLKOWR7S.js +1 -0
  46. package/dist/chunk-OLKOWR7S.js.map +1 -0
  47. package/dist/chunk-QJECDMQQ.js +1 -0
  48. package/dist/{chunk-LZYHBFJC.js.map → chunk-QJECDMQQ.js.map} +1 -1
  49. package/dist/chunk-SC4MEHVY.js +1 -0
  50. package/dist/chunk-SC4MEHVY.js.map +1 -0
  51. package/dist/chunk-T7IQHFQ2.js +1 -0
  52. package/dist/chunk-T7IQHFQ2.js.map +1 -0
  53. package/dist/chunk-W5ULI75F.js +1 -0
  54. package/dist/chunk-W5ULI75F.js.map +1 -0
  55. package/dist/chunk-WV7QCDL5.js +1 -0
  56. package/dist/chunk-WV7QCDL5.js.map +1 -0
  57. package/dist/{chunk-SDXUPP4P.js → chunk-XSLPWM5N.js} +1 -1
  58. package/dist/{chunk-S3MONE55.js → chunk-YJAH6DFY.js} +1 -1
  59. package/dist/chunk-YJAH6DFY.js.map +1 -0
  60. package/dist/chunk-ZZTDEDXJ.js +1 -0
  61. package/dist/{chunk-FA5JVXVG.js.map → chunk-ZZTDEDXJ.js.map} +1 -1
  62. package/dist/equipment/engineering.d.ts +53 -1
  63. package/dist/equipment/engineering.js +1 -1
  64. package/dist/equipment/index.d.ts +13 -2
  65. package/dist/equipment/index.js +1 -1
  66. package/dist/equipment/modification-journal.js +1 -1
  67. package/dist/equipment/modifications.d.ts +32 -9
  68. package/dist/equipment/modifications.js +1 -1
  69. package/dist/equipment/suits.d.ts +32 -2
  70. package/dist/equipment/suits.js +1 -1
  71. package/dist/equipment/upgrade-costs.js +1 -1
  72. package/dist/equipment/weapons.d.ts +16 -2
  73. package/dist/equipment/weapons.js +1 -1
  74. package/dist/i18n/index.d.ts +76 -0
  75. package/dist/i18n/index.js +1 -1
  76. package/dist/i18n/modules.d.ts +10 -7
  77. package/dist/i18n/modules.js +1 -1
  78. package/dist/i18n/personal-modifications.d.ts +68 -0
  79. package/dist/i18n/personal-modifications.js +1 -0
  80. package/dist/i18n/personal-modifications.js.map +1 -0
  81. package/dist/i18n/personal-weapons.d.ts +33 -0
  82. package/dist/i18n/personal-weapons.js +1 -0
  83. package/dist/i18n/personal-weapons.js.map +1 -0
  84. package/dist/i18n/pre-engineered.js +1 -1
  85. package/dist/i18n/suits.d.ts +59 -0
  86. package/dist/i18n/suits.js +1 -0
  87. package/dist/i18n/suits.js.map +1 -0
  88. package/dist/materials/index.js +1 -1
  89. package/dist/materials/micro-resources-all.js +1 -1
  90. package/dist/materials/micro-resources.js +1 -1
  91. package/dist/ships/blueprint-costs.js +1 -1
  92. package/dist/ships/blueprint-journal.js +1 -1
  93. package/dist/ships/build-metrics.js +1 -1
  94. package/dist/ships/engineering-options.d.ts +2 -2
  95. package/dist/ships/engineering-options.js +1 -1
  96. package/dist/ships/engineering.js +1 -1
  97. package/dist/ships/index.d.ts +5 -5
  98. package/dist/ships/index.js +1 -1
  99. package/dist/ships/loadout-calculations.d.ts +11 -2
  100. package/dist/ships/loadout-calculations.js +1 -1
  101. package/dist/ships/loadout-validation.d.ts +7 -2
  102. package/dist/ships/loadout-validation.js +1 -1
  103. package/dist/ships/modules-all.d.ts +2 -2
  104. package/dist/ships/modules-all.js +1 -1
  105. package/dist/ships/modules-core.d.ts +3 -3
  106. package/dist/ships/modules-core.js +1 -1
  107. package/dist/ships/modules-hardpoint.d.ts +1 -1
  108. package/dist/ships/modules-internal.d.ts +17 -6
  109. package/dist/ships/modules-internal.js +1 -1
  110. package/dist/ships/modules-utility.d.ts +1 -1
  111. package/dist/ships/modules.d.ts +14 -7
  112. package/dist/ships/modules.js +1 -1
  113. package/dist/ships/pre-engineered-stats.js +1 -1
  114. package/dist/ships/pre-engineered.js +1 -1
  115. package/dist/ships/shield-capacitor.js +1 -1
  116. package/dist/ships/ship-loadout.d.ts +71 -23
  117. package/dist/ships/ship-loadout.js +1 -1
  118. package/dist/ships/weapons.js +1 -1
  119. package/package.json +13 -1
  120. package/dist/chunk-3CKWT2F3.js +0 -1
  121. package/dist/chunk-3P455LF4.js +0 -1
  122. package/dist/chunk-3P455LF4.js.map +0 -1
  123. package/dist/chunk-5QSCLEEB.js +0 -1
  124. package/dist/chunk-5QSCLEEB.js.map +0 -1
  125. package/dist/chunk-6GJHUWMO.js +0 -1
  126. package/dist/chunk-B7EYTZRK.js +0 -1
  127. package/dist/chunk-C5HG4GJI.js +0 -1
  128. package/dist/chunk-C5HG4GJI.js.map +0 -1
  129. package/dist/chunk-DCB4COCL.js +0 -1
  130. package/dist/chunk-DCB4COCL.js.map +0 -1
  131. package/dist/chunk-E3AUKPSQ.js +0 -1
  132. package/dist/chunk-E3AUKPSQ.js.map +0 -1
  133. package/dist/chunk-FA5JVXVG.js +0 -1
  134. package/dist/chunk-GISL7YML.js +0 -1
  135. package/dist/chunk-GISL7YML.js.map +0 -1
  136. package/dist/chunk-KLC5XT6F.js +0 -1
  137. package/dist/chunk-KLC5XT6F.js.map +0 -1
  138. package/dist/chunk-LOWDDWDT.js +0 -1
  139. package/dist/chunk-LZYHBFJC.js +0 -1
  140. package/dist/chunk-MGRDB2BZ.js +0 -1
  141. package/dist/chunk-NBLLW7TH.js +0 -1
  142. package/dist/chunk-NBLLW7TH.js.map +0 -1
  143. package/dist/chunk-OJC6AUL7.js +0 -1
  144. package/dist/chunk-OJC6AUL7.js.map +0 -1
  145. package/dist/chunk-S3MONE55.js.map +0 -1
  146. /package/dist/{chunk-A6OINYNP.js.map → chunk-3WGXGBW5.js.map} +0 -0
  147. /package/dist/{chunk-7S7ZTZZY.js.map → chunk-7EWZGDWY.js.map} +0 -0
  148. /package/dist/{chunk-YCGISPDR.js.map → chunk-ETWMPMZP.js.map} +0 -0
  149. /package/dist/{chunk-SDXUPP4P.js.map → chunk-XSLPWM5N.js.map} +0 -0
@@ -16,6 +16,8 @@ Referred to throughout by source name; the pin is here, once.
16
16
  | EDCD/Coriolis — the application, for its formulas | commit `68c042ca6e3db62372cbbb2077cf972345511712` | 2026-08-01 UTC |
17
17
  | msarilar/EDEngineer `EDEngineer/Resources/Data/blueprints.json` | SHA-256 `787e6bd0579264d7b4615a281318792cb212285786f4ae07f61ec1cc464cdec0` — read from the branch tip, so pinned by digest | 2026-08-08 UTC |
18
18
  | Elite Dangerous in-game verification | game version `4.4.0.3`; direct in-game observation | 2026-08-14 UTC |
19
+ | Elite Dangerous in-game purchase capture | no game version recorded; nine `ModuleBuyAndStore` journal entries from market `128666762`, 17:34-17:37, at a 10% and a 2.5% outfitting discount | 2026-08-30 UTC |
20
+ | Elite Dangerous in-game outfitting observation | no game version recorded; direct in-game observation that outfitting offers no non-SCO size-8 frame shift drive | 2026-08-30 UTC |
19
21
 
20
22
  Every `eddb.js` derivation uses the baseline snapshot unless its catalogue note names
21
23
  the Vessel Hangar snapshot.
@@ -351,7 +353,7 @@ FDevIDs, stats from coriolis-data and EDSY, joined on `symbol`.
351
353
  `id,symbol,category,name,mount,guidance,ship,class,rating,entitlement`, supplemented
352
354
  for the six bundle-granted Vessel Hangars by the pinned CAPI response below.
353
355
  - **Identity derivation:** the acquired FDevIDs module records are kept in CSV order
354
- within each category file. The catalogue contains 484 internal records and **1199**
356
+ within each category file. The catalogue contains 484 internal records and **1194**
355
357
  records across all four categories. The CSV's numeric `id`
356
358
  column is dropped — modules are keyed by `symbol` — and rows marked `removed` are
357
359
  excluded because they are not current outfitting modules. `class` is FDevIDs' `class` — the
@@ -384,7 +386,7 @@ FDevIDs, stats from coriolis-data and EDSY, joined on `symbol`.
384
386
  II's `Int_MkIIAgileBoost_Engine_*` thrusters.
385
387
  - **A fuel tank is the one module built for two kinds of mount:** it is `fuelTank`
386
388
  and also fits any optional slot large enough, exactly as the game sells it.
387
- - **`kind` is the ordinary engineering-menu family.** The 1005 records mapped by
389
+ - **`kind` is the ordinary engineering-menu family.** The 1000 records mapped by
388
390
  `engineering-options.jsonc` repeat that map's group key in the compact on-disk `kind`
389
391
  field. The remaining 194 records carry no `kind` because they have no ordinary
390
392
  engineering menu; this includes the five Enzyme/AX weapons, the fixed Mining Laser and
@@ -393,7 +395,7 @@ FDevIDs, stats from coriolis-data and EDSY, joined on `symbol`.
393
395
  Guardian families and coverage are documented under Engineering options below; this
394
396
  field is a projection of that map, not a separate classification source.
395
397
  - **`familyId` groups every outfitting list.** Every record in all four catalogues
396
- carries the id of the family its outfitting list shows it under: 521 core modules in
398
+ carries the id of the family its outfitting list shows it under: 516 core modules in
397
399
  8 families, 484 internal modules in 32, 159 hardpoints in 25, and 35 utility fittings
398
400
  in 12. No family crosses an outfitting category. The ids and their canonical English
399
401
  names are `module-families.jsonc`, below; localized labels are
@@ -405,7 +407,7 @@ FDevIDs, stats from coriolis-data and EDSY, joined on `symbol`.
405
407
  family where their function differs. A core record's family follows from its `slot`,
406
408
  which is on every one of them, so the two never disagree.
407
409
  - **`supercruiseOvercharge` — the SCO drive line.** The 36
408
- `Int_Hyperdrive_Overcharge_*` records carry `true`; the 36 plain drives of the same
410
+ `Int_Hyperdrive_Overcharge_*` records carry `true`; the 31 plain drives of the same
409
411
  sizes and ratings, and every other record in all four catalogues, omit it.
410
412
  - **Source:** FDevIDs `outfitting.csv`, which sells the two drive lines under distinct
411
413
  symbol families and names the Overcharge one `Frame Shift Drive (SCO)`. No registry
@@ -415,13 +417,44 @@ FDevIDs, stats from coriolis-data and EDSY, joined on `symbol`.
415
417
  above, and it is applied once here so that consumers read a datum instead of
416
418
  matching `Int_Hyperdrive_Overcharge` themselves — which a pre-engineered or fitted
417
419
  article's symbol does not always let them do. Stored as a sparse flag rather than a
418
- `true`/`false` pair on all 72 drives, following `alwaysPowered` and
420
+ `true`/`false` pair on all 67 drives, following `alwaysPowered` and
419
421
  `guardianZoneResistance`.
420
422
  - **Nothing else is derived from it.** The two lines share every jump constant this
421
423
  catalogue carries (`optMass`, `maxFuel`, `fuelMul`, `fuelPower`, and the
422
- size-determined `fsdHeatRate` above), so no calculation reads the flag; the
424
+ `fsdHeatRate`, which is a function of a drive's size alone — 10, 14, 18, 27, 37, 43
425
+ and 50 for sizes 2 to 8, identical on the plain and SCO lines at every size), so no
426
+ calculation reads the flag; the
423
427
  overcharged supercruise behaviour the drives are named for has no published figures
424
428
  and none are stored.
429
+ - **`cabinCapacity` — a passenger cabin's berths.** The 23 `passengerCabins` records
430
+ carry it; every other record in all four catalogues omits it, and no cabin carries a
431
+ zero. It is the module's own seat count, not the ship's.
432
+ - **Source:** coriolis-data
433
+ `modules/internal/{economy,business,first,luxury}_passenger_cabin.json` (`passengers`)
434
+ and EDSY `eddb.js` (`cabincap`, whose `fdattr` is the journal's `CabinCapacity`), from
435
+ the snapshots pinned above. The two agree on every one of the 14 Mk I cabins, so
436
+ neither is preferred; the nine Mk II cabins rest on EDSY alone, which is where their
437
+ masses come from as well. FDevIDs publishes no capacity column.
438
+ - **Values:** Mk I economy 2/4/8/16/32 by size 2–6, business 3/6/10/16 by size 3–6,
439
+ first 3/6/12 by size 4–6 and luxury 4/8 by size 5–6. Mk II economy 3/6/12/24/48 by
440
+ size 2–6 and Mk II business 4/9/15/24 by size 3–6 — half again the Mk I cabin of the
441
+ same class and size, bar the size-3 business cabin, whose 3 berths become 4 rather
442
+ than 4.5.
443
+ - **Stored per record rather than derived from size and rating.** A Mk II cabin runs one
444
+ rating better than the Mk I cabin of the same class — Mk II economy is `D` where Mk I
445
+ economy is `E`, and Mk II business `C` where Mk I business is `D` — so one
446
+ size-and-rating rule cannot produce both lines.
447
+ - **The four passenger classes are not carried as a field, and are not lost.** EDSY
448
+ publishes one (`cabincls`, values `E`/`B`/`F`/`L`), which this catalogue does not
449
+ copy: the symbol already states the class, uniformly across both lines. The symbol's
450
+ `_ClassN` suffix — not the `class` field, which is the slot size — is 1/2/3/4 for
451
+ economy/business/first/luxury on all 23 records, which is what the English `name` and
452
+ EDSY's `cabincls` both agree with; the `rating` field is what does not state it, for
453
+ the reason above. The Mk I line runs all four, the Mk II line economy and business
454
+ only.
455
+ - **EDSY's five `Int_PassengerCabin_Size{2..6}_Class0` "Prisoner Cells" rows are not
456
+ carried**, capacity or otherwise: EDSY has them commented out with a null `fdid`, and
457
+ the inclusion rule below excludes an identity no registry offers to players.
425
458
  - **`grantOnly` — the articles that arrive granted rather than sold.** Fifteen records
426
459
  carry `true`: the nine `*_free` starter fittings (power plant, thrusters, drive, life
427
460
  support, distributor, sensors, fuel tank, shield generator, cargo rack) and the six
@@ -439,12 +472,11 @@ FDevIDs, stats from coriolis-data and EDSY, joined on `symbol`.
439
472
  twin bar the price (§Prices), so a screen offering both would show one article twice,
440
473
  once unpriced.
441
474
  - **It is not a statement about price, and the flag is not extended on a missing one.**
442
- Eleven further records carry no `cost` — the five size-8 drives, the three Mk II
443
- Vessel Hangars, the two unsold Corrosion Resistant Cargo Racks and the 1B shield
444
- generator and none is flagged. The two racks are the closest case and still fail
445
- the rule: no source states them as a hull or bundle grant, and each is its own
446
- identity at a capacity no other rack carries, not a second identity for something a
447
- station already sells.
475
+ Three further records carry no `cost` — the two unsold Corrosion Resistant Cargo
476
+ Racks and the 1B shield generator and none is flagged. The two racks are the
477
+ closest case and still fail the rule: no source states them as a hull or bundle
478
+ grant, and each is its own identity at a capacity no other rack carries, not a second
479
+ identity for something a station already sells.
448
480
 
449
481
  ### `module-families.jsonc` — the family ids and their English names
450
482
 
@@ -457,8 +489,7 @@ FDevIDs, stats from coriolis-data and EDSY, joined on `symbol`.
457
489
  `Manifest Scanners`, `Torpedo Pylons` is `Torpedoes` and `Mine Launchers` is `Mines`.
458
490
  The remaining 19 keep the Almanac's own descriptive name, because the outfitting
459
491
  screen does not name them separately. Each id is the camelCase form of its English
460
- name with punctuation removed, which `modules.test.ts` asserts, so an id and its
461
- label cannot drift apart.
492
+ name with punctuation removed.
462
493
  - **Merged families.** Where the game lists several Almanac families under one category,
463
494
  they are one family here: the operations, rescue, universal and xeno multi-limpet
464
495
  controllers are `multiLimpetControllers`; mining volley repeaters and seismic charge
@@ -598,14 +629,11 @@ Thirteen stored fields supply the base values referenced by engineering recipes:
598
629
  - **`EnergyPerRegen` needs no stored value.** All 57 shield generators carry
599
630
  `distributorDraw`, and EDSY (`genpwr`) and coriolis (`distdraw`) both confirm it is the
600
631
  same stat under the journal's other name.
601
- - **Nine figures no third-party registry lists, derived from the family rule.** Eight
602
- records: the three `*_free` starter fittings (thrusters, drive, sensors — the sensors
603
- contribute both a `scannerRange` and a `scanAngle`) and the five plain size-8 drives.
632
+ - **Four figures no third-party registry lists, derived from the family rule.** Three
633
+ records: the `*_free` starter fittings (thrusters, drive, sensors — the sensors
634
+ contribute both a `scannerRange` and a `scanAngle`).
604
635
  Each `*_free` record is byte-identical to its priced twin apart from the missing
605
- `cost`, so it takes that twin's value. A drive's heat rate is a function of its **size
606
- alone** across all 66 records EDSY does carry — 10, 14, 18, 27, 37, 43 for sizes 2 to
607
- 7, identical between the plain and SCO lines at every size — and the size-8 SCO drives
608
- are 50, so the size-8 plain drives take 50. Stated as derivation, not as a reading.
636
+ `cost`, so it takes that twin's value. Stated as derivation, not as a reading.
609
637
  The Mk II supercharge-optimised size-8 SCO drive is **not** among them: EDSY publishes
610
638
  its `fsdheat: 50` outright, spelling the symbol
611
639
  `Int_Hyperdrive_Overcharge_Size8_Class5_Overchargebooster_MkII` where the outfitting
@@ -656,8 +684,8 @@ Two starter capacities are derived rather than read:
656
684
  2^size across all eight sizes of both families, with no exception.
657
685
 
658
686
  **In-game coverage, stated separately from registry coverage.** In-game verification
659
- covers **1193/1199** catalogue identities. Numeric verification covers **952/1199**
660
- non-armour modules, and the other **241/1199** verified identities are the ship-specific
687
+ covers **1188/1194** catalogue identities. Numeric verification covers **947/1194**
688
+ non-armour modules, and the other **241/1194** verified identities are the ship-specific
661
689
  armour modules; their class, mass, hull boost and resistances retain their registry
662
690
  provenance rather than being described as game-verified. The six bundle-granted Vessel
663
691
  Hangars rely on the public registry and CAPI evidence below. Their stats match their
@@ -665,8 +693,8 @@ ordinary twins, but they have not been independently checked in the module panel
665
693
 
666
694
  Every numeric field available through in-game verification was compared. Exact
667
695
  full-field coverage includes `powerDraw`
668
- 831/837, `bootTime` 827/833, power-plant output and efficiency 43/43 each, every FSD
669
- field 72/72, every thruster heat-rate record 40/40, all six distributor fields 49/49,
696
+ 826/832, `bootTime` 822/828, power-plant output and efficiency 43/43 each, every FSD
697
+ field 67/67, every thruster heat-rate record 40/40, all six distributor fields 49/49,
670
698
  sensor range and angle 41/41, shield mass/strength curves 57/57, shield regeneration
671
699
  57/57, shield-cell timing/reinforcement/heat 40/40, fuel-scoop rate 40/40,
672
700
  interdictor range/facing 20/20, cargo capacity 16/16, fuel capacity 9/9, hull
@@ -674,7 +702,7 @@ reinforcement 30/30, module protection 20/20, shield addition 10/10, Guardian ju
674
702
  boost 5/5, weapon armour piercing 157/157, burst rounds 18/18, burst rate 16/16 and
675
703
  rounds per shot 19/19. `powerDraw` and `bootTime` have no discrepancies at all.
676
704
 
677
- In-game verification did not yield the 1173 store prices, hardpoint reserve ammo (120),
705
+ In-game verification did not yield the 1176 store prices, hardpoint reserve ammo (120),
678
706
  projectile speed (111), rail-gun charge time (3), or the 23 hardpoint scanners'
679
707
  range/angle/time fields. Twenty-one hardpoint maximum-range values, ECM heat and reload,
680
708
  the 241 armour modules, blueprint grade rolls and crafting costs remain unverified too.
@@ -731,8 +759,8 @@ external readings of an unmodified weapon's folded figure. On a beam laser the f
731
759
  trivial because `damage` is already per second; the huge and medium gimballed beams have
732
760
  no separate journal `Damage` reading.
733
761
 
734
- **Every module in every catalogue carries at least one stat** (1199/1199), and no
735
- record holds only a lone `mass`. 244 of the 833 `bootTime` values are `0` (every hardpoint
762
+ **Every module in every catalogue carries at least one stat** (1194/1194), and no
763
+ record holds only a lone `mass`. 244 of the 828 `bootTime` values are `0` (every hardpoint
736
764
  among them); they are stored rather than omitted, because an absent field means
737
765
  absent.
738
766
 
@@ -827,8 +855,7 @@ damage for the small 1D cannon and **38.5** for the medium 2B cannon. Those read
827
855
  settle the registry disagreement in favour of coriolis-data's 22 / 38.5 rather than
828
856
  EDSY's 40 / 70. The catalogue stores the displayed values without further derivation and
829
857
  applies the same correction to their exact thermal and anti-xeno components. The two
830
- records are pinned in `fixtures/ships/module-stats.jsonc`. A panel reading has no upstream
831
- immutable revision.
858
+ panel readings have no upstream immutable revision.
832
859
 
833
860
  **Values that look wrong and are not.** Three records break the pattern their family
834
861
  follows and are confirmed outright by EDSY. Recorded so the "breaks its family's curve"
@@ -842,7 +869,7 @@ heuristic does not keep rediscovering them:
842
869
  distributor integrity otherwise tracks 0.80× the A-rated standard ladder, which would
843
870
  put size 5 near 85; EDSY states 99 for both sizes. The duplicate is in the game data.
844
871
  - **`Int_DroneControl_Recon_Size5_Class1` `bootTime` really is 9.85** — the only
845
- non-integer boot time in all 1199 records, where its three family siblings are exactly 10.
872
+ non-integer boot time in all 1194 records, where its three family siblings are exactly 10.
846
873
  EDSY gives `boottime:9.85`.
847
874
 
848
875
  ### Prices — `cost` on modules, `hullCost` / `retailCost` on hulls
@@ -857,18 +884,22 @@ the Lynx Highliner, which has no coriolis entry.
857
884
  Ship-specific **armour** is priced from each hull's `bulkheads` upstream, joined on hull
858
885
  and bulkhead name because those records carry no symbol upstream.
859
886
 
860
- - **All 48 hulls are priced. 1173 of 1199 modules are.** The 26 without a price are the
861
- fifteen grant/starter `*_free` variants, the five size-8 frame shift drives, the three Mk II
862
- Vessel Hangars, the two unsold Corrosion Resistant Cargo Racks (both Community Goal
863
- rewards) and `Int_ShieldGenerator_Size1_Class4` no registry publishes a figure for
864
- them. **`cost` is omitted, never set to 0**: `0` is a real price (the starter
887
+ - **All 48 hulls are priced. 1176 of 1194 modules are.** The 18 without a price are the
888
+ fifteen grant/starter `*_free` variants, the two unsold Corrosion Resistant Cargo Racks
889
+ (both Community Goal rewards) and `Int_ShieldGenerator_Size1_Class4` no registry
890
+ publishes a figure for them. The three Mk II Vessel Hangars are no longer among them:
891
+ they are priced from an in-game purchase capture, below.
892
+ **`cost` is omitted, never set to 0**: `0` is a real price (the starter
865
893
  Lightweight Alloy bulkhead costs nothing), while omission means unknown.
866
894
  - **Sixteen duplicated symbols take the first occurrence's price.** Where coriolis-data
867
895
  holds a symbol twice, the "first occurrence wins" rule that governs `mass` governs
868
896
  `cost` too; taking the _second_, unpriced record would leave them at `0`. The sixteen:
869
- `Hpt_HeatSinkLauncher_Turret_Tiny` 3500 — confirmed independently against a real
870
- journal, which prices the fitted module at 3071 = 3500 less the 12.25% outfitting
871
- discount that export was taken at; `Int_Hyperdrive_Size5_Class5` 5 103 953;
897
+ `Hpt_HeatSinkLauncher_Turret_Tiny` 3500 — confirmed independently against Frontier's
898
+ own journals, which price the fitted module at 3072 at a 12.25% discount
899
+ (`journal-the-deep-black.jsonc` and `journal-caspian-explorer.jsonc`, twice each). The
900
+ **3071** in `slef-the-deep-black.jsonc` is an EDSY export's own arithmetic over its own
901
+ cost table, not a game reading, and the two disagree by a credit here; §Prices read from
902
+ an in-game purchase capture turns on the difference; `Int_Hyperdrive_Size5_Class5` 5 103 953;
872
903
  `Int_CargoRack_Size5_Class1` 111 566 and `_Size6_Class1` 362 591;
873
904
  `Int_DetailedSurfaceScanner_Tiny` 250 000; `Hpt_MultiCannon_Fixed_Medium` 38 000;
874
905
  `Hpt_Railgun_Fixed_Medium` 412 800; `Hpt_BasicMissileRack_Fixed_Medium` 512 400;
@@ -884,8 +915,11 @@ and bulkhead name because those records carry no symbol upstream.
884
915
  registries price they agree exactly (`_Size1_Class1` 6250, `_Size4_Class1` 94 330), and
885
916
  the only corrosion racks FDevIDs `outfitting.csv` lists at all are those two plus
886
917
  `_Size1_Class2` itself — so it is the last of the purchasable ones. It is certainly not
887
- free: the Deep Black's journal buys the size-4 at 82 775 = 94 330 less that export's
888
- 12.25% discount.
918
+ free: `journal-the-deep-black.jsonc` carries the size-4 at `Value: 82 774` under a
919
+ 12.25% discount. (That reading sits a credit below what 94 330 predicts under the
920
+ step-wise discount rule in §Prices read from an in-game purchase capture, and is
921
+ recorded there as a known residual. Earlier revisions of this file quoted **82 775**
922
+ here as a journal reading; it is the arithmetic, not the capture.)
889
923
  - **Read that 12 560 as a 10-granular figure, not a to-the-credit one.** EDSY publishes
890
924
  module costs at **10-credit granularity**, which is measured rather than assumed. Two
891
925
  observations, both scoped to `eddb.module` — EDSY's outfitting table, where module
@@ -915,9 +949,13 @@ and bulkhead name because those records carry no symbol upstream.
915
949
  where both registries publish a multiple of 10 still differ by 10
916
950
  (`Int_FighterBay_Size{6,7}_Class1`, `Int_PassengerCabin_Size6_Class1`), which no
917
951
  rounding explains: whatever the real price is, at least one of the two registries is
918
- wrong about it by five credits or more, and neither says which. So treat 12 560 as the
919
- best published figure at 10-credit resolution, not as an accuracy guarantee; only an
920
- in-game reading settles the last digits. Every EDSY-sourced price in this catalogue
952
+ wrong about it by five credits or more, and neither says which. In-game readings
953
+ settle two of the three, with coriolis exactly right in both:
954
+ `Int_FighterBay_Size7_Class1` is 2 369 320 and `Int_FighterBay_Size6_Class1` is
955
+ 1 869 340, so EDSY's 2 369 330 is the wrong member of that pair (see "Prices read from
956
+ an in-game purchase capture" below). So treat 12 560 as
957
+ the best published figure at 10-credit resolution, not as an accuracy guarantee; only
958
+ an in-game reading settles the last digits. Every EDSY-sourced price in this catalogue
921
959
  carries the same granularity, so this record is no less exact than the rest of them.
922
960
  - **The size-5 and size-6 Corrosion Resistant Cargo Racks have no list price to
923
961
  publish**, and their absent `cost` means _no list price exists_, not _none has been
@@ -965,9 +1003,84 @@ and bulkhead name because those records carry no symbol upstream.
965
1003
  thermal −0.2, explosive 0.5, draw 0.6. The cargo hatch (`ModularCargoBayDoor`) takes
966
1004
  the 0.6 MW draw Coriolis hard-codes for it (`ModuleUtils.cargoHatch`), since it is
967
1005
  fitted to every hull and cannot be removed.
968
- - **Not modelled:** passenger capacity and fighter-bay/rebuild counts. The
969
- **Merc-Coin** price of the pre-engineered variants is carried, but on the variant
970
- rather than the module — see `mercCoinCost` in the pre-engineered section.
1006
+ - **Not modelled:** fighter-bay/rebuild counts. The **Merc-Coin** price of the
1007
+ pre-engineered variants is carried, but on the variant rather than the module — see
1008
+ `mercCoinCost` in the pre-engineered section.
1009
+
1010
+ ### Prices read from an in-game purchase capture
1011
+
1012
+ Nine `ModuleBuyAndStore` journal entries, all from market `128666762` within four minutes
1013
+ on **2026-08-30 UTC** and supplied by the repository owner, price three module records no
1014
+ registry carries and confirm or correct six that were already priced. The capture was
1015
+ taken at a **10% and a 2.5% discount**, so each `BuyPrice` is the list price with both
1016
+ discounts applied. A journal `BuyPrice` is what the commander was actually charged, which
1017
+ is the objection that disqualifies a `Loadout` `Value` of unknown provenance (§the size-5
1018
+ and size-6 racks): here the discount is stated rather than solved for.
1019
+
1020
+ - **The game subtracts a truncated discount at each step; it does not truncate the
1021
+ product.** Each discount takes `floor(price x rate)` off the running price, so
1022
+ `paid = ceil(ceil(list x 0.9) x 0.975)`, and the two orders give the same answer for
1023
+ every row here. This is measured, not assumed: scored against **every** `Loadout`
1024
+ `Value` in `fixtures/ships/journal-*.jsonc` that a catalogue price and a standard
1025
+ discount can reproduce (455 readings), the step-wise rule matches 304, a single
1026
+ `ceil(list x 0.8775)` 275, `round` 230 and `floor` 92. Restricting to 2.5%-only
1027
+ readings gives the same ordering. **Do not derive a price from a single control**: two
1028
+ readings were originally used here and both were unrepresentative, which is how an
1029
+ earlier revision of this section published a truncation rule and eight wrong figures.
1030
+ - **`Hpt_HeatSinkLauncher_Turret_Tiny` is the reading that settles the last credit.** Its
1031
+ list price is 3500, both registries agree, and `3500 x 0.8775 = 3071.25`. Frontier's own
1032
+ journals read **3072** — `fixtures/ships/journal-the-deep-black.jsonc` and
1033
+ `journal-caspian-explorer.jsonc`, twice each — which excludes truncation and
1034
+ round-to-nearest alike. The **3071** quoted elsewhere in this file is not a game reading:
1035
+ it comes from `fixtures/ships/slef-the-deep-black.jsonc`, an EDSY SLEF export whose
1036
+ `Value`s are EDSY's own arithmetic. The two documents disagree wherever they overlap
1037
+ (3071/3072, 82 775/82 774, 195 191/195 195), so a third-party export is not admissible
1038
+ as evidence of what the game charged.
1039
+
1040
+ | Symbol | Paid | List price | Was |
1041
+ | ---------------------------------------- | ---------- | ---------- | --------------------- |
1042
+ | `Int_FighterBayMk2_Size5_Class1` | 697 076 | 794 387 | unpriced |
1043
+ | `Int_FighterBayMk2_Size6_Class1` | 2 263 678 | 2 579 689 | unpriced |
1044
+ | `Int_FighterBayMk2_Size7_Class1` | 2 869 128 | 3 269 661 | unpriced |
1045
+ | `Int_FighterBay_Size7_Class1` | 2 079 079 | 2 369 320 | 2 369 320 (confirmed) |
1046
+ | `Int_Hyperdrive_Overcharge_Size8_Class1` | 6 000 827 | 6 838 548 | 6 838 550 (EDSY) |
1047
+ | `Int_Hyperdrive_Overcharge_Size8_Class2` | 18 002 479 | 20 515 645 | 20 515 650 (EDSY) |
1048
+ | `Int_Hyperdrive_Overcharge_Size8_Class3` | 18 002 479 | 20 515 645 | 20 515 650 (EDSY) |
1049
+ | `Int_Hyperdrive_Overcharge_Size8_Class4` | 18 002 479 | 20 515 645 | 20 515 650 (EDSY) |
1050
+ | `Int_Hyperdrive_Overcharge_Size8_Class5` | 54 007 436 | 61 546 935 | 61 546 940 (EDSY) |
1051
+
1052
+ - **Seven of the nine readings pin one integer; two do not, and are settled by other
1053
+ evidence.** A one-credit band of paid price spans `400/351 = 1.14` credits of list price,
1054
+ so a reading admits one or two candidates.
1055
+ - `Int_FighterBayMk2_Size5_Class1` is pinned at 794 387 by the capture **and**
1056
+ independently by a second reading this repository already held: `Value: 774 528` at
1057
+ 2.5%, in both `journal-the-deep-black.jsonc` and `journal-caspian-explorer.jsonc`. Two
1058
+ captures at different discounts, one answer.
1059
+ - `Int_FighterBayMk2_Size6_Class1` admits 2 579 689 and 2 579 690. It takes **2 579 689**
1060
+ from the family rule below, which is a derivation and not a reading.
1061
+ - `Int_FighterBay_Size7_Class1` admits 2 369 319 and 2 369 320, and **keeps coriolis's
1062
+ 2 369 320** — the reading confirms the published figure rather than moving it, and
1063
+ settles one of the three registry pairs §Prices flags as differing by 10. Its sibling
1064
+ `Int_FighterBay_Size6_Class1` (1 869 340) is confirmed outright by four corvette
1065
+ readings at two different discounts.
1066
+ - **The Mk II bays are `floor(Mk I x 1.38)` at every size.** The rule reproduces both
1067
+ independently pinned values exactly — `floor(575 643 x 1.38) = 794 387` and
1068
+ `floor(2 369 320 x 1.38) = 3 269 661` — which is what licenses it to choose 2 579 689 for
1069
+ the size-6 bay. Stated as derivation for that one record, and as an observation for the
1070
+ other two.
1071
+ - **The size-8 SCO drives are priced identically to their size-7 siblings.** All four
1072
+ distinct readings land exactly on coriolis's size-7 figures (6 838 548, 20 515 645,
1073
+ 61 546 935), and EDSY's size-8 numbers are those same figures rounded to 10. The
1074
+ catalogue carries the exact readings rather than EDSY's rounded figures.
1075
+ - **A known residual.** `Int_CorrosionProofCargoRack_Size4_Class1` at 94 330 predicts
1076
+ 82 775 under this rule where `journal-the-deep-black.jsonc` reads **82 774**. It is one
1077
+ of a minority of records whose catalogue list price appears to sit a credit or two above
1078
+ the game's, and it is recorded here rather than explained away — it is not evidence for
1079
+ truncation, which the corpus rejects 304 to 92.
1080
+ - **What would supersede these figures:** a purchase or outfitting reading at 0% discount,
1081
+ which needs no model at all. Until then they are readings through one measured
1082
+ arithmetic rule, which is a weaker claim than a direct quote and a stronger one than a
1083
+ registry's rounded figure.
971
1084
 
972
1085
  ### Armour, and the fields kept deliberately
973
1086
 
@@ -1057,7 +1170,11 @@ Records not in coriolis-data / FDevIDs at the acquired revisions:
1057
1170
  bays at half the mass (10/20/30 t, integrity 60/80/120, power
1058
1171
  0.25/0.35/0.35 MW). The three Mk I **Fighter Hangar** records are named **Mk I Vessel
1059
1172
  Hangar** (same symbols and stats; the Operations update renamed them and let them
1060
- deploy the Nomad).
1173
+ deploy the Nomad). Their prices — which no registry carries, EDSY having no record of
1174
+ the Mk II bays at all — are read from the in-game purchase capture in §Prices: 794 387,
1175
+ 2 579 689 and 3 269 661. Each Mk II bay is `floor(Mk I x 1.38)`; that is an observation
1176
+ about the size-5 and size-7 figures, whose readings pin them outright, and the
1177
+ derivation that settles the size-6 figure, whose reading admits two candidates.
1061
1178
  - **Six bundle-granted variants are separate identities.** The pinned CAPI response
1062
1179
  lists `Int_FighterBay{,Mk2}_Size{5,6,7}_Class1_Free` as modules with `bundle: true`
1063
1180
  and the grant tokens `ELITE_V_MKIFIGHTERBAY_FREE` / `ELITE_V_MKIIFIGHTERBAY_FREE`.
@@ -1070,8 +1187,10 @@ Records not in coriolis-data / FDevIDs at the acquired revisions:
1070
1187
  zero is the bundle charge, not a standard purchase price for a separately sold
1071
1188
  module.
1072
1189
  - **Mk II passenger cabins** (`Int_MkII_PassengerCabin_Size{2..6}_Class{1,2}`) — identity
1073
- records from FDevIDs, with mass added (2.5/5/10/20/40 t by size) and the two size-6
1074
- records' `class` corrected from 5 to 6.
1190
+ records from FDevIDs, with mass (2.5/5/10/20/40 t by size) and `cabinCapacity` added
1191
+ from EDSY, the only registry that publishes stats for them — coriolis-data carries no
1192
+ record at all — and the two size-6 records' `class` corrected from 5 to 6. The
1193
+ capacities and how they relate to the Mk I cabins' are under `cabinCapacity` above.
1075
1194
  - **Corrosion Resistant Cargo Racks** `Int_CorrosionProofCargoRack_Size{5,6}_Class1`
1076
1195
  (capacity 32/64) and the built-in **Cargo Hatch** `ModularCargoBayDoor` (power 0.6 MW)
1077
1196
  — live EDSY records (not commented out, unlike the 1B shield generator below) that the
@@ -1087,6 +1206,34 @@ Records not in coriolis-data / FDevIDs at the acquired revisions:
1087
1206
  carries it `cost: NaN` annotated "never released". A variant that never reached
1088
1207
  players is not a player-facing outfitting record, so it goes the way of the other
1089
1208
  non-purchasable internal variants that rule excludes.
1209
+ - **Plain size-8 Frame Shift Drives are not carried: outfitting does not sell them.**
1210
+ `Int_Hyperdrive_Size8_Class{1..5}` are excluded on direct in-game observation
1211
+ (2026-08-30 UTC, §the pinned sources) that no non-SCO size-8 drive is offered. The five
1212
+ `Int_Hyperdrive_Overcharge_Size8_Class{1..5}` records are the size-8 drives the game
1213
+ does sell. Only one hull in the catalogue carries a size-8 frame-shift mount at all —
1214
+ the Caspian Explorer (`Explorer_NX`), every other size-8-capable hull including the
1215
+ Panther Clipper Mk II mounting a size 7 — and `default-loadouts.jsonc` ships that hull
1216
+ with `Int_Hyperdrive_Overcharge_Size8_Class1`, so the one stock size-8 drive in the
1217
+ data is an SCO drive.
1218
+ - **The case rests on the observation, not on the registries, and is weaker than the
1219
+ precedent it follows.** FDevIDs `outfitting.csv` does carry these five rows — which is
1220
+ why they were in the catalogue, and it means the Inclusion rule below, read literally,
1221
+ admits them. What overrides it is this catalogue's own rule that in-game verification
1222
+ governs where a registry disagrees. The other two registries are consistent but not
1223
+ probative: coriolis-data has no record, which is also true of the Mk II Vessel Hangars
1224
+ and the size-5/6 Corrosion Resistant Cargo Racks that _are_ carried; EDSY carries the
1225
+ five commented out, which is also true of `Int_ShieldGenerator_Size1_Class4` below,
1226
+ carried as real. `Int_CorrosionProofCargoRack_Size2_Class1` is the stronger case — no
1227
+ FDevIDs row at all and an explicit EDSY "never released" — so this exclusion is the
1228
+ same _kind_ of judgement on thinner evidence, and a contrary in-game reading should
1229
+ reverse it.
1230
+ - **The 2026-08-14 in-game audit's totals counted these five as verified, and are
1231
+ restated.** That audit reported `identityMatches` 1193 of 1199 with 952 non-armour
1232
+ modules checked numerically, and "every FSD field 72/72" — figures that could not have
1233
+ been true of five records the game does not offer. The corrected accounting is 1188
1234
+ identity matches, 947 non-armour modules and 67/67 FSD fields, with
1235
+ `registryOnlyIdentities` at 6. Those are the standing audit figures; the source
1236
+ capture itself has not changed.
1090
1237
  - **1B Shield Generator** (`Int_ShieldGenerator_Size1_Class4`) — a gap in FDevIDs, not
1091
1238
  in the game: every other shield-generator size carries all five ratings, and size 1
1092
1239
  ran E/D/C/A with **B missing**. The module is real, so the record is carried with the
@@ -1484,13 +1631,13 @@ up straight through with no disambiguation at all. Both paths are evidence that
1484
1631
  - **Availability is a property of the module, not of the blueprint.** A Pulse Laser and a
1485
1632
  Rail Gun both take the Efficient blueprint but offer different experimental effects, so
1486
1633
  "which experimentals go with blueprint X" has no single answer. Modules are therefore
1487
- grouped (48 groups covering 1005 ordinary engineering menus) and each group lists the
1634
+ grouped (48 groups covering 1000 ordinary engineering menus) and each group lists the
1488
1635
  `blueprints` and `experimentals` it offers.
1489
1636
  - **A group carries no display name.** The grouping is this repository's own partition of
1490
1637
  the modules sharing one menu; the game publishes no engineering-group label, heading
1491
- each menu with the module's outfitting family instead. EDSY's `mtype-*` strings, which
1492
- this file previously stored as group names, are outfitting labels reached by a name
1493
- join — which is why they disagreed with `module-families.jsonc` in 22 places on wording
1638
+ each menu with the module's outfitting family instead. EDSY's `mtype-*` strings are
1639
+ outfitting labels reached by a name join and disagree with `module-families.jsonc` in
1640
+ 22 places on wording
1494
1641
  (`cannons` Russian "Пушки" against the family's "Орудия", `moduleReinforcements`
1495
1642
  Brazilian Portuguese title case against the family's sentence case). Consumers name a
1496
1643
  group by joining a module's `familyId` to `module-families.jsonc`. The two partitions
@@ -1504,7 +1651,7 @@ up straight through with no disambiguation at all. Both paths are evidence that
1504
1651
  `modifications/modules.json`, which carries the same per-group lists keyed by the
1505
1652
  journal `BlueprintName`s this catalogue joins on.
1506
1653
  - **Coverage: every retained group EDSY's `mtype` table gives a `blueprints:` key.** After
1507
- the corrections below, that is 48 groups over 1005
1654
+ the corrections below, that is 48 groups over 1000
1508
1655
  modules, including
1509
1656
  bulkheads (the 241 ship armour records), life
1510
1657
  support, sensors, the Detailed Surface Scanner, refineries, AFMUs, fuel
@@ -1597,13 +1744,12 @@ up straight through with no disambiguation at all. Both paths are evidence that
1597
1744
  fixed Expanded Cargo Rack reward identity. Four Operations keys _are_ named by a
1598
1745
  group, because they are recipes a player applies — see "Four Operations recipes are
1599
1746
  listed by a menu" below.
1600
- - **14 modules are bound by the family rule, not by a source row.** EDSY has no live
1601
- entry for `Int_Hyperdrive_Size8_Class{1..5}` or `Int_ShieldGenerator_Size1_Class4`
1602
- (both present but commented out, and both naming their `mtype` `cfsd` and `isg`),
1603
- nor for eight of the `*_free` starter fittings. Each takes its family's group, on the
1604
- same rule the stats above use: a `*_free` variant is its priced twin bar the price,
1605
- and a size-8 drive is a drive. `Int_FuelTank_Size1_Class3_free` is not bound because
1606
- fuel tanks are not engineerable.
1747
+ - **9 modules are bound by the family rule, not by a source row.** EDSY has no live
1748
+ entry for `Int_ShieldGenerator_Size1_Class4` (present but commented out, and naming
1749
+ its `mtype` `isg`), nor for eight of the `*_free` starter fittings. Each takes its
1750
+ family's group, on the same rule the stats above use: a `*_free` variant is its priced
1751
+ twin bar the price. `Int_FuelTank_Size1_Class3_free` is not bound because fuel tanks
1752
+ are not engineerable.
1607
1753
  - **Corpus evidence does not override `noblueprints`.** Two corpus builds declare
1608
1754
  `Weapon_Efficient` on the Mk II Plasma Shock Accelerator, while EDSY denies that module
1609
1755
  every blueprint (`noblueprints: {'*'}`). Coriolis publishes only the group-level menu
@@ -1660,7 +1806,7 @@ up straight through with no disambiguation at all. Both paths are evidence that
1660
1806
  five module mappings and the corresponding projected `kind` fields in
1661
1807
  `modules-hardpoint.jsonc`.
1662
1808
  - **An empty experimental menu is still distinct from no menu.** 27 of the 48 groups offer
1663
- no experimental at all, so 368 of the 1005 grouped modules have an empty experimental
1809
+ no experimental at all, so 368 of the 1000 grouped modules have an empty experimental
1664
1810
  list while retaining blueprints.
1665
1811
  - **Key form:** the Anti-Guardian blueprint is listed under `GuardianModule_Sturdy`, the id
1666
1812
  a Loadout writes and the one EDSY uses — the same and only spelling `blueprints.jsonc`
@@ -1747,6 +1893,29 @@ up straight through with no disambiguation at all. Both paths are evidence that
1747
1893
 
1748
1894
  ## Pre-engineered modules
1749
1895
 
1896
+ ### A tech-broker unlock may be sold under its own name
1897
+
1898
+ Nineteen of the tech-broker variants are not sold as the plain base module. Each carries
1899
+ one of three markers, supplied by repository-owner in-game verification (2026-08-30 UTC);
1900
+ no registry publishes them, and the game exposes no immutable identifier for its
1901
+ outfitting strings.
1902
+
1903
+ | Marker | Variants | Name |
1904
+ | --- | ---: | --- |
1905
+ | `Sirius` | 1 + 2 | Sirius Heat Sink Launcher, Sirius AX Missile Rack |
1906
+ | `Azimuth` | 2 | Azimuth Enhanced AX Multi-Cannon |
1907
+ | `Modified` | 2 + 2 + 2 | Modified Guardian Plasma Charger / Gauss Cannon / Shard Cannon |
1908
+ | `V1` | 6 + 1 + 1 | Frame Shift Drive (SCO) V1, Detailed Surface Scanner V1, Seeker Missile Rack V1 |
1909
+
1910
+ `Sirius` and `Azimuth` are the manufacturers that sell the unlock; `V1` marks a first
1911
+ fixed revision. The marker is a property of the **tech-broker** route, not of the module:
1912
+ `Hpt_Guardian_ShardCannon_Fixed_Medium` is a Modified Guardian Shard Cannon bought from a
1913
+ broker and a plain Guardian Shard Cannon earned from a community goal, and the two rows
1914
+ differ only by `acquisition`. The same holds for `Hpt_BasicMissileRack_Fixed_Medium`,
1915
+ whose thermal-cascade tech-broker row is a Seeker Missile Rack V1 while its three
1916
+ community-goal rows are plain. Two tech-broker rows carry no marker at all and keep their
1917
+ base module's name: the Mining Laser and the size-5 Frame Shift Drive.
1918
+
1750
1919
  ### Festive variants
1751
1920
 
1752
1921
  - **File:** `pre-engineered.jsonc`. Three grade-5 records pair
package/README.md CHANGED
@@ -140,12 +140,13 @@ data-free leaf modules under `ships/jump-range`, `ships/power`, `ships/shields`,
140
140
  `ships/resistances`.
141
141
 
142
142
  `build.validation()` reports validity and operational completeness. `cargoCapacity`,
143
- `fuelCapacity` and `unladenMass` always have an answer, because no article the
144
- catalogue cannot weigh reaches a build; the eight metrics that depend on build state
145
- (`mobilityMetrics`, `mobilityCapacitorMetrics`, `shieldMetrics`,
146
- `shieldCapacitorMetrics`, `shieldRecovery`, `heatMetrics`, `distributorMetrics` and
147
- `standardLoad`) are nullable and each has a `…Result` counterpart naming what is
148
- missing, switched off, shed or unpowered. `parseSlef`
143
+ `passengerCapacity`, `fuelCapacity` and `unladenMass` always have an answer, because no
144
+ article the catalogue cannot weigh reaches a build. The eight build-state methods
145
+ (`mobilityMetricsResult`, `mobilityCapacitorMetricsResult`, `shieldMetricsResult`,
146
+ `shieldCapacitorMetricsResult`, `shieldRecoveryResult`, `heatMetricsResult`,
147
+ `distributorMetricsResult` and `standardLoadResult`) return a diagnostic result whose
148
+ value is either complete or `null`, with issues naming what is missing, unresolved,
149
+ switched off, shed or invalid. `parseSlef`
149
150
  is strict; `inspectSlef` is the tolerant importer for mixed files and returns indexed
150
151
  diagnostics instead of silently dropping entries.
151
152
 
@@ -172,16 +173,30 @@ import {
172
173
  getMicroResourceName,
173
174
  getModuleName,
174
175
  getOutfittingFamilyName,
176
+ getPersonalModificationName,
177
+ getSuitName,
175
178
  } from '@elite-dangerous-almanac/core/i18n';
176
179
 
177
180
  getModuleName('Int_Hyperdrive_Size6_Class5', 'de-DE'); // -> "Frameshiftantrieb"
178
181
  getBlueprintName('FSD_LongRange', 'fr-FR'); // -> "Portée FSD améliorée"
179
182
  getMaterialName('GridResistors', 'de'); // -> "Gitterwiderstände"
180
183
  getMicroResourceName('graphene', 'fr'); // -> "Graphène"
184
+ getSuitName('explorationsuit_class3', 'de'); // -> "Artemis-Anzug"
185
+ getPersonalModificationName('suit_nightvision', 'fr'); // -> "Vision nocturne"
181
186
  getOutfittingFamilyName('shieldGenerators', 'de'); // -> "Schildgeneratoren"
182
187
  getOutfittingFamilyName('xenoScanners', 'de'); // -> null
183
188
  ```
184
189
 
190
+ Display prose the game shows a player — a suit's or a handheld weapon's blurb, what an
191
+ experimental effect or an engineer's modification does — has its own lookups in the same
192
+ `i18n` area, each on its own subpath, complete in all six locales:
193
+
194
+ ```ts
195
+ import { getSuitDescription } from '@elite-dangerous-almanac/core/i18n/suits';
196
+
197
+ getSuitDescription('utilitysuit', 'en')?.slice(0, 20); // -> "The Maverick suit is"
198
+ ```
199
+
185
200
  The functions return an explicit source value verbatim, so a source-backed spelling may
186
201
  happen to equal English; the library itself never supplies an English fallback. The
187
202
  catalogues carry English, French, German, Portuguese, Russian and Spanish, each stored
@@ -161,7 +161,10 @@ a verbatim copy produced at build time — edit this file, never the copy.
161
161
  with its [announcement](https://x.com/EliteDangerous/status/1812792503776489745).
162
162
  Frontier's own **in-game localisation** supplies the outfitting category labels behind
163
163
  the module families and their German, Spanish, French, Brazilian Portuguese and Russian
164
- display text, and the engineering modification names in the six stored locales.
164
+ display text, the engineering modification names in the six stored locales, and the
165
+ personal-equipment display text in `data/i18n/`: the suit names and descriptions, the
166
+ handheld-weapon descriptions, and the names and descriptions of the engineer-applied
167
+ suit and weapon modifications.
165
168
  Values read directly from the running game are Frontier's too — see the notice below.
166
169
  - **A [community description](https://www.reddit.com/r/EliteDangerous/comments/1uk2zhp/plasma_laser_theorycrafting_following_new/)
167
170
  by u/Techno3020** — linked only as corroboration that the Operations Plasma conversion's
@@ -1 +1 @@
1
- import{requireFiniteNonNegative}from"./chunk-65FZECUX.js";import{journalRateOfFire}from"./chunk-EFQOUATZ.js";function combinedRateOfFire(weapon){return journalRateOfFire(weapon.burstInterval,weapon.burstRounds,weapon.burstRateOfFire)}var ZERO_SPLIT={kinetic:0,thermal:0,explosive:0,absolute:0,antiXeno:0};function splitDamage(damage,distribution){if(!distribution)return Object.freeze({...ZERO_SPLIT,absolute:damage});return Object.freeze({kinetic:damage*(distribution.kinetic??0),thermal:damage*(distribution.thermal??0),explosive:damage*(distribution.explosive??0),absolute:damage*(distribution.absolute??0),...(distribution.unclassified??0)===0?{}:{unclassified:damage*distribution.unclassified},antiXeno:damage*(distribution.antiXeno??0)})}function splitComponents(damage,components){const unclassified=(components.unclassified??[]).reduce((sum,value)=>sum+value,0);const conventional=(components.kinetic??0)+(components.thermal??0)+(components.explosive??0)+(components.absolute??0)+unclassified;if(conventional<=0)return Object.freeze({...ZERO_SPLIT,absolute:damage});const scale=damage/conventional;return Object.freeze({kinetic:(components.kinetic??0)*scale,thermal:(components.thermal??0)*scale,explosive:(components.explosive??0)*scale,absolute:(components.absolute??0)*scale,...unclassified===0?{}:{unclassified:unclassified*scale},antiXeno:(components.antiXeno??0)*scale})}function sustainedFireFactor(weapon){const rateOfFire=weapon.rateOfFire;const clip=weapon.clipSize===void 0?void 0:Math.ceil(weapon.clipSize);if(!rateOfFire||!clip||clip<=0)return 1;const burst=weapon.burstRounds&&weapon.burstRounds>0?weapon.burstRounds:1;const burstRate=weapon.burstRateOfFire&&weapon.burstRateOfFire>0?weapon.burstRateOfFire:1;const reload=weapon.reloadTime??0;const cycle=(clip-burst)/rateOfFire+(burst-1)/burstRate+reload;if(cycle<=0)return 1;const sustainedRate=clip/cycle;return Math.min(1,sustainedRate/rateOfFire)}function damagePerSecond(weapon){const damage=weapon.damage??0;const rounds=weapon.roundsPerShot&&weapon.roundsPerShot>0?weapon.roundsPerShot:1;return damage*rounds*(weapon.rateOfFire??1)}function sustainedDamagePerSecond(weapon){return damagePerSecond(weapon)*sustainedFireFactor(weapon)}function energyPerSecond(weapon){return(weapon.distributorDraw??0)*(weapon.rateOfFire??1)}function heatPerSecond(weapon){return(weapon.thermalLoad??0)*(weapon.rateOfFire??1)}function damageFalloff(weapon,metres){const maximum=weapon.maximumRange;if(maximum!==void 0&&metres>maximum)return 0;const falloff=weapon.falloffRange;if(falloff===void 0||maximum===void 0||metres<=falloff)return 1;const taper=maximum-falloff;if(taper<=0)return 1;return Math.max(0,1-(metres-falloff)/taper)}function armourPiercingFactor(armourPiercing,hardness){requireFiniteNonNegative("armourPiercingFactor","armour piercing",armourPiercing);requireFiniteNonNegative("armourPiercingFactor","hardness",hardness);if(hardness===0)return 1;return Math.min(1,armourPiercing/hardness)}function weaponMetrics(weapon){const continuous=weapon.rateOfFire===void 0;const rateOfFire=weapon.rateOfFire??1;const factor=sustainedFireFactor(weapon);const dps=damagePerSecond(weapon);const sdps=dps*factor;const eps=energyPerSecond(weapon);const hps=heatPerSecond(weapon);const rounds=weapon.roundsPerShot&&weapon.roundsPerShot>0?weapon.roundsPerShot:1;return Object.freeze({damagePerShot:(weapon.damage??0)*rounds,rateOfFire:rateOfFire,sustainedRateOfFire:rateOfFire*factor,damagePerSecond:dps,sustainedDamagePerSecond:sdps,energyPerSecond:eps,sustainedEnergyPerSecond:eps*factor,heatPerSecond:hps,sustainedHeatPerSecond:hps*factor,thermalLoad:weapon.thermalLoad??0,powerDraw:weapon.powerDraw??0,damageByType:weapon.damageComponents?splitComponents(dps,weapon.damageComponents):splitDamage(dps,weapon.damageDistribution),sustainedDamageByType:weapon.damageComponents?splitComponents(sdps,weapon.damageComponents):splitDamage(sdps,weapon.damageDistribution),continuous:continuous})}function sumWeaponMetrics(metrics){const damageByType=emptyDamageSplitAccumulator();const sustainedDamageByType=emptyDamageSplitAccumulator();let damagePerSecond2=0;let sustainedDamagePerSecond2=0;let energyPerSecond2=0;let sustainedEnergyPerSecond=0;let heatPerSecond2=0;let sustainedHeatPerSecond=0;let thermalLoad=0;let powerDraw=0;for(const metric of metrics){damagePerSecond2+=metric.damagePerSecond;sustainedDamagePerSecond2+=metric.sustainedDamagePerSecond;energyPerSecond2+=metric.energyPerSecond;sustainedEnergyPerSecond+=metric.sustainedEnergyPerSecond;heatPerSecond2+=metric.heatPerSecond;sustainedHeatPerSecond+=metric.sustainedHeatPerSecond;thermalLoad+=metric.thermalLoad;powerDraw+=metric.powerDraw;addDamageSplit(damageByType,metric.damageByType);addDamageSplit(sustainedDamageByType,metric.sustainedDamageByType)}return Object.freeze({damagePerSecond:damagePerSecond2,sustainedDamagePerSecond:sustainedDamagePerSecond2,energyPerSecond:energyPerSecond2,sustainedEnergyPerSecond:sustainedEnergyPerSecond,heatPerSecond:heatPerSecond2,sustainedHeatPerSecond:sustainedHeatPerSecond,thermalLoad:thermalLoad,powerDraw:powerDraw,damageByType:finishDamageSplit(damageByType),sustainedDamageByType:finishDamageSplit(sustainedDamageByType)})}function emptyDamageSplitAccumulator(){return{kinetic:0,thermal:0,explosive:0,absolute:0,unclassified:0,antiXeno:0}}function addDamageSplit(total,split){total.kinetic+=split.kinetic;total.thermal+=split.thermal;total.explosive+=split.explosive;total.absolute+=split.absolute;total.unclassified+=split.unclassified??0;total.antiXeno+=split.antiXeno}function finishDamageSplit(total){const{unclassified:unclassified,...classified}=total;return Object.freeze(unclassified===0?classified:{...classified,unclassified:unclassified})}export{armourPiercingFactor,combinedRateOfFire,damageFalloff,damagePerSecond,energyPerSecond,heatPerSecond,splitDamage,sumWeaponMetrics,sustainedDamagePerSecond,sustainedFireFactor,weaponMetrics};//# sourceMappingURL=chunk-AAU7OM47.js.map
1
+ import{journalRateOfFire}from"./chunk-EFQOUATZ.js";import{requireFiniteNonNegative}from"./chunk-65FZECUX.js";function combinedRateOfFire(weapon){return journalRateOfFire(weapon.burstInterval,weapon.burstRounds,weapon.burstRateOfFire)}var ZERO_SPLIT={kinetic:0,thermal:0,explosive:0,absolute:0,antiXeno:0};function splitDamage(damage,distribution){if(!distribution)return Object.freeze({...ZERO_SPLIT,absolute:damage});return Object.freeze({kinetic:damage*(distribution.kinetic??0),thermal:damage*(distribution.thermal??0),explosive:damage*(distribution.explosive??0),absolute:damage*(distribution.absolute??0),...(distribution.unclassified??0)===0?{}:{unclassified:damage*distribution.unclassified},antiXeno:damage*(distribution.antiXeno??0)})}function splitComponents(damage,components){const unclassified=(components.unclassified??[]).reduce((sum,value)=>sum+value,0);const conventional=(components.kinetic??0)+(components.thermal??0)+(components.explosive??0)+(components.absolute??0)+unclassified;if(conventional<=0)return Object.freeze({...ZERO_SPLIT,absolute:damage});const scale=damage/conventional;return Object.freeze({kinetic:(components.kinetic??0)*scale,thermal:(components.thermal??0)*scale,explosive:(components.explosive??0)*scale,absolute:(components.absolute??0)*scale,...unclassified===0?{}:{unclassified:unclassified*scale},antiXeno:(components.antiXeno??0)*scale})}function sustainedFireFactor(weapon){const rateOfFire=weapon.rateOfFire;const clip=weapon.clipSize===void 0?void 0:Math.ceil(weapon.clipSize);if(!rateOfFire||!clip||clip<=0)return 1;const burst=weapon.burstRounds&&weapon.burstRounds>0?weapon.burstRounds:1;const burstRate=weapon.burstRateOfFire&&weapon.burstRateOfFire>0?weapon.burstRateOfFire:1;const reload=weapon.reloadTime??0;const cycle=(clip-burst)/rateOfFire+(burst-1)/burstRate+reload;if(cycle<=0)return 1;const sustainedRate=clip/cycle;return Math.min(1,sustainedRate/rateOfFire)}function damagePerSecond(weapon){const damage=weapon.damage??0;const rounds=weapon.roundsPerShot&&weapon.roundsPerShot>0?weapon.roundsPerShot:1;return damage*rounds*(weapon.rateOfFire??1)}function sustainedDamagePerSecond(weapon){return damagePerSecond(weapon)*sustainedFireFactor(weapon)}function energyPerSecond(weapon){return(weapon.distributorDraw??0)*(weapon.rateOfFire??1)}function heatPerSecond(weapon){return(weapon.thermalLoad??0)*(weapon.rateOfFire??1)}function damageFalloff(weapon,metres){const maximum=weapon.maximumRange;if(maximum!==void 0&&metres>maximum)return 0;const falloff=weapon.falloffRange;if(falloff===void 0||maximum===void 0||metres<=falloff)return 1;const taper=maximum-falloff;if(taper<=0)return 1;return Math.max(0,1-(metres-falloff)/taper)}function armourPiercingFactor(armourPiercing,hardness){requireFiniteNonNegative("armourPiercingFactor","armour piercing",armourPiercing);requireFiniteNonNegative("armourPiercingFactor","hardness",hardness);if(hardness===0)return 1;return Math.min(1,armourPiercing/hardness)}function weaponMetrics(weapon){const continuous=weapon.rateOfFire===void 0;const rateOfFire=weapon.rateOfFire??1;const factor=sustainedFireFactor(weapon);const dps=damagePerSecond(weapon);const sdps=dps*factor;const eps=energyPerSecond(weapon);const hps=heatPerSecond(weapon);const rounds=weapon.roundsPerShot&&weapon.roundsPerShot>0?weapon.roundsPerShot:1;return Object.freeze({damagePerShot:(weapon.damage??0)*rounds,rateOfFire:rateOfFire,sustainedRateOfFire:rateOfFire*factor,damagePerSecond:dps,sustainedDamagePerSecond:sdps,energyPerSecond:eps,sustainedEnergyPerSecond:eps*factor,heatPerSecond:hps,sustainedHeatPerSecond:hps*factor,thermalLoad:weapon.thermalLoad??0,powerDraw:weapon.powerDraw??0,damageByType:weapon.damageComponents?splitComponents(dps,weapon.damageComponents):splitDamage(dps,weapon.damageDistribution),sustainedDamageByType:weapon.damageComponents?splitComponents(sdps,weapon.damageComponents):splitDamage(sdps,weapon.damageDistribution),continuous:continuous})}function sumWeaponMetrics(metrics){const damageByType=emptyDamageSplitAccumulator();const sustainedDamageByType=emptyDamageSplitAccumulator();let damagePerSecond2=0;let sustainedDamagePerSecond2=0;let energyPerSecond2=0;let sustainedEnergyPerSecond=0;let heatPerSecond2=0;let sustainedHeatPerSecond=0;let thermalLoad=0;let powerDraw=0;for(const metric of metrics){damagePerSecond2+=metric.damagePerSecond;sustainedDamagePerSecond2+=metric.sustainedDamagePerSecond;energyPerSecond2+=metric.energyPerSecond;sustainedEnergyPerSecond+=metric.sustainedEnergyPerSecond;heatPerSecond2+=metric.heatPerSecond;sustainedHeatPerSecond+=metric.sustainedHeatPerSecond;thermalLoad+=metric.thermalLoad;powerDraw+=metric.powerDraw;addDamageSplit(damageByType,metric.damageByType);addDamageSplit(sustainedDamageByType,metric.sustainedDamageByType)}return Object.freeze({damagePerSecond:damagePerSecond2,sustainedDamagePerSecond:sustainedDamagePerSecond2,energyPerSecond:energyPerSecond2,sustainedEnergyPerSecond:sustainedEnergyPerSecond,heatPerSecond:heatPerSecond2,sustainedHeatPerSecond:sustainedHeatPerSecond,thermalLoad:thermalLoad,powerDraw:powerDraw,damageByType:finishDamageSplit(damageByType),sustainedDamageByType:finishDamageSplit(sustainedDamageByType)})}function emptyDamageSplitAccumulator(){return{kinetic:0,thermal:0,explosive:0,absolute:0,unclassified:0,antiXeno:0}}function addDamageSplit(total,split){total.kinetic+=split.kinetic;total.thermal+=split.thermal;total.explosive+=split.explosive;total.absolute+=split.absolute;total.unclassified+=split.unclassified??0;total.antiXeno+=split.antiXeno}function finishDamageSplit(total){const{unclassified:unclassified,...classified}=total;return Object.freeze(unclassified===0?classified:{...classified,unclassified:unclassified})}export{armourPiercingFactor,combinedRateOfFire,damageFalloff,damagePerSecond,energyPerSecond,heatPerSecond,splitDamage,sumWeaponMetrics,sustainedDamagePerSecond,sustainedFireFactor,weaponMetrics};//# sourceMappingURL=chunk-2OBUSQA7.js.map