feat(lunar): add a solar/lunar/both mode to the detail view - #167
Open
daneden wants to merge 5 commits into
Open
feat(lunar): add a solar/lunar/both mode to the detail view#167daneden wants to merge 5 commits into
daneden wants to merge 5 commits into
Conversation
EclipseCalculator held a complete Meeus lunar and solar theory — chapter 47's truncated ELP-2000/82 series, topocentric parallax, nutation, sidereal time and ΔT — and every bit of it was private to that file. None of it is specific to eclipses, and none of it could be used for anything else. Move it to Ephemeris. EclipseCalculator keeps only the eclipse geometry: finding lunations whose shadow reaches the Earth, and what an observer at one place sees of it. The move is numerically inert. Counting every numeric literal across both files gives 937 before and 937 after; the only differences are that the lunation index went from being spelled out twice to once, and two new constants for the mean lunar radius and the synodic month. EclipseCalculatorTests is unchanged and is the real check. One constant deliberately does not become the default. The moon radius here was the umbral k = 0.272281, chosen so totality durations match NASA's, and it is the wrong figure for how large the moon looks. Ephemeris exposes the mean radius as moonRadiusKm and keeps the eclipse value as umbralMoonRadiusKm, so eclipse contacts still use it and nothing else picks it up by accident. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PR3DD68Nh3jRJ6H7z8fHn1
Adds LunarCalculator on top of the extracted Ephemeris, and a toolbar control that switches what the daily chart and rows are about. Moonrise and moonset are the only genuinely new astronomy. The sun's rise and set fall out of a single daily position; the moon moves about 13 degrees a day, so its altitude is sampled across the local day and the horizon crossings are bisected. Both are optional, and a missing one is ordinary rather than an error: the moon rises roughly 50 minutes later each day, so about once a lunation a calendar day has no moonrise, or no moonset, at every latitude. The standard altitude deserves a note. Meeus gives h0 = 0.7275*pi - 34' for moonrise, but that is for use with geocentric positions — the 0.7275*pi term exists to fold in parallax. Ephemeris returns topocentric positions, where parallax is already applied, so applying it again would move moonrise by the better part of two hours. What remains is refraction and the semidiameter, exactly as for the sun. The test that the full moon rises as the sun sets is there to catch precisely that mistake. Phases use a narrow window around each principal phase rather than eight equal 45-degree slices. Equal slices would label a moon that is 68% lit "first quarter", and a first quarter moon is half lit — anyone who looked up would see the app was wrong. Meeus's own worked example falls in that gap and now reads as waxing gibbous, which is what it is. Checked against the book where it can be: chapter 47 for position, 25 for the sun and 48 for illumination are now asserted directly rather than only through whether an eclipse landed in the right place. The rest is checked against physics — the full moon rising at sunset, the daily retardation of moonrise showing the shallow autumn angle, successive full moons a synodic month apart, and the phase search agreeing with the independently written eclipse search about when the 2026 new moon is. The sky gradient stays solar in every mode. It is driven by the sun's altitude, and keeping it as the backdrop is what makes the moon's path readable — you can see whether the moon is up in darkness or wasted in daylight. That also gives "both" a clean meaning: the sky is always the sun's, the mode picks which bodies are drawn on it. Phase glyphs are mirrored below the equator. The moon is lit from the same side for everyone, but southern observers see it the other way up. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PR3DD68Nh3jRJ6H7z8fHn1
Xcode Cloud could not open the project at all: "The project 'Solstice' is
damaged and cannot be opened due to a parse error." Both checks came back after
about 20 seconds against 5-12 minutes for a real build, which was the tell that
nothing had been compiled.
project.pbxproj is an OpenStep property list, where an unquoted token may only
contain [A-Za-z0-9_./]. A plus sign has to be quoted. The script that wired the
three new files in used one template for all of them, which is fine for
Ephemeris.swift and LunarCalculator.swift and unparseable for the one name with
a ++ in it. The repo's own NTSolar++.swift has always been quoted.
Checking brace and paren balance after editing the file, as I did, could never
have caught this — balance says nothing about token grammar. The check that
would have is:
grep -nE 'path = [^";]*[^A-Za-z0-9_./;][^";]*;' Solstice.xcodeproj/project.pbxproj
No Swift changed. The project never opened, so the build has said nothing yet
about whether the lunar code compiles; guessing at Swift fixes now would only
muddy the next run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PR3DD68Nh3jRJ6H7z8fHn1
…traction
Three call sites in EclipseCalculator still referred to date(fromJulianDay:)
unqualified after it moved to Ephemeris, so the module failed to build with
"Cannot find 'date' in scope".
The check I ran after the extraction looked for unqualified uses of the moved
symbols, but against a hand-written list — and `date` was not on it. Deriving
the list from Ephemeris itself instead finds these immediately, and finds
nothing else:
names = static funcs + static lets + structs declared in Ephemeris.swift
flag any use of those names in EclipseCalculator/LunarCalculator that is
not preceded by a dot and not defined locally
The remaining hits from that scan are argument labels — `after date: Date` and
`Ephemeris.deltaT(julianDay:)` — not references to the moved functions.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PR3DD68Nh3jRJ6H7z8fHn1
…he jank Feedback from the first build on device. The moon's path had no marker, so nothing showed where the moon actually is. It now gets the phase glyph — the same symbol the moon row uses, which carries information a plain dot would not. Positioned directly rather than through AlongSolarPath: that machinery exists to glide the sun's marker during a scrub, and given the animation problem below the moon should not carry it. The moon's line drops from markSize/2 to markSize/3 so it reads as clearly secondary to the sun's path. In lunar-only mode the sun's circle and its glow both go. Two separate things: the marker is hidden rather than skipped, because sunBelowHorizon also draws the below-horizon veil and applies the chart's mask, and those belong to the chart rather than to the sun. The glow goes by leaving sunPoint unset, which is what becomes the SkyGradient's sunAnchor — publishing it with no sun drawn left a bright highlight tracking an invisible marker. The sky keeps its day/night colouring either way, since that comes from date and horizonY. The lunar rows move into their own section. Interleaved with the sun's, the two bodies' times read as a single list. On the sluggishness: findMoon assigned through withAnimation, and its task is keyed on the day, so every day change during time travel animated the entire form. The phase glyph's contentTransition fired on the same data arrival. Both removed. The mode-switch animation stays — it runs only on a deliberate tap, and a section appearing with no transition is jarring. Worth noting why the eclipse section, which is keyed the same way, never caused this: its result is identical for months at a time, so the assignment re-assigns an equal value and SwiftUI does nothing. The moon changes every day. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PR3DD68Nh3jRJ6H7z8fHn1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #166 — based on
claude/solstice-solar-eclipse-n2k27cso the diff shows only the lunar work. Retarget tomainonce #166 merges.Adds a toolbar control that switches the detail view between the sun, the moon, and both.
Why this was cheaper than it looks
EclipseCalculatoralready held a complete Meeus lunar theory — chapter 47's truncated ELP-2000/82 series, topocentric parallax, nutation, sidereal time and ΔT — and every bit of it wasprivate. None of it is specific to eclipses. The first commit lifts it intoEphemeris; the second builds on it.The extraction is numerically inert, and checkably so: counting every numeric literal across both files gives 937 before and 937 after. The only differences are the lunation index going from spelled out twice to once, and two new constants.
EclipseCalculatorTestsis unchanged and is the real check.One constant deliberately does not become the default. The moon radius in
EclipseCalculatorwas the umbralk = 0.272281, chosen so totality durations match NASA's, and it is the wrong figure for how large the moon looks.Ephemerisexposes the mean radius asmoonRadiusKmand keeps the eclipse value asumbralMoonRadiusKm.The one piece of genuinely new astronomy
Moonrise and moonset. The sun's fall out of a single daily position; the moon moves ~13°/day, so altitude is sampled across the local day and the crossings bisected.
The trap worth knowing about: Meeus gives
h₀ = 0.7275·π − 34′for moonrise, but that is for geocentric positions — the0.7275·πterm exists to fold in parallax.Ephemerisreturns topocentric positions, where parallax is already applied, so using Meeus's figure would move moonrise by the better part of two hours. What remains is refraction and the semidiameter, as for the sun. The test that the full moon rises as the sun sets exists to catch exactly that.Both times are optional, and a missing one is ordinary rather than an error: about once a lunation a calendar day has no moonrise, or no moonset, at every latitude.
Phases
Eight equal 45° slices would label a moon that is 68% lit "first quarter" — a first quarter moon is half lit, and anyone who looked up would see the app was wrong. The four principal phases get a narrow window instead and the crescents and gibbous phases take everything between. Meeus's own worked example falls in that gap and now reads as waxing gibbous.
Glyphs are mirrored below the equator: the moon is lit from the same side for everyone, but southern observers see it the other way up.
Verification
Asserted against the book, which the extraction made possible for the first time — these were previously only checked indirectly, through whether an eclipse landed in the right place:
And against physics, where no published example exists:
Judgement calls
SkyRenderCachepattern rather than.task— the share card renders this chart throughImageRenderer, which never runs the view lifecycle, so anything task-driven would come out blank there.Not verified
No Swift toolchain in the session, so none of this has been compiled or run — the validation above is via a line-for-line Python port of the same algorithms. Worth a close look on first build:
moonrise,moonset,moonphase.*) render as blank space rather than failing the build if a name is wrong.SolsticeTestshas never executed in CI — both checks on feat(eclipses): surface upcoming solar eclipses and alert on major ones #166 are archive actions.Follow-up
PR 2 will add the phase calendar replacing the annual chart in lunar mode. Lunar eclipses are deliberately deferred.
Generated by Claude Code