Methodology

How the Sounding Explorer works

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 explorer

1. Where the data comes from

A 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.

SourceCoverageLatencyResolution
SPC observedUSFastest — ~1 h post-synoptic~110–200 levels
IEM RAOBUS + Canada~1 h behind SPC~180–250 levels
UW mirrorGlobal6×/day cronFull BUFR
NOAA IGRA v2Global, 1905–now~1–2 day lagMandatory + significant

The physics floor on "real-time"

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.

Why Wyoming is mirrored, not fetched. The University of Wyoming archive sends no 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.

2. The physics engine

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.

3. ECAPE — entraining CAPE

The problem with textbook CAPE

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.

What ECAPE does 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 can exceed CAPE — and that isn't a bug. The implementation returns Ẽ × CAPE, where the factor carries a storm-relative kinetic-energy term alongside the entrainment penalty. When storm-relative inflow is strong, the updraft can draw on that kinetic energy, and the ratio rises above 1. So the ECAPE/CAPE ratio is not a pure "efficiency" bounded at 100% — it is entrainment loss and inflow gain netted together. A ratio of 55% means entrainment dominates; 105% means vigorous storm-relative flow is more than paying for the mixing.

The three flavors

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 quantityFormulaWhat 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 / CAPEratioEntrainment 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.

4. Moist static energy

ECAPE is built on moist static energy, so the explorer plots it directly.

h = cpT + gz + Lvq

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.

Fort Worth, 11 Jul 2026 12Z. Boundary-layer h (344.1 kJ/kg, gold) exceeds the environment's saturation MSE (red) through the shaded layer — the parcel is buoyant there. The minimum h* of 337.6 kJ/kg at 4.3 km gives an MSE deficit of −6.5 kJ/kg: conditional instability, and the energetic expression of this sounding's ~989 J/kg of CAPE.
ValueMeaning
MSE (0–500 m)Boundary-layer h — the energy a surface parcel carries aloft
Min saturation MSEThe 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 MSEThe vertical integral (1/g)∫h dp, in GJ/m² — the state variable of tropical MSE-budget theory

Tropopause and column moisture

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.

A worked check. Ponape, Caroline Islands (deep tropics): WMO tropopause 16.2 km / 108 hPa, cold point 16.1 km at −81 °C, CRH 80%. The lifted parcel's equilibrium level came out at 16.4 km — within 200 m of the independently computed tropopause, which is exactly what should happen when an undilute tropical parcel rises until the stratosphere stops it. Two unrelated calculations agreeing is the best evidence either one is right.

Reading the hodograph

Beyond the height-coloured trace and Bunkers left/right movers, three things are worth knowing:

Boundary-layer depth

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.

Winter

SHARPlib ships a winter module, and the explorer surfaces it:

5. Climatology — where a value ranks

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.

Reading the colour shading

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 seeWhat it means
Nothing — plain textBetween 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 yearAn 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 panel

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?"

ElementMeaning
Inner shaded band25th–75th percentile — the middle half of all soundings.
Outer shaded band10th–90th percentile — the ordinary range.
Pale lineThe median.
Red envelopeThe record high for each day of the year — the outer limit of what this station has ever produced.
Blue envelopeThe record low, likewise.
Gold dotThe 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.

What is ranked

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.

Two decisions that matter

A non-convective sounding has ECAPE of zero — not "missing." It is tempting to record ECAPE only when a parcel has positive CAPE. That is wrong: it makes the climatology conditional on convective days, so a "10th-percentile ECAPE" would describe only the days convection happened. One Argentine station had 51 such January samples against 2,007 for precipitable water — the percentiles were meaningless. Valid soundings with no CAPE now correctly contribute zeros, which is what makes "a 95th-percentile ECAPE day" mean anything at all.
Never fabricate missing data. Radiosonde reporting is far from uniform: much of South America reports temperature on ~99% of levels but dewpoint on only ~39%, with as few as 12 levels per sounding. Filling those gaps with plausible-looking guesses silently produces confident, wrong numbers — it manufactured absurd 0.5 mm precipitable-water values and destroyed CAPE across an entire continent. The builder now refuses to invent data: a sounding qualifies only with a real surface dewpoint, ascent to at least 400 hPa, and dewpoint on ≥60% of levels below that. Genuine gaps inside a qualifying profile are interpolated in log-pressure; everything else is skipped rather than fudged. The cost is a smaller sample. The benefit is that the sample is real.

6. The code

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 →

FileWhat 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.

7. What it can't do

← Open the explorer