About CritLab
CritLab estimates, second by second, how much of a criterium you spent doing your own work and how much you spent on somebody else's wheel — then tells you where on the circuit you kept getting it wrong.
It runs entirely in your browser. There is no CritLab server, no account, and no upload. Races you load are stored in this browser's IndexedDB and go nowhere else.
What it actually computes
For every second of the race it works out the solo-required power: the watts it would have taken to hold your measured speed, on your measured gradient, in the wind that was actually blowing, with nobody in front of you. Divide your real power by that and you get the draft ratio.
- Around 1.0 — you were paying full price. Clean air.
- 0.55–0.75 — a wheel. Somebody else is punching the hole.
- Above 1.0 — more than a solo rider would need, so you were accelerating: closing a gap, sprinting out of a corner, or attacking.
The model behind the solo-required figure is the standard one:
P = (m·g·sin θ + m·g·Crr·cos θ + ½·ρ·CdA(β)·vapp·vhead + (m + mrot)·a) · v / η
with a few refinements that matter at crit speeds:
- Humid air density. Water vapour is lighter than dry air; a muggy July evening is measurably faster than the dry-air formula suggests.
- Apparent wind, not ground wind. The aero term uses the wind you actually feel, resolved into head and cross components from your bearing, with a yaw term that widens effective CdA as the wind comes further off the nose.
- Wind at rider height, not 10 m. Weather stations report wind at 10 metres. A rider sits at about 1.3. The logarithmic wind profile scales between them, and the surface-roughness setting is the single biggest lever: an airport-flat course sees roughly six times the wind of a downtown circuit boxed in by buildings.
- Gradient by regression, not differencing. Barometric altimeters are noisy at one-second resolution, and naively differentiating them invents gradients that the power model dutifully converts into phantom watts. CritLab fits altitude against distance over a rolling window instead, widening it when you are moving fast enough that a fixed window holds too few samples.
- Recording gaps are excluded, not interpolated. If your head unit auto-paused at a stoplight, those seconds are flagged and left out rather than filled with invented power.
Where the wind comes from
Three sources, in decreasing order of how much you should trust them:
| Source | Unknowns to solve | Needs |
|---|---|---|
| Weather | CdA only | GPS and a start timestamp |
| Manual | CdA only | You to know what it was doing |
| Infer | CdA and a wind vector | A course with several bearings |
Weather comes from Open-Meteo, which is keyless and needs no account. CritLab sends it your race's coordinates and date and gets back an hourly series — temperature, humidity, pressure, wind speed, direction and gusts — which it interpolates per sample, because a 60-minute crit can straddle a wind shift.
Inference is the fallback when there is no weather. It exploits the fact that with the wrong wind assumed, apparent exposure becomes a function of which way you are pointing — the north-bound straight looks systematically harder than the south-bound one. The wind that flattens that difference is the wind that was blowing. This needs you to have ridden in several directions: a four-corner rectangle gives four bearings against three unknowns, which is solvable but loose, and CritLab will tell you when it is only guessing.
The CdA assumption, stated plainly
CritLab does not know your CdA, so it calibrates one: it assumes that in your least sheltered moments — by default the 85th percentile of steady, fast, pedalling seconds — you were riding in clean air, and picks the CdA that makes that moment come out at a ratio of exactly 1.0.
This is an assumption, not a measurement, and it is the single knob that moves the absolute numbers. If you spent the whole race buried in the bunch and never once hit the front, the assumption is wrong and the reported exposure will be too high. Drag the clean-air calibration percentile slider and watch the headline percentages move.
What barely moves is the pattern: which stretch of the circuit was worst, which corner cost the most, whether your matches were lit from a wheel or from the wind. That is why the sector view is ranked by how often a stretch came out worst on its own lap, rather than by an absolute threshold — a finding built that way survives being wrong about CdA.
Laps, corners and sectors
Laps come from repeated passes of one point on the circuit, gated on heading so an out-and-back section cannot fire a false lap. If the detected laps are not near-identical in length, CritLab decides it is not looking at a circuit and says so rather than inventing structure.
Where that point sits is worth being honest about. By default it is where your recording started moving — which is wherever you pressed start: the staging area, the back of the grid, the far end of the car park. The actual start/finish line is paint on a road, and nothing in a GPS trace identifies it. No heuristic can find it, so CritLab does not pretend to. Instead, click Set start/finish line on the circuit map and put it where it belongs; laps, corner numbering and sectors are all measured from there, and the choice is saved with the race. A point you do not cross every lap is refused rather than quietly accepted.
Corners are not found by thresholding the curvature of your raw track, which sounds like the obvious approach and fights physics it cannot win: at 43 km/h a 1 Hz recorder puts a sample every 12 metres, so a 16 metre corner is two or three points and the GPS noise between them is comparable to the turn itself.
But a crit is the same corner twenty times. CritLab stacks every lap onto a shared lap-distance grid and takes the median line — robust to the one lap you went wide to come past somebody, and positional noise falls by roughly the square root of the lap count. Corners are then measured on a clean 2 metre grid instead of a ragged 12 metre one, with the radius from a least-squares circle fit, and every lap contributes one pass by construction rather than by hoping a threshold fires again. On a simulated circuit built from known 16 metre arcs, this recovers 16.8–17.5 m; the naive method reported 31 m.
Turn 1 is the first corner you reach after the start, Turn 2 the next, and so on — the way you would count them yourself. That is worth stating because the obvious ordering, by distance around the lap, is measured from the lap-detection anchor, and that anchor is picked for being an unmistakable piece of straight road somewhere in the first half of the ride. Good for detecting a lap crossing; arbitrary for counting corners. So the numbering comes from the track: the first moment in the recording that you pass through each corner.
For each pass it measures entry speed, apex speed, how long you took to get back to entry speed, and the work above CP that took — the exit cost. Consistency matters as much as the mean: a corner you take differently every lap is one you are arriving at in a different position every lap.
Sectors are the stretches between corners. The lap × sector grid is the view worth the whole tool: a column that stays red top to bottom is a place you were in the wind every single lap, and that is a habit, not luck.
Matches and W′
Where CP and W′ come from. The defaults are the activity's own: when a ride is pulled from intervals.icu it carries the critical power and W′ that its power model held on that date, so a race in June is judged against June's fitness rather than today's. Move a slider and your value wins from then on; the rail says which is in force, and offers a way back.
Those defaults are worth checking, because a CP that is too low makes W′ worthless in a specific and misleading way. CP is by definition a power you can hold more or less indefinitely, so if the whole race was ridden above it the number is wrong — the tank drains from the gun and pins at empty, and every race reports "0% W′ remaining" regardless of how it actually went. That looks like the model working and is not. CritLab checks for it: normalised power for the race is a floor CP cannot sit below, since you demonstrably held it, and if CP is under that the panel says so and offers to fix it.
W′ balance uses Skiba's differential model with Clarke's recovery expression: the anaerobic tank drains at (P − CP) above critical power and refills below it with a time constant set by how far below you are recovering. The reported balance is clamped at empty — "how much is left" cannot be negative — but demand beyond empty is not thrown away; it accumulates as an overdraft, which is the honest signal that your configured W′ is too small.
A crit sits above any fixed watts threshold almost constantly, so CritLab does not call every kick a match. An effort counts only if it took a real bite out of W′, and efforts that are simply corner-exit accelerations are classified separately — they cost W′, but they are the price of the circuit rather than a tactical decision, and they are already priced on the Corners tab.
The map
The circuit map draws roads, buildings and water from OpenStreetMap underneath your track, as vectors rather than raster tiles — so it can be styled to sit quietly beneath the exposure colours instead of competing with them, and so it takes one cached request per circuit rather than a screenful of tiles. Buildings are not decoration: on a town-centre circuit they are the reason one straight is sheltered and the opposite one is not.
Map data comes from the volunteer-run Overpass API, which is free and sometimes busy. If it is rate-limiting or down, the map simply draws without a backdrop.
Trimming the race out of the file
A crit file is usually three things end to end: a warm-up or neutral roll-out, the race, and a cool-down lap spinning with mates. All three look like riding, and analysing them together quietly corrupts almost everything — W′ starts draining before the start, the cool-down dilutes the exposure percentages, and a soft-pedalled lap drags the sector medians toward "sheltered" for reasons that have nothing to do with shelter.
Two signals go into finding it, because neither works alone:
- Circuit geometry — was the rider on the circuit at all? This separates the ride to the venue and the ride home from everything that happened there. Pace cannot do it: a 30 km/h approach and a 30 km/h warm-up lap look identical until you notice one of them is two miles away.
- Power — among the laps actually on the circuit, which were raced? Geometry cannot tell these apart, because they are all the same road.
Leaving the racing line comes first. A rider who has finished often stops riding the course: peels into the pits, cuts across, drifts wide, or leaves for the car park. That is better evidence than anything power can give, because you cannot soft-pedal your way onto a different road — a lap ridden at 300 W into the parking lot is still not a race lap. So a racing line is built from the fastest laps only (building it from every lap would fold the cool-down into the reference and hide the very deviation being looked for), and any lap spending a fifth of its length outside that corridor, or straying several times further from it than a race lap does, is not a race lap.
On a closed circuit with no traffic this often does not fire at all — you cool down on the same line you raced, within a metre or two — and then power decides. Both are checked; the geometry simply gets the first word when it has something to say.
Power rather than pace, and that distinction was learned the hard way. An earlier version asked whether a lap was within 12% of race pace. On a real crit the lap speeds ran 39, 41, 39, 42, 41, 40, 39, 40, 38, 35, 35, 35, 34, 35, 35, 34 km/h — a smooth decline with no discontinuity anywhere — and the rule lopped off the last four as a cool-down. One of them was ridden at 264 W, higher than three of the laps it kept. That is not a cool-down; that is the end of a hard race.
What actually distinguishes a cool-down is that you stop pedalling hard, so the test is on power: a cool-down lap is soft, somewhere near half your racing power, and no amount of fading gets you there while still racing. The two ends are held to different bars, because what sits at them differs — after the finish you soft-pedal, but before the start you roll out neutralised, slower than racing but still in a bunch at real power and still part of the race. So the front demands much clearer evidence before anything is removed, and laps in the middle are never touched however slow the race got.
Boundaries land on lap crossings, so if you have placed a start/finish line the race begins and ends exactly there rather than mid-lap. A sustained stop after the racing is treated as a hard end — the race cannot span it — which helps if you come to a halt at the finish and costs nothing if you roll straight into a cool-down lap.
Partial laps get the same treatment. A race does not oblige you to stop on a lap boundary — riders roll past the line and stop wherever — so there is nearly always a fragment after the last crossing. Taking the window to that crossing throws it away whatever it contains, and on one real race that discarded 2:12 ridden at 278 W, above that race's own reference power. Partial or not, that is racing. So a fragment is judged on power exactly as a lap is: soft means cool-down and goes, racing power stays.
Files that arrive already trimmed are recognised. Riders crop in intervals.icu, or simply start and stop the head unit on the line. The tell is stopped time: a raw crit recording has the rider stationary on the grid before the start, so a file with essentially none, moving from the first second, has already had its ends taken off. That inverts the prior — whatever survived your own editing is more likely to be race you meant to keep than a cool-down you missed — so the bar for removing anything further is doubled, and CritLab says why rather than staying silent.
Two guards keep it from over-trimming. A first lap from a standing start is legitimately slower than the ones after it — the field is rolling out, not racing yet, but it is still the race — so leading laps are held to a much looser bar than the rest: obviously slow, not merely slower. And plenty of files arrive already trimmed, cut in intervals.icu or simply started and stopped at the line; those are recognised and reported as already being just the race, rather than either nagging about a trim that would remove nothing or staying silent about whether anything was checked.
There are also settings for things no signal processing can discover: Settings → How you record → I start recording on the start/finish line means nothing is ever trimmed from the front of a race, and the start/finish line is taken to be where the recording begins. Drop cool-down laps automatically removes the laps after the racing without asking each time, still reversible on any individual race, and with a Review the trim button if you want to see and adjust what it did. Both are facts about your habits rather than the file, so you have to say them.
Nothing is trimmed without asking. CritLab shows you the whole recording split into phases, plus every lap with its pace against the race-pace reference and the kept laps lit — the evidence the decision was made on, not just the decision. You can add or drop a lap at either end before accepting, and undo the whole thing later. The trim is applied before anything is derived, so every number downstream is measured on the race alone.
Heart rate and cadence
Aerobic decoupling compares your power-to-heart-rate ratio between the first and second half of the race. A rising heart rate for the same watts is the classic sign of running out of aerobic road — and it is a useful counterweight to W′, which is a model where this is a measurement. Above about 8% is worth noticing; above 15% the race was limited by something other than the repeated efforts.
Cadence through the corners is reported per turn, along with how much of each corner you spent freewheeling. A corner taken coasting costs speed you then buy back with watts on the exit, and the Corners tab prices exactly that.
The shape of the race
Lap-by-lap normalised power says whether you faded, held, or built, which single lap was the outlier you had to cover, and whether the last lap was your hardest or the point at which there was nothing left.
This started life as a changepoint detector looking for the lap where the pace stepped up and stayed up — the selection. Measured against four real crits, no such step existed in any of them: one declined steadily with a big final lap, one went out hard and faded, one was flat with a single enormous lap in the middle. A detector that fires on nothing is worse than none, so it reports what the laps actually do instead.
Racing the same circuit twice
When two loaded races share a circuit — same centre within 250 m, lap lengths within 12% — the Compare tab says so and lines the corners up side by side. That is a different question from comparing four different courses: on one circuit, Turn 3 means the same thing in every row, so a corner that keeps costing you speed is a line to work on rather than a fitness problem.
What it needs from a file
- Power is required. Everything here is built on comparing your watts to a modelled requirement.
- GPS is strongly recommended. Without it there are no laps, no corners, no sectors, no map, and no gradient — you get the timeline, the load metrics and W′.
- A start timestamp is what lets CritLab date the weather.
Accepted formats: .fit from any head unit, activities pulled
directly from intervals.icu, and JSON — either CritLab's own shape or an
intervals.icu stream dump.
Privacy, and your API key
CritLab has no server. It is a static page — HTML, CSS and JavaScript served from a file host. There is no backend, no database, no account, and no analytics.
That single fact is what makes the rest of this section true rather than a
promise. Your intervals.icu API key is never sent to CritLab, because there
is nothing to send it to. It goes to one place and one place only: the
Authorization header of the requests your own browser makes
directly to intervals.icu — which is where the key came
from, and the only service where it means anything.
To be precise, because a vague security claim is worse than none: the key does travel over the network, to intervals.icu, over HTTPS. That is how the API authenticates you and there is no way around it. What it never does is reach us, or a third party, or any intermediary — there is no proxy in the path, and nowhere else in this application touches it.
Where it is kept
So you don't have to paste it on every visit, the key is held in your browser's storage under its own entry, separate from every other setting. Under Settings → intervals.icu you choose:
- In this browser, until I remove it —
localStorage, survives closing the browser. - Only until I close this tab —
sessionStorage, gone the moment the tab closes.
If your browser blocks storage entirely — private browsing in some browsers, or a page opened straight off disk — the key is held in memory for the life of the page instead, so it still works and still goes nowhere, but you will need to paste it again next visit.
Forget key erases it from both immediately. And because an intervals.icu key only ever grants access to your own account, you can revoke it outright at intervals.icu → Settings → Developer at any time, which invalidates it everywhere regardless of what any application has done with it.
Everything else
- Your races are stored in this browser's IndexedDB. They are never uploaded anywhere.
- Open-Meteo receives the rounded coordinates and date of the race, to return the weather. Nothing else, and no key is involved — the service needs no account.
- Overpass / OpenStreetMap receives the bounding box of the circuit, to return roads and buildings. Nothing else.
- Neither is told who you are, because CritLab does not know.
- Everything stored locally can be wiped from Settings: races under Delete all stored races, map data under Clear cached map data, the key under Forget key.
You do not have to take any of this on faith. The whole application is a handful of readable JavaScript files with no build step and no minification: open your browser's devtools, watch the network tab while you load a ride, and you will see exactly three hosts — intervals.icu, Open-Meteo and Overpass — and nothing else.
The sample race
Load sample race generates a synthetic crit rather than shipping a recording: a 25-lap, 1.07 km four-corner circuit simulated forward through the same power model CritLab runs in reverse, with a real wind, a braking and accelerating speed profile that respects corner radii, and a positioning leak deliberately planted on the windward straight for 11 of the 25 laps. The analyser finds it. It carries no date on purpose, so there is no weather to fetch and the wind gets inferred — which is the case worth seeing.
Caveats worth repeating
- Absolute exposure percentages depend on the CdA assumption. Treat them as a scale, not a measurement.
- Inferred wind on a course with few bearings can be tens of degrees out. CritLab flags this when it applies.
- Draft ratio cannot distinguish "sheltered" from "freewheeling downhill in a bunch" — both look like cheap watts, because both are.
- A ratio above 1.0 is not always exposure; hard acceleration produces the same signature. That is why the surge ledger and the corner view exist separately.
- Weather is a grid cell, not your start-finish straight. On a sheltered industrial-park circuit the roughness setting matters more than the wind speed does.