Internal module guide. This module is now part of
@astrology/western; use the public package README for application setup.
transit module
Implements transits, the comparison of current sky positions with a reference chart. Compares elected moving/reference body pairs using Composer geometry. The natal
reference is fixed on the transit time axis; its birth-time velocity never decides
applying/separating phase (whether the aspect is tightening or loosening). Unknown moving speed remains unknown, while geometric
exactness remains exact. Inputs are not mutated and Binder’s stored aspects are
not reused as transit contacts.
Point in time
const rules = validateWesternTransitRules({
pairs: [{ movingBody: 'moon', referenceBody: 'sun' }],
aspectPolicy: {
id: 'transit-orbs', version: '1', motion: 'fixed-target',
rules: [{ id: 'square', angle: 90, orb: 3 }],
},
});
const result = evaluateWesternTransits(reference, moving, geometry, rules);
Both snapshots must already be admitted. Their zodiac/ayanamsa and coordinate conventions must be compatible; birth instants and local house observers may differ. Every elected body must exist. Contacts retain moving/reference chart hashes and body IDs, the selected policy, and deciding rule evidence. Houses and probe access are not required.
Window solver and budget decision
Window mode elects window rules with event: 'exact-crossing', an integer scan
step, integer time tolerance, angular tolerance in degrees, iteration cap, and a
maximum observed angular displacement per scan step (at most 90°). It searches
exact aspect roots bracketed by adjacent samples, including exact sampled/window
endpoints. It does not calculate orb-entry/exit intervals. Constant exact
plateaus do not create repeated events.
The host opens a dedicated C-04 session with the request’s context and budget,
then supplies its probe port and geometry port to scanWesternTransits:
const searchRules = validateWesternTransitRules({
...rules,
window: {
event: 'exact-crossing',
stepMilliseconds: 3_600_000,
timeToleranceMilliseconds: 1_000,
angularToleranceDegrees: 0.0001,
maxIterations: 32,
maxStepDegrees: 30,
},
});
const probe = composer.openProbeSession(context, budget);
const result = await scanWesternTransits(
{ reference, context, budget }, { geometry: composer.geometry, probe }, searchRules,
);
All budget fields are explicit: inclusive start/end, maximum duration, body and evaluation caps, minimum grid step, and serial concurrency. The complete initial grid must fit the declared evaluation cap; refinement spends additional attempts from that same cap. Single refinement reads may be closer than the series minimum step, as C-04 permits. Provider failures and cancellation propagate; unsuccessful provider attempts remain accounted for by C-04. No retries or hidden time limits are introduced. The host may supply a cancellation/deadline signal in the budget.
Each crossing uses actual midpoint reads and bounded bisection on unwrapped
longitude. A root must meet both the time-bracket and angular-residual tolerances;
an exactly evaluated root has a zero-width bracket. Millisecond resolution or the
iteration cap can prevent convergence, which is explicit. Shared geometry checks
the solved contact using an event policy whose orb is the numerical angular
tolerance, independently of the point-in-time orb. That policy is retained on the
contact under the :solver-tolerance policy ID suffix.
Window results contain solved timestamps, brackets, residuals, directed contacts, probe identities and the successful probe reads with their original evidence. They retain the reference chart identity and requested budget bounds without serializing cancellation/deadline signals. Events indistinguishable within the time tolerance are deduplicated per elected pair/aspect. Both directed branches of a shortest-arc aspect are searched; conjunction/opposition duplicates are removed. Multiple retrograde passes remain separate events when resolved by the grid.
Search outcomes and resolution
coverage: 'sampled-brackets' applies to every window result. status: 'finished'
means every scan bracket was processed. outcome: 'no-bracketed-event' means no
root was found at that resolution, even if samples were inside an aspect’s orb.
It does not certify absence of all possible exact contacts: a reversal, tangency
or complete revolution entirely between samples can evade bracketing. Hosts
must select an appropriate step for the moving bodies and desired resolution.
Observed steps exceeding maxStepDegrees, or nonmonotone refinement samples,
produce resolution-exceeded. Exhausted probe/refinement limits produce
limit-exceeded. Both return outcome: 'incomplete' and retain already solved
events and successful probe evidence. A sampled in-orb contact is never promoted
to a solved event. Invalid budgets, incompatible contexts and missing elected
bodies fail admission rather than returning an empty search.
Feature binding
createTransitFeature(admittedRules) binds the admitted bundle hash and existing
cross-chart-geometry gate. supportedModes(upstream) advertises window only
when window rules are elected and C-04 probes are available; snapshot support
remains independent. Elected window features optionally bind the probes gate
and enforce its presence when evaluating a window request.
Both operations use snapshot: 'none'. Use
engine.operation({ profile, technique: 'transit', request }) with supplied
reference/moving charts or a reference plus window context/budget. The engine
opens the declared bounded probe session only for window mode. Original chart
evidence, probe IDs and incomplete-search outcomes propagate to its envelope.
engine.compose can acquire references and point-in-time moving charts first.
Related code and verification
Contacts, scanner, solver, rule contracts, feature, analytic fixtures, acceptance tests.
Run pnpm --filter @astrology/western... -r build,
pnpm --filter @astrology/western check, and
pnpm --filter @astrology/western test.