Dasha Engine — High-Level Design
Status: Draft for review · Part of: Astro Composer · Updated: 2026-10-24
The dasha engine turns the Moon’s position at birth into a Vimshottari timeline: mahadasha, antardasha and pratyantardasha (first-, second- and third-level planetary periods) as continuous UTC instants, plus the active chain at any asOf. It is pure time arithmetic over a rulebook sequence — no clock, no calendar, no binder.
1. Purpose
The birth balance is a fraction of one nakshatra (lunar mansion). One arcsecond of lunar longitude moves a period boundary by roughly two seconds. Year length is also a convention — 365.25, 360 and 365.2425 days all appear — and over 120 years those choices differ by months. Two implementations of one chart therefore produce two calendars for “when my Saturn period starts”.
The dasha engine exists so that:
- one function produces every dasha boundary a product shows or notifies on;
- periods tile the timeline exactly, at every requested level;
- dates are absolute instants, independent of server timezone or locale;
- the response names the sequence, year length, profile and rulebook behind it.
2. Where it sits
chart core · Moon longitude + nakshatra id ──┐
request · birth instant + depth ─────────────┼──▶ dasha engine ──▶ periods + lookup
profile · dasha year length ─────────────────┤
rulebook · nine lords, years, 120 total ─────┘
- Upstream: chart core supplies the binder’s full-precision Moon longitude and nakshatra id. The facade supplies the absolute birth instant, requested depth and one rulebook handle.
- Downstream: the facade assembles the dasha layer into
POST /v1/charts; transit and notification features ask the same engine which period is active at an instant. - Never: no binder call, no nakshatra re-derivation, no clock read, no calendar or timezone database.
Input contract
| Input | Source | Used for |
|---|---|---|
| Birth instant | Caller, canonicalised by the facade | Timeline origin |
| Moon longitude | Binder via chart core, full precision | Elapsed fraction of the birth nakshatra |
| Moon nakshatra id | Binder via chart core | First lord, and the authority on the starting segment |
| Depth | Request | 2, 3 or 4 levels; default 3 |
| Year length | Profile | Duration of one dasha year in days |
| Sequence table | Rulebook | Nine lords and their years, summing to 120 |
asOf |
Request, optional | Active-chain lookup at one instant |
Output contract
| Output | Shape | Read by |
|---|---|---|
system |
vimshottari |
Response meta |
yearLengthDays |
Profile value, echoed | Audit and fixtures |
timelineStart |
Nominal start of the first mahadasha | Clients that render the full cycle |
periods[] |
Lord, level, start, end, parent, partial/balance | Response, transit windows |
active[] |
One period per level at asOf |
Transit and notification features |
cycle |
120-year cycle index for dates after the first cycle | Long-range lookups |
3. Responsibilities
| Area | Owns | Deterministic rule |
|---|---|---|
| Balance | Fraction of the birth nakshatra already spent | elapsed = (moon mod 13°20′) / 13°20′ |
| First lord | Lord of the birth nakshatra | Binder nakshatra id, never a recomputed index |
| Sequence | Nine lords and their years | Rulebook table; sum of 120 asserted at load |
| Division | Sub-periods at every level | Cumulative year fractions, never accumulated durations |
| Timeline | One continuous nested set of periods | Children inside the parent; no gap or overlap |
| Instant math | Days to UTC instants | days × 86 400 s; no months, timezones or DST |
| Lookup | Active chain at asOf |
Half-open intervals; deepest requested level wins |
| Output | Canonical ordering and identifiers | Depth-first, parent before children |
4. Architecture
Diagram: reference design, not deployed behaviour.
The engine reads one Moon position and one sequence table and returns a value object. It holds no calendar, clock or state between requests, so the same birth data reproduces the same boundaries on any JVM in any timezone.
5. Rules
- The Moon alone decides. No other body, house, aspect or transit enters Vimshottari. The engine takes one longitude and one nakshatra id from the chart, nothing else.
- The binder owns the nakshatra. The engine takes the binder’s nakshatra id for the first lord and computes only the fraction inside it. A computed index that disagrees is a boundary contract failure, not a rounding difference.
- Full precision in, one rounding point out. Balance uses the binder’s unrounded longitude. Durations stay at full precision and are serialised to the second once, at assembly.
- The year length is a profile field. Default 365.25 days; 360 and 365.2425 are supported by the rulebook. It is echoed in every response, and changing it is a profile version, never a per-request override.
- Boundaries come from cumulative fractions. A child starts at
parentStart + parentDuration × cumulativeYears / 120and ends at the next cumulative boundary. Summed durations drift; the last child must end exactly where its parent ends. - Time is absolute. Dates are UTC instants. No timezone database, calendar month, local midnight or leap-second arithmetic; display formatting belongs to the client.
- Nesting is an invariant. Every child lies inside its parent, siblings tile without gaps or overlaps, and the 120-year cycle repeats without drift. This is tested, not assumed.
- The first period is partial. Its nominal start precedes birth. The response carries that start and
partial: truewith the balance at birth, so a client can show the full period or “balance at birth” without guessing. - Depth is explicit and capped. Default 3, maximum 4. Depth 4 never silently returns 3; depth 5 is rejected at validation.
- Rulebook integrity is a load-time gate. Nine lords, each named once, years summing to 120. A failing table is not served.
- Deterministic order and ids. Periods are emitted depth-first in mahadasha order with stable parent references, so two responses diff byte for byte.
- There is no “now”. The engine never reads a clock. A caller wanting the current period supplies
asOf; without it, the response is the timeline only. - One division primitive. KP sub-lord division, when it lands, reuses the same proportional splitter. A second “divide a segment by the Vimshottari years” implementation is a defect.
6. Edge cases
| Case | Behaviour |
|---|---|
| Moon exactly on a nakshatra start | Elapsed 0; full first period, no partial balance |
| Moon exactly on a nakshatra end | Half-open rule yields the next nakshatra and elapsed 0 |
| Moon at 359.9999° | Wraps to Ashwini; elapsed is never negative |
| Birth instant exactly on a period boundary | The period starting at that instant is the active one |
| Balance shorter than a single sub-period | Children scale inside the short parent; no minimum duration |
| Year length 360 versus 365.25 | Dates differ by months over a cycle; the response names the year length |
| Depth 4 requested | Allowed; each level nests inside its parent |
asOf before the nominal timeline start |
Typed empty result, not the first period and not an error |
asOf after 120 years |
Cycle index reported; the next cycle is generated on demand |
| Moon missing from the chart | Request fails; dasha never defaults to a placeholder |
| Leap day in a displayed date | Not a special case; instants are the source of truth |
| Retrograde or mean/true node choice | Irrelevant to Vimshottari; the Moon is never retrograde |
| Rulebook years sum to 119 | Startup fails; no chart is served under a broken sequence |
7. Out of scope
- Dasha interpretation, antardasha prose, remedies and predictions (product content).
- KP sub-lord and sub-sub-lord output (later KP profile; the division primitive is shared).
- Ashtakavarga, transits, Sade Sati and muhurta rules (their own designs).
- Chart frame, house placement, dignity and combustion (chart core).
- Divisional charts and vargottama (varga engine).
- Binder access, caching and provenance (facade and binder).
- Timezone display, locale formatting and calendar conversion (client).
8. Planned code
| Piece | Responsibility |
|---|---|
dasha/VimshottariSequence |
Nine lords, year table and load-time integrity checks |
dasha/DashaBalanceResolver |
Elapsed fraction, first lord and balance at birth |
dasha/ProportionalDivider |
Cumulative-fraction subdivision shared by every level |
dasha/TimelineBuilder |
Depth-first periods with parent references and partial flags |
dasha/PeriodLookup |
Active chain at asOf with half-open intervals and cycle index |
dasha/DashaInstantMath |
Day-to-instant arithmetic without calendar or timezone |