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 };
});