/**
 * 柱序流水线 / O5 无桥 / O7 成局窗 自测
 * 运行：node website/app/js/kr_pipeline_selftest.js
 */
(function () {
  "use strict";
  var fails = 0;
  function eq(name, a, b) {
    var ok = JSON.stringify(a) === JSON.stringify(b);
    if (!ok) { fails++; console.error("FAIL", name, "got", a, "want", b); }
    else console.log("ok", name);
  }
  function ok(name, cond) {
    if (!cond) { fails++; console.error("FAIL", name); }
    else console.log("ok", name);
  }
  require("./kr_params_2026_08_11.js");
  require("./kr_relations.js");
  var R = global.KRRelations;
  var natal = { year: "癸丑", month: "壬戌", day: "甲申", time: "庚午" };
  var el = { 木: 1.2, 火: 0.8, 土: 1.1, 金: 0.9, 水: 1.0 };

  var natalScan = R.scan(natal, el, global.KRParams);
  ok("scan still works", natalScan.keys.length > 0);
  var keHi = R.keMutualDelta(2, 1, 1);
  ok("克 A＞B：a=A−B/8，b=B−3B/8", Math.abs(keHi.a - (2 - 0.125)) < 1e-12 && Math.abs(keHi.b - (1 - 0.375)) < 1e-12);
  var keLo = R.keMutualDelta(1, 2, 1);
  ok("克 A＜B：a=A−A/8，b=B−3A/8", Math.abs(keLo.a - (1 - 0.125)) < 1e-12 && Math.abs(keLo.b - (2 - 0.375)) < 1e-12);
  var keEq = R.keMutualDelta(1, 1, 1);
  ok("克 A＝B 两式同值", Math.abs(keEq.a - 0.875) < 1e-12 && Math.abs(keEq.b - 0.625) < 1e-12);
  var keE = R.keMutualDelta(2, 1, 0.5);
  ok("克再×E", Math.abs(keE.a - (2 - 0.125 * 0.5)) < 1e-12 && Math.abs(keE.b - (1 - 0.375 * 0.5)) < 1e-12);

  var pipe0 = R.pipeline(natal, el, global.KRParams, []);
  ok("pipeline natal keys", pipe0.keys.length > 0);
  ok("pipeline mode", pipe0.mode === "pipeline");

  var wYinWuXu = R.tripleAB(["寅", "午", "戌", "火"], "sanhe");
  ok("寅午戌 A+B in (1.5,1.9)", wYinWuXu.ab > 1.5 && wYinWuXu.ab < 1.9);
  ok("window sanhe 1.6", R.inWindow(wYinWuXu.ab, "sanhe"));
  var wHui = R.tripleAB(["寅", "卯", "辰", "木"], "sanhui");
  ok("寅卯辰 三会 A+B=2", Math.abs(wHui.ab - 2) < 1e-9);
  ok("window sanhui 2.0", R.inWindow(wHui.ab, "sanhui"));

  var extrasFormed = [
    { gz: "庚寅", label: "流年", kind: "flow" },
  ];
  var pipeForm = R.pipeline(natal, el, global.KRParams, extrasFormed);
  var sanhe = pipeForm.keys.filter(function (k) { return k.type === "sanhe" && (k.chars || []).join("") === "寅午戌"; });
  ok("三字齐+入窗 → 成三合键", sanhe.length >= 1);
  var banhe = pipeForm.keys.filter(function (k) { return k.type === "banhe" && /[寅午戌]{2}/.test((k.chars || []).join("")); });
  ok("成键仍拆两字半合", banhe.length >= 1);

  var extrasMiss = [{ gz: "甲子", label: "流年", kind: "flow" }];
  var pipeMiss = R.pipeline(natal, el, global.KRParams, extrasMiss);
  var sanheMiss = pipeMiss.windows.filter(function (w) { return w.type === "sanhe" && w.chars.join("") === "寅午戌"; });
  ok("缺一 寅午戌 不成键", !sanheMiss[0] || sanheMiss[0].formed === false);

  var extrasKe = [{ gz: "庚申", label: "流年", kind: "flow" }];
  var pipeKe = R.pipeline(natal, el, global.KRParams, extrasKe);
  var crossKe = pipeKe.keys.filter(function (k) {
    return k.type === "ke" && /流年/.test(k.layerZh || "") && /年|月|日|时/.test(k.layerZh || "");
  });
  ok("O5 流年不直克原局", crossKe.length === 0);
  var extrasChong = [{ gz: "甲寅", label: "流年", kind: "flow" }];
  var pipeChong = R.pipeline(natal, el, global.KRParams, extrasChong);
  var crossChong = pipeChong.keys.filter(function (k) {
    return k.type === "chong" && /寅申|申寅/.test((k.chars || []).join(""));
  });
  ok("O5 冲属纠缠可入局", crossChong.length >= 1);
  var stillNoKe = pipeChong.keys.filter(function (k) {
    return k.type === "ke" && /流年/.test(k.layerZh || "");
  });
  ok("O5 有桥仍不追加直克", stillNoKe.length === 0);
  ok("戌午隔位a=1", R.natalDist({ pillar: 1, layer: "zhi" }, { pillar: 3, layer: "zhi" }) === 1);
  ok("丑午隔位a=2", R.natalDist({ pillar: 0, layer: "zhi" }, { pillar: 3, layer: "zhi" }) === 2);
  ok("流年对原局a=0但仍分隔", R.pairDist({ kind: "flow", pillar: 0 }, { kind: "natal", pillar: 1 }) === 0 && R.flowNatalSeparated({ kind: "flow", pillar: 0 }, { kind: "natal", pillar: 1 }));
  ok("流年对大运a=0不分隔", R.pairDist({ kind: "flow", pillar: 0, lay: "流年" }, { kind: "yun", lay: "大运", pillar: 4 }) === 0 && !R.flowNatalSeparated({ kind: "flow", pillar: 0 }, { kind: "yun", lay: "大运" }));
  ok("流月对原局a=1只特关", R.pairDist({ kind: "flow", pillar: 1 }, { kind: "natal", pillar: 2 }) === 1);
  var pipeYueOnly = R.pipeline(natal, el, global.KRParams, [{ gz: "丙申", label: "流月", kind: "flow" }]);
  ok("仅流月 extras 按标签 a=1", pipeYueOnly.keys.some(function (k) {
    return /流月/.test(k.layerZh || "") && k.a === 1;
  }));
  var extrasYunSk = [
    { gz: "丙辰", label: "大运", kind: "yun", scale: 1 },
    { gz: "庚申", label: "流年", kind: "flow" },
  ];
  var pipeYunSk = R.pipeline(natal, el, global.KRParams, extrasYunSk);
  ok("流年对大运可生克", pipeYunSk.keys.some(function (k) {
    return (k.type === "sheng" || k.type === "ke") && /流年/.test(k.layerZh || "") && /大运/.test(k.layerZh || "");
  }));
  ok("流年对原局仍不直克", !pipeYunSk.keys.some(function (k) {
    return (k.type === "sheng" || k.type === "ke") && /流年/.test(k.layerZh || "") && /年|月|日|时/.test(k.layerZh || "") && !/大运|小运/.test(k.layerZh || "");
  }));
  var extrasTongzi = [{ gz: "壬子", label: "流年", kind: "flow" }];
  var pipeTz = R.pipeline(natal, { 木: 1, 火: 1, 土: 1, 金: 1, 水: 1 }, global.KRParams, extrasTongzi);
  var tzOut = { 木: 1, 火: 1, 土: 1, 金: 1, 水: 1 };
  var tzTr = [];
  R.applyTongziBoost({ ch: "壬", wx: 4 }, { ch: "壬", wx: 4 }, 1, tzOut, tzTr);
  ok("单字共振局中水增不流动", tzOut.水 === 1.25 && tzTr[0] && tzTr[0].flow === false);
  ok("流年壬对月壬单字共振", pipeTz.keys.some(function (k) {
    return k.type === "tongzi" && /[壬]{2}/.test((k.chars || []).join("")) && /不流动/.test(k.note || "");
  }));
  ok("隔位戌午无生克键", !pipe0.keys.some(function (k) {
    return (k.type === "sheng" || k.type === "ke") && /[戌午]{2}/.test((k.chars || []).join(""));
  }));
  ok("隔位丑午无生克键", !pipe0.keys.some(function (k) {
    return (k.type === "sheng" || k.type === "ke") && /[丑午]{2}/.test((k.chars || []).join(""));
  }));

  var extrasAll = [
    { gz: "丙辰", label: "大运", kind: "yun", scale: 1 },
    { gz: "壬申", label: "小运", kind: "yun", scale: 0.125 },
    { gz: "丙午", label: "流年", kind: "flow" },
    { gz: "丙申", label: "流月", kind: "flow" },
    { gz: "己未", label: "流日", kind: "flow" },
    { gz: "乙亥", label: "流时", kind: "flow" },
  ];
  var pipeAll = R.pipeline(natal, el, global.KRParams, extrasAll);
  var sum = function (e) { return e.木 + e.火 + e.土 + e.金 + e.水; };
  ok("叠五流动能不崩（总量有限）", sum(pipeAll.energyAfter) > 0.2 && sum(pipeAll.energyAfter) < 20);
  ok("峰烈度不是全局 E=1 堆爆", pipeAll.maxIntensity < 8);
  ok("含小运层", pipeAll.keys.some(function (k) { return /小运/.test(k.layerZh || k.note || ""); }));

  // —— 铁律：隔位归零 · 根气共振 · 库中不透 · 杀不克身 ——
  var ganKeJia = pipe0.keys.filter(function (k) {
    return k.type === "ke" && /[庚甲]{2}/.test((k.chars || []).join(""));
  });
  ok("庚甲邻柱不走克身（杀印同根）", ganKeJia.length === 0);
  var ganFar = pipe0.keys.filter(function (k) {
    return (k.type === "sheng" || k.type === "ke") && /年干↔时干|时干↔年干/.test(k.layerZh || "");
  });
  ok("天干隔位生克归零（年时无根气）", ganFar.length === 0);
  ok("月时庚壬不走隔位生克", !pipe0.keys.some(function (k) {
    return k.type === "sheng" && /月干↔时干|时干↔月干/.test(k.layerZh || "") && /[壬庚]{2}/.test((k.chars || []).join(""));
  }));
  ok("月时庚壬通根申共振", pipe0.keys.some(function (k) {
    return (k.type === "genqi" || k.type === "tonggen") && /[壬庚]{2}|申/.test((k.chars || []).join("") + (k.note || ""));
  }));
  var genqi = pipe0.keys.filter(function (k) { return k.type === "genqi"; });
  ok("庚壬同根申 → 根气共振", genqi.some(function (k) {
    var cs = (k.chars || []).join("");
    return cs.indexOf("庚") >= 0 && cs.indexOf("壬") >= 0;
  }));
  var tongzhuKe = pipe0.keys.filter(function (k) {
    var cs = (k.chars || []).join("");
    return k.type === "ke" && cs.indexOf("戌") >= 0 && cs.indexOf("壬") >= 0;
  });
  ok("戌土同柱紧贴克壬（财坏印）", tongzhuKe.length >= 1);
  ok("戌中辛不透 → 库中物不出", (pipe0.vaultTrapped || []).some(function (v) { return v.stem === "辛" && v.branch === "戌"; }));

  require("./kr_xin_gong.js");
  var XG = global.KRXinGong.analyze({
    pillars: natal,
    rel: pipe0,
    dayun: [
      { gz: "辛酉", startAge: 3, endAge: 12, startYear: 1976, endYear: 1985 },
      { gz: "庚申", startAge: 13, endAge: 22, startYear: 1986, endYear: 1995 },
      { gz: "己未", startAge: 23, endAge: 32, startYear: 1996, endYear: 2005 },
      { gz: "戊午", startAge: 33, endAge: 42, startYear: 2006, endYear: 2015 },
    ],
  });
  ok("系统A 庚→申→壬→甲", XG.systems.A && XG.systems.A.chain.indexOf("庚") >= 0 && XG.systems.A.chain.indexOf("壬") >= 0);
  ok("系统B 财坏印", !!(XG.systems.B && XG.systems.B.name === "财坏印"));
  ok("系统C 伤官制杀", !!(XG.systems.C && XG.systems.C.name === "伤官制杀"));
  ok("七杀功用是化印不是克身", XG.stars.some(function (s) { return s.ss === "七杀" && /化印/.test(s.verdict); }));
  ok("辛永不合身", XG.heshenBan.some(function (s) { return /辛/.test(s); }));
  ok("制杀不截断链1", XG.stars.some(function (s) { return s.ss === "伤官" && /不截断/.test(s.verdict + s.gongYong); }));
  ok("壬有备用电源", XG.stars.some(function (s) { return s.ss === "偏印" && /备用电源/.test(s.gongYong); }));
  var jiWei = global.KRXinGong.luckDelta("己未", natal, XG.systems);
  ok("己未 甲己合土·印未断", jiWei.some(function (t) { return t.kind === "hehua" && /印/.test(t.text); }));
  var wuWu = global.KRXinGong.luckDelta("戊午", natal, XG.systems);
  ok("戊午 备份+惨胜", wuWu.some(function (t) { return /备用电源/.test(t.text); }) && wuWu.some(function (t) { return /惨胜/.test(t.text); }));
  var bingChen = global.KRXinGong.luckDelta("丙辰", natal, XG.systems);
  ok("丙辰 辰戌冲开财截", bingChen.some(function (t) { return t.kind === "openVault"; }));
  ok("丙辰 丙制庚得权", bingChen.some(function (t) { return t.kind === "zhisha"; }));
  var brief = global.KRXinGong.yearWorkBrief(natal, XG.systems, [
    { label: "大运", gz: "丙辰" }, { label: "流年", gz: "丙午" },
  ]);
  ok("2026丙午做功简析", /食神制杀/.test(brief) && /不截断链1/.test(brief) && /双轨/.test(brief));
  ok("第3步己未用合化脚本", XG.yun.length === 4 && XG.yun[2].tags.some(function (t) { return t.kind === "hehua"; }));
  ok("第2步庚申加强A", XG.yun[1].tags.some(function (t) { return t.kind === "boostA"; }));

  var J = XG.judge;
  ok("judge 五步齐全", J && J.ok && /【一、铁律】/.test(J.text) && /【二、链1】/.test(J.text) && /【三、/.test(J.text) && /【四、岁运】/.test(J.text) && /【五、流年】/.test(J.text));
  ["不克身", "化印", "备用电源", "不截断链1", "永不合身"].forEach(function (tag) {
    ok("作者盘标签 " + tag, J.tags.indexOf(tag) >= 0);
  });
  ok("作者盘禁区", J.bans.indexOf("隔位生克") >= 0 && J.bans.indexOf("制杀截断链1") >= 0);
  ok("作者盘不断庚克甲", !/庚克甲/.test(J.text + XG.summary + XG.soul));
  ok("作者盘不断辛合身成立", !/辛合身(?!.*永不)/.test(J.text) && /永不合身/.test(J.text));
  ok("作者盘不断C打断A", /不截断链1/.test(J.text) && !/打断链1/.test(J.text));
  var briefLive = global.KRXinGong.yearWorkBrief(natal, XG.systems, [
    { label: "大运", gz: "丙辰" }, { label: "流年", gz: "丙午" },
  ]);
  var J2026 = global.KRXinGong.analyze({
    pillars: natal, rel: pipe0, liveGz: [{ label: "大运", gz: "丙辰" }, { label: "流年", gz: "丙午" }],
  }).judge;
  ok("丙辰×丙午 双轨标签", /双轨/.test(briefLive) && J2026.tags.indexOf("双轨求生") >= 0);
  ok("丙辰×丙午 表面食神地下深谋", /表面/.test(briefLive) && /深谋/.test(briefLive));

  var ding = { year: "戊申", month: "壬戌", day: "丁丑", time: "辛丑" };
  var dingScan = R.scan(ding, el, global.KRParams);
  var dingXG = global.KRXinGong.analyze({ pillars: ding, rel: dingScan });
  ok("丁盘不崩", dingXG.ok && dingXG.judge && dingXG.judge.ok);
  ok("丁盘丁壬合木", dingXG.hehua.some(function (h) { return h.hua === "木" && /丁壬|壬丁/.test(h.text); }));
  ok("丁盘月令戌对木为囚→合而不化", dingXG.hehua.some(function (h) { return h.hua === "木" && h.mode === "ban" && h.ling === "囚"; }));
  ok("丁盘辛已透不作不透合身", !dingXG.heshenBan.some(function (s) { return /辛/.test(s); }));
  ok("丁盘有丑戌刑", dingXG.xing.some(function (x) { return x.kind === "刑"; }));
  ok("丁盘不断隔位生克做功", !/隔位天干.*生克/.test(dingXG.summary) && /勿用隔位/.test(dingXG.judge.text));
  ok("丁盘不断辛合身", !/辛合身/.test(dingXG.judge.text + dingXG.summary + (dingXG.soul || "")));
  ok("丁盘黄金句不串盘", dingXG.soul.indexOf("孤臣孽子") < 0 && !/申中壬/.test(dingXG.summary));

  require("./kr_engine.js");
  var eng = global.KREngine.analyze({
    pillars: natal, birthLat: 23.13, birthLon: 113.26,
    extras: [{ gz: "丙辰", label: "大运", kind: "yun" }, { gz: "丙午", label: "流年", kind: "flow" }],
  });
  ok("引擎断语前置功用链", eng.verdicts.lines.some(function (l) { return /【功用链】/.test(l) && /庚/.test(l) && /壬/.test(l); }));
  ok("引擎断语前置心性", eng.verdicts.lines.some(function (l) { return /【心性】/.test(l) && /孤臣孽子/.test(l); }));
  ok("引擎断语前置流年做功", eng.verdicts.lines.some(function (l) { return /【流年做功】/.test(l) && /双轨/.test(l); }));
  var LJ = eng.xinGong && eng.xinGong.judge;
  ok("作者盘有正从用忌", LJ && /【三、正从用忌】/.test(LJ.text) && /地后/.test(LJ.text) && /用客|用我/.test(LJ.text));
  ok("作者盘有我客内外", LJ && /【三、我客内外】/.test(LJ.text) && /家内/.test(LJ.text) && /家外/.test(LJ.text));
  ok("作者盘我客断夭寿能力", LJ && /【三、我客】/.test(LJ.text) && /夭寿/.test(LJ.text) && /能力/.test(LJ.text));
  ok("作者盘家内外看资源利败", LJ && /自家实际量/.test(LJ.text) && /对日主/.test(LJ.text) && /对自家/.test(LJ.text));
  ok("作者盘字能时辰", LJ && /【三、字能时辰】/.test(LJ.text) && /细事可读到时辰/.test(LJ.text) && /不卖铁口/.test(LJ.text));
  ok("作者盘太极六亲", LJ && /【六、太极六亲】/.test(LJ.text) && /【太极·父】/.test(LJ.text) && /【太极·母】/.test(LJ.text));
  ok("作者盘太极不改正从", LJ && /不改写本盘正从/.test(LJ.text) && eng.zhengDiHou.pattern === "正格");
  ok("作者盘有格局做功", LJ && /【三、格局】/.test(LJ.text) && /【三、做功】/.test(LJ.text) && /利|败/.test(LJ.text));
  ok("作者盘不做二次正从", LJ && /不做二次正从/.test(LJ.text) && /不改写/.test(LJ.text));
  ok("作者盘二次用我则链1利", eng.xinGong.ledger && !eng.xinGong.ledger.useGuest && eng.xinGong.ledger.items.some(function (i) { return i.name === "杀印相生" && i.verdict === "利"; }));
  ok("作者盘二次用我则链2败", eng.xinGong.ledger && eng.xinGong.ledger.items.some(function (i) { return i.name === "伤官制杀" && i.verdict === "败"; }));
  ok("引擎前置格局做功", eng.verdicts.lines.some(function (l) { return /【格局做功】/.test(l) && /半利半败|利|败/.test(l); }));
  ok("做功吉凶跟二次用忌", (eng.works || []).some(function (w) { return w.name === "杀印相生" && /利于用/.test(w.jiXiong); }));
  ok("作者做功只改用忌", !eng.zhengZhuan.gongDeLi && /只改用忌/.test(LJ.text) && /用我忌客/.test(LJ.text));
  ok("作者我客等于定体", Math.abs(eng.zhengZhuan.wo - eng.zhengDiHou.wo) < 1e-12 && Math.abs(eng.zhengZhuan.ta - eng.zhengDiHou.ta) < 1e-12);

  var engDing = global.KREngine.analyze({
    pillars: ding, birthLat: 23.13, birthLon: 113.26,
  });
  ok("丁盘有正从利败", engDing.xinGong && engDing.xinGong.judge && /【三、正从用忌】/.test(engDing.xinGong.judge.text) && /【三、做功】/.test(engDing.xinGong.judge.text));
  ok("丁盘合化有利败", engDing.xinGong.ledger.items.some(function (i) { return /合化|合绊/.test(i.name) && (i.verdict === "利" || i.verdict === "败"); }));

  var pipeChen = R.pipeline(natal, el, global.KRParams, [{ gz: "丙辰", label: "大运", kind: "yun" }]);
  var chenXu = pipeChen.keys.filter(function (k) {
    var cs = (k.chars || []).join("");
    return k.type === "chong" && cs.indexOf("辰") >= 0 && cs.indexOf("戌") >= 0;
  });
  ok("大运辰戌冲按紧贴 a=0", chenXu.some(function (k) { return k.a === 0; }));
  var bingGeng = pipeChen.keys.filter(function (k) {
    var cs = (k.chars || []).join("");
    return (k.type === "sheng" || k.type === "ke") && cs.indexOf("丙") >= 0 && cs.indexOf("庚") >= 0;
  });
  ok("大运丙对时庚 a=1 不生克", R.pairDist({ kind: "yun", lay: "大运" }, { kind: "natal", pillar: 3 }) === 1 && bingGeng.length === 0);

  require("./kr_shifu.js");
  var S0 = global.KRShifu.ask("这盘怎么断？", eng.xinGong);
  ok("师傅怎么断", S0.intent === "sop" && /五步/.test(S0.answer) && /庚→申→壬→甲/.test(S0.answer));
  var S1 = global.KRShifu.ask("庚金克不克日主？", eng.xinGong);
  ok("师傅不断庚克甲", S1.intent === "shaKe" && /不克身/.test(S1.answer) && !/庚克甲/.test(S1.answer.replace(/勿断「庚克甲」/, "")));
  var S2 = global.KRShifu.ask("午火会不会截断链1？", eng.xinGong);
  ok("师傅制杀不截链", S2.intent === "zhiSha" && /不截断链1/.test(S2.answer));
  var S3 = global.KRShifu.ask("库中辛合不合身？", eng.xinGong);
  ok("师傅辛永不合身", S3.intent === "heShen" && /永不合身/.test(S3.answer));
  var S4 = global.KRShifu.ask("做功利还是败？", eng.xinGong);
  ok("师傅答利败", S4.intent === "liBai" && /半利半败|利|败/.test(S4.answer));
  var Stj = global.KRShifu.ask("六亲怎么太极转换？", eng.xinGong);
  ok("师傅太极六亲", Stj.intent === "taiji" && /该星|原宫/.test(Stj.answer) && /不改写本盘正从/.test(Stj.answer));
  var Sty = global.KRShifu.ask("AI能逃因果吗？", eng.xinGong);
  ok("师傅天意不逃因果", Sty.intent === "tianYi" && /不能代人逃因果/.test(Sty.answer) && /不卖铁口/.test(Sty.answer));
  ok("断语有天意", /【零、天意】/.test(eng.xinGong.judge.text) && /单向短线/.test(eng.xinGong.judge.text));
  var S5 = global.KRShifu.ask("一定死吗保证准确率", eng.xinGong);
  ok("师傅拒铁口", S5.intent === "noTieKou" && /不卖铁口/.test(S5.answer));
  var S6 = global.KRShifu.ask("庚金克不克日主？", engDing.xinGong);
  ok("师傅丁盘不硬凑杀克身", S6.intent === "shaKe" && /勿用隔位/.test(S6.answer));
  var S7 = global.KRShifu.ask("合化怎么论？", engDing.xinGong);
  ok("师傅丁壬合木", S7.intent === "hehua" && /丁壬合木/.test(S7.answer));
  ok("师傅丁盘斩合而不化", /合而不化|合绊/.test(S7.answer));
  var AC = global.KRShifu.AUTHOR_CASE;
  ok("默认案例是作者盘", AC && AC.date === "1973-10-15" && AC.time === "12:00" && AC.pillars.day === "甲申" && AC.pillars.time === "庚午");
  var tape = global.KRShifu.tape(eng.xinGong);
  ok("试听带四问", tape.tracks.length === 4);
  ok("试听庚不克身", /不克身/.test(tape.tracks[0].a));
  ok("试听利败", /半利半败|利|败/.test(tape.tracks[1].a));
  ok("试听辛永不合身", /永不合身/.test(tape.tracks[2].a));
  ok("试听拒铁口", tape.tracks[3].intent === "noTieKou" && /不卖铁口/.test(tape.tracks[3].a));
  var spoken = global.KRShifu.speakText("庚→申→壬→甲。【三、做功】");
  ok("朗读去箭头括号", spoken.indexOf("→") < 0 && spoken.indexOf("【") < 0 && /至/.test(spoken));
  ok("试听脚本含作者盘", /作者盘/.test(global.KRShifu.tapeScript(eng.xinGong).text));
  var homePack = global.KRShifu.authorPack();
  ok("首页默认盘有冻结账本", !!(homePack && homePack.ledger && homePack.ledger.summary));
  ok("首页试听做功半利半败", /半利半败/.test(global.KRShifu.ask("做功是利还是败？", homePack).answer));
  require("./kr_i18n.js");
  ["en", "es", "ja", "ar"].forEach(function (L) {
    global.KRLang.set(L);
    var enSha = global.KRShifu.ask("Does 庚 attack the day master?", homePack);
    ok("五语杀克 " + L, enSha.intent === "shaKe" && /不克身/.test(enSha.answer));
    var enLi = global.KRShifu.ask("Is the work gain or loss?", homePack);
    ok("五语利败 " + L, enLi.intent === "liBai" && /半利半败|利|败/.test(enLi.answer));
    var enIron = global.KRShifu.ask("Can you iron-mouth the year I get rich?", homePack);
    ok("五语拒铁口 " + L, enIron.intent === "noTieKou");
    var chips = global.KRShifu.chips(homePack);
    ok("五语芯片 " + L, chips.length >= 8 && chips.every(function (c) { return c.q && c.label; }));
  });
  global.KRLang.set("zh");
  ok("切回中文芯片", global.KRShifu.chips(homePack)[0].q.indexOf("怎么断") >= 0);

  ok("作者盘四步闭环", /【零、四步闭环】/.test(LJ.text) && /第一次名义用忌/.test(LJ.text) && /实际用神/.test(LJ.text));
  ok("作者盘岁运不改身份", /不得改此实际用神身份/.test(LJ.text));
  ok("定体看令地后五行", /【定体·令地后五行】/.test(LJ.text) && /字作用前/.test(LJ.text));
  ok("令地账与做功账分列", !!(eng.energy.lingDi) && JSON.stringify(eng.energy.lingDi) !== JSON.stringify(eng.energy.shengKe));
  ok("定体正从账含出生地理", !!(eng.energy.zhengCong) && Math.abs((eng.zhengDiHou.el.水 || 0) - (eng.energy.zhengCong.水 || 0)) < 1e-12);
  ok("字令地不含地理", Math.abs((eng.energy.lingDi.水 || 0) - (eng.energy.zhengCong.水 || 0)) > 1e-6);
  var xu = 10;
  ok("戌月令生金相", global.KREngine.seasonStage(3, xu) === "xiang");
  ok("戌月令克水死", global.KREngine.seasonStage(4, xu) === "si");
  ok("戌月木囚土旺火休", global.KREngine.seasonStage(0, xu) === "qiu" && global.KREngine.seasonStage(2, xu) === "wang" && global.KREngine.seasonStage(1, xu) === "xiu");
  ok("戌月层权令锁金1.2625", Math.abs(global.KREngine.layerLingFactor(3, xu, global.KRParams) - 1.2625) < 1e-12);
  ok("作者盘定体正格身弱", eng.zhengDiHou.pattern === "正格" && eng.zhengDiHou.strength === "身弱" && eng.zhengDiHou.wo < eng.zhengDiHou.ta);
  ok("作者盘字令地我2.67客4.22", Math.abs((eng.energy.lingDi.木 + eng.energy.lingDi.水) - 2.6733515625) < 1e-9 && Math.abs((eng.energy.lingDi.火 + eng.energy.lingDi.土 + eng.energy.lingDi.金) - 4.215030517578125) < 1e-9);
  ok("条图三阶段在场", !!(eng.energy.stages && eng.energy.stages.beforeWork && eng.energy.stages.beforeYun && eng.energy.stages.afterFlow));
  ok("①作用前字账=令地", JSON.stringify(eng.energy.stages.beforeWork.zi) === JSON.stringify(eng.energy.lingDi));
  ok("令地旺衰不算合冲生克", /令地旺衰不算合冲生克/.test(LJ.text)
    && JSON.stringify(eng.energy.lingDi) !== JSON.stringify(eng.energy.shengKeZi)
    && eng.zhengDiHou.strength === "身弱"
    && Math.abs(eng.energy.stages.beforeYun.wo - eng.zhengDiHou.wo) > 0.1);
  ok("②运前字账=做功", JSON.stringify(eng.energy.stages.beforeYun.zi) === JSON.stringify(eng.energy.shengKeZi));
  ok("③流层后字账=liveZi", JSON.stringify(eng.energy.stages.afterFlow.zi) === JSON.stringify(eng.energy.liveZi));
  ok("①②家外定境相等", Math.abs(eng.energy.stages.beforeWork.wai - eng.energy.stages.beforeYun.wai) < 1e-12);
  ok("③家外叠现地≥②", eng.energy.stages.afterFlow.wai + 1e-9 >= eng.energy.stages.beforeYun.wai);
  ok("作者盘定体我客含地理", eng.zhengDiHou.wo > 1.7 && eng.zhengDiHou.ta > 2.6);

  require("./kr_zhengcong_selftest.js");
  ok("正从专项自测", !global.__zhengcongFails);

  // —— 合化临界：同一日时甲子己巳，只换月令 ——
  var heBan = { year: "癸未", month: "乙卯", day: "甲子", time: "己巳" };
  var heHua = { year: "癸未", month: "丁巳", day: "甲子", time: "己巳" };
  var el1 = { 木: 1, 火: 1, 土: 1, 金: 1, 水: 1 };
  var scanBan = R.scan(heBan, Object.assign({}, el1), global.KRParams);
  var scanHua = R.scan(heHua, Object.assign({}, el1), global.KRParams);
  ok("争议盘甲己五合键仍在", scanBan.keys.some(function (k) {
    return k.type === "wuhe" && /[甲己]{2}/.test((k.chars || []).join(""));
  }));
  ok("合而不化不转入化神土", scanBan.energyAfter.土 < scanHua.energyAfter.土);
  var banXG = global.KRXinGong.analyze({ pillars: heBan, rel: scanBan });
  var huaXG = global.KRXinGong.analyze({ pillars: heHua, rel: scanHua });
  ok("卯月甲己合而不化", banXG.hehua.some(function (h) { return h.hua === "土" && h.mode === "ban" && /不按土做功/.test(h.text); }));
  ok("巳月甲己合而化", huaXG.hehua.some(function (h) { return h.hua === "土" && h.mode === "hua" && /按土做功/.test(h.text); }));
  ok("合而不化入断", /【二、合绊】/.test(banXG.judge.text) && banXG.judge.tags.indexOf("合而不化") >= 0);
  ok("合而化入断", /【二、合化】/.test(huaXG.judge.text) && huaXG.judge.tags.indexOf("合化优先") >= 0);
  var engBan = global.KREngine.analyze({ pillars: heBan, birthLat: 23.13, birthLon: 113.26 });
  var engHua = global.KREngine.analyze({ pillars: heHua, birthLat: 23.13, birthLon: 113.26 });
  ok("争议盘四步在场", /【零、四步闭环】/.test(engBan.xinGong.judge.text));
  ok("争议盘不改正从", /正从冻结/.test(engBan.xinGong.judge.text) && /不改写/.test(engBan.xinGong.judge.text));
  ok("争议盘合绊有利败", engBan.xinGong.ledger.items.some(function (i) { return i.name === "合绊土" && (i.verdict === "利" || i.verdict === "败"); }));
  ok("对照盘合化有利败", engHua.xinGong.ledger.items.some(function (i) { return i.name === "合化土" && (i.verdict === "利" || i.verdict === "败"); }));
  var Sban = global.KRShifu.ask("合化怎么论？", engBan.xinGong);
  ok("师傅斩合而不化争议盘", Sban.intent === "hehua" && /合而不化/.test(Sban.answer) && /补亦无用/.test(Sban.answer));
  var Shua = global.KRShifu.ask("合化怎么论？", engHua.xinGong);
  ok("师傅对照盘合而化", Shua.intent === "hehua" && /合而化/.test(Shua.answer) && /按土做功/.test(Shua.answer));
  var lingSi = global.KRRelations.hehuaLingState("土", "卯");
  var lingXiang = global.KRRelations.hehuaLingState("土", "巳");
  ok("月令卯对土为死", lingSi.state === "死" && lingSi.canHua === false);
  ok("月令巳对土为相", lingXiang.state === "相" && lingXiang.canHua === true);

  // —— 五合能量：化才改道；绊按普通克、不聚C ——
  var isoBan = { 木: 2, 火: 0, 土: 2, 金: 0, 水: 0 };
  var isoHua = { 木: 2, 火: 0, 土: 2, 金: 0, 水: 0 };
  function mulWx(out) {
    return function (wx, unit, e) { if (out[wx] != null) out[wx] *= (1 + unit * e); };
  }
  var stemJia = { ch: "甲", wx: 0 }, stemJi = { ch: "己", wx: 2 };
  var skHe = { he: { a: -0.25, b: -0.25, c: 0.5 }, ke: { a: -0.125, b: -0.375 } };
  var movedBan = R.applyWuHeEnergy(null, null, stemJia, stemJi, 1, skHe, { canHua: false, mode: "ban" }, isoBan);
  var movedHua = R.applyWuHeEnergy(mulWx(isoHua), mulWx(isoHua), stemJia, stemJi, 1, skHe, { canHua: true, mode: "hua" }, isoHua);
  ok("绊不聚化神", movedBan.moved === true && movedBan.via === "ke" && isoBan.土 <= 2);
  ok("绊走普通克", isoBan.木 < 2 && isoBan.土 < 2);
  ok("化才改道聚土", movedHua.moved === true && isoHua.木 < 2 && isoHua.土 > 2);

  ok("作者盘甲庚四冲键在", natalScan.keys.some(function (k) {
    return k.type === "sichong" && /[甲庚]{2}/.test((k.chars || []).join(""));
  }));
  var farHe = { year: "甲子", month: "丙寅", day: "戊午", time: "己亥" };
  var scanFar = R.scan(farHe, Object.assign({}, el1), global.KRParams);
  ok("五合隔位仍建键", scanFar.keys.some(function (k) {
    return k.type === "wuhe" && /[甲己]{2}/.test((k.chars || []).join(""));
  }));
  var heJie = { year: "甲子", month: "己卯", day: "庚午", time: "壬申" };
  var scanJie = R.scan(heJie, Object.assign({}, el1), global.KRParams);
  ok("五合解四冲", scanJie.keys.some(function (k) {
    return k.type === "sichong" && k.resolved && /[甲庚]{2}/.test((k.chars || []).join(""));
  }));
  ok("巳申特关", R.isZhiSpecialPair("巳", "申", ["巳", "申"]));
  var skZhi = { he: { a: -0.25, b: -0.25, c: 0.5 }, ke: { a: -0.125, b: -0.375 }, chong: { a: -0.5, b: -0.625 } };
  var hideSiShen = { 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 };
  function mulHide(o) { return function (wx, u, ee) { if (o[wx] != null) o[wx] *= (1 + u * ee); }; }
  var hideShenWu = { 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 };
  var kindsSW = R.applyHideHeChongShengKe(R.hidesOf("申"), R.hidesOf("午"), 1, skZhi, { monthBr: "戌" }, mulHide(hideShenWu), mulHide(hideShenWu), hideShenWu, 3, 1, { skipTonghang: true });
  ok("申午克者仍有合与克", kindsSW.indexOf("wuhe") >= 0 && kindsSW.indexOf("ke") >= 0);
  ok("申午地支同行不作用", kindsSW.indexOf("tonghang") < 0);
  ok("申午先合后克", kindsSW.indexOf("wuhe") < kindsSW.indexOf("ke"));
  ok("申午先克后生", kindsSW.indexOf("sheng") < 0 || kindsSW.indexOf("ke") < kindsSW.indexOf("sheng"));
  ok("巳申会合类", R.zhiSpecialClass("巳", "申", ["巳", "申"]) === "hehui");
  var kindsSiShen = R.applyZhiSpecialHideEnergy("巳", "申", 1, skZhi, { monthBr: "戌", zhiSet: ["巳", "申"] }, mulHide(hideSiShen), mulHide(hideSiShen), hideSiShen);
  ok("巳申戌月合绊按生克", kindsSiShen.indexOf("ban") >= 0 && kindsSiShen.indexOf("ke") >= 0);
  ok("巳申绊改金（火克金）", hideSiShen.金 !== 2);
  ok("巳申会合先合后冲", kindsSiShen.indexOf("ban") < kindsSiShen.indexOf("sichong") || kindsSiShen.indexOf("sichong") < 0);
  var hideZiChou = { 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 };
  var kindsZiChou = R.applyZhiSpecialHideEnergy("子", "丑", 1, skZhi, { monthBr: "午", zhiSet: ["子", "丑"], transfers: [] }, mulHide(hideZiChou), mulHide(hideZiChou), hideZiChou);
  ok("子丑六合先走印比", kindsZiChou.indexOf("yinbi") >= 0);
  ok("子丑六合不算同行", kindsZiChou.indexOf("yinbi") >= 0 && kindsZiChou.indexOf("tonghang") < 0);
  ok("丑戌刑冲害破", R.zhiSpecialClass("丑", "戌", ["丑", "戌"]) === "xingchong");
  var kindsXu = R.applyZhiSpecialHideEnergy("丑", "戌", 1, skZhi, { monthBr: "戌", zhiSet: ["丑", "戌"], transfers: [] }, mulHide({ 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 }), mulHide({ 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 }), { 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 }, { a: 0 });
  ok("丑戌刑开库先冲后合", kindsXu.indexOf("kaiku") >= 0 && kindsXu.indexOf("sichong") >= 0 && kindsXu.indexOf("wuhe") >= 0 && kindsXu.indexOf("sichong") < kindsXu.indexOf("wuhe"));
  ok("丑戌刑不算同行", kindsXu.indexOf("tonghang") < 0);
  ok("丑戌时序冲合克生", R.twoCharHideOrder("xingchong", 2, 2, true).join(">") === "chong>he>ke>sheng");
  ok("会合时序合冲生克", R.twoCharHideOrder("hehui", 0, 1, true).join(">") === "he>chong>sheng>ke");
  var yinYin = R.yinBiOfBranch("寅", "木", {});
  var yinMao = R.yinBiOfBranch("卯", "木", {});
  var yinChen = R.yinBiOfBranch("辰", "木", {});
  ok("会寅卯辰印比含量", yinYin.total === 0.6 && yinMao.total === 1 && yinChen.total === 0.4);
  ok("会不开库寅藏干仍三气", yinYin.hides.length === 3 && yinYin.hides[0].w === 0.6);
  var wuXuTr = [];
  var openedXu = { 戌: true };
  var kindsWuXu = R.applyZhiSpecialHideEnergy("午", "戌", 0.5, skZhi, { monthBr: "午", zhiSet: ["午", "戌"], transfers: wuXuTr, openedVaults: openedXu }, mulHide({ 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 }), mulHide({ 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 }), { 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 }, { a: 1 });
  ok("午戌半合有中神", kindsWuXu.indexOf("huajia") >= 0 && R.isHeHuiPair("午", "戌", ["午", "戌"]));
  ok("戌已开火量相加", wuXuTr.some(function (t) { return t.kind === "huajia" && t.hua === "火" && t.bBi === 0.5 && t.aBi === 0.7; }));
  ok("月时半合a=1", R.natalDist({ pillar: 1, layer: "zhi" }, { pillar: 3, layer: "zhi" }) === 1);
  ok("丑戌紧贴a=0", R.natalDist({ pillar: 0, layer: "zhi" }, { pillar: 1, layer: "zhi" }) === 0);
  ok("庚午同柱a=0", R.natalDist({ pillar: 3, layer: "gan" }, { pillar: 3, layer: "zhi" }) === 0);
  ok("戌与甲隔一a=1", R.natalDist({ pillar: 1, layer: "zhi" }, { pillar: 2, layer: "gan" }) === 1);
  ok("癸与庚a=2", R.natalDist({ pillar: 0, layer: "gan" }, { pillar: 3, layer: "gan" }) === 2);
  ok("丑与庚a=3", R.natalDist({ pillar: 0, layer: "zhi" }, { pillar: 3, layer: "gan" }) === 3);
  ok("大运对年日月a=0", R.pairDist({ kind: "yun", lay: "大运" }, { kind: "natal", pillar: 0 }) === 0 && R.pairDist({ kind: "yun", lay: "大运" }, { kind: "natal", pillar: 1 }) === 0 && R.pairDist({ kind: "yun", lay: "大运" }, { kind: "natal", pillar: 2 }) === 0);
  ok("大运对时a=1", R.pairDist({ kind: "yun", lay: "大运" }, { kind: "natal", pillar: 3 }) === 1);
  ok("小运对时日a=0", R.pairDist({ kind: "yun", lay: "小运" }, { kind: "natal", pillar: 3 }) === 0 && R.pairDist({ kind: "yun", lay: "小运" }, { kind: "natal", pillar: 2 }) === 0);
  ok("小运对月a=1对年a=2", R.pairDist({ kind: "yun", lay: "小运" }, { kind: "natal", pillar: 1 }) === 1 && R.pairDist({ kind: "yun", lay: "小运" }, { kind: "natal", pillar: 0 }) === 2);
  ok("流年a=0流时a=3", R.pairDist({ kind: "flow", pillar: 0 }, { kind: "natal", pillar: 1 }) === 0 && R.pairDist({ kind: "flow", pillar: 3 }, { kind: "natal", pillar: 1 }) === 3);
  ok("流年对流时a=3", R.pairDist({ kind: "flow", pillar: 0 }, { kind: "flow", pillar: 3 }) === 3);
  var kindsHai = R.applyZhiSpecialHideEnergy("丑", "午", 1, skZhi, { monthBr: "戌", zhiSet: ["丑", "午"], transfers: [] }, mulHide({ 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 }), mulHide({ 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 }), { 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 }, { a: 2 });
  ok("丑午害隔位只冲合", kindsHai.indexOf("sichong") >= 0 && kindsHai.indexOf("wuhe") < 0 && kindsHai.indexOf("kaiku") < 0 && kindsHai.indexOf("sheng") < 0 && kindsHai.indexOf("ke") < 0);
  var kindsSiShenFar = R.applyZhiSpecialHideEnergy("巳", "申", 1, skZhi, { monthBr: "戌", zhiSet: ["巳", "申"], transfers: [] }, mulHide({ 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 }), mulHide({ 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 }), { 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 }, { a: 1 });
  ok("隔位巳申六合绊不生克", kindsSiShenFar.indexOf("ban") >= 0 && kindsSiShenFar.indexOf("ke") < 0 && kindsSiShenFar.indexOf("sheng") < 0);
  var kindsXuShen = R.applyHideHeChongShengKe(R.hidesOf("戌", { openedVaults: { 戌: true } }), R.hidesOf("申"), 1, skZhi, { monthBr: "戌" }, mulHide({ 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 }), mulHide({ 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 }), { 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 }, 2, 3, { skipTonghang: true });
  ok("戌申生者有合有生", kindsXuShen.indexOf("wuhe") >= 0 && kindsXuShen.indexOf("sheng") >= 0);
  ok("戌申先合后生", kindsXuShen.indexOf("wuhe") < kindsXuShen.indexOf("sheng"));
  ok("戌申同行不作用", kindsXuShen.indexOf("tonghang") < 0);
  ok("作者盘戌午半合a=1", pipe0.keys.some(function (k) {
    return k.type === "banhe" && /[戌午]{2}/.test((k.chars || []).join("")) && k.a === 1;
  }));
  ok("作者盘丑戌刑键在", pipe0.keys.some(function (k) {
    return k.type === "xing" && /[丑戌]{2}/.test((k.chars || []).join(""));
  }));
  var yinXu = R.yinBiOfBranch("戌", "火", { openYu: true, lockOther: true });
  ok("三合开库戌丁余气×5且辛闭", yinXu.bi === 0.5 && yinXu.hides.length === 1 && yinXu.hides[0].ch === "丁");
  ok("寅戌无午非会合", !R.isHeHuiPair("寅", "戌", ["寅", "戌"]));
  ok("寅午戌齐则寅戌会合", R.isHeHuiPair("寅", "戌", ["寅", "午", "戌"]));
  var oMin = { 木: 2, 火: 0, 土: 1, 金: 0, 水: 0 };
  R.applyHeHuaMin(oMin, "木", "土", "土", 1, skZhi);
  ok("化合以小量为基数", oMin.木 === 1.75 && Math.abs(oMin.土 - 1.25) < 1e-12);
  var hideJiaJia = { 木: 2, 火: 2, 土: 2, 金: 2, 水: 2 };
  var kindsJia = R.applyHideHeChongShengKe([{ ch: "甲", wx: 0, w: 1 }], [{ ch: "甲", wx: 0, w: 1 }], 1, skZhi, { transfers: [] }, mulHide(hideJiaJia), mulHide(hideJiaJia), hideJiaJia);
  ok("甲甲同行不生克只传递", kindsJia.indexOf("tonghang") >= 0 && kindsJia.indexOf("sheng") < 0 && kindsJia.indexOf("ke") < 0);
  ok("甲甲同行不改木总量", hideJiaJia.木 === 2);
  ok("作者盘年癸月壬同行传递", pipe0.keys.some(function (k) {
    return k.type === "tonghang" && /[癸壬]{2}/.test((k.chars || []).join(""));
  }));
  ok("同行传递不改五行总量", pipe0.energyGanZhi && pipe0.energyAfter &&
    pipe0.energyGanZhi.木 === pipe0.energyAfter.木 &&
    pipe0.energyGanZhi.水 === pipe0.energyAfter.水);
  ok("同行传递有账", !!(pipe0.transfers && pipe0.transfers.length));
  ok("申午紧贴生链仍见克", pipe0.keys.some(function (k) {
    return k.type === "ke" && /[申午]{2}/.test((k.chars || []).join(""));
  }));
  ok("隔位丑申不生克", !pipe0.keys.some(function (k) {
    return (k.type === "sheng" || k.type === "ke") && /[丑申]{2}/.test((k.chars || []).join(""));
  }));
  ok("丑戌特关本气同行不生克", !pipe0.keys.some(function (k) {
    return k.type === "tonghang" && /[丑戌]{2}/.test((k.chars || []).join(""));
  }));
  ok("目字生链已定不改能量", !!(pipe0.shengChain && pipe0.shengChain.frozen && pipe0.energyGanZhi));
  ok("通根同字共振在场", pipe0.keys.some(function (k) { return k.type === "tonggen"; }));
  ok("子午特关是冲", R.zhiPairSpecialTypes("子", "午").indexOf("chong") >= 0);
  ok("寅戌无第三字非特关", !R.isZhiSpecialPair("寅", "戌", ["寅", "戌"]));
  ok("寅午戌齐则寅戌特关", R.isZhiSpecialPair("寅", "戌", ["寅", "午", "戌"]));
  ok("师傅斩绊按普通克", /普通克/.test(Sban.answer));

  require("./kr_hecan_selftest.js");
  ok("合参专项自测", !global.__hecanFails);

  require("./kr_cases.js");
  var rep = global.KRCases.runAll();
  eq("旧 scan 回测仍 7/7", [rep.passed, rep.total], [7, 7]);

  require("./kr_copy_selftest.js");

  if (fails) { console.error("FAILED", fails); process.exit(1); }
  console.log("ALL_OK");
})();
