verbs/flee.js
40 lines · in natura-src-1.0.0.zip as natura/verbs/flee.js · a module · its entry in the module reference
// verbs/flee.js -- ONE life ontology verb, one concern: move AWAY from a local threat (the inverse of forage). Pure
// and RENDER-FREE. Generalized from the CANONICAL herbivore flight in ecosystem.js (a grazer sums an away-vector
// from predators within a NEAR radius, goes WARY at a wider PERCEIVE radius) -- lifted OUT of that one system so
// every life system shares it instead of re-implementing it inline (the bad habit these steers are correcting).
// Universal: a butterfly from a hunting bird, a herd from a wolf, a shoal from a shark. UMD.
(function (root, factory) {
if (typeof module !== 'undefined' && module.exports) module.exports = factory();
else (root.NaturaVerbs = root.NaturaVerbs || {}).flee = factory();
})(typeof self !== 'undefined' ? self : this, function () {
'use strict';
// flee(pos, threats, opts) -> { fx, fy (escape vector), wary, fleeing, dist }
// pos = {x,y} ; threats = [{x,y}] ; opts = {
// near:0.12 -- bolt inside this radius
// perceive:near*2 -- go wary (but not yet bolt) inside this radius
// raw:false -- false: sum UNIT away-directions (each threat equal weight) and return a NORMALIZED unit
// escape vector (a butterfly, a bird -- direction is all that matters). true: sum the RAW
// away-displacements (a nearer threat within `near` pushes softer than a farther one -- the
// distance-weighted herd flight tuned in ecosystem.js) and return that sum UN-normalized.
// delta:(pos,threat)->[dx,dy] -- the away-vector metric; default euclidean. A WRAPPED world passes its own
// toroidal delta so the verb stays geometry-agnostic (it never assumes a flat plane).
// }
function flee(pos, threats, opts) {
opts = opts || {};
const near = opts.near != null ? opts.near : 0.12, perceive = opts.perceive != null ? opts.perceive : near * 2;
const raw = !!opts.raw, delta = opts.delta || euclid;
const n2 = near * near, p2 = perceive * perceive;
let fx = 0, fy = 0, wary = false, fleeing = false, nd = Infinity;
for (let i = 0; i < threats.length; i++) {
const d = delta(pos, threats[i]), dx = d[0], dy = d[1], d2 = dx * dx + dy * dy;
if (d2 < p2) { wary = true;
if (d2 < n2) { fleeing = true; nd = Math.min(nd, d2);
if (raw) { fx += dx; fy += dy; } // distance-weighted, left un-normalized
else { const dm = Math.sqrt(d2) || 1e-4; fx += dx / dm; fy += dy / dm; } } } // equal-weight unit directions
}
if (!raw) { const m = Math.sqrt(fx * fx + fy * fy) || 1; fx /= m; fy /= m; } // normalize only the unit-mode escape
return { fx: fx, fy: fy, wary: wary, fleeing: fleeing, dist: Math.sqrt(nd) };
}
function euclid(p, t) { return [p.x - t.x, p.y - t.y]; }
return { flee };
});