Packages4 MIN READ110 / 116

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.

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.