Reading the Chart Composer
Chart Composer turns completed astronomical reads into neutral chart facts. It serves both the Vedic and Western engines; those engines add their own judgments afterward.
If you want to understand one request, start with the facade rather than reading every contract first. You can return to the types as each value enters the flow.
Suggested reading order
| Read | What to look for |
|---|---|
| composer/src/composer.ts | The entry point: validate context, acquire facts, assemble a snapshot. |
| contracts/src/context.ts | The explicit choices that define a calculation. |
| context/src/normalize-context.ts | Which inputs are rejected and which representations are normalized. |
| composer/src/operations.ts | When positions and houses are actually read. |
| binder-client/src/positions.ts and houses.ts | How a request maps to Binder and how returned facts/evidence survive. |
| chart-core/src/assemble.ts | How already acquired facts become one snapshot. |
| chart-core/src/frame.ts, placement.ts and rosters.ts | Reference selection, body-to-house placement and grouping by house. |
| evidence/src/identity.ts | What makes two calculations have the same identity. |
Then read geometry/src/aspects.ts for aspect evaluation, or probes/src/session.ts for bounded reads at several instants.
Each internal module has a focused guide: contracts, context, evidence, Binder bridge, chart core, geometry, probes and facade.
Follow one composition
Suppose the host requests Sun and Moon positions at an explicit instant, in the sidereal Lahiri zodiac, with geocentric positions and local whole-sign houses.
- The host initializes Binder with its tested TT resolver, wraps it with
createBinderReader, and injects that reader intocreateChartComposer. TT is Terrestrial Time (TT), the uniform astronomical time scale Binder needs; resolving it is outside Composer. compose(input)validates the requested conventions. It turns an offset-bearing ISO timestamp into epoch milliseconds and resolves omitted observer elevation to zero metres. It does not resolve a civil timezone or shift any astronomical longitude.- Acquisition checks coarse capabilities, then reads positions once. Although the chart has a local house observer, the geocentric position request has no observer field.
- Because a house system was requested, acquisition then reads local houses using the house observer. Binder still owns provider admission, coverage checks, normalization and shared facts.
- Pure assembly checks that the completed reads match their expected requests and declarations. Lagna uses the supplied ascendant to select the reference sign.
- Whole-sign placement counts forward from that sign. For example, with Pisces as house one,
Aries is house two. Cusp systems instead delegate placement to the existing
resolveHousehelper. - Bodies retain their requested order; rosters list houses 1–12 and group those body IDs. The snapshot retains position and house evidence separately, records degradation, and hashes the conventions and contributing identities.
See the host example and the Binder integration test. The test uses the real Binder coordination code with controlled providers. The additional native test runs the actual Swiss worker through Composer against standalone official-C fixtures.
Terms that are easy to confuse
| Term | Meaning here |
|---|---|
| Position origin | Where positions are observed from: Earth’s centre (geocentric) or an explicit local observer (topocentric). See geocentric and topocentric. |
| House observer | The local latitude/longitude used for house geometry. It does not change position origin. |
| Zodiac / ayanamsa | The longitude reference (zodiac). Sidereal requires a named ayanamsa, the offset between tropical and sidereal zero points; Binder has already applied it to returned positions. |
| Chart frame | The reference sign used to count houses: lagna, Moon, Sun, or none. |
| Coordinate frame | The astronomical coordinate system, currently apparent-ecliptic-of-date (see apparent positions and the ecliptic). It is not changed by choosing a Moon chart frame. |
| Lagna | The rising sign used as house one (lagna), resolved from the supplied ascendant. |
| Cusp | A boundary between houses (house cusp). A point exactly on a cusp starts the entering house. |
| Roster | Body IDs grouped into one numbered house (a Composer term, not astrological vocabulary). |
| Nakshatra / pada | A sidereal lunar-mansion sector of 13°20′ (nakshatra) and its quarter (pada). Tropical facts use null. |
| ARMC | Equatorial right ascension of the meridian, in degrees. It is not a zodiacal longitude and must not receive an ayanamsa subtraction. |
| Evidence | The operation’s provider declaration, normalization provenance and routing decision. |
| Identity | A deterministic hash of calculation choices and contributing versions/data, not a protected cache key. |
Positions use ecliptic longitude/latitude in degrees, distance in astronomical units, and angular speed in degrees/day. Observer elevation is in metres. Absolute instants and probe steps use milliseconds.
Read a snapshot without guessing
The central result is ChartSnapshot. Its groups answer different questions:
| Field | What it tells you |
|---|---|
context |
Which instant, observers, bodies and conventions were requested. |
frame |
Which reference sign was chosen for house counting. |
bodies |
Unchanged normalized positions, shared semantics, and one-based house placement. |
houses |
The ordered house rosters. |
angles |
Original supplied cusp/angle geometry; ARMC keeps its equatorial meaning. |
binderAspects |
Original aspects from Binder’s construction-time policy. |
evidence |
Separate contributing position/house sources and their precision/rounding information. |
identity |
Reproducibility hash and contributing implementation/rule versions. |
degraded |
Whether any contributing source declares reduced precision. |
Use houseSystem: null and frame: 'none' for the simplest position-only request. That acquires no
houses, returns null angles and body houses, and returns an empty roster list.
Null speed means the derivative is unavailable, not that the body is stationary. Zero is a real returned derivative. An absent optional field means that part of the contract was not supplied. Readonly types do not freeze the returned object graph at runtime: consumers must not mutate the retained position, semantic or evidence references.
Understand aspect motion
The geometry package always evaluates the explicit rules passed with a policy ID and version. It does not silently reuse Binder’s construction-time orb defaults. An orb is the allowed deviation from an aspect’s exact angular separation.
| Motion policy | Derivatives used | Typical use |
|---|---|---|
returned-speeds |
Both supplied speeds | Same-chart natal aspects. |
fixed-target |
Source speed; target treated as zero | A moving transit against a fixed natal reference. |
unknown |
Unknown derivatives | Timeless synastry, unless a technique defines a shared time parameter. |
For a source at 0° moving +1°/day and a target at 95° moving +2°/day, the square’s orb is 5°. Using both speeds makes separation increase, so the square is separating. Holding the target fixed makes separation decrease, so it is applying. The target’s stored speed remains +2 in both cases. This distinction is exercised in geometry.test.ts.
Technique-derived points carry their method, version and source IDs. Their speed stays null unless the technique defines a derivative; they need neither a fabricated Binder snapshot nor invented houses.
Understand probe state
A probe session pins one context and varies only the instant. It owns a finite evaluation budget, a serial queue, successful-read reuse and a record of failed instants.
evaluate(instant) supports individual refinement probes. series({ instants }) first admits
the complete ordered grid, its minimum step and remaining attempt budget, then acquires the reads.
The minimum step applies to series grids; individual refinement probes may be closer together.
Successful reads are reused only inside that session. A failed position attempt still consumes budget and is not retried. A reused read retains its evidence, while its returned cumulative consumed count reflects the session’s current usage.
Cancellation and host deadline signals stop subsequent scheduling. They cannot interrupt native work already executing. Invalid instant/grid inputs can throw synchronously; asynchronous capability or acquisition failures reject the returned promise.
Boundaries and current limits
The host owns civil-time resolution, Binder initialization, TT resolution, persistence and cache protection. Binder owns astronomy, provider routing, normalization and base semantics. Composer owns acquisition coordination and neutral chart assembly. The engines own tradition-specific rules.
Returned Binder numbers are already normalized. Composer preserves those digits and their rounding provenance; it cannot recover raw provider precision.
Native Hindu rise/set is implemented through Binder and the optional reader/session port. It uses the explicit house observer and a window of at most 72 hours. Sunset is opt-in. Missing events remain null, including polar cases and a next sunrise outside the window. A session reserves two evaluations for sunrise/next sunrise, or three with sunset. Other horizon definitions remain unsupported. See probe events for the policy.
Verify while reading
From the repository root:
pnpm check:chart-composer
pnpm test:chart-composer
The focused tests cover boundary placement, coordinate admission, motion policies, stable identity, budget enforcement, failure propagation and native-free imports. See the development plan for completed criteria and the remaining event prerequisite.