caelus 0.11.0 → 0.13.0

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 (45) hide show
  1. package/README.md +1 -1
  2. package/accuracy.json +1 -1
  3. package/dist/src/chart.d.ts +193 -13
  4. package/dist/src/chart.js +163 -13
  5. package/dist/src/compiler.d.ts +44 -3
  6. package/dist/src/compiler.js +44 -3
  7. package/dist/src/core.d.ts +33 -2
  8. package/dist/src/core.js +33 -2
  9. package/dist/src/derived.d.ts +91 -2
  10. package/dist/src/derived.js +91 -2
  11. package/dist/src/directions.d.ts +27 -0
  12. package/dist/src/directions.js +69 -0
  13. package/dist/src/eclipses.d.ts +17 -1
  14. package/dist/src/eclipses.js +17 -1
  15. package/dist/src/events.d.ts +53 -7
  16. package/dist/src/events.js +53 -7
  17. package/dist/src/features.d.ts +69 -7
  18. package/dist/src/features.js +69 -7
  19. package/dist/src/firdaria.d.ts +49 -0
  20. package/dist/src/firdaria.js +62 -0
  21. package/dist/src/houses.d.ts +13 -4
  22. package/dist/src/houses.js +13 -4
  23. package/dist/src/index.d.ts +9 -0
  24. package/dist/src/index.js +9 -0
  25. package/dist/src/lots.d.ts +18 -0
  26. package/dist/src/lots.js +52 -0
  27. package/dist/src/pheno.d.ts +16 -2
  28. package/dist/src/pheno.js +16 -2
  29. package/dist/src/profections.d.ts +27 -0
  30. package/dist/src/profections.js +49 -0
  31. package/dist/src/query.d.ts +47 -6
  32. package/dist/src/query.js +47 -6
  33. package/dist/src/releasing.d.ts +32 -0
  34. package/dist/src/releasing.js +109 -0
  35. package/dist/src/turbo.d.ts +35 -1
  36. package/dist/src/turbo.js +35 -1
  37. package/dist/src/vargas.d.ts +32 -0
  38. package/dist/src/vargas.js +52 -0
  39. package/dist/src/vedic.d.ts +66 -0
  40. package/dist/src/vedic.js +101 -0
  41. package/dist/src/yogas.d.ts +26 -0
  42. package/dist/src/yogas.js +39 -0
  43. package/dist/src/yogini.d.ts +54 -0
  44. package/dist/src/yogini.js +63 -0
  45. package/package.json +1 -1
package/README.md CHANGED
@@ -100,4 +100,4 @@ test/golden.test.ts conformance suite vs Python fixtures
100
100
  - caelus — this package
101
101
  - [caelus-birth](https://www.npmjs.com/package/caelus-birth) — local birth time + place → UT (charts take UT; use this)
102
102
  - [caelus-wheel](https://www.npmjs.com/package/caelus-wheel) — React SVG chart wheel
103
- - [caelus-mcp](https://www.npmjs.com/package/caelus-mcp) — MCP server, nine chart tools over stdio
103
+ - [caelus-mcp](https://www.npmjs.com/package/caelus-mcp) — MCP server, eighteen chart tools over stdio
package/accuracy.json CHANGED
@@ -273,7 +273,7 @@
273
273
  "counts": {
274
274
  "house_systems": 12,
275
275
  "sidereal_ayanamsas": 7,
276
- "mcp_tools": 9,
276
+ "mcp_tools": 18,
277
277
  "default_bodies": 13
278
278
  }
279
279
  }
@@ -17,51 +17,82 @@ export interface Observer {
17
17
  lonEast: number;
18
18
  altM?: number;
19
19
  }
20
+ /** Options shared by the single-body calls ({@link Engine.position},
21
+ * {@link Engine.longitude}) and by charts. */
20
22
  export interface CalcOptions {
23
+ /** Tropical (the default) or a sidereal ayanamsa, e.g. `"sidereal:lahiri"`. */
21
24
  zodiac?: Zodiac;
25
+ /** Apply topocentric parallax for `observer`. Defaults to `false`. */
22
26
  topocentric?: boolean;
27
+ /** Observer location; required when `topocentric` is set. */
23
28
  observer?: Observer;
24
29
  }
30
+ /** Options for {@link Engine.chart} and {@link Engine.chartAt}, extending
31
+ * {@link CalcOptions}. */
25
32
  export interface ChartOptions extends CalcOptions {
33
+ /** House system to compute. Defaults to `"placidus"`. */
26
34
  houseSystem?: HouseSystem;
35
+ /** Extra bodies to compute beyond the core chart set. */
27
36
  bodies?: BodyId[];
37
+ /** Per-aspect orb overrides in degrees, keyed by aspect name. */
28
38
  orbs?: Record<string, number>;
29
39
  }
40
+ /** A body's full apparent position, as returned by {@link Engine.position}. */
30
41
  export interface Position {
42
+ /** Ecliptic longitude in degrees, `[0, 360)`. */
31
43
  lon: number;
44
+ /** Daily motion in longitude, degrees/day; negative when retrograde. */
32
45
  speed: number;
46
+ /** Whether the body is in apparent retrograde motion (`speed < 0`). */
33
47
  retrograde: boolean;
48
+ /** Zodiac sign containing `lon`, e.g. `"Leo"`. */
34
49
  sign: string;
50
+ /** Longitude within the sign, degrees `[0, 30)`. */
35
51
  signDeg: number;
36
52
  /** Ecliptic latitude, deg (0 for nodes). */
37
53
  lat: number;
38
54
  /** Geocentric distance in AU (Moon included); null for nodes and Lilith. */
39
55
  dist: number | null;
40
- /** Equatorial coordinates, true equinox of date, deg. */
56
+ /** Equatorial right ascension, true equinox of date, degrees. */
41
57
  ra: number;
58
+ /** Equatorial declination, true equinox of date, degrees. */
42
59
  dec: number;
43
60
  }
61
+ /** One aspect between two bodies in a {@link Chart}. */
44
62
  export interface Aspect {
63
+ /** First body id. */
45
64
  a: string;
65
+ /** Second body id. */
46
66
  b: string;
67
+ /** Aspect name, e.g. `"trine"`. */
47
68
  aspect: string;
69
+ /** Orb from exact, in degrees. */
48
70
  orb: number;
49
71
  }
72
+ /** A full natal chart, as returned by {@link Engine.chart} and
73
+ * {@link Engine.chartAt}. Longitudes are degrees in the chart's `zodiac`. */
50
74
  export interface Chart {
75
+ /** The instant, as a Julian Day (UT). */
51
76
  jdUt: number;
77
+ /** The zodiac the longitudes are expressed in. */
52
78
  zodiac: Zodiac;
53
79
  /** House system actually used. May differ from the request: Placidus and
54
80
  * Koch are undefined above the polar circles and fall back to whole_sign. */
55
81
  houseSystem: HouseSystem;
82
+ /** The house system originally requested, before any polar fallback. */
56
83
  houseSystemRequested: HouseSystem;
84
+ /** Apparent {@link Position} per body, keyed by body id. */
57
85
  bodies: Record<string, Position>;
86
+ /** Chart angles in degrees: Ascendant, Midheaven, Vertex, East Point. */
58
87
  angles: {
59
88
  asc: number;
60
89
  mc: number;
61
90
  vertex: number;
62
91
  eastPoint: number;
63
92
  };
93
+ /** The twelve house cusp longitudes in degrees, house 1 first. */
64
94
  cusps: number[];
95
+ /** Aspects found among the bodies, within the active orbs. */
65
96
  aspects: Aspect[];
66
97
  }
67
98
  export declare class Engine {
@@ -72,15 +103,56 @@ export declare class Engine {
72
103
  constructor(data: EngineData);
73
104
  private pack;
74
105
  private moonInRange;
75
- /** Body ids this engine can compute, given the data it was handed. */
106
+ /**
107
+ * The body ids this engine can compute, given the data pack it was
108
+ * constructed with. The core set is always present; extra asteroids and
109
+ * hypotheticals appear only when their Chebyshev or Kepler packs are loaded.
110
+ *
111
+ * @returns Body ids accepted by {@link Engine.position},
112
+ * {@link Engine.longitude}, and {@link Engine.chart}.
113
+ * @example
114
+ * ```ts
115
+ * engine.bodies().includes("ceres"); // true only if the Ceres pack is loaded
116
+ * ```
117
+ */
76
118
  bodies(): BodyId[];
77
- /** Apparent geocentric [lon rad, lat rad, dist AU | null] at TT jde.
78
- * Building block for the events module; chart consumers want
79
- * position() instead. */
119
+ /**
120
+ * Low-level apparent geocentric ecliptic coordinates at a **TT** Julian Day,
121
+ * in **radians**. This is the engine's internal building block for the events
122
+ * module; it takes TT (not UT) and does no zodiac shift. Most callers want
123
+ * {@link Engine.position} (full Position in degrees) or
124
+ * {@link Engine.longitude} (longitude in degrees) instead.
125
+ *
126
+ * @param body A body id from {@link Engine.bodies}.
127
+ * @param jde Julian Day in **TT** (Terrestrial Time), e.g. `jdTT(jdUt)`.
128
+ * @returns `[lon, lat, dist]` — longitude and latitude in **radians** (true
129
+ * equinox of date), distance in AU, or `null` distance for nodes and
130
+ * Lilith points.
131
+ * @throws Error if no data is loaded for `body`.
132
+ */
80
133
  ecliptic(body: BodyId, jde: number): [number, number, number | null];
81
134
  /** Degrees to subtract from a true-equinox tropical longitude. */
82
135
  private ayanShift;
83
- /** Apparent place of a catalog star: lon/lat/ra/dec (deg), sign, mag. */
136
+ /**
137
+ * Apparent place of a catalog fixed star at a Julian Day (UT). Requires the
138
+ * fixed-star catalog to be present in the data pack; see
139
+ * {@link Engine.starNames} for the available names.
140
+ *
141
+ * @param name Catalog star name, e.g. `"Regulus"` (see
142
+ * {@link Engine.starNames}).
143
+ * @param jdUt Julian Day in UT.
144
+ * @param opts Calculation options; only `zodiac` is meaningful here (tropical
145
+ * by default, or a sidereal ayanamsa).
146
+ * @returns Ecliptic `lon`/`lat`, equatorial `ra`/`dec` (all degrees), the
147
+ * zodiac `sign` and `signDeg`, and the star's visual magnitude `mag`.
148
+ * @throws Error if `name` is not in the loaded catalog.
149
+ * @example
150
+ * ```ts
151
+ * const regulus = engine.fixedStar("Regulus", julianDay(2025, 1, 1));
152
+ * regulus.sign; // e.g. "Leo"
153
+ * regulus.mag; // apparent magnitude
154
+ * ```
155
+ */
84
156
  fixedStar(name: string, jdUt: number, opts?: CalcOptions): {
85
157
  lon: number;
86
158
  lat: number;
@@ -90,23 +162,131 @@ export declare class Engine {
90
162
  sign: string;
91
163
  signDeg: number;
92
164
  };
93
- /** Names in the loaded fixed-star catalog (sorted). */
165
+ /**
166
+ * The names in the loaded fixed-star catalog, sorted. Empty if no catalog is
167
+ * present in the data pack. Pass any of these to {@link Engine.fixedStar}.
168
+ *
169
+ * @returns Sorted catalog star names.
170
+ */
94
171
  starNames(): string[];
95
172
  private lonOnly;
96
- /** Apparent geocentric ecliptic longitude (deg). Tropical: true equinox
97
- * of date. Sidereal: mean equinox minus ayanamsa. */
173
+ /**
174
+ * Apparent geocentric ecliptic longitude of a body, in degrees `[0, 360)`,
175
+ * at a Julian Day (UT). The fast path when you need only a longitude — a
176
+ * transit position, an aspect angle, a sign — without the full
177
+ * {@link Position}. In the tropical zodiac this is referred to the true
178
+ * equinox of date; sidereal subtracts the ayanamsa.
179
+ *
180
+ * @param body A body id from {@link Engine.bodies}.
181
+ * @param jdUt Julian Day in UT.
182
+ * @param opts Calculation options: `zodiac` (tropical or a sidereal
183
+ * ayanamsa), and `topocentric` with an `observer` for a parallax-corrected
184
+ * place.
185
+ * @returns Ecliptic longitude in degrees, `[0, 360)`.
186
+ * @example
187
+ * ```ts
188
+ * engine.longitude("mars", julianDay(2025, 6, 1)); // tropical
189
+ * engine.longitude("mars", julianDay(2025, 6, 1), { zodiac: "sidereal:lahiri" });
190
+ * ```
191
+ * @see {@link Engine.position} for speed, retrograde, latitude, and distance.
192
+ */
98
193
  longitude(body: BodyId, jdUt: number, opts?: CalcOptions): number;
99
- /** Geometric heliocentric ecliptic of date (deg, deg, AU). */
194
+ /**
195
+ * Geometric heliocentric ecliptic position (Sun-centred) at a Julian Day
196
+ * (UT), referred to the ecliptic of date. Unlike {@link Engine.position},
197
+ * this is a geometric place — no light-time, aberration, or nutation — and is
198
+ * undefined for the Sun, the Moon, and the lunar nodes.
199
+ *
200
+ * @param body A Sun-orbiting body (planet or asteroid) from
201
+ * {@link Engine.bodies}.
202
+ * @param jdUt Julian Day in UT.
203
+ * @returns Heliocentric `lon`/`lat` in degrees and `dist` in AU.
204
+ * @throws Error if `body` has no heliocentric solution (e.g. the Moon).
205
+ */
100
206
  heliocentric(body: BodyId, jdUt: number): {
101
207
  lon: number;
102
208
  lat: number;
103
209
  dist: number;
104
210
  };
105
- /** Full position: lon/speed/retrograde/sign + lat, dist (AU), ra, dec. */
211
+ /**
212
+ * Full apparent position of a body at a Julian Day (UT): ecliptic longitude
213
+ * and daily speed (with a retrograde flag), the zodiac sign, ecliptic
214
+ * latitude, geocentric distance, and equatorial right ascension and
215
+ * declination. The general-purpose single-body call; use
216
+ * {@link Engine.longitude} when you need only the longitude.
217
+ *
218
+ * @param body A body id from {@link Engine.bodies}.
219
+ * @param jdUt Julian Day in UT.
220
+ * @param opts Calculation options: `zodiac` (tropical or a sidereal
221
+ * ayanamsa), and `topocentric` with an `observer` for a parallax-corrected
222
+ * place.
223
+ * @returns A {@link Position}: `lon`, `speed`, `retrograde`, `sign`,
224
+ * `signDeg`, `lat`, `dist` (AU; `null` for nodes and Lilith), `ra`, `dec`.
225
+ * @example
226
+ * ```ts
227
+ * const mars = engine.position("mars", julianDay(2025, 6, 1));
228
+ * mars.retrograde; // boolean
229
+ * mars.speed; // degrees/day (negative when retrograde)
230
+ * ```
231
+ */
106
232
  position(body: BodyId, jdUt: number, opts?: CalcOptions): Position;
107
- /** Full natal chart. Time is UT. East longitude positive. The ninth
108
- * argument takes a house system name (0.2.x form) or a ChartOptions bag. */
233
+ /**
234
+ * Full natal chart: body positions, house cusps, angles, and aspects for one
235
+ * instant and place.
236
+ *
237
+ * The first six arguments are calendar fields in **UT** — not local civil
238
+ * time, and not a Julian Day. Passing a JD in `y` builds an instant far
239
+ * outside the fitted range and throws `RangeError`; use {@link Engine.chartAt}
240
+ * for a chart from a JD. For a birth time given in a local time zone, resolve
241
+ * it to UT first (see the `caelus-birth` package).
242
+ *
243
+ * @param y Year in UT, e.g. `1990` — a calendar year, not a Julian Day.
244
+ * @param mo Month, `1`–`12`.
245
+ * @param d Day of month, `1`–`31`.
246
+ * @param h Hour in UT, `0`–`23`.
247
+ * @param mi Minute, `0`–`59`.
248
+ * @param s Second, `0`–`59`.
249
+ * @param lat Geographic latitude in degrees, north positive.
250
+ * @param lonEast Geographic longitude in degrees, **east positive** (so
251
+ * 82.46° W is `-82.46`).
252
+ * @param opts A house-system name (e.g. `"placidus"`) or a
253
+ * {@link ChartOptions} bag for zodiac, topocentric mode, extra bodies, and
254
+ * custom orbs. Defaults to Placidus houses in the tropical zodiac.
255
+ * @returns A {@link Chart}: `bodies`, `cusps`, `angles`, and `aspects`, plus
256
+ * `jdUt` and the house system actually used (Placidus and Koch fall back to
257
+ * whole-sign above the polar circles).
258
+ * @throws RangeError if the instant lands outside the fitted range
259
+ * (1800–2149) — most often from passing a Julian Day where a year belongs.
260
+ * @example
261
+ * ```ts
262
+ * // 1990-06-10 14:30 UT at Tampa, FL (27.95° N, 82.46° W), Placidus houses
263
+ * const chart = engine.chart(1990, 6, 10, 14, 30, 0, 27.95, -82.46, "placidus");
264
+ * chart.bodies.sun.lon; // Sun's ecliptic longitude, degrees
265
+ * chart.angles.asc; // Ascendant, degrees
266
+ * ```
267
+ * @see {@link Engine.chartAt} to build the same chart from a Julian Day.
268
+ */
109
269
  chart(y: number, mo: number, d: number, h: number, mi: number, s: number, lat: number, lonEast: number, opts?: HouseSystem | ChartOptions): Chart;
270
+ /**
271
+ * Full natal chart from a Julian Day (UT) — identical output to
272
+ * {@link Engine.chart}, without the calendar round-trip. Reach for this when
273
+ * you already hold a JD: transit and event scans, `rankMoments` winners, or
274
+ * `position`/`longitude` workflows.
275
+ *
276
+ * @param jdUt Julian Day in UT, e.g. from {@link julianDay} or a scan.
277
+ * @param lat Geographic latitude in degrees, north positive.
278
+ * @param lonEast Geographic longitude in degrees, east positive.
279
+ * @param opts A house-system name or a {@link ChartOptions} bag. Defaults to
280
+ * Placidus houses in the tropical zodiac.
281
+ * @returns The same {@link Chart} shape returned by {@link Engine.chart}.
282
+ * @example
283
+ * ```ts
284
+ * const jd = julianDay(1990, 6, 10, 14, 30, 0);
285
+ * const chart = engine.chartAt(jd, 27.95, -82.46, "placidus");
286
+ * ```
287
+ * @see {@link Engine.chart} for the calendar-field entry point.
288
+ */
289
+ chartAt(jdUt: number, lat: number, lonEast: number, opts?: HouseSystem | ChartOptions): Chart;
110
290
  }
111
291
  export declare function findAspects(bodies: Record<string, Position>, orbs?: Record<string, number>): Aspect[];
112
292
  export declare function fmtLon(deg: number): string;
package/dist/src/chart.js CHANGED
@@ -73,7 +73,18 @@ export class Engine {
73
73
  return !!this.moonCheb
74
74
  && this.moonCheb.jd0 <= jde - 0.1 && jde + 0.1 <= this.moonCheb.jd1;
75
75
  }
76
- /** Body ids this engine can compute, given the data it was handed. */
76
+ /**
77
+ * The body ids this engine can compute, given the data pack it was
78
+ * constructed with. The core set is always present; extra asteroids and
79
+ * hypotheticals appear only when their Chebyshev or Kepler packs are loaded.
80
+ *
81
+ * @returns Body ids accepted by {@link Engine.position},
82
+ * {@link Engine.longitude}, and {@link Engine.chart}.
83
+ * @example
84
+ * ```ts
85
+ * engine.bodies().includes("ceres"); // true only if the Ceres pack is loaded
86
+ * ```
87
+ */
77
88
  bodies() {
78
89
  return [
79
90
  ...[...BODIES, ...EXTRA_BODIES].filter((b) => b !== "chiron" || this.chironCheb),
@@ -81,9 +92,20 @@ export class Engine {
81
92
  ...Object.keys(this.data.keplerPack?.bodies ?? {}),
82
93
  ];
83
94
  }
84
- /** Apparent geocentric [lon rad, lat rad, dist AU | null] at TT jde.
85
- * Building block for the events module; chart consumers want
86
- * position() instead. */
95
+ /**
96
+ * Low-level apparent geocentric ecliptic coordinates at a **TT** Julian Day,
97
+ * in **radians**. This is the engine's internal building block for the events
98
+ * module; it takes TT (not UT) and does no zodiac shift. Most callers want
99
+ * {@link Engine.position} (full Position in degrees) or
100
+ * {@link Engine.longitude} (longitude in degrees) instead.
101
+ *
102
+ * @param body A body id from {@link Engine.bodies}.
103
+ * @param jde Julian Day in **TT** (Terrestrial Time), e.g. `jdTT(jdUt)`.
104
+ * @returns `[lon, lat, dist]` — longitude and latitude in **radians** (true
105
+ * equinox of date), distance in AU, or `null` distance for nodes and
106
+ * Lilith points.
107
+ * @throws Error if no data is loaded for `body`.
108
+ */
87
109
  ecliptic(body, jde) {
88
110
  if (body === "sun")
89
111
  return sunApparent(this.data, jde);
@@ -140,7 +162,26 @@ export class Engine {
140
162
  }
141
163
  return mod(nutation(this.data, jde)[0] / DEG + ayanamsa(jde, mode), 360);
142
164
  }
143
- /** Apparent place of a catalog star: lon/lat/ra/dec (deg), sign, mag. */
165
+ /**
166
+ * Apparent place of a catalog fixed star at a Julian Day (UT). Requires the
167
+ * fixed-star catalog to be present in the data pack; see
168
+ * {@link Engine.starNames} for the available names.
169
+ *
170
+ * @param name Catalog star name, e.g. `"Regulus"` (see
171
+ * {@link Engine.starNames}).
172
+ * @param jdUt Julian Day in UT.
173
+ * @param opts Calculation options; only `zodiac` is meaningful here (tropical
174
+ * by default, or a sidereal ayanamsa).
175
+ * @returns Ecliptic `lon`/`lat`, equatorial `ra`/`dec` (all degrees), the
176
+ * zodiac `sign` and `signDeg`, and the star's visual magnitude `mag`.
177
+ * @throws Error if `name` is not in the loaded catalog.
178
+ * @example
179
+ * ```ts
180
+ * const regulus = engine.fixedStar("Regulus", julianDay(2025, 1, 1));
181
+ * regulus.sign; // e.g. "Leo"
182
+ * regulus.mag; // apparent magnitude
183
+ * ```
184
+ */
144
185
  fixedStar(name, jdUt, opts = {}) {
145
186
  const s = this.data.fixedStars?.stars[name];
146
187
  if (!s)
@@ -157,7 +198,12 @@ export class Engine {
157
198
  sign: SIGNS[Math.floor(lon / 30)], signDeg: mod(lon, 30),
158
199
  };
159
200
  }
160
- /** Names in the loaded fixed-star catalog (sorted). */
201
+ /**
202
+ * The names in the loaded fixed-star catalog, sorted. Empty if no catalog is
203
+ * present in the data pack. Pass any of these to {@link Engine.fixedStar}.
204
+ *
205
+ * @returns Sorted catalog star names.
206
+ */
161
207
  starNames() {
162
208
  return Object.keys(this.data.fixedStars?.stars ?? {}).sort();
163
209
  }
@@ -173,14 +219,43 @@ export class Engine {
173
219
  lonDeg = mod(lonDeg - this.ayanShift(jde, mode), 360);
174
220
  return lonDeg;
175
221
  }
176
- /** Apparent geocentric ecliptic longitude (deg). Tropical: true equinox
177
- * of date. Sidereal: mean equinox minus ayanamsa. */
222
+ /**
223
+ * Apparent geocentric ecliptic longitude of a body, in degrees `[0, 360)`,
224
+ * at a Julian Day (UT). The fast path when you need only a longitude — a
225
+ * transit position, an aspect angle, a sign — without the full
226
+ * {@link Position}. In the tropical zodiac this is referred to the true
227
+ * equinox of date; sidereal subtracts the ayanamsa.
228
+ *
229
+ * @param body A body id from {@link Engine.bodies}.
230
+ * @param jdUt Julian Day in UT.
231
+ * @param opts Calculation options: `zodiac` (tropical or a sidereal
232
+ * ayanamsa), and `topocentric` with an `observer` for a parallax-corrected
233
+ * place.
234
+ * @returns Ecliptic longitude in degrees, `[0, 360)`.
235
+ * @example
236
+ * ```ts
237
+ * engine.longitude("mars", julianDay(2025, 6, 1)); // tropical
238
+ * engine.longitude("mars", julianDay(2025, 6, 1), { zodiac: "sidereal:lahiri" });
239
+ * ```
240
+ * @see {@link Engine.position} for speed, retrograde, latitude, and distance.
241
+ */
178
242
  longitude(body, jdUt, opts = {}) {
179
243
  const mode = parseZodiac(opts.zodiac ?? "tropical");
180
244
  const topo = opts.topocentric ? opts.observer ?? null : null;
181
245
  return this.lonOnly(body, jdUt, mode, topo);
182
246
  }
183
- /** Geometric heliocentric ecliptic of date (deg, deg, AU). */
247
+ /**
248
+ * Geometric heliocentric ecliptic position (Sun-centred) at a Julian Day
249
+ * (UT), referred to the ecliptic of date. Unlike {@link Engine.position},
250
+ * this is a geometric place — no light-time, aberration, or nutation — and is
251
+ * undefined for the Sun, the Moon, and the lunar nodes.
252
+ *
253
+ * @param body A Sun-orbiting body (planet or asteroid) from
254
+ * {@link Engine.bodies}.
255
+ * @param jdUt Julian Day in UT.
256
+ * @returns Heliocentric `lon`/`lat` in degrees and `dist` in AU.
257
+ * @throws Error if `body` has no heliocentric solution (e.g. the Moon).
258
+ */
184
259
  heliocentric(body, jdUt) {
185
260
  const jde = jdTT(jdUt);
186
261
  let l;
@@ -214,7 +289,27 @@ export class Engine {
214
289
  }
215
290
  return { lon: l / DEG, lat: b / DEG, dist: r };
216
291
  }
217
- /** Full position: lon/speed/retrograde/sign + lat, dist (AU), ra, dec. */
292
+ /**
293
+ * Full apparent position of a body at a Julian Day (UT): ecliptic longitude
294
+ * and daily speed (with a retrograde flag), the zodiac sign, ecliptic
295
+ * latitude, geocentric distance, and equatorial right ascension and
296
+ * declination. The general-purpose single-body call; use
297
+ * {@link Engine.longitude} when you need only the longitude.
298
+ *
299
+ * @param body A body id from {@link Engine.bodies}.
300
+ * @param jdUt Julian Day in UT.
301
+ * @param opts Calculation options: `zodiac` (tropical or a sidereal
302
+ * ayanamsa), and `topocentric` with an `observer` for a parallax-corrected
303
+ * place.
304
+ * @returns A {@link Position}: `lon`, `speed`, `retrograde`, `sign`,
305
+ * `signDeg`, `lat`, `dist` (AU; `null` for nodes and Lilith), `ra`, `dec`.
306
+ * @example
307
+ * ```ts
308
+ * const mars = engine.position("mars", julianDay(2025, 6, 1));
309
+ * mars.retrograde; // boolean
310
+ * mars.speed; // degrees/day (negative when retrograde)
311
+ * ```
312
+ */
218
313
  position(body, jdUt, opts = {}) {
219
314
  const mode = parseZodiac(opts.zodiac ?? "tropical");
220
315
  const topo = opts.topocentric ? opts.observer ?? null : null;
@@ -239,14 +334,69 @@ export class Engine {
239
334
  ra: ra / DEG, dec: dec / DEG,
240
335
  };
241
336
  }
242
- /** Full natal chart. Time is UT. East longitude positive. The ninth
243
- * argument takes a house system name (0.2.x form) or a ChartOptions bag. */
337
+ /**
338
+ * Full natal chart: body positions, house cusps, angles, and aspects for one
339
+ * instant and place.
340
+ *
341
+ * The first six arguments are calendar fields in **UT** — not local civil
342
+ * time, and not a Julian Day. Passing a JD in `y` builds an instant far
343
+ * outside the fitted range and throws `RangeError`; use {@link Engine.chartAt}
344
+ * for a chart from a JD. For a birth time given in a local time zone, resolve
345
+ * it to UT first (see the `caelus-birth` package).
346
+ *
347
+ * @param y Year in UT, e.g. `1990` — a calendar year, not a Julian Day.
348
+ * @param mo Month, `1`–`12`.
349
+ * @param d Day of month, `1`–`31`.
350
+ * @param h Hour in UT, `0`–`23`.
351
+ * @param mi Minute, `0`–`59`.
352
+ * @param s Second, `0`–`59`.
353
+ * @param lat Geographic latitude in degrees, north positive.
354
+ * @param lonEast Geographic longitude in degrees, **east positive** (so
355
+ * 82.46° W is `-82.46`).
356
+ * @param opts A house-system name (e.g. `"placidus"`) or a
357
+ * {@link ChartOptions} bag for zodiac, topocentric mode, extra bodies, and
358
+ * custom orbs. Defaults to Placidus houses in the tropical zodiac.
359
+ * @returns A {@link Chart}: `bodies`, `cusps`, `angles`, and `aspects`, plus
360
+ * `jdUt` and the house system actually used (Placidus and Koch fall back to
361
+ * whole-sign above the polar circles).
362
+ * @throws RangeError if the instant lands outside the fitted range
363
+ * (1800–2149) — most often from passing a Julian Day where a year belongs.
364
+ * @example
365
+ * ```ts
366
+ * // 1990-06-10 14:30 UT at Tampa, FL (27.95° N, 82.46° W), Placidus houses
367
+ * const chart = engine.chart(1990, 6, 10, 14, 30, 0, 27.95, -82.46, "placidus");
368
+ * chart.bodies.sun.lon; // Sun's ecliptic longitude, degrees
369
+ * chart.angles.asc; // Ascendant, degrees
370
+ * ```
371
+ * @see {@link Engine.chartAt} to build the same chart from a Julian Day.
372
+ */
244
373
  chart(y, mo, d, h, mi, s, lat, lonEast, opts = "placidus") {
374
+ return this.chartAt(julianDay(y, mo, d, h, mi, s), lat, lonEast, opts);
375
+ }
376
+ /**
377
+ * Full natal chart from a Julian Day (UT) — identical output to
378
+ * {@link Engine.chart}, without the calendar round-trip. Reach for this when
379
+ * you already hold a JD: transit and event scans, `rankMoments` winners, or
380
+ * `position`/`longitude` workflows.
381
+ *
382
+ * @param jdUt Julian Day in UT, e.g. from {@link julianDay} or a scan.
383
+ * @param lat Geographic latitude in degrees, north positive.
384
+ * @param lonEast Geographic longitude in degrees, east positive.
385
+ * @param opts A house-system name or a {@link ChartOptions} bag. Defaults to
386
+ * Placidus houses in the tropical zodiac.
387
+ * @returns The same {@link Chart} shape returned by {@link Engine.chart}.
388
+ * @example
389
+ * ```ts
390
+ * const jd = julianDay(1990, 6, 10, 14, 30, 0);
391
+ * const chart = engine.chartAt(jd, 27.95, -82.46, "placidus");
392
+ * ```
393
+ * @see {@link Engine.chart} for the calendar-field entry point.
394
+ */
395
+ chartAt(jdUt, lat, lonEast, opts = "placidus") {
245
396
  const o = typeof opts === "string" ? { houseSystem: opts } : opts;
246
397
  const houseSystem = o.houseSystem ?? "placidus";
247
398
  const zodiac = o.zodiac ?? "tropical";
248
399
  const mode = parseZodiac(zodiac);
249
- const jdUt = julianDay(y, mo, d, h, mi, s);
250
400
  const calc = {
251
401
  zodiac,
252
402
  topocentric: o.topocentric,
@@ -15,9 +15,25 @@ export type Constraint = {
15
15
  degree: number;
16
16
  weight?: number;
17
17
  };
18
- /** Degrees by which a single constraint is unmet given the longitudes. */
18
+ /**
19
+ * Degrees by which a single {@link Constraint} is unmet for a set of body
20
+ * longitudes — `0` when satisfied. The pure building block of {@link formLoss}
21
+ * and {@link compileForm}.
22
+ *
23
+ * @param lons Body longitudes in degrees, keyed by body id.
24
+ * @param c The constraint to score.
25
+ * @returns The unmet amount in degrees (`0` = satisfied).
26
+ */
19
27
  export declare function constraintLoss(lons: Record<string, number>, c: Constraint): number;
20
- /** Total weighted constraint loss for a set of body longitudes. */
28
+ /**
29
+ * Total weighted loss for a set of body longitudes — the sum of each
30
+ * constraint's {@link constraintLoss} times its weight. The objective
31
+ * {@link compileForm} minimizes.
32
+ *
33
+ * @param lons Body longitudes in degrees, keyed by body id.
34
+ * @param constraints The constraints to score.
35
+ * @returns The total weighted loss in degrees (`0` = all satisfied).
36
+ */
21
37
  export declare function formLoss(lons: Record<string, number>, constraints: Constraint[]): number;
22
38
  export interface CompiledForm {
23
39
  longitudes: Record<string, number>;
@@ -34,5 +50,30 @@ export interface CompileOptions {
34
50
  /** A form is impossible when its worst constraint exceeds this (degrees). */
35
51
  impossibleDeg?: number;
36
52
  }
37
- /** Find body longitudes minimizing the weighted constraint loss. */
53
+ /**
54
+ * Synthesize a chart form from geometric constraints — the inverse of
55
+ * (time, place) → chart. Given weighted {@link Constraint}s (aspects between
56
+ * bodies, sign placements, exact degrees), find the body longitudes that best
57
+ * satisfy them via deterministic coordinate descent, and report how well they
58
+ * can be met. When even the best fit is poor, the form is flagged `impossible`
59
+ * — a valid, informative result.
60
+ *
61
+ * @param constraints The geometric constraints to satisfy; each may carry a
62
+ * `weight` (default `1`).
63
+ * @param opts `restarts` and `iters` tune the optimizer (more = slower, more
64
+ * thorough); `impossibleDeg` is the worst-constraint threshold in degrees
65
+ * above which the form is impossible. Defaults: `12`, `8`, `5`.
66
+ * @returns A {@link CompiledForm}: solved `longitudes`, total `residual`,
67
+ * `maxConstraintLoss`, the `impossible` flag, and each constraint annotated
68
+ * with its `loss`.
69
+ * @example
70
+ * ```ts
71
+ * const form = compileForm([
72
+ * { kind: "aspect", a: "sun", b: "moon", angle: 120 }, // trine
73
+ * { kind: "sign", body: "sun", sign: 0 }, // Aries
74
+ * ]);
75
+ * form.impossible; // false
76
+ * form.longitudes.sun; // a degree within Aries
77
+ * ```
78
+ */
38
79
  export declare function compileForm(constraints: Constraint[], opts?: CompileOptions): CompiledForm;
@@ -21,7 +21,15 @@ function signLoss(lon, sign) {
21
21
  return 0.0;
22
22
  return Math.min(d - 30.0, 360.0 - d);
23
23
  }
24
- /** Degrees by which a single constraint is unmet given the longitudes. */
24
+ /**
25
+ * Degrees by which a single {@link Constraint} is unmet for a set of body
26
+ * longitudes — `0` when satisfied. The pure building block of {@link formLoss}
27
+ * and {@link compileForm}.
28
+ *
29
+ * @param lons Body longitudes in degrees, keyed by body id.
30
+ * @param c The constraint to score.
31
+ * @returns The unmet amount in degrees (`0` = satisfied).
32
+ */
25
33
  export function constraintLoss(lons, c) {
26
34
  if (c.kind === "aspect")
27
35
  return Math.abs(angDist(lons[c.a], lons[c.b]) - c.angle);
@@ -29,7 +37,15 @@ export function constraintLoss(lons, c) {
29
37
  return signLoss(lons[c.body], c.sign);
30
38
  return angDist(lons[c.body], c.degree);
31
39
  }
32
- /** Total weighted constraint loss for a set of body longitudes. */
40
+ /**
41
+ * Total weighted loss for a set of body longitudes — the sum of each
42
+ * constraint's {@link constraintLoss} times its weight. The objective
43
+ * {@link compileForm} minimizes.
44
+ *
45
+ * @param lons Body longitudes in degrees, keyed by body id.
46
+ * @param constraints The constraints to score.
47
+ * @returns The total weighted loss in degrees (`0` = all satisfied).
48
+ */
33
49
  export function formLoss(lons, constraints) {
34
50
  let total = 0;
35
51
  for (const c of constraints)
@@ -58,7 +74,32 @@ function bodyLoss(lons, body, constraints) {
58
74
  total += (c.weight ?? 1.0) * constraintLoss(lons, c);
59
75
  return total;
60
76
  }
61
- /** Find body longitudes minimizing the weighted constraint loss. */
77
+ /**
78
+ * Synthesize a chart form from geometric constraints — the inverse of
79
+ * (time, place) → chart. Given weighted {@link Constraint}s (aspects between
80
+ * bodies, sign placements, exact degrees), find the body longitudes that best
81
+ * satisfy them via deterministic coordinate descent, and report how well they
82
+ * can be met. When even the best fit is poor, the form is flagged `impossible`
83
+ * — a valid, informative result.
84
+ *
85
+ * @param constraints The geometric constraints to satisfy; each may carry a
86
+ * `weight` (default `1`).
87
+ * @param opts `restarts` and `iters` tune the optimizer (more = slower, more
88
+ * thorough); `impossibleDeg` is the worst-constraint threshold in degrees
89
+ * above which the form is impossible. Defaults: `12`, `8`, `5`.
90
+ * @returns A {@link CompiledForm}: solved `longitudes`, total `residual`,
91
+ * `maxConstraintLoss`, the `impossible` flag, and each constraint annotated
92
+ * with its `loss`.
93
+ * @example
94
+ * ```ts
95
+ * const form = compileForm([
96
+ * { kind: "aspect", a: "sun", b: "moon", angle: 120 }, // trine
97
+ * { kind: "sign", body: "sun", sign: 0 }, // Aries
98
+ * ]);
99
+ * form.impossible; // false
100
+ * form.longitudes.sun; // a degree within Aries
101
+ * ```
102
+ */
62
103
  export function compileForm(constraints, opts = {}) {
63
104
  const restarts = opts.restarts ?? 12;
64
105
  const iters = opts.iters ?? 8;