stigmergy-test.js
61 lines · in natura-src-1.0.0.zip as natura/stigmergy-test.js · a test
// stigmergy-test.js -- assertions for SELF-ORGANIZED TRAILS (deposited/decaying scent -> an emergent foraging
// highway). Run: node stigmergy-test.js
const N = require('./natura.js');
const { makeStigmergy } = require('./stigmergy.js');
const { World, UNITS } = N;
const GRID = 48, DAY = UNITS.DAY;
let pass = 0, fail = 0;
const ok = (c, m) => { if (c) pass++; else { fail++; console.log(' FAIL: ' + m); } };
const build = (cfg) => World({ seed: 4, systems: [makeStigmergy(Object.assign({ N: GRID, seed: 4, ants: 150, nest: { x: 10, y: 24 }, food: [{ x: 38, y: 24, amount: Infinity }] }, cfg))] });
const run = (w, steps) => { for (let i = 0; i < steps; i++) w.step(30 * DAY); }; // each step ~= 64 ant-ticks
console.log('stigmergy assertions:');
// 1. DETERMINISM
{
const a = build({}), b = build({}); run(a, 20); run(b, 20);
const fa = a.fields().stigmergy.foodTrail, fb = b.fields().stigmergy.foodTrail;
let same = true; for (let k = 0; k < GRID * GRID; k++) if (fa[k] !== fb[k]) { same = false; break; }
ok(same, 'same seed -> identical trail field (reproducible)');
}
// 2. THE FORAGE LOOP WORKS -- ants find the food and carry it home (collected rises)
{
const w = build({}); run(w, 40);
ok(w.fields().stigmergy.collected > 0, 'ants find the food and bring it back to the nest (collected ' + w.fields().stigmergy.collected + ')');
}
// 3. THE EMERGENCE -- a TRAIL self-organizes: the food-scent piles into a concentrated ridge, not a uniform haze
{
const w = build({}); run(w, 70); const f = w.fields().stigmergy;
ok(f.trailConcentration > 10, 'a foraging highway SELF-ORGANIZES: the food trail is a concentrated ridge (peak ' + f.trailConcentration + 'x the map average)');
}
// 4. EVAPORATION -- unfollowed scent FADES (seed a trail, run with no deposition, it decays away)
{
const w = build({ ants: 0, deposit: 0 }); const st = w.get('stigmergy');
for (let y = 22; y <= 26; y++) for (let x = 12; x <= 36; x++) st.food[y * GRID + x] = 5;
let before = 0; for (let k = 0; k < GRID * GRID; k++) before += st.food[k];
run(w, 8); let after = 0; { const ft = w.fields().stigmergy.foodTrail; for (let k = 0; k < GRID * GRID; k++) after += ft[k]; }
ok(after < before * 0.1, 'unfollowed scent EVAPORATES away (' + before.toFixed(0) + ' -> ' + after.toFixed(0) + ')');
}
// 5. FOOD RUNS OUT -- a finite source is foraged to exhaustion; the tally accounts for nearly all of it
{
const w = build({ food: [{ x: 38, y: 24, amount: 240 }] });
for (let i = 0; i < 220 && w.fields().stigmergy.foodRemaining > 0; i++) w.step(30 * DAY);
const f = w.fields().stigmergy;
ok(f.foodRemaining <= 0, 'a finite food source is foraged to EXHAUSTION (' + f.foodRemaining + ' left)');
run(w, 25); // let the ants still carrying the last crumbs finish their way home
const f2 = w.fields().stigmergy;
ok(f2.collected >= 232, 'nearly all of it is carried home once the last laden ants arrive (' + f2.collected + ' of 240)');
}
// 6. THE TRAIL REACHES THE FOOD -- food-scent piles up around the source (the highway connects nest to food)
{
const w = build({}); run(w, 70); const f = w.fields().stigmergy;
ok(f.atFood > f.trailMean * 3, 'the trail reaches the food: scent around the source is well above background (' + f.atFood + ' vs mean ' + f.trailMean + ')');
}
console.log(`\n${pass} passed, ${fail} failed`);
process.exit(fail ? 1 : 0);