caelus 0.18.0 → 0.19.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.
@@ -242,6 +242,44 @@ export declare class Engine {
242
242
  * @returns Sorted catalog star names.
243
243
  */
244
244
  starNames(): string[];
245
+ /**
246
+ * Fixed-star conjunctions in a chart: each body within `orb` of a catalog
247
+ * star, in the chart's own zodiac. Feed the result to
248
+ * {@link interpretationContext} as `stars` to project `star` fact atoms (the
249
+ * Chart itself carries no star catalog).
250
+ *
251
+ * @param chart A chart from {@link Engine.chart} / {@link Engine.chartAt}.
252
+ * @param opts `orb` (default 1°); `stars` to restrict to named stars (then no
253
+ * magnitude filter); else `maxMag` keeps only stars brighter than it
254
+ * (default 2.5) so obscure catalog entries do not flood the result.
255
+ * @returns Conjunctions sorted by increasing orb.
256
+ */
257
+ starConjunctions(chart: Chart, opts?: {
258
+ orb?: number;
259
+ maxMag?: number;
260
+ stars?: string[];
261
+ }): {
262
+ body: string;
263
+ star: string;
264
+ orb: number;
265
+ }[];
266
+ /**
267
+ * The seven Hermetic lots of a chart, each placed by sign and house. Sect is
268
+ * read from the Sun (above the horizon -> a day chart). Feed the result to
269
+ * {@link interpretationContext} as `lots` to project `lot` fact atoms.
270
+ *
271
+ * @param chart A chart from {@link Engine.chart} / {@link Engine.chartAt}; it
272
+ * must carry the seven classical planets.
273
+ * @returns One entry per lot with its longitude, sign, `signDeg`, and house,
274
+ * or an empty array if a required planet is absent.
275
+ */
276
+ lots(chart: Chart): {
277
+ lot: string;
278
+ lon: number;
279
+ sign: string;
280
+ signDeg: number;
281
+ house: number;
282
+ }[];
245
283
  private lonOnly;
246
284
  /**
247
285
  * Apparent geocentric ecliptic longitude of a body, in degrees `[0, 360)`,
package/dist/src/chart.js CHANGED
@@ -1,6 +1,7 @@
1
1
  /** astroengine chart -- public API: natal charts, aspects, retrogrades. */
2
2
  import { DEG, mod, jdTT, julianDay, ChebSeries, planetApparent, sunApparent, moonApparentSeries, moonApparentPrecise, plutoApparent, chironApparent, meanNode, trueNodeSeries, trueNodePrecise, equatorial, ayanamsa, AYANAMSA_J2000, meanLilith, topocentricEcl, oscApogeePrecise, oscApogeeSeries, KeplerOrbit, trueObliquity, nutation, plutoHeliocentric, vsopHeliocentric, precessEcliptic, J2000, } from "./core.js";
3
3
  import { starApparent } from "./stars.js";
4
+ import { hermeticLots, HERMETIC_LOTS } from "./lots.js";
4
5
  import * as H from "./houses.js";
5
6
  const TWO_PI = 2 * Math.PI;
6
7
  export const BODIES = [
@@ -295,6 +296,68 @@ export class Engine {
295
296
  starNames() {
296
297
  return Object.keys(this.data.fixedStars?.stars ?? {}).sort();
297
298
  }
299
+ /**
300
+ * Fixed-star conjunctions in a chart: each body within `orb` of a catalog
301
+ * star, in the chart's own zodiac. Feed the result to
302
+ * {@link interpretationContext} as `stars` to project `star` fact atoms (the
303
+ * Chart itself carries no star catalog).
304
+ *
305
+ * @param chart A chart from {@link Engine.chart} / {@link Engine.chartAt}.
306
+ * @param opts `orb` (default 1°); `stars` to restrict to named stars (then no
307
+ * magnitude filter); else `maxMag` keeps only stars brighter than it
308
+ * (default 2.5) so obscure catalog entries do not flood the result.
309
+ * @returns Conjunctions sorted by increasing orb.
310
+ */
311
+ starConjunctions(chart, opts = {}) {
312
+ const catalog = this.data.fixedStars?.stars;
313
+ if (!catalog)
314
+ return [];
315
+ const orbLimit = opts.orb ?? 1.0;
316
+ const names = opts.stars ?? Object.keys(catalog);
317
+ const useMag = opts.stars === undefined;
318
+ const maxMag = opts.maxMag ?? 2.5;
319
+ const out = [];
320
+ for (const name of names) {
321
+ const s = catalog[name];
322
+ if (!s || (useMag && s.mag > maxMag))
323
+ continue;
324
+ const starLon = this.fixedStar(name, chart.jdUt, { zodiac: chart.zodiac }).lon;
325
+ for (const [body, p] of Object.entries(chart.bodies)) {
326
+ if (!p)
327
+ continue;
328
+ const sep = Math.abs(mod(p.lon - starLon + 180, 360) - 180);
329
+ if (sep <= orbLimit)
330
+ out.push({ body, star: name, orb: sep });
331
+ }
332
+ }
333
+ out.sort((a, b) => a.orb - b.orb);
334
+ return out;
335
+ }
336
+ /**
337
+ * The seven Hermetic lots of a chart, each placed by sign and house. Sect is
338
+ * read from the Sun (above the horizon -> a day chart). Feed the result to
339
+ * {@link interpretationContext} as `lots` to project `lot` fact atoms.
340
+ *
341
+ * @param chart A chart from {@link Engine.chart} / {@link Engine.chartAt}; it
342
+ * must carry the seven classical planets.
343
+ * @returns One entry per lot with its longitude, sign, `signDeg`, and house,
344
+ * or an empty array if a required planet is absent.
345
+ */
346
+ lots(chart) {
347
+ const b = chart.bodies;
348
+ const need = ["sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn"];
349
+ if (need.some((k) => !b[k]))
350
+ return [];
351
+ const day = (b.sun.house >= 7); // Sun above the horizon (houses 7-12)
352
+ const h = hermeticLots(chart.angles.asc, day, b.sun.lon, b.moon.lon, b.mercury.lon, b.venus.lon, b.mars.lon, b.jupiter.lon, b.saturn.lon);
353
+ return HERMETIC_LOTS.map((lot) => {
354
+ const lon = mod(h[lot], 360);
355
+ return {
356
+ lot, lon, sign: SIGNS[Math.floor(lon / 30)], signDeg: mod(lon, 30),
357
+ house: houseIndex(lon, chart.cusps),
358
+ };
359
+ });
360
+ }
298
361
  lonOnly(body, jdUt, mode, topo) {
299
362
  const jde = jdTT(jdUt);
300
363
  let [lon, lat, dist] = this.ecliptic(body, jde);
@@ -63,6 +63,17 @@ export declare function hasDispositor(filter?: {
63
63
  export declare function hasReception(filter?: {
64
64
  body?: string;
65
65
  }): Selector;
66
+ /** Matches a fixed-star conjunction by the catalog star and/or the body on it. */
67
+ export declare function hasStar(filter?: {
68
+ body?: string;
69
+ star?: string;
70
+ }): Selector;
71
+ /** Matches a Hermetic lot by name, and/or its sign or house. */
72
+ export declare function hasLot(filter?: {
73
+ lot?: string;
74
+ sign?: string;
75
+ house?: number;
76
+ }): Selector;
66
77
  /** Matches only when every selector matches; returns the union of their atoms. */
67
78
  export declare function matchAll(...sels: Selector[]): Selector;
68
79
  /** Matches when any selector matches; returns the atoms from those that did. */
@@ -61,6 +61,19 @@ export function hasReception(filter = {}) {
61
61
  return (ctx) => hit(ctx.atoms.filter((a) => a.kind === "reception"
62
62
  && (filter.body === undefined || a.bodies.includes(filter.body))));
63
63
  }
64
+ /** Matches a fixed-star conjunction by the catalog star and/or the body on it. */
65
+ export function hasStar(filter = {}) {
66
+ return (ctx) => hit(ctx.atoms.filter((a) => a.kind === "star"
67
+ && (filter.body === undefined || a.body === filter.body)
68
+ && (filter.star === undefined || a.star === filter.star)));
69
+ }
70
+ /** Matches a Hermetic lot by name, and/or its sign or house. */
71
+ export function hasLot(filter = {}) {
72
+ return (ctx) => hit(ctx.atoms.filter((a) => a.kind === "lot"
73
+ && (filter.lot === undefined || a.lot === filter.lot)
74
+ && (filter.sign === undefined || a.sign === filter.sign)
75
+ && (filter.house === undefined || a.house === filter.house)));
76
+ }
64
77
  // ----------------------------------------------------------------- combinators
65
78
  /** Matches only when every selector matches; returns the union of their atoms. */
66
79
  export function matchAll(...sels) {
@@ -4,7 +4,7 @@ import { ChartPattern } from "./patterns.js";
4
4
  import { ChartSignature } from "./signature.js";
5
5
  import type { Realm, Certainty } from "./provenance.js";
6
6
  /** Atom kinds in an {@link InterpretationContext}. */
7
- export type FactKind = "placement" | "aspect" | "pattern" | "signature" | "angle" | "dispositor" | "reception";
7
+ export type FactKind = "placement" | "aspect" | "pattern" | "signature" | "angle" | "dispositor" | "reception" | "star" | "lot";
8
8
  interface FactAtomBase {
9
9
  /** Stable, content-addressable id, e.g. `"placement:mars"` or
10
10
  * `"aspect:mars~saturn:square"`. Interpretations cite this. */
@@ -72,7 +72,24 @@ export interface ReceptionAtom extends FactAtomBase {
72
72
  * else a sorted pair for a mixed reception (e.g. `"domicile-exaltation"`). */
73
73
  by: string;
74
74
  }
75
- export type FactAtom = PlacementAtom | AspectAtom | PatternAtom | SignatureAtom | AngleAtom | DispositorAtom | ReceptionAtom;
75
+ export interface StarAtom extends FactAtomBase {
76
+ kind: "star";
77
+ /** The body conjunct the fixed star. */
78
+ body: string;
79
+ /** Catalog star name (see {@link Engine.starNames}). */
80
+ star: string;
81
+ /** Orb from exact conjunction, degrees. */
82
+ orb: number;
83
+ }
84
+ export interface LotAtom extends FactAtomBase {
85
+ kind: "lot";
86
+ /** Hermetic lot name, e.g. `"fortune"` (see {@link HERMETIC_LOTS}). */
87
+ lot: string;
88
+ sign: string;
89
+ signDeg: number;
90
+ house: number;
91
+ }
92
+ export type FactAtom = PlacementAtom | AspectAtom | PatternAtom | SignatureAtom | AngleAtom | DispositorAtom | ReceptionAtom | StarAtom | LotAtom;
76
93
  /** A chart as a flat, ranked list of {@link FactAtom}s. */
77
94
  export interface InterpretationContext {
78
95
  jdUt: number;
@@ -108,6 +125,10 @@ export interface SalienceWeights {
108
125
  dispositor: number;
109
126
  /** Added to a mutual reception. */
110
127
  reception: number;
128
+ /** Added to a body's conjunction with a fixed star. */
129
+ star: number;
130
+ /** Added to a Hermetic lot (the Part of Fortune and its companions). */
131
+ lot: number;
111
132
  }
112
133
  export declare const DEFAULT_SALIENCE: SalienceWeights;
113
134
  export interface ContextOptions {
@@ -122,6 +143,22 @@ export interface ContextOptions {
122
143
  realm?: Realm;
123
144
  certainty?: Certainty;
124
145
  };
146
+ /** Fixed-star conjunctions to project as `star` atoms. The engine does not
147
+ * compute these from a bare {@link Chart} (the star catalog lives in the
148
+ * data pack), so a caller supplies them, e.g. from
149
+ * {@link Engine.starConjunctions}. */
150
+ stars?: {
151
+ body: string;
152
+ star: string;
153
+ orb: number;
154
+ }[];
155
+ /** Hermetic lots to project as `lot` atoms, e.g. from {@link Engine.lots}. */
156
+ lots?: {
157
+ lot: string;
158
+ sign: string;
159
+ signDeg: number;
160
+ house: number;
161
+ }[];
125
162
  }
126
163
  /**
127
164
  * Project a {@link Chart} into a ranked list of {@link FactAtom}s -- the
@@ -53,6 +53,7 @@ const DIGNITY_RANK = { domicile: 3, exaltation: 2, triplicity: 1 };
53
53
  export const DEFAULT_SALIENCE = {
54
54
  base: 1, luminary: 1.5, angular: 1, chartRuler: 1,
55
55
  dignity: 0.5, hardAspect: 1, pattern: 4, dispositor: 0.5, reception: 2,
56
+ star: 2, lot: 2,
56
57
  };
57
58
  /** How much to keep of a time-sensitive atom's salience at each certainty -- the
58
59
  * Moon and the angles move fastest, so an uncertain instant trusts them least. */
@@ -62,7 +63,7 @@ const TIME_SENSITIVE_KEEP = {
62
63
  /** Time-sensitive atoms: the angles (rotate ~15°/h) and anything about the Moon
63
64
  * (~13°/day), the fastest-shifting facts under a time error. */
64
65
  function timeSensitive(atom) {
65
- return atom.kind === "angle" || atom.bodies.includes("moon");
66
+ return atom.kind === "angle" || atom.kind === "lot" || atom.bodies.includes("moon");
66
67
  }
67
68
  function title(body) {
68
69
  return body.split("_").map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
@@ -234,6 +235,25 @@ export function interpretationContext(chart, opts = {}) {
234
235
  angleAtom("mc", chart.angles.mc);
235
236
  angleAtom("vertex", chart.angles.vertex);
236
237
  angleAtom("eastPoint", chart.angles.eastPoint);
238
+ // Fixed-star conjunctions (caller-supplied; the catalog is not on the Chart).
239
+ for (const sc of opts.stars ?? []) {
240
+ let salience = w.base + w.star;
241
+ if (LUMINARIES.has(sc.body))
242
+ salience += w.luminary;
243
+ atoms.push({
244
+ id: `star:${sc.body}:${sc.star}`, kind: "star", bodies: [sc.body], salience,
245
+ body: sc.body, star: sc.star, orb: sc.orb,
246
+ text: `${title(sc.body)} conjunct ${sc.star} (orb ${sc.orb.toFixed(1)}°)`,
247
+ });
248
+ }
249
+ // Hermetic lots (caller-supplied; computed from the chart's points + sect).
250
+ for (const l of opts.lots ?? []) {
251
+ atoms.push({
252
+ id: `lot:${l.lot}`, kind: "lot", bodies: [], salience: w.base + w.lot,
253
+ lot: l.lot, sign: l.sign, signDeg: l.signDeg, house: l.house,
254
+ text: `Lot of ${title(l.lot)} in ${l.sign}, house ${l.house}`,
255
+ });
256
+ }
237
257
  // An inexact instant trusts the fast-moving facts least.
238
258
  const prov = opts.provenance;
239
259
  if (prov?.certainty && prov.certainty !== "exact") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "caelus",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "Astrological ephemeris engine. MIT, no AGPL, no ephemeris files. Checked against Swiss Ephemeris.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",