Skip to content

calculate_planets: ~18″ p95 drift vs VedicRishi where another Swiss Ephemeris binding agrees to ~1″ — need help understanding the difference #8

Description

@Hari-Bonda

Hi, thanks for shipping this library — the Wasm-first packaging fits Cloudflare Workers perfectly so we adopted it across our sidereal-astrology site. We're now in a parity audit against VedicRishi (json.astrologyapi.com) and a second open-source Node.js Swiss-Ephemeris binding, and we'd love help interpreting the results.

What we ran

  • @fusionstrings/panchangam v0.2.1 (same numbers on v0.2.0)
  • 200 random cases, dates 1925–2025, 30 cities (lat −40° to +60°, lon −150° to +150°), 5 ayanamsa modes
  • Same case set sent to:
    1. @fusionstrings/panchangam via p_julday(year, month, day, utc_hour, 1)calculate_planets(jd, mode)
    2. VedicRishi /v1/planets/ — industry baseline used by most KP/Vedic apps
    3. A second Node.js Swiss-Ephemeris stack — mivion/swisseph (canonical Node binding to the upstream Swiss Ephemeris C library, v0.5.17) wrapped via rish-0-0/astroreha v1.1.5. Different binding to the same Swiss Ephemeris algorithm.

Results — Lahiri ayanamsa (mode 1), 200 cases, p95 |Δ| in arcsec

Planet panchangam vs VR mivion/swisseph vs VR panchangam vs mivion/swisseph
Sun 18.5″ 1.9″ 19.0″
Moon 41.5″ 28.9″ 33.1″
Mars 18.1″ 3.2″ 19.8″
Mercury 18.8″ 6.4″ 22.9″
Jupiter 17.8″ 0.9″ 18.0″
Venus 18.5″ 4.2″ 19.7″
Saturn 18.1″ 0.5″ 17.9″
Rahu 17.9″ 0.1″ 17.8″
Ketu 17.9″ 0.1″ 17.8″

Means hover near zero for both implementations — so this isn't a systematic ayanamsa offset, it's spread. Pattern is identical for Raman (mode 3) and Krishnamurti (mode 5). True Citra (mode 27) shows a separate +55″ systematic offset which is a different issue (likely different True-Citra formula).

The pattern that surprises us: the other Swiss Ephemeris implementation reproduces VedicRishi to 0.1″ on Rahu/Ketu (mean node) and 0.5–1″ on Saturn/Jupiter, while @fusionstrings/panchangam shows ~18″ p95 on every planet. Whatever is causing the spread looks specific to your library, not Swiss Ephemeris in general.

Minimal reproducer

import { p_julday, calculate_planets } from '@fusionstrings/panchangam';

// 2026-05-11 00:00:00 IST → UT 2026-05-10 18:30
const jd = p_julday(2026, 5, 10, 18.5, 1);
const sun = calculate_planets(jd, 1).find(p => p.name === 'Sun');
console.log(sun.longitude);
// panchangam   → 25.97303°  (Aries 25°58'23")
// VedicRishi    → 25.97139°  (Aries 25°58'17")  Δ = +5.9″
// `mivion/swisseph` stack → 25.97142°            Δ = +0.1″ from VR

Our current workaround (Moon)

While debugging, I added a manual correction in our wrapper: after calling calculate_planets, I shift the Moon longitude by +(ΔT_seconds × 0.549″/sec) / 3600° using a local IERS ΔT table (1900–2100). With this patch:

  • Without it: Moon mean Δ vs VR ≈ −11″, p95 ≈ 39″, max ≈ 60″
  • With it: Moon mean Δ vs VR ≈ +15″, p95 ≈ 41″, max ≈ 51″

So the correction moves us in the right direction (closes a ~26″ gap) but overshoots by ~14″ — implying the real internal ΔT use inside calculate_planets is between "treats jd as TT" and "treats jd as UT", or the correction needs a different scaling. The mivion/swisseph stack matches VR without any wrapper-level patch — its swe_calc_ut applies ΔT internally and the output is correct out of the box.

For Sun / Mercury / Venus / Mars / Jupiter / Saturn / Rahu we apply no manual correction — the values come straight from calculate_planets. That's the column shown as "panchangam vs VR" in the table above (still ~18″ p95).

Our approach so far

  1. Time conversion: parse local birth time + timezone offset → UTC decimal hour → p_julday(year, month, day, utc_hour, 1). Handles day wraparound when UTC crosses midnight.
  2. Planet positions: calculate_planets(jd, mode) straight out, no transformation, used as-is for Sun/Mars/Mercury/Venus/Jupiter/Saturn/Rahu.
  3. Moon: manual arc-second correction (described above) before consuming the longitude.
  4. Ketu: derived as Rahu + 180° (mod 360).
  5. KP custom variants (kp_new, kp_straight_line, kp_kullar): compute the panchangam Lahiri sidereal output, then add a custom adjustment (Lahiri_ayanamsa − our_KP_ayanamsa). Works for non-Rahu planets; for Rahu it produces a mean-node value (matches the mivion/swisseph stack) while VedicRishi returns true-node for KP_NEW — that's a known third-party divergence we accept.
  6. Outer planets (Uranus / Neptune / Pluto): use calc_ut(jd, SE_URANUS, …) directly, subtracting the ayanamsa value. This path works correctly — agrees with VR to within ~1″ on those.

What we'd love help with

We need our charts to reproduce VedicRishi-style output to within ≤1–2″ across these five ayanamsas. We're stuck because:

  1. We can't tell if we're calling calculate_planets wrong (TT vs UT semantics on the JD argument), or if the spread is inherent to your wasm build.
  2. The Moon workaround works directionally but overshoots — we'd like the underlying mechanism documented so we can apply the right correction (or have the library apply it).
  3. Outer planets via calc_ut agree with VR to ~1″, but calculate_planets for the main 7 shows ~18″ — strongly suggests calculate_planets uses a different internal path.

Specific questions:

  1. What does calculate_planets(jd, mode) treat jd as — TT or UT? (i.e., does it call swe_calc or swe_calc_ut internally?)
  2. Which Swiss Ephemeris data set is the wasm compiled against — DE441 / Moshier / built-in? This affects last-arcsecond agreement.
  3. Is the ~18″ spread expected, or does it indicate a config issue on our side?
  4. Could you expose a calc vs calc_ut distinction so downstream apps can pick the convention that matches their reference software?
  5. For mode 27 (TrueCitra) — which Spica/Citra formula is implemented?

I can share the full CSV (200 cases × 5 ayanamsas × all planets, signed Δ vs each reference) if it helps reproduce. Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions