Spec: the Moon's phase and libration #1

Closed
opened 2026-10-22 22:26:44 -05:00 by jeff · 1 comment
Owner

Problem Statement

A Zig program that wants to know about the Moon — when the next full Moon is, how much of the disk is lit tonight, which way the Moon is tipped so that features near the limb can be seen — has nothing to call. It would have to transcribe a lunar theory from a book itself, along with the time-scale conversions, nutation, the Sun's position and the coordinate transforms that the theory leans on, and it would have to get every unit, sign convention and time scale right on its own. Those are exactly the places such code goes quietly wrong: degrees mistaken for radians, UT passed where TT was meant, libration reported with the opposite sign.

Solution

zig-astro provides geocentric calculations of the Moon's phase and libration, built on Jean Meeus's Astronomical Algorithms (2nd edition, 1998; see ADR 0001), together with the foundations they need, each of which is public and reusable by the bodies that come later:

  • an instant type that carries its time scale, with ΔT built in;
  • an angle type, so that degrees and radians cannot be confused;
  • conversion between ecliptic and equatorial coordinates;
  • the Earth's nutation and obliquity;
  • the Sun's apparent position;
  • the Moon's apparent position, its phase at an instant, its principal phases, its named phase and lunar age, its libration and the position angle of the axis, and the subsolar point and colongitude.

Every calculation is a pure function of an instant. The vocabulary is the project's GLOSSARY.md.

User Stories

Instants and time scales

  1. As a library user, I want to construct an instant from a Julian Day and a time scale, so that I can pass any moment I already have in astronomical form.
  2. As a library user, I want to construct an instant from a Unix timestamp, so that I can ask about "now" or about a time from a computer clock.
  3. As a library user, I want an instant from a Unix timestamp to be counted as UT, so that civil time is never mistaken for TT.
  4. As a library user, I want to construct an instant from a calendar date and time of day, so that I can ask about a date written down by a person.
  5. As a library user, I want to say whether a calendar date is Julian or Gregorian, so that a historical date from a country that switched calendars late is read correctly.
  6. As a library user, I want no automatic switch between the Julian and Gregorian calendars at 1582 October 15, so that the library never guesses which calendar a date was written in.
  7. As a library user, I want to read an instant back as a calendar date in either calendar, so that I can show a computed instant to a person.
  8. As a library user, I want to read an instant back as a Unix timestamp, so that I can hand a computed instant to the rest of my program.
  9. As a library user, I want to convert an instant between TT and UT, so that I can work in whichever scale my data is in.
  10. As a library user, I want ΔT supplied by a built-in model, so that I do not have to look it up to work in civil time.
  11. As a library user, I want to be able to supply my own ΔT, so that I can use a measured or more recent value than the model's.
  12. As a library user, I want to ask for the model's ΔT at an instant, so that I can see what correction is being applied.
  13. As a library user, I want every calculation to accept an instant in either time scale, so that I never have to convert before calling.
  14. As a library user, I want instants far outside the ΔT model's range still to be accepted, so that a calculation for the year 3500 returns an answer of documented, reduced accuracy rather than an error.

Angles and coordinates

  1. As a library user, I want results that are angles to come back as an angle type, so that I cannot read degrees as radians or the other way round.
  2. As a library user, I want to read an angle in degrees or in radians, so that I can use whichever my program needs.
  3. As a library user, I want to construct an angle from degrees, from radians, or from degrees, minutes and seconds, so that I can enter values from any source.
  4. As a library user, I want to convert ecliptic coordinates to equatorial coordinates and back for a given obliquity, so that I can move between the frames the theories and the observers use.
  5. As a library user, I want longitudes, right ascensions, elongations and position angles normalized to [0°, 360°), so that results compare and display consistently.
  6. As a library user, I want latitudes and declinations in [−90°, +90°], so that results are always in their natural range.

The Earth

  1. As a library user, I want the nutation in longitude and in obliquity at an instant, so that I can turn mean positions into apparent ones.
  2. As a library user, I want the mean and true obliquity at an instant, so that I can convert between ecliptic and equatorial coordinates of date.
  3. As a library user, I want nutation computed from the full IAU 1980 series, so that it never limits the accuracy of anything built on it.

The Sun

  1. As a library user, I want the Sun's apparent geocentric ecliptic longitude and latitude at an instant, so that I can compute anything that depends on where the Sun is.
  2. As a library user, I want the Sun's distance from the Earth in AU, so that I can scale anything that depends on it.
  3. As a library user, I want the Sun's apparent right ascension and declination, so that I can work in equatorial coordinates.
  4. As a library user, I want the Sun's position documented as accurate to about 0.01°, so that I know what it is and is not good enough for.

The Moon's position

  1. As a library user, I want the Moon's apparent geocentric ecliptic longitude and latitude at an instant, so that I can compute anything that depends on where the Moon is.
  2. As a library user, I want the Moon's distance from the Earth's center in kilometers, so that I can compute its apparent size or parallax.
  3. As a library user, I want the Moon's apparent right ascension and declination, so that I can work in equatorial coordinates.
  4. As a library user, I want the Moon's position documented as accurate to about 10″ in longitude, and how that degrades over the centuries, so that I know when I would need a more precise theory.

Phase at an instant

  1. As a library user, I want the Moon's elongation at an instant, so that I know how far it is from the Sun in the sky.
  2. As a library user, I want the Moon's phase angle at an instant, so that I can compute its brightness or its appearance.
  3. As a library user, I want the Moon's illuminated fraction at an instant, so that I can say how much of the disk is lit.
  4. As a library user, I want the position angle of the Moon's bright limb at an instant, so that I can draw the lit side facing the right way.
  5. As a library user, I want to know whether the Moon is waxing or waning at an instant, so that I can tell the evening crescent from the morning one.
  6. As a library user, I want the Moon's named phase at an instant, so that I can show a person a familiar word such as "waxing gibbous".
  7. As a library user, I want the named phases defined as eight equal 45° sectors of elongation, so that the name depends only on the sky and never on a timezone or a calendar day.
  8. As a library user, I want the phase angle in [0°, 180°] and the illuminated fraction in [0, 1], so that the results are always in their natural ranges.

Principal phases and lunar age

  1. As a library user, I want the instant of the next principal phase of a given kind after an instant, so that I can ask "when is the next full Moon?".
  2. As a library user, I want the instant of the previous principal phase of a given kind before an instant, so that I can ask "when was the last new Moon?".
  3. As a library user, I want to iterate over every principal phase between two instants in order, so that I can build a calendar of phases for a month or a year.
  4. As a library user, I want each principal phase reported with its kind and its instant in TT, so that I can convert it to whatever time I want to display.
  5. As a library user, I want principal phases found by a lunation count that I never have to see, so that I do not have to learn Meeus's numbering or the Brown lunation number.
  6. As a library user, I want the lunar age at an instant, measured from the actual previous new Moon, so that it agrees exactly with the previous principal phase.
  7. As a calendar author, I want to name the day a principal phase falls on myself, from its instant and my own timezone, so that the library does not decide what day it is for me.

Libration

  1. As a library user, I want the Moon's optical libration in longitude and latitude at an instant, so that I know which parts of the limb are turned towards the Earth.
  2. As a library user, I want the physical libration in longitude and latitude at an instant, so that I have the complete libration, not only its geometric part.
  3. As a library user, I want the total libration, so that I do not have to add the two parts myself.
  4. As a library user, I want the position angle of the Moon's axis at an instant, so that I can orient a map of the Moon to the sky.
  5. As a library user, I want libration in longitude and latitude reported in (−180°, +180°], so that a small tilt reads as −0.8° rather than 359.2°.
  6. As a library user, I want selenographic longitudes positive towards Mare Crisium, as in Meeus and the IAU convention, and stated in the documentation, so that I know which way a positive libration tips the Moon.

The Sun on the Moon

  1. As a library user, I want the subsolar point in selenographic coordinates at an instant, so that I know where on the Moon the Sun is overhead.
  2. As a library user, I want the colongitude at an instant, so that I know where the morning terminator is and which craters are catching the sunrise.
  3. As an observer, I want the colongitude to agree with the libration computed for the same instant, so that the two can be combined on one map.

Robustness

  1. As a library user, I want every calculation to be a pure function with no allocation, no I/O and no global state, so that I can call it from anywhere, including concurrently.
  2. As a library user, I want a NaN or infinite instant to give NaN results rather than a panic or an error, so that bad input cannot crash my program.
  3. As a library user, I want finding a principal phase from a NaN or infinite instant to return NaN rather than panicking, so that the one place where a float becomes an integer cannot crash my program.

Documentation and trust

  1. As a library user, I want every public declaration to say what units, time scale and reference frame its values are in, so that I never have to guess.
  2. As a library user, I want every calculation's documentation to cite the chapter and equation of Meeus it implements, so that I can check it against the book.
  3. As a library user, I want the documented accuracy of each calculation backed by tests against JPL Horizons, so that I can trust the numbers, not just the transcription.
  4. As a maintainer, I want the Horizons comparison data checked in together with the query that produced it, so that anyone can regenerate and audit it.
  5. As a maintainer, I want the sources used listed in the README's references cited, so that readers know where the numbers come from.

Implementation Decisions

  • The theory is Meeus, Astronomical Algorithms, 2nd ed. (ADR 0001). Julian Day and calendars from chapter 7, coordinate transforms from chapter 13, nutation and obliquity from chapter 22 (the full 63-term IAU 1980 table), the Sun from chapter 25 at its low accuracy (about 0.01°), the Moon's position from chapter 47 (truncated ELP-2000/82), phase at an instant from chapter 48 (the exact form, from the positions of the Moon and the Sun), principal phases from chapter 49, and libration, the position angle of the axis and the subsolar point from chapter 53.
  • ΔT comes from the Espenak–Meeus 2006 polynomials, which span −1999 to +3000, with their documented extrapolation beyond that. A caller can override it.
  • Namespaces: astro.time (the instant, the time scale, calendars, ΔT), astro.Angle, astro.coord (ecliptic and equatorial coordinates and the conversion between them), astro.earth (nutation, obliquity), astro.sun (the apparent position), and astro.moon holding position, phase and libration. The time and angle types sit at the top because every later body will use them, and each body gets a namespace of its own.
  • The instant is our own type, not zig-datetime's (ADR 0002): a Julian Day as an f64 and a time scale, TT or UT. UTC is counted as UT; the difference is under 0.9 s, which moves the Moon about 0.5″. Calendar construction takes an explicit Julian or Gregorian choice. Every calculation accepts either scale and converts internally.
  • Angles are a small type holding radians, with degree and radian accessors and constructors from degrees, radians, and degrees–minutes–seconds. It costs nothing at run time. Internal arithmetic is plain f64 radians.
  • Floating point is f64 throughout, with no generic float parameter.
  • Results are named structs whose angular fields are angles and whose lengths carry their unit in the field name: the Moon's distance in kilometers, the Sun's in AU.
  • Positions are apparent: nutation and aberration applied, referred to the true equator and equinox of date. No geometric or J2000 variants are provided for now.
  • Normalization: longitudes, right ascensions, elongations and position angles in [0°, 360°); latitudes and declinations in [−90°, +90°]; libration in longitude and latitude, and the subsolar longitude, in (−180°, +180°]; phase angle in [0°, 180°]; illuminated fraction in [0, 1].
  • Sign conventions follow chapter 53 exactly: selenographic longitude positive towards Mare Crisium. The documentation says so.
  • Named phases are eight equal 45° sectors of elongation centered on 0°, 45°, …, 315°: new Moon, waxing crescent, first quarter, waxing gibbous, full Moon, waning gibbous, last quarter, waning crescent. "Last quarter" is the term used, never "third quarter".
  • Principal phases are exposed as "next of a kind after an instant", "previous of a kind before an instant", and an iterator over the principal phases between two instants. Meeus's lunation number k stays internal. Results are instants in TT.
  • Lunar age is the time since the previous new Moon as found by the principal-phase search, in days.
  • Purity: no allocation, no Io, no global state anywhere.
  • Non-finite input gives NaN results, never a panic or an error. The float-to-integer step in the principal-phase search checks its input before converting, because @intFromFloat panics on NaN and on values out of range.
  • Out-of-range dates are computed anyway, and the documentation says how the accuracy falls off.
  • Out of the theory's way: nothing on the public surface names the theory, so a more precise one can be put behind the same API later.

Testing Decisions

  • One seam: the public API of the astro module. Tests call only what a consumer can call. Internal helpers — series evaluation, the lunation number, angle reduction — are not tested directly; an error in any of them shows up as an error in a public result.
  • A good test checks external behavior against an independent source and states its tolerance and why. It never checks how a result was computed.
  • Worked examples: each of Meeus's worked examples for chapters 7, 13, 22, 25, 47, 48, 49 and 53 becomes a test that reproduces the book's printed values to the precision printed, and cites the example number beside it.
  • Reference tables: values from JPL Horizons spanning 1900–2100, covering the Moon's and the Sun's apparent positions, the illuminated fraction, the libration and the subsolar point, are checked in as a data file with the query that produced them. Each quantity is compared with a stated tolerance consistent with the documented accuracy. Principal-phase instants are checked against a published table of phases, to within a stated number of minutes.
  • Properties and fuzzing: for any f64 Julian Day, including NaN, ±∞ and extreme finite values, no calculation panics; every finite result lies in its documented range; and non-finite input gives NaN. Further properties: the next principal phase of a kind is after the instant and the previous one is before it; consecutive principal phases go new → first quarter → full → last quarter; lunar age is never negative and is less than about 29.9 days; the illuminated fraction agrees with the phase angle. The same targets run under zig build test on fixed seeds and under zig build fuzz --fuzz. The test binary is compiled with LLVM so that the fuzzer and kcov get coverage.
  • Prior art: there are no tests in the codebase yet besides the module's empty test block. zig-ftp's layout, with fuzz targets that are also ordinary tests, is the pattern to follow.

Out of Scope

  • Observers. Topocentric libration, the topocentric position of the Moon, parallax, sidereal time, refraction, altitude and azimuth, and rising and setting. These need an observer's location and will be designed separately, probably on zig-geo's Position.
  • More precise theories. Full ELP/MPP02, the JPL DE ephemerides and VSOP87 for the Sun.
  • Geometric and J2000 positions.
  • UT1 as distinct from UTC, and IERS bulletins.
  • Calendar-day phase naming, such as "today is Full Moon", which belongs to a calendar built on the principal-phase instants.
  • Eclipses, apsides, nodes, declination extremes and occultations.
  • The planets and other natural and artificial satellites.
  • A zig-datetime adapter. It can be added later as a thin layer on the instant type.

Further Notes

  • The vocabulary is defined in GLOSSARY.md, and the two decisions behind the theory and the instant type are ADRs 0001 and 0002.
  • The sources — Astronomical Algorithms, the Espenak–Meeus ΔT polynomials and JPL Horizons — go into the project's reference collection and the README's references cited as they are first used.
  • The library's dependency order is time and angle → coordinates → the Earth → the Sun → the Moon's position → phase and libration.
## Problem Statement A Zig program that wants to know about the Moon — when the next full Moon is, how much of the disk is lit tonight, which way the Moon is tipped so that features near the limb can be seen — has nothing to call. It would have to transcribe a lunar theory from a book itself, along with the time-scale conversions, nutation, the Sun's position and the coordinate transforms that the theory leans on, and it would have to get every unit, sign convention and time scale right on its own. Those are exactly the places such code goes quietly wrong: degrees mistaken for radians, UT passed where TT was meant, libration reported with the opposite sign. ## Solution zig-astro provides geocentric calculations of the Moon's phase and libration, built on Jean Meeus's *Astronomical Algorithms* (2nd edition, 1998; see ADR 0001), together with the foundations they need, each of which is public and reusable by the bodies that come later: - an **instant** type that carries its **time scale**, with **ΔT** built in; - an angle type, so that degrees and radians cannot be confused; - conversion between ecliptic and equatorial coordinates; - the Earth's **nutation** and **obliquity**; - the Sun's **apparent position**; - the Moon's apparent position, its phase at an instant, its **principal phases**, its **named phase** and **lunar age**, its **libration** and the **position angle of the axis**, and the **subsolar point** and **colongitude**. Every calculation is a pure function of an instant. The vocabulary is the project's `GLOSSARY.md`. ## User Stories ### Instants and time scales 1. As a library user, I want to construct an instant from a Julian Day and a time scale, so that I can pass any moment I already have in astronomical form. 2. As a library user, I want to construct an instant from a Unix timestamp, so that I can ask about "now" or about a time from a computer clock. 3. As a library user, I want an instant from a Unix timestamp to be counted as UT, so that civil time is never mistaken for TT. 4. As a library user, I want to construct an instant from a calendar date and time of day, so that I can ask about a date written down by a person. 5. As a library user, I want to say whether a calendar date is Julian or Gregorian, so that a historical date from a country that switched calendars late is read correctly. 6. As a library user, I want no automatic switch between the Julian and Gregorian calendars at 1582 October 15, so that the library never guesses which calendar a date was written in. 7. As a library user, I want to read an instant back as a calendar date in either calendar, so that I can show a computed instant to a person. 8. As a library user, I want to read an instant back as a Unix timestamp, so that I can hand a computed instant to the rest of my program. 9. As a library user, I want to convert an instant between TT and UT, so that I can work in whichever scale my data is in. 10. As a library user, I want ΔT supplied by a built-in model, so that I do not have to look it up to work in civil time. 11. As a library user, I want to be able to supply my own ΔT, so that I can use a measured or more recent value than the model's. 12. As a library user, I want to ask for the model's ΔT at an instant, so that I can see what correction is being applied. 13. As a library user, I want every calculation to accept an instant in either time scale, so that I never have to convert before calling. 14. As a library user, I want instants far outside the ΔT model's range still to be accepted, so that a calculation for the year 3500 returns an answer of documented, reduced accuracy rather than an error. ### Angles and coordinates 15. As a library user, I want results that are angles to come back as an angle type, so that I cannot read degrees as radians or the other way round. 16. As a library user, I want to read an angle in degrees or in radians, so that I can use whichever my program needs. 17. As a library user, I want to construct an angle from degrees, from radians, or from degrees, minutes and seconds, so that I can enter values from any source. 18. As a library user, I want to convert ecliptic coordinates to equatorial coordinates and back for a given obliquity, so that I can move between the frames the theories and the observers use. 19. As a library user, I want longitudes, right ascensions, elongations and position angles normalized to [0°, 360°), so that results compare and display consistently. 20. As a library user, I want latitudes and declinations in [−90°, +90°], so that results are always in their natural range. ### The Earth 21. As a library user, I want the nutation in longitude and in obliquity at an instant, so that I can turn mean positions into apparent ones. 22. As a library user, I want the mean and true obliquity at an instant, so that I can convert between ecliptic and equatorial coordinates of date. 23. As a library user, I want nutation computed from the full IAU 1980 series, so that it never limits the accuracy of anything built on it. ### The Sun 24. As a library user, I want the Sun's apparent geocentric ecliptic longitude and latitude at an instant, so that I can compute anything that depends on where the Sun is. 25. As a library user, I want the Sun's distance from the Earth in AU, so that I can scale anything that depends on it. 26. As a library user, I want the Sun's apparent right ascension and declination, so that I can work in equatorial coordinates. 27. As a library user, I want the Sun's position documented as accurate to about 0.01°, so that I know what it is and is not good enough for. ### The Moon's position 28. As a library user, I want the Moon's apparent geocentric ecliptic longitude and latitude at an instant, so that I can compute anything that depends on where the Moon is. 29. As a library user, I want the Moon's distance from the Earth's center in kilometers, so that I can compute its apparent size or parallax. 30. As a library user, I want the Moon's apparent right ascension and declination, so that I can work in equatorial coordinates. 31. As a library user, I want the Moon's position documented as accurate to about 10″ in longitude, and how that degrades over the centuries, so that I know when I would need a more precise theory. ### Phase at an instant 32. As a library user, I want the Moon's elongation at an instant, so that I know how far it is from the Sun in the sky. 33. As a library user, I want the Moon's phase angle at an instant, so that I can compute its brightness or its appearance. 34. As a library user, I want the Moon's illuminated fraction at an instant, so that I can say how much of the disk is lit. 35. As a library user, I want the position angle of the Moon's bright limb at an instant, so that I can draw the lit side facing the right way. 36. As a library user, I want to know whether the Moon is waxing or waning at an instant, so that I can tell the evening crescent from the morning one. 37. As a library user, I want the Moon's named phase at an instant, so that I can show a person a familiar word such as "waxing gibbous". 38. As a library user, I want the named phases defined as eight equal 45° sectors of elongation, so that the name depends only on the sky and never on a timezone or a calendar day. 39. As a library user, I want the phase angle in [0°, 180°] and the illuminated fraction in [0, 1], so that the results are always in their natural ranges. ### Principal phases and lunar age 40. As a library user, I want the instant of the next principal phase of a given kind after an instant, so that I can ask "when is the next full Moon?". 41. As a library user, I want the instant of the previous principal phase of a given kind before an instant, so that I can ask "when was the last new Moon?". 42. As a library user, I want to iterate over every principal phase between two instants in order, so that I can build a calendar of phases for a month or a year. 43. As a library user, I want each principal phase reported with its kind and its instant in TT, so that I can convert it to whatever time I want to display. 44. As a library user, I want principal phases found by a lunation count that I never have to see, so that I do not have to learn Meeus's numbering or the Brown lunation number. 45. As a library user, I want the lunar age at an instant, measured from the actual previous new Moon, so that it agrees exactly with the previous principal phase. 46. As a calendar author, I want to name the day a principal phase falls on myself, from its instant and my own timezone, so that the library does not decide what day it is for me. ### Libration 47. As a library user, I want the Moon's optical libration in longitude and latitude at an instant, so that I know which parts of the limb are turned towards the Earth. 48. As a library user, I want the physical libration in longitude and latitude at an instant, so that I have the complete libration, not only its geometric part. 49. As a library user, I want the total libration, so that I do not have to add the two parts myself. 50. As a library user, I want the position angle of the Moon's axis at an instant, so that I can orient a map of the Moon to the sky. 51. As a library user, I want libration in longitude and latitude reported in (−180°, +180°], so that a small tilt reads as −0.8° rather than 359.2°. 52. As a library user, I want selenographic longitudes positive towards Mare Crisium, as in Meeus and the IAU convention, and stated in the documentation, so that I know which way a positive libration tips the Moon. ### The Sun on the Moon 53. As a library user, I want the subsolar point in selenographic coordinates at an instant, so that I know where on the Moon the Sun is overhead. 54. As a library user, I want the colongitude at an instant, so that I know where the morning terminator is and which craters are catching the sunrise. 55. As an observer, I want the colongitude to agree with the libration computed for the same instant, so that the two can be combined on one map. ### Robustness 56. As a library user, I want every calculation to be a pure function with no allocation, no I/O and no global state, so that I can call it from anywhere, including concurrently. 57. As a library user, I want a NaN or infinite instant to give NaN results rather than a panic or an error, so that bad input cannot crash my program. 58. As a library user, I want finding a principal phase from a NaN or infinite instant to return NaN rather than panicking, so that the one place where a float becomes an integer cannot crash my program. ### Documentation and trust 59. As a library user, I want every public declaration to say what units, time scale and reference frame its values are in, so that I never have to guess. 60. As a library user, I want every calculation's documentation to cite the chapter and equation of Meeus it implements, so that I can check it against the book. 61. As a library user, I want the documented accuracy of each calculation backed by tests against JPL Horizons, so that I can trust the numbers, not just the transcription. 62. As a maintainer, I want the Horizons comparison data checked in together with the query that produced it, so that anyone can regenerate and audit it. 63. As a maintainer, I want the sources used listed in the README's references cited, so that readers know where the numbers come from. ## Implementation Decisions - **The theory is Meeus, *Astronomical Algorithms*, 2nd ed.** (ADR 0001). Julian Day and calendars from chapter 7, coordinate transforms from chapter 13, nutation and obliquity from chapter 22 (the full 63-term IAU 1980 table), the Sun from chapter 25 at its low accuracy (about 0.01°), the Moon's position from chapter 47 (truncated ELP-2000/82), phase at an instant from chapter 48 (the exact form, from the positions of the Moon and the Sun), principal phases from chapter 49, and libration, the position angle of the axis and the subsolar point from chapter 53. - **ΔT** comes from the Espenak–Meeus 2006 polynomials, which span −1999 to +3000, with their documented extrapolation beyond that. A caller can override it. - **Namespaces:** `astro.time` (the instant, the time scale, calendars, ΔT), `astro.Angle`, `astro.coord` (ecliptic and equatorial coordinates and the conversion between them), `astro.earth` (nutation, obliquity), `astro.sun` (the apparent position), and `astro.moon` holding `position`, `phase` and `libration`. The time and angle types sit at the top because every later body will use them, and each body gets a namespace of its own. - **The instant** is our own type, not zig-datetime's (ADR 0002): a Julian Day as an `f64` and a time scale, TT or UT. UTC is counted as UT; the difference is under 0.9 s, which moves the Moon about 0.5″. Calendar construction takes an explicit Julian or Gregorian choice. Every calculation accepts either scale and converts internally. - **Angles** are a small type holding radians, with degree and radian accessors and constructors from degrees, radians, and degrees–minutes–seconds. It costs nothing at run time. Internal arithmetic is plain `f64` radians. - **Floating point** is `f64` throughout, with no generic float parameter. - **Results** are named structs whose angular fields are angles and whose lengths carry their unit in the field name: the Moon's distance in kilometers, the Sun's in AU. - **Positions are apparent**: nutation and aberration applied, referred to the true equator and equinox of date. No geometric or J2000 variants are provided for now. - **Normalization:** longitudes, right ascensions, elongations and position angles in [0°, 360°); latitudes and declinations in [−90°, +90°]; libration in longitude and latitude, and the subsolar longitude, in (−180°, +180°]; phase angle in [0°, 180°]; illuminated fraction in [0, 1]. - **Sign conventions** follow chapter 53 exactly: selenographic longitude positive towards Mare Crisium. The documentation says so. - **Named phases** are eight equal 45° sectors of elongation centered on 0°, 45°, …, 315°: new Moon, waxing crescent, first quarter, waxing gibbous, full Moon, waning gibbous, last quarter, waning crescent. "Last quarter" is the term used, never "third quarter". - **Principal phases** are exposed as "next of a kind after an instant", "previous of a kind before an instant", and an iterator over the principal phases between two instants. Meeus's lunation number *k* stays internal. Results are instants in TT. - **Lunar age** is the time since the previous new Moon as found by the principal-phase search, in days. - **Purity:** no allocation, no `Io`, no global state anywhere. - **Non-finite input** gives NaN results, never a panic or an error. The float-to-integer step in the principal-phase search checks its input before converting, because `@intFromFloat` panics on NaN and on values out of range. - **Out-of-range dates** are computed anyway, and the documentation says how the accuracy falls off. - **Out of the theory's way:** nothing on the public surface names the theory, so a more precise one can be put behind the same API later. ## Testing Decisions - **One seam: the public API of the `astro` module.** Tests call only what a consumer can call. Internal helpers — series evaluation, the lunation number, angle reduction — are not tested directly; an error in any of them shows up as an error in a public result. - **A good test** checks external behavior against an independent source and states its tolerance and why. It never checks how a result was computed. - **Worked examples:** each of Meeus's worked examples for chapters 7, 13, 22, 25, 47, 48, 49 and 53 becomes a test that reproduces the book's printed values to the precision printed, and cites the example number beside it. - **Reference tables:** values from JPL Horizons spanning 1900–2100, covering the Moon's and the Sun's apparent positions, the illuminated fraction, the libration and the subsolar point, are checked in as a data file with the query that produced them. Each quantity is compared with a stated tolerance consistent with the documented accuracy. Principal-phase instants are checked against a published table of phases, to within a stated number of minutes. - **Properties and fuzzing:** for any `f64` Julian Day, including NaN, ±∞ and extreme finite values, no calculation panics; every finite result lies in its documented range; and non-finite input gives NaN. Further properties: the next principal phase of a kind is after the instant and the previous one is before it; consecutive principal phases go new → first quarter → full → last quarter; lunar age is never negative and is less than about 29.9 days; the illuminated fraction agrees with the phase angle. The same targets run under `zig build test` on fixed seeds and under `zig build fuzz --fuzz`. The test binary is compiled with LLVM so that the fuzzer and kcov get coverage. - **Prior art:** there are no tests in the codebase yet besides the module's empty test block. zig-ftp's layout, with fuzz targets that are also ordinary tests, is the pattern to follow. ## Out of Scope - **Observers.** Topocentric libration, the topocentric position of the Moon, parallax, sidereal time, refraction, altitude and azimuth, and rising and setting. These need an observer's location and will be designed separately, probably on zig-geo's `Position`. - **More precise theories.** Full ELP/MPP02, the JPL DE ephemerides and VSOP87 for the Sun. - **Geometric and J2000 positions.** - **UT1 as distinct from UTC**, and IERS bulletins. - **Calendar-day phase naming**, such as "today is Full Moon", which belongs to a calendar built on the principal-phase instants. - **Eclipses, apsides, nodes, declination extremes and occultations.** - **The planets** and other natural and artificial satellites. - **A zig-datetime adapter.** It can be added later as a thin layer on the instant type. ## Further Notes - The vocabulary is defined in `GLOSSARY.md`, and the two decisions behind the theory and the instant type are ADRs 0001 and 0002. - The sources — *Astronomical Algorithms*, the Espenak–Meeus ΔT polynomials and JPL Horizons — go into the project's reference collection and the README's references cited as they are first used. - The library's dependency order is time and angle → coordinates → the Earth → the Sun → the Moon's position → phase and libration.
Author
Owner

Every ticket the spec was broken into is done: #2 through #9.

Every ticket the spec was broken into is done: #2 through #9.
jeff closed this issue 2026-10-23 00:25:31 -05:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jeff/zig-astro#1
No description provided.