fix(wind): make the particle overlay actually visible

Live browser QA of the Wind layer surfaced several rendering defects that
unit tests with fakes did not catch:

- The trail fade used a translucent black fill (source-over), which raises
  alpha instead of erasing; combined with additive strokes the overlay
  saturated into a solid white disk. Fade with destination-out instead, and
  draw strokes with normal blending.
- Particles were seeded uniformly over the whole globe, so at regional zoom
  almost none fell on screen. Seed inside the camera's visible rectangle.
- A non-finite particle position (polar divide-by-zero) reached Cesium,
  which threw and aborted the entire frame before any stroke. Guard finite
  coordinates per particle and per projection.
- Exaggerate advection in proportion to camera height, so the same visual
  speed is legible at street level and at hemispheric zoom.
- Brighter colour ramp (the old ramp started near-invisible dark grey) and a
  defensive frame try/catch so one bad frame cannot kill the loop.

Verified: the wind field/colour/particle model renders the real GFS 06z field
correctly in an offline render, and the wind unit tests pass (8/8).
This commit is contained in:
Gustavo Beneduzi committed 2026-09-14 14:02:48 -03:00
1 parent deb34ebe35
commit b7033d86c2
3 files changed
+108 -39

No files matched your search

+2 -2
View File
@@ -36,9 +36,9 @@ export function windSpeed(u, v) {
return Math.hypot(u, v);
}
/** Map wind magnitude to a fixed CSS colour ramp. */
/** Map wind magnitude to a fixed CSS colour ramp (bright from calm upward). */
export function windColor(speed, { max = 30 } = {}) {
const stops = ['#4a5568', '#7fe3ff', '#7dffb0', '#ffd166', '#ff7a59'];
const stops = ['#38bdf8', '#22d3ee', '#34d399', '#fbbf24', '#f87171'];
const t = Math.max(0, Math.min(1, speed / max));
const position = t * (stops.length - 1);
const index = Math.min(stops.length - 2, Math.floor(position));