Ephemeris8 MIN READ15 / 119

Astronomy Engine adapter

This optional JavaScript provider implements every operation and vocabulary in the project’s EphemerisAdapter contract. Astronomy Engine supplies physical ephemerides, observer parallax, clocks and coordinate transformations; local mathematics supplies the astrology conventions. Swiss remains the default provider until the host explicitly selects Astronomy Engine.

Supported behavior

Area Support
Bodies Sun, Moon, Mercury, Venus, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto, Rahu and Ketu
Lunar nodes Explicit mean or true/osculating nodes; Ketu is opposite Rahu
Zodiac Tropical and sidereal
Ayanamsas Lahiri, Raman, Krishnamurti and Fagan–Bradley
Observer Geocentric or topocentric planetary positions, including elevation
Speeds Optional longitude, latitude and distance derivatives
Houses Placidus, Whole Sign, Equal, Sripati, Koch and Campanus; twelve cusps and eight named angles
Solar events Hindu sunrise, next sunrise and optional sunset in the requested window

The integration interval is Julian day TT 2415019.5–2488069.5 (1899-12-31 through 2100-01-01 TT). The leading day admits 1900 UTC despite negative Delta-T. This is narrower than Astronomy Engine’s theoretical range. Invalid requests, out-of-range instants and mathematically undefined geometry still return canonical errors. Placidus and Koch are undefined inside the polar circles; exact geographic poles and horizon tangencies have undefined house angles. The adapter never silently substitutes another house system.

Positions, zodiac and nodes

Planetary vectors include the engine’s aberration/light-time treatment and are transformed to true ecliptic coordinates of date. Observer requests use its topocentric equatorial calculation before that transformation. The engine special-cases its approximate lunar model; results are not numerically identical to Swiss apparent places.

Mean Rahu uses the secular lunar-node polynomial. True Rahu comes from the angular momentum of the instantaneous lunar position/velocity, with distance evaluated on the osculating ellipse at the node. Nodes remain geocentric orbital constructs in observer requests, matching the Swiss convention; they do not receive lunar-body parallax. Node latitude is zero. Ketu is exactly opposite Rahu with the same distance and longitude speed.

The four ayanamsas use mean-equinox J2000 reference offsets, IAU 2006 general precession and the engine’s longitude nutation. They are local approximations to the named conventions. Sidereal longitudes subtract the offset once. Speeds differentiate the complete requested geometry, including sidereal drift and observer rotation, using an 86.4-second half-step in TT. Longitude differences unwrap across 0°/360°; omitted speeds are null. Neighbouring derivative samples can lie just outside the advertised request interval, within the underlying engine’s range.

UTC approximates UT1, with Astronomy Engine’s default Espenak–Meeus Delta-T supplying TT. Do not change its process-global SetDeltaTFunction when reproducible default results are required. Source declarations identify engine 2.1.19 and local astrology algorithms v1, actual clocks, observer origin and the applied offset. There are no native flags or data files. precisionArcsec remains null: feature support and sampled agreement are not accuracy guarantees.

Houses and solar events

House calculations use the engine’s apparent sidereal time and true obliquity. Placidus solves semi-diurnal/nocturnal fractions with a bracketed root. Koch divides the MC degree’s rise-to-meridian time. Campanus uses intersections with prime-vertical division planes; its meridian half is chosen toward the zenith even inside polar circles. Sripati boundaries lie halfway between Porphyry quadrant-trisection centres. Whole Sign begins at the sign containing the Ascendant in the requested zodiac, including sidereal requests. Longitude-like angles shift with ayanamsa; ARMC remains equatorial. Elevation does not affect houses.

Hindu events use the geocentric Sun’s disc centre with zero ecliptic latitude and no refraction. Elevation and zodiac do not change this physical definition. The solver locates altitude extrema, then brackets horizon crossings between them, including short near-polar days. A window is at most 72 hours; both endpoints must lie within coverage. Missing events are null, sunset is omitted unless requested, and next sunrise is searched independently rather than manufactured as sunrise + 24 h. Timestamps are rounded to integer UTC milliseconds and clipped to the requested [start, end) window.

Use directly

import { AstronomyEngineAdapter } from '@astrology/ephemeris/astronomy-engine';

const adapter = new AstronomyEngineAdapter();
const result = await adapter.positions({
  instant: '2000-01-01T12:00:00Z',
  zodiac: 'sidereal',
  ayanamsa: 'lahiri',
  bodies: ['sun', 'moon', 'rahu', 'ketu'],
  nodeType: 'true',
  observer: { latitude: 19.076, longitude: 72.8777, elevationMeters: 14 },
  includeSpeed: true,
});
const houses = await adapter.houses({
  instant: '2000-01-01T12:00:00Z',
  zodiac: 'sidereal', ayanamsa: 'lahiri',
  observer: { latitude: 19.076, longitude: 72.8777 }, system: 'placidus',
});

This entry does not import Swiss, start workers or make network calls. Its async methods perform synchronous calculation and block the calling event loop. Astronomy Engine is MIT licensed; retain its notice. The encompassing ephemeris package still depends on Swiss: this import does not relicense Swiss code/data or remove that package dependency. No Swiss implementation was copied into the local astrology helpers; Swiss is used only as a comparison provider in tests/benchmarks.

Select through the Binder

import { createBinder } from '@astrology/ephemeris/node';

const binder = await createBinder({
  nasa: { userAgent: 'my-chart-app/1.0 (ops@example.com)' },
  astronomyEngine: true,
  resolveJulianDayTt: epochMilliseconds => timeService.julianDayTt(epochMilliseconds),
  routing: { primaryProvider: 'astronomy-engine' },
});

timeService is the host’s tested TT resolver. This factory still initializes Swiss and NASA, so its pinned Swiss files remain necessary. For injected adapters, pass AstroBinder.create({ swiss, nasa, astronomyEngine: adapter }, options). Registration alone preserves Swiss as primary. Once selected, Astronomy Engine handles positions, houses and solar events itself; it does not fall back to Swiss for missing functionality. Normalization retains provider identity and the Binder adds existing astrology facts.

Benchmark and verification

From the repository root:

pnpm build:ephemeris
pnpm setup:ephemeris # only if pinned Swiss files are missing
pnpm --filter @astrology/ephemeris benchmark:astronomy-engine \
  --samples 100 --warmup 5 --output /tmp/astronomy-engine-benchmark.json

The recorded JSON retains environment, provider versions, Swiss-file hashes, individual latency samples and drift observations. The runner compares the public adapters and a reused internal Swiss engine as a diagnostic control. That control bypasses per-call worker isolation and file verification; it is not a supported production adapter.

Each workload uses 100 serial requests after five warmups per mode, rotating execution order and varying the same instants in six-hour increments from 2024-01-01. Workloads cover two/ten/twelve bodies, speeds, sidereal and topocentric requests, each of the six houses, and 48-hour Hindu event windows. Calls include validation, with no Binder, normalization or application result cache. Internal ephemeris caches warm up. Throughput is serial completed calls per measured second, not server capacity under concurrent load. First-call timings exclude module imports and do not guarantee cold OS file caches. Run without simultaneous builds/tests for useful timings.

The planetary drift sample retains 440 body comparisons at 44 dates from 1900 through late 2099. It includes same-UTC adapter differences plus raw Astronomy Engine geometry at Swiss’s declared TT, separating time-model offsets from position-model differences. Feature comparisons additionally cover all four ayanamsas, both node types, topocentric/sidereal positions, all six house systems across equatorial, northern, southern and polar locations, and solar events. Matched polar house errors are recorded explicitly. These samples establish agreement only at the tested cases.

Recorded results

Measured 2026-10-27, Apple M4, Darwin 25.5.0 arm64, Node 26.7.0. The table shows p50 / p95 milliseconds per request, with 100 measured calls per workload and mode.

Workload Astronomy Engine adapter Existing Swiss adapter Reused Swiss engine control
Ten tropical bodies with speeds 0.250 / 0.335 33.520 / 35.436 0.052 / 0.062
Twelve sidereal bodies with speeds 0.309 / 0.373 33.544 / 35.388 0.071 / 0.077
Twelve sidereal/topocentric bodies with speeds 0.315 / 0.410 33.738 / 36.302 0.165 / 0.183
Sidereal Placidus houses 0.035 / 0.050 33.449 / 35.689 0.047 / 0.051
Hindu solar events, 48 hours 1.122 / 1.237 33.715 / 35.804 0.105 / 0.126

The remaining five house-system workloads and all individual samples are in the JSON. Across all twelve workloads and three modes it contains 3,600 latency measurements. The reused Swiss control is faster for planetary positions and solar events. Much of the difference between public adapters is Swiss’s per-call process startup and file verification, not faster ephemeris mathematics.

The feature matrix uses four dates (1900, 2000, 2024 and 2099), all named ayanamsas, both node types and seven locations. It records 16 ayanamsa, 16 node-body, 168 house and 96 topocentric body comparisons, plus three solar windows. Sixteen house requests have matching polar-undefined errors; the other 152 have matching supported geometry.

Measured feature Maximum absolute difference versus Swiss in this sample
Ayanamsa offset 0.108 arcseconds
Mean node longitude 0.133 arcseconds
True node longitude 11.806 arcseconds
House cusp longitude 32.893 arcseconds
Named house angles 11.775 arcseconds
Topocentric/sidereal body longitude 55.340 arcseconds

The separate 44-date geocentric sample retains the largest Moon longitude difference of 64.771 arcseconds at the end of 2099 with same-UTC adapter inputs; matching the reference TT reduces the sampled Moon maximum to 2.916 arcseconds. Provider TT differs by up to 109.469 seconds. Validate boundary-sensitive astrology against its own acceptable tolerances.

The London reference has a documented event-search discrepancy: Swiss’s repeated next-sunrise search reports 2000-01-03 08:12:05.994Z, whereas a fresh Swiss search for that day reports 08:12:16.007Z. Astronomy Engine’s independent horizon root is 08:12:16.330Z. Tests check fresh-search agreement and consistency across clipped windows rather than copying that discrepancy.

Adapter tests use independent C swetest fixtures for positions, speeds, named sidereal offsets, house cusps/angles and solar events, plus real Swiss comparisons across locations. They exercise node antipodes, observer isolation, longitude wrap, retrograde motion, polar failures/no-event windows, public import independence and Binder/factory routing for every operation.

The workspace build, package typechecks and documentation links/render parsing passed. Relevant suites passed 125 ephemeris, 20 runtime and 10 drift-validator tests, including 33 focused Astronomy Engine tests. The aggregate check:packages command still stops at an existing Vedic engine-to-calculator boundary violation in packages/vedic/src/application/engine/engine.ts. Aggregate test:packages stops at an existing logger test expecting @astrology/api.requests while its input uses @astrology/astrology-api.requests. Neither unrelated file was changed.