/**
 * 键共振 Destiny · Web 引擎移植（相对 Flutter 主包的可运行子集）
 * 来源：solar_time / global_chart_rules / southern_hemisphere /
 *       energy_rules.geo* / body_strength_engine / verdict_pairing_index
 * 参数冻结：KRParams 2026-08-11
 * © 2026 Jason Li
 */
(function (global) {
  "use strict";

  const TG = ["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"];
  const DZ = ["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"];
  const WX = ["木","火","土","金","水"]; // index 0..4
  const STEM_WX = [0,0,1,1,2,2,3,3,4,4];
  const BRANCH_WX = [4,2,0,0,2,1,1,2,3,3,2,4];
  const HIDE = {
    子:[["癸",1]], 丑:[["己",0.6],["癸",0.3],["辛",0.1]],
    寅:[["甲",0.6],["丙",0.3],["戊",0.1]], 卯:[["乙",1]],
    辰:[["戊",0.6],["乙",0.3],["癸",0.1]], 巳:[["丙",0.6],["戊",0.3],["庚",0.1]],
    午:[["丁",0.7],["己",0.3]], 未:[["己",0.6],["丁",0.3],["乙",0.1]],
    申:[["庚",0.6],["壬",0.3],["戊",0.1]], 酉:[["辛",1]],
    戌:[["戊",0.6],["辛",0.3],["丁",0.1]], 亥:[["壬",0.7],["甲",0.3]],
  };
  const OPP = {子:"午",午:"子",丑:"未",未:"丑",寅:"申",申:"寅",卯:"酉",酉:"卯",辰:"戌",戌:"辰",巳:"亥",亥:"巳"};

  // 城市库（可扩展）
  const PLACES = {
    chicago: { name:"芝加哥", lat:41.8781, lng:-87.6298, zone:-6 },
    beijing: { name:"北京", lat:39.9042, lng:116.4074, zone:8 },
    shanghai: { name:"上海", lat:31.2304, lng:121.4737, zone:8 },
    taipei: { name:"台北", lat:25.0330, lng:121.5654, zone:8 },
    hongkong: { name:"香港", lat:22.3193, lng:114.1694, zone:8 },
    newyork: { name:"纽约", lat:40.7128, lng:-74.0060, zone:-5 },
    london: { name:"伦敦", lat:51.5074, lng:-0.1278, zone:0 },
    sydney: { name:"悉尼", lat:-33.8688, lng:151.2093, zone:10 },
    rio: { name:"里约热内卢", lat:-22.9068, lng:-43.1729, zone:-3 },
    sao_paulo: { name:"圣保罗", lat:-23.5505, lng:-46.6333, zone:-3 },
    la: { name:"洛杉矶", lat:34.0522, lng:-118.2437, zone:-8 },
  };

  const BEIJING_MERIDIAN = 120;
  /** 以北京为中心的日界线（大西洋对跖）≈ 60°W；金木在此达 1 */
  const ATLANTIC_DATE_LINE = -60;
  const AMERICAS_EAST_BOUND = -25; // 日柱进一区东界 (−180°, −25]
  const BODY_TH = 1.0;
  const BALANCE_RATIO = 1 / 8;
  const YONGJI_SOFT_TH = 2.0;
  const BI_RUO_JI = 0.3;
  const BI_PIAN = 0.8;
  const BI_QIANG = 1.2;
  const MANIFEST_TH = 2.0;

  function normLon(lon) {
    let x = lon;
    while (x > 180) x -= 360;
    while (x <= -180) x += 360;
    return x;
  }

  function inAmericasDayAhead(lon) {
    const x = normLon(lon);
    return x > -180 && x <= AMERICAS_EAST_BOUND;
  }

  /** 近似均时差（秒）· Spencer 公式；完整表见 Flutter equation_of_time.dart */
  function eotSeconds(y, m, d) {
    const day = Math.floor((Date.UTC(y, m - 1, d) - Date.UTC(y, 0, 0)) / 86400000);
    const b = (2 * Math.PI * (day - 81)) / 365;
    // minutes → seconds
    return (9.87 * Math.sin(2 * b) - 7.53 * Math.cos(b) - 1.5 * Math.sin(b)) * 60;
  }

  function addMinutes(date, mins) {
    return new Date(date.getTime() + mins * 60000);
  }

  /**
   * 当地墙钟 → 北京民事时 → 真太阳时
   * @param local {{y,m,d,h,mi}} 出生地当地钟表
   * @param lon 出生经度
   * @param zoneHours 政治时区（相对 UTC）；中国=8
   * @param useTrueSolar 是否加经度差+均时差
   * @param inputIsBeijingClock 输入已是北京钟表
   */
  function correctTime(local, lon, zoneHours, useTrueSolar, inputIsBeijingClock) {
    const lonN = normLon(lon);
    let beijingCivil;
    if (inputIsBeijingClock || Math.abs(zoneHours - 8) < 1e-9) {
      beijingCivil = new Date(local.y, local.m - 1, local.d, local.h, local.mi, 0);
    } else {
      // local → UTC → Beijing(+8)
      const utcMs = Date.UTC(local.y, local.m - 1, local.d, local.h, local.mi) - zoneHours * 3600000;
      beijingCivil = new Date(utcMs + 8 * 3600000);
    }
    const lonMin = (lonN - BEIJING_MERIDIAN) * 4;
    const eot = eotSeconds(
      beijingCivil.getFullYear(),
      beijingCivil.getMonth() + 1,
      beijingCivil.getDate()
    );
    let trueSolar = beijingCivil;
    if (useTrueSolar) {
      trueSolar = addMinutes(beijingCivil, lonMin + eot / 60);
    }
    const dayAhead = inAmericasDayAhead(lonN) ? 1 : 0;
    // 公历／农历始终用当地墙钟日；日干支进一在 adjustPillarsForGeo 处理，不改历日
    return {
      beijingCivil,
      trueSolar,
      pillarSolar: trueSolar,
      localCivil: new Date(local.y, local.m - 1, local.d, local.h, local.mi, 0),
      dayAhead,
      lonMin,
      eotSec: eot,
      southern: false,
      note: dayAhead
        ? "美洲进一：当地公历／农历不变，仅日干支+1；时支当地、时干五鼠遁"
        : "日柱按当地／真太阳日",
    };
  }

  function flipMonthBranch(monthGz) {
    if (!monthGz || monthGz.length < 2) return monthGz;
    const stem = monthGz[0], br = monthGz[1];
    return stem + (OPP[br] || br);
  }

  function jiaziIndex(gz) {
    if (!gz || gz.length < 2) return 0;
    for (let i = 0; i < 60; i++) {
      if (TG[i % 10] + DZ[i % 12] === gz) return i;
    }
    return 0;
  }

  function nextJiazi(gz) {
    const n = (jiaziIndex(gz) + 1) % 60;
    return TG[n % 10] + DZ[n % 12];
  }

  /** 时辰地支索引：23:00–01:00＝子 */
  function hourBranchIndex(hour, minute) {
    const total = (+hour || 0) + (+minute || 0) / 60;
    if (total >= 23 || total < 1) return 0;
    return Math.floor((total + 1) / 2) % 12;
  }

  /** 五鼠遁：由日干 + 时支 → 时干 */
  function hourStemFromDay(dayStem, hourZhiIdx) {
    const baseMap = { 甲:0, 己:0, 乙:2, 庚:2, 丙:4, 辛:4, 丁:6, 壬:6, 戊:8, 癸:8 };
    const base = baseMap[dayStem] != null ? baseMap[dayStem] : 0;
    return TG[(base + (hourZhiIdx % 12) + 120) % 10];
  }

  /**
   * 地理校正四柱（不改公历／农历）
   * · 美洲进一区：仅日干支 +1；时支取当地，时干按新日干五鼠遁
   * · 南半球：仅月支取对冲，月干不变
   */
  function adjustPillarsForGeo(pillars, opts) {
    const dayAhead = !!opts.dayAhead;
    const southern = !!opts.southern;
    const hour = opts.hour != null ? opts.hour : 12;
    const minute = opts.minute != null ? opts.minute : 0;
    let p = {
      year: pillars.year,
      month: pillars.month,
      day: pillars.day,
      time: pillars.time,
    };
    const notes = [];
    if (dayAhead) {
      const oldDay = p.day;
      const oldTime = p.time;
      p.day = nextJiazi(p.day);
      const hb = hourBranchIndex(hour, minute);
      const hs = hourStemFromDay(p.day[0], hb);
      p.time = hs + DZ[hb];
      notes.push(
        `美洲进一：公历／农历不变，日干支 ${oldDay}→${p.day}；时支当地、时干五鼠遁 ${oldTime}→${p.time}`
      );
    }
    if (southern && p.month) {
      const before = p.month;
      p.month = flipMonthBranch(p.month);
      if (p.month !== before) {
        notes.push(`南半球：月支取对冲（天干不变）${before}→${p.month}`);
      }
    }
    return { pillars: p, notes, dayAhead, southern };
  }

  /**
   * 水火量度（纬度）
   * · 赤道 0° → 水=0、火=1
   * · 极点 |纬|=90°（北+90 / 南−90）→ 水=1、火=0
   * · 水 + 火 ≡ 1；南半球纬度为负，取绝对值参与量度
   */
  function geoFireWater(lat) {
    const t = Math.min(90, Math.max(0, Math.abs(+lat || 0))) / 90;
    return {
      fire: 1 - t,
      water: t,
      lat: +lat || 0,
      note: `|纬|/${90} → 水=${t.toFixed(4)} · 火=${(1 - t).toFixed(4)}（水+火=1）`,
    };
  }

  /**
   * 相对北京子午线的有向经差，归一到 (−180°, 180°]
   * 正＝北京以东（向太平洋／大西洋日界线东弧）→ 木
   * 负＝北京以西（向欧非／大西洋日界线西弧）→ 金
   * 日界线在大西洋对跖 ≈ −60°（距北京东西各 180°）
   */
  function geoLonDeltaFromBeijing(lon) {
    let d = normLon(lon) - BEIJING_MERIDIAN;
    while (d > 180) d -= 360;
    while (d <= -180) d += 360;
    return d;
  }

  /** 自北京向东走到 lon 的度数 [0, 360) */
  function eastwardFromBeijing(lon) {
    let d = normLon(lon) - BEIJING_MERIDIAN;
    while (d < 0) d += 360;
    while (d >= 360) d -= 360;
    return d;
  }

  /**
   * 金木含量（北京为中心 · 大西洋日界线）
   * · 中轴：北京子午线 120°E → 金=0、木=0
   * · 日界线：大西洋对跖 ≈ 60°W（−60°），东西各 180° 处金或木 → 1
   * · 北京以东 0 < 木 ≤ 1（至日界线），金=0
   * · 北京以西 0 < 金 ≤ 1（至日界线），木=0
   */
  function geoMetalWoodFromBeijing(lon) {
    const d = geoLonDeltaFromBeijing(lon); // (−180, 180]
    const east = eastwardFromBeijing(lon);
    if (Math.abs(d) < 1e-12) {
      return {
        metal: 0, wood: 0, delta: 0,
        axis: BEIJING_MERIDIAN,
        dateLine: ATLANTIC_DATE_LINE,
        note: "恰在北京子午线 · 金0 木0（日界线在大西洋 −60°）",
      };
    }
    // 东弧：0° < east ≤ 180° → 木；西弧：east > 180° → 金
    if (east <= 180 + 1e-12) {
      const wood = Math.min(1, Math.max(0, east / 180));
      return {
        metal: 0, wood, delta: d,
        axis: BEIJING_MERIDIAN,
        dateLine: ATLANTIC_DATE_LINE,
        note: `北京以东 ${east.toFixed(2)}°／180° → 木=${wood.toFixed(4)} · 金=0（至大西洋日界线）`,
      };
    }
    const west = 360 - east;
    const metal = Math.min(1, Math.max(0, west / 180));
    return {
      metal, wood: 0, delta: d,
      axis: BEIJING_MERIDIAN,
      dateLine: ATLANTIC_DATE_LINE,
      note: `北京以西 ${west.toFixed(2)}°／180° → 金=${metal.toFixed(4)} · 木=0（至大西洋日界线）`,
    };
  }

  /** @deprecated 保留别名；金木均相对北京中轴／大西洋日界线 */
  function geoMetalWoodRelative(_birthLon, standLon) {
    return geoMetalWoodFromBeijing(standLon);
  }

  /**
   * 月支本气令位：旺＝当令；相＝令生者；休＝生令者；死＝令克者；囚＝克令者。
   * 只给合化临界（化神对月令旺／相才化）。能量令用 layerLingFactor，勿混。
   */
  function seasonStage(elIdx, monthBranchIdx) {
    const cmd = BRANCH_WX[monthBranchIdx % 12];
    if (elIdx === cmd) return "wang";
    if ((cmd + 1) % 5 === elIdx) return "xiang";
    if ((elIdx + 1) % 5 === cmd) return "xiu";
    if ((cmd + 2) % 5 === elIdx) return "si";
    return "qiu";
  }

  function lingFactor(elIdx, monthBranchIdx, P) {
    const L = (P && P.ling) || { wang:1.5, xiang:1.25, xiu:0.75, qiu:0.875, si:0.625 };
    return L[seasonStage(elIdx, monthBranchIdx)] || 1;
  }

  function lingLayerCoef(stage, P) {
    const L = (P && (P.lingLayer || P.ling)) || { wang:1.5, xiang:1.25, xiu:0.75, qiu:0.875, si:0.625 };
    return L[stage] || 1;
  }

  function stageVsCommand(elIdx, cmd) {
    if (elIdx === cmd) return "wang";
    if ((cmd + 1) % 5 === elIdx) return "xiang";
    if ((elIdx + 1) % 5 === cmd) return "xiu";
    if ((cmd + 2) % 5 === elIdx) return "si";
    return "qiu";
  }

  /**
   * 原局字距（令地与合冲同一尺）：同柱／同层邻柱 a=0；
   * 同层 a=|柱差|−1；跨层 a=|柱差|。不是整柱一个 E。
   * 令地：戌／月令源 1−(1−L)×E戌；午／时地源 1−(1−D)×¼×E午。先戌后午。地打满盘。
   */
  function natalCharDist(pa, layerA, pb, layerB) {
    if (pa === pb) return 0;
    if (layerA === layerB) return Math.max(0, Math.abs(pa - pb) - 1);
    return Math.abs(pa - pb);
  }
  function distE(a) { return Math.pow(0.5, a); }
  /**
   * 令落盘：1−(1−L)×E。字距只打折令的损益，不把整字乘 E。
   * 例：癸在戌月 L=⅝×0.6＋1.25×0.3＋⅞×0.1＝0.8375，年干 E=½
   * → 1−(1−0.8375)×½＝0.91875。
   */
  function lingApply(L, E) {
    const e = E == null ? 1 : E;
    return 1 - (1 - L) * e;
  }
  /**
   * 地落盘：1−(1−D)×¼×E。D＝时支层权令，¼＝地相对令，E＝午字距。
   * 例：令后癸 G，午 D水＝⅞×0.7＋⅝×0.3＝0.8，年干 E＝⅛
   * → g＝G×[1−(1−0.8)×¼×⅛]＝G×0.99375。
   */
  function diApply(D, E, diRel) {
    const e = E == null ? 1 : E;
    const r = diRel == null ? 0.25 : diRel;
    return 1 - (1 - D) * r * e;
  }
  /** 戌字距 E。例：丑戌／戌申 a=0→1；戌甲 a=1→½；戌午 a=1→½；戌庚 a=2→¼。 */
  function lingFallE(pillarIdx, layer) {
    const p = pillarIdx == null ? 1 : pillarIdx;
    const lay = layer || "zhi";
    return 1.0 * distE(natalCharDist(1, "zhi", p, lay));
  }
  /** 午字距 E。年干 a=3→⅛；年支 a=2→¼；月干 a=2→¼；月支 a=1→½；日干 a=1→½；日支／时柱 a=0→1。 */
  function diFallE(pillarIdx, layer) {
    const p = pillarIdx == null ? 3 : pillarIdx;
    const lay = layer || "zhi";
    return 1.0 * distE(natalCharDist(3, "zhi", p, lay));
  }

  /** 本钱尺。天干以日主为中心：日1.0、月7/8、年／时3/4。 */
  function stemBaseW(pillarIdx) {
    const i = pillarIdx == null ? 2 : pillarIdx;
    if (i === 2) return 1.0;
    if (i === 1) return 7 / 8;
    return 3 / 4;
  }
  /** 本钱尺。地支以月令为中心：月1.0、日7/8、年／时3/4。 */
  function branchBaseW(pillarIdx) {
    const i = pillarIdx == null ? 1 : pillarIdx;
    if (i === 1) return 1.0;
    if (i === 2) return 7 / 8;
    return 3 / 4;
  }

  /**
   * 流局层序：流年0／流月1／流日2／流时3。
   * 流局是外部环境，不是人类盘：每字1.0，只乘与命主距离 E=(1/2)^a。
   */
  function flowLayerIndex(label, fallback) {
    const map = { "流年": 0, "流月": 1, "流日": 2, "流时": 3 };
    if (label && Object.prototype.hasOwnProperty.call(map, label)) return map[label];
    return fallback == null ? 0 : fallback;
  }
  function flowLayerE(idx) {
    return distE(idx == null ? 0 : idx);
  }

  /**
   * 层权令：L(E)=Σ 藏干层权_i × 令级(E | 当令=该藏干五行)。
   * 令级唯一尺：旺1.5／相1.25／休0.75／囚⅞／死⅝。
   * 先戌后午：令系数＝1−(1−L)×E戌；地系数＝1−(1−D)×¼×E午。D＝时支层权令。地打满盘。
   * 定体、字能、岁运令地只认此尺。合化临界只用月支本气旺相休囚死（化／绊），不另设令级。
   */
  function layerLingFactor(elIdx, monthBranchIdx, P) {
    const hides = HIDE[DZ[monthBranchIdx % 12]] || [];
    if (!hides.length) return lingLayerCoef(seasonStage(elIdx, monthBranchIdx), P);
    let sum = 0;
    for (let i = 0; i < hides.length; i++) {
      const cmd = STEM_WX[TG.indexOf(hides[i][0])];
      sum += hides[i][1] * lingLayerCoef(stageVsCommand(elIdx, cmd), P);
    }
    return sum;
  }

  /** 时支层权令 D。作者盘午：水0.8／火1.275／土1.325／金0.8125／木0.7875。 */
  function layerDiFactor(elIdx, hourBranchIdx, P) {
    return layerLingFactor(elIdx, hourBranchIdx, P);
  }

  function shiShenBucket(dayStem, otherStem) {
    if (dayStem === otherStem) return "biJie";
    const me = STEM_WX[TG.indexOf(dayStem)];
    const him = STEM_WX[TG.indexOf(otherStem)];
    const samePolar = ("甲丙戊庚壬".includes(dayStem) === "甲丙戊庚壬".includes(otherStem));
    if (me === him) return "biJie";
    const sheng = (me + 1) % 5;
    const ke = (me + 2) % 5;
    const keMe = (him + 2) % 5 === me;
    const shengMe = (him + 1) % 5 === me;
    if (sheng === him) return "shiShang";
    if (ke === him) return "cai";
    if (keMe) return "guan";
    if (shengMe) return "yin";
    return "biJie";
  }

  function emptyEl() { return { 木:0, 火:0, 土:0, 金:0, 水:0 }; }

  function addEl(el, wxName, v) { el[wxName] = (el[wxName] || 0) + v; }

  const GEO_STACK = 0.35;

  /** 地理水火／金木 → 五行增量。不进字能，只进正从／我客／家内外总量。 */
  function geoStackEl(fw, mw, stack) {
    const s = stack == null ? GEO_STACK : stack;
    return {
      木: (mw && mw.wood || 0) * s,
      火: (fw && fw.fire || 0) * s,
      土: 0,
      金: (mw && mw.metal || 0) * s,
      水: (fw && fw.water || 0) * s,
    };
  }

  function mergeEl(a, b) {
    const o = emptyEl();
    WX.forEach(w => { o[w] = (a && a[w] || 0) + (b && b[w] || 0); });
    return o;
  }

  function sumEl(el) {
    return el ? (el.木 + el.火 + el.土 + el.金 + el.水) : 0;
  }

  /**
   * 地后能量骨架：先戌（令系数 1−(1−L)×E戌）后午（地系数 1−(1−D)×¼×E午）。地打满盘。
   * 令地旺衰不算合冲生克：此处只相加。
   * + 出生地水火（纬度）+ 出生地金木（相对北京子午线）
   * （非完整 BQM 生克链；专档在此基础上做轻量冲合损耗示意）
   */
  function accumulateNatal(pillars, birthLat, birthLon, P) {
    const dayStem = pillars.day[0];
    const monthBr = DZ.indexOf(pillars.month[1]);
    const hourBr = DZ.indexOf(pillars.time[1]);
    const diRel = (P && P.diRelativeToLing != null) ? P.diRelativeToLing : 0.25;
    const el = emptyEl();
    const elWai = emptyEl();
    const elNei = emptyEl();
    const parts = { biJie:0, yin:0, cai:0, guan:0, shiShang:0 };
    const gzList = [pillars.year, pillars.month, pillars.day, pillars.time];

    function addStemEnergy(stem, base, pillarIdx) {
      const wi = STEM_WX[TG.indexOf(stem)];
      const f = lingApply(layerLingFactor(wi, monthBr, P), lingFallE(pillarIdx, "gan"))
        * diApply(layerDiFactor(wi, hourBr, P), diFallE(pillarIdx, "gan"), diRel);
      const e = base * f;
      addEl(el, WX[wi], e);
      const b = shiShenBucket(dayStem, stem);
      if (stem === dayStem) parts.biJie += e;
      else parts[b] += e;
      return e;
    }
    function addBranch(br, pillarIdx) {
      const hides = HIDE[br] || [];
      hides.forEach(([stem, w]) => {
        const wi = STEM_WX[TG.indexOf(stem)];
        const f = lingApply(layerLingFactor(wi, monthBr, P), lingFallE(pillarIdx, "zhi"))
          * diApply(layerDiFactor(wi, hourBr, P), diFallE(pillarIdx, "zhi"), diRel);
        const e = w * branchBaseW(pillarIdx) * f;
        addEl(el, WX[wi], e);
        const b = shiShenBucket(dayStem, stem);
        parts[b] += e;
      });
    }

    gzList.forEach((gz, i) => {
      const before = { 木: el.木, 火: el.火, 土: el.土, 金: el.金, 水: el.水 };
      addStemEnergy(gz[0], stemBaseW(i), i);
      addBranch(gz[1], i);
      const dest = i < 2 ? elWai : elNei;
      WX.forEach(w => addEl(dest, w, el[w] - before[w]));
    });

    // 字能／做功只认干支×令×地。地理水火金木不进字，只进正从／我客／家内外总量。
    const elLingDi = { 木: el.木, 火: el.火, 土: el.土, 金: el.金, 水: el.水 };
    const fw = geoFireWater(birthLat);
    const mw = geoMetalWoodFromBeijing(birthLon);
    const geoBirth = geoStackEl(fw, mw, GEO_STACK);

    return { el: mergeEl(elLingDi, geoBirth), elLingDi, elNei, elWai, geoBirth, parts, fireWater: fw, metalWood: mw, monthBr };
  }

  /**
   * 现地流层：外部环境，不是另一命局。
   * 每字 1.0，只乘与命主距离：流年1／流月½／流日¼／流时⅛。
   * 无令、无地、无年权。藏干仍按层权分账。地理不进流层字。
   * 令地／年权只给人类盘（本命／太极／合参他人）。
   */
  function accumulateFlow(flowPillars, standLat, standLon, P) {
    const fw = geoFireWater(standLat);
    const mw = geoMetalWoodFromBeijing(standLon);
    const el = emptyEl();
    if (flowPillars) {
      [["year",0],["month",1],["day",2],["time",3]].forEach(([k, i]) => {
        const gz = flowPillars[k];
        if (!gz || gz.length < 2) return;
        const e = flowLayerE(i);
        const wi = STEM_WX[TG.indexOf(gz[0])];
        addEl(el, WX[wi], 1.0 * e);
        const hides = HIDE[gz[1]] || [];
        hides.forEach(([stem, w]) => {
          const hwi = STEM_WX[TG.indexOf(stem)];
          addEl(el, WX[hwi], w * e);
        });
      });
    }
    const geoStand = geoStackEl(fw, mw, GEO_STACK);
    return { el, geoStand, fireWater: fw, metalWood: mw };
  }

  /** 轻量生克后：按冲合对地支做损耗（参数表） */
  function applyShengKeSketch(el, pillars, P) {
    const out = Object.assign({}, el);
    const sk = (P && P.shengKe) || {};
    const z = [pillars.year[1], pillars.month[1], pillars.day[1], pillars.time[1]];
    const chong = {子:"午",午:"子",丑:"未",未:"丑",寅:"申",申:"寅",卯:"酉",酉:"卯",辰:"戌",戌:"辰",巳:"亥",亥:"巳"};
    const he = {子:"丑",丑:"子",寅:"亥",亥:"寅",卯:"戌",戌:"卯",辰:"酉",酉:"辰",巳:"申",申:"巳",午:"未",未:"午"};
    for (let i = 0; i < 4; i++) {
      for (let j = i + 1; j < 4; j++) {
        const a = z[i], b = z[j];
        const wa = BRANCH_WX[DZ.indexOf(a)], wb = BRANCH_WX[DZ.indexOf(b)];
        if (chong[a] === b) {
          const lossA = sk.chong ? sk.chong.a : -0.5;
          const lossB = sk.chong ? sk.chong.b : -0.625;
          out[WX[wa]] *= (1 + lossA);
          out[WX[wb]] *= (1 + lossB);
        }
        if (he[a] === b) {
          const lossA = sk.he ? sk.he.a : -0.25;
          const lossB = sk.he ? sk.he.b : -0.25;
          out[WX[wa]] *= (1 + lossA);
          out[WX[wb]] *= (1 + lossB);
        }
      }
    }
    return out;
  }

  function campFromParts(parts) {
    // 产品文案：我＝印比；客＝食伤财官（食伤计入客）
    const wo = parts.biJie + parts.yin;
    const ta = parts.cai + parts.guan + parts.shiShang;
    return { ...parts, wo, ta };
  }

  function campFromElements(el, dayStem) {
    const dm = STEM_WX[TG.indexOf(dayStem)];
    const yin = (dm + 4) % 5, shi = (dm + 1) % 5, cai = (dm + 2) % 5, guan = (dm + 3) % 5;
    const parts = {
      biJie: el[WX[dm]] || 0,
      yin: el[WX[yin]] || 0,
      shiShang: el[WX[shi]] || 0,
      cai: el[WX[cai]] || 0,
      guan: el[WX[guan]] || 0,
    };
    return campFromParts(parts);
  }

  /** 做功后五行比照令地，按比例落到家内／家外字账（地理不进字，不参与比例）。 */
  function scaleElByGong(elPart, elBefore, elAfter) {
    const o = emptyEl();
    WX.forEach(w => {
      const b = (elBefore && elBefore[w]) || 0;
      const a = (elAfter && elAfter[w]) || 0;
      const p = (elPart && elPart[w]) || 0;
      o[w] = b > 1e-12 ? p * (a / b) : p;
    });
    return o;
  }

  const GOD_ROWS = [
    { key: "yin", name: "印枭", bucket: "wo" },
    { key: "biJie", name: "比劫", bucket: "wo" },
    { key: "shiShang", name: "食伤", bucket: "ta" },
    { key: "cai", name: "财才", bucket: "ta" },
    { key: "guan", name: "官杀", bucket: "ta" },
  ];

  function woBand(wo) {
    if (wo <= 0.25) return "趋零";
    if (wo <= 1.0) return "危";
    if (wo < 2.0) return "安";
    return "稳旺";
  }

  /**
   * 我客：断正从；作用后十神增减 → 夭寿／身体／能力好指数。
   * 家内外：自家／可利用资源实际量；作用后看双方增减＋十神对自家与日主利败。
   */
  function judgeVitality(opts) {
    const wo0 = opts.woBefore || 0, wo1 = opts.woAfter || 0;
    const ta0 = opts.taBefore || 0, ta1 = opts.taAfter || 0;
    const pb = opts.partsBefore || {};
    const pa = opts.partsAfter || {};
    const useGuest = !!opts.useGuest;
    const balanced = !!opts.balanced;
    const gongDeLi = !!opts.gongDeLi;
    const band = woBand(wo1);
    const shouZh = band === "趋零" ? "夭危（我方趋零，非医疗诊断）"
      : band === "危" ? "寿偏危（我方未过1.0，先护体）"
      : band === "安" ? "寿安（我方过阈值，体气可承）"
      : "寿稳（我方稳旺）";
    const yinBi1 = (pa.yin || 0) + (pa.biJie || 0);
    const shenBand = woBand(yinBi1);
    const shenZh = shenBand === "趋零" ? "体危（印比趋零）"
      : shenBand === "危" ? "体偏弱（印比未过1.0）"
      : shenBand === "安" ? "体安"
      : "体旺";
    const dWo = wo1 - wo0;
    let nengBand, nengZh;
    if (gongDeLi) { nengBand = "用神得力"; nengZh = "能力好（做功后身旺，用神得力）"; }
    else if (dWo > 0.15) { nengBand = "做功增益"; nengZh = "能力升（做功后我方增）"; }
    else if (dWo < -0.15) { nengBand = "做功减损"; nengZh = "能力降（做功后我方减）"; }
    else { nengBand = "持平"; nengZh = "能力平（做功后我方持平）"; }
    const gods = GOD_ROWS.map(g => {
      const before = pb[g.key] || 0;
      const after = pa[g.key] || 0;
      const toDay = balanced
        ? (signedHelp(opts.dWoGong || 0) > 0 ? "利" : (signedHelp(opts.dWoGong || 0) < 0 ? "败" : "平"))
        : ((useGuest ? g.bucket === "ta" : g.bucket === "wo") ? "利" : "败");
      const toJia = balanced
        ? (signedHelp(opts.dNeiGong || 0) > 0 ? "利" : (signedHelp(opts.dNeiGong || 0) < 0 ? "败" : "平"))
        : toDay;
      return {
        key: g.key, name: g.name, bucket: g.bucket,
        before, after, delta: after - before,
        toDay: toDay,
        toJia: toJia,
      };
    });
    return {
      woBefore: wo0, taBefore: ta0, woAfter: wo1, taAfter: ta1,
      shou: { score: wo1, band: band, zh: shouZh },
      shen: { score: yinBi1, band: shenBand, zh: shenZh },
      neng: { score: wo1, band: nengBand, zh: nengZh, delta: dWo },
      gods,
    };
  }

  function isBalanced(wo, ta) {
    const m = Math.max(wo, ta);
    if (m <= 1e-12) return false;
    return Math.abs(wo - ta) / m <= BALANCE_RATIO + 1e-12;
  }

  function signedHelp(d) {
    if (d > 1e-12) return 1;
    if (d < -1e-12) return -1;
    return 0;
  }

  /** 正格平衡：用忌看做功对自身与家内增减，不走身强用客／身弱用我。 */
  function judgeYongJiFromGong(dWo, dNei) {
    const s = signedHelp(dWo);
    const n = signedHelp(dNei);
    const helpSelf = s > 0, helpNei = n > 0;
    const hurtSelf = s < 0, hurtNei = n < 0;
    let orient;
    if (s === 0 && n === 0) orient = "用忌看家内外做功";
    else if (!hurtSelf && !hurtNei) orient = "做功利自己与家内";
    else if (!helpSelf && !helpNei) orient = "做功败自己与家内";
    else orient = "做功一利一败";
    return {
      pattern: "正格",
      strength: "平衡",
      balanced: true,
      useGuest: null,
      yongShen: [],
      jiShen: [],
      yongJiSoft: false,
      orient,
      helpSelf, helpNei, hurtSelf, hurtNei,
      dWo: dWo || 0, dNei: dNei || 0,
      wo: null, ta: null,
      summary: `正格·平衡 · ${orient}`,
      notes: [`正格平衡：Δ我${(dWo || 0) >= 0 ? "+" : ""}${(dWo || 0).toFixed(2)}，Δ家内${(dNei || 0) >= 0 ? "+" : ""}${(dNei || 0).toFixed(2)} → ${orient}`],
    };
  }

  function judgeZhengCong(camp) {
    const wo = camp.wo, ta = camp.ta;
    const notes = [];
    let pattern, strength, useGuest, balanced = false, orient;
    if (wo <= BODY_TH) {
      pattern = "从弱"; strength = "身弱"; useGuest = true;
      notes.push(`我方${wo.toFixed(2)}≤${BODY_TH} → 从弱（用客忌我）`);
    } else if (ta <= BODY_TH) {
      pattern = "从强"; strength = "身强"; useGuest = false;
      notes.push(`客方${ta.toFixed(2)}≤${BODY_TH} → 从强（用我忌客）`);
    } else {
      pattern = "正格";
      if (isBalanced(wo, ta)) {
        strength = "平衡"; useGuest = null; balanced = true;
        notes.push(`正格：|我${wo.toFixed(2)}−客${ta.toFixed(2)}|／max≤⅛ → 平衡（用忌看家内外做功）`);
      } else if (wo > ta) {
        strength = "身强"; useGuest = true;
        notes.push(`正格：我${wo.toFixed(2)}＞客${ta.toFixed(2)} → 身强（用客忌我）`);
      } else {
        strength = "身弱"; useGuest = false;
        notes.push(`正格：我${wo.toFixed(2)}＜客${ta.toFixed(2)} → 身弱（用我忌客）`);
      }
    }
    const yongShen = balanced ? [] : (useGuest
      ? ["食神","伤官","正财","偏财","正官","七杀"]
      : ["正印","偏印","比肩","劫财"]);
    const jiShen = balanced ? [] : (useGuest
      ? ["正印","偏印","比肩","劫财"]
      : ["食神","伤官","正财","偏财","正官","七杀"]);
    const yongJiSoft = pattern === "正格" && wo > YONGJI_SOFT_TH && ta > YONGJI_SOFT_TH;
    orient = balanced ? "用忌看家内外做功" : (useGuest ? "用客忌我" : "用我忌客");
    return {
      pattern, strength, useGuest, balanced, yongShen, jiShen, yongJiSoft, orient,
      wo, ta,
      summary: `${pattern}·${strength}${yongJiSoft?"·用忌轻":""} · 我${wo.toFixed(2)}∶客${ta.toFixed(2)} · ${orient}`,
      notes,
    };
  }

  function computeBI(parts) {
    const support = parts.yin + parts.biJie;
    const attack = parts.cai + parts.guan + parts.shiShang;
    const bi = attack < 1e-12 ? (support > 0 ? 99 : 0) : support / attack;
    const total = support + attack || 1;
    const label = bi > 1.2 ? "身强" : (bi < 0.8 ? "身弱" : "平衡");
    return {
      bi, label,
      shares: {
        yin: parts.yin / total,
        biJie: parts.biJie / total,
        shiShang: parts.shiShang / total,
        cai: parts.cai / total,
        guanSha: parts.guan / total,
      },
    };
  }

  function pairingBand(bi) {
    if (bi == null) return "中和";
    if (bi <= BI_RUO_JI) return "身弱极";
    if (bi < BI_PIAN) return "偏弱中和";
    if (bi <= BI_QIANG) return "中和";
    return "身强极";
  }

  const MAXIMS_RUO = [
    { key: "ruoSha", when: (c) => c.shares.guanSha >= 0.25, text: "【专属·杀印相生】杀重身弱，印星通关，化杀为权。压力傍身，贵气在「智」不在「力」。" },
    { key: "ruoCai", when: (c) => c.shares.cai >= 0.30, text: "【专属·财旺耗身】富屋贫人，财多累身。账面富贵，囊中羞涩，守财比求财更难。" },
    { key: "ruoShi", when: (c) => c.shares.shiShang >= 0.22, text: "【专属·食伤泄身】才华横溢，但「才」亦耗身。表达欲越强，元气越伤，宜以印制约食伤。" },
    { key: "ruoBase", when: () => true, text: "【身弱极】BI≤0.3：宜生扶——印比为舟，忌再叠财官食伤攻身。" },
  ];
  const MAXIMS_QIANG = [
    { key: "qiangYin", when: (c) => c.shares.yin + c.shares.biJie >= 0.28, text: "印比偏壅：贵人同事虽多，易滞重；宜食伤财官运疏通，忌再叠印比。" },
    { key: "qiangShi", when: (c) => c.useGuest && c.shares.shiShang >= 0.20, text: "用客·食伤有气：泄秀变现顺势；忌印运截流夺食。" },
    { key: "qiangBase", when: () => true, text: "【身强极】强者宜克泄——食伤泄秀、财耗、官杀制为顺；印比再壅为滞。" },
  ];

  function vt(key, vars) {
    const L = global.KRLang;
    if (L && L.verdict) return L.verdict(key, vars);
    return null;
  }
  function trm(s) {
    const L = global.KRLang;
    return L && L.term ? L.term(s) : s;
  }
  function keyList(arr) {
    return arr.map(k => `${(k.chars || []).join("")}${trm(k.cls)}(${(k.intensity || 0).toFixed(2)})`).join("、");
  }

  function selectVerdicts(ctx) {
    const band = pairingBand(ctx.bi);
    const lines = [];
    const take = (s) => { if (s && !lines.includes(s) && lines.length < 8) lines.push(s); };
    const yong = ctx.yongShen.slice(0, 3), ji = ctx.jiShen.slice(0, 3);
    const jx = ctx.workJiXiong ? String(ctx.workJiXiong).replace(/用忌轻（正格双＞2）· /, () => (trm("用忌轻") + " · ")) : "";
    const jxT = jx.split("· ").map(x => trm(x.trim())).join(" · ");

    if (ctx.xinGong && ctx.xinGong.ok) {
      const xg = ctx.xinGong;
      if (xg.ledger && xg.ledger.summary) take(`【格局做功】${xg.ledger.summary}`);
      if (xg.summary) take(`【功用链】${xg.summary}`);
      if (xg.soul) take(`【心性】${xg.soul}`);
      if (xg.yearBrief) take(`【流年做功】${xg.yearBrief}`);
    }

    take(vt("pairing", { band, bi: ctx.bi.toFixed(2), biLabel: ctx.biLabel, orient: ctx.orient, yong, ji }) ||
      `【配对指数】${band} · BI=${ctx.bi.toFixed(2)}（${ctx.biLabel}）· ${ctx.orient} · 用[${yong.join("·")}] 忌[${ji.join("·")}]`);
    if (ctx.workJiXiong) take(vt("work", { name: ctx.workName || "格局运转", jx: jxT || trm(ctx.workJiXiong) }) ||
      `【做功】${ctx.workName || "格局运转"} → ${ctx.workJiXiong}`);
    if (ctx.manifestKeys && ctx.manifestKeys.length) {
      const top = ctx.manifestKeys.slice().sort((a, b) => b.intensity - a.intensity).slice(0, 3);
      take(vt("manifest", { list: keyList(top) }) ||
        `【键·事显】${top.map(k => `${(k.chars || []).join("")}${k.cls}(烈${k.intensity})`).join("、")} → 烈度≥2.0 主导本盘应期与显象。`);
    } else if (ctx.keys && ctx.keys.length) {
      const top = ctx.keys.slice().sort((a, b) => b.intensity - a.intensity).slice(0, 2);
      if (top.length) take(vt("latent", { list: keyList(top) }) ||
        `【键·潜】${top.map(k => `${(k.chars || []).join("")}${k.cls}(烈${k.intensity})`).join("、")} → 未达事显(＜2.0)，逢岁运催化方显。`);
    }
    if (ctx.tonggen && ctx.tonggen.length) {
      const roots = ctx.tonggen.filter(t => t.self);
      if (roots.length) take(vt("root", { list: roots.slice(0, 3).map(t => `${t.stem}→${t.atPillar}`).join("、") }) ||
        `【通根】日/干本根：${roots.slice(0, 3).map(t => `${t.stem}根${t.atPillar}支`).join("、")} → 有根不弱，得地气承载。`);
    }

    if (band === "身弱极") {
      MAXIMS_RUO.forEach(m => {
        if (!m.when(ctx)) return;
        const key = m.key;
        take((key && vt(key)) || m.text);
      });
    } else if (band === "身强极" || (ctx.shenQiang && ctx.bi >= BI_QIANG)) {
      MAXIMS_QIANG.forEach(m => {
        if (!m.when(ctx)) return;
        const key = m.key;
        take((key && vt(key)) || m.text);
      });
    } else {
      take(vt("mid", { band, bi: ctx.bi.toFixed(2), yin: pct(ctx.shares.yin), biJie: pct(ctx.shares.biJie), shi: pct(ctx.shares.shiShang), cai: pct(ctx.shares.cai), guan: pct(ctx.shares.guanSha), orient: ctx.orient, jx: jxT || trm(ctx.workJiXiong || "待核") }) ||
        `【专属·${band}】BI=${ctx.bi.toFixed(2)} · 十神份额 印${pct(ctx.shares.yin)} 比${pct(ctx.shares.biJie)} 食伤${pct(ctx.shares.shiShang)} 财${pct(ctx.shares.cai)} 官杀${pct(ctx.shares.guanSha)} · 冻结${ctx.orient} · 做功${ctx.workJiXiong || "待核"}`);
      take(vt("midNote") || "中和带禁套极带库；句内须含 BI·十神份额·冻结用忌·做功吉凶；相对本盘日主。");
    }
    if (ctx.intensity >= MANIFEST_TH) {
      take(vt("showTh", { n: ctx.intensity.toFixed(2), th: MANIFEST_TH }) ||
        `【事显】烈度 ${ctx.intensity.toFixed(2)} ≥ ${MANIFEST_TH}，达事显阈值（烈度＝事显概率／幅度，≠五行能量条）。`);
    } else if (ctx.liveWhen) {
      take(vt("hereNow") || "【当时当地】条图与断语跟洋葱流层：年→时→运→流时；烈度只定事显，不改写五行总量。");
    }
    return { band, lines };
  }

  function pct(v) { return Math.round((v || 0) * 100) + "%"; }

  /** 做功格局示意：按十神结构点名 */
  function workJiXiong(zheng, useGuestYes, useGuestNo) {
    if (zheng && zheng.balanced) return zheng.orient || "用忌看家内外做功";
    return zheng && zheng.useGuest ? useGuestYes : useGuestNo;
  }

  function detectWorkPatterns(parts, zheng) {
    const hits = [];
    if (parts.guan >= 0.35 && parts.yin >= 0.25) {
      hits.push({ name: "杀印相生", jiXiong: workJiXiong(zheng, "利忌偏多·需二级再辨", "有利于用（生扶）"), note: "官杀有气、印通关" });
    }
    if (parts.shiShang >= 0.3 && parts.cai >= 0.25) {
      hits.push({ name: "食伤生财", jiXiong: workJiXiong(zheng, "利于用（泄耗）", "利忌·泄身"), note: "食伤泄秀生财" });
    }
    if (parts.cai >= 0.35 && parts.guan >= 0.2) {
      hits.push({ name: "财官相生", jiXiong: workJiXiong(zheng, "利于用", "利忌·财官攻身"), note: "财生官杀" });
    }
    if (parts.biJie >= 0.4 && parts.cai >= 0.2) {
      hits.push({ name: "比劫夺财", jiXiong: workJiXiong(zheng, "利忌·争合", "利于用（比劫）需防夺"), note: "身党见财" });
    }
    if (!hits.length) {
      hits.push({ name: "扶抑常规", jiXiong: workJiXiong(zheng, "宜克泄耗疏通", "宜生扶护身"), note: "无特殊合化旗标" });
    }
    // 相对冻结用忌粗判吉凶
    hits.forEach(h => {
      if (zheng.yongJiSoft) h.jiXiong = "用忌轻（正格双＞2）· " + h.jiXiong;
    });
    return hits;
  }

  /**
   * 主入口：给定四柱 + 地理 → 全流程分析结果
   *
   * 双锚：
   * · 出生地 → 公历/农历/干支、大运/小运 + 出生水火 + 出生金木（北京子午线轴）
   * · 现地 → 流年/流月/流日/流时 + 现地水火 + 现地金木（北京子午线轴）
   */
  function analyze(opts) {
    const P = opts.params || global.KRParams || {};
    const pillars0 = opts.pillars; // {year,month,day,time}
    const birthLat = opts.birthLat;
    const birthLon = opts.birthLon;
    const standLat = opts.standLat != null ? opts.standLat : birthLat;
    const standLon = opts.standLon != null ? opts.standLon : birthLon;
    const timeInfo = opts.timeInfo || null;
    const flowPillars = opts.flowPillars || null; // {year,month,day,time}
    const extras = opts.extras || null; // [{gz,label,kind,scale}] 柱序动态层
    const flowTimeInfo = opts.flowTimeInfo || null;
    const localHour = opts.localHour != null ? opts.localHour : 12;
    const localMinute = opts.localMinute != null ? opts.localMinute : 0;

    // 公历／农历由调用方按当地日显示；此处只改干支：美洲日+1、南半球月支冲
    const natalAdj = adjustPillarsForGeo(pillars0, {
      dayAhead: inAmericasDayAhead(birthLon),
      southern: birthLat < 0,
      hour: localHour,
      minute: localMinute,
    });
    let pillars = natalAdj.pillars;
    const south = natalAdj.southern;
    const southNote = natalAdj.notes.filter(n => n.indexOf("南半球") >= 0).join("；");
    const dayAheadNotes = natalAdj.notes.filter(n => n.indexOf("美洲") >= 0);

    const natal = accumulateNatal(pillars, birthLat, birthLon, P);
    // 令地旺衰不算合冲生克：字账只相加。地理水火金木不进字，只叠进正从／我客／家内外总量。
    const elLingDi = natal.elLingDi || natal.el;
    const geoBirth = natal.geoBirth || geoStackEl(natal.fireWater, natal.metalWood, GEO_STACK);
    const elZheng = mergeEl(elLingDi, geoBirth);
    const campDi = campFromElements(elZheng, pillars.day[0]);
    // 身强／身弱只看令地＋出生地理，不算合冲生克。
    const zheng = judgeZhengCong(campDi);
    zheng.el = elZheng;
    zheng.elLingDi = elLingDi;

    // ③④ 纠缠 + 生克链：从令地字账做功（地理不进字作用）
    const relNatal = (global.KRRelations && global.KRRelations.scan)
      ? global.KRRelations.scan(pillars, Object.assign({}, elLingDi), P)
      : null;
    const relLive = (extras && extras.length && global.KRRelations && global.KRRelations.pipeline)
      ? global.KRRelations.pipeline(pillars, Object.assign({}, elLingDi), P, extras)
      : null;
    const rel = relNatal;
    const elShengKeZi = rel ? rel.energyAfter : applyShengKeSketch(elLingDi, pillars, P);
    const elShengKe = mergeEl(elShengKeZi, geoBirth);
    const campSk = campFromElements(elShengKe, pillars.day[0]);
    // 做功字账增减只供二次用忌。定境家内外总量不改写。
    const elNeiGongZi = scaleElByGong(natal.elNei, elLingDi, elShengKeZi);
    const dWoGong = campSk.wo - campDi.wo;
    const dNeiGong = sumEl(elNeiGongZi) - sumEl(natal.elNei);
    // 第二次用忌：做功后只改用忌。正从、我客、家内外不改。
    // 正格平衡不按做功后我客重判身强身弱，只看 Δ我／Δ家内。
    const zhengZhuanJudge = zheng.balanced
      ? Object.assign(judgeYongJiFromGong(dWoGong, dNeiGong), { yongJiSoft: zheng.yongJiSoft })
      : judgeZhengCong(campSk);
    const flippedYong = zheng.balanced
      ? (zhengZhuanJudge.orient !== zheng.orient)
      : (zhengZhuanJudge.useGuest !== zheng.useGuest);
    const zhengZhuan = Object.assign({}, zheng, {
      useGuest: zhengZhuanJudge.useGuest,
      balanced: !!zheng.balanced,
      yongShen: zhengZhuanJudge.yongShen,
      jiShen: zhengZhuanJudge.jiShen,
      yongJiSoft: zhengZhuanJudge.yongJiSoft,
      orient: zhengZhuanJudge.orient,
      helpSelf: zhengZhuanJudge.helpSelf,
      helpNei: zhengZhuanJudge.helpNei,
      dWoGong: dWoGong,
      dNeiGong: dNeiGong,
      gongDeLi: false,
      summary: `${zheng.pattern}·${zheng.strength} · 我${zheng.wo.toFixed(2)}∶客${zheng.ta.toFixed(2)} · 二次用忌${zhengZhuanJudge.orient}${flippedYong ? "（用忌已变）" : "（用忌未变）"}`,
      notes: [
        "做功后只改用忌。正从、我客、家内外不改。",
        `定体 ${zheng.pattern}·${zheng.strength} 我${zheng.wo.toFixed(2)}∶客${zheng.ta.toFixed(2)}`,
        `定境名义用忌＝${zheng.orient}；二次实际用忌＝${zhengZhuanJudge.orient}${flippedYong ? "（已变）" : "（未变）"}`,
      ],
      diHouPattern: zheng.pattern, diHouStrength: zheng.strength,
      yongFlipped: flippedYong,
    });

    const biInfo = computeBI(natal.parts);
    const works = detectWorkPatterns(natal.parts, zhengZhuan);
    const core = works[0];

    // —— 出生层：水火 + 金木（北京轴）——
    const fwBirth = natal.fireWater;
    const mwBirth = natal.metalWood;

    // —— 现地层：流年→时 + 现地水火 + 现地金木（北京轴）——
    // 现地亦：公历农历不变；美洲则流日干支+1；南半球则流月支对冲
    let flowPillarsAdj = null;
    let flowSouthNote = "";
    let flowDayNotes = [];
    if (flowPillars) {
      const flowHour = opts.flowHour != null ? opts.flowHour : localHour;
      const flowMinute = opts.flowMinute != null ? opts.flowMinute : 0;
      const flowAdj = adjustPillarsForGeo(flowPillars, {
        dayAhead: inAmericasDayAhead(standLon),
        southern: standLat < 0,
        hour: flowHour,
        minute: flowMinute,
      });
      flowPillarsAdj = flowAdj.pillars;
      flowSouthNote = flowAdj.notes.filter(n => n.indexOf("南半球") >= 0).join("；");
      flowDayNotes = flowAdj.notes.filter(n => n.indexOf("美洲") >= 0);
    }
    const flowAcc = accumulateFlow(flowPillarsAdj, standLat, standLon, P);
    const fwStand = flowAcc.fireWater;
    const mwStand = flowAcc.metalWood;

    // 烈度＝键共振烈度（canon：事显≥2.0）；有流水线则用当时当地键；烈度≠五行能量
    const live = relLive || rel;
    const intensity = live ? live.maxIntensity
      : (Math.max(campSk.wo, campSk.ta) + (works.length > 1 ? 0.4 : 0));
    const liveGz = (extras || []).map(e => ({ label: e.label, gz: e.gz }));
    const geoStand = flowAcc.geoStand || geoStackEl(fwStand, mwStand, GEO_STACK);
    const elLiveZi = relLive ? relLive.energyAfter : elShengKeZi;
    const elLive = mergeEl(elLiveZi, geoStand);
    const woGong = zheng.wo;
    const woLive = campFromElements(elLive, pillars.day[0]).wo;
    const geoRemedy = woGong > 0 && woLive < woGong * 0.7;
    const elNei = natal.elNei || emptyEl();
    const elWai = natal.elWai || emptyEl();
    const dayStem = pillars.day[0];
    const camp0 = campFromElements(elZheng, dayStem);
    const camp1 = campFromElements(elShengKe, dayStem);
    const camp2 = campFromElements(elLive, dayStem);
    // ①作用前／②运前：定境家外＝年月字，流层未叠。做功不改写家内外。
    const nei0 = sumEl(elNei) + sumEl(geoBirth);
    const wai0 = sumEl(elWai);
    const nei1 = nei0;
    const wai1 = wai0;
    // ③流层后：家内仍定境；家外叠流层＋现地。
    const nei2 = nei0;
    const wai2 = wai0 + sumEl(flowAcc.el) + sumEl(geoStand);
    const neiBefore = nei0;
    const neiAfter = nei1;
    const waiBefore = wai0;
    const waiAfter = wai1;
    function packStage(id, zh, zi, tot, camp, nei, wai, fw, mw) {
      return {
        id: id, zh: zh,
        zi: { 木: zi.木 || 0, 火: zi.火 || 0, 土: zi.土 || 0, 金: zi.金 || 0, 水: zi.水 || 0 },
        el: { 木: tot.木 || 0, 火: tot.火 || 0, 土: tot.土 || 0, 金: tot.金 || 0, 水: tot.水 || 0 },
        wo: camp.wo, ta: camp.ta,
        nei: nei, wai: wai,
        fireWater: fw, metalWood: mw,
      };
    }
    const stages = {
      beforeWork: packStage("beforeWork", "①作用前", elLingDi, elZheng, camp0, nei0, wai0, fwBirth, mwBirth),
      beforeYun: packStage("beforeYun", "②运前", elShengKeZi, elShengKe, camp1, nei1, wai1, fwBirth, mwBirth),
      afterFlow: packStage("afterFlow", "③流层后", elLiveZi, elLive, camp2, nei2, wai2, fwStand, mwStand),
    };
    const partsBefore = campFromElements(elLingDi, pillars.day[0]);
    const partsAfter = partsBefore;
    const vitality = judgeVitality({
      woBefore: zheng.wo, taBefore: zheng.ta,
      woAfter: zheng.wo, taAfter: zheng.ta,
      partsBefore: partsBefore, partsAfter: partsAfter,
      gongDeLi: false, useGuest: zhengZhuan.useGuest,
      balanced: !!zheng.balanced, dWoGong: dWoGong, dNeiGong: dNeiGong,
    });
    const freeze = {
      diHou: zheng,
      zhuan: zhengZhuan,
      works: works,
      bi: biInfo,
      nei: neiBefore,
      wai: waiBefore,
      neiAfter: neiAfter,
      waiAfter: waiAfter,
      neiDelta: neiAfter - neiBefore,
      waiDelta: waiAfter - waiBefore,
      vitality: vitality,
      partsBefore: partsBefore,
      partsAfter: partsAfter,
      elLingDi: elLingDi,
      elShengKeZi: elShengKeZi,
      elLiveZi: elLiveZi,
      geoBirth: geoBirth,
      geoStand: geoStand,
      neiFlow: nei2,
      waiFlow: wai2,
      stages: stages,
      geoRemedy: geoRemedy,
      woGong: woGong,
      woLive: woLive,
      dWoGong: dWoGong,
      dNeiGong: dNeiGong,
      neiGong: sumEl(elNeiGongZi) + sumEl(geoBirth),
    };
    const sex = opts.sex || opts.gender || (opts.sexNum === 0 ? "F" : "M");
    const xinGong = (global.KRXinGong && global.KRXinGong.analyze)
      ? global.KRXinGong.analyze({
          pillars: pillars,
          rel: live || relNatal,
          extras: extras,
          dayun: opts.dayun || [],
          liveGz: liveGz,
          freeze: freeze,
          sex: sex,
        })
      : null;
    const verdicts = selectVerdicts({
      bi: biInfo.bi,
      biLabel: biInfo.label,
      shares: biInfo.shares,
      orient: zhengZhuan.orient,
      yongShen: zhengZhuan.yongShen,
      jiShen: zhengZhuan.jiShen,
      useGuest: zhengZhuan.useGuest,
      shenQiang: zhengZhuan.strength === "身强",
      workName: core.name,
      workJiXiong: core.jiXiong,
      intensity,
      keys: live ? live.keys : [],
      manifestKeys: live ? live.manifest : [],
      tonggen: live ? live.tonggen : [],
      liveWhen: !!relLive,
      xinGong: xinGong,
    });

    return {
      pillars,
      south, southNote,
      timeInfo,
      dayAhead: natalAdj.dayAhead ? 1 : 0,
      geoAdjustNotes: natalAdj.notes,
      layers: {
        birth: {
          role: "出生地",
          sync: "公历·农历（当地不变）·干支（美洲日+1／南月支冲）·大运·小运",
          fireWater: fwBirth,
          metalWood: mwBirth,
          lat: birthLat, lon: birthLon,
          adjustNotes: natalAdj.notes,
        },
        stand: {
          role: "现地",
          sync: "流年·流月·流日·流时（现地公历农历不变；美洲流日+1；南半球流月支冲）",
          fireWater: fwStand,
          metalWood: mwStand,
          lat: standLat, lon: standLon,
          pillars: flowPillarsAdj,
          timeInfo: flowTimeInfo,
          southNote: flowSouthNote,
          adjustNotes: flowDayNotes.concat(flowSouthNote ? [flowSouthNote] : []),
          energy: flowAcc.el,
          dayAhead: inAmericasDayAhead(standLon) ? 1 : 0,
        },
      },
      geo: {
        birthLat, birthLon, standLat, standLon,
        fireWaterBirth: fwBirth,
        fireWaterStand: fwStand,
        metalWoodBirth: mwBirth,
        metalWoodStand: mwStand,
        metalWood: mwStand, // 兼容旧字段＝现地金木
        americas: inAmericasDayAhead(birthLon),
        beijingMeridian: BEIJING_MERIDIAN,
        canonZh:
          "水火：|纬|/90→水，火=1−水（±90°水=1，赤道水=0）。" +
          "金木：北京120°E为中心，大西洋日界线≈−60°；以东0<木≤1，以西0<金≤1。",
      },
      energy: {
        diHou: elLingDi,
        lingDi: elLingDi,
        zhengCong: elZheng,
        geoBirth: geoBirth,
        geoStand: geoStand,
        geoNatal: natal.el,
        shengKe: elShengKe,
        shengKeZi: elShengKeZi,
        parts: natal.parts,
        flow: flowAcc.el,
        live: elLive,
        liveZi: elLiveZi,
        stages: stages,
      },
      geoRemedy: geoRemedy,
      vitality: vitality,
      zhengDiHou: zheng,
      zhengZhuan,
      bi: biInfo,
      works,
      verdicts,
      intensity,
      keys: live ? live.keys : [],
      natalKeys: rel ? rel.keys : [],
      tonggen: live ? live.tonggen : [],
      manifestKeys: live ? live.manifest : [],
      keyCounts: live ? live.counts : {},
      pipeline: relLive || null,
      xinGong: xinGong,
      paramsVersion: P.version || "2026-08-11",
      flowNote:
        "公历／农历＝当地不变；美洲仅日干支+1（时干五鼠遁）；南半球仅月支对冲；出生绑大小运+水火金木，现地绑流年→时+水火金木",
    };
  }

  global.KREngine = {
    PLACES, TG, DZ, WX, OPP,
    BEIJING_MERIDIAN, ATLANTIC_DATE_LINE, AMERICAS_EAST_BOUND, MANIFEST_TH,
    normLon, inAmericasDayAhead, correctTime, flipMonthBranch,
    nextJiazi, hourBranchIndex, hourStemFromDay, adjustPillarsForGeo,
    geoFireWater, geoMetalWoodFromBeijing, geoMetalWoodRelative,
    geoLonDeltaFromBeijing, accumulateFlow, analyze,
    judgeZhengCong, judgeYongJiFromGong, isBalanced, BALANCE_RATIO,
    computeBI, pairingBand, selectVerdicts, judgeVitality,
    seasonStage, lingFactor, layerLingFactor, layerDiFactor, lingLayerCoef, accumulateNatal,
    natalCharDist, distE, lingApply, diApply, lingFallE, diFallE,
    stemBaseW, branchBaseW,
    flowLayerIndex, flowLayerE,
    geoStackEl, mergeEl, sumEl, scaleElByGong, campFromElements, GEO_STACK,
  };
})(typeof window !== "undefined" ? window : globalThis);
