Astrology9 MIN READ51 / 116

Composite Method midpoint@1 — Specification (W-05)

Status: Implemented v1 convention · Task: W-05 · Updated: 2026-10-25 · Implementation: W-06 package.

A composite chart is a relationship chart: one derived chart built from two admitted reference charts. Two construction families exist and are not interchangeable:

  • Midpoint (space) methods average the two charts’ ecliptic longitudes. No new astronomy is needed.
  • Time/place methods (Davison) compute an averaged instant and location, then ask Composer for a real chart at that point.

midpoint@1 is the first family only. The second is a separate, unsupported variant (§10).

1. Admitted variant

Field Value
Technique composite
Version 1
Variant midpoint
Rulebook entry { "technique": "composite", "version": "1", "variant": "midpoint", "hash": "<payload hash>" }

Rule payload (validated by validateCompositeRules):

{
  "method": "midpoint",
  "tieBreak": "forward-from-a",
  "angles": "midpoint",
  "houses": "midpoint-cusps"
}

The payload fixes the conventions. Bodies come from the profile; the aspect/orb policy is the profile’s aspectPolicy with the motion override of §5. No field is optional: an unrecognised value is INVALID_RULES, never a default.

2. Inputs and compatibility

Input Source Use
Chart A Admitted snapshot, host-supplied Source points, angles, cusps
Chart B Admitted snapshot, host-supplied Source points, angles, cusps
Profile Engine Body set, zodiac, house system, aspect policy
Injected GeometryPort WesternFeatureContext.upstream.geometry Optional contact evaluation

Charts are admitted, not composed here: the engine (or a future chart-pair operation) supplies both snapshots. This feature never calls ComposerPort.compose.

Required equality between A and B:

Field Rule Failure
context.zodiac (and ayanamsa when sidereal) Must be identical PROFILE_INCOMPATIBLE
context.positionOrigin Must be identical PROFILE_INCOMPATIBLE
context.houseSystem, when cusps are used Must be identical and non-null on both charts FACTOR_UNAVAILABLE (houses)

Different instant, houseObserver and positionObserver are legitimate — two people are born at different times and places — and never fail. Different frame values do not matter either: the composite carries no house-counting reference. Only the cusp ring constrains the two charts, and it needs equal houseSystem values, not equal frames.

3. Construction

3.1 Angle helpers

All longitudes are degrees in [0, 360).

wrap360(x) = ((x % 360) + 360) % 360
wrap180(x) = r > 180 ? r - 360 : r,  where r = wrap360(x)   // range (-180, 180]

wrap180 is closed at +180 and open at -180. That single choice makes the opposite-point tie-break in §3.2 deterministic.

3.2 Body midpoint

For a body present in both charts with longitudes a (chart A) and b (chart B):

d = wrap180(b - a)
m = wrap360(a + d / 2)

d is the signed shorter arc from A to B; m is its midpoint. The antipode wrap360(m + 180) is never chosen, never used as a “house-friendly” alternative, and never selected to keep a planet in a sign. At d = 180 exactly (opposite points) the tie is resolved forward from A:

a = 0,   b = 180  ->  m = 90
a = 270, b = 90   ->  m = 0     (270 + 90 = 360)

This tie-break is the tieBreak: "forward-from-a" value; the other convention is a different variant.

Bodies present in only one chart are omitted and reported as missing-bodies, in profile body order. A body is never interpolated, borrowed from the other chart or filled from a previous request.

3.3 Derived angles

Point Rule
Ascendant midpoint(ascA, ascB) by §3.2
Midheaven midpoint(mcA, mcB) by §3.2
All other angles (vertex, armc, co-ascendants, …) Absent — not derived by this method

armc is equatorial right ascension, not a zodiacal longitude, and is excluded outright: averaging it would produce a number that is neither an ARMC nor a longitude.

If either chart has no angles (angles === null), the composite angles are null with missing-angles; the bodies still compose.

3.4 Derived house ring

When houses: "midpoint-cusps" and both charts carry cusps:

cusp_k = midpoint(cuspA_k, cuspB_k),  k = 1..12

Properties that must be asserted in fixtures:

  • the ring is derived, not a Binder house result; it must not be presented as a valid cusp set for the named house system;
  • cusp 1 equals the derived ascendant whenever both source charts satisfy cusp 1 === ascendant;
  • the ring is not sorted, rotated or normalised into monotone order. A midpoint ring may be non-monotone; that is a property of the method, not an error to repair;
  • body-to-house placement is not computed here. A consumer may pass the composite GeometryChart (derived cusps included) to the shared overlay port; Western code adds no placement algorithm.

With houses: "none" the ring is absent. With midpoint-cusps and a missing source ring the ring is absent with FACTOR_UNAVAILABLE (houses) and the bodies still compose.

3.5 Speeds

Every composite point has speedLongitude: null. A composite chart is not a moving chart, and this method defines no derivative; inventing one would fabricate applying/separating state. Contacts are therefore evaluated with motion: "unknown".

4. Result

Field Meaning
method { id: 'midpoint', version: '1', tieBreak: 'forward-from-a' }
sources { a: SnapshotIdentity, b: SnapshotIdentity }, in request order
bodies[] { body, longitude, origin: DerivedPointOrigin } in profile order
angles { ascendant, midheaven } or null, each derived
houses { system, cusps, derived: true } or null
contacts? Same-chart contacts of the composite chart (§5), when geometry is connected
missing missing-bodies, missing-angles, missing-houses, missing-aspects

origin for every point is:

{ "kind": "derived", "method": "composite.midpoint", "version": "1",
  "sources": ["<A snapshot hash>", "<B snapshot hash>"] }

The result retains both source hashes and the method identity so the chart is replayable without re-reading either source.

5. Contacts

Composite contacts are same-chart contacts of the derived chart, not cross-chart contacts of A and B. Re-labelling A-to-B synastry contacts as composite aspects is a defect.

When geometry is connected:

policy = { ...profile.aspectPolicy, motion: 'unknown' }
contacts = geometry.evaluateChartAspects(compositeGeometry, policy)
  • compositeGeometry is a GeometryChart with points built from §3.2, origin.kind = 'derived' and cusps from §3.4 when available.
  • The evaluated policy is recorded verbatim, including motion: 'unknown'.
  • If geometry is absent, contacts is omitted with missing-aspects; the chart itself is still a successful result.

6. Composer facts and ports

Need Provided by Absent behaviour
Two admitted snapshots (bodies, angles, cusps) Host / engine chart-pair input INSUFFICIENT_INPUT
Zodiac/ayanamsa equality Snapshot contexts PROFILE_INCOMPATIBLE
GeometryPort WesternFeatureContext.upstream.geometry missing-aspects only
ComposerPort Not required for midpoint —

Declared requirements: snapshot: 'none', upstream: ['positions'], optionalUpstream: ['geometry'], dependencies: [].

positions is required even though no astronomy is performed: without observed source longitudes there is nothing to average. The gate is inspected for provenance, not for a new read. The cusp ring comes from the supplied pair, not from the engine’s request snapshot, so composite does not raise the snapshot requirement to houses.

The pair is routed through WesternEngine.operation; WesternEngine.compose can acquire each source first. No unrelated request snapshot is composed for this operation.

7. Failures

Case Behaviour
Fewer than two snapshots INVALID_REQUEST
Different zodiac/ayanamsa or position origin PROFILE_INCOMPATIBLE
Unknown method, tieBreak, angles or houses value INVALID_RULES
Source chart lacks a profile body Omit that body, missing-bodies
Source chart lacks angles angles: null, missing-angles
Cusp ring requested, a source ring missing or systems differ houses: null, FACTOR_UNAVAILABLE (houses)
Geometry not connected contacts omitted, missing-aspects
time-place requested LAYER_UNSUPPORTED, never approximated by midpoints

8. Worked examples

All values are exact for the stated inputs.

Id Input Expected
C-F1 A 10°, B 50° 30°
C-F2 A 350°, B 10° 0° (wrap through 0)
C-F3 A 359°, B 1° 0°
C-F4 A 100°, B 100° 100° (identical charts are their own composite)
C-F5 A 0°, B 180° 90° — tie forward from A
C-F6 A 270°, B 90° 0° — tie forward from A; the antipode 180° is not chosen
C-F7 A 10°, B 190° 100° — tie forward from A
C-F8 A has Sun/Moon, B has Sun only Sun composed; Moon missing-bodies
C-F9 A asc 15°, mc 105°; B asc 45°, mc 135° asc 30°, mc 120°
C-F10 A cusp 1 = 15°, B cusp 1 = 45° derived cusp 1 = 30° = derived ascendant
C-F11 Either chart position-only bodies compose; houses: null, missing-houses
C-F12 Geometry connected, derived Sun 30°, derived Moon 150° trine contact (120°), phase: 'unknown', policy records motion: 'unknown'
C-F13 A 10°, B 50°, then swapped 30° both ways; sources order follows the request
C-F14 A 0°, B 180°, then swapped 90° then 270° — the tie-break is deliberately asymmetric

C-F13 is the required symmetry check: for every pair except exact opposition, midpoint(a, b) = midpoint(b, a). C-F14 records the one exception. A tie must break somewhere, and “forward from A” makes the swap observable; a hidden symmetry rule (for example “prefer the midpoint in the same sign as A”) is a different variant and must not be added to make the swap look stable.

9. Prerequisites before W-06

The implementation and coordinator integration now satisfy these runtime seams; convention sources remain the responsibility of each supplied production rulebook. See the package tests.

Id Prerequisite Owner
P1 A chart-pair input (two admitted snapshots) is expressible by the engine request or a dedicated operation W-11/W-04 owner (see techniques index)
P2 A reviewed source for the midpoint rule and the forward tie-break recorded in the manifest Composite reviewer
P3 composite.midpoint@1 payload schema and validator registered W-06

10. Unsupported variants

Variant Why unsupported
time-place (Davison) Needs an averaged instant, a great-circle midpoint location and a real Composer chart there. Averaging longitudes is not a degraded version of it.
house-ring-recomputed Recomputes cusps from the derived MC and a latitude; no Composer operation exposes that construction.
sun-moon-only, angle-only Different point sets with different semantics; not a parameter of this method.
Interpretation, chart drawing, relationship scoring Downstream products.

The method is never chosen by the engine: the profile elects composite@1/midpoint, and an unregistered or unreviewed method is LAYER_UNSUPPORTED.