← Natura·Docs/Modules/Source

creatures/behaviour.js

67 lines · in natura-src-1.0.0.zip as natura/creatures/behaviour.js · a module · its entry in the module reference

// creatures/behaviour.js -- CreatureBehaviour: the DECISION layer.
//
// What an animal DOES over time, kept apart from how it is drawn (creatures/render.js) and from
// what it is (creatures/species.js). A behaviour is a loop of segments; each segment holds a slice
// of the loop (f, a fraction) and the posture + movement to hold during it: adv (cross-ground move,
// 0..1, or >1 for a dash), neck/ear/stand posture, an optional peck (bird strike), and an optional
// gait override (a bolting deer swaps to the gallop mid-loop). targetOf() picks the current segment
// for a creature at loop time e; the scene eases the real pose toward it so heads raise and legs
// plant smoothly. The renderer never sees a behaviour, only the resulting pose -- so the same loop
// drives the meadow piece or a Natura-world agent identically.
//
// UMD (node test / window.CreatureBehaviour). No dependencies.
(function (root, factory) {
  if (typeof module !== 'undefined' && module.exports) module.exports = factory();
  else root.CreatureBehaviour = factory();
})(typeof self !== 'undefined' ? self : this, function () {
  'use strict';

  // a gallop gait: long stride, brief ground contact so there is an airborne suspension phase, a big loft
  const GALLOP = { stride:44, lift:18, duty:0.34, speed:2.2, gallop:true };
  const TROT   = { stride:30, lift:11, duty:0.5,  speed:1.5, trot:true };   // the diagonal 2-beat between a walk and a run

  const BEH = {
    graze: { period:16, seg:[
      {f:0.55, adv:0.0, neck:1.78, ear:-0.18, stand:1},          // head down, cropping the grass
      {f:0.13, adv:0.0, neck:-0.35, ear:0.55, stand:1},          // head snaps up, ears forward -- a check
      {f:0.32, adv:1.0, neck:0.05, ear:0.15, stand:0} ]},        // ambles on to fresh grass
    flee:  { period:19, seg:[
      {f:0.58, adv:0.0, neck:1.78, ear:-0.15, stand:1},          // grazing, calm and unaware
      {f:0.08, adv:0.0, neck:-0.5, ear:0.75, stand:1},           // ALARM -- head jerks up, ears pinned forward
      {f:0.22, adv:1.9, neck:0.5, ear:-0.1, stand:0, gait:GALLOP}, // BOLT -- gallops away, neck thrust forward
      {f:0.12, adv:0.5, neck:-0.15, ear:0.5, stand:0} ]},        // eases up, still trotting, glancing back
    stalk: { period:22, seg:[
      {f:0.78, adv:1.0, neck:0.30, ear:0.20, stand:0},           // steady low-headed padding
      {f:0.22, adv:0.0, neck:-0.10, ear:0.55, stand:1} ]},       // halts, head up, scenting the air
    freeze:{ period:11, seg:[
      {f:0.68, adv:0.0, neck:-0.18, ear:0.5, stand:1},           // frozen bolt-still, ears up, listening
      {f:0.32, adv:1.0, neck:0.10, ear:0.1, stand:0} ]},         // then a sudden bounding dash
    wade:  { period:15, seg:[
      {f:0.52, adv:0.0, neck:0, stand:1, peck:0},                // stock-still over the shallows, neck coiled
      {f:0.10, adv:0.0, neck:0, stand:1, peck:1},                // the STRIKE -- neck stabs down at a fish
      {f:0.38, adv:0.4, neck:0, stand:0, peck:0} ]},             // a few slow, deliberate wading steps
    forage:{ period:1,  seg:[ {f:1.0, adv:1.0, neck:0} ]},       // continuous walk/hop + the gait's own auto-peck
    sitforage:{ period:12, seg:[                                  // a SQUIRREL: scamper to a spot, sit up and eat, move on
      {f:0.30, adv:1.2, neck:0,    ear:0.1, stand:0, siteat:0},   // bounds to a fresh patch
      {f:0.55, adv:0.0, neck:0,    ear:0.2, stand:1, siteat:1},   // SITS UP on its haunches, food to the mouth, nibbling
      {f:0.15, adv:0.0, neck:-0.1, ear:0.6, stand:1, siteat:1} ]},// still sitting -- a quick look-round mid-meal
    dig:   { period:14, seg:[                                     // a BADGER's nature: dig, look, trundle on, dig again
      {f:0.60, adv:0.0, neck:1.95, ear:-0.2, stand:1, dig:1},     // head down, front paws scraping earth, soil flung back
      {f:0.14, adv:0.0, neck:-0.2, ear:0.6,  stand:1, dig:0},     // pauses -- head up, a quick look round, snout testing the air
      {f:0.26, adv:0.9, neck:0.85, ear:0.1,  stand:0, dig:0} ]},  // trundles low to a fresh patch, head still carried low
    roost: { period:18, seg:[                                     // a perched OWL: sits upright and watches, never walks (the head-swivel carries the life)
      {f:0.50, adv:0.0, neck:0.0,  ear:0.15, stand:1},            // sits bolt-upright, watching the meadow
      {f:0.14, adv:0.0, neck:-0.12, ear:0.5, stand:1},            // a small rouse -- lifts, alert
      {f:0.36, adv:0.0, neck:0.04, ear:0.1,  stand:1} ]},         // settles again, still perched
  };

  // Pick the active segment for creature `c` (needs c.beh + c.seed) at loop time `e` (seconds).
  function targetOf(c, e) {
    const B = c.beh; if (!B) return { adv: 1 };
    let t = ((e / B.period + c.seed) % 1 + 1) % 1, acc = 0;
    for (const s of B.seg) { acc += s.f; if (t < acc) return s; }
    return B.seg[B.seg.length - 1];
  }

  return { GALLOP, TROT, BEH, targetOf };
});