verbs/daynight.js
51 lines · in natura-src-1.0.0.zip as natura/verbs/daynight.js · a module · its entry in the module reference
// verbs/daynight.js -- ONE life ontology verb, one concern: an organism's day/night ACTIVITY (wake <-> rest)
// read from the LOCAL sun by its CHRONOTYPE. Pure and RENDER-FREE (no canvas, no DOM) -- a life system calls it.
//
// `sun` in [-1,1] is the local sun elevation (-1 deep dark, 0 twilight, +1 overhead). The CALLER samples it from
// the world's real sun -- celestial.js `sunAltitude` (its sine is already ~[-1,1]) or the clock's dayPhase -- at the
// entity's OWN place (its latitude/longitude via a region phase, its canopy/depth via a shade debt). The physics of
// where the sun is lives UPSTREAM (celestial/clock); this maps local-sun -> activity, and nothing more. It applies
// to ALL life, not just animals: a phototroph plant is "active" (open, fixing carbon) exactly when the sun is up.
// (Consolidates the day/night logic that was scattered across art pieces; the small-file sibling of forage/flee.) UMD.
(function (root, factory) {
if (typeof module !== 'undefined' && module.exports) module.exports = factory();
else (root.NaturaVerbs = root.NaturaVerbs || {}).daynight = 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;
const CHRONOTYPES = ['diurnal', 'nocturnal', 'crepuscular', 'phototroph', 'cathemeral', 'sessile-day', 'sessile-night'];
// activity(sun, chronotype) -> { active, rest, day, night, low, sun }. The whole verb is this one reading.
function activity(sun, chronotype) {
const day = Math.max(0, sun), night = Math.max(0, -sun), low = clamp(1 - Math.abs(sun), 0, 1);
let a;
switch (chronotype) {
case 'nocturnal': a = clamp(night + low * 0.6, 0, 1); break; // works the dark + twilight
case 'crepuscular': a = low; break; // peaks at dawn/dusk, dips at noon AND midnight
case 'phototroph': a = day; break; // photosynthesis follows the sun directly
case 'cathemeral': a = 0.5 + 0.16 * sun; break; // a faint preference, never fully at rest
case 'sessile-night': a = clamp(night + low * 0.4, 0, 1); break;
case 'sessile-day':
case 'diurnal':
default: a = clamp(day + low * 0.4, 0, 1); // works the day + a little twilight
}
a = clamp(a, 0, 1);
return { active: a, rest: 1 - a, day: day, night: night, low: low, sun: sun };
}
// sun ADAPTERS -- turn whatever the world hands you into the [-1,1] elevation this verb reads.
const sunFromDayPhase = p => (p == null ? 1 : -Math.cos(p * TAU)); // 0 midnight -> -1, 0.25 dawn -> 0, 0.5 noon -> +1
const sunFromTod = sunFromDayPhase; // tod == dayPhase, kept for callers that say "tod"
const sunFromSinAlt = s => (s == null ? 1 : clamp(s, -1, 1)); // celestial's sin(sunAltitude), already ~[-1,1]
// LOCAL reads (space): shade = canopy/depth light-debt (subtracts from sun -> a dimmer AND shorter local day, never
// faked darkness); phase = the place's own dawn offset (region/longitude, shifts the day so east wakes before west).
function atTod(tod, chronotype, opts) {
if (tod == null) return { active: 1, rest: 0, day: 1, night: 0, low: 0, sun: 1 }; // no clock -> awake/open
opts = opts || {};
return activity(sunFromDayPhase(tod + (opts.phase || 0)) - (opts.shade || 0), chronotype);
}
const restAtTod = (tod, ct, opts) => atTod(tod, ct, opts).rest;
return { activity, atTod, restAtTod, sunFromDayPhase, sunFromTod, sunFromSinAlt, CHRONOTYPES };
});