Syzygy
Syzygy is a solar system explorer and eclipse finder that runs entirely in your browser. It knows every solar and lunar eclipse from 1900 to 2100, draws the Moon’s shadow racing across the Earth, and works out which ones you’ll be able to see from where you are.
EILAID built it overnight, from an empty repo, while I sat at the gates. Part 2 has the short version; this is the long one. The picture’s live, by the way: click it and you’re in the app.


What I asked for
The brief is a cut-down version of one of EILAID’s own benchmarks. The full one has the agents fit their own ephemeris to NASA’s JPL data, which the first decision record reckons is “weeks of work, plus large data files”. This one had to land in one night, so it leans on astronomy-engine, an open source library that’s good to about an arcminute, and spends the effort on the experience and the eclipse maths.
It asks for “beautiful, correct and fast”. Correct means agreeing with NASA, with a check behind every acceptance criterion. Beautiful means belonging on this site, or in the style guide’s words, “an engraved scientific orrery that happens to be alive”. Fast means a 350 KB budget for the first load, which it meets with 298 KB. The stack (Vite, React 19, strict TypeScript, three.js and zustand) was fixed up front, and it all builds to static files, so the app is just a folder on this site.
Three views
Keys 1, 2 and 3 flip between the views, and each picture below opens the app at that view and moment.


15 May 1946, readable scale. Distances are squashed so everything fits; the labels give the real ones.open this view (system) ↗


8 April 2024, greatest eclipse, readable scale. The Moon's shadow reaches the Earth.open this view (earth-moon) ↗


12 August 2045, greatest eclipse. A 259 km path of totality from the United States to Brazil.open this view (globe) ↗
The system view is the orrery. At the readable scale the distances are logarithmic, so the inner planets get some room with Neptune still on screen. Switch to true scale and everything slides to where it really is. The Earth–Moon view shows sunlight as parallel rays and both bodies’ shadow cones. The globe is 28,823 dots of land lit by the real Sun, with the path of totality worked out in a background thread.
Underneath them all is a timeline from 1900 to 2100 that zooms from 200 years down to six hours. Click an eclipse and it flies you there, arriving three minutes before the peak. The address bar keeps up as you go, so any moment can be shared as a link.
The night
At 22:27 UT on 6 October I sent EILAID one message: build Syzygy from the brief and the style guide, and “treat it as an epic for a new app from an empty repo”. The architect took about 18 minutes to split it into 17 items and seven decision records, and the scaffold merged half an hour after the first agent started.
From there EILAID ran three agents at once, each on its own item in its own git worktree. I bumped that to four once they’d stopped fighting over a port, then back down when the box ran out of breath. Twenty items merged in the two hours from 23:00. Then the flaky tests turned up.
A timeline of the first night, from 22:27 to 06:12 UT on 6 to 7 October 2026. 43 items merged: 18 features, 11 bugs and 14 chores. Merges per hour, starting with the 22:00 hour: 1, 10, 10, 4, 2, 6, 3, 4, 3. The scaffold merged at 22:56. The first flaky test blocked a merge at 01:22. From 02:36 to 05:31, 7 of the 14 merges were fixes for flaky tests. The last merge was at 06:12. The full list follows the chart.
all 43 merges
- SYZ-2choreScaffold: Vite + React 19 + TS + R3F app at /syzygy/
- SYZ-4featureLand-mask data: the dotted globe's land points and loader
- SYZ-5featureApp state: store, clock, URL state, catalogue loader
- SYZ-19chorePin Node 22 and keep Node types out of browser code
- SYZ-20bugDate parser rejects years 0000-0099
- SYZ-3featureAstronomy core: time, positions, frames, shadow geometry
- SYZ-21choreTest that setTime rejects the year 0024
- SYZ-6featureDesign system and HUD shell: tokens, both themes, Geist Mono
- SYZ-7featureEclipse catalogue 1900-2100, checked against NASA's canon
- SYZ-23choreAGENTS.md: test reference values must cite their source
- SYZ-8featurePath of totality and local circumstances, checked against NASA
- SYZ-13featureTimeline ruler 1900-2100: zoom, drag, playhead, eclipse glyphs
- SYZ-31choree2e port derived per worktree so parallel runs don't collide
- SYZ-14featurePlayback controls, keyboard shortcuts, time travel to eclipses
- SYZ-15featureEclipse list: next and previous eclipses, filters, countdowns
- SYZ-9featureScene core: canvas, camera rig and flights, dotted globe, labels
- SYZ-27choreADR 0007 amended: NASA's umbral Moon radius k = 0.272281
- SYZ-35bugControls fix: 'now' stuck after pause, date field rewound time
- SYZ-24choreMobile sheet peek shows the next eclipse date
- SYZ-25choreFour borderline eclipse types reconciled with NASA's canon
- SYZ-16featureVisible from you: local eclipses, Sun coverage, .ics export
- SYZ-30featureTimeline: keyboard access to eclipse glyphs
- SYZ-17featureCallout labels without overlap, and the body info card
- SYZ-12featureGlobe view: dotted Earth, racing umbra, path of totality
- SYZ-26choreHUD polish: mobile top bar, skip link, sheet tap, landmarks
- SYZ-48bugUnit tests: wall-clock asserts replaced by work bounds
- SYZ-40bugFlaky: e2e-port config test timed out under load
- SYZ-66bugLocal eclipse search fell into a multi-year scan at 48N 30E
- SYZ-42bugGlobe shadow honours the axis offset; umbra sits on the track
- SYZ-59choreFlaky: check-budgets CLI tests timed out spawning tsx
- SYZ-61choreTests compared app time to the real clock; now fake timers
- SYZ-33featureTimeline: lunar eclipses get their own lane at year zoom
- SYZ-56bugGlobe camera re-aims when the selected eclipse changes
- SYZ-64choreFlaky: timeline ruler tests hit the 5 s timeout under load
- SYZ-11featureEarth-Moon view: Sun direction, Moon orbit, phase, shadow cones
- SYZ-10featureSystem view: engraved orrery, readable/true scale, click to focus
- SYZ-70choreCap test parallelism under CI so verifications don't starve
- SYZ-68bugMobile timeline ruler too short: year labels were clipped
- SYZ-67choreFlaky: label and theme tests timed out with 4 suites running
- SYZ-28bugPath-of-totality follow-ups: test place, docs, width at the limb
- SYZ-60featureGlobe polish: dark-theme shadow, no fake umbra, callouts on screen
- SYZ-86bugLinks with both ?t and ?e no longer lose the URL time
- SYZ-85bugFlaky: Visible-from-you real-data test timed out under load
That was the first night. Polishing took most of the next day, and these are the numbers for the whole run, from 22:27 on the 6th to about 15:00 the next afternoon:
On the first night, 85 briefs went through gate 1 and I sent 6 back. At gate 2 I accepted 43 changes and sent 6 back. The agents asked the Oracle 49 questions: it answered 26 from the project, and the other 23 came to me, nearly all from shapers working out a brief. My favourite answer was about framing on a portrait phone: “Presets say what to frame, not where to stand.”
I wasn’t only at the gates, mind. I resolved 14 blockers, filed 15 items and edited the agents’ specs 11 times. I didn’t write any of the code, though. My only commits were five small changes to EILAID’s config, mostly how many agents could run at once.
It agrees with NASA
This is the part I cared about most, and each of these is a test:
- Eclipse types. All 913 eclipses from 1900 to 2100 (454 solar, 459 lunar) have the same type as in NASA’s Five Millennium Canon, and the test’s list of allowed exceptions is empty.
- Timings. Every peak is within 120 seconds of NASA’s. The worst is 14.6 seconds out.
- The 2024 path. The centre line stays within 0.3° of NASA’s (0.11° at worst at the same instant), and the path is 200.6 km wide where NASA says 197.5.
- From the ground. Maximum eclipse in Dallas in 2024 lands 1.9 seconds from NASA’s time, and in Nashville in 2017, 0.5 seconds from the US Naval Observatory’s (NASA has no table for Nashville).
- Planets. Within 0.05° of JPL Horizons at six dates across the two centuries.
The last few eclipse types were the fun bit. NASA draws the Moon’s shadow from a Moon 0.7545 km smaller than the real one (k = 0.272281, if you’re keeping score), and the catalogue did the same from the start. Four eclipses still disagreed: three lunar ones that are penumbral by a whisker, and 9 May 1948. Switching the Earth’s shadow to the rule NASA’s lunar canon uses fixed the three, and 1948 came round once the classifier used NASA’s 696,000 km Sun as well. One line in the decision records did a lot of work: “any failure is a real bug or a fixture problem, never a reason to loosen tolerances”.
The tests say where their numbers come from, too. A repo rule (SYZ-23, merged at 23:26) makes every reference value name its source, and the fixtures were pulled from NASA’s and JPL’s pages by script rather than typed in. My answer to the architect’s very first question was “Do not transcribe from memory.” The best catch here wasn’t mine, though. A reviewer noticed that the local timing tests used Wakefield, Massachusetts, while the app’s default is Wakefield, UK, which is where I actually live.
What I caught
Tests can prove the maths, but not whether it makes sense to a person. That’s what the gates are for:
- SYZ-16, visible from you. It listed eclipses whose maximum happens with the Sun below the horizon, “and quotes the coverage at max, which nobody in Wakefield can see.” Back it went, and now an eclipse can “set eclipsed” or “rise eclipsed”.
- SYZ-13, the timeline. Every lunar eclipse falls about 15 days from a solar one, so at year zoom the thinning always dropped it. “Blood moons should be visible at decade zoom.” They got their own lane (SYZ-33).
- SYZ-77, a camera brief. It routed flights over the pole, where “the camera roll-snaps at the pole. That’s the same kind of jump we’re fixing.” A wrong brief is the worst kind, because the tests then lock the bug in.
- SYZ-11, the Earth–Moon view. Correct, but “at true scale and edge-on, the cones are hairlines.” I accepted it with a follow-up to make it readable.
That follow-up gave me my favourite exchange of the night, and the catch was the agent’s. Drawing the Earth and Moon eight times bigger would put the Moon in the Earth’s shadow at almost every full moon, which the shaper said “could read as ‘eclipses happen every month’”. I said “Great catch” and took its option of drawing the cones only near a real eclipse. That landed the next day (SYZ-81): the readable scale draws both eight times bigger, and the cones only appear around a real eclipse.
The Builder’s spec picked up 35 lessons overnight, including this one from SYZ-16: “Before showing a computed figure, check the user can observe it from their place and time.”
Flaky tests
The first flaky test blocked a merge at 01:21. SYZ-30 had already landed on main when a test failed there: it asserted that a search step took under 50 ms, and with four agents running it took 130 to 149 ms. From 02:36 to 05:31, 7 of the 14 merges were fixes for flaky tests, and over the whole night 9 of the 43 items were.
The causes were dull. Parallel worktrees fought over port 4173. Tests timed things with the wall clock, and single tests did years of eclipse searching. Underneath it all, three or four test suites shared a 4-core box, and some of that was on me: for part of the night I had other agents building easter eggs for this site on the same machine.
I’d set the rule at 00:49, answering a shaper: “Flaky tests: never retry or skip to get green. Find and fix the cause, and prove it by running the file 20 times under parallel load.” So a brief that only raised a timeout (SYZ-40) went straight back. The night’s fixes are now rules in the repo:
- Each worktree gets its own port for browser tests.
- Unit tests count the work (engine searches, new moons examined) instead of timing it, and real timings live in
npm run perf. - No single test does unbounded work. A loop over slow cases becomes
it.each. - Under CI, vitest gets 2 workers and Playwright gets 1 (
CI=1 npm test).
Counting the work even caught a real bug (SYZ-66): at 48°N 30°E the local eclipse search quietly fell through to a multi-year scan. The loop wouldn’t accept a flaky test, so the dull fixes got done in the middle of the night rather than never.
What EILAID got wrong
That’s what building something real is for. It showed up a few things:
- Approval comments go nowhere. Only rejections reach the agents, so a fix I asked for when approving SYZ-44’s brief was still missing five and a half hours later.
- Duplicate tickets. One port problem got three tickets (SYZ-29, 31 and 32), and one of them was mine.
- No embeddings. On keyword search alone, the Oracle escalated questions I’d already answered in other words, once only ten minutes later, which is why part 1 makes such a fuss about embeddings.
- Merged but blocked. If the tests on main fail after a merge, the item is blocked with its code already in, and unblocking it logs a second merge that merges nothing (hence one count of 44 merges for 43 items).
- The learning loop needs a human. None of the Coach’s builder versions went live on its own, so I merged its lessons by hand, and the Builder’s spec grew from 11,615 characters to 20,438.
Where it ended up
Syzygy is live at /syzygy/. The second day went on polish: the readable Earth–Moon view, framing that works on every screen shape (landscape phones included), labels that stay out from under the eclipse panel, and a pile of test and performance fixes. In all, 94 items shipped and 39 were cancelled, most of them by me at the end, when I decided the rest could wait. Labels still sometimes sit on the Sun, which is the first thing I’d fix next.
If you only try one thing, open it at the 2024 eclipse and watch the shadow cross Mexico. For how EILAID works, part 2 has the tour, and part 1 is the idea behind it.
next · part 4Ponder →Chess against an engine written from scratch, which shows you how it thinks while it thinks. EILAID built it in 29 hours, engine and all, without a line of the app's code from me.