@elite-dangerous-almanac/core 0.2.1 → 0.2.3

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 (97) hide show
  1. package/PROVENANCE/ships/SOURCES.md +25 -0
  2. package/README.md +3 -2
  3. package/THIRD_PARTY_NOTICES.md +24 -2
  4. package/dist/astro/body-orbit.d.ts +174 -0
  5. package/dist/astro/body-orbit.js +1 -0
  6. package/dist/astro/body-orbit.js.map +1 -0
  7. package/dist/astro/body-physics.d.ts +228 -0
  8. package/dist/astro/body-physics.js +1 -0
  9. package/dist/astro/body-physics.js.map +1 -0
  10. package/dist/astro/body-rings.d.ts +223 -0
  11. package/dist/astro/body-rings.js +1 -0
  12. package/dist/astro/body-rings.js.map +1 -0
  13. package/dist/astro/codex-region-lookup.js +1 -1
  14. package/dist/astro/codex-region-lookup.js.map +1 -1
  15. package/dist/astro/index.d.ts +38 -1
  16. package/dist/astro/index.js +1 -1
  17. package/dist/astro/nebulae.d.ts +4 -4
  18. package/dist/astro/procedural-system.js +1 -1
  19. package/dist/astro/star-physics.d.ts +255 -0
  20. package/dist/astro/star-physics.js +1 -0
  21. package/dist/astro/star-physics.js.map +1 -0
  22. package/dist/body-scan-CEUYjCfH.d.ts +416 -0
  23. package/dist/{chunk-HUYYGTRM.js → chunk-3LZXB2MC.js} +1 -1
  24. package/dist/{chunk-AAE4KZ2V.js → chunk-6IWKFKQF.js} +1 -1
  25. package/dist/{chunk-AAE4KZ2V.js.map → chunk-6IWKFKQF.js.map} +1 -1
  26. package/dist/{chunk-MRTTQYT5.js → chunk-7EWZGDWY.js} +1 -1
  27. package/dist/{chunk-TBZJIQ5P.js → chunk-7TVFV3VO.js} +1 -1
  28. package/dist/chunk-7TVFV3VO.js.map +1 -0
  29. package/dist/chunk-7ZERP4TB.js +1 -0
  30. package/dist/{chunk-APEG4ZER.js.map → chunk-7ZERP4TB.js.map} +1 -1
  31. package/dist/chunk-AGRSIKNV.js +1 -0
  32. package/dist/chunk-AGRSIKNV.js.map +1 -0
  33. package/dist/chunk-CHA6LKH4.js +1 -0
  34. package/dist/chunk-CHA6LKH4.js.map +1 -0
  35. package/dist/chunk-DABPLBTN.js +1 -0
  36. package/dist/chunk-DABPLBTN.js.map +1 -0
  37. package/dist/chunk-DAQ3PJ4R.js +1 -0
  38. package/dist/chunk-DAQ3PJ4R.js.map +1 -0
  39. package/dist/chunk-G2VSQFD6.js +1 -0
  40. package/dist/{chunk-T62GO4AA.js.map → chunk-G2VSQFD6.js.map} +1 -1
  41. package/dist/chunk-GBTCWNO6.js +1 -0
  42. package/dist/chunk-GBTCWNO6.js.map +1 -0
  43. package/dist/chunk-KPJ77VPS.js +1 -0
  44. package/dist/{chunk-PL75RTA2.js.map → chunk-KPJ77VPS.js.map} +1 -1
  45. package/dist/chunk-NEXDA73Y.js +1 -0
  46. package/dist/chunk-NEXDA73Y.js.map +1 -0
  47. package/dist/chunk-P2MUFVJC.js +1 -0
  48. package/dist/chunk-P2MUFVJC.js.map +1 -0
  49. package/dist/chunk-TMXKC6JL.js +1 -0
  50. package/dist/chunk-TMXKC6JL.js.map +1 -0
  51. package/dist/chunk-ZZ376WHL.js +1 -0
  52. package/dist/chunk-ZZ376WHL.js.map +1 -0
  53. package/dist/commodities/commodities.d.ts +1 -1
  54. package/dist/equipment/index.js +1 -1
  55. package/dist/equipment/upgrade-costs.js +1 -1
  56. package/dist/equipment/upgrade-costs.js.map +1 -1
  57. package/dist/i18n/index.js +1 -1
  58. package/dist/i18n/pre-engineered.js +1 -1
  59. package/dist/materials/index.js +1 -1
  60. package/dist/materials/materials.d.ts +1 -1
  61. package/dist/materials/micro-resources-all.js +1 -1
  62. package/dist/materials/micro-resources.d.ts +1 -1
  63. package/dist/materials/micro-resources.js +1 -1
  64. package/dist/ships/build-metrics.d.ts +21 -232
  65. package/dist/ships/build-metrics.js +1 -1
  66. package/dist/ships/distributor.d.ts +1 -1
  67. package/dist/ships/heat.d.ts +1 -1
  68. package/dist/ships/index.d.ts +2 -2
  69. package/dist/ships/index.js +1 -1
  70. package/dist/ships/loadout-calculations.d.ts +2 -2
  71. package/dist/ships/mobility-capacitor.d.ts +1 -1
  72. package/dist/ships/modules-all.js +1 -1
  73. package/dist/ships/modules.d.ts +1 -1
  74. package/dist/ships/modules.js +1 -1
  75. package/dist/ships/pre-engineered-stats.d.ts +16 -9
  76. package/dist/ships/pre-engineered-stats.js +1 -1
  77. package/dist/ships/pre-engineered.d.ts +4 -3
  78. package/dist/ships/pre-engineered.js +1 -1
  79. package/dist/ships/shield-capacitor.d.ts +1 -1
  80. package/dist/ships/shields.d.ts +1 -1
  81. package/dist/ships/ship-loadout.d.ts +80 -16
  82. package/dist/ships/ship-loadout.js +1 -1
  83. package/package.json +19 -4
  84. package/dist/chunk-6S36AVA4.js +0 -1
  85. package/dist/chunk-6S36AVA4.js.map +0 -1
  86. package/dist/chunk-APEG4ZER.js +0 -1
  87. package/dist/chunk-GVNTKCVF.js +0 -1
  88. package/dist/chunk-GVNTKCVF.js.map +0 -1
  89. package/dist/chunk-JET673E5.js +0 -1
  90. package/dist/chunk-JET673E5.js.map +0 -1
  91. package/dist/chunk-PL75RTA2.js +0 -1
  92. package/dist/chunk-SG6YLUQ4.js +0 -1
  93. package/dist/chunk-SG6YLUQ4.js.map +0 -1
  94. package/dist/chunk-T62GO4AA.js +0 -1
  95. package/dist/chunk-TBZJIQ5P.js.map +0 -1
  96. /package/dist/{chunk-HUYYGTRM.js.map → chunk-3LZXB2MC.js.map} +0 -0
  97. /package/dist/{chunk-MRTTQYT5.js.map → chunk-7EWZGDWY.js.map} +0 -0
@@ -1791,6 +1791,31 @@ up straight through with no disambiguation at all. Both paths are evidence that
1791
1791
  against rows already here — the large rack is 3A and its other Merc row is 900 MC, the
1792
1792
  medium is 2B and its Lockdown row is 800 MC — and that corroboration is what carries
1793
1793
  the weight.
1794
+ - **Ten of the 22 carry a default experimental effect** applied beside the grade-1
1795
+ blueprint, recorded as `experimentalEffectSymbol`: Screening Shell on both Double
1796
+ Screaming Fragment Cannons, Incendiary Rounds on the Long Range Mining Laser,
1797
+ Phasing Sequence on the Rapid Phase Multi-Cannon, Feedback Cascade on the Enduring
1798
+ Feedback Rail Gun, Drag Munitions on both Drag Seeker Missile Racks, Thermal Cascade
1799
+ on the Lightweight Thermal Seeker Missile Rack, and FSD Interrupt on both Lockdown
1800
+ Seeker Missile Racks. Source: repository-owner in-game observation, recorded
1801
+ 2026-08-29 UTC; no registry publishes them. These rows still carry no `modifiers`:
1802
+ the effect is recorded, the unpublished grade-1 blueprint transformation is not.
1803
+ - **Two independent checks each corroborate nine of the ten, and between them cover
1804
+ all ten.** The first: the effect is one the stock module's own experimental menu
1805
+ offers, so the shop is baking in an effect the module can carry
1806
+ (`engineering-options.jsonc`). That covers every row but the Mining Laser, whose
1807
+ base module offers no experimental effect at all. The second: the same base module
1808
+ already carries the same effect on a catalogued community-goal or tech-broker
1809
+ variant. That covers every row but the large Drag Seeker Missile Rack, whose
1810
+ corroboration is its medium twin rather than a variant of its own symbol.
1811
+ - **The mining laser is the weakest of the ten.** Incendiary Rounds is outside
1812
+ `Hpt_MiningLaser_Fixed_Small`'s experimental menu — it is the only row a shop menu
1813
+ could not have offered — so only the twin check speaks for it, and a twin is
1814
+ consistency more than evidence. It is recorded because it was observed. The
1815
+ blueprint names of the seeker racks and the rail gun do echo their effects
1816
+ (`SeekerMissileRack*_Lockdown`, `_LightWeightThermal`, `_Drag`, "Enduring
1817
+ Feedback"), but a name is weaker evidence than either check above, and no row
1818
+ rests on one.
1794
1819
  - **`communityGoal`** — modules awarded for taking part in a community goal. Source:
1795
1820
  EDSY's stored-module presets, which record each reward as an encoded module state; the
1796
1821
  blueprint, grade and experimental effect were
package/README.md CHANGED
@@ -45,7 +45,8 @@ coordinate lookup are only exported from their leaf subpaths, not the feature ba
45
45
 
46
46
  The package has six feature areas:
47
47
 
48
- - `astro`: procedural names, id64 addresses, regions, nebulae and permit locks;
48
+ - `astro`: procedural names, id64 addresses, regions, nebulae, permit locks, and scanned
49
+ bodies with their physics;
49
50
  - `ships`: ships, modules, SLEF loadouts, engineering and build metrics;
50
51
  - `equipment`: Odyssey suits, handheld weapons, grade upgrades and modifications;
51
52
  - `i18n`: sparse localized catalogue names, descriptions, slot labels and structured
@@ -120,7 +121,7 @@ const build = ShipLoadout.fromSlef(slefJsonString);
120
121
  const metrics = BuildMetrics.of(build);
121
122
  metrics.maxJumpRange();
122
123
  metrics.powerBudget();
123
- metrics.shieldMetrics();
124
+ metrics.shieldMetricsResult().value;
124
125
  metrics.armourMetrics();
125
126
  metrics.weaponMetrics();
126
127
  metrics.weaponsCapacitorMetrics({ weaponsPips: 2 });
@@ -61,6 +61,28 @@ a verbatim copy produced at build time — edit this file, never the copy.
61
61
  - **SLEF parsing and writing** — both follow the
62
62
  [Inara Ship Loadout Export Format specification](https://inara.cz/elite/inara-impexp-slef/)
63
63
  published by **Inara** (Artie).
64
+ - **Body calculations** — bulk density, the rigid and fluid Roche limits, the Hill radius,
65
+ a primary's angular diameter, orbit extents and their eccentricity bands, spin-orbit
66
+ resonance, equatorial velocity, ring dynamics and surface density, the invisible-ring
67
+ heuristic, ring-particle densities, main-sequence lifetime, absolute bolometric
68
+ magnitude, the Schwarzschild radius, the degeneracy-pressure mass limits and the
69
+ neutron-star classes are ported as fact (our own implementation, re-expressed in the
70
+ journal's units) from **canonn-signals**' `body-physics.service.ts`,
71
+ `stellar-physics.service.ts` and `stellar-reference.ts` — credited under Data below.
72
+ Most of it is textbook astrophysics; three parts are the **Canonn Research Group's own
73
+ observational research** into how the game behaves, and are theirs rather than
74
+ astronomy's: the `3/8` nominal-radius factor that recovers a ring's single rigid-body
75
+ rotation period, the invisible-ring width and surface-density thresholds, and the
76
+ neutron-star classification bands with the observed in-game mass past which neutron
77
+ stars become rare.
78
+ Each carries that provenance in its own TSDoc.
79
+ - **The journal `Scan` event shape** — the body-scan import interface describes the format
80
+ **Frontier Developments plc** writes its Player Journal in, and its field set, types and
81
+ conditional presence are cross-checked against
82
+ [jixxed/ed-journal-schemas](https://github.com/jixxed/ed-journal-schemas), the
83
+ community-maintained JSON Schemas for post-Odyssey journal events, **Apache-2.0**.
84
+ Field names and types are facts about Frontier's format; no schema text is redistributed
85
+ and no code from that project is used.
64
86
 
65
87
  ## Data
66
88
 
@@ -101,8 +123,8 @@ a verbatim copy produced at build time — edit this file, never the copy.
101
123
  [Frontier forums](https://forums.frontier.co.uk/threads/determining-the-region-of-a-system.537845/).
102
124
  - **[canonn-science/canonn-signals](https://github.com/canonn-science/canonn-signals)** by
103
125
  the **Canonn Research Group**, **MIT**, © 2023 — the route by which two of the sources
104
- below were obtained, and the TypeScript port the procedural-naming algorithm passed
105
- through.
126
+ below were obtained, the TypeScript port the procedural-naming algorithm passed through,
127
+ and the source of the body calculations credited under Algorithms above.
106
128
  - **EDAstro nebulae coordinates** published by **CMDR Orvidius**
107
129
  ([EDAstro](https://edastro.com/mapcharts/)) — nebula names, catalogued systems,
108
130
  coordinates, classes and region ids, obtained via canonn-signals. EDAstro states no
@@ -0,0 +1,174 @@
1
+ import { b as BodyProperties } from '../body-scan-CEUYjCfH.js';
2
+ import './system-address-input.js';
3
+
4
+ /**
5
+ * What a scanned body's orbit and spin work out to: where the orbit actually reaches, how
6
+ * eccentric it is, whether the spin is locked to the orbit, and how fast the surface moves.
7
+ *
8
+ * Every function takes {@link BodyProperties} — a journal `Scan` line as it comes, or any
9
+ * record carrying the same fields — and works in the journal's own units: metres for
10
+ * distance, seconds for a period, degrees for an angle. Nothing here holds data. The type
11
+ * itself is exported from `../astro`, alongside `BodyScanEvent`.
12
+ *
13
+ * These read the body's own fields only, so none of them needs the primary.
14
+ *
15
+ * The maths is ported from the Canonn Research Group's
16
+ * [canonn-signals](https://github.com/canonn-science/canonn-signals); see
17
+ * [ATTRIBUTIONS.md](https://github.com/DarkSession/Elite-Dangerous-Almanac/blob/main/ATTRIBUTIONS.md).
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * import { orbitExtents } from '@elite-dangerous-almanac/core/astro/body-orbit';
22
+ *
23
+ * // A semi-major axis is a mean: this orbit is 10% closer at one end and 10% further at the other.
24
+ * const extents = orbitExtents({ SemiMajorAxis: 1e9, Eccentricity: 0.1 });
25
+ * extents?.periapsis; // -> 900000000
26
+ * extents?.apoapsis; // -> 1100000000
27
+ * ```
28
+ *
29
+ * @packageDocumentation
30
+ */
31
+
32
+ /** How far an orbit actually reaches at each end, in metres from the primary's centre. */
33
+ interface OrbitExtents {
34
+ /** The orbit's semi-major axis, in metres — its mean distance, as the scan wrote it. */
35
+ readonly semiMajorAxis: number;
36
+ /** Closest approach, in metres — `a(1 − e)`. */
37
+ readonly periapsis: number;
38
+ /** Furthest recession, in metres — `a(1 + e)`. */
39
+ readonly apoapsis: number;
40
+ /** The eccentricity used, which is `0` when the scan wrote none. */
41
+ readonly eccentricity: number;
42
+ }
43
+ /**
44
+ * How round an orbit is, in the four bands the Canonn tooling reports.
45
+ *
46
+ * - `circular` — eccentricity exactly `0`.
47
+ * - `nearly-circular` — below `0.4`.
48
+ * - `eccentric` — below `0.8`.
49
+ * - `highly-eccentric` — `0.8` and above.
50
+ *
51
+ * @remarks
52
+ * The bands are descriptive rather than physical: nothing changes at `0.4`. They exist so
53
+ * that "how unusual is this orbit" has one answer everywhere instead of a different
54
+ * threshold per caller.
55
+ */
56
+ type EccentricityClass = 'circular' | 'nearly-circular' | 'eccentric' | 'highly-eccentric';
57
+ /** A spin-orbit resonance as the small whole-number ratio it is. */
58
+ interface SpinOrbitResonance {
59
+ /** Rotations the body makes — the `3` of a 3:2 resonance. */
60
+ readonly rotations: number;
61
+ /** Orbits it makes in the same time — the `2` of a 3:2 resonance. */
62
+ readonly orbits: number;
63
+ }
64
+ /**
65
+ * Where a body's orbit actually reaches, from the mean distance and eccentricity the scan
66
+ * wrote.
67
+ *
68
+ * @remarks
69
+ * {@link BodyProperties.SemiMajorAxis | SemiMajorAxis} is an average, and an eccentric
70
+ * orbit spends none of its time there. Periapsis is the figure a Roche breach, a tidal
71
+ * heating argument or a close-approach question turns on.
72
+ *
73
+ * @param body - The body. Needs {@link BodyProperties.SemiMajorAxis | SemiMajorAxis};
74
+ * {@link BodyProperties.Eccentricity | Eccentricity} is taken as `0` when absent, which is
75
+ * what a circular orbit means.
76
+ * @returns The extents in metres, or `null` when the body has no usable semi-major axis —
77
+ * a single-star system's primary star, which orbits nothing, among them.
78
+ * @throws {TypeError} If `body` is not an object.
79
+ * @example
80
+ * ```ts
81
+ * import { orbitExtents } from '@elite-dangerous-almanac/core/astro/body-orbit';
82
+ *
83
+ * orbitExtents({ SemiMajorAxis: 1e9, Eccentricity: 0.25 })?.periapsis; // -> 750000000
84
+ *
85
+ * // No eccentricity written means a circle: both ends sit on the axis.
86
+ * orbitExtents({ SemiMajorAxis: 1e9 })?.apoapsis; // -> 1000000000
87
+ *
88
+ * orbitExtents({ MassEM: 1 }); // -> null
89
+ * ```
90
+ */
91
+ declare function orbitExtents(body: BodyProperties): OrbitExtents | null;
92
+ /**
93
+ * Which of the four descriptive bands an eccentricity falls in.
94
+ *
95
+ * @param eccentricity - An orbital eccentricity, as
96
+ * {@link BodyProperties.Eccentricity | Eccentricity} carries it: `0` for a circle, rising
97
+ * towards `1` for a long ellipse.
98
+ * @returns The band. See {@link EccentricityClass} for the thresholds.
99
+ * @throws {RangeError} If `eccentricity` is not a finite non-negative number. This takes a
100
+ * bare number rather than a body, so there is no absent field for a bad one to be.
101
+ * @example
102
+ * ```ts
103
+ * import { classifyEccentricity } from '@elite-dangerous-almanac/core/astro/body-orbit';
104
+ *
105
+ * classifyEccentricity(0); // -> 'circular'
106
+ * classifyEccentricity(0.0549); // -> 'nearly-circular'
107
+ * classifyEccentricity(0.9); // -> 'highly-eccentric'
108
+ * ```
109
+ */
110
+ declare function classifyEccentricity(eccentricity: number): EccentricityClass;
111
+ /**
112
+ * The simple whole-number resonance between a body's spin and its orbit, when there is one.
113
+ *
114
+ * @remarks
115
+ * A body in a spin-orbit resonance turns a whole number of times for a whole number of
116
+ * orbits. `1:1` is tidal locking — one face kept to the primary, which the scan also
117
+ * reports as {@link BodyProperties.TidalLock | TidalLock} — and `3:2` is the next most
118
+ * common, the resonance Mercury sits in.
119
+ *
120
+ * Both terms are searched up to 5, and a ratio counts as a resonance when it lands within
121
+ * 1% of it. A retrograde rotation is written as a negative period; the resonance is a
122
+ * ratio of magnitudes, so a retrograde tidally-locked body still reads `1:1`.
123
+ *
124
+ * @param body - The body. Needs {@link BodyProperties.RotationPeriod | RotationPeriod} and
125
+ * {@link BodyProperties.OrbitalPeriod | OrbitalPeriod}. Both are seconds, and only their
126
+ * ratio matters.
127
+ * @returns The resonance, or `null` when either period is missing or the ratio is not
128
+ * close to any simple one — most bodies are in no resonance at all.
129
+ * @throws {TypeError} If `body` is not an object.
130
+ * @example
131
+ * ```ts
132
+ * import { spinOrbitResonance } from '@elite-dangerous-almanac/core/astro/body-orbit';
133
+ *
134
+ * // One rotation per orbit: tidally locked.
135
+ * spinOrbitResonance({ RotationPeriod: 86400, OrbitalPeriod: 86400 }); // -> { rotations: 1, orbits: 1 }
136
+ *
137
+ * // Mercury: three rotations every two orbits.
138
+ * spinOrbitResonance({ RotationPeriod: 5067360, OrbitalPeriod: 7600544 }); // -> { rotations: 3, orbits: 2 }
139
+ *
140
+ * spinOrbitResonance({ RotationPeriod: 86400, OrbitalPeriod: 271828 }); // -> null
141
+ * ```
142
+ */
143
+ declare function spinOrbitResonance(body: BodyProperties): SpinOrbitResonance | null;
144
+ /**
145
+ * How fast a body's equator moves as it turns, in metres per second.
146
+ *
147
+ * @remarks
148
+ * `2πR / T`. This is the ground speed a point on the equator carries, which is why a fast
149
+ * rotator is worth knowing about before landing on one: the surface of a neutron star
150
+ * spinning in milliseconds is moving at a sizeable fraction of light speed.
151
+ *
152
+ * Speed is a magnitude, so a retrograde body — written with a negative
153
+ * {@link BodyProperties.RotationPeriod | RotationPeriod} — reads the same as a prograde
154
+ * one turning as fast.
155
+ *
156
+ * @param body - The body. Needs {@link BodyProperties.Radius | Radius} and
157
+ * {@link BodyProperties.RotationPeriod | RotationPeriod}.
158
+ * @returns The equatorial speed in m/s, or `null` when either is missing. A body the scan
159
+ * reports as not rotating carries no period, and answers `null` rather than `0`.
160
+ * @throws {TypeError} If `body` is not an object.
161
+ * @example
162
+ * ```ts
163
+ * import { equatorialVelocity } from '@elite-dangerous-almanac/core/astro/body-orbit';
164
+ *
165
+ * // Earth: about 465 m/s at the equator.
166
+ * equatorialVelocity({ Radius: 6_378_137, RotationPeriod: 86164.1 }); // -> 465.1…
167
+ *
168
+ * // Retrograde is the same speed, the other way round.
169
+ * equatorialVelocity({ Radius: 6_378_137, RotationPeriod: -86164.1 }); // -> 465.1…
170
+ * ```
171
+ */
172
+ declare function equatorialVelocity(body: BodyProperties): number | null;
173
+
174
+ export { type EccentricityClass, type OrbitExtents, type SpinOrbitResonance, classifyEccentricity, equatorialVelocity, orbitExtents, spinOrbitResonance };
@@ -0,0 +1 @@
1
+ export{classifyEccentricity,equatorialVelocity,orbitExtents,spinOrbitResonance}from"../chunk-ZZ376WHL.js"; //# sourceMappingURL=body-orbit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"O"}
@@ -0,0 +1,228 @@
1
+ import { b as BodyProperties } from '../body-scan-CEUYjCfH.js';
2
+ import './system-address-input.js';
3
+
4
+ /**
5
+ * Bulk properties of a scanned body, and the geometry between a body and the one it
6
+ * orbits: mass, density, Roche limits, Hill radius, apparent size.
7
+ *
8
+ * Every function takes {@link BodyProperties} — a journal `Scan` line as it comes, or any
9
+ * record carrying the same fields — and works in the journal's own units: metres,
10
+ * kilograms, seconds. Nothing here holds data or reads a catalogue. The type itself is
11
+ * exported from `../astro`, alongside `BodyScanEvent`.
12
+ *
13
+ * **A calculation that compares two bodies takes both.** A journal line names a body's
14
+ * parent only by `BodyID` ({@link BodyProperties} carries no parent), so
15
+ * {@link rocheLimits}, {@link hillRadius} and {@link primaryAngularDiameter} take the
16
+ * primary as a second argument. Resolve it from your own record of the system — the
17
+ * `Parents` chain on the scan says which `BodyID` to look for.
18
+ *
19
+ * **Everything answers `null` rather than guessing.** A field the scan did not write, a
20
+ * zero radius, a `NaN` mass: all are "cannot be computed", never a substituted zero.
21
+ *
22
+ * The maths is ported from the Canonn Research Group's
23
+ * [canonn-signals](https://github.com/canonn-science/canonn-signals); see
24
+ * [ATTRIBUTIONS.md](https://github.com/DarkSession/Elite-Dangerous-Almanac/blob/main/ATTRIBUTIONS.md).
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * import { bulkDensity } from '@elite-dangerous-almanac/core/astro/body-physics';
29
+ *
30
+ * bulkDensity({ MassEM: 1, Radius: 6_371_000 }); // -> 5513.2…
31
+ * ```
32
+ *
33
+ * @packageDocumentation
34
+ */
35
+
36
+ /**
37
+ * Newton's gravitational constant, in m³ kg⁻¹ s⁻² (CODATA).
38
+ *
39
+ * @remarks
40
+ * Exported because every orbital figure this library reports is proportional to it, so a
41
+ * consumer reproducing one gets the same answer rather than a near miss.
42
+ */
43
+ declare const GRAVITATIONAL_CONSTANT = 6.6743e-11;
44
+ /**
45
+ * Kilograms in one Earth mass — the unit {@link BodyProperties.MassEM | MassEM} is written
46
+ * in.
47
+ */
48
+ declare const KG_PER_EARTH_MASS = 5.972e+24;
49
+ /**
50
+ * Kilograms in one solar mass — the unit {@link BodyProperties.StellarMass | StellarMass}
51
+ * is written in.
52
+ */
53
+ declare const KG_PER_SOLAR_MASS = 1.989e+30;
54
+ /**
55
+ * The two Roche limits for one pair of bodies, in metres from the primary's centre.
56
+ *
57
+ * @remarks
58
+ * Inside the Roche limit a satellite is pulled apart by the tide the primary raises on it.
59
+ * Which limit applies depends on what holds the satellite together: a rigid body resists
60
+ * with its own strength, a fluid one only with self-gravity, so the fluid limit is the
61
+ * larger and the truth for a real body sits between them.
62
+ */
63
+ interface RocheLimits {
64
+ /**
65
+ * Rigid-body limit, in metres — `1.26 · R · (ρ_primary / ρ_satellite)^(1/3)`. The
66
+ * conservative figure: a solid body can survive closer than this only on its own
67
+ * material strength.
68
+ */
69
+ readonly rigid: number;
70
+ /**
71
+ * Fluid-body limit, in metres — `2.456 · R · (ρ_primary / ρ_satellite)^(1/3)`. The
72
+ * generous figure, for a satellite held together by gravity alone.
73
+ */
74
+ readonly fluid: number;
75
+ }
76
+ /**
77
+ * A body's mass in kilograms, whichever unit the scan wrote it in.
78
+ *
79
+ * @param body - The body. A planet or moon carries
80
+ * {@link BodyProperties.MassEM | MassEM} (Earth masses), a star
81
+ * {@link BodyProperties.StellarMass | StellarMass} (solar masses).
82
+ * @returns The mass in kilograms, or `null` when the scan wrote neither field usably. When
83
+ * a record carries both — which no journal line does — `MassEM` is the one read.
84
+ * @throws {TypeError} If `body` is not an object.
85
+ * @example
86
+ * ```ts
87
+ * import { bodyMass } from '@elite-dangerous-almanac/core/astro/body-physics';
88
+ *
89
+ * bodyMass({ MassEM: 1 }); // -> 5.972e24
90
+ * bodyMass({ StellarMass: 1 }); // -> 1.989e30
91
+ * bodyMass({ Radius: 6_371_000 }); // -> null
92
+ * ```
93
+ */
94
+ declare function bodyMass(body: BodyProperties): number | null;
95
+ /**
96
+ * A body's mean density, in kg/m³ — its mass spread evenly through a sphere of its radius.
97
+ *
98
+ * @param body - The body. Needs a mass ({@link bodyMass}) and
99
+ * {@link BodyProperties.Radius | Radius}.
100
+ * @returns Density in kg/m³, or `null` when either is missing. The scale is wide: an icy
101
+ * moon reads near 1000, rock and metal a few thousand, a neutron star around 10¹⁷.
102
+ * @throws {TypeError} If `body` is not an object.
103
+ * @example
104
+ * ```ts
105
+ * import { bulkDensity } from '@elite-dangerous-almanac/core/astro/body-physics';
106
+ *
107
+ * // Earth: one Earth mass in a 6371 km sphere.
108
+ * bulkDensity({ MassEM: 1, Radius: 6_371_000 }); // -> 5513.2…
109
+ * ```
110
+ */
111
+ declare function bulkDensity(body: BodyProperties): number | null;
112
+ /**
113
+ * Roche limits around a primary for a satellite of a stated density.
114
+ *
115
+ * @remarks
116
+ * Use this when the satellite's density is assumed rather than measured — ring material,
117
+ * for instance, whose density comes from its class rather than from a mass and a radius
118
+ * (`ringRocheLimits` in `./body-rings` is this function with that density filled in).
119
+ * When the satellite is a scanned body with a mass and a radius of its own, prefer
120
+ * {@link rocheLimits}, which reads the density off it.
121
+ *
122
+ * @param primary - The body being orbited. Needs a mass and a
123
+ * {@link BodyProperties.Radius | Radius}.
124
+ * @param satelliteDensityKgM3 - The orbiting material's density in kg/m³.
125
+ * @returns Both limits in metres from the primary's centre, or `null` when the primary's
126
+ * own density cannot be computed.
127
+ * @throws {TypeError} If `primary` is not an object.
128
+ * @throws {RangeError} If `satelliteDensityKgM3` is not a finite positive number — an
129
+ * assumed density is the caller's own figure, so a bad one is a mistake rather than a
130
+ * missing scan field.
131
+ * @example
132
+ * ```ts
133
+ * import { rocheLimitsForDensity } from '@elite-dangerous-almanac/core/astro/body-physics';
134
+ *
135
+ * // Icy particles (1000 kg/m³) around an Earth-density primary.
136
+ * const limits = rocheLimitsForDensity({ MassEM: 1, Radius: 6_371_000 }, 1000);
137
+ * limits?.rigid; // -> 14181242.2…
138
+ * limits?.fluid; // -> 27642167.3…
139
+ * ```
140
+ */
141
+ declare function rocheLimitsForDensity(primary: BodyProperties, satelliteDensityKgM3: number): RocheLimits | null;
142
+ /**
143
+ * Roche limits around a primary for one scanned satellite.
144
+ *
145
+ * @remarks
146
+ * Compare them against the satellite's **periapsis**, not its semi-major axis: a breach is
147
+ * set by closest approach, so an eccentric moon can dip inside the rigid limit while its
148
+ * mean distance looks safe. `orbitExtents` in `./body-orbit` gives that periapsis.
149
+ *
150
+ * @param satellite - The orbiting body. Needs a mass and a
151
+ * {@link BodyProperties.Radius | Radius}, from which its density is taken.
152
+ * @param primary - The body being orbited. Needs the same.
153
+ * @returns Both limits in metres from the primary's centre, or `null` when either body's
154
+ * density cannot be computed.
155
+ * @throws {TypeError} If either argument is not an object.
156
+ * @example
157
+ * ```ts
158
+ * import { rocheLimits } from '@elite-dangerous-almanac/core/astro/body-physics';
159
+ * import { orbitExtents } from '@elite-dangerous-almanac/core/astro/body-orbit';
160
+ *
161
+ * const moon = { MassEM: 0.0123, Radius: 1_737_400, SemiMajorAxis: 3.844e8, Eccentricity: 0.0549 };
162
+ * const earth = { MassEM: 1, Radius: 6_371_000 };
163
+ *
164
+ * const limits = rocheLimits(moon, earth);
165
+ * const closest = orbitExtents(moon)?.periapsis ?? 0;
166
+ *
167
+ * closest < (limits?.rigid ?? 0); // -> false
168
+ * ```
169
+ */
170
+ declare function rocheLimits(satellite: BodyProperties, primary: BodyProperties): RocheLimits | null;
171
+ /**
172
+ * The radius of a body's Hill sphere, in metres — how far its own gravity wins against the
173
+ * body it orbits.
174
+ *
175
+ * @remarks
176
+ * `r_H = a · (m / 3M)^(1/3)`. Anything orbiting the body must stay inside this radius to
177
+ * remain bound to it, so it is the reach a moon has over nearby material — which is what
178
+ * makes a small moon a shepherd of a ring it does not sit in.
179
+ *
180
+ * The semi-major axis is used as written, so the figure is the mean-distance Hill radius.
181
+ * On an eccentric orbit the sphere shrinks at periapsis; recompute with
182
+ * `orbitExtents(body)?.periapsis` in place of the axis when that matters.
183
+ *
184
+ * @param body - The body whose sphere is wanted. Needs
185
+ * {@link BodyProperties.SemiMajorAxis | SemiMajorAxis} and a mass.
186
+ * @param primary - The body it orbits. Needs a mass.
187
+ * @returns The Hill radius in metres, or `null` when a mass or the orbit is missing.
188
+ * @throws {TypeError} If either argument is not an object.
189
+ * @example
190
+ * ```ts
191
+ * import { hillRadius } from '@elite-dangerous-almanac/core/astro/body-physics';
192
+ *
193
+ * // Earth about the Sun: about 1.5 million km.
194
+ * hillRadius({ MassEM: 1, SemiMajorAxis: 1.496e11 }, { StellarMass: 1 }); // -> 1496417737.0…
195
+ * ```
196
+ */
197
+ declare function hillRadius(body: BodyProperties, primary: BodyProperties): number | null;
198
+ /**
199
+ * How large the body it orbits appears in a body's sky, as an angular diameter in degrees.
200
+ *
201
+ * @remarks
202
+ * `2 · atan(R_primary / a)`, taken exactly rather than through the small-angle
203
+ * approximation `2R/a`: the two diverge by percent at the tens of degrees a parent
204
+ * actually subtends from a close moon, which is the range worth asking about. A landable
205
+ * moon whose primary fills 45° of sky is a view; one at 0.5° is a dot.
206
+ *
207
+ * Distance is the body's own semi-major axis, centre to centre, so the figure is the mean.
208
+ *
209
+ * @param body - The body being stood on. Needs
210
+ * {@link BodyProperties.SemiMajorAxis | SemiMajorAxis}.
211
+ * @param primary - The body it orbits. Needs {@link BodyProperties.Radius | Radius}.
212
+ * @returns The angular diameter in degrees, or `null` when the orbit or the primary's
213
+ * radius is missing.
214
+ * @throws {TypeError} If either argument is not an object.
215
+ * @example
216
+ * ```ts
217
+ * import { primaryAngularDiameter } from '@elite-dangerous-almanac/core/astro/body-physics';
218
+ *
219
+ * // A primary as far away as it is wide fills a right angle of sky.
220
+ * primaryAngularDiameter({ SemiMajorAxis: 1e9 }, { Radius: 1e9 }); // -> 90
221
+ *
222
+ * // Earth's Moon, looking back: Earth subtends about 2°.
223
+ * primaryAngularDiameter({ SemiMajorAxis: 3.844e8 }, { Radius: 6_371_000 }); // -> 1.899…
224
+ * ```
225
+ */
226
+ declare function primaryAngularDiameter(body: BodyProperties, primary: BodyProperties): number | null;
227
+
228
+ export { GRAVITATIONAL_CONSTANT, KG_PER_EARTH_MASS, KG_PER_SOLAR_MASS, type RocheLimits, bodyMass, bulkDensity, hillRadius, primaryAngularDiameter, rocheLimits, rocheLimitsForDensity };
@@ -0,0 +1 @@
1
+ export{GRAVITATIONAL_CONSTANT,KG_PER_EARTH_MASS,KG_PER_SOLAR_MASS,bodyMass,bulkDensity,hillRadius,primaryAngularDiameter,rocheLimits,rocheLimitsForDensity}from"../chunk-TMXKC6JL.js"; //# sourceMappingURL=body-physics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"O"}