Zodiacs Engine

Calculate charts in your own process.

@zodiacs/engine is the chart engine this site runs. It takes an instant and a pair of coordinates and returns bodies, angles, house cusps and aspects. The calculation is synchronous and makes no network request: nothing about the birth details it is given leaves the process it runs in.

MIT, ESM, TypeScript declarations included, Node.js 18 and newer or any browser with a bundler. One runtime dependency — Astronomy Engine by Don Cross, also MIT, which supplies the planetary and lunar series. Zodiacs did not write those models; what is ours is the chart on top of them — houses, angles, aspects, the record format and the limits.

A worked example

This is the whole of it. The output below is what the program prints — a test in this repository runs this exact source against the archive below and fails if the two stop matching.

import { natalChart } from '@zodiacs/engine';

// 15 June 1990, 13:30 UTC, London. No API key and no network call.
const chart = natalChart({
  utc: '1990-06-15T13:30:00Z',
  latitude: 51.5074,
  longitude: -0.1278,
  houseSystem: 'placidus',
});

for (const p of chart.bodies) {
  const mark = p.retrograde ? ' retrograde' : '';
  console.log(`${p.body.padEnd(10)} ${p.degree.toFixed(2).padStart(6)}° ${p.sign}${mark}`);
}
console.log(`ascendant  ${chart.angles.asc.toFixed(2)}°   engine ${chart.engineVersion}`);
Sun         24.19° gemini
Moon        16.19° pisces
Mercury      5.80° gemini
Venus       18.85° taurus
Mars        11.09° aries
Jupiter     15.90° cancer
Saturn      24.03° capricorn retrograde
Uranus       8.16° capricorn retrograde
Neptune     13.72° capricorn retrograde
Pluto       15.40° scorpio retrograde
North Node   8.12° aquarius
South Node   8.12° leo
ascendant  191.24°   engine 0.1.1-rc.6

Install it

@zodiacs/engine is not on npm yet — npm view @zodiacs/engine returns 404, and the support page says why. The published archive is the evaluation route, and it is the bytes a registry release will carry if it is published from this archive rather than repacked from a directory. Paste this into a POSIX shell; it refuses to install anything whose digest does not match.

( set -eu
# POSIX shell — macOS, Linux or WSL. Not PowerShell. It runs in a subshell, so
# a failure stops the install without closing your terminal.
BASE=$(pwd)
FILE='zodiacs-engine-0.1.1-rc.6.tgz'
if test -e "$FILE" || test -L "$FILE"; then
  echo "Stop: $FILE already exists here. Move or delete it, then run this again." >&2
  exit 1
fi
# A download that fails verification is removed, so the guard above does not
# then block the retry. Once npm takes over the archive stays: npm reports its
# own failures, and the file is what you would retry with.
trap 'status=$?; if test "$status" -ne 0; then rm -f "$BASE/$FILE"; fi; exit $status' EXIT

curl --disable --fail --silent --show-error --location --proto '=https' --max-time 120 \
  'https://raw.githubusercontent.com/ZodiacsOfficial/sdk/51129a197cd3f2a2a8c966fb797ea4da1e147b3d/artifacts/zodiacs-engine-0.1.1-rc.6.tgz' -o "$FILE"

node --input-type=module <<'JS'
import { readFileSync } from 'node:fs';
import { createHash } from 'node:crypto';
const file = 'zodiacs-engine-0.1.1-rc.6.tgz';
const expected = '09c3e63432f8ba2e9df05af137c42f65ab039740a207a89418d9e6470ea3db3e';
const bytes = readFileSync(file);
const actual = createHash('sha256').update(bytes).digest('hex');
if (actual !== expected) {
  console.error(`Stop: this is not the published archive.\n  expected ${expected}\n  got      ${actual}`);
  console.error('Nothing was installed.');
  process.exit(1);
}
console.log(`Archive verified: ${actual}`);
JS

trap - EXIT
npm install --ignore-scripts "./$FILE"
echo "Installed @zodiacs/engine@0.1.1-rc.6 from the verified archive."
)

SHA-256: 09c3e63432f8ba2e9df05af137c42f65ab039740a207a89418d9e6470ea3db3e

Prefer a project that already runs? The starter has natal, transit and widget examples wired up, and the MCP adapter puts the same engine behind an assistant over stdio.

What it computes

positions(date)
Apparent geocentric tropical longitudes for an instant: Sun through Pluto plus the true nodes, with ecliptic latitude, daily speed, retrograde state and sign.
natalChart(birth)
A full chart from a resolved UTC instant and coordinates: bodies, angles, house cusps and major aspects, plus the flags that say what was assumed. Without a known time it returns positions and leaves angles and houses null.
transits(natal, date)
Current positions and their major aspects to a natal chart.
synastry(first, second)
Inter-chart aspects between two charts with element and modality balances.
moonPhase(date)
Phase angle, illumination and name, from the same positions a chart uses.
saturnReturn(birth)
Natal Saturn and every return season to roughly age 92, each with its exact crossings.

Whole-sign and Placidus houses; the five major aspects with separate luminary orbs. Two optional entry points ship beside the core: @zodiacs/engine/geo resolves a local wall time and IANA zone to an instant, and @zodiacs/engine/receipt validates a portable chart record. Both are separate imports, so neither costs anything if you do not use them.

One thing to know about @zodiacs/engine/geo before you import it: the time resolver itself is offline, but the same entry point also exports a GeoNames place-lookup client, and that one makes HTTP requests to wherever you point it. Import resolveBirth by name and a bundler drops the client; import the namespace and you ship it. The core entry point contains no fetch at all.

What it costs

Bundled for a browser with esbuild 0.28.1, minified, entry import { natalChart } from '@zodiacs/engine' — its ephemeris included, because that is what actually ships:

EntryMinifiedgzipbrotli
Natal charts57.7 KB24.7 KB20.7 KB
+ resolveBirth, local-time resolution alone61.1 KB25.9 KB21.8 KB
+ the whole /geo entry point, GeoNames client included63.9 KB27.1 KB22.8 KB

Most of that is the ephemeris series, and it is the price of not calling a server. There are no data files to host or download at runtime, no key, and no account. This site keeps the engine out of its own homepage bundle and loads it only on pages that cast a chart; a bundler will do the same for you if you import it behind a dynamic import.

How close it is

Every change has to pass a comparison against NASA JPL Horizons reference data before it ships. Separately, on 20 September 2026 the engine was measured against Swiss Ephemeris 2.10.03 over a stratified set of dates written down beforehand: across the 160 measurements from 1801 to 2026 the median disagreement in ecliptic longitude is 1.6 arcseconds and the largest is 18.6 arcseconds.

Three caveats that matter more than the number. Both programs descend from JPL development ephemerides, so that is two implementations agreeing rather than a check against observation.

Further out the disagreement grows, and past the observed record it is mostly the clock: the Moon is 64.8 arcseconds at 2100 and 159.4 arcseconds at 2190, almost all of which is the two programs extrapolating the Earth's slowing rotation differently rather than either ephemeris being wrong.

And the engine does not bound its input date at all. It will compute year 900 or year 3500 and return a chart, with no error and no flag, and nothing above says anything about either. zodiacs.org clamps its own forms to 1800–2199; if you are taking dates from users, pick a bound and enforce it yourself.

One report covers all of it — positions, angles and houses, local time, event search and runtime support — with the measured residual for each, the pinned oracle it came from, and what it does not establish. It also carries the event-search contract that is still failing, which is the weakest thing anyone evaluating this should know.

What it does not do

  • Tropical only. No sidereal mode, no ayanamsha, no topocentric parallax.
  • Sun through Pluto and the true nodes. No Chiron, no asteroids.
  • Placidus is undefined near the poles; above 66° absolute latitude it falls back to whole sign and flags it.
  • Event searches cap ephemeris evaluations, so they can miss an event between samples. That is a sampling bound, not a guarantee of complete discovery.
  • No input-date bound, and no warning outside the range anyone has checked. See the caveat under accuracy.
  • The manifest declares Node 18 and newer. What has actually been executed is Node 22.23.2, Node 24.19.0 and Chrome 152 — narrower than the declaration.
  • No interpretation. The engine returns geometry; what it means is your problem, and not an astronomical claim.

The support matrix states each of these precisely, along with the runtimes actually tested, the input rules, and the licensing obligations that come with the optional GeoNames-derived place index.

Source and licence

MIT, in packages/engine of the Zodiacs SDK repository at commit fb57af7a2cd7. Keep the packaged LICENSE and NOTICE when you redistribute: the NOTICE carries Astronomy Engine's MIT notice and the CC BY 4.0 attribution that GeoNames-derived place data requires. Report a problem.