← Natura·Docs/Modules/Source

weather-patch.js

44 lines · in natura-src-1.0.0.zip as natura/weather-patch.js · a module · its entry in the module reference · its test: weather-patch-test.js

// weather-patch.js -- the LOCAL SKY over a single place, a render-free Natura system. The grid weather.js models a
// whole map's pressure fronts + orographic rain; this is its PATCH-scale sibling: one spot's CLOUD cover, RAIN, and
// soil MOISTURE, drifting over time and exposed as fields the rest of the world reads -- a cloud dims the canopy
// light, rain wets the ground, moisture makes the flowers bloom and the fruit ripen. "Weather that changes the world
// it falls on," at the scale of a clearing. Deterministic: cloud drifts as a sum of slow sines at incommensurate
// (seeded) periods, so it never repeats but always reproduces; moisture integrates the rain and drains when dry.
// It runs on REAL elapsed time (ctx dt), independent of the day cycle, so wet and dry spells come and go over minutes
// while the day turns on its own clock. UMD (node / window.WeatherPatch).
(function (root, factory) {
  if (typeof module !== 'undefined' && module.exports) module.exports = factory();
  else root.WeatherPatch = factory();
})(typeof self !== 'undefined' ? self : this, function () {
  'use strict';
  const TAU = Math.PI * 2, clamp = (v, a, b) => v < a ? a : v > b ? b : v;

  function makeWeatherPatch(config) {
    config = config || {};
    const wetGain = config.wetGain != null ? config.wetGain : 0.06;    // moisture gained per second of full rain
    const dryLoss = config.dryLoss != null ? config.dryLoss : 0.01;    // moisture lost per second when dry
    const rainThresh = config.rainThresh != null ? config.rainThresh : 0.62;   // cloud cover at which rain starts
    const periods = config.periods || [41, 23, 67, 13];                // incommensurate cloud-drift periods (seconds)

    return {
      name: config.name || 'weather', domain: 'physical', scale: 'local', params: config,

      init(rng) {
        const ph = []; for (let i = 0; i < periods.length; i++) ph.push((rng ? rng() : 0.3) * TAU);
        return { tt: 0, ph: ph, cloud: 0.3, rain: 0, moisture: 0.5 };
      },
      step(state, ctx, dt) {
        state.tt += dt;
        let c = 0.5;                                                    // drift between clear and overcast
        for (let i = 0; i < periods.length; i++) c += (0.28 - i * 0.05) * Math.sin(state.tt * TAU / periods[i] + state.ph[i]);
        state.cloud = clamp(c, 0, 1);
        state.rain = clamp((state.cloud - rainThresh) * 2.6, 0, 1);     // rain only under a heavy enough sky
        state.moisture = clamp(state.moisture + (state.rain * wetGain - dryLoss) * dt, 0, 1);   // wets in rain, drains when dry
        return state;
      },
      fields(state) { return { cloud: state.cloud, rain: state.rain, moisture: state.moisture }; }
    };
  }

  return { makeWeatherPatch };
});