Skip to content

Add a GFS storm-potential (surface CAPE) overlay#13

Merged
johncarmack1984 merged 1 commit into
mainfrom
hazard-forecast
Jun 27, 2026
Merged

Add a GFS storm-potential (surface CAPE) overlay#13
johncarmack1984 merged 1 commit into
mainfrom
hazard-forecast

Conversation

@johncarmack1984

@johncarmack1984 johncarmack1984 commented Jun 27, 2026

Copy link
Copy Markdown
Owner

Summary

A new storm potential layer (default-off) shading where the atmosphere is primed for thunderstorms — GFS surface CAPE (J/kg) — alongside the live NWS alerts, which stay the authoritative "now" hazard layer.

CAPE is decoded from the same NOAA GFS GRIB2 source as temperature/wind/precip (no new external source) and shipped as a grayscale equirectangular texture over 0…5000 J/kg that the web denormalizes and colormaps on the GPU, faded out below ~250 J/kg. It rides the shared forecast-hour timeline, so one scrub moves the grid, cities, wind, precipitation, and storm potential together.

  • ingest: factored the per-step REFC fetch into a generic fetch_scalar_tex helper now shared by refc + cape; cape writes capetex/{snapshot}/{hour}.png (immutable) + capetex/latest.json (CapeTexIndex). CAPE bounds live in gfs.rs; cape/all added to job dispatch.
  • web: CapeRasterLayer (mirrors RefcRasterLayer) + a cape layer with a CAPE colormap and opacity control; useCapeTex feed; registered in the layer list.
  • cdk: cape schedule (6 h, one per GFS cycle) + capetex/ lifecycle (2-day expiry).
  • deploy: prime cape after cdk deploy so a brand-new feed never 404s.

Also fixes a latent prod bug (precip GFS-forecast raster)

While verifying, the CAPE raster rendered blank — traced to luma deriving the bound UBO block name as `${module.name}Uniforms` (shadertools/getShaderModuleUniformBlockName). The equirect raster layers used module name 'raster' with refcUniforms/capeUniforms blocks, so the UBO never bound — uniforms read 0, every fragment discards, blank. Wind happened to be correct ('raster' + rasterUniforms). Renaming the modules to 'refc'/'cape' (matching their blocks) fixes CAPE and the precipitation GFS-forecast raster, which had the same mismatch — live radar was unaffected, so it slipped through.

Verification

  • ingest: just weather local cape produced 57 valid forecast steps (CAPE values: ~16.5% of the globe > 250 J/kg, max 5000).
  • Rendering confirmed in-browser (and now headless too, post-fix): the CAPE colormap and the precip-forecast raster both paint, console clean of the Uniforms not found warnings.
  • tsc, biome, web build, CDK tsc, cargo fmt --check, clippy -D warnings, and the specta type drift-check all pass.

Docs & attribution

  • README updated (intro, architecture diagram, GFS notes, Configuration schedules row, Attribution).
  • On-map attribution (web/src/App.tsx) now reads "Temps, wind, precip & storm forecast: NOAA GFS".
  • Every new external source is credited — no new source (CAPE is from the already-credited NOAA GFS); wording in both places now includes storm potential.
  • N/A

@johncarmack1984 johncarmack1984 added the enhancement New feature or request label Jun 27, 2026
A new default-off layer shading where the atmosphere is primed for
thunderstorms, alongside the live NWS alerts. Surface CAPE (J/kg) is
decoded from the same GFS GRIB2 source as temperature/wind/precip and
shipped as a grayscale equirectangular texture (0..5000 J/kg) that the
web colormaps on the GPU and scrubs on the shared forecast timeline.

- ingest: generic fetch_scalar_tex helper (shared by refc + cape) writes
  capetex/{snapshot}/{hour}.png + capetex/latest.json; CapeTexIndex
  contract type; CAPE bounds in gfs.rs; cape/all job dispatch.
- web: CapeRasterLayer (mirrors RefcRasterLayer) + cape layer with a CAPE
  colormap and opacity control; useCapeTex feed.
- cdk: cape schedule (6 h) + capetex/ lifecycle (2-day expiry).
- deploy: prime cape after stack deploy so the feed never 404s.
- docs: README + on-map attribution cover storm potential / GFS CAPE.

Also fix the equirect raster UBO binding: luma derives the bound block
name as `${module.name}Uniforms`, so a 'raster' module name with a
refcUniforms/capeUniforms block silently never binds (uniforms read 0 →
everything discards → blank). Name the modules 'refc'/'cape' to match.
This also repairs the precip GFS-forecast raster, which had the same
latent mismatch (live radar was unaffected).
@johncarmack1984 johncarmack1984 merged commit 0199c89 into main Jun 27, 2026
7 checks passed
@johncarmack1984 johncarmack1984 deleted the hazard-forecast branch June 27, 2026 03:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant