Astrology7 MIN READ55 / 116

Harmonic Chart harmonic-n@1 — Specification (W-05)

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

A harmonic chart multiplies every ecliptic longitude by an integer factor. It is the smallest transformation in the engine and the easiest one to mislabel: the output is derived coordinates, not astronomy, and the transformed chart does not inherit the natal house ring.

1. Admitted variant

Field Value
Technique harmonics
Version 1
Variant harmonic-n
Rulebook entry { "technique": "harmonics", "version": "1", "variant": "harmonic-n", "hash": "<payload hash>" }

Rule payload (validated by validateHarmonicRules):

{
  "transform": "multiply",
  "angles": "transformed",
  "houses": "none",
  "minFactor": 2,
  "maxFactor": 64,
  "allowedFactors": null
}

factor is a request parameter, constrained by the payload. allowedFactors: [4, 5, 7, 9] pins a reviewed subset; null admits every integer in [minFactor, maxFactor]. The factor is part of the result and of the rule evidence, never of the technique identity.

2. Inputs

Input Source Meaning
Reference snapshot Engine Bodies, angles, speeds
factor Request Integer harmonic number
Profile Engine Body set, aspect/orb policy
Injected GeometryPort Optional Harmonic-chart contacts

No ComposerPort and no ProbePort: the transformation is pure and needs no new astronomy.

3. Transformation

λ_h = wrap360(n × λ_0)
Point Rule
Bodies Every body in the reference snapshot, profile order
Ascendant wrap360(n × asc) when angles: "transformed" and the angle exists
Midheaven wrap360(n × mc) when angles: "transformed" and the angle exists
Other angles Not transformed
Cusps Not transformed and never attached

wrap360 is defined in the composite specification §3.1. The multiplication is done in full precision and wrapped once, at the end. Wrapping an earlier intermediate value is equivalent modulo 360, but it hides the single normalisation point and makes fixture diffs harder to read.

Every transformed point is:

{ "kind": "derived", "method": "harmonics.transform", "version": "1",
  "sources": ["<reference snapshot hash>"] }

The factor is retained in the technique result and in the deciding RuleMatch.facts ({ factor: n }), because DerivedPointOrigin has no factor field. Two results with different factors are never merged or cached as one.

4. Speeds

The transformation has an exact derivative:

s_h = (s_0 === null) ? null : n × s_0

This is the derivative of the transformed coordinate, not a claim about the body’s physical motion, and it is the only reason harmonic contacts can carry a phase. Unknown source speed stays null and reaches the shared geometry as phase: 'unknown'; zero is never substituted.

Contacts are evaluated with motion: "returned-speeds" so both derived derivatives are used. If the source chart was composed without speeds, every harmonic phase is unknown and the contacts are still reported.

5. Angles and houses

Policy Behaviour
angles: "transformed" Ascendant and midheaven are multiplied like bodies and labelled derived
angles: "omitted" Not admitted by this variant; a different version
houses: "none" The harmonic chart has no cusps; overlays are unavailable

Natal cusps are never attached to the harmonic chart. A harmonic chart is not a relocated natal chart, and a transformed point does not occupy a natal house. A request for harmonic house placement is LAYER_UNSUPPORTED; an overlay evaluated against the natal chart is a separate, explicit consumer choice and is not produced here.

6. Contacts (optional)

policy   = { ...profile.aspectPolicy, motion: 'returned-speeds' }
contacts = geometry.evaluateChartAspects(harmonicGeometry, policy)
  • Same-chart contacts of the harmonic chart, not a re-labelling of natal aspects.
  • Orb set and aspect ids are the profile’s; the harmonic technique adds no orb table.
  • harmonicGeometry carries points from §3, derived origins, derived speeds and no cusps.
  • With geometry absent, contacts is omitted with missing-aspects; the harmonic chart is still a successful result.

7. Result

Field Meaning
method { id: 'harmonic-n', version: '1', transform: 'multiply' }
factor The requested integer
reference { snapshot: <hash>, instant }
points[] { id, longitude, speedLongitude, origin: DerivedPointOrigin }
angles { ascendant, midheaven } or null
houses Always null
contacts? Harmonic-chart contacts (§6)
missing missing-angles, missing-aspects

The original reference snapshot is returned unchanged and is never mutated. Repeated calls with the same snapshot and factor are byte-identical.

8. Composer facts and ports

Need Provided by Absent behaviour
Reference bodies and speeds Reference snapshot INSUFFICIENT_INPUT
Reference angles Reference snapshot (house read) missing-angles; bodies still transform
Aspect evaluation GeometryPort missing-aspects only
ComposerPort, ProbePort Not used —

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

The admitted variant transforms the reference angles, so the snapshot requirement is houses. A host without a house observer fails context assembly explicitly; a positions-only harmonic variant is a different version. The missing-angles row in §9 applies to snapshots supplied directly to the pure transformation.

9. Failures

Case Behaviour
factor absent or non-integer INVALID_REQUEST
factor === 1 INVALID_REQUEST; the identity transform is not a harmonic
factor < minFactor (other than 1), > maxFactor, or outside allowedFactors LIMIT_EXCEEDED
Unknown transform, angles or houses value INVALID_RULES
Reference has no angles missing-angles; bodies still transform
Harmonic houses requested LAYER_UNSUPPORTED
Geometry absent contacts omitted, missing-aspects
Source speed unknown speedLongitude: null, phase: 'unknown'; never zero

10. Worked examples

n is the factor, λ the reference longitude.

Id Input Expected
H-F1 n=4, λ=10° 40°
H-F2 n=4, λ=100° 40° (400 mod 360)
H-F3 n=4, λ=350° 320° (1400 mod 360)
H-F4 n=4, λ=90° 0° (360 mod 360)
H-F5 n=5, λ=137.4° 327° (687 mod 360)
H-F6 n=7, λ=300° 300° — a fixed point, because 6 × 300 = 5 × 360
H-F7 n=2, λ=200° 40°
H-F8 n=4, s=0.5°/day s_h = 2°/day
H-F9 n=4, s=null null, phase: 'unknown'
H-F10 n=4, asc 15°, mc 105° asc 60°, mc 60° (420 mod 360)
H-F11 n=4, derived Sun 40°, derived Moon 40° conjunction, separation 0, phase: 'exact'
H-F12 n=1 INVALID_REQUEST
H-F13 n=4.5 INVALID_REQUEST
H-F14 n=65 LIMIT_EXCEEDED
H-F15 allowedFactors: [4,5,7,9], n=6 LIMIT_EXCEEDED
H-F16 House placement requested LAYER_UNSUPPORTED
H-F17 Same snapshot and n=4 requested twice identical points, speeds and contacts

H-F6 is the arithmetic check that catches a missing wrap: 7 × 300 = 2100, and 2100 mod 360 is 300, so the transformed point must equal the source. H-F2, H-F3 and H-F4 catch per-step wrapping and off-by-one sign errors.

11. Unsupported variants

Variant Why unsupported
Non-integer factors The transformation is undefined on the circle
n = 1 Identity; not a harmonic chart
n > 64 Declared bound; the result is still defined but unreviewed
angles: "omitted" A different point set; needs its own fixtures
Transformed or inherited houses No reviewed construction; natal cusps are never attached
Harmonic-of-harmonic chains Composition order and identity are undefined here
Harmonic midpoint trees, dial charts, 90° dials Different techniques
Interpretation of harmonic aspects Downstream products

12. Prerequisites before W-10

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 reviewed source for the multiply-transform, the factor bounds and the angle policy Harmonic reviewer
P2 harmonics.harmonic-n@1 payload schema and validator registered W-10
P3 Shared geometry accepts derived points with defined derivatives and no cusps (C-03) Compose owner
P4 Determinism fixture: two calls over one immutable snapshot W-10