← Natura·Docs/Modules/Source

mind-test.js

156 lines · in natura-src-1.0.0.zip as natura/mind-test.js · a test

// mind-test.js -- assertions for the MIND system. Determinism, boundedness, and the EMERGENCE proofs that
// the model is real: a mind's belief tracks the world when it attends, DIVERGES when it does not, and is
// DISTORTED by fear (a self-sustaining phantom). "The map is not the territory," made to run.
const N = require('./natura.js');
const { makeMind, INTENT } = require('./mind.js');
const { World, UNITS } = N;

let pass = 0, fail = 0;
const ok = (c, m) => { if (c) pass++; else { fail++; console.log('  FAIL: ' + m); } };

function run(seed, cfg, steps, dt) {
  dt = dt || UNITS.HOUR;
  const w = World({ seed, systems: [makeMind(cfg)] });
  for (let i = 0; i < steps; i++) w.step(dt);
  return w;
}
function meanDiv(f, truthThreat) { let s = 0; for (let i = 0; i < f.M; i++) s += Math.abs(f.bThreat[i] - truthThreat); return s / f.M; }
function mean(a) { let s = 0; for (let i = 0; i < a.length; i++) s += a[i]; return s / a.length; }

console.log('mind assertions:');

// 1. DETERMINISM -- same seed + dt sequence -> identical minds
{
  const a = run(3, { minds: 30 }, 60).fields().mind, b = run(3, { minds: 30 }, 60).fields().mind;
  let same = true; for (let i = 0; i < a.M; i++) if (a.bThreat[i] !== b.bThreat[i] || a.fear[i] !== b.fear[i] || a.arousal[i] !== b.arousal[i]) { same = false; break; }
  ok(same, 'same seed -> identical minds (reproducible)');
}

// 2. BOUNDED + FINITE -- every belief/emotion/arousal stays in [0,1], no NaN, over a long varied run
{
  const f = run(5, { minds: 40 }, 300).fields().mind;
  let good = true;
  for (const arr of [f.bThreat, f.bFood, f.fear, f.hunger, f.content, f.curiosity, f.arousal])
    for (let i = 0; i < f.M; i++) { const v = arr[i]; if (!isFinite(v) || v < -1e-6 || v > 1 + 1e-6) { good = false; break; } }
  ok(good, 'all beliefs + emotions + arousal stay finite and in [0,1]');
  let intentOk = true; for (let i = 0; i < f.M; i++) if (f.intent[i] < 0 || f.intent[i] > 4) intentOk = false;
  ok(intentOk, 'every intent is a valid decision (REST/FORAGE/FLEE/EXPLORE/SEEK)');
  let warmOk = true; for (let i = 0; i < f.M; i++) { const v = f.bWarmth[i], h = f.hope[i]; if (!isFinite(v) || v < -1e-6 || v > 1 + 1e-6 || !isFinite(h) || h < -1e-6 || h > 1 + 1e-6) warmOk = false; }
  ok(warmOk, 'the warm pole (warmth-belief + hope) stays finite and in [0,1] too');
}

// 3. ATTENTION TRACKS TRUTH -- awake, undistorted (fearBias 0), a mind's belief converges on the real threat
{
  const f = run(7, { minds: 50, fearBias: 0, truth: () => ({ threat: 0.6, food: 0.5 }) }, 80).fields().mind;
  const d = meanDiv(f, 0.6);
  ok(d < 0.18, 'an attending mind\'s belief tracks the truth (mean divergence ' + d.toFixed(3) + ' from a true 0.6)');
}

// 4. THE MAP DIVERGES -- a mind that does not attend (asleep) holds a belief detached from the world.
//    Same high-threat world; the awake mind learns it, the asleep mind never does. The core uncanny emergence.
{
  const cfg = { minds: 50, truth: () => ({ threat: 0.9, food: 0.4 }) };
  const awake = run(9, cfg, 90).fields().mind;
  const asleep = run(9, Object.assign({ forceSleep: true }, cfg), 90).fields().mind;
  const dAwake = meanDiv(awake, 0.9), dAsleep = meanDiv(asleep, 0.9);
  ok(dAwake < 0.25, 'the WAKING mind closes on the world (divergence ' + dAwake.toFixed(3) + ')');
  ok(dAsleep > 0.5, 'the SLEEPING mind\'s model is detached from the world (divergence ' + dAsleep.toFixed(3) + ')');
  ok(dAsleep > dAwake + 0.35, 'attention is what binds the model to the world (' + dAwake.toFixed(2) + ' awake vs ' + dAsleep.toFixed(2) + ' asleep)');
}

// 5. FEAR DISTORTS -- after a threat is GONE, a strongly fear-prone mind stays GRIPPED by a phantom (the
//    fear conjures the threat it fears -> a self-sustaining loop, above a threshold: trauma). An undistorted
//    mind lets the belief fall to the truth. Emotion warps the model; past a threshold it TRAPS it.
{
  const truth = (x, y, ctx) => ({ threat: ctx.clock.t < 20 * 3600 ? 0.85 : 0.0, food: 0.5 });   // threat for 20h, then gone
  const fearful = run(11, { minds: 60, fearBias: 1.3, truth }, 40).fields().mind;   // 40h: 20 with threat, 20 long after it left
  const clear = run(11, { minds: 60, fearBias: 0.0, truth }, 40).fields().mind;
  const bFearful = mean(fearful.bThreat), bClear = mean(clear.bThreat);
  ok(bClear < 0.25, 'the undistorted mind\'s belief falls with the vanished threat (' + bClear.toFixed(3) + ')');
  ok(bFearful > 0.5, 'a fear-gripped mind stays trapped believing a threat long gone -- a self-sustaining PHANTOM (' + bFearful.toFixed(2) + ')');
  ok(bFearful > bClear + 0.3, 'FEAR warps the model away from the world (' + bFearful.toFixed(2) + ' fearful vs ' + bClear.toFixed(2) + ' clear)');
}

// 6. SELF-REGULATION -- a threatening world keeps minds AWAKE (fear rouses); a safe, fed world lets them sleep.
{
  const threatened = run(13, { minds: 50, truth: () => ({ threat: 0.8, food: 0.5 }) }, 60).fields().mind;
  const safe = run(13, { minds: 50, truth: () => ({ threat: 0.0, food: 0.95 }) }, 60).fields().mind;
  ok(mean(threatened.arousal) > mean(safe.arousal), 'threat keeps minds awake, safety lets them rest (arousal ' + mean(threatened.arousal).toFixed(2) + ' vs ' + mean(safe.arousal).toFixed(2) + ')');
}

// 7. EVENTS -- the uncanny moments surface: fleeing a threat that is not there (a phantom startle)
{
  // a threat pulse then gone, with strong fear-distortion -> phantom flees after it leaves
  const truth = (x, y, ctx) => ({ threat: ctx.clock.t < 12 * 3600 ? 0.9 : 0.0, food: 0.5 });
  const w = World({ seed: 17, systems: [makeMind({ minds: 60, fearBias: 0.9, truth })] });
  let phantoms = 0, falseBeliefs = 0;
  for (let i = 0; i < 40; i++) { w.step(UNITS.HOUR); for (const e of (w.events() || [])) { if (e.kind === 'startle' && e.phantom) phantoms++; if (e.kind === 'falseBelief') falseBeliefs++; } }
  ok(phantoms > 0, 'a frightened mind FLEES a threat that is not there (' + phantoms + ' phantom startles)');
  ok(falseBeliefs > 0, 'false beliefs are surfaced as events (' + falseBeliefs + ')');
}

// 8. THE WARM MIRROR -- hope sustains a good place after it is gone (the grace-phantom: a mind keeps
//    homing to a hearth gone cold). The exact mirror of the fear-trap in test 5, and the warm mode the
//    piece needs so the experience is not rooted only in the disturbing.
{
  // lean food keeps the minds awake and yearning (a warm belief alone would just lull them to sleep) -- a
  // hungry creature homing across the cold toward a remembered hearth.
  const truth = (x, y, ctx) => ({ threat: 0.0, food: 0.15, warmth: ctx.clock.t < 20 * 3600 ? 0.85 : 0.0 });   // a hearth for 20h, then gone cold
  const hopeful = run(11, { minds: 60, hopeBias: 1.5, truth }, 40).fields().mind;   // 40h: 20 warmed, 20 long after it went out
  const hopeless = run(11, { minds: 60, hopeBias: 0.0, truth }, 40).fields().mind;
  const wHopeful = mean(hopeful.bWarmth), wHopeless = mean(hopeless.bWarmth);
  ok(wHopeless < 0.35, 'the undistorted mind lets the warmth-belief fade with the cold hearth (' + wHopeless.toFixed(3) + ')');
  ok(wHopeful > 0.5, 'a hopeful mind keeps believing in the good place long after it is gone -- a warm PHANTOM, solace (' + wHopeful.toFixed(2) + ')');
  ok(wHopeful > wHopeless + 0.3, 'HOPE sustains the model of a good that has left the world (' + wHopeful.toFixed(2) + ' hopeful vs ' + wHopeless.toFixed(2) + ' hopeless)');
}

// 9. THE WARM PHANTOM SURFACES AS AN EVENT -- a mind at peace homes (SEEK) to a hearth that is no longer
//    there. The gentle twin of the phantom startle.
{
  const truth = (x, y, ctx) => ({ threat: 0.0, food: 0.15, warmth: ctx.clock.t < 12 * 3600 ? 0.9 : 0.0 });
  const w = World({ seed: 17, systems: [makeMind({ minds: 60, hopeBias: 1.2, truth })] });
  let solaces = 0; for (let i = 0; i < 40; i++) { w.step(UNITS.HOUR); for (const e of (w.events() || [])) if (e.kind === 'solace' && e.phantom) solaces++; }
  ok(solaces > 0, 'a hopeful mind homes toward a hearth that is not there (' + solaces + ' solace phantoms)');
}

// 10. BALANCE -- one world holding BOTH a roaming threat and a still hearth grows BOTH poles at once: some
//     minds afraid and fleeing, others hopeful and homing. The population is not monolithic; the piece can
//     run either mode because the model carries both, and their contrast sharpens each.
{
  const w = World({ seed: 23, systems: [makeMind({ minds: 80 })] });   // defaultTruth: roaming threat + fixed hearth + patchy food
  const seen = new Set();
  for (let i = 0; i < 40; i++) { w.step(UNITS.HOUR); const f = w.fields().mind; for (let m = 0; m < f.M; m++) seen.add(f.intent[m]); }
  const f = w.fields().mind;
  ok(f.fracAfraid > 0 && f.fracHopeful > 0, 'fear AND hope coexist in one world (afraid ' + f.fracAfraid.toFixed(2) + ', hopeful ' + f.fracHopeful.toFixed(2) + ')');
  ok(seen.has(INTENT.FLEE) && seen.has(INTENT.SEEK), 'some minds FLEE the dark while others SEEK the warm -- both modes live at once');
}

// 11. DREAMING -- a mind that falls asleep still afraid DREAMS the threat and keeps believing it, even with
//     nothing in the world; without the dream that belief would just fade. The sleeping mind sustains its own
//     dread. (White-box: the same afraid-sleeper, dreaming vs not, aimless wander off, no real threat.)
{
  const mk = (db) => {
    const sys = makeMind({ minds: 1, dreamBias: db, forceSleep: true, truth: () => ({ threat: 0, food: 0.5, warmth: 0 }) });
    const rng = () => 0.5, st = sys.init(rng);
    st.fear[0] = 0.6; st.bThreat[0] = 0.6;   // it fell asleep afraid, belief + feeling aligned
    const ctx = { clock: { t: 0, dayPhase: () => 0.5 }, rng, fields: {} };
    for (let k = 0; k < 8; k++) sys.step(st, ctx, 3600);
    return st.bThreat[0];
  };
  const dreamer = mk(0.5), noDream = mk(0.0);
  ok(dreamer > 0.4 && dreamer > noDream + 0.2, 'a mind that sleeps still afraid DREAMS the threat and keeps believing it -- a nightmare that would otherwise have faded (dreaming ' + dreamer.toFixed(2) + ' vs no-dream ' + noDream.toFixed(2) + ')');
}

// 12. RELIEF -- the grace that answers the phantom: a mind awake, believing a threat that is not there,
//     SEES clearly and lets the dread fall past the line -- the map re-fitting the world, the ghost dissolving.
{
  const sys = makeMind({ minds: 1, truth: () => ({ threat: 0, food: 0.5, warmth: 0 }) });
  const rng = () => 0.5, st = sys.init(rng);
  st.bThreat[0] = 0.75; st.fear[0] = 0.7; st.arousal[0] = 0.8;   // awake, gripped by a threat that is not really there
  const ctx = { clock: { t: 0, dayPhase: () => 0.5 }, rng, fields: {} };
  let relief = false; for (let k = 0; k < 12; k++) { sys.step(st, ctx, 3600); for (const e of sys.events(st)) if (e.kind === 'relief') relief = true; }
  ok(relief && st.bThreat[0] < 0.4, 'a mind awake sees the threat is gone and its dread FALLS -- relief, the ghost dissolving (belief now ' + st.bThreat[0].toFixed(2) + ')');
}

console.log(`\n${pass} passed, ${fail} failed`);
process.exit(fail ? 1 : 0);