Who makes
Starlink
dodge?
SpaceX reported that Starlink satellites moved out of the way of other objects 355,848 times between June 2025 and May 2026 (as reported by Space.com). Screening for close approaches is free now. Who causes them is not published. DODGE screens every Starlink satellite against everything else in its altitude band once a day, from public CelesTrak data, names the owner of the other object, and signs the result. Live: dodge.gautamkhosla.com. Code: dodge, MIT. Data: CC BY 4.0.
SEC. 01
Screening is free. Attribution is not.
SpaceX's semiannual reports to the FCC put Starlink's avoidance maneuvers at 148,696 from June to November 2025 and 207,152 from December 2025 to May 2026: 355,848 in a year, about 40 per satellite. Those figures come from Space.com's report on the filings; I have not reopened the filings themselves. Finding close approaches is no longer the hard part. SpaceX's Stargaze, the US government's TraCSS and Space-Track all screen for them.
What nobody publishes is the other half: which objects and operators cause the risk, who shares where their satellites are, and who leaves spent hardware behind. DODGE is that record, rebuilt every day from public data by code anyone can read and rerun.
It measures exposure and transparency, not blame. Most passes involve small satellites without propulsion or old hardware, which sit where physics puts them.
SEC. 02
The orbit history nobody keeps
CelesTrak publishes the current orbit of every tracked object and keeps only the latest one. There is no public history to download, so DODGE records its own, starting on 6 October 2026. A GitHub Actions job pulls the public element sets for active satellites, analyst objects, recent launches, all debris and all rocket bodies, plus the satellite catalog. It also pulls the ephemerides that operators publish themselves through CelesTrak's SupGP: Starlink and ten other fleets, including OneWeb, Kuiper, Planet and Iridium.
Operator ephemerides beat radar-based sets for the same object, so they win whenever both exist. DODGE does not use Space-Track data at all. Everything it screens is something a reader can download from the same public source.
SEC. 03
The screen
Every remaining object is propagated with SGP4, through Brandon Rhodes' wrapper of David Vallado's reference code, for 24 hours on a 10 second grid: 8,640 steps. At each step the positions go into 100 km cubes. For each Starlink, DODGE checks its own cube and the 26 around it and keeps every pair closer than 100 km. A unit test proves this finds exactly the pairs a full distance matrix finds.
Inside a step, closest approach comes from straight-line relative motion. That is good enough because two objects 100 km apart in low orbit feel almost the same gravity: the relative acceleration is about 0.12 m/s², which bends the path by about 1.5 m over five seconds. Any pass estimated under 10 km is propagated again with SGP4 at 50 ms steps, and passes that refine to under 5 km are kept.
One early mistake was which orbit to use. SupGP files can hold several fits for the same object, some dated ahead, and the first version took the newest one. The newest fit is not the right fit for a screen that starts now. The screen now takes the element set whose epoch is closest to the screen start (ed723fe), and that decision is written into the changelog because it can move published numbers.
SEC. 04
Does it miss anything?
A fast screen that quietly skips passes would still produce a convincing table, so the first check had to be something that does not trust the shortcut. On 6 October I took a random sample of real recorded objects, 1,500 Starlink and 1,500 others, and found every pass under 5 km over three hours by brute force: every pair, every second. Then I ran the fast screen on the same sample.
| Check | Judge | Result |
|---|---|---|
| Recall on real data | Brute force, every pair every second | 80 of 80 found, 0 missed |
| Spatial hash | Full distance matrix, points on cell edges | Identical pairs |
| Crafted 53° vs 97.5° crossing | 20 ms brute force around the reported time | Found; line estimate within 200 m of SGP4 |
The spatial hash test and the crafted crossing live in the engine's unit tests, which run in CI before every daily screen; the screen does not publish if they fail. The recall check is different, and SEC. 08 says how.
SEC. 05
Physics and SpaceX as outside judges
Recall says the screen finds what is in its input. It says nothing about whether the totals are plausible, so I checked them against two things the code cannot influence.
A kinetic gas estimate. On 6 October the screen kept about 4,500 non-Starlink objects in the 160 to 600 km band, which holds about 2.4 × 10¹¹ km³. A 5 km miss radius is a cross-section of 78.5 km², and closing speeds average about 10 km/s. That predicts about 600 passes under 5 km per hour. The screen found about 12,300 a day, which is 512 an hour.
SpaceX's own count. About 430 passes under 1 km a day is roughly 157,000 a year, against 355,848 maneuvers. SpaceX maneuvers at a 3 in 10 million collision probability, which is far more cautious than a fixed kilometre and covers wider passes with large uncertainty. Same order of magnitude is all this check claims, and all it can.
SEC. 06
What the first day said
The first screen started on 6 October 2026 at 22:56 UTC and covered the next 24 hours: 11,143 Starlink satellites against 4,502 other objects crossing their altitudes, 12,290 predicted passes under 5 km and 431 under 1 km. The closest was 50 m, between STARLINK-30376 and a Chinese CZ-2D rocket body, closing at 13.9 km/s.
Per object, Chinese and US hardware come close at the same rate: 2.77 and 2.67 passes a day.
Objects with no registered owner come close most often, at 6.48 passes per object. Many are recent rideshare deployments still waiting to be identified, including some from SpaceX's own Transporter missions. About one pass in six involves a spent rocket stage or debris, hardware nobody can steer.
Eleven days later the totals are similar. As of the screen of 17 October: 11,156 Starlink, 4,572 others, 12,591 passes under 5 km, 464 under 1 km.
Read the closest pass with care. Public element sets are typically good to about a kilometre near epoch. A single predicted pass under a few hundred metres means close, not a measured 50 m. Totals and per-object rates are robust; single passes are indicative.
SEC. 07
Sealed, signed, and refusing to seal today
A public record is only worth something if it cannot be quietly rewritten. Every day's files
get a SHA-256 manifest that names the exact hash of the previous day's manifest, and a Sigstore
attestation from the repository's own screen workflow, recorded in the public Rekor log. Every
daily release also carries SLSA provenance. A reader checks the hashes and the chain with a
Python script that uses no network and no dependencies, and the signatures with
gh attestation verify.
The guard I care most about is a refusal. The screen will not seal today or a future day, and it will never write into a release that is already sealed (3a9d097). A day that is still being recorded cannot be sealed without freezing a partial set of files, and a sealed day that can still change is not sealed. The parsers for incoming orbit data and the verifier itself are fuzzed with ClusterFuzzLite and Atheris, because they are the first code to touch input from outside.
SEC. 08
What broke, and what I would change
The recorder died on a CelesTrak outage. The scheduled run at 06:07 UTC on 7 October hit a CelesTrak timeout, the recorder raised, and the publish step then tried to upload a release with no files in it. Downloads now retry timeouts, connection errors and server errors at 20, 60 and 120 seconds; a run that still gets nothing is logged as a miss instead of crashing, and an empty upload is skipped with a warning. A real crash still fails the job (PR #1, with tests for each case).
CodeQL found files left open, unused imports and a redundant assignment (e37071e). Small, but it was in code that runs unattended every day.
The limits are in the data, not the code. Public element sets are good to about a kilometre. The screen does not model maneuvers after an element set's epoch, and Starlink maneuvers often, so some predicted passes will never happen. Objects without public element sets cannot be screened at all. DODGE has no covariance, so it cannot compute a collision probability and must not be used for collision avoidance.
What I would change: the 80 of 80 is one sample, on one day, over three hours. It is the strongest number on this page and it has a single row behind it. The recall check should run against brute force on a fresh sample every day, with the result sealed into that day's release next to the passes it vouches for.
SEC. 09
Follow-up: can public data see the burns?
DODGE's sealed history made a second question testable. Maneuver Ledger reads DODGE's sealed releases and looks for the step an avoidance burn leaves in the size of a satellite's orbit. SpaceX filed 207,152 avoidance maneuvers with the FCC for December 2025 to May 2026. Public element sets cannot rebuild them: the 207,152 was not reproduced.
In the cleanest test, burns of 16 cm/s or more are at most 2.8 per satellite-year, 8% of the lower filed rate.
The check I registered in advance failed. The public GP element sets and CelesTrak's SupGP sets agreed on 29% of steps beyond chance, against a line of 70%. So the result is a detection limit, not a count.