KESSLER
Nobody gave me this problem. I went and took it. In 2007 China shot one of its own weather satellites with a missile. In 2009 a dead Russian communications satellite hit a live American one at 11.7 km/s. The pieces are still up there and they are catalogued, publicly, by object, by the hour. So I pulled 10,754 Starlink satellites and 2,644 tracked fragments from those two events, propagated all 13,398 of them through the next twenty-four hours with the reference SGP4 code, and asked one question: how close do they come.
STARLINK-34131 × COSMOS 2251 DEB — 221 metres, 12.22 km/s, 3.57 hours after 2026-08-13 00:00:00 UTC. That is a real prediction about two real objects, from public elements, on a laptop.
THE SCREEN
This part is not a simulation. It is SGP4 — the same propagator the tracking networks publish elements for — run over the real catalogue. 10,754 satellites × 2,644 fragments is 28.4 million pairs at every instant, so you cannot test them all. Coarse pass first: sample every 15 seconds, build a k-d tree at each step, keep any pair inside 120 km. At 15-second steps and 15 km/s of closing speed a true miss of d can only hide from a radius bigger than d + 112 km, so 120 km is the honest floor. That found 232,286 candidate pairs in 82 seconds. Then re-propagate the survivors at 0.05 s around each minimum to get the true time of closest approach: 10,794 of them, 9 seconds.
Nothing came inside 200 metres in this particular day. Six passes came inside 500. The curve is roughly cubic in r below 10 km, which is what you would expect if the fragments were uniformly smeared through the shell — they are not, but at this density it barely matters.
Flat. 907 sub-10 km passes spread evenly across twenty-four hours — between 25 and 49 an hour, every hour. There is no quiet time. The busiest single satellite, STARLINK-35267, had 3 of them in the day.
| # | MISS (km) | SATELLITE | FRAGMENT | CLOSING (km/s) | T + (h) |
|---|---|---|---|---|---|
| 1 | 0.221 | STARLINK-34131 | COSMOS 2251 DEB (COSMOS-2251) | 12.22 | 3.57 |
| 2 | 0.291 | STARLINK-5167 | FENGYUN 1C DEB (FENGYUN-1C) | 5.98 | 1.20 |
| 3 | 0.408 | STARLINK-4400 | COSMOS 2251 DEB (COSMOS-2251) | 4.31 | 0.60 |
| 4 | 0.409 | STARLINK-5604 | FENGYUN 1C DEB (FENGYUN-1C) | 6.81 | 4.54 |
| 5 | 0.420 | STARLINK-5364 | COSMOS 2251 DEB (COSMOS-2251) | 5.11 | 20.27 |
| 6 | 0.448 | STARLINK-11698 [DTC] | FENGYUN 1C DEB (FENGYUN-1C) | 9.46 | 14.61 |
| 7 | 0.525 | STARLINK-36555 | COSMOS 2251 DEB (COSMOS-2251) | 5.54 | 20.44 |
| 8 | 0.565 | STARLINK-35438 | COSMOS 2251 DEB (COSMOS-2251) | 12.15 | 6.09 |
| 9 | 0.643 | STARLINK-32900 | COSMOS 2251 DEB (COSMOS-2251) | 12.34 | 16.83 |
| 10 | 0.646 | STARLINK-35651 | FENGYUN 1C DEB (FENGYUN-1C) | 6.01 | 3.48 |
| 11 | 0.758 | STARLINK-34954 | COSMOS 2251 DEB (COSMOS-2251) | 15.03 | 2.00 |
| 12 | 0.778 | STARLINK-5625 | COSMOS 2251 DEB (COSMOS-2251) | 7.54 | 20.33 |
| 13 | 0.810 | STARLINK-5360 | FENGYUN 1C DEB (FENGYUN-1C) | 13.33 | 3.35 |
| 14 | 0.877 | STARLINK-32243 | FENGYUN 1C DEB (FENGYUN-1C) | 5.89 | 16.31 |
| 15 | 0.882 | STARLINK-30444 | COSMOS 2251 DEB (COSMOS-2251) | 7.75 | 17.18 |
| 16 | 0.904 | STARLINK-5706 | FENGYUN 1C DEB (FENGYUN-1C) | 9.61 | 3.05 |
| 17 | 0.935 | STARLINK-34419 | FENGYUN 1C DEB (FENGYUN-1C) | 14.84 | 5.54 |
| 18 | 0.969 | STARLINK-34613 | COSMOS 2251 DEB (COSMOS-2251) | 14.99 | 16.21 |
| 19 | 0.979 | STARLINK-34548 | FENGYUN 1C DEB (FENGYUN-1C) | 15.08 | 1.15 |
| 20 | 0.980 | STARLINK-5899 | FENGYUN 1C DEB (FENGYUN-1C) | 13.63 | 9.35 |
| 21 | 1.055 | STARLINK-2170 | FENGYUN 1C DEB (FENGYUN-1C) | 12.62 | 13.26 |
| 22 | 1.086 | STARLINK-34507 | FENGYUN 1C DEB (FENGYUN-1C) | 3.86 | 14.34 |
| 23 | 1.105 | STARLINK-33744 | FENGYUN 1C DEB (FENGYUN-1C) | 9.55 | 14.52 |
| 24 | 1.176 | STARLINK-37058 | FENGYUN 1C DEB (FENGYUN-1C) | 8.50 | 15.08 |
| 25 | 1.177 | STARLINK-36275 | COSMOS 2251 DEB (COSMOS-2251) | 10.07 | 8.10 |
The twenty-five closest approaches in the window, sorted. Every name is a real catalogued object.
THE EMPTINESS
I came here to build a Kessler cascade. The runaway chain reaction, the sky closing over. I could not do it honestly and I am going to show you exactly why, because the failure is more interesting than the thing I wanted. Space is not crowded. Space is catastrophically empty. A 500 km shell has about forty billion cubic kilometres in it and roughly thirteen thousand tracked things inside, each a few metres across. The reason we get 20 sub-kilometre passes a day is not density — it is that everything up there is moving at 7.5 km/s and there are a lot of days.
64 real satellites against 176 real fragments whose orbits actually cross theirs. Six hours, 2-second steps, swept-segment closest approach, Kepler + J2.
1 pass under 5 km. 0 under 1 km. 0 collisions.
249 real objects, a hash grid, a NASA-style breakup model, 96 minutes of orbit. I swept the collision radius: 0.05 km, 0.5 km, 1.0 km.
0 collisions and 0 fragments at every physical radius. The chain reaction only ignites at a 2 km hit radius — a satellite roughly 40,000 times its real size — and then it produces 576 collisions and 1,151 fragments while destroying exactly 1 satellite. The rest is fragments hitting fragments. That is not a Kessler cascade, that is a number generator. Cut.
STARLINK-34131 — the satellite with the closest real conjunction in the whole catalogue — against the 89 real fragments that cross its shell. 72 hours.
3 approaches under 25 km. Closest 15.807 km. One satellite on its own sees almost nothing. The 221-metre pass at the top of this page only exists because 10,754 of them are up there at once.
The cascade sim compiled, ran, and produced beautiful numbers. I deleted it from the page and kept the corpse here instead. If I had shipped the 2 km version you would have seen 576 collisions and a fragment count climbing on a graph and it would have been a lie. The honest result is that at real physical scale, on real element sets, over a real day, nothing hits anything. So I pointed the machinery at the problem that actually exists: not the cascade, the dodge.
WHERE THE HONESTY LINE IS
Everything above is SGP4. Everything below runs in your browser in freestanding C compiled to WebAssembly, and it is not SGP4 — it is two-body Kepler motion with the J2 oblateness terms, which is what fits in a hot loop that has to evaluate millions of futures. I measured the gap. Forty real satellites, all started from one common-epoch state vector, propagated both ways:
| HORIZON | MEDIAN ERROR | 90th %ILE | WORST |
|---|---|---|---|
| 0.5 h | 9.985 km | 15.391 km | 22.552 km |
| 3 h | 81.013 km | 114.522 km | 134.058 km |
| 12 h | 310.418 km | 444.772 km | 523.604 km |
| 24 h | 606.321 km | 873.246 km | 984.371 km |
| 72 h | 1,875.232 km | 2,630.898 km | 2,805.992 km |
So: the engine below is a real result about a real dynamical system derived from the real catalogue. It is not a prediction about real satellites. At a 24-hour horizon my propagator is already 606 km away from SGP4 on a median object. Nobody should manoeuvre a spacecraft on it. What it is good for is the question I actually care about, which is a question about search and not about orbits: given the same physics, the same fuel and the same warning, does looking further ahead beat reacting? Both controllers below run inside the identical model, so the model error cancels out of the comparison. That comparison is real.
THE SHELL
3,041 real objects — 400 satellites sampled one per orbital plane, and every one of the 2,641 fragments in the snapshot — each carrying its own semi-major axis, eccentricity, inclination, node, argument of perigee and mean anomaly, converted from an SGP4 state vector at one common instant. No textures, no models, no library. Six numbers per object and a fillRect.
Sweep the next hour of orbit at 2-second steps, swept-segment closest approach on every satellite–fragment pair that shares a hash-grid cell, and count everything that comes inside the radius. This is the same algorithm as Part One, just small enough to run at 60 fps in a tab.
TWO WAYS TO DODGE
Pick one satellite. Give it four burn opportunities over the next twenty-four hours, six hours apart, and four metres per second of fuel total — a real avoidance budget, a real cadence. It can burn prograde or retrograde in quarter-metre steps, which raises or lowers the orbit by a few hundred metres and, far more importantly, shifts when it arrives. Two controllers, identical physics, identical fuel:
At each slot, look at the next six hours only. If something is inside the safety radius, pick the burn that maximises the miss distance in that window. This is the sane, obvious, correct-looking policy. It is roughly what a human operator on a console does.
At each slot, try every burn, and for each one re-screen the entire remaining twenty-four hours against every active fragment, then rewind and try the next. Keep the burn whose worst case over the whole window is best. Same fuel, same slots, same physics. It just refuses to be pleased by the near term.
The failure mode is not subtle once you see it. A myopic dodge changes your period. Changing your period changes where you are eighteen hours from now. The manoeuvre that opens up the conjunction in front of you slides you into a different one behind it — one you were never going to have. Here is the first case that showed me, 72 hours on STARLINK-34131 at a 20 km safety radius:
It burned fuel to cut its own safety margin nearly in half. Lookahead spent more fuel and roughly doubled it.
738 SCENARIOS
250 satellites, one per orbital plane, each against the fragments whose orbits actually cross theirs, at three different safety radii — 750 planning problems attempted, 738 of them scored. (12 runs, four satellites, had no approach at all inside the 60 km logging threshold; they are ties in every column and I left them out of the means rather than let a sentinel poison them.) 32,272 full-window futures imagined. Same code you just ran in your tab, run offline on the same laptop. Nothing here is smoothed or hand-picked. 249 of the scored runs started with a conjunction already inside the safety radius; the tightest do-nothing case in the whole set was 1.688 km.
That last pair of numbers is the whole page. The myopic controller burned fuel and ended up in a tighter spot than doing nothing 98 times. The lookahead controller did it zero times out of 738. Not because it is clever — because it scores every candidate burn against the entire remaining window instead of the next six hours, so a manoeuvre that buys the near term and sells the far one can never look good to it.
| SAFETY RADIUS | RUNS | DO-NOTHING WORST | MYOPIC WORST | DEEP WORST | MYOPIC Δv | DEEP Δv | DEEP / MYO / TIE | MYOPIC SELF-HARM |
|---|---|---|---|---|---|---|---|---|
| 10 km | 246 | 21.823 | 22.591 | 23.657 | 0.405 | 0.083 | 46 / 23 / 177 | 17 |
| 15 km | 246 | 21.823 | 22.972 | 25.860 | 0.712 | 0.235 | 82 / 27 / 137 | 30 |
| 20 km | 246 | 21.823 | 23.466 | 28.371 | 1.136 | 0.499 | 126 / 30 / 90 | 51 |
Worst miss = the closest approach anywhere in the 24-hour window under that plan. Bigger is safer. Δv in metres per second, mean over all runs at that radius including the ones that correctly spent nothing.
Satellite #124 at a 20 km radius. Doing nothing: 33.123 km. The myopic controller spent 3.00 m/s over 2 burns and came out at 5.116 km — it threw away 28.006 km of margin buying safety it did not get. Lookahead, same fuel budget, same slots: 33.123 km for 0.00 m/s.
Satellite #186 at a 10 km radius. Myopic reached 36.676 km; deep only reached 11.630 km — it lost by 25.046 km. Lookahead is not magic. It optimises the worst case over the window it can see with the fuel it has, and sometimes a greedy grab happens to land in a better basin. 80 times out of 738 it did. There is also a design choice in there I should name: once the whole window is clear of the safety radius, my deep controller stops spending and prefers the cheapest plan. This run is exactly that — it reached 11.630 km on a 10 km radius for a quarter of a metre per second, declared itself done, and watched myopic keep burning to 36.676 km for eight times the fuel. It is still a loss on the metric in the column, so it is counted as one.
WHAT THIS IS NOT
- ✕ Not a conjunction warning service. Part One is real SGP4 on real elements, but element sets go stale in hours, and I am not modelling manoeuvres, drag variation or covariance. If your satellite is in that table, phone your own analyst.
- ✕ Not SGP4 in the browser. See the error table. Kepler + J2 drifts 606 km from SGP4 in a day.
- ✕ Not a Kessler cascade. I tried. It does not exist at this scale on this data. Part Two is the receipts.
- ✕ No satellite-versus-satellite risk. Starlink flies phase-controlled trains; a J2-only propagator drifts them into fake sub-kilometre approaches within a plane. Every conjunction on this page is satellite × debris only. That one cost me a day.
- ✕ No learning, no model, no network. There is no neural anything on this page. It is a propagator and a search that imagines futures and throws them away.
- ✓ What is real: the catalogue, the 232,286 screened pairs, the 221-metre pass, the emptiness, and the 738-scenario comparison between two controllers running in identical physics.
One more thing worth writing down, because it took a wasted benchmark run to learn. A two-line element set is only valid at its own epoch, and every object in the catalogue has a different one. Building a scene by reading the elements straight off the TLEs and calling them all t=0 produced a flat 12,195 km error at every horizon — a constant offset, which is the shape of a bug and not the shape of a model. The fix is to SGP4 every object forward to one common instant, take the state vector, and convert that back to classical elements. Every number below the fold comes from a snapshot built that way.
Element sets: CelesTrak GP data, groups starlink, cosmos-2251-debris, iridium-33-debris, fengyun-1c-debris, retrieved 2026-08-12. Propagator for Part One: the AIAA reference SGP4. Engines: freestanding C, no libc, compiled to WebAssembly.