Conjunction screening and collision-avoidance planning for Earth orbit — running entirely in a browser tab.
Pick a satellite. Kessler pulls the live tracked catalogue from CelesTrak, propagates every object with SGP4, finds every close approach in the next three days, works out the probability of collision for each one, and then searches for the cheapest avoidance burn that clears the risk without creating a new conjunction with something else.
No backend. No API key. About a second against 14,000 objects.
▶ Live demo: https://jisco1.github.io/kessler/
Screening the ISS against the live catalogue: 14,356 objects considered, 14,117 removed by the perigee/apogee sieve before a single propagation, 232 propagated through 252,000 SGP4 evaluations — 16 close approaches found in about a second. A satellite in the crowded 800 km shell keeps far more of the catalogue and takes a few seconds.
There are more than 14,000 tracked objects in low Earth orbit and only a fraction of them are working spacecraft. The rest are spent rocket bodies and fragments — including the debris clouds from the 2007 Fengyun-1C anti-satellite test, the 2009 Iridium 33 / Cosmos 2251 collision, and the 2021 Cosmos 1408 test that sent the ISS crew into their return capsules. Every one of those fragments is a bullet that stays in orbit for decades.
Operators handle this by conjunction assessment: continuously screening their spacecraft against the catalogue, and occasionally spending propellant to move out of the way. The analysis behind those decisions is expensive, largely proprietary, and effectively unavailable to the university groups, cubesat teams and new operators who now make up a large share of what is flying.
Kessler is that analysis, made open and made fast enough to run interactively on a laptop.
Advance Space Exploration with AI — specifically debris tracking, spacecraft monitoring, and making space data more accessible. Kessler takes the one orbital dataset that is public for the entire catalogue and turns it into an operational decision, in a form anyone can open in a browser.
The naive approach — sample every object every second across a 72-hour window — is 3.7 billion samples for a single asset, over 7 billion SGP4 evaluations. Kessler gets the same answer in a quarter of a million, using three ideas that discard work without ever discarding a conjunction:
A perigee/apogee sieve. If two orbits do not occupy overlapping radial shells, they cannot conjunct, and that is decided with arithmetic instead of propagation. Screening the ISS against the full catalogue, this removes 14,117 of 14,356 objects before a single propagation runs.
A step bounded by relative motion. Range shrinks no faster than relative speed, and relative speed changes no faster than relative gravitational acceleration (bounded by ~0.02 km/s² in LEO). So from a sample at range d, with slack s = d - gate, the range provably cannot reach the gate before s = v·t + a·t²/2, and the next step is the positive root of that quadratic. Far apart, the walk strides minutes at a time; near the gate it collapses to seconds on its own.
This bound is where most of the speed comes from. Bounding by the sum of the two inertial speeds (~15 km/s) is also safe, but wildly pessimistic for the pairs that dominate the runtime: two satellites in the same 800 km shell close at metres per second, not kilometres. Switching to the quadratic bound cut a full screening pass from 25 s to 4.7 s.
Minima detected by range rate, not by comparing samples. If range rate is negative at one sample and positive at the next, a closest approach lies strictly between them — true no matter how coarse the sampling is. A 14 km/s head-on encounter cannot hide between two steps. Bracketed minima are then refined by golden-section search to sub-second precision.
Miss distance is a bad decision variable. A 200 m miss between two well-tracked objects can be safer than a 2 km miss between two poorly tracked ones. Kessler computes probability of collision the way operators do:
- the encounter collapses onto the plane perpendicular to the relative velocity (the standard Foster 2-D formulation)
- both objects' position covariances are rotated into that plane and summed
- the collision integral over the combined hard-body radius is evaluated with Chan's analytic series, which is stable across the full range of miss distances
Because public TLEs carry no covariance, one has to be assumed, and this is the single largest modelling assumption in the project. It is stated explicitly rather than hidden: a RIC uncertainty that starts at roughly (0.3, 1.0, 0.3) km and grows at (0.2, 2.2, 0.2) km per day of propagation, with the along-track channel degrading faster for high-drag objects. The shape follows the known behaviour of SGP4 against precise ephemerides — error is dominated by the along-track direction, because a small error in mean motion smears position along the track.
The encounter-plane plot in the UI is the picture that explains the number: the asset at the origin inside its hard-body circle, the debris at the miss point, and the uncertainty as an ellipse around it. Pc is literally the share of that ellipse's probability mass overlapping the circle. It makes the two very different failure modes obvious — a small ellipse far from the origin is a safe pass; a huge ellipse sitting over the origin means nobody knows where the debris is, and the answer is better tracking, not propellant.
Avoidance is bought with lead time, not propellant. An in-track burn changes the semi-major axis, which changes the orbital period, which makes along-track displacement grow linearly with time: a burn of one centimetre per second, a day ahead, moves the asset about 2.6 km. Kessler models this with the Clohessy–Wiltshire equations for impulsive relative motion.
The planner searches burn direction × lead time × magnitude for the cheapest impulse that clears the risk, subject to a minimum lead time for a human to approve and uplink the command. It reports the whole trade — typically 6–8 options in about 200 ms — not just an answer, because the choice between "burn now, cheaply" and "burn later, when we know more" is an operator's call.
The second bug this caught. The first version sized every burn against the single time of closest approach, and produced beautiful numbers: 78 mm/s takes this encounter from Pc 2.4×10⁻⁵ to 1×10⁻⁷. Re-screening the burn against the whole catalogue said otherwise — the worst risk afterwards was the same debris object, 1.55 hours earlier, which is exactly one ISS revolution. An in-track burn slides the asset along its own track, so a geometry that repeats every revolution does not go away; the encounter simply happens on the neighbouring pass at a similar miss distance. Sizing against one TCA gives a number that looks excellent and a spacecraft that is no safer.
The planner now prices every candidate by screening the displaced trajectory against that object across the entire window — about ten milliseconds — and escalates the burn until the object is genuinely cleared. For this encounter that means 428 mm/s rather than 78 mm/s, and the risk really does go to 3×10⁻¹². Candidates that only move the problem around are still listed, with their true residual risk, because "no cheap burn fixes this" is sometimes the honest answer. Cross-track burns were added at the same time: they buy no secular drift and are expensive, but changing the orbit plane is the only lever that moves where the two paths cross.
This is the part that matters most and is easiest to skip. An avoidance maneuver moves your spacecraft through a catalogue of 14,000 objects. Solving one conjunction by flying into another is a real failure mode.
So before recommending anything, Kessler re-screens the entire catalogue against the displaced trajectory, on exactly the same code path as the original screening, and reports any conjunction the burn would create. In testing this routinely surfaces several — usually harmless, occasionally not, and never visible if you only look at the encounter you started with.
The bug this caught. The first working version of the verifier reported that moving the ISS would put it 1.3 km from ISS (NAUKA) at a probability of 4×10⁻³. NAUKA is a module bolted to the ISS. CelesTrak catalogues station modules and docked vehicles under their own NORAD ids but publishes them with the station's element set, so they propagate to exactly the same point in space — range identically zero. They never appear in a nominal screen, because range rate never changes sign and no minimum is ever bracketed; they appear the instant the asset is displaced and the docked stack is not. Kessler now identifies them by co-motion rather than by name — same position, same velocity, at the start epoch — so it works for any station, any docked vehicle and any duplicate catalogue entry. Removing them also cut the stations group from 692,660 propagations to 1,471, because two objects at zero range pin the adaptive walk to its minimum step for the entire window.
Any encounter can be exported as a CCSDS 508.0 Conjunction Data Message — the format space surveillance providers issue and flight dynamics teams already ingest — with the recommended burn appended. The message carries its own provenance in the comment header: state vectors are SGP4 propagations of public TLEs, and the covariance is Kessler's documented model rather than a measured orbit-determination covariance. Anyone reading it should know the difference, so the file says so.
Correctness claims about a fast algorithm are worthless without a slow one to check them against. scripts/validate-screen.mjs runs an exhaustive fixed-step scan over the same objects and window and compares the answer sets:
primary : SUOMI NPP (37849)
secondary : 400 objects
window : 72 h, gate 25 km, brute-force step 1 s
adaptive : 3 conjunctions, 142,735 propagations, 0.38 s
brute : 3 conjunctions, 207,361,600 propagations, 121.5 s
speedup : 323x faster, 1453x fewer propagations
matched : 3/3
missed : 0
worst refined-minus-brute delta: 0.0 m
PASS: the adaptive screen found every conjunction the exhaustive scan found.
The refined times of closest approach agree with a brute-force one-second scan to within a metre.
Chan's equation is a series expansion of the collision integral, so getting a
plausible number out of it proves nothing. scripts/check-pc.mjs evaluates the
same integral by direct 2-D numerical integration — in polar coordinates about
the hard-body disc, so the domain is exact — and compares:
m=(0,0) s=(1,1) R=0.02 chan=1.99980e-4 numeric=1.99980e-4 rel=0.0000%
m=(1.5,0) s=(1,1) R=0.02 chan=6.49313e-5 numeric=6.49313e-5 rel=0.0000%
m=(0,0) s=(1,1) R=2 chan=8.64665e-1 numeric=8.64665e-1 rel=0.0001%
m=(20,3) s=(10.5,0.92) R=0.0555 chan=1.27635e-7 numeric=1.28133e-7 rel=0.3889%
The last row is the real ISS / Cosmos 2251 encounter this README opens with. Agreement is better than 0.4% everywhere, across tiny and large hard bodies, centred and offset misses, and the strongly elongated covariances that real encounters actually produce.
The same script also verifies the analytic small-body limit R²/2σ₁σ₂, the
exp(-k²/2) falloff at 1σ/2σ/3σ, saturation, monotonicity, and the risk-band
thresholds. scripts/check-planner.mjs confirms the Clohessy–Wiltshire
displacement matches the 3·Δv·t rule (0.66 km against 0.65 km predicted at
6 h; 2.55 km against 2.59 km at 24 h), then plans and verifies a real burn end
to end.
node scripts/check-pc.mjs
node scripts/check-screen.mjs
node scripts/check-planner.mjs
node scripts/validate-screen.mjs 400 72 1Kessler is an autonomous decision agent for a domain where the honest answer is physics plus search, not a language model. It runs the full perceive → predict → reason → explain loop:
| Stage | What it does |
|---|---|
| Perceive | Ingests the live tracked catalogue from CelesTrak, ~14,000 objects, in the browser |
| Predict | Propagates every object with SGP4 and propagates uncertainty with an explicit RIC growth model |
| Reason | Constrained search over burn direction, lead time and magnitude — minimising propellant subject to a residual-risk target, an operational lead-time constraint, and a no-induced-conjunction constraint verified against the whole catalogue |
| Explain | Generates a grounded operator brief for every encounter |
The explanation layer is deliberately grounded rather than generative. Every sentence in a brief is derived from a computed quantity — the Mahalanobis distance separating "they pass close" from "we do not know where they are", which element set contributes the larger share of the uncertainty, how stale that element set is, what the relative velocity implies about the encounter geometry. For a tool whose output is "spend propellant" or "don't", a number that cannot be traced back to arithmetic is worse than no number. Nothing here can hallucinate a conjunction.
The interesting AI problem in orbital safety is not text generation. It is search under uncertainty with a constraint that most systems ignore — that the fix can be worse than the fault — and that is the part Kessler actually solves.
Stated plainly, because a risk tool that hides its assumptions is worse than none:
- TLE covariance is modelled, not measured. Public element sets carry no covariance. Real conjunction assessment uses operator ephemerides with real covariance. Absolute Pc values here are indicative; relative ranking between encounters is the trustworthy part.
- Hard-body radii are assumed by object class (station ~55 m, satellite ~5 m, fragment ~0.5 m). TLEs carry no size.
- Maneuvers use a linearised (Clohessy–Wiltshire) model, valid while displacement stays small relative to orbit radius — true by three orders of magnitude for the centimetre-per-second burns involved, but not a replacement for a full trajectory optimiser.
- SGP4 is a general-perturbations model. It is the right and only choice for TLE data, and it is not a precision orbit determination.
This is a decision-support demonstrator, not flight software.
npm install
npm run devThen open the local URL. The app fetches the live catalogue from CelesTrak on load; a snapshot of ~14,000 element sets ships in public/data/ and is used automatically if the network is unavailable, so it works offline.
npm run build # production build
npm test # Pc math, screening correctness, planner end to end
npm run validate # the exhaustive brute-force comparison (takes a few minutes)
npm run shot # regenerate the README screenshot from a running previewThe deploy workflow runs check-pc and check-screen before it will publish, so the physics is checked on every push.
| Orbital propagation | satellite.js (SGP4) |
| Element sets | CelesTrak GP data, fetched live with open CORS |
| 3-D view | three.js — deliberately not a photographic Earth; this is an operations picture, and a texture would compete with the thing that matters |
| UI | React + Vite, no component library |
| Compute | A Web Worker owns the catalogue and all propagation, so the globe keeps turning and progress streams while millions of evaluations run |
Everything is client-side. The whole thing is a static site.
src/lib/orbit.js SGP4 wrappers, RIC frame, orbit geometry
src/lib/pc.js covariance model, encounter plane, Chan's Pc
src/lib/screen.js the screening engine: sieve, adaptive walk, TCA refinement
src/lib/maneuver.js Clohessy-Wiltshire model, burn search, post-burn verification
src/lib/brief.js grounded natural-language briefs
src/lib/cdm.js CCSDS conjunction data message export
src/worker/ the worker that owns the catalogue
src/components/ globe, encounter plane, timeline, planner
scripts/ validation against brute force
To be completed by the author.
MIT. Element set data is courtesy of CelesTrak.
