Retrospect
Your Last.fm history against the actual sky. Every song you’ve played, set against the real planets, moons, solar storms and eclipses of that night, then held to a standard astrology never asks for.
- Questions, the same for everyone
- 12corrected for asking 12
- Fake histories given a false yes
- 9.4%1,000 with no sky in them
- Plays in my history
- 500ksince October 2009, one account
- Hosting cost
- $0by design
Astrology never has to be right
TL;DR
“Mercury retrograde makes you revisit the past” is unfalsifiable as usually stated. It is not unfalsifiable in principle, though. You need two things: a record of what someone actually did, and a real calendar of when the sky did the thing. A Last.fm history is the first. An ephemeris is the second.

Every planetary window is computed, not looked up. Retrogrades, full moons and the signs the planets sit in come from planetary positions via astronomy-engine. Solar storms, flares and photos come from NASA, and asteroid flybys from JPL. Below the field, the landing page shows the goods on a made-up listener’s history: a sample song’s sky, a sample night, tonight, and a sample set of answers.

What if the answer is no?
TL;DR
The honest version of this app answers no most of the time. That is the whole premise, and it is also a product problem, because “we checked, nothing happened” is a terrible thing to show someone who just waited for half a million plays to sync.
So the first visit is paced like a story rather than dropped as a dashboard. Four screens: the scale of your history against the sky, your wildest night, the song with the strangest sky, and only then the answers. Every listening figure from here on is one real history, mine: 500,413 plays since October 2009, through 216 times Venus changed sign and 201 solar storms. Someone else’s numbers will be different, and that is the entire point of the thing.
Astrology is the question. Statistics is the answer. If the sky is innocent, the app has to be willing to say so, and still be worth the wait.
My wildest night, by the sky, was the total solar eclipse of April 8, 2024. I played 80 songs. A usual Monday is 79. The last screen sorts the answers to twelve fixed questions, explained below, into jars. Mine came back zero Yes, three Maybe, two Not clearly and seven No.

Tonight, every night, and the whole sky
TL;DR
Tonight opens on the sky right now and lines it up with your own answers: Venus is retrograde until November 13, and here is what Venus retrograde has looked like in your listening so far. Every night is a calendar of every night you’ve listened, filterable down to the storm nights, the flare nights, the eclipses and the full moons. Sky is a wheel of the planets with a timeline of all your years under it.



Anything you open, whether a song, a night, a planet or one of the questions, opens as a sheet with its own address. A link to ?q=fullmoon opens on that answer. A link that lands in a chat unfurls into a share card drawn from the answers already stored, never recomputed, so the card can’t say something the page doesn’t.
Compare puts two listeners’ answers to the same twelve questions side by side, and stops there. The old version named one of them the more sky-ruled listener. That ranked two people on results that were mostly chance, so there is no winner and no score.

Facts are free, claims are earned
TL;DR
One rule governs every line of copy. A pairing is a dated fact: I first played Twilight by Elliott Smith at 4:04 p.m. on August 1, 2012, under a full moon, with Mercury retrograde. I’ve played it 148 times since. That is all true, and none of it says the moon did anything.
Every pairing that touches a question links to it, in the same words every time: “Coincidence or pattern?” and which of the twelve has the answer, or needs more nights to give one. The fun stays, and the claim goes where it can be checked.

Twelve questions, asked the same way for everyone
TL;DR
Any large listening history shows some difference during retrograde, because any two arbitrary buckets of days differ. The question is whether the difference beats what chance produces on its own.
So every question is checked against fake skies, a circular permutation test. The real calendar of events is slid to 2,000 random start dates against the same listening, and the real result only counts when it beats most of them. Sliding matters more than it sounds: a plain reshuffle scrambles both the calendar and the listening, and hands back a version of chance that is easy to beat. Sliding keeps seasons, weekly rhythm and the streaks people listen in. The fake skies keep the shape of your actual life.
The random slides are fixed per listener, per question and per version of the engine, so the same history gets the same answer every time. They’re drawn as fractions of the history’s length, so a new day of plays shifts them only slightly instead of drawing a fresh set.
The twelve are fixed and the same for everyone. Each gets one word. Yes needs two things: it has to survive a correction for asking twelve questions at once (Benjamini-Hochberg, at 10%, for anyone checking), and chance alone has to produce a swing that big less than one time in twenty. Maybe is under one in ten, Not clearly is under about one in three, and anything weaker is No. Fewer than six events, or fewer than 500 plays inside them, is Too early. The size and direction of a swing go in the sentence, never in the word.

The one finding, asked again
TL;DR
The previous version of Retrospect didn’t have twelve questions. It had a sweep: five skies against five measures, 25 combinations, each with its own scramble test. On my history exactly one cleared the usual bar, a full moon keeping me up past midnight, at +33%. The app called it an iron grip.
The old write-up of it named the catch: run 25 tests at the usual bar and about one false positive is expected from chance alone, and one came back. It chose to label results by strength rather than correct for that. The grip meter measured how big a swing was. Whether it was real rested on a single test that knew nothing about the other 24.
The redesign asks the full moon once, in a fixed question, with the correction. On 4 October 2026 it answers Maybe. Across 208 full moons, about a 40% bigger share of my plays came after midnight, so the swing held up. The evidence didn’t: chance alone produces a swing that big about one time in eleven, and once the other eleven questions are allowed for, it isn’t close to the bar for a yes. The likely range runs from 6% smaller to 108% bigger, which includes no effect at all.

The method got better, and the one striking finding didn’t survive it.

A thousand listeners with no sky in them
TL;DR
The correction is only as good as its false alarms, so the repo tests it directly. npm run null-test builds 1,000 synthetic listening histories with no sky effect in them at all, runs all twelve questions on each, and counts how many get any Yes.
On the current code it was 94 of 1,000, or 9.4%, against a ceiling of 13%: the 10% the correction aims for, plus room for chance. Without the correction, 393 of those 1,000 histories, 39.3%, had at least one question that cleared the usual bar by chance. That is the failure the correction exists for: ask twelve questions of a sky that does nothing, and four histories in ten turn up something that looks real.
The full run is meant for any change to the engine or the answer rule. A quicker version, 100 smaller histories with a looser ceiling, runs in CI with every other test, and it also checks that the same histories would break that ceiling without the correction. Drop the correction and the build goes red, which is what “it can’t happen again” means here: not a promise, a check.
Your habits leave fingerprints anyway
TL;DR
The listening profile needs no astronomy at all, and it says so in its own subtitle: “Not the sky.” Archetypes, golden hour, the day you lean on, loudest month, pace and best streak, all computed from the same timestamps, all there whether or not the planets did anything.


Sleep sounds were the quiet threat to all of this. Rain, white noise and ASMR tracks run for hours unattended, and those plays add up to counts that can dwarf anything you actually chose that week. The previous version made filtering them a switch. Now they’re left out of every measure automatically, because a data-quality fix shouldn’t depend on someone finding the toggle.
A loading screen worth waiting through
TL;DR
The first sync of a large history takes minutes, because Last.fm is rate limited and the history is pulled page by page. That wait wasn’t going away, so it got a planetary system instead of a spinner. Every planet is a year of your history recovered so far: they spawn, orbit, occasionally collide and explode, while the count climbs underneath and the copy keeps you company (“Mercury has never once hurried.”).
The sync is built for short server calls. Each one pulls pages for eight seconds, four at a time with a quarter-second pause between batches, to stay polite to Last.fm. It saves once at the end, because every save rewrites the stored history. And it throws out duplicates when reading, because new plays arriving mid-download shift which songs land on which page.
Built to cost nothing, on purpose
TL;DR
Retrospect runs on Vercel’s free tier with Cloudflare R2 for storage. R2 was chosen specifically because it has zero egress fees: it charges nothing to serve data out, so a rush of visitors can’t generate a surprise bill, and Vercel Hobby pauses rather than charges when limits are hit. Two daily jobs do the housekeeping: one fills and refreshes NASA’s data, the other deletes any history nobody has looked at in 90 days. None of the NASA or JPL sources need a key.
Birth charts are the one thing that never touches a server. The chart is a sheet on the Sky view: Sun, Moon, Mercury, Venus and Mars at the minute you were born, and a rising sign if you add a place, worked out in the browser and saved only on that device. Birth date, time and place are a more sensitive payload than anything else the app handles.
