← Natura·Docs/Modules/Source

organisms.js

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

// organisms.js -- a GENERAL Natura LIFE system: a population of ENTITIES (animals AND phototroph plants alike) that
// each wear the shared ontology VERBS, driven by the world's LOCAL sun and fields. Render-FREE (touches no canvas).
//
// One system, many biomes: a biome supplies a SPECIES REGISTRY -- data plus a small render-free behave() -- and this
// system gives every entity the shared lifecycle for free: its activity read from its CHRONOTYPE against the LOCAL
// sun (sampled from a structure field if present, else the world's clock/celestial), and access to the OTHER species
// (for forage/flee). Entities are layered STATE BAGS per SCHEMA.md (kind/species/pos/vel/state), not a class tree;
// a plant is just an entity with a phototroph chronotype and a growth behave. Follows the Natura system interface
// (init/step/fields) so it composes in a World and couples via ctx.fields. Coordinates are the biome's own [0,1]
// space (the renderer maps to pixels); the system never assumes a grid. UMD (node / window.Organisms).
(function (root, factory) {
  if (typeof module !== 'undefined' && module.exports) module.exports = factory(require('./verbs'));
  else root.Organisms = factory(root.Verbs);
})(typeof self !== 'undefined' ? self : this, function (Verbs) {
  'use strict';
  const TAU = Math.PI * 2, clamp = (v, a, b) => v < a ? a : v > b ? b : v;

  // resolve the world's AMBIENT sun elevation [-1,1] from whatever the coupling provides (celestial > explicit sun >
  // the clock's day phase). A structure field may override this PER-POSITION via sunAt(x,y) in step().
  function ambientSun(ctx) {
    const F = ctx.fields || {};
    if (F.celestial && F.celestial.sunAltitude != null) return Verbs.sunFromSinAlt(Math.sin(F.celestial.sunAltitude));
    if (F.sun && F.sun.value != null) return clamp(F.sun.value, -1, 1);
    if (ctx.clock && ctx.clock.dayPhase) return Verbs.sunFromDayPhase(ctx.clock.dayPhase());
    return 1;
  }

  // makeOrganisms({ name?, species, structure? }) -> a Natura system spec.
  //   species  = { name: { chronotype, count, band:[y0,y1], spawn(entity,rng)?, behave(entity, api)? } }
  //   structure = the name of a coupled field system exposing sunAt(x,y) (e.g. a canopy) -- optional.
  // behave(entity, api): api = { dt, ctx, of(name)->entities, struct, sun0, Verbs, clock }. Render-FREE.
  function makeOrganisms(config) {
    config = config || {};
    const species = config.species || {};
    const names = Object.keys(species);
    const structName = config.structure || null;

    return {
      name: config.name || 'organisms',
      domain: 'life',
      scale: 'local',
      params: config,

      init(rng, params, clock) {
        const entities = [], bySpecies = {}; let id = 0;
        for (const nm of names) {
          const s = species[nm]; bySpecies[nm] = [];
          const n = s.count || 0, band = s.band || [0, 1];
          for (let i = 0; i < n; i++) {
            const e = {
              id: id++, kind: 'organism', species: nm, chronotype: s.chronotype || 'diurnal',
              pos: [rng(), band[0] + rng() * (band[1] - band[0])], vel: [0, 0], ph: rng() * TAU,
              state: { active: 1, rest: 0 }
            };
            if (s.spawn) s.spawn(e, rng);
            entities.push(e); bySpecies[nm].push(e);
          }
        }
        return { entities: entities, bySpecies: bySpecies };
      },

      step(state, ctx, dt) {
        const struct = (structName && ctx.fields) ? ctx.fields[structName] : null;
        const sun0 = ambientSun(ctx);
        const of = nm => state.bySpecies[nm] || [];
        const api = { dt: dt, ctx: ctx, of: of, struct: struct, sun0: sun0, Verbs: Verbs, clock: ctx.clock };
        for (const e of state.entities) {
          const localSun = (struct && struct.sunAt) ? struct.sunAt(e.pos[0], e.pos[1]) : sun0;   // ITS layer/region's sun
          const a = Verbs.activity(localSun, e.chronotype);
          e.state.active = a.active; e.state.rest = a.rest; e.state.sun = localSun;              // inherited lifecycle: wear the verb
          const s = species[e.species]; if (s.behave) s.behave(e, api);                          // the species' own render-free act
        }
        return state;
      },

      fields(state) {
        return {
          entities: state.entities, bySpecies: state.bySpecies, count: state.entities.length,
          of: nm => state.bySpecies[nm] || []
        };
      }
    };
  }

  return { makeOrganisms };
});