Logbook · Entry 06Derivation evolution6 min read

Retiring 121 metres per degree

Every cloud base the forecast engine published climbed exactly 121 m for each degree of surface dew-point depression. Where did that number come from? A nineteenth-century observation, by way of canadarasp — and cloud base was the last published quantity still computed by a rule of thumb. Replacing it looked like tidiness: an exact lifted condensation level, a floor drawn from the model’s own moisture column, a few dozen metres of correction. Then the re-derivation turned up the consequence that justified the whole exercise — afternoon hours that looked cloud-capped actually hit the sink crossing hundreds of metres lower. This was the engine’s first science change since the derivations became their own baseline.

An estimate with a lineage

The constant is old and honourable. Approximating condensation height as a fixed climb per degree of temperature–dew-point spread goes back to James Espy’s nineteenth-century cloud observations, and 121 m/°C is the value canadarasp’s windgram-continental.ncl used. This project inherited it the way it inherited the strongest-core updraft profile and the 1 m/s sink threshold: ported constant-for-constant, because during the schema migration the derivations were the verification oracle — the one thing deliberately held fixed while everything around them moved. That debt is recorded with gratitude in the forecast derivations; the estimate served canadarasp’s pilots for years and served this project as its proof of equivalence.

An oracle’s virtue is constancy, not precision. Once the migration closed, the linear estimate stopped being an asset and became an approximation of a quantity the forecast engine could compute exactly — from numbers it was already publishing.

The exact answer costs nothing the documents don’t already carry

The parcel LCL needs exactly two inputs: surface temperature and surface dew point. Every builder publishes both — the ECCC deterministic family, the NOAA family including the newly adopted NAM pair, and both ensembles, whose members each carry their own surface fields, so the change flows through REPS and GEPS percentiles with no per-model work at all. The derivation stays one shared function of the source column; no builder was touched.

Between the exact formulas, pragmatics decided. Romps (2017) gives the closed-form LCL, but it needs the Lambert W function — a dependency, in a pipeline that is deliberately standard-library-only. Bolton (1980, eq. 15) gives the LCL temperature explicitly from temperature and dew point, accurate to 0.1 K, and agrees with Romps to about 1% in height across the meteorological range. The code cites both and uses Bolton; the equation and its constants now live in the derivation reference and in forecast/src/derive.ts.

Believe the column when it shows cloud below the parcel

A parcel LCL answers one question: where would air lifted from the model surface condense. The model column answers another: where has the model already put cloud. When a published level saturates below the parcel LCL — dew-point depression down at the same 0.5 °C the renderer hatches as dense cloud, with the crossing interpolated between samples — a climb meets cloud there, whatever the surface parcel says. So the published cloudBaseM is now the lower of the two answers, clamped to model terrain.

The threshold is deliberately the hatch threshold. Before this change 0.5 °C was a display choice; now it is load-bearing: the cloud-base line can never sit above a layer the chart cross-hatches as saturated, so the two moisture signals can no longer contradict each other on height.

What changed, measured

Both real columns were re-derived old-versus-new: the 15-hour HRDPS column behind the package’s pipeline-parity fixture and the 10-hour HRRR column behind the site’s fixed forecast example. Both are dry columns — no hour triggers the saturated-layer floor, which is exercised by synthetic tests instead — so the measured shift is pure Bolton-versus-121:

ColumnHoursCloud-base shift, new − old (min / median / max)
HRDPS 2.5 km, Dundee15+34.9 / +42.1 / +58.2 m
HRRR CONUS, Red Mtn10+36.3 / +45.2 / +51.1 m

The direction is uniform: at these warm surface temperatures the true condensation slope runs nearer 124 m/°C, so the inherited 121 sat 35–58 m low. A modest correction — and not the interesting one.

The interesting one is what the low cloud base had been hiding. The usable-lift derivation walks the retained levels and, on reaching one above cloud base, publishes the cap — trusting lift to persist that far. Four hours across the two columns changed:

Hour (UTC)ColumnOld usableLiftTopMNew usableLiftTopMChange
08-08 19:00HRDPS3,733.8 m (cloud-capped)3,254.3 m (sink crossing)−479.5 m
08-08 20:00HRDPS3,759.0 m (cloud-capped)3,659.9 m (sink crossing)−99.1 m
08-08 21:00HRDPS4,118.2 m (cloud-capped)4,173.5 m (cloud-capped)+55.3 m
08-08 22:00HRRR3,037.1 m (cloud-capped)3,086.5 m (cloud-capped)+49.4 m

In the two de-capped hours, the old cloud base sat just below the 650 hPa level, so the scan stopped and published the cap without ever evaluating the core there. Raising cloud base ~56 m let the scan reach that level, find the strongest core already dead, and interpolate the real sink crossing — 480 m below what the chart had been advertising at 19:00. The old answer was not 56 m optimistic; it was hiding a crossing half a kilometre down. That is the de-capping finding, and it is the reason an exact cloud base matters beyond tidiness: the cap is only as accurate as the base that draws it.

The usable-lift derivation itself is untouched — same profile, same constants, as Why usable lift can sit above the boundary layer describes. Every shift above enters through its cloud-base input alone. Boundary-layer top and thermal velocity are moisture-free and did not move.

See every hour’s shift below — the uniform cloud-base rise, and the two rust bars where the cap came off.

What the exact cloud base moved

Metres of change per forecast hour, new − old, on two real recorded columns.

HRDPS 2.5 km · Dundee: cloud base rises 34.9 to 58.2 m; usable-lift changes 19Z -479.5 m (de-capped), 20Z -99.1 m (de-capped), 21Z +55.3 m. HRRR CONUS · Red Mtn: cloud base rises 36.3 to 51.1 m; usable-lift changes 22Z +49.4 m.

Per-hour change in published cloud base and usable-lift top after replacing the 121 m per degree estimate with the Bolton LCL HRDPS 2.5 km · Dundee: cloud base rises 34.9 to 58.2 m; usable-lift changes 19Z -479.5 m (de-capped), 20Z -99.1 m (de-capped), 21Z +55.3 m. HRRR CONUS · Red Mtn: cloud base rises 36.3 to 51.1 m; usable-lift changes 22Z +49.4 m.Δ metrescloud-base shiftusable-lift shift (where it changed)HRDPS 2.5 km · DundeeHRDPS 2.5 km · Dundee — 14Z: cloud base +36.8 m14ZHRDPS 2.5 km · Dundee — 15Z: cloud base +40.1 mHRDPS 2.5 km · Dundee — 16Z: cloud base +49.1 mHRDPS 2.5 km · Dundee — 17Z: cloud base +51.0 m17ZHRDPS 2.5 km · Dundee — 18Z: cloud base +53.7 mHRDPS 2.5 km · Dundee — 19Z: cloud base +55.9 m HRDPS 2.5 km · Dundee — 19Z: usable-lift top -479.5 m — de-capped: the old value was pinned at cloud base; the sink crossing sits lower -479.5HRDPS 2.5 km · Dundee — 20Z: cloud base +58.2 m HRDPS 2.5 km · Dundee — 20Z: usable-lift top -99.1 m — de-capped: the old value was pinned at cloud base; the sink crossing sits lower -99.120ZHRDPS 2.5 km · Dundee — 21Z: cloud base +55.3 m HRDPS 2.5 km · Dundee — 21Z: usable-lift top +55.3 m HRDPS 2.5 km · Dundee — 22Z: cloud base +46.4 mHRDPS 2.5 km · Dundee — 23Z: cloud base +38.6 m23ZHRDPS 2.5 km · Dundee — 00Z: cloud base +38.5 mHRDPS 2.5 km · Dundee — 01Z: cloud base +38.0 mHRDPS 2.5 km · Dundee — 02Z: cloud base +42.1 m02ZHRDPS 2.5 km · Dundee — 03Z: cloud base +37.8 mHRDPS 2.5 km · Dundee — 04Z: cloud base +34.9 mHRRR CONUS · Red MtnHRRR CONUS · Red Mtn — 19Z: cloud base +36.3 m19ZHRRR CONUS · Red Mtn — 20Z: cloud base +42.1 mHRRR CONUS · Red Mtn — 21Z: cloud base +45.9 mHRRR CONUS · Red Mtn — 22Z: cloud base +49.4 m HRRR CONUS · Red Mtn — 22Z: usable-lift top +49.4 m 22ZHRRR CONUS · Red Mtn — 23Z: cloud base +51.1 mHRRR CONUS · Red Mtn — 00Z: cloud base +49.0 mHRRR CONUS · Red Mtn — 01Z: cloud base +46.1 m01ZHRRR CONUS · Red Mtn — 02Z: cloud base +44.5 mHRRR CONUS · Red Mtn — 03Z: cloud base +41.7 mHRRR CONUS · Red Mtn — 04Z: cloud base +41.6 m04Z
Every hour's cloud base rises 35–58 m — Bolton against the retired linear estimate, on dry columns where the saturated-layer floor never fires. The two rust bars falling far below zero are the de-capped hours: usable lift previously published at cloud base, now published at the sink crossing the cap had concealed. A dated record: these values were measured on 2026-08-08, the entry's date, by re-deriving both recorded model columns (HRDPS and HRRR) through the pipeline's derive.py as it stood that day — recorded here by hand.Units Δ metres · hours UTC

What stays fixed

Published history is append-only: documents already in the archive keep the values they were built with, and the renderer keeps drawing stored documents byte-for-byte — the reference goldens did not change. The parity fixture, whose contract is “current pipeline output, unrounded”, was regenerated, and the package’s parameterized usableLiftTopM still reproduces the forecast engine’s published value float-exactly at the default sink rate. From the next run onward, every model — deterministic and ensemble alike — publishes the exact base.