#!/usr/bin/env node
'use strict';
// בדיקות יחידה לשכבת-הקוגניציה SkyLattice-Λ: מכונת-נוסחאות בטוחה, רגרסיה
// לוגיסטית מקוונת, שודד רב-זרועי (UCB1), ערמת-שכבות מסתגלת, מיצוע פדרטיבי,
// ומודל-עצמי (דופק/חיוניות/אנטרופיה/החלטה). מתמטיקה אמיתית, דטרמיניסטית-בזרע.
process.env.SC_TEST = '1';
const t = require('./dump-test.js').__test;
const assert = require('node:assert');

let pass = 0;
const ok = (name, cond) => { assert.ok(cond, name); console.log('PASS ' + name); pass++; };
const near = (a, b, eps = 1e-9) => Math.abs(a - b) <= eps;

// ── FormulaVM: פירוש והערכה בטוחים (בלי eval) ──
const fvm = new t.FormulaVM();
ok('FormulaVM precedence 2+3*4=14', fvm.eval('2+3*4') === 14);
ok('FormulaVM parens (2+3)*4=20', fvm.eval('(2+3)*4') === 20);
ok('FormulaVM right-assoc power 2^3^2=512', fvm.eval('2^3^2') === 512);
ok('FormulaVM unary minus -3+5=2', fvm.eval('-3+5') === 2);
ok('FormulaVM functions clamp/max/pow', fvm.eval('clamp(2^3 + max(1,4)*2, 0, 100)') === 16);
ok('FormulaVM if + comparison', fvm.eval('if(vitality > 0.5, 1, 0)', { vitality: 0.7 }) === 1);
ok('FormulaVM variables', near(fvm.eval('a*b + c', { a: 2, b: 3, c: 4 }), 10));
ok('FormulaVM sqrt/abs', near(fvm.eval('sqrt(abs(-16))'), 4));
let threw = false; try { fvm.eval('2 + '); } catch { threw = true; }
ok('FormulaVM rejects malformed expression', threw);
let threw2 = false; try { fvm.eval('x + 1'); } catch { threw2 = true; }
ok('FormulaVM rejects undefined variable', threw2);
let threw3 = false; try { fvm.eval('2 + system("rm")'); } catch { threw3 = true; }
ok('FormulaVM has no eval/host access (unknown fn throws)', threw3);

// ── AdaptiveLayer: רגרסיה לוגיסטית מקוונת לומדת בעיה ניתנת-להפרדה ──
const rnd = t.mulberry32(12345);
const layer = new t.AdaptiveLayer(2, { seed: 999, lr: 0.2 });
// כלל אמת: y=1 אם x0+x1>1
const sample = () => { const x = [rnd() * 2, rnd() * 2]; return { x, y: x[0] + x[1] > 2 ? 1 : 0 }; };
let firstLoss = 0, lastLoss = 0;
for (let i = 0; i < 4000; i++) { const s = sample(); const l = layer.learn(s.x, s.y); if (i < 50) firstLoss += l; if (i >= 3950) lastLoss += l; }
ok('AdaptiveLayer loss decreases with training', lastLoss / 50 < firstLoss / 50 * 0.5);
let correct = 0; for (let i = 0; i < 1000; i++) { const s = sample(); if ((layer.predict(s.x) > 0.5 ? 1 : 0) === s.y) correct++; }
ok('AdaptiveLayer learned rule (>85% accuracy)', correct > 850);

// ── Bandit (UCB1): מתכנס לזרוע הטובה ביותר ──
const trueMeans = [0.2, 0.5, 0.9, 0.4];
const br = t.mulberry32(42);
const bandit = new t.Bandit(['a', 'b', 'c', 'd'], { seed: 42 });
let pullsBest = 0;
for (let i = 0; i < 5000; i++) { const a = bandit.choose(); const r = br() < trueMeans[a] ? 1 : 0; bandit.reward(a, r); if (i >= 4000 && a === 2) pullsBest++; }
ok('Bandit identifies best arm', bandit.best() === 2);
ok('Bandit concentrates pulls on best arm late', pullsBest > 800);

// ── LayerStack: דטרמיניסטי-בזרע, פלט חסום, ומשקולות שמתעדכנות ──
const s1 = new t.LayerStack([3, 4, 2], { seed: 7 });
const s2 = new t.LayerStack([3, 4, 2], { seed: 7 });
const o1 = s1.forward([0.5, 0.2, 0.9]);
const o2 = s2.forward([0.5, 0.2, 0.9]);
ok('LayerStack deterministic under same seed', JSON.stringify(o1) === JSON.stringify(o2));
ok('LayerStack output bounded [0,1]', o1.every((v) => v >= 0 && v <= 1) && o1.length === 2);
const wBefore = JSON.stringify(s1.weights());
for (let i = 0; i < 200; i++) { s1.forward([0.5, 0.2, 0.9]); s1.feedback([1, 0]); }
const out2 = s1.forward([0.5, 0.2, 0.9]);
ok('LayerStack self-updates weights via feedback', JSON.stringify(s1.weights()) !== wBefore);
ok('LayerStack moves output toward target', out2[0] > o1[0]);

// ── fedAverage: מיצוע פדרטיבי של משקולות ──
const avg = t.fedAverage([[0, 10, 2], [4, 0, 4], [2, 2, 0]]);
ok('fedAverage element-wise mean', near(avg[0], 2) && near(avg[1], 4) && near(avg[2], 2));

// ── SelfModel: דופק/חיוניות/אנטרופיה/החלטה אוטונומית ──
const sm = new t.SelfModel();
let p;
for (let i = 0; i < 10; i++) p = sm.observe({ relaysUp: 8, phiAvg: 0.5, scores: [1, 1, 1, 1, 1, 1, 1, 1], snapFresh: 1, backlog: 0 });
ok('SelfModel high vitality when healthy', p.vitality > 0.7 && p.action === 'steady');
ok('SelfModel vitality bounded [0,1]', p.vitality >= 0 && p.vitality <= 1);
const sm2 = new t.SelfModel();
let q; for (let i = 0; i < 10; i++) q = sm2.observe({ relaysUp: 1, phiAvg: 9, scores: [0.9], snapFresh: 0.1, backlog: 15 });
ok('SelfModel recommends hop when few relays / high phi', q.action === 'hop');
ok('SelfModel low vitality when degraded', q.vitality < 0.5);
ok('SelfModel entropy: even spread ~1, concentrated <1', t.SelfModel.entropy([1, 1, 1, 1]) > 0.99 && t.SelfModel.entropy([10, 0.01, 0.01]) < 0.5);

console.log('\nALL ' + pass + ' LAMBDA TESTS PASSED');
