Data sources, the physics engine, entraining CAPE, moist static energy, and how every value is ranked against a station's own history — written out honestly, including what it can't do.
← Open the explorerA sounding can arrive from four places. The app walks them in order of freshness, and the status line always names the one you're looking at, so you are never guessing.
| Source | Coverage | Latency | Resolution |
|---|---|---|---|
| SPC observed | US | Fastest — ~1 h post-synoptic | ~110–200 levels |
| IEM RAOB | US + Canada | ~1 h behind SPC | ~180–250 levels |
| UW mirror | Global | 6×/day cron | Full BUFR |
| NOAA IGRA v2 | Global, 1905–now | ~1–2 day lag | Mandatory + significant |
A radiosonde is launched at 00Z/12Z, but the balloon takes 90 minutes to two hours to ascend and transmit. No source on Earth has a complete 12Z sounding before about 13:30 UTC. "Real-time" for soundings means as soon as the atmosphere finishes reporting itself — not at 12:00 sharp. Within that floor, SPC is the fastest public source, which is why it is tried first even though it carries slightly fewer levels than IEM.
Access-Control-Allow-Origin header, so a browser cannot read it directly. A scheduled
job pulls ~600 stations six times a day into a data branch that GitHub serves CORS-open. Complete
UTC days are additionally bundled into an append-only archive — a permanently growing
high-resolution record the browser unzips client-side.
The analysis is SHARPlib (Kelton Halbert, of NWS Storm Prediction Center lineage) compiled to WebAssembly — about 250 KB running inside your browser tab. There is no backend and no compute bill.
Crucially, the same compute_sounding() entry point is used by both the
browser and the offline climatology builder. A live value and its historical percentile are computed
by identical code, so the two can never quietly drift apart.
Classic CAPE lifts a parcel undilute — as if sealed in a bag, never mixing with the air it rises through. Real updrafts are not sealed. They entrain drier environmental air across their edges, which evaporates cloud water, cools the parcel, and destroys buoyancy. This is why a sounding advertising 3000 J/kg routinely fails to produce the 77 m/s updraft that number implies.
Undilute CAPE therefore overstates the energy available to a storm — and it overstates it unevenly. The error depends on the wind profile and the storm's size, so two soundings with identical CAPE can behave completely differently.
ECAPE (Peters et al., 2023, J. Atmos. Sci.) derives the entrainment rate from theory rather than a tuned fudge factor. The insight: an updraft's dilution depends on its width, and its width is set by the storm-relative wind and shear. Stronger inflow and deeper shear support a wider, better-protected updraft core, which entrains proportionally less and preserves more buoyancy.
So ECAPE is a function of the thermodynamic profile and the kinematic profile together — which is why its implementation demands winds and a moist-static-energy profile, not just temperature and dewpoint. The gap between CAPE and ECAPE is physically meaningful: it is the buoyancy the atmosphere promises but entrainment takes away.
ECAPE is defined per lifted parcel, exactly like CAPE, so the Parcels table shows CAPE and ECAPE side by side for each: SB (surface-based), ML (mixed-layer — the most representative for ordinary convection), and MU (most-unstable, for elevated storms).
| Derived quantity | Formula | What it tells you |
|---|---|---|
| Max updraft (ECAPE) | √(2·ECAPE) | Realistic, entrainment-limited updraft speed |
| Max updraft (CAPE) | √(2·CAPE) | The classic undilute speed, for contrast |
| ECAPE / CAPE | ratio | Entrainment loss netted against storm-relative inflow gain (can exceed 100%) |
Both speeds are ceilings that ignore water loading and the perturbation pressure gradient, so real updrafts fall short of even the ECAPE value. The point is the comparison: where the ECAPE updraft falls well short of the CAPE one, most of the advertised buoyancy is fiction.
ECAPE is built on moist static energy, so the explorer plots it directly.
MSE is conserved under both dry and moist adiabatic ascent — a parcel carries its h upward unchanged, whether or not it is condensing. That makes it the natural currency for convection.
The panel plots two curves: h, the actual moist static energy at each level, and h*, the saturation MSE — what h would be if that level were saturated. Because h* depends only on temperature and pressure, it describes the environment's capacity, not its moisture.
A parcel lifted from the boundary layer conserves its h. It is buoyant wherever its h exceeds the environment's h*. That region — shaded below — is precisely the layer generating CAPE. The instability isn't inferred; it's drawn to scale.
| Value | Meaning |
|---|---|
| MSE (0–500 m) | Boundary-layer h — the energy a surface parcel carries aloft |
| Min saturation MSE | The lowest h* aloft — the mid-level "dry hole" a parcel must survive |
| MSE deficit (h*−h) | Negative ⇒ the boundary layer out-energizes the mid-levels ⇒ conditionally unstable |
| Column MSE | The vertical integral (1/g)∫h dp, in GJ/m² — the state variable of tropical MSE-budget theory |
SHARPlib has no tropopause routine, so both standard definitions are computed directly from the profile. The WMO tropopause is the lowest level where the lapse rate falls to ≤ 2 K/km and stays ≤ 2 K/km on average through the next 2 km — the second clause guards against false hits on shallow stable layers. The cold point is simply the temperature minimum, and it is the definition that matters in the deep tropics, where it marks the top of the tropical tropopause layer.
Column relative humidity (CRH) — the mass-weighted column saturation fraction, ∫q dp ⁄ ∫qsat dp — is reported alongside a mid-level (700–500 hPa) mean. In the tropics CRH, not CAPE, is what convective onset scales with: deep convection switches on sharply once the column is moist enough that entrainment stops killing updrafts. Mid-level RH is the layer that does most of that entraining.
Beyond the height-coloured trace and Bunkers left/right movers, three things are worth knowing:
The PBL top is found by scanning virtual potential temperature upward for the first level exceeding the surface value by 0.5 K — the top of the well-mixed layer. It's reported as both a height AGL and a pressure. A 12Z sounding taken just after sunrise typically shows a shallow nocturnal layer (a few hundred metres); by 00Z, after a day of surface heating, it deepens dramatically.
SHARPlib ships a winter module, and the explorer surfaces it:
Every station's entire period of record is pulled from IGRA (up to ~90 MB per station, reaching back to the 1930s), every historical sounding is analyzed, and the results are reduced to monthly percentile breakpoints plus record extremes with their year. That drives the color-coding in the panel (blue below the median, red above, saturating toward the tails, with a ★ on an outright record) and the record watch on the map — a red ring around any live station whose latest sounding is beyond the 5th or 95th percentile.
A value is only marked when it is genuinely unusual. Anything between the 10th and 90th percentile is left completely plain — a "P53" badge tells you nothing except that the reading is ordinary, and tinting every row would drown the cases that matter.
| What you see | What it means |
|---|---|
| Nothing — plain text | Between the 10th and 90th percentile. Unremarkable for this station, on this day of the year. |
Red tint + P93 |
Above the 90th percentile. The tint ramps within the tail — P91 is a whisper, P99 is loud — so intensity tracks how extreme it really is, not merely how far from the median. |
Blue tint + P4 |
Below the 10th percentile, shaded the same way. |
| ★ with a year | An outright record — the highest (or lowest) value ever observed at this station near this date, with the year it was set. |
Hovering a marked value gives the exact percentile. Nothing is flagged unless that index has at least 30 samples in the window: a "record" drawn from four soundings is noise, and the app used to do exactly that until a Brazilian sounding was flagged as a July ECAPE record on the strength of a four-sounding climatology.
The 📊 Climatology button opens the full annual picture for any ranked variable. It answers a different question from the badge: not "is today unusual?" but "what does a year look like at this station, and where does today sit inside it?"
| Element | Meaning |
|---|---|
| Inner shaded band | 25th–75th percentile — the middle half of all soundings. |
| Outer shaded band | 10th–90th percentile — the ordinary range. |
| Pale line | The median. |
| Red envelope | The record high for each day of the year — the outer limit of what this station has ever produced. |
| Blue envelope | The record low, likewise. |
| Gold dot | The sounding currently on display, placed on its own day of the year. |
The gap between the shaded bands and the record envelopes is itself informative: where they sit close together the station is well-behaved, and where the envelope flares far above the 90th percentile the station is capable of rare, violent excursions in that variable.
The curves are day-of-year, not monthly: each of 73 anchor points pools a ±10-day window across every year on record. So a sounding from 11 July is compared with early-July weather, not with everything that happened between 1 and 31 July — which matters most in spring and autumn, when the climate is moving fast within a single month.
Eleven indices carry a climatology: precipitable water, the 850/700/500 hPa temperatures, 500 hPa height, 1000–500 hPa thickness, freezing level, K-index, Total Totals, ECAPE and SHIP. The first nine are exact profile quantities; the last two require the full parcel model, which is why they rest on fewer samples — always worth a glance before leaning on one.
Everything is open — the repository is a plain GitHub Pages site, so what you're reading and what runs in your browser are the same files. Browse the whole repository →
| File | What it does |
|---|---|
skewt_wasm.cpp |
The single source of truth for the physics. A C++ wrapper over SHARPlib — parcels, ECAPE, PBL top, shear, SRH, composites — compiled once to WebAssembly for the browser and again natively for the climatology, so the two can never disagree. |
app.js |
The entire client: data sources and parsers (SPC, IEM, UW mirror, IGRA), the WASM bridge, and every chart — skew-T, hodograph, moist static energy — drawn on canvas. Tropopause, column RH and wet-bulb zero are computed here. |
build_climo.py |
Per-station climatology: downloads a station's full period of record, analyses every historical sounding, reduces to monthly percentiles and records. Contains the quality gates. |
climo_cape.cpp |
Native build of the same wrapper, so ECAPE and SHIP in the climatology are computed by identical code to the live values. |
flag_anomalies.py |
The record watch — compares each new sounding to its station's climatology and flags the tails. |
mirror_soundings.py
· archive_days.py |
Mirrors the University of Wyoming feed (which sends no CORS headers) and bundles complete UTC days into the permanent archive. |
build_wasm.sh |
Compiles SHARPlib to WebAssembly with Emscripten. |
skewt-data.yml |
The scheduled job: mirror → archive → flag anomalies → publish, six times a day. |
The data lives on orphan branches that GitHub serves CORS-open, so the browser can read them directly:
skewt-data (latest soundings),
skewt-archive (permanent day bundles), and
skewt-climo (per-station climatology).
The physics itself is SHARPlib,
which is Kelton Halbert's, not mine.