← Natura·Docs/Modules/Source

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