dsh-metaphysics 0.22.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client.js CHANGED
@@ -746,7 +746,7 @@ function resolveAddress(input) {
746
746
 
747
747
  const DETAIL = /(区|县|镇|乡|村|小区|街道|路|号|巷|弄|组|队)/;
748
748
 
749
- const USER_AGENT = 'dsh-metaphysics/0.22.0 (+https://github.com/xuanfu-web/dsh-metaphysics)';
749
+ const USER_AGENT = 'dsh-metaphysics/0.23.0 (+https://github.com/xuanfu-web/dsh-metaphysics)';
750
750
 
751
751
  async function onlineLookup(text, { timeoutMs = 2500 } = {}) {
752
752
  if (typeof fetch !== 'function') return null;
@@ -2509,6 +2509,270 @@ function qtLine(e) {
2509
2509
  return { QT_BRANCHES, QT_MONTHS, QT_BASE_URL, QT_NOTE, QT, qtOf, qtLine };
2510
2510
  })();
2511
2511
 
2512
+ __mods.classics = (function () {
2513
+
2514
+ const CLASSIC_BOOKS = {
2515
+ ziping: { name: '子平真诠', author: '清·沈孝瞻', base: 'https://www.diancang.xyz/xuanxuewushu/zipingzhenquan/' },
2516
+ ditian: { name: '滴天髓', author: '明·京图(通行本)', base: 'https://www.diancang.xyz/xuanxuewushu/ditiansui/' },
2517
+ };
2518
+
2519
+ const CLASSICS = [
2520
+ {
2521
+ id: 'zp-yongshen',
2522
+ book: 'ziping',
2523
+ chapter: '八、论用神',
2524
+ scene: '用神',
2525
+ tags: [],
2526
+ quote: '八字用神,专求月令,以日干配月令地支,而生克不同,格局分焉。财官印食,此用神之善而顺用之者也;煞伤劫刃,用神之不善而逆用之者也。',
2527
+ plain: '用神从月令里找:财、官、印、食四吉神顺用(生之护之),煞、伤、劫、刃四凶神逆用(制之化之)。',
2528
+ url: 'https://www.diancang.xyz/xuanxuewushu/zipingzhenquan/53628.html',
2529
+ },
2530
+ {
2531
+ id: 'zp-zhenguan',
2532
+ book: 'ziping',
2533
+ chapter: '三十一、论正官',
2534
+ scene: '格局',
2535
+ tags: ['正官'],
2536
+ quote: '以刑冲破害为忌,则以生之护之为喜矣。存其喜而去其忌则贵,而贵之中又有高低者,何也?',
2537
+ plain: '正官格最忌刑冲破害,喜财生官、印护官;去忌存喜才谈得上贵,贵的层次还要看财印是否并透、有无伤官七煞混杂。',
2538
+ url: 'https://www.diancang.xyz/xuanxuewushu/zipingzhenquan/53651.html',
2539
+ },
2540
+ {
2541
+ id: 'zp-cai',
2542
+ book: 'ziping',
2543
+ chapter: '三十三、论财',
2544
+ scene: '格局',
2545
+ tags: ['财'],
2546
+ quote: '财为我克,使用之物也,以能生官,所以为美。为财帛,为妻妾,为才能,为驿马,皆财类也。',
2547
+ plain: '财是我所克、能为我用之物,因能生官而美;它同时代表钱财、配偶、才能、走动,不只「钱」一项。',
2548
+ url: 'https://www.diancang.xyz/xuanxuewushu/zipingzhenquan/53653.html',
2549
+ },
2550
+ {
2551
+ id: 'zp-yin',
2552
+ book: 'ziping',
2553
+ chapter: '三十五、论印绶',
2554
+ scene: '格局',
2555
+ tags: ['印'],
2556
+ quote: '印绶喜其生身,正偏同为美格,故财与印不分偏正,同为一格而论之。',
2557
+ plain: '印能生身,正印偏印都算美格,不分偏正而论;印格的关键在身印强弱与官杀、食伤、财的配合。',
2558
+ url: 'https://www.diancang.xyz/xuanxuewushu/zipingzhenquan/53655.html',
2559
+ },
2560
+ {
2561
+ id: 'zp-shishen',
2562
+ book: 'ziping',
2563
+ chapter: '三十七、论食神',
2564
+ scene: '格局',
2565
+ tags: ['食神'],
2566
+ quote: '食神本属泄气,以其能生正财,所以喜之。故食神生财,美格也。财要有根,不必偏正叠出,如身强食旺而财透,大贵之格。',
2567
+ plain: '食神本是泄气,可贵在能生财;食神生财要财有根,身强、食旺、财透三者齐,格局才大。',
2568
+ url: 'https://www.diancang.xyz/xuanxuewushu/zipingzhenquan/53657.html',
2569
+ },
2570
+ {
2571
+ id: 'zp-pianguan',
2572
+ book: 'ziping',
2573
+ chapter: '三十九、论偏官',
2574
+ scene: '格局',
2575
+ tags: ['七杀', '偏官'],
2576
+ quote: '煞以攻身,似非美物,百大贵之格,多存七煞。盖控制得宜,煞为我用,如大英雄大豪杰,似难驾驭,而处之有方,则惊天动地之功,忽焉而就。',
2577
+ plain: '七煞攻身,看着不美,但大贵之格多有七煞——能控制住就为我所用,像驾驭豪杰,处之有方就能成大事。',
2578
+ url: 'https://www.diancang.xyz/xuanxuewushu/zipingzhenquan/53659.html',
2579
+ },
2580
+ {
2581
+ id: 'zp-shangguan',
2582
+ book: 'ziping',
2583
+ chapter: '四十一、论伤官',
2584
+ scene: '格局',
2585
+ tags: ['伤官'],
2586
+ quote: '伤官虽非吉神,实为秀气,故文人学士,多于伤官格内得之。而夏木见水,冬金见火,则又为秀之尤秀者也。',
2587
+ plain: '伤官不是吉神,却是秀气所在,文人多出伤官格;夏木得水、冬金得火,是秀气里更秀的两种。',
2588
+ url: 'https://www.diancang.xyz/xuanxuewushu/zipingzhenquan/53661.html',
2589
+ },
2590
+ {
2591
+ id: 'zp-yangren',
2592
+ book: 'ziping',
2593
+ chapter: '四十三、论阳刃',
2594
+ scene: '格局',
2595
+ tags: ['劫财', '阳刃'],
2596
+ quote: '阳刃者,劫我正财之神,乃正财之七煞也。禄前一位,惟五阳有之,故为阳刃。不曰劫而曰刃,劫之甚也。刃宜伏制,官煞皆宜,财印相随,尤为贵显。',
2597
+ plain: '阳刃是劫财之极(只有五阳干有),宜用官煞制伏;财印相随而不相碍,格局更显。',
2598
+ url: 'https://www.diancang.xyz/xuanxuewushu/zipingzhenquan/53663.html',
2599
+ },
2600
+ {
2601
+ id: 'zp-lujie',
2602
+ book: 'ziping',
2603
+ chapter: '四十五、论建禄月劫',
2604
+ scene: '格局',
2605
+ tags: ['比肩', '建禄', '月劫'],
2606
+ quote: '故建禄与月劫,可同一格,不必加分,皆以透干支,别取财官煞食为用。',
2607
+ plain: '建禄、月劫同看一格,月令本身不算用神,要另看透出的财官煞食取用。',
2608
+ url: 'https://www.diancang.xyz/xuanxuewushu/zipingzhenquan/53665.html',
2609
+ },
2610
+ {
2611
+ id: 'zp-qihou',
2612
+ book: 'ziping',
2613
+ chapter: '十四、论用神配气候得失',
2614
+ scene: '调候',
2615
+ tags: [],
2616
+ quote: '论命惟以月令用神为主,然亦须配气候而互参之。譬如英雄豪杰,生得其时,自然事半功倍;遭时不顺,虽有奇才,成功不易。',
2617
+ plain: '月令用神为主,但必须配气候参看;同样的格局,生在得时的气候里事半功倍,逆着气候就费劲。',
2618
+ url: 'https://www.diancang.xyz/xuanxuewushu/zipingzhenquan/53634.html',
2619
+ },
2620
+ {
2621
+ id: 'dt-hannuan',
2622
+ book: 'ditian',
2623
+ chapter: '通神论·寒暖',
2624
+ scene: '调候',
2625
+ tags: [],
2626
+ quote: '天道有寒暖,生育万物,人道行之,不可过也。',
2627
+ plain: '天有寒暖才能生养万物,人命里寒暖也不能过——过寒过热都要调。',
2628
+ url: 'https://www.diancang.xyz/xuanxuewushu/ditiansui/153091.html',
2629
+ },
2630
+ {
2631
+ id: 'dt-zaoshi',
2632
+ book: 'ditian',
2633
+ chapter: '通神论·燥湿',
2634
+ scene: '调候',
2635
+ tags: [],
2636
+ quote: '地道有燥湿,生成品汇,人道得之,不可偏也。',
2637
+ plain: '地有燥湿才有品类生成,人命得燥湿之气也不能偏。',
2638
+ url: 'https://www.diancang.xyz/xuanxuewushu/ditiansui/153091.html',
2639
+ },
2640
+ {
2641
+ id: 'dt-shuaiwang',
2642
+ book: 'ditian',
2643
+ chapter: '通神论·衰旺',
2644
+ scene: '调候',
2645
+ tags: [],
2646
+ quote: '能知旺衰之真机,其于三命之奥,思过半矣。',
2647
+ plain: '真正看懂旺衰的机关,命理之奥就过了一半——旺衰不是数个数,要看真机。',
2648
+ url: 'https://www.diancang.xyz/xuanxuewushu/ditiansui/153091.html',
2649
+ },
2650
+ {
2651
+ id: 'dt-zhonghe',
2652
+ book: 'ditian',
2653
+ chapter: '通神论·中和',
2654
+ scene: '调候',
2655
+ tags: [],
2656
+ quote: '既识中和之正理,而于五行之妙,有全能焉。',
2657
+ plain: '懂得中和的道理,五行的妙处就都在其中了。',
2658
+ url: 'https://www.diancang.xyz/xuanxuewushu/ditiansui/153091.html',
2659
+ },
2660
+ {
2661
+ id: 'dt-guansha',
2662
+ book: 'ditian',
2663
+ chapter: '通神论·官杀',
2664
+ scene: '十神',
2665
+ tags: ['正官', '七杀'],
2666
+ quote: '官杀混杂来问我,有可有不可。',
2667
+ plain: '官杀混杂不是一律不好,要看能不能去留取清。',
2668
+ url: 'https://www.diancang.xyz/xuanxuewushu/ditiansui/153091.html',
2669
+ },
2670
+ {
2671
+ id: 'dt-shangguan',
2672
+ book: 'ditian',
2673
+ chapter: '通神论·伤官',
2674
+ scene: '十神',
2675
+ tags: ['伤官'],
2676
+ quote: '伤官见官最难辨,可见不可见。',
2677
+ plain: '伤官见官是最难分辨的一条:有的能见,有的不能见,不能一句「伤官见官为祸百端」了事。',
2678
+ url: 'https://www.diancang.xyz/xuanxuewushu/ditiansui/153091.html',
2679
+ },
2680
+ {
2681
+ id: 'dt-yueling',
2682
+ book: 'ditian',
2683
+ chapter: '通神论·月令',
2684
+ scene: '用神',
2685
+ tags: [],
2686
+ quote: '月令乃提纲之府,譬之宅也,人元为用事之神,宅之定向也,不可以不卜。',
2687
+ plain: '月令像宅子,藏干是用事之神,像宅子的朝向,必须细看。',
2688
+ url: 'https://www.diancang.xyz/xuanxuewushu/ditiansui/153091.html',
2689
+ },
2690
+ {
2691
+ id: 'dt-tiyong',
2692
+ book: 'ditian',
2693
+ chapter: '通神论·体用',
2694
+ scene: '用神',
2695
+ tags: [],
2696
+ quote: '道有体用,不可以一端论也,要在扶之抑之得其宜。',
2697
+ plain: '体用不能只看一头,关键是扶抑得宜。',
2698
+ url: 'https://www.diancang.xyz/xuanxuewushu/ditiansui/153091.html',
2699
+ },
2700
+ {
2701
+ id: 'dt-jingshen',
2702
+ book: 'ditian',
2703
+ chapter: '通神论·精神',
2704
+ scene: '用神',
2705
+ tags: [],
2706
+ quote: '人有精神,不可以一偏求也,要在损之益之得其中。',
2707
+ plain: '看精神不能偏执一端,要在损益之间求其中。',
2708
+ url: 'https://www.diancang.xyz/xuanxuewushu/ditiansui/153091.html',
2709
+ },
2710
+ ];
2711
+
2712
+ const BY_ID = CLASSICS.reduce((m, e) => { m[e.id] = e; return m; }, {});
2713
+
2714
+ const GEJU_KEY = {
2715
+ 正官格: '正官',
2716
+ 七杀格: '七杀',
2717
+ 偏官格: '偏官',
2718
+ 正财格: '财',
2719
+ 偏财格: '财',
2720
+ 正印格: '印',
2721
+ 偏印格: '印',
2722
+ 食神格: '食神',
2723
+ 伤官格: '伤官',
2724
+ 阳刃格: '阳刃',
2725
+ 月劫格: '月劫',
2726
+ 建禄格: '建禄',
2727
+ };
2728
+
2729
+ function classicOf(id) {
2730
+ return BY_ID[id] || null;
2731
+ }
2732
+
2733
+ function tagOf(gejuName) {
2734
+ const name = String(gejuName || '');
2735
+ if (GEJU_KEY[name]) return GEJU_KEY[name];
2736
+ const bare = name.replace(/格$/, '');
2737
+ return GEJU_KEY[bare + '格'] || bare;
2738
+ }
2739
+
2740
+ function classicsFor(r, limit = 4) {
2741
+ if (!r) return [];
2742
+ const out = [];
2743
+ const seen = new Set();
2744
+ const push = (entry, why) => {
2745
+ if (!entry || seen.has(entry.id) || out.length >= limit) return;
2746
+ seen.add(entry.id);
2747
+ out.push({ ...entry, why });
2748
+ };
2749
+ const gejuList = Array.isArray(r.geju) ? r.geju : (r.frame && Array.isArray(r.frame.geju) ? r.frame.geju : []);
2750
+ for (const g of gejuList) {
2751
+ if (!g || (g.kind && g.kind !== '月令格')) continue;
2752
+ const tag = tagOf(g.name);
2753
+ const hit = CLASSICS.find((e) => e.scene === '格局' && e.tags.includes(tag));
2754
+ if (hit) push(hit, `你的月令格局是「${g.name}」,故取${CLASSIC_BOOKS[hit.book].name}论${hit.chapter.replace(/^.*、/, '')}`);
2755
+ }
2756
+ const cli = r.climate || (r.frame && r.frame.yongshen && (r.frame.yongshen.climate || r.frame.yongshen.tiaohou));
2757
+ if (cli && cli.kind && cli.kind !== '平和') {
2758
+ const cold = !!(cli.cold || cli.kind.indexOf('寒') >= 0);
2759
+ const hot = !!(cli.hot || cli.kind.indexOf('热') >= 0);
2760
+ push(classicOf(cold ? 'dt-hannuan' : 'dt-zaoshi'), `本盘气候判为「${cli.kind}」,故取${CLASSIC_BOOKS.ditian.name}寒暖燥湿一条`);
2761
+ push(classicOf('zp-qihou'), '气候与月令用神要互参,故取子平真诠「用神配气候」一条');
2762
+ if (hot) push(classicOf('dt-hannuan'), '本盘偏热,寒暖一条正对');
2763
+ }
2764
+ push(classicOf('zp-yongshen'), '用神从月令取、四吉顺用四凶逆用,是通用总纲');
2765
+ return out;
2766
+ }
2767
+
2768
+ function classicsText(rows) {
2769
+ if (!rows || !rows.length) return '';
2770
+ return rows.map((e) => `《${CLASSIC_BOOKS[e.book].name}·${e.chapter}》「${e.quote}」`).join('\n');
2771
+ }
2772
+
2773
+ return { CLASSIC_BOOKS, CLASSICS, classicOf, classicsFor, classicsText };
2774
+ })();
2775
+
2512
2776
  __mods.geju = (function () {
2513
2777
 
2514
2778
  const { judgeClimate, arbitrate } = __mods.tiaohou;
@@ -2849,7 +3113,7 @@ __mods.bazi = (function () {
2849
3113
  const { STEMS, BRANCHES, TERM_NAMES, ganzhiName, dayGanzhiIndex, jdToParts, partsToJd, sexagenaryIndex, termWindow, previousJie, nextJie, equationOfTime, formatJd, xunKongOf } = __mods.calendar;
2850
3114
  const { resolveAddress } = __mods.geo;
2851
3115
  const { computeBaziShensha } = __mods.shensha;
2852
- const { assertCivil } = __mods.validate;
3116
+ const { assertCivil, daysInMonth } = __mods.validate;
2853
3117
  const { applyDst } = __mods.dst;
2854
3118
  const { nayinOf } = __mods.nayin;
2855
3119
  const { SANHE } = __mods.sanhe;
@@ -3092,6 +3356,16 @@ function buildBazi(input) {
3092
3356
  let startAgeMonths = Math.round(totalMonths % 12);
3093
3357
  if (startAgeMonths >= 12) { startAgeMonths -= 12; startAgeYears += 1; }
3094
3358
 
3359
+ const JIAO_YEAR_DAYS = 365.2422;
3360
+ const JIAO_SCALE = JIAO_YEAR_DAYS / 3;
3361
+ const jiaoFromTerm = forward ? nextJieTerm : prevJieTerm;
3362
+ const jiaoJd = jd + spanDays * JIAO_SCALE;
3363
+ const jiaoParts = jdToParts(jiaoJd);
3364
+ const addYearsClamped = (p, n) => {
3365
+ const y = p.year + n;
3366
+ return { year: y, month: p.month, day: Math.min(p.day, daysInMonth(y, p.month)), hour: p.hour, minute: p.minute };
3367
+ };
3368
+
3095
3369
  const dayun = [];
3096
3370
  for (let i = 1; i <= 8; i++) {
3097
3371
  const idx = (((monthIdx + (forward ? i : -i)) % 60) + 60) % 60;
@@ -3104,6 +3378,8 @@ function buildBazi(input) {
3104
3378
  branch: g.branch,
3105
3379
  stemTenGod: tenGod(dayStem, g.stem),
3106
3380
  nayin: nayin(idx),
3381
+ fromDate: formatJd(partsToJd(addYearsClamped(jiaoParts, (i - 1) * 10))),
3382
+ toDate: formatJd(partsToJd(addYearsClamped(jiaoParts, i * 10))),
3107
3383
  });
3108
3384
  }
3109
3385
 
@@ -3299,6 +3575,17 @@ function buildBazi(input) {
3299
3575
  rule: `${isYangYear ? '阳' : '阴'}年${gender === 'male' ? '男' : '女'}${forward ? '顺排' : '逆排'}`,
3300
3576
  startAge: { years: startAgeYears, months: startAgeMonths },
3301
3577
  toNextJieDays: Math.round(spanDays * 100) / 100,
3578
+ jiao: {
3579
+ at: formatJd(jiaoJd),
3580
+ jd: Math.round(jiaoJd * 1e6) / 1e6,
3581
+ scaleDaysPerDay: JIAO_SCALE,
3582
+ yearDays: JIAO_YEAR_DAYS,
3583
+ spanDays: Math.round(spanDays * 1e4) / 1e4,
3584
+ from: { name: jiaoFromTerm.name, time: formatJd(jiaoFromTerm.jd) },
3585
+ frame: solar.offsetMinutes ? '真太阳时' : '平太阳时',
3586
+ offsetMinutes: solar.offsetMinutes,
3587
+ note: '三日折一年:出生到节令的真实间隔按 365.2422/3 放大后加到出生时刻;同一比例即「一日折四月、一时辰折十日」,每步大运在同月同日同时刻换',
3588
+ },
3302
3589
  list: dayun,
3303
3590
  },
3304
3591
  strength,
@@ -3360,8 +3647,133 @@ const GENERATES = { 木: '火', 火: '土', 土: '金', 金: '水', 水: '木' }
3360
3647
  const OVERCOMES = { 木: '土', 土: '水', 水: '火', 火: '金', 金: '木' };
3361
3648
 
3362
3649
  const LIUSHEN = ['青龙', '朱雀', '勾陈', '螣蛇', '白虎', '玄武'];
3650
+ const LIUQIN_NAMES = ['父母', '兄弟', '子孙', '妻财', '官鬼'];
3363
3651
  const LIUSHEN_START = { 甲: 0, 乙: 0, 丙: 1, 丁: 1, 戊: 2, 己: 3, 庚: 4, 辛: 4, 壬: 5, 癸: 5 };
3364
3652
 
3653
+ const ZHI_CHONG_PAIRS = [['子', '午'], ['丑', '未'], ['寅', '申'], ['卯', '酉'], ['辰', '戌'], ['巳', '亥']];
3654
+ const ZHI_HE_PAIRS = [['子', '丑'], ['寅', '亥'], ['卯', '戌'], ['辰', '酉'], ['巳', '申'], ['午', '未']];
3655
+ const CHONG_OF = Object.fromEntries(ZHI_CHONG_PAIRS.flatMap(([a, b]) => [[a, b], [b, a]]));
3656
+ const TRIGRAM_OPPOSITE = { 乾: '巽', 巽: '乾', 坎: '离', 离: '坎', 艮: '坤', 坤: '艮', 震: '兑', 兑: '震' };
3657
+
3658
+ const JIN_TUI_PAIRS = [['寅', '卯'], ['巳', '午'], ['申', '酉'], ['亥', '子'], ['丑', '辰'], ['辰', '未'], ['未', '戌'], ['戌', '丑']];
3659
+ const JIN_OF = new Map(JIN_TUI_PAIRS);
3660
+ const TUI_OF = new Map(JIN_TUI_PAIRS.map(([a, b]) => [b, a]));
3661
+
3662
+ function jinTuiOf(from, to) {
3663
+ if (!from || !to || from === to) return null;
3664
+ if (JIN_OF.get(from) === to) return '进';
3665
+ if (TUI_OF.get(from) === to) return '退';
3666
+ return null;
3667
+ }
3668
+
3669
+ function pairThirds(branches, table) {
3670
+ const cnt = new Map();
3671
+ for (const b of branches) cnt.set(b, (cnt.get(b) || 0) + 1);
3672
+ const hits = [];
3673
+ for (const [a, b] of table) {
3674
+ const na = cnt.get(a) || 0;
3675
+ const nb = cnt.get(b) || 0;
3676
+ if (!na && !nb) continue;
3677
+ if (na !== 1 || nb !== 1) return null;
3678
+ cnt.set(a, 0);
3679
+ cnt.set(b, 0);
3680
+ hits.push([a, b]);
3681
+ }
3682
+ return hits.length * 2 === branches.length ? hits : null;
3683
+ }
3684
+
3685
+ function hexBranches(hex) {
3686
+ const inner = NAJIA[hex.lower].inner.slice();
3687
+ const outer = NAJIA[hex.upper].outer.slice();
3688
+ return { inner, outer, all: [...inner, ...outer] };
3689
+ }
3690
+
3691
+ function hexBody(hex) {
3692
+ const { inner, outer, all } = hexBranches(hex);
3693
+ const chongPairs = pairThirds(all, ZHI_CHONG_PAIRS);
3694
+ const hePairs = pairThirds(all, ZHI_HE_PAIRS);
3695
+ return {
3696
+ name: hex.name,
3697
+ upper: hex.upper,
3698
+ lower: hex.lower,
3699
+ inner,
3700
+ outer,
3701
+ chong: Boolean(chongPairs),
3702
+ he: Boolean(hePairs),
3703
+ chongPairs: chongPairs || [],
3704
+ hePairs: hePairs || [],
3705
+ };
3706
+ }
3707
+
3708
+ function bodyRelations(ben, bian = null, moving = []) {
3709
+ const b = hexBody(ben);
3710
+ const out = {
3711
+ ben: { name: b.name, chong: b.chong, he: b.he, chongPairs: b.chongPairs, hePairs: b.hePairs, inner: b.inner, outer: b.outer },
3712
+ bian: null,
3713
+ fuYin: { inner: false, outer: false },
3714
+ fanYin: { inner: null, outer: null }, // null 或 { by: ['chong'|'pair'] }
3715
+ yaoFuYin: [],
3716
+ yaoFanYin: [],
3717
+ jinTui: [], // [{ position, from, to, type: '进'|'退' }]
3718
+ };
3719
+ if (!bian) return out;
3720
+
3721
+ const v = hexBody(bian);
3722
+ out.bian = { name: v.name, chong: v.chong, he: v.he, chongPairs: v.chongPairs, hePairs: v.hePairs, inner: v.inner, outer: v.outer };
3723
+
3724
+ for (const side of ['inner', 'outer']) {
3725
+ const x = b[side];
3726
+ const y = v[side];
3727
+ const tri = side === 'inner' ? [ben.lower, bian.lower] : [ben.upper, bian.upper];
3728
+ const changed = tri[0] !== tri[1];
3729
+ if (changed && x.every((z, i) => z === y[i])) {
3730
+ out.fuYin[side] = true;
3731
+ continue;
3732
+ }
3733
+ const by = [];
3734
+ if (changed && x.every((z, i) => CHONG_OF[z] === y[i])) by.push('chong');
3735
+ if (changed && TRIGRAM_OPPOSITE[tri[0]] === tri[1]) by.push('pair');
3736
+ if (by.length) out.fanYin[side] = { by };
3737
+ }
3738
+
3739
+ for (const p of moving) {
3740
+ const i = p - 1;
3741
+ if (!(i >= 0 && i < 6)) continue;
3742
+ const x = i < 3 ? b.inner[i] : b.outer[i - 3];
3743
+ const y = i < 3 ? v.inner[i] : v.outer[i - 3];
3744
+ if (x === y) out.yaoFuYin.push(p);
3745
+ else if (CHONG_OF[x] === y) out.yaoFanYin.push(p);
3746
+ const jt = jinTuiOf(x, y);
3747
+ if (jt) out.jinTui.push({ position: p, from: x, to: y, type: jt });
3748
+ }
3749
+ return out;
3750
+ }
3751
+
3752
+ function bodyLabels(body) {
3753
+ if (!body || !body.ben) return [];
3754
+ const fuYin = body.fuYin || {};
3755
+ const fanYin = body.fanYin || {};
3756
+ const yaoFuYin = body.yaoFuYin || [];
3757
+ const yaoFanYin = body.yaoFanYin || [];
3758
+ const out = [];
3759
+ if (body.ben.chong) out.push('六冲卦');
3760
+ if (body.ben.he) out.push('六合卦');
3761
+ if (body.bian) {
3762
+ if (body.bian.chong) out.push('变六冲');
3763
+ else if (body.bian.he) out.push('变六合');
3764
+ }
3765
+ const sides = [['inner', '内卦'], ['outer', '外卦']];
3766
+ for (const [k, label] of sides) if (fuYin[k]) out.push(`${label}伏吟`);
3767
+ for (const [k, label] of sides) if (fanYin[k]) out.push(`${label}反吟`);
3768
+ if (yaoFuYin.length) out.push(`爻伏吟 ${yaoFuYin.join('、')}`);
3769
+ if (yaoFanYin.length) out.push(`爻反吟 ${yaoFanYin.join('、')}`);
3770
+ const jin = (body.jinTui || []).filter((x) => x.type === '进').map((x) => x.position);
3771
+ const tui = (body.jinTui || []).filter((x) => x.type === '退').map((x) => x.position);
3772
+ if (jin.length) out.push(`化进神 ${jin.join('、')}`);
3773
+ if (tui.length) out.push(`化退神 ${tui.join('、')}`);
3774
+ return out;
3775
+ }
3776
+
3365
3777
  function liuqin(palaceElement, branchElement) {
3366
3778
  if (palaceElement === branchElement) return '兄弟';
3367
3779
  if (GENERATES[branchElement] === palaceElement) return '父母';
@@ -3416,6 +3828,166 @@ function hexagramFromBits(bits) {
3416
3828
  return HEXAGRAM_INDEX.get(`${upper}|${lower}`);
3417
3829
  }
3418
3830
 
3831
+ const PURE_BY_PALACE = new Map();
3832
+ for (const h of HEXAGRAM_INDEX.values()) if (h.order === 0) PURE_BY_PALACE.set(h.palace, h);
3833
+
3834
+ const YONGSHEN_RULES = Object.freeze({
3835
+ wealth: { liuqin: '妻财', ask: '求财、收入、订单' },
3836
+ career: { liuqin: '官鬼', ask: '工作、职位、考核' },
3837
+ health: { liuqin: '官鬼', ask: '病与压力(子孙为医药)' },
3838
+ study: { liuqin: '父母', ask: '考试、证书、文书合同' },
3839
+ love: { liuqin: null, ask: '感情:男看妻财、女看官鬼' },
3840
+ travel: { liuqin: null, ask: '出行以世爻为用,父母为舟车' },
3841
+ general: { liuqin: null, ask: '未指明所问之事' },
3842
+ });
3843
+
3844
+ const WUXING_SAY = {
3845
+ 飞生伏: '飞神生伏神,伏神借得上力',
3846
+ 伏生飞: '伏神生飞神,力气往外泄',
3847
+ 飞克伏: '飞神克伏神,伏神受压难出',
3848
+ 伏克飞: '伏神克飞神,伏神能自出',
3849
+ 比和: '飞伏比和,同气相求',
3850
+ };
3851
+
3852
+ const RELATION_LABEL = {
3853
+ 飞生伏: '伏神得飞神生',
3854
+ 伏生飞: '伏神泄气于飞神',
3855
+ 飞克伏: '伏神受飞神克',
3856
+ 伏克飞: '伏神克飞神可出',
3857
+ 比和: '飞伏比和',
3858
+ };
3859
+
3860
+ function wuxingBetween(min, max) {
3861
+ if (GENERATES[max] === min) return '飞生伏';
3862
+ if (GENERATES[min] === max) return '伏生飞';
3863
+ if (OVERCOMES[max] === min) return '飞克伏';
3864
+ if (OVERCOMES[min] === max) return '伏克飞';
3865
+ return '比和';
3866
+ }
3867
+
3868
+ const sideOf = (position) => (position <= 3 ? 'inner' : 'outer');
3869
+
3870
+ function yongshenOf(result, opts = {}) {
3871
+ const r = result && typeof result === 'object' ? result : {};
3872
+ const ben = r.ben && typeof r.ben === 'object' ? r.ben : {};
3873
+ const lines = Array.isArray(ben.lines) ? ben.lines : [];
3874
+ const direction = YONGSHEN_RULES[opts.direction] ? opts.direction : 'general';
3875
+ const rule = YONGSHEN_RULES[direction];
3876
+ const gender = opts.gender === 'female' ? 'female' : 'male';
3877
+ const body = r.body && typeof r.body === 'object' ? r.body : {};
3878
+ const kongArr = Array.isArray(r.xunKong)
3879
+ ? r.xunKong
3880
+ : (typeof r.xunKong === 'string' ? r.xunKong.split('') : []);
3881
+
3882
+ const direct = LIUQIN_NAMES.includes(opts.liuqin) ? opts.liuqin : null;
3883
+ const byShi = !direct && direction === 'travel';
3884
+ const liuqinName = direct || (byShi ? '世' : (direction === 'love' ? (gender === 'female' ? '官鬼' : '妻财') : rule.liuqin));
3885
+ const base = {
3886
+ direction,
3887
+ gender,
3888
+ ask: rule.ask,
3889
+ byShi,
3890
+ liuqin: liuqinName,
3891
+ found: [],
3892
+ missing: false,
3893
+ fushen: [],
3894
+ labels: [],
3895
+ };
3896
+ if (!liuqinName) { base.labels = ['用神未取(未指明所问之事)']; return base; }
3897
+
3898
+ const found = byShi
3899
+ ? lines.filter((l) => l.position === ben.shi)
3900
+ : lines.filter((l) => l.liuqin === liuqinName);
3901
+ base.found = found.map((l) => ({
3902
+ position: l.position,
3903
+ name: l.name,
3904
+ liuqin: l.liuqin,
3905
+ branch: l.branch,
3906
+ element: l.element,
3907
+ ganzhi: l.ganzhi,
3908
+ moving: Boolean(l.moving),
3909
+ liushen: l.liushen,
3910
+ shi: l.position === ben.shi,
3911
+ ying: l.position === ben.ying,
3912
+ kong: kongArr.includes(l.branch),
3913
+ }));
3914
+
3915
+ const labels = [];
3916
+ const label = byShi ? '用神 世爻' : `用神 ${liuqinName}`;
3917
+ labels.push(label);
3918
+ const jin = (body.jinTui || []).filter((x) => x.type === '进').map((x) => x.position);
3919
+ const tui = (body.jinTui || []).filter((x) => x.type === '退').map((x) => x.position);
3920
+ const yaoFu = body.yaoFuYin || [];
3921
+ const yaoFan = body.yaoFanYin || [];
3922
+
3923
+ if (found.length) {
3924
+ labels.push(`${label}在 ${found.map((l) => `${l.position}爻`).join('、')}`);
3925
+ if (found.length > 1) labels.push(`用神两现 ${found.map((l) => l.position).join('、')}`);
3926
+ if (found.some((l) => l.position === ben.shi)) labels.push('用神持世');
3927
+ if (found.some((l) => l.position === ben.ying)) labels.push('用神临应');
3928
+ if (found.some((l) => l.moving)) labels.push('用神发动');
3929
+ if (found.some((l) => kongArr.includes(l.branch))) labels.push('用神空亡');
3930
+ if (found.some((l) => jin.includes(l.position))) labels.push('用神化进神');
3931
+ if (found.some((l) => tui.includes(l.position))) labels.push('用神化退神');
3932
+ if (found.some((l) => yaoFu.includes(l.position))) labels.push('用神爻伏吟');
3933
+ if (found.some((l) => yaoFan.includes(l.position))) labels.push('用神爻反吟');
3934
+ } else {
3935
+ base.missing = true;
3936
+ const palace = String(ben.palace || '').replace(/宫$/, '');
3937
+ const pure = PURE_BY_PALACE.get(palace) || null;
3938
+ if (pure) {
3939
+ for (let i = 0; i < 6; i++) {
3940
+ const isInner = i < 3;
3941
+ const nj = NAJIA[isInner ? pure.lower : pure.upper];
3942
+ const branch = isInner ? nj.inner[i] : nj.outer[i - 3];
3943
+ const gan = isInner ? nj.gan[0] : nj.gan[1];
3944
+ const element = BRANCH_ELEMENT[branch];
3945
+ if (liuqin(pure.palaceElement, element) !== liuqinName) continue;
3946
+ const fei = lines[i] || null;
3947
+ const relation = fei ? wuxingBetween(element, fei.element) : null;
3948
+ base.fushen.push({
3949
+ position: i + 1,
3950
+ liuqin: liuqinName,
3951
+ branch,
3952
+ element,
3953
+ ganzhi: gan + branch,
3954
+ relation,
3955
+ fei: fei ? { position: fei.position, liuqin: fei.liuqin, branch: fei.branch, element: fei.element, ganzhi: fei.ganzhi } : null,
3956
+ });
3957
+ }
3958
+ }
3959
+ if (base.fushen.length) {
3960
+ labels.push(`用神不上卦·伏于 ${base.fushen.map((f) => `${f.position}爻`).join('、')}`);
3961
+ if (base.fushen.length > 1) labels.push(`伏神两现 ${base.fushen.map((f) => f.position).join('、')}`);
3962
+ const rels = [...new Set(base.fushen.map((f) => f.relation).filter(Boolean))];
3963
+ for (const rel of rels) labels.push(RELATION_LABEL[rel] || `伏神${rel}`);
3964
+ } else {
3965
+ labels.push('用神不上卦');
3966
+ }
3967
+ }
3968
+
3969
+ const sides = found.length ? [...new Set(found.map((l) => sideOf(l.position)))] : [];
3970
+ if (body.ben) {
3971
+ if (body.ben.chong) labels.push('用神在六冲卦');
3972
+ if (body.ben.he) labels.push('用神在六合卦');
3973
+ if (body.bian && body.bian.chong) labels.push('用神逢变六冲');
3974
+ if (body.bian && body.bian.he) labels.push('用神逢变六合');
3975
+ }
3976
+ for (const side of sides) {
3977
+ if (body.fuYin && body.fuYin[side]) labels.push(`用神在${side === 'inner' ? '内' : '外'}卦伏吟`);
3978
+ if (body.fanYin && body.fanYin[side]) {
3979
+ const by = (body.fanYin[side].by || []).includes('chong') ? '地支全冲' : '八卦正对';
3980
+ labels.push(`用神在${side === 'inner' ? '内' : '外'}卦反吟`);
3981
+ labels.push(`反吟来源:${by}`);
3982
+ }
3983
+ }
3984
+
3985
+ base.labels = labels;
3986
+ base.relationSay = base.fushen.map((f) => WUXING_SAY[f.relation]).filter(Boolean);
3987
+ base.side = sides.length === 1 ? (sides[0] === 'inner' ? '内卦' : '外卦') : null;
3988
+ return base;
3989
+ }
3990
+
3419
3991
  function tossLines(random = Math.random) {
3420
3992
  const lines = [];
3421
3993
  for (let i = 0; i < 6; i++) {
@@ -3531,10 +4103,124 @@ function buildLiuyao(input = {}) {
3531
4103
  lines: bianRows,
3532
4104
  }
3533
4105
  : null,
4106
+ body: (() => {
4107
+ const body = bodyRelations(ben, bian, moving.map((i) => i + 1));
4108
+ body.labels = bodyLabels(body);
4109
+ return body;
4110
+ })(),
3534
4111
  };
3535
4112
  }
3536
4113
 
3537
- return { HEXAGRAM_COUNT, hexagramFromBits, tossLines, buildLiuyao };
4114
+ const ZHI_ELEMENT = {
4115
+ 子: '水', 丑: '土', 寅: '木', 卯: '木', 辰: '土', 巳: '火',
4116
+ 午: '火', 未: '土', 申: '金', 酉: '金', 戌: '土', 亥: '水',
4117
+ };
4118
+ const ELEMENT_SHENG = { 木: '火', 火: '土', 土: '金', 金: '水', 水: '木' };
4119
+ const ELEMENT_KE = { 木: '土', 土: '水', 水: '火', 火: '金', 金: '木' };
4120
+ const MONTH_STATE_ORDER = ['旺', '相', '休', '囚', '死'];
4121
+ const MONTH_STATE_SAY = {
4122
+ 旺: '与月令同气,当令得时',
4123
+ 相: '受月令之生,气有余',
4124
+ 休: '生月令而气泄,力减',
4125
+ 囚: '克月令而力困',
4126
+ 死: '被月令所克,气受制',
4127
+ };
4128
+ const DAY_RELATION_ORDER = ['日生', '日克', '比和', '泄', '耗'];
4129
+ const DAY_RELATION_SAY = {
4130
+ 日生: '日辰生扶,当天有人托着',
4131
+ 日克: '日辰克制,当天被压一头',
4132
+ 比和: '与日辰同气,当天得到同类之助',
4133
+ 泄: '生扶日辰,气往外走',
4134
+ 耗: '克制日辰,力有所耗',
4135
+ };
4136
+
4137
+ function zhiElementOf(branch) {
4138
+ return ZHI_ELEMENT[branch] || null;
4139
+ }
4140
+
4141
+ function monthStateOf(element, monthElement) {
4142
+ if (!element || !monthElement) return 'unknown';
4143
+ if (element === monthElement) return '旺';
4144
+ if (ELEMENT_SHENG[monthElement] === element) return '相';
4145
+ if (ELEMENT_SHENG[element] === monthElement) return '休';
4146
+ if (ELEMENT_KE[element] === monthElement) return '囚';
4147
+ if (ELEMENT_KE[monthElement] === element) return '死';
4148
+ return 'unknown';
4149
+ }
4150
+
4151
+ function dayRelationOf(element, dayElement) {
4152
+ if (!element || !dayElement) return 'unknown';
4153
+ if (element === dayElement) return '比和';
4154
+ if (ELEMENT_SHENG[dayElement] === element) return '日生';
4155
+ if (ELEMENT_KE[dayElement] === element) return '日克';
4156
+ if (ELEMENT_SHENG[element] === dayElement) return '泄';
4157
+ if (ELEMENT_KE[element] === dayElement) return '耗';
4158
+ return 'unknown';
4159
+ }
4160
+
4161
+ function yongshenStrength(result, useGod, opts = {}) {
4162
+ if (!useGod || !Array.isArray(useGod.found) || !useGod.found.length) return null;
4163
+ const monthBranch = opts.monthBranch || null;
4164
+ const dayBranch = opts.dayBranch || (result && result.dayGanZhi ? result.dayGanZhi.branch : null);
4165
+ if (!monthBranch && !dayBranch) return null;
4166
+ const monthElement = monthBranch ? zhiElementOf(monthBranch) : null;
4167
+ const dayElement = dayBranch ? zhiElementOf(dayBranch) : null;
4168
+ const kong = Array.isArray(result && result.xunKong)
4169
+ ? result.xunKong
4170
+ : (result && typeof result.xunKong === 'string' ? result.xunKong.split('') : []);
4171
+
4172
+ const lines = useGod.found.map((l) => {
4173
+ const element = l.element || zhiElementOf(l.branch);
4174
+ const monthChong = !!(monthBranch && CHONG_OF[l.branch] === monthBranch);
4175
+ const dayChong = !!(dayBranch && CHONG_OF[l.branch] === dayBranch);
4176
+ const monthState = monthStateOf(element, monthElement);
4177
+ const dayRelation = dayRelationOf(element, dayElement);
4178
+ const isEmpty = kong.includes(l.branch);
4179
+ return {
4180
+ position: l.position,
4181
+ branch: l.branch,
4182
+ element,
4183
+ monthState,
4184
+ monthSay: MONTH_STATE_SAY[monthState] || '',
4185
+ monthChong,
4186
+ dayRelation,
4187
+ daySay: DAY_RELATION_SAY[dayRelation] || '',
4188
+ dayChong,
4189
+ kong: isEmpty,
4190
+ moving: !!l.moving,
4191
+ chongTag: monthChong ? '月破'
4192
+ : dayChong ? (isEmpty ? '冲空' : (l.moving ? '逢日辰冲' : '暗动')) : '',
4193
+ };
4194
+ });
4195
+
4196
+ const labels = [];
4197
+ const states = [...new Set(lines.map((l) => l.monthState))].filter((x) => x !== 'unknown');
4198
+ states.sort((a, b) => MONTH_STATE_ORDER.indexOf(a) - MONTH_STATE_ORDER.indexOf(b));
4199
+ for (const st of states) labels.push(`用神月建${st}`);
4200
+ if (lines.some((l) => l.monthChong)) labels.push('用神月破');
4201
+ const rels = [...new Set(lines.map((l) => l.dayRelation))].filter((x) => x !== 'unknown');
4202
+ rels.sort((a, b) => DAY_RELATION_ORDER.indexOf(a) - DAY_RELATION_ORDER.indexOf(b));
4203
+ for (const rel of rels) {
4204
+ labels.push(rel === '日生' ? '用神得日辰生'
4205
+ : rel === '日克' ? '用神受日辰克'
4206
+ : rel === '比和' ? '用神与日辰比和'
4207
+ : rel === '泄' ? '用神泄于日辰' : '用神耗日辰');
4208
+ }
4209
+ for (const tag of [...new Set(lines.map((l) => l.chongTag))].filter(Boolean)) {
4210
+ labels.push(tag === '月破' ? '' : `用神${tag}`);
4211
+ }
4212
+ const clean = labels.filter(Boolean).filter((x, i, arr) => arr.indexOf(x) === i);
4213
+
4214
+ return {
4215
+ month: monthBranch ? { branch: monthBranch, element: monthElement } : null,
4216
+ day: dayBranch ? { branch: dayBranch, element: dayElement } : null,
4217
+ lines,
4218
+ labels: clean,
4219
+ note: '月建主这一段的旺衰,日辰看当天是扶是克;两处都只给状态标签,不折成分数,也不单独断吉凶。',
4220
+ };
4221
+ }
4222
+
4223
+ return { jinTuiOf, hexBody, bodyRelations, bodyLabels, HEXAGRAM_COUNT, hexagramFromBits, YONGSHEN_RULES, yongshenOf, tossLines, buildLiuyao, zhiElementOf, monthStateOf, dayRelationOf, yongshenStrength };
3538
4224
  })();
3539
4225
 
3540
4226
  __mods.ziwei = (function () {
@@ -4017,6 +4703,8 @@ function buildZiwei(input) {
4017
4703
 
4018
4704
  chart.patterns = detectPatterns(chart);
4019
4705
  chart.daxianList = daxianList(chart);
4706
+ chart.fly = flyStars(chart);
4707
+ chart.flyChains = flyChains(chart, { from: '命宫' });
4020
4708
  enrichLiunian(chart);
4021
4709
  chart.flow = flowOf(chart, input.asOf);
4022
4710
  chart.patternSummary = {
@@ -4196,6 +4884,71 @@ function daxianList(chart) {
4196
4884
  .sort((a, b) => a.ages[0] - b.ages[0]);
4197
4885
  }
4198
4886
 
4887
+ const FLY_TYPES = ['禄', '权', '科', '忌'];
4888
+
4889
+ function flyStarOf(stem, type) {
4890
+ const map = MUTAGEN[stem];
4891
+ if (!map) return null;
4892
+ for (const [star, t] of Object.entries(map)) if (t === type) return star;
4893
+ return null;
4894
+ }
4895
+
4896
+ function flyStars(chart) {
4897
+ const palaces = chart.palaces || [];
4898
+ return palaces.map((p) => ({
4899
+ palace: p.palace,
4900
+ branch: p.branch,
4901
+ stem: p.stem,
4902
+ flies: FLY_TYPES.map((type) => {
4903
+ const star = flyStarOf(p.stem, type);
4904
+ const b = star ? starBranchOf(chart, star) : -1;
4905
+ const target = b >= 0 ? palaces.find((x) => x.branch === BRANCHES[b]) : null;
4906
+ return {
4907
+ type,
4908
+ star,
4909
+ to: target ? target.palace : null,
4910
+ toBranch: target ? target.branch : null,
4911
+ self: !!(target && target.palace === p.palace),
4912
+ };
4913
+ }),
4914
+ }));
4915
+ }
4916
+
4917
+ function flyChain(chart, { from = '命宫', type = '忌', maxSteps = 12 } = {}) {
4918
+ const palaces = chart.palaces || [];
4919
+ const start = palaces.find((p) => p.palace === from) || palaces[0];
4920
+ if (!start) return null;
4921
+ const steps = [];
4922
+ const seen = new Set([start.palace]);
4923
+ let cur = start;
4924
+ let ended = 'max';
4925
+ for (let i = 0; i < Math.max(1, maxSteps); i++) {
4926
+ const star = flyStarOf(cur.stem, type);
4927
+ if (!star) { ended = 'no-star'; break; }
4928
+ const b = starBranchOf(chart, star);
4929
+ if (b < 0) { ended = 'off-chart'; break; }
4930
+ const to = palaces.find((x) => x.branch === BRANCHES[b]);
4931
+ steps.push({ from: cur.palace, fromBranch: cur.branch, fromStem: cur.stem, star, type, to: to.palace, toBranch: to.branch });
4932
+ if (seen.has(to.palace)) { ended = 'cycle'; break; }
4933
+ seen.add(to.palace);
4934
+ cur = to;
4935
+ }
4936
+ return {
4937
+ from: start.palace,
4938
+ type,
4939
+ steps,
4940
+ path: [start.palace, ...steps.map((s) => s.to)],
4941
+ ended,
4942
+ closed: ended === 'cycle',
4943
+ };
4944
+ }
4945
+
4946
+ function flyChains(chart, { from = '命宫', maxSteps = 12 } = {}) {
4947
+ const out = {};
4948
+ for (const type of FLY_TYPES) out[type] = flyChain(chart, { from, type, maxSteps });
4949
+ return out;
4950
+ }
4951
+
4199
4952
  function enrichLiunian(chart) {
4200
4953
  for (const ln of chart.liunian) {
4201
4954
  const stem = ln.ganzhi[0];
@@ -4298,7 +5051,7 @@ function flowOf(chart, asOf) {
4298
5051
  };
4299
5052
  }
4300
5053
 
4301
- return { PALACE_NAMES, BRIGHTNESS, MUTAGEN, LUCUN, KUI_YUE, MING_ZHU, SHEN_ZHU, triadOf, HUO_START, LING_START, TIANMA, XIANCHI, HUAGAI, JIANGXING, GUCHEN, GUASU, posuIndex, XIAOXIAN_START, CHANGSHENG_NAMES, BOSHI_NAMES, JIANGQIAN_NAMES, SUIQI_NAMES, CHANGSHENG_START, hourBranchIndex, ziweiTianfuIndex, xiaoxianAge, buildZiwei, detectPatterns, daxianList, enrichLiunian, asOfParts, flowOf };
5054
+ return { PALACE_NAMES, BRIGHTNESS, MUTAGEN, LUCUN, KUI_YUE, MING_ZHU, SHEN_ZHU, triadOf, HUO_START, LING_START, TIANMA, XIANCHI, HUAGAI, JIANGXING, GUCHEN, GUASU, posuIndex, XIAOXIAN_START, CHANGSHENG_NAMES, BOSHI_NAMES, JIANGQIAN_NAMES, SUIQI_NAMES, CHANGSHENG_START, hourBranchIndex, ziweiTianfuIndex, xiaoxianAge, buildZiwei, detectPatterns, daxianList, FLY_TYPES, flyStarOf, flyStars, flyChain, flyChains, enrichLiunian, asOfParts, flowOf };
4302
5055
  })();
4303
5056
 
4304
5057
  __mods.tarot = (function () {
@@ -6494,6 +7247,90 @@ function gatePalaceRelation(gate, palace) {
6494
7247
  if (SHENG[pe] === ge) return { type: '宫生门', gate: ge, palace: pe, note: `${pe}生${ge}` };
6495
7248
  return { type: '比和', gate: ge, palace: pe, note: `${ge}与${pe}比和` };
6496
7249
  }
7250
+
7251
+ const STEM_ELEMENT = {
7252
+ 甲: '木', 乙: '木', 丙: '火', 丁: '火', 戊: '土',
7253
+ 己: '土', 庚: '金', 辛: '金', 壬: '水', 癸: '水',
7254
+ };
7255
+
7256
+ function stemPalaceRelation(stem, palace) {
7257
+ const se = STEM_ELEMENT[stem];
7258
+ const pe = PALACE_ELEMENT[palace];
7259
+ if (!se || !pe) return { type: 'unknown', stem: se || null, palace: pe || null, note: '缺少干或宫的五行,无法判断' };
7260
+ if (KE[se] === pe) return { type: '干克宫', stem: se, palace: pe, note: `${se}克${pe},天盘干克宫` };
7261
+ if (KE[pe] === se) return { type: '宫克干', stem: se, palace: pe, note: `${pe}克${se},宫克天盘干` };
7262
+ if (SHENG[se] === pe) return { type: '干生宫', stem: se, palace: pe, note: `${se}生${pe},干泄气于宫` };
7263
+ if (SHENG[pe] === se) return { type: '宫生干', stem: se, palace: pe, note: `${pe}生${se},宫来生干` };
7264
+ return { type: '比和', stem: se, palace: pe, note: `${se}与${pe}比和` };
7265
+ }
7266
+
7267
+ const JI_XING = {
7268
+ 戊: { palace: '3', zhi: '卯', basis: '甲子戊,子刑卯' },
7269
+ 己: { palace: '2', zhi: '未', basis: '甲戌己,戌刑未' },
7270
+ 庚: { palace: '8', zhi: '寅', basis: '甲申庚,申刑寅' },
7271
+ 辛: { palace: '9', zhi: '午', basis: '甲午辛,午自刑' },
7272
+ 壬: { palace: '4', zhi: '辰', basis: '甲辰壬,辰自刑' },
7273
+ 癸: { palace: '4', zhi: '巳', basis: '甲寅癸,寅刑巳' },
7274
+ };
7275
+ const RU_MU = {
7276
+ 乙: { palace: '2', zhi: '未', basis: '乙木墓于未,未在坤二宫' },
7277
+ 丙: { palace: '6', zhi: '戌', basis: '丙火墓于戌,戌在乾六宫' },
7278
+ 丁: { palace: '6', zhi: '戌', basis: '丁火墓于戌,戌在乾六宫' },
7279
+ };
7280
+
7281
+ function jiXingOf(palaces) {
7282
+ const out = [];
7283
+ for (const p of palaces || []) {
7284
+ const hit = JI_XING[p.tianPan];
7285
+ if (hit && hit.palace === String(p.palace)) out.push({ palace: p.palace, stem: p.tianPan, zhi: hit.zhi, basis: hit.basis });
7286
+ }
7287
+ return out;
7288
+ }
7289
+
7290
+ function ruMuOf(palaces) {
7291
+ const out = [];
7292
+ for (const p of palaces || []) {
7293
+ const hit = RU_MU[p.tianPan];
7294
+ if (hit && hit.palace === String(p.palace)) out.push({ palace: p.palace, stem: p.tianPan, zhi: hit.zhi, basis: hit.basis });
7295
+ }
7296
+ return out;
7297
+ }
7298
+
7299
+ const QIMEN_CONDITION_TABLES = Object.freeze({
7300
+ jiXing: Object.freeze(Object.fromEntries(Object.entries(JI_XING).map(([k, v]) => [k, { ...v }]))),
7301
+ ruMu: Object.freeze(Object.fromEntries(Object.entries(RU_MU).map(([k, v]) => [k, { ...v }]))),
7302
+ });
7303
+
7304
+ const BRANCH_ELEMENT = {
7305
+ 子: '水', 丑: '土', 寅: '木', 卯: '木', 辰: '土', 巳: '火',
7306
+ 午: '火', 未: '土', 申: '金', 酉: '金', 戌: '土', 亥: '水',
7307
+ };
7308
+ function monthState(element, monthElement) {
7309
+ if (!element || !monthElement) return 'unknown';
7310
+ if (element === monthElement) return '旺';
7311
+ if (SHENG[monthElement] === element) return '相';
7312
+ if (SHENG[element] === monthElement) return '休';
7313
+ if (KE[element] === monthElement) return '囚';
7314
+ if (KE[monthElement] === element) return '死';
7315
+ return 'unknown';
7316
+ }
7317
+ function monthElementOf(branch) {
7318
+ return BRANCH_ELEMENT[branch] || null;
7319
+ }
7320
+ function groupByState(items, monthElement) {
7321
+ const out = { 旺: [], 相: [], 休: [], 囚: [], 死: [] };
7322
+ for (const it of items || []) {
7323
+ const st = monthState(it.element, monthElement);
7324
+ if (out[st]) out[st].push(it.name);
7325
+ }
7326
+ return out;
7327
+ }
7328
+ const QIMEN_STATE_TABLES = Object.freeze({
7329
+ gate: Object.freeze({ ...GATE_ELEMENT }),
7330
+ star: Object.freeze({ ...STAR_ELEMENT }),
7331
+ branch: Object.freeze({ ...BRANCH_ELEMENT }),
7332
+ stem: Object.freeze({ ...STEM_ELEMENT }),
7333
+ });
6497
7334
  function getDiPan(type, num) {
6498
7335
  const result = {};
6499
7336
  let gong = num;
@@ -6666,6 +7503,9 @@ function buildQimen(input) {
6666
7503
  const maZhi = maMap[hourGz[1]] || '';
6667
7504
  const maGong = ZHI_TO_GONG[maZhi] || '';
6668
7505
 
7506
+ const monthBranch = (snapshot.monthGanZhi || '')[1] || '';
7507
+ const monthElement = BRANCH_ELEMENT[monthBranch] || null;
7508
+
6669
7509
  const centerPalace = {
6670
7510
  palace: '5',
6671
7511
  diPan: diPan['5'] || '',
@@ -6677,7 +7517,11 @@ function buildQimen(input) {
6677
7517
  palaceElement: PALACE_ELEMENT['5'],
6678
7518
  gateElement: null,
6679
7519
  starElement: STAR_ELEMENT['天禽'],
7520
+ stemElement: STEM_ELEMENT[tianPan['5']] || null,
6680
7521
  gateRelation: { type: 'unknown', note: '中宫无门,不参与门宫关系' },
7522
+ stemRelation: stemPalaceRelation(tianPan['5'], '5'),
7523
+ starState: monthState(STAR_ELEMENT['天禽'], monthElement),
7524
+ stemState: monthState(STEM_ELEMENT[tianPan['5']], monthElement),
6681
7525
  kong: false,
6682
7526
  ma: false,
6683
7527
  center: true,
@@ -6694,7 +7538,12 @@ function buildQimen(input) {
6694
7538
  palaceElement: PALACE_ELEMENT[gong],
6695
7539
  gateElement: GATE_ELEMENT[bm.baMen[gong]] || null,
6696
7540
  starElement: STAR_ELEMENT[jx.jiuXing[gong]] || null,
7541
+ stemElement: STEM_ELEMENT[tianPan[gong]] || null,
6697
7542
  gateRelation: gatePalaceRelation(bm.baMen[gong], gong),
7543
+ stemRelation: stemPalaceRelation(tianPan[gong], gong),
7544
+ gateState: monthState(GATE_ELEMENT[bm.baMen[gong]], monthElement),
7545
+ starState: monthState(STAR_ELEMENT[jx.jiuXing[gong]], monthElement),
7546
+ stemState: monthState(STEM_ELEMENT[tianPan[gong]], monthElement),
6698
7547
  kong: kongGong.includes(gong),
6699
7548
  ma: maGong === gong,
6700
7549
  }));
@@ -6722,6 +7571,16 @@ function buildQimen(input) {
6722
7571
  zhiShi: { gate: bm.zhiShiMen, gong: bm.zhiShiGong },
6723
7572
  kongWang: kongZhi,
6724
7573
  ma: { zhi: maZhi, gong: maGong },
7574
+ conditions: {
7575
+ jiXing: jiXingOf(palaces),
7576
+ ruMu: ruMuOf(palaces),
7577
+ },
7578
+ month: { branch: monthBranch, element: monthElement },
7579
+ states: {
7580
+ gate: groupByState(palaces.filter((p) => p.gate).map((p) => ({ name: p.gate, element: p.gateElement })), monthElement),
7581
+ star: groupByState(palaces.map((p) => ({ name: p.star, element: p.starElement })), monthElement),
7582
+ stem: groupByState(palaces.filter((p) => p.tianPan).map((p) => ({ name: p.tianPan, element: p.stemElement })), monthElement),
7583
+ },
6725
7584
  palaces,
6726
7585
  centerPalace,
6727
7586
  rules: [
@@ -6733,7 +7592,7 @@ function buildQimen(input) {
6733
7592
  };
6734
7593
  }
6735
7594
 
6736
- return { buildSnapshot, wuxingRelation, gatePalaceRelation, buildQimen };
7595
+ return { buildSnapshot, wuxingRelation, gatePalaceRelation, stemPalaceRelation, jiXingOf, ruMuOf, QIMEN_CONDITION_TABLES, monthState, monthElementOf, groupByState, QIMEN_STATE_TABLES, buildQimen };
6737
7596
  })();
6738
7597
 
6739
7598
  __mods['qimen-interpret'] = (function () {
@@ -6769,6 +7628,18 @@ const YONG_SHEN = {
6769
7628
  travel: { label: '出行', primary: ['九天'], secondary: ['驿马'], note: '出行以九天为主,兼看驿马' },
6770
7629
  };
6771
7630
 
7631
+ function stemRelationLine(r) {
7632
+ const groups = new Map();
7633
+ for (const p of r.palaces || []) {
7634
+ const rel = p.stemRelation || null;
7635
+ if (!rel || rel.type === 'unknown' || !p.tianPan) continue;
7636
+ if (!groups.has(rel.type)) groups.set(rel.type, []);
7637
+ groups.get(rel.type).push(`${p.palace}宫${p.tianPan}`);
7638
+ }
7639
+ const order = ['干克宫', '宫克干', '干生宫', '宫生干', '比和'];
7640
+ return order.filter((k) => groups.has(k)).map((k) => `${k} ${groups.get(k).join('、')}`).join(';') || '—';
7641
+ }
7642
+
6772
7643
  const REL_TEXT = {
6773
7644
  生: '主体力量流向环境或趋势',
6774
7645
  克: '主体对环境或趋势构成压力、有改造意图',
@@ -6859,15 +7730,36 @@ function interpretQimen(r, opts = {}) {
6859
7730
  else if (rel.type === '宫制门') controlled.push(`${p.palace}宫(宫${rel.palace}克${p.gate}${rel.gate})`);
6860
7731
  }
6861
7732
  const kongCells = r.palaces.filter((p) => p.kong).map((p) => `${p.palace}宫`);
7733
+ const jiXing = (r.conditions && r.conditions.jiXing) || [];
7734
+ const ruMu = (r.conditions && r.conditions.ruMu) || [];
6862
7735
  sections.push({
6863
7736
  key: 'conditions',
6864
7737
  label: '宫位条件',
6865
7738
  text: `门迫(同宫内门克宫):${forced.length ? forced.join('、') : '本盘无'};`
6866
7739
  + `宫制门(宫克门,属「制」不为迫):${controlled.length ? controlled.join('、') : '无'}。`
7740
+ + `击刑(天盘六仪落刑宫):${jiXing.length ? jiXing.map((x) => `${x.palace}宫${x.stem}(${x.basis})`).join('、') : '本盘无'};`
7741
+ + `入墓(天盘三奇落墓宫):${ruMu.length ? ruMu.map((x) => `${x.palace}宫${x.stem}(${x.basis})`).join('、') : '本盘无'}。`
6867
7742
  + (kongCells.length ? `空亡:${kongCells.join('、')}——落空之宫事多不实。` : '无落空之宫。')
6868
- + `门迫只作宫位条件,不自动等同于用神受制。`,
7743
+ + `这三项都只作宫位条件:门迫、击刑、入墓不自动等同于用神受制,取象时看它们落在哪个符号上。`,
6869
7744
  });
6870
7745
 
7746
+ const mo = r.month || null;
7747
+ const st = r.states || null;
7748
+ if (mo && st) {
7749
+ const order = ['旺', '相', '休', '囚', '死'];
7750
+ const line = (g) => order.filter((k) => g && Array.isArray(g[k]) && g[k].length)
7751
+ .map((k) => `${k} ${g[k].join('、')}`).join(';') || '—';
7752
+ sections.push({
7753
+ key: 'states',
7754
+ label: '月令旺衰',
7755
+ text: `月令 ${mo.branch}(${mo.element}):`
7756
+ + `门 ${line(st.gate)}。星 ${line(st.star)}。天盘干 ${line(st.stem)}。`
7757
+ + `旺为同月令之气,相为月令所生,休为生月令,囚为克月令,死为月令所克。`
7758
+ + `干宫的落宫关系另算:${stemRelationLine(r)}。`
7759
+ + `这一层只说明门、星、干自身力量强弱,不合吉凶:门旺而宫克门,仍受宫制;星旺而落宫死地,也难全其用。`,
7760
+ });
7761
+ }
7762
+
6871
7763
  if (unresolved.length) {
6872
7764
  sections.push({
6873
7765
  key: 'tendency',
@@ -6881,7 +7773,7 @@ function interpretQimen(r, opts = {}) {
6881
7773
  key: 'tendency',
6882
7774
  label: '最终倾向',
6883
7775
  text: `在当前输入、规则集与所问之事下:若以「${primary.key}」为用神,用神宫对值符宫为「${rel}」,整体${tone};`
6884
- + `若${ys.label}实际取另一个候选,或时间范围不同,解读边界随之变化。未决项:月令旺衰未纳入本规则集。`,
7776
+ + `若${ys.label}实际取另一个候选,或时间范围不同,解读边界随之变化。未决项:天盘干与地盘的十干克应(100 组)未纳入本规则集,干宫生克只作落宫条件。`,
6885
7777
  });
6886
7778
  }
6887
7779
 
@@ -7602,6 +8494,407 @@ function buildNumerology(input) {
7602
8494
  return { NUMBER_MEANING, reduceNumber, buildNumerology };
7603
8495
  })();
7604
8496
 
8497
+ __mods.mbti = (function () {
8498
+ const { fail } = __mods.fail;
8499
+
8500
+ const DIMENSIONS = [
8501
+ {
8502
+ key: 'EI',
8503
+ left: 'E',
8504
+ right: 'I',
8505
+ name: '能量方向',
8506
+ leftSay: '向外:和人打交道时来电,边说边想',
8507
+ rightSay: '向内:独处时回血,先想清楚再开口',
8508
+ tie: 'I',
8509
+ note: '不是「会不会社交」,而是社交之后你更有电还是更耗电。',
8510
+ },
8511
+ {
8512
+ key: 'SN',
8513
+ left: 'S',
8514
+ right: 'N',
8515
+ name: '信息接收',
8516
+ leftSay: '具体:先看事实、细节和已有经验',
8517
+ rightSay: '联想:先看模式、隐喻和下一步可能',
8518
+ tie: 'N',
8519
+ note: '不是「聪明与否」,而是你先抓住哪一头。',
8520
+ },
8521
+ {
8522
+ key: 'TF',
8523
+ left: 'T',
8524
+ right: 'F',
8525
+ name: '决策依据',
8526
+ leftSay: '逻辑:先看是否站得住、是否一致',
8527
+ rightSay: '价值:先看人在其中会不会被伤到',
8528
+ tie: 'F',
8529
+ note: '不是「冷血或情绪化」,而是权衡时先放哪把尺子。',
8530
+ },
8531
+ {
8532
+ key: 'JP',
8533
+ left: 'J',
8534
+ right: 'P',
8535
+ name: '生活节奏',
8536
+ leftSay: '收束:先定计划,喜欢事情有个了结',
8537
+ rightSay: '开放:先留着口子,喜欢事情还能变',
8538
+ tie: 'P',
8539
+ note: '不是「勤快或懒散」,而是面对不确定时更愿意钉死还是留着。',
8540
+ },
8541
+ ];
8542
+
8543
+ const QUESTIONS = [
8544
+ { id: 'e1', dim: 'EI', side: 'E', text: '在一个都是陌生人的场合,你会主动找几个人聊起来。' },
8545
+ { id: 's1', dim: 'SN', side: 'S', text: '别人讲一件事,你更关心具体发生了什么、细节对不对得上。' },
8546
+ { id: 't1', dim: 'TF', side: 'T', text: '朋友来问你两难的选择,你会先把利弊摆清楚再谈感受。' },
8547
+ { id: 'j1', dim: 'JP', side: 'J', text: '出门前你习惯把行程和要带的东西先定下来。' },
8548
+ { id: 'e2', dim: 'EI', side: 'I', text: '忙完一整天,你更想一个人安静待着,而不是再约一轮。' },
8549
+ { id: 's2', dim: 'SN', side: 'N', text: '你常从一个现象跳到「再往下可能还会怎样」的联想上。' },
8550
+ { id: 't2', dim: 'TF', side: 'F', text: '做决定时,你更在意这件事对身边人的实际影响。' },
8551
+ { id: 'j2', dim: 'JP', side: 'P', text: '计划临时被打乱,你多半能顺势改,不太难受。' },
8552
+ { id: 'e3', dim: 'EI', side: 'E', text: '想事情的时候,你更习惯说给人听,说着说着就清楚了。' },
8553
+ { id: 's3', dim: 'SN', side: 'S', text: '你更信「做过、验证过」的办法,而不是听起来很新的办法。' },
8554
+ { id: 't3', dim: 'TF', side: 'T', text: '规则一旦定了,哪怕对自己不方便,你也倾向照着走。' },
8555
+ { id: 'j3', dim: 'JP', side: 'J', text: '有一件事没做完,你心里会一直悬着,想先把它收尾。' },
8556
+ { id: 'e4', dim: 'EI', side: 'I', text: '电话响了或有人临时找来,你第一反应是先缓一下再应对。' },
8557
+ { id: 's4', dim: 'SN', side: 'N', text: '你容易被「这背后是什么道理」这类问题勾住。' },
8558
+ { id: 't4', dim: 'TF', side: 'F', text: '别人情绪不好时,你直觉是先接住情绪,而不是先讲道理。' },
8559
+ { id: 'j4', dim: 'JP', side: 'P', text: '你常常边走边看,事到临头再定具体怎么做。' },
8560
+ { id: 'e5', dim: 'EI', side: 'E', text: '人多的场合待久了,你通常越待越有精神。' },
8561
+ { id: 's5', dim: 'SN', side: 'S', text: '你记事情偏向记「原话、原样、当时的情形」。' },
8562
+ { id: 't5', dim: 'TF', side: 'T', text: '和人争论时,你更在意谁的推理有漏洞,而不是谁伤了面子。' },
8563
+ { id: 'j5', dim: 'JP', side: 'J', text: '你喜欢把待办列出来,一条条划掉。' },
8564
+ { id: 'e6', dim: 'EI', side: 'I', text: '有新想法时,你更愿意先自己消化一轮,再拿出去讲。' },
8565
+ { id: 's6', dim: 'SN', side: 'N', text: '比起照现状做好,你更享受把现状改成一个更好的样子。' },
8566
+ { id: 't6', dim: 'TF', side: 'F', text: '要做取舍时,你会想「这样是不是对人不公平」。' },
8567
+ { id: 'j6', dim: 'JP', side: 'P', text: '临出发才收拾行李,你也觉得没什么问题。' },
8568
+ ];
8569
+
8570
+ const FUNCTION_STACKS = {
8571
+ ISTJ: ['Si', 'Te', 'Fi', 'Ne'],
8572
+ ISFJ: ['Si', 'Fe', 'Ti', 'Ne'],
8573
+ INFJ: ['Ni', 'Fe', 'Ti', 'Se'],
8574
+ INTJ: ['Ni', 'Te', 'Fi', 'Se'],
8575
+ ISTP: ['Ti', 'Se', 'Ni', 'Fe'],
8576
+ ISFP: ['Fi', 'Se', 'Ni', 'Te'],
8577
+ INFP: ['Fi', 'Ne', 'Si', 'Te'],
8578
+ INTP: ['Ti', 'Ne', 'Si', 'Fe'],
8579
+ ESTP: ['Se', 'Ti', 'Fe', 'Ni'],
8580
+ ESFP: ['Se', 'Fi', 'Te', 'Ni'],
8581
+ ENFP: ['Ne', 'Fi', 'Te', 'Si'],
8582
+ ENTP: ['Ne', 'Ti', 'Fe', 'Si'],
8583
+ ESTJ: ['Te', 'Si', 'Ne', 'Fi'],
8584
+ ESFJ: ['Fe', 'Si', 'Ne', 'Ti'],
8585
+ ENFJ: ['Fe', 'Ni', 'Se', 'Ti'],
8586
+ ENTJ: ['Te', 'Ni', 'Se', 'Fi'],
8587
+ };
8588
+
8589
+ const FUNCTIONS = {
8590
+ Si: '内感觉:拿过去的经验当尺子,稳、细、记得住。',
8591
+ Se: '外感觉:对眼前的具体变化反应快,先动手再说。',
8592
+ Ni: '内直觉:把零碎线索收成一个方向感,先看到终局。',
8593
+ Ne: '外直觉:从一个点炸开很多可能,擅长另起一条路。',
8594
+ Ti: '内思考:要自己把逻辑盘通,讲得出为什么才认。',
8595
+ Te: '外思考:要事情跑起来,讲流程、指标和结果。',
8596
+ Fi: '内情感:先问「这符不符合我在意的东西」,价值感很强。',
8597
+ Fe: '外情感:先读空气、先顾关系,擅长把人和在一起。',
8598
+ };
8599
+
8600
+ const TYPES = {
8601
+ ISTJ: {
8602
+ title: '检查者',
8603
+ keywords: '稳、准、守得住',
8604
+ oneLine: '答应的事会做完,讨厌临时改口和半成品。',
8605
+ strengths: ['流程感强,细节不漏', '说到做到,扛得住长期重复', '遇到混乱先立规矩'],
8606
+ blindspots: ['对「新办法」先起疑,容易错过转弯的机会', '把责任揽太多,难受也说不出口'],
8607
+ work: '适合规则清楚、可累积经验的岗位:审计、运维、法务、工程管理。',
8608
+ love: '表达朴素但稳定,重视承诺;需要对方把需求说明白,别指望你自己猜。',
8609
+ study: '靠体系与刷题推进,把知识整理成清单会明显提速。',
8610
+ growth: '练一件「没有标准答案」的事,容许自己在过程中改主意。',
8611
+ },
8612
+ ISFJ: {
8613
+ title: '守护者',
8614
+ keywords: '暖、细、记得住人',
8615
+ oneLine: '会默默把别人的需要记在心里,然后提前做好。',
8616
+ strengths: ['照顾细节与情绪', '记性好,了解身边人的习惯', '愿意做没人愿意做的收尾'],
8617
+ blindspots: ['习惯先委屈自己,之后积成怨', '对方没察觉你的付出时容易失落'],
8618
+ work: '适合需要耐心与责任心的岗位:护理、教育、行政、客户成功、人事。',
8619
+ love: '用做事表达感情,需要被明确看见与感谢;也要练习直接要。',
8620
+ study: '喜欢有例题、有反馈的学习方式,做学习小组的稳定输出者很合适。',
8621
+ growth: '练习说「这件事我做不到」,先照顾自己的电量。',
8622
+ },
8623
+ INFJ: {
8624
+ title: '提倡者',
8625
+ keywords: '深、理想、看穿人心',
8626
+ oneLine: '关心人为什么这么活,愿意为一个「值得」的目标长期投入。',
8627
+ strengths: ['能从细节里读出动机', '表达有感染力,适合讲清意义', '对长期方向有耐心'],
8628
+ blindspots: ['标准高,容易对现实失望', '想得太深而迟迟不开口,外人看不出你在乎'],
8629
+ work: '适合内容、咨询、教育、公益、产品策略这类「讲清一件事」的岗位。',
8630
+ love: '要的是深谈与理解,浅社交会耗电;需要对方给得起精神上的回应。',
8631
+ study: '适合主题式深挖,写读书笔记与建立自己的框架比刷题更有效。',
8632
+ growth: '把理想拆成这周能交付的一小步,别只在脑子里完成它。',
8633
+ },
8634
+ INTJ: {
8635
+ title: '建筑师',
8636
+ keywords: '远、冷静、自己搭系统',
8637
+ oneLine: '先看清全局再动手,讨厌无效沟通和低效流程。',
8638
+ strengths: ['长期规划清晰', '能独立啃下复杂问题', '对标准挑剔,产品因此更硬'],
8639
+ blindspots: ['说话直,容易让人觉得被否定', '对「慢」和「情绪」耐心不足'],
8640
+ work: '适合战略、研发、系统设计、投资研究这类需要独立判断的岗位。',
8641
+ love: '表达感情偏克制,用解决问题的方式示好;需要对方接受你的独处需求。',
8642
+ study: '自学效率高,适合先建框架再补细节;警惕只读不产出。',
8643
+ growth: '在给方案之前先接一句对方的感受,合作阻力会小很多。',
8644
+ },
8645
+ ISTP: {
8646
+ title: '巧匠',
8647
+ keywords: '手快、冷静、先修再说',
8648
+ oneLine: '遇到问题先上手拆开看,不爱开长会。',
8649
+ strengths: ['动手能力强,现场判断快', '压力下不慌,能抓住关键', '讲实际,不绕弯'],
8650
+ blindspots: ['对长期计划不感兴趣,容易拖到最后', '话少,别人不知道你在想什么'],
8651
+ work: '适合现场性强的岗位:工程、设备、急救、运维、技术支持、手艺型工作。',
8652
+ love: '不擅长说,但会用行动帮忙;需要对方接受你「不解释」的习惯。',
8653
+ study: '边做边学最快,先碰真实设备或真实项目,再回头补理论。',
8654
+ growth: '给重要的事留一个检查点,别全靠临场反应。',
8655
+ },
8656
+ ISFP: {
8657
+ title: '探险家',
8658
+ keywords: '真、敏感、跟着感觉走',
8659
+ oneLine: '在意「这件事对不对味」,不喜欢被安排得太死。',
8660
+ strengths: ['审美与体感准', '待人温和,不强加于人', '在真实情境里学得快'],
8661
+ blindspots: ['不愿冲突,把不满压着', '长期目标容易停在「想」这一步'],
8662
+ work: '适合与美、身体、具体对象打交道的岗位:设计、摄影、餐饮、康复、手作。',
8663
+ love: '用陪伴和小惊喜表达在意,讨厌被审问;需要空间也需要被确认。',
8664
+ study: '先看示例、先动手,理论放在用到的时候补,效率更高。',
8665
+ growth: '给在意的事定一个可交付的日期,把感觉变成作品。',
8666
+ },
8667
+ INFP: {
8668
+ title: '调停者',
8669
+ keywords: '柔、较真、心里有一杆秤',
8670
+ oneLine: '对人真诚,对自己在意的事格外较真,讨厌虚伪。',
8671
+ strengths: ['共情与想象力都好', '写与表达有个人味道', '能为价值观长期坚持'],
8672
+ blindspots: ['容易把「不够好」归到自己身上', '计划性弱,想法多而落地慢'],
8673
+ work: '适合创作、教育、心理、内容、公益这类能表达价值的岗位;强规则环境会耗电。',
8674
+ love: '看重真诚与被理解,愿意为对方调整;需要明确的回应,别长期猜。',
8675
+ study: '靠兴趣驱动,给自己找「为什么学」比逼自己更有效;用短周期输出防拖延。',
8676
+ growth: '把心里的标准写下来,分「必须有」和「有更好」,少一点全或无。',
8677
+ },
8678
+ INTP: {
8679
+ title: '逻辑家',
8680
+ keywords: '想透、好问、自己盘逻辑',
8681
+ oneLine: '喜欢把问题拆到原理层,讨厌「一直这样所以就这样」。',
8682
+ strengths: ['抽象思考强,能发现系统里的漏洞', '学新领域快', '不轻易被权威说服'],
8683
+ blindspots: ['想得多做得少,收尾困难', '指出问题时不修饰,容易树敌'],
8684
+ work: '适合研究、算法、架构、分析、写作这类需要反复推演的岗位。',
8685
+ love: '用分享想法表达亲近,需要对方能聊得深;别让讨论变成辩论。',
8686
+ study: '先弄清原理再练,适合自己搭知识树;给自己设「讲给别人听」的交付点。',
8687
+ growth: '给探索设时间盒,到点就交出一个能用的版本。',
8688
+ },
8689
+ ESTP: {
8690
+ title: '企业家',
8691
+ keywords: '快、敢、临场能打',
8692
+ oneLine: '看到机会先动手,比开会讨论更信现场反馈。',
8693
+ strengths: ['行动快,敢试错', '会看人、会周旋', '压力下反而清醒'],
8694
+ blindspots: ['耐心不足,容易留一堆半成品', '对长期代价估计偏轻'],
8695
+ work: '适合销售、商务、创业、现场运营、竞技类岗位;文牍型工作会闷。',
8696
+ love: '热烈直接,喜欢一起做事;需要对方接受你「说到就到」的节奏。',
8697
+ study: '靠实战与竞赛驱动,做项目比听课有效;需要有人帮你收尾。',
8698
+ growth: '动手前多问一句「三个月后这件事还成立吗」。',
8699
+ },
8700
+ ESFP: {
8701
+ title: '表演者',
8702
+ keywords: '热、会带气氛、活在当下',
8703
+ oneLine: '在人群里自带气氛,喜欢真实可感的体验。',
8704
+ strengths: ['感染力强,能把场子撑起来', '对人和现场的细节敏感', '愿意尝试新鲜事'],
8705
+ blindspots: ['对长期枯燥的事耐力有限', '不太愿意面对冲突背后的结构问题'],
8706
+ work: '适合面向人的岗位:活动、销售、主持、文旅、服务、内容出镜。',
8707
+ love: '表达直接,喜欢一起玩;需要对方给你稳定感,也别把自由当借口。',
8708
+ study: '边玩边学最有效,把知识做成游戏、演示或小组活动。',
8709
+ growth: '留一件「不好玩但重要」的事,按周推进。',
8710
+ },
8711
+ ENFP: {
8712
+ title: '竞选者',
8713
+ keywords: '亮、点子多、对人好奇',
8714
+ oneLine: '对人和可能性都很好奇,容易同时点亮很多盏灯。',
8715
+ strengths: ['联想快,能给人新视角', '真诚热情,容易建立信任', '擅长起头与鼓舞'],
8716
+ blindspots: ['兴趣转移快,收尾吃力', '情绪起伏大,容易被细节拖垮'],
8717
+ work: '适合创意、品牌、内容、培训、早期项目这类需要不断开新局的地方。',
8718
+ love: '需要新鲜感与被看见,愿意为对方想很多;要练习把承诺落到实处。',
8719
+ study: '用主题和讨论驱动,找学习搭子比一个人硬啃有效。',
8720
+ growth: '同时只开三条线,其余写进「以后再说」清单。',
8721
+ },
8722
+ ENTP: {
8723
+ title: '辩论家',
8724
+ keywords: '机敏、爱拆、想法往外冒',
8725
+ oneLine: '喜欢把既有说法掰开看,享受把问题换个角度。',
8726
+ strengths: ['反应快,能现场找到漏洞', '对新领域上手快', '擅长把复杂事讲得好玩'],
8727
+ blindspots: ['为辩而辩,容易伤人', '起头多、收尾少'],
8728
+ work: '适合咨询、产品、创业、策略、媒体这类需要不断破局的地方。',
8729
+ love: '用斗嘴表达亲近,需要对方接得住;别把关系也当成辩论场。',
8730
+ study: '讨论与质疑中理解最快,适合做分享者;给自己定交付时间。',
8731
+ growth: '挑一件「不新但重要」的事,把它做完再开下一个。',
8732
+ },
8733
+ ESTJ: {
8734
+ title: '总经理',
8735
+ keywords: '务实、能扛、把事推到底',
8736
+ oneLine: '目标定了就拆任务、定人、定时间,讨厌含糊。',
8737
+ strengths: ['组织与推进力强', '标准清楚,团队知道该做什么', '对结果负责'],
8738
+ blindspots: ['节奏太紧,忽视人的感受', '对新方法的容忍度低'],
8739
+ work: '适合管理、运营、项目、供应链、政务这类要「把事办成」的岗位。',
8740
+ love: '用承担责任表达在意,需要对方直接说需求;记得留不安排的时间。',
8741
+ study: '目标拆解 + 打卡推进最有效,适合带小组复习。',
8742
+ growth: '在下判断前多问一句「有没有我没想到的难处」。',
8743
+ },
8744
+ ESFJ: {
8745
+ title: '执政官',
8746
+ keywords: '周到、热络、把人放在心上',
8747
+ oneLine: '习惯先照顾气氛和关系,让事情在人情里办成。',
8748
+ strengths: ['组织人际与活动', '记得住每个人的状况', '愿意为集体做脏活累活'],
8749
+ blindspots: ['太在意别人评价,容易内耗', '对「不合群」的选项排斥'],
8750
+ work: '适合人力、行政、教育、客户、医护、社群运营这类以人为中心的岗位。',
8751
+ love: '投入、周到,需要明确的回应与稳定;别把照顾变成控制。',
8752
+ study: '在小组里学得最好,讲给别人听会记得更牢。',
8753
+ growth: '练习在不解释的情况下做一次自己的选择。',
8754
+ },
8755
+ ENFJ: {
8756
+ title: '主人公',
8757
+ keywords: '暖、会带人、看得见潜力',
8758
+ oneLine: '天然想把人和事往好的方向推,愿意为别人多走一步。',
8759
+ strengths: ['洞察他人需求', '善于组织与鼓舞', '把长期关系经营得住'],
8760
+ blindspots: ['过度承担别人的情绪', '为了和谐压下自己的判断'],
8761
+ work: '适合教育、管理、培训、公益、品牌这类「带人成长」的岗位。',
8762
+ love: '投入且会照顾,需要对方也主动;别把付出当成默认义务。',
8763
+ study: '教别人是最好的学法,适合做学习小组的组织者。',
8764
+ growth: '把「我该帮」和「不该我扛」分开,先照顾自己的电量。',
8765
+ },
8766
+ ENTJ: {
8767
+ title: '指挥官',
8768
+ keywords: '决断、看清、把盘做大',
8769
+ oneLine: '看得远也敢拍板,喜欢把资源调到最该去的地方。',
8770
+ strengths: ['目标与路径清楚', '能在压力下决断', '擅长组织复杂局面'],
8771
+ blindspots: ['节奏压人,忽略情绪成本', '把「效率」当成唯一标准'],
8772
+ work: '适合管理、创业、投资、战略、大型项目这类要定方向并扛结果的岗位。',
8773
+ love: '用规划和承担表达在意,需要对方能对话;记得把「安排」换成「商量」。',
8774
+ study: '先建目标与评估标准,按里程碑推进;警惕只搭框架不落细节。',
8775
+ growth: '定期问自己「这件事赢了,人会怎样」,把人的成本算进去。',
8776
+ },
8777
+ };
8778
+
8779
+ const SCALE_SAY = {
8780
+ 1: '完全不像',
8781
+ 2: '不太像',
8782
+ 3: '说不准',
8783
+ 4: '有点像',
8784
+ 5: '很像我',
8785
+ };
8786
+
8787
+ function mbtiPaper() {
8788
+ return {
8789
+ system: 'mbti',
8790
+ needAnswers: true,
8791
+ scale: SCALE_SAY,
8792
+ total: QUESTIONS.length,
8793
+ dimensions: DIMENSIONS.map((d) => ({ key: d.key, name: d.name, left: d.left, right: d.right, leftSay: d.leftSay, rightSay: d.rightSay, note: d.note })),
8794
+ questions: QUESTIONS.map((q, i) => ({ no: i + 1, id: q.id, dim: q.dim, text: q.text })),
8795
+ note: '这是一份 MBTI 风格的问卷(不是官方 MBTI 量表):每题按 1(完全不像我)到 5(很像我)打分,把 24 个分数按顺序交给 answers 即可出类型。题目只作自我参照,不适合用来评价别人的能力或做招聘、晋升等决定。',
8796
+ };
8797
+ }
8798
+
8799
+ function normalizeAnswers(answers) {
8800
+ if (!Array.isArray(answers)) {
8801
+ throw fail('E_INPUT', 'answers 必须是数组:每题 1–5 分,按 questions 的顺序给;答题前先不带 answers 调一次,拿到题目');
8802
+ }
8803
+ if (answers.length > QUESTIONS.length) {
8804
+ throw fail('E_INPUT', `answers 最多 ${QUESTIONS.length} 个(对应 ${QUESTIONS.length} 题),收到 ${answers.length} 个`);
8805
+ }
8806
+ const out = [];
8807
+ for (let i = 0; i < QUESTIONS.length; i += 1) {
8808
+ const raw = answers[i];
8809
+ if (raw === undefined || raw === null || raw === '') {
8810
+ out.push(null);
8811
+ continue;
8812
+ }
8813
+ const v = Number(raw);
8814
+ if (!Number.isInteger(v) || v < 1 || v > 5) {
8815
+ throw fail('E_RANGE', `第 ${i + 1} 题的分值必须是 1–5 的整数(1 完全不像,5 很像我),收到「${raw}」`);
8816
+ }
8817
+ out.push(v);
8818
+ }
8819
+ return out;
8820
+ }
8821
+
8822
+ function strengthOf(pct, answered) {
8823
+ if (!answered) return '未作答';
8824
+ if (pct <= 0.12) return '临界';
8825
+ if (pct <= 0.3) return '轻微';
8826
+ if (pct <= 0.6) return '明显';
8827
+ return '很强';
8828
+ }
8829
+
8830
+ function buildMbti(input = {}) {
8831
+ const answers = normalizeAnswers(input.answers);
8832
+ const answeredAll = answers.filter((v) => v !== null).length;
8833
+ if (answeredAll < 12) {
8834
+ throw fail('E_INPUT', `至少答 ${12} 题才能出口径(现答 ${answeredAll} 题);可以先不带 answers 调一次,把题目发给用户逐题打分`);
8835
+ }
8836
+ const dims = DIMENSIONS.map((d) => {
8837
+ let sum = 0;
8838
+ let answered = 0;
8839
+ QUESTIONS.forEach((q, i) => {
8840
+ const v = answers[i];
8841
+ if (v === null || q.dim !== d.key) return;
8842
+ answered += 1;
8843
+ sum += (v - 3) * (q.side === d.left ? 1 : -1);
8844
+ });
8845
+ const maxAbs = answered * 2;
8846
+ const pct = maxAbs ? Math.abs(sum) / maxAbs : 0;
8847
+ const picked = sum > 0 ? d.left : sum < 0 ? d.right : d.tie;
8848
+ return {
8849
+ key: d.key,
8850
+ name: d.name,
8851
+ left: d.left,
8852
+ right: d.right,
8853
+ leftSay: d.leftSay,
8854
+ rightSay: d.rightSay,
8855
+ note: d.note,
8856
+ sum,
8857
+ answered,
8858
+ maxAbs,
8859
+ pct: Math.round(pct * 1000) / 1000,
8860
+ picked,
8861
+ strength: strengthOf(pct, answered),
8862
+ tie: sum === 0,
8863
+ };
8864
+ });
8865
+ const type = dims.map((d) => d.picked).join('');
8866
+ const profile = TYPES[type];
8867
+ if (!profile) throw fail('E_SYSTEM', `内部错误:类型 ${type} 没有对应文案`);
8868
+ const stack = (FUNCTION_STACKS[type] || []).map((f) => ({ code: f, say: FUNCTIONS[f] || '' }));
8869
+ return {
8870
+ system: 'mbti',
8871
+ type,
8872
+ title: profile.title,
8873
+ keywords: profile.keywords,
8874
+ oneLine: profile.oneLine,
8875
+ dims: dims.map(({ sum, maxAbs, answered, ...rest }) => ({ ...rest, sum, maxAbs, answered })),
8876
+ profile: {
8877
+ title: profile.title,
8878
+ keywords: profile.keywords,
8879
+ oneLine: profile.oneLine,
8880
+ strengths: profile.strengths.slice(),
8881
+ blindspots: profile.blindspots.slice(),
8882
+ work: profile.work,
8883
+ love: profile.love,
8884
+ study: profile.study,
8885
+ growth: profile.growth,
8886
+ stack,
8887
+ },
8888
+ answered: answeredAll,
8889
+ skipped: QUESTIONS.length - answeredAll,
8890
+ total: QUESTIONS.length,
8891
+ note: '四维各 6 题、李克特 1–5 计分,按维度净分取字母(平手时取 I / N / F / P 一侧并标「临界」)。这是 MBTI 风格的问卷测算,不是官方量表;类型只作自我参照,不是能力评价,也不用它给人下结论。',
8892
+ };
8893
+ }
8894
+
8895
+ return { DIMENSIONS, QUESTIONS, FUNCTION_STACKS, FUNCTIONS, TYPES, mbtiPaper, buildMbti };
8896
+ })();
8897
+
7605
8898
  __mods.chenggu = (function () {
7606
8899
  const { solarToLunar } = __mods.lunar;
7607
8900
  const { ganzhiName, toJDN, dayGanzhiIndex } = __mods.calendar;
@@ -8132,6 +9425,55 @@ const STRENGTH_PLAIN = {
8132
9425
  身弱: '底子偏薄,先修恢复力再谈冲刺,硬撑的性价比最低',
8133
9426
  };
8134
9427
 
9428
+ const PLAIN_BODY = {
9429
+ 六冲卦: '这一卦是「六冲」,事情起落快,急事反而好办,拖久了的人事容易散。',
9430
+ 六合卦: '这一卦是「六合」,容易谈成,也容易黏住不放。',
9431
+ 变六冲: '往下走会散,收尾别拖。',
9432
+ 变六合: '往下走会合,事情倾向于延续下去。',
9433
+ 内卦伏吟: '事情卡在原地打转,想动但动不了。',
9434
+ 外卦伏吟: '外面那层没变,看起来在动,其实格局照旧。',
9435
+ 内卦反吟: '反复在自己这一头,改了又改回来。',
9436
+ 外卦反吟: '外面反复,对方或环境会来回拉扯。',
9437
+ 化进神: '有一爻在往上走,顺着这股劲推比往回拉顺。',
9438
+ 化退神: '有一爻在往后退,别按原来的势头硬推。',
9439
+ };
9440
+
9441
+ function usePlainOf(ug) {
9442
+ if (!ug || !ug.liuqin) return '';
9443
+ if (ug.found.length) {
9444
+ const pos = ug.found.map((f) => `第${f.position}爻`).join('、');
9445
+ const moving = ug.found.some((f) => f.moving);
9446
+ const who = ug.byShi ? '代表你自己' : `就是「${ug.liuqin}」这一类事`;
9447
+ return `你问的这件事,在卦里对应${pos}${who}——${moving ? '这一爻还在动,说明事情正在推进' : '这一爻没动,先按现状看'}。`;
9448
+ }
9449
+ if (ug.fushen.length) {
9450
+ const f = ug.fushen[0];
9451
+ return `你问的这件事,卦面上没有直接露出来,得往深处找:它藏在第${f.position}爻下面(伏神),与上面的关系是「${f.relation || '—'}」,要借力才出得来。`;
9452
+ }
9453
+ return '';
9454
+ }
9455
+
9456
+ function useStrengthPlain(st) {
9457
+ if (!st || !st.lines || !st.lines.length) return '';
9458
+ const dead = st.lines.filter((l) => /死|囚/.test(l.monthState) || l.monthChong);
9459
+ const live = st.lines.filter((l) => /旺|相/.test(l.monthState));
9460
+ const when = st.month && st.month.branch ? `眼下这阵子(${st.month.branch}月)` : '起卦这段日子';
9461
+ if (dead.length && !live.length) return `${when}对这件事不太帮忙,气有点被压着——先别急着硬推,等风向转。`;
9462
+ if (live.length && !dead.length) return `${when}对这件事是帮着的,气正在往上走,能推就趁现在。`;
9463
+ return `${when}对这件事有帮有压,一半的力要自己挣——结果更看你怎么走。`;
9464
+ }
9465
+
9466
+ function bodyPlainOf(labels) {
9467
+ const keyOf = (l) => Object.keys(PLAIN_BODY).find((k) => l === k || l.startsWith(`${k} `)) || '';
9468
+ const primary = labels.find((l) => keyOf(l));
9469
+ const main = primary ? PLAIN_BODY[keyOf(primary)] : '';
9470
+ const extra = labels
9471
+ .filter((l) => l !== primary && /^化[进退]神 /.test(l))
9472
+ .map((l) => PLAIN_BODY[keyOf(l)])
9473
+ .filter(Boolean);
9474
+ return main + extra.join('');
9475
+ }
9476
+
8135
9477
  const DIR_SECTION = {
8136
9478
  wealth: '「工作与职业选择」与「时间窗」',
8137
9479
  career: '「事业发展」与「时间窗」',
@@ -8174,22 +9516,30 @@ function buildAnswer(r, ask, ctx = {}) {
8174
9516
  const body = r.body || {};
8175
9517
  const bureau = r.bureau || {};
8176
9518
  const md = r.lifeMaster || '';
9519
+ const ji = r.flyChains && r.flyChains.忌;
9520
+ const plainFly = ji && ji.steps.length
9521
+ ? `还有一条线值得留意:${ji.path.map((p) => p.replace(/宫$/, '')).join(' → ')}——这几件事在你身上是连着转的,一件卡住,容易牵动下一件。`
9522
+ : '';
8177
9523
  return [
8178
9524
  head + `先说结论:你的命宫落${s(soul.branch, '—')}宫` +
8179
9525
  `${bureau.name ? `、${bureau.name}` : ''}${md ? `,命主${md}` : ''}` +
8180
9526
  `${body.lands ? `,身宫落在「${body.lands}」——这张盘的重心在这件事上` : ''}。`,
9527
+ plainFly,
8181
9528
  `具体到你问的这件事,重点看${where}。`,
8182
- ].join('');
9529
+ ].filter(Boolean).join('');
8183
9530
  }
8184
9531
 
8185
9532
  if (r.system === 'liuyao') {
8186
9533
  const ben = r.ben || {};
8187
9534
  const moving = (r.cast && r.cast.movingPositions) || [];
8188
9535
  const bian = r.bian || {};
9536
+ const labels = (r.body && Array.isArray(r.body.labels)) ? r.body.labels : [];
9537
+ const plainBody = bodyPlainOf(labels);
8189
9538
  return [
8190
9539
  head + `先说结论:本卦是${s(ben.name, '—')}` +
8191
9540
  `${ben.palace ? `(${ben.palace})` : ''}` +
8192
- `${moving.length ? `,第 ${moving.join('、')} 爻动,变为${s(bian.name, '—')}` : ',六爻安静没有动爻'}。`,
9541
+ `${moving.length ? `,第 ${moving.join('、')} 爻动,变为${s(bian.name, '—')}` : ',六爻安静没有动爻'}。` +
9542
+ `${plainBody}${usePlainOf(r.useGod)}${useStrengthPlain(r.useGodStrength)}`,
8193
9543
  '断卦的落点在「世爻」(代表你)和「动爻」(事情的推动点),下面「看盘顺序」就按这个次序展开。',
8194
9544
  ].join('');
8195
9545
  }
@@ -8213,14 +9563,24 @@ function buildPlain(r, ask, ctx = {}) {
8213
9563
  if (r.system === 'ziwei') {
8214
9564
  const soul = r.soul || {};
8215
9565
  const body = r.body || {};
8216
- return `紫微看的是「你与环境怎么互动」。命宫落${s(soul.branch, '—')}宫,说明你做事的第一反应是从这个宫位的气质出发` +
8217
- `${body.lands ? `;身宫在「${body.lands}」,意味着真正让你上心的,是${body.lands}这件事` : ''}。`;
9566
+ const ji = r.flyChains && r.flyChains.忌;
9567
+ const plainFly = ji && ji.steps.length
9568
+ ? `另外有一条线值得留意:${ji.path.map((p) => p.replace(/宫$/, '')).join(' → ')}——这几件事在你身上是连着转的,一件事卡住,容易牵动下一件。`
9569
+ : '';
9570
+ return [
9571
+ `紫微看的是「你与环境怎么互动」。命宫落${s(soul.branch, '—')}宫,说明你做事的第一反应是从这个宫位的气质出发` +
9572
+ `${body.lands ? `;身宫在「${body.lands}」,意味着真正让你上心的,是${body.lands}这件事` : ''}。`,
9573
+ plainFly,
9574
+ ].filter(Boolean).join('');
8218
9575
  }
8219
9576
  if (r.system === 'liuyao') {
8220
9577
  const ben = r.ben || {};
8221
9578
  const moving = (r.cast && r.cast.movingPositions) || [];
9579
+ const labels = (r.body && Array.isArray(r.body.labels)) ? r.body.labels : [];
9580
+ const plainBody = bodyPlainOf(labels);
8222
9581
  return `${s(ben.name, '这一卦')}讲的是一个「${moving.length ? '正在动、还会变' : '暂时不动、先看清'}」的局面——` +
8223
- `${moving.length ? '有动爻,说明事情还有转圜空间,不是死局。' : '六爻安静,说明当下更该观察而不是硬推。'}`;
9582
+ `${moving.length ? '有动爻,说明事情还有转圜空间,不是死局。' : '六爻安静,说明当下更该观察而不是硬推。'}` +
9583
+ `${plainBody}${usePlainOf(r.useGod)}${useStrengthPlain(r.useGodStrength)}`;
8224
9584
  }
8225
9585
  return '';
8226
9586
  }
@@ -8445,13 +9805,31 @@ const KNOWLEDGE = [
8445
9805
  status: 'variant',
8446
9806
  note: '空劫是否见禄反吉,各家说法不一,本插件只提示变动性质,不判吉凶。',
8447
9807
  },
9808
+ {
9809
+ id: 'ziwei-feixing',
9810
+ system: 'ziwei', dims: ['overall', 'career'],
9811
+ gist: '宫干定四化,星落哪宫,这一宫的力就往哪边走。',
9812
+ detail: '生年四化是先天带的,飞星四化是宫位自己去找的:每一宫的宫干定出禄权科忌四颗星,那几颗星落在哪一宫,就是这一宫的力气往哪边使。所以同一个「化忌」,从命宫飞出去和从财帛飞出去,说的是两件事。',
9813
+ source: '《紫微斗数全书》四化篇;中州派宫干四化传法',
9814
+ status: 'variant',
9815
+ note: '飞星派(如钦天四化)把宫干四化当主线,三合派只作辅助;庚干四化各派另有异说,本插件取中州派常用表。',
9816
+ },
9817
+ {
9818
+ id: 'ziwei-jichuanji',
9819
+ system: 'ziwei', dims: ['overall'],
9820
+ gist: '忌转忌:化忌落进哪一宫,就从那一宫再化出去。',
9821
+ detail: '串连是跟着同一支化逐宫走:命宫化忌入父母,父母宫的宫干再化忌往下一宫,直到绕回走过的宫成环。环越短,越容易在同一组宫位上反复;走不出去(传统说「忌出不去」)不等于凶,只是这条力没有别的出口。',
9822
+ source: '《紫微斗数全书》四化篇;忌转忌串连法',
9823
+ status: 'variant',
9824
+ note: '串连几跳为止、是否允许借对宫飞化,各派不一;本插件只跟单一支化逐宫走,走满 12 宫或成环即停。',
9825
+ },
8448
9826
 
8449
9827
  {
8450
9828
  id: 'liuyao-yongshen',
8451
- system: 'liuyao', dims: ['overall'],
8452
- gist: '问什么,就以那一类六亲为用神。',
8453
- detail: '问财看妻财、问事业官位看官鬼、问文书房产看父母、问子女下属看子孙、问同辈朋友看兄弟。用神两现时,取动者、取临世者、取旺者;用神不上卦,则从本宫首卦取伏神。这一步定错,后面全盘白算。',
8454
- source: '《卜筮正宗》用神章;《增删卜易》同',
9829
+ system: 'liuyao', dims: ['overall', 'work', 'wealth', 'love', 'health', 'study'],
9830
+ gist: '问什么就以哪一类六亲为用神,这一步定错,后面全盘白算。',
9831
+ detail: '取用按所问之事:求财与男命妻缘看妻财,工作职位考核看官鬼,文书合同学历看父母,健康以官鬼为病、子孙为医药,出行以世爻为用,感情男看妻财、女看官鬼。用神在卦上就取那一爻,出现两处记「两现」;用神不上卦,按本宫首卦同六亲的爻位取伏神,再看飞伏生克。本插件把这些位置与动变、旬空、卦体标签一一对上,但不替你选两现之中的主用。',
9832
+ source: '《卜筮正宗》用神章、伏神章;《增删卜易》用神章',
8455
9833
  status: 'settled',
8456
9834
  },
8457
9835
  {
@@ -8473,10 +9851,20 @@ const KNOWLEDGE = [
8473
9851
  {
8474
9852
  id: 'liuyao-fushen',
8475
9853
  system: 'liuyao', dims: ['overall'],
8476
- gist: '伏神是藏在下面、要借力才出得来的那一爻。',
8477
- detail: '用神不上卦,就从本宫首卦的同位取伏神。伏神要得出——得日月生扶、或飞神生伏神、或伏神自身旺相,才有用;伏在忌神之下且被克,则虽有如无。',
8478
- source: '《卜筮正宗》伏神章',
8479
- status: 'settled',
9854
+ gist: '用神不上卦,就去本宫首卦的同位取伏神。',
9855
+ detail: '本卦六爻里没有用神那一类六亲,就叫用神不上卦。取法是回到本卦所属宫的首卦(八纯卦),找出该六亲所在的爻位,那一爻即伏神,本卦同一爻位上的爻叫飞神。伏神要「得出」才有用:飞神生伏神、伏神克飞神、日月生扶都算得力;飞神克伏神则受压难出。本插件给出伏神爻位、干支五行与飞伏生克关系,不出具体的出伏日期。',
9856
+ source: '《卜筮正宗》伏神章;《增删卜易》伏神篇',
9857
+ status: 'variant',
9858
+ note: '首卦里同一六亲也可能出现两次(如乾宫乾为天的父母在三爻与上爻),此时取哪一爻为伏神各家不一;本插件把候选都列出并标「伏神两现」,不代选。伏神出伏的应期各家分歧更大,故不输出日期。',
9859
+ },
9860
+ {
9861
+ id: 'liuyao-yongshen-shuai',
9862
+ system: 'liuyao', dims: ['overall', 'work'],
9863
+ gist: '用神旺不旺,看月建给的气与日辰当天的扶克。',
9864
+ detail: '月建管「这一段时令」:爻五行与月支五行同气为旺、受月令生为相、生月令为休、克月令为囚、被月令克为死。日辰管「当天这一股力」:日辰生爻为生、克爻为克、同气为比和,反过来则是爻泄日、爻耗日。此外月建冲爻叫月破,日辰冲静爻叫暗动、冲旬空之爻叫冲空。本插件把这几项折成标签逐爻列出,不折成分数。',
9865
+ source: '《增删卜易》月建章、日辰章;《卜筮正宗》月建论',
9866
+ status: 'variant',
9867
+ note: '月破、暗动、冲空三者各家取法不完全一致(有派别把日冲动爻也叫暗动,有派别只认旺相之爻暗动);本插件按「月建冲=月破、日冲静爻=暗动、日冲旬空=冲空」这一套给标签,并注明用了哪一支月建。',
8480
9868
  },
8481
9869
  {
8482
9870
  id: 'liuyao-yingqi',
@@ -8496,6 +9884,59 @@ const KNOWLEDGE = [
8496
9884
  status: 'settled',
8497
9885
  },
8498
9886
 
9887
+ {
9888
+ id: 'liuyao-liuchong',
9889
+ system: 'liuyao', dims: ['overall'],
9890
+ gist: '六冲卦讲「散」:起落快、来去急。',
9891
+ detail: '六冲不按「爻位相邻」判,而是看六个地支能不能正好配成子午、寅申、辰戌三组对冲。按纳甲实排,八纯卦之外天雷无妄、雷天大壮也成六冲。冲不是单纯的坏:近病逢冲主速愈、久病逢冲才忌;求财问婚,冲多主聚不住。',
9892
+ source: '《卜筮正宗》六冲章;《增删卜易》卦体说',
9893
+ status: 'variant',
9894
+ note: '通行口诀多把六冲限为八纯卦,本插件按纳甲地支实配判定,故多出无妄与大壮两支;《增删卜易》卦例里这两卦确被称作六冲,可互证。',
9895
+ },
9896
+ {
9897
+ id: 'liuyao-liuhe',
9898
+ system: 'liuyao', dims: ['overall', 'love'],
9899
+ gist: '六合卦讲「成」:容易谈拢,也容易黏住。',
9900
+ detail: '六个地支正好配成子丑、寅亥、卯戌、辰酉、巳申、午未三组相合,即六合卦,共天地否、地天泰、地雷复、雷地豫、山火贲、水泽节、泽水困、火山旅八支。六合主和合、拖延:谈事情容易成,也容易久拖不决;问病则忌缠绵。',
9901
+ source: '《卜筮正宗》六合章',
9902
+ status: 'settled',
9903
+ },
9904
+ {
9905
+ id: 'liuyao-fanyin',
9906
+ system: 'liuyao', dims: ['overall', 'work'],
9907
+ gist: '反吟主反复:去了又来、定了又改。',
9908
+ detail: '卦反吟有两种成法:一是变卦某宫的三爻地支与本卦对应宫正好全冲;二是本卦之宫与变卦之宫在后天八卦上正对(乾巽、坎离、艮坤、震兑)。爻反吟则是动爻变出的地支回头冲本爻。无论哪种,取象都是反复、翻覆、旧事重提。',
9909
+ source: '《增删卜易》反吟伏吟篇;《卜筮正宗》',
9910
+ status: 'variant',
9911
+ note: '各家对「卦反吟」是否只认地支全冲有分歧;本插件的八卦正对一路,是从《增删卜易》卦例(如山地剥之坤为地、风天小畜之乾为天)反推出来的。',
9912
+ },
9913
+ {
9914
+ id: 'liuyao-fuyin',
9915
+ system: 'liuyao', dims: ['overall'],
9916
+ gist: '伏吟主呻吟:事情在原地打转。',
9917
+ detail: '卦变了,纳甲地支却没变,叫卦伏吟。纳甲上只有乾与震共用子寅辰、午申戌一套地支,所以卦伏吟只会出在乾与震互换的卦上,例如天雷无妄之雷天大壮、天风姤之雷风恒。动爻变出的地支与本爻同支,则是爻伏吟。伏吟取象是欲动不动、原地反复,常指人想动而形势不许。',
9918
+ source: '《增删卜易》伏吟篇;《卜筮正宗》',
9919
+ status: 'variant',
9920
+ note: '另有「伏吟以用神受困论」的说法,不限于卦体;本插件只报卦体层面的伏吟与动爻伏吟,不推断吉凶轻重。',
9921
+ },
9922
+ {
9923
+ id: 'liuyao-jintui',
9924
+ system: 'liuyao', dims: ['overall'],
9925
+ gist: '动爻变出同五行顺次之支,为化进神;反向为化退神。',
9926
+ detail: '动爻变出的地支与本爻同五行、且在顺次上往前一步,叫化进神;倒退一步叫化退神。八对是:寅→卯(木)、巳→午(火)、申→酉(金)、亥→子(水),土一支成环,丑→辰→未→戌→丑。同五行但不相邻(如寅→辰)不算。进神取象是这一爻代表的人或事有后劲、势在往前;退神取象是渐渐退气、按原势头推会落空。',
9927
+ source: '《增删卜易》进神退神章',
9928
+ status: 'settled',
9929
+ },
9930
+ {
9931
+ id: 'liuyao-jintui-yong',
9932
+ system: 'liuyao', dims: ['overall', 'work'],
9933
+ gist: '化进化退要看是用神还是忌神,还要叠月建日辰。',
9934
+ detail: '进神退神本身只说明这一爻的向外趋势:用神化进为得力,忌神化进则为患加剧;用神化退是力衰,忌神化退反而是解脱。传统还要看它在本月令衰旺、有没有日辰生扶,空亡或受克时进者未必能进。所以本插件只标出化进/化退与爻位,力重与吉凶仍留给用神一节判断。',
9935
+ source: '《增删卜易》进神退神章、用神章',
9936
+ status: 'variant',
9937
+ note: '土之进退次序各家说法不完全一致,有以丑→辰→未→戌为进、也有只取辰戌丑未相对者;本插件按丑辰未戌成环处理,并用《增删卜易》卦例中「未土化戌土为进」「戌土化未土为退」对拍。',
9938
+ },
9939
+
8499
9940
  {
8500
9941
  id: 'meihua-tiyong',
8501
9942
  system: 'meihua', dims: ['overall'],
@@ -8521,6 +9962,50 @@ const KNOWLEDGE = [
8521
9962
  status: 'variant',
8522
9963
  note: '三种取局法在节气交接前后差异最明显,本插件同时实现三种,可在参数里指定。',
8523
9964
  },
9965
+ {
9966
+ id: 'qimen-jixing',
9967
+ system: 'qimen', dims: ['overall', 'work'],
9968
+ gist: '六仪落刑宫叫击刑,是宫位条件不是判决。',
9969
+ detail: '六仪按所配地支相刑:甲子戊落震三(子刑卯)、甲戌己落坤二(戌刑未)、甲申庚落艮八(申刑寅)、甲午辛落离九(午自刑)、甲辰壬落巽四(辰自刑)、甲寅癸落巽四(寅刑巳)。天盘六仪落到自己那一宫即击刑,取象是受制、掣肘、旧账翻出来,多用来量这个符号本身使不使得上力。',
9970
+ source: '《烟波钓叟歌》刑格注;《奇门遁甲统宗》',
9971
+ status: 'variant',
9972
+ note: '有流派只论天盘六仪击刑,也有把地盘一并算的;本插件只判天盘,且只作宫位条件,不自动断用神受制。',
9973
+ },
9974
+ {
9975
+ id: 'qimen-rumu',
9976
+ system: 'qimen', dims: ['overall'],
9977
+ gist: '三奇落墓宫叫入墓:力量收进去,不容易使出来。',
9978
+ detail: '按五行墓库推:乙木墓在未(未属坤二宫),丙丁火墓在戌(戌属乾六宫)。天盘乙奇落坤二、丙奇或丁奇落乾六,即三奇入墓。取象是收束、藏起来、事情压住不动;好坏要看所问之事——问收藏、问了结,入墓未必是坏事。',
9979
+ source: '《奇门遁甲统宗》三奇入墓条;五行墓库见《五行大义》',
9980
+ status: 'variant',
9981
+ note: '各家对入墓范围说法不一:有只论三奇,也有把六仪、八门、九星一并论墓的;本插件只判天盘三奇,其余留白。',
9982
+ },
9983
+ {
9984
+ id: 'qimen-wangshuai',
9985
+ system: 'qimen', dims: ['overall', 'work'],
9986
+ gist: '门、星与天盘干按月令分旺相休囚死,只说明力量强弱。',
9987
+ detail: '旺相休囚死以月支五行为尺:与月令同气为旺,月令所生为相,生月令为休,克月令为囚,被月令所克为死。八门、九星与天盘干(三奇六仪)各按自身五行对月令,得出该符号当下「使不使得上力」。比如午月火旺,景门(火)旺、生门死门(土)相、休门(水)囚、开门惊门(金)死,天盘丙丁(火)旺、庚辛(金)死。',
9988
+ source: '《奇门遁甲统宗》门星旺衰条;五行旺相休囚死见《五行大义》',
9989
+ status: 'settled',
9990
+ },
9991
+ {
9992
+ id: 'qimen-wangshuai-yong',
9993
+ system: 'qimen', dims: ['overall', 'work'],
9994
+ gist: '旺衰是助力,不单独定吉凶:门旺而宫克门仍受制。',
9995
+ detail: '月令旺衰只回答「这个符号自身有多少气」,不回答「事情成不成」。取象时要和宫位关系叠起来看:门旺但同宫门克宫(门迫),力量被宫位掣肘;门囚但宫生门,仍得宫助。星同理,旺星落宫若被克,也难全其用。',
9996
+ source: '《奇门遁甲统宗》;《烟波钓叟歌》门宫生克注',
9997
+ status: 'variant',
9998
+ note: '有流派按时令而非月令论门星旺衰,也有以落宫五行当令者;本插件统一按月支五行判,并在解读里明确只作强度参考。',
9999
+ },
10000
+ {
10001
+ id: 'qimen-gan-shengke',
10002
+ system: 'qimen', dims: ['overall', 'work'],
10003
+ gist: '天盘干落宫论生克:干克宫主进取,宫克干受制,比和相安。',
10004
+ detail: '天盘干(三奇六仪)落到某宫,就看干的五行与该宫五行的生克:干克宫,是符号压向环境、偏进取与主动;宫克干,是环境反过来管住符号、偏受制与掣肘;干生宫,力量往外泄;宫生干,环境给力;比和,两下相安。这一层与门宫关系(门迫、宫制门)并列,各说各的,不互相替换;两者同时成立时分别列出,不折成一个分数。',
10005
+ source: '《奇门遁甲统宗》干宫生克条;《烟波钓叟歌》门宫生克注',
10006
+ status: 'variant',
10007
+ note: '各家对「干与宫」的取象宽窄不一:有只论干宫、有连天盘干加地盘干(十干克应)一并论的。本插件只判天盘干与所落宫,十干克应 100 组未做。',
10008
+ },
8524
10009
  {
8525
10010
  id: 'tarot-orientation',
8526
10011
  system: 'tarot', dims: ['overall'],
@@ -8530,24 +10015,24 @@ const KNOWLEDGE = [
8530
10015
  status: 'variant',
8531
10016
  note: '逆位是否参与解读本身就有分歧(有牌阵传统完全不用逆位)。本插件按削弱加内向综合取象,并在开头声明。',
8532
10017
  },
10018
+ {
10019
+ id: 'bazi-dayun-jiaoqi',
10020
+ system: 'bazi', dims: [],
10021
+ gist: '交运按「三日折一年」放大到日、时:起运时刻与每步换运日期都给。',
10022
+ detail: '把「出生 → 起运那一侧节令」的真实间隔按 365.2422/3(约 121.7474 倍)放大,加到出生时刻上就是交运时刻;同一比例也兑现「一日折四月、一时辰折十日」。每步大运在同月同日同时刻换,闰日退到 2 月 28 日。年长取回归年 365.2422 日,故与按 360 日折算的流派会差一两天。',
10023
+ source: '《渊海子平》起运法(三日折一年);《三命通会》论行运',
10024
+ status: 'variant',
10025
+ note: '各家对「一年折几日」「交运日是否按 360 日计」取法不一,本插件固定 365.2422 日并把口径写进结果(dayun.jiao.yearDays)。出生时刻或经度一变,交运日会前后移一两天;月柱在交节边界时尤其要复核。',
10026
+ },
8533
10027
  {
8534
10028
  id: 'liuyao-yingqi-exact',
8535
10029
  system: 'liuyao', dims: [],
8536
10030
  gist: '具体应期(哪一天应验)本插件尚未推算。',
8537
10031
  detail: '应期需要综合用神旺衰、动变、旬空出空、日月冲合多层条件,且各家取法不同。当前只给时间窗口的方向性提示,不给具体日期——与其给一个看起来精确、实则拍脑袋的日子,不如留白。',
8538
- source: '待补:《卜筮正宗》应期章逐条实现 + 138 条卦例对拍',
10032
+ source: '待补:《卜筮正宗》应期章逐条实现 + 已录 128 条卦例对拍(源 138 条)',
8539
10033
  status: 'todo',
8540
10034
  note: '扩展方向:用《卜筮正宗》卦例里有明确应验日期的条目做回归,先验证再输出。',
8541
10035
  },
8542
- {
8543
- id: 'bazi-dayun-jiaoqi',
8544
- system: 'bazi', dims: [],
8545
- gist: '起运时间按「三日折一年」折算,未做交运日的时辰级校正。',
8546
- detail: '传统折算为三天折一年、一天折四月、一时辰折十天。本插件按此折算到年月,不含交运当日的时辰细分,因此起运年份在边界处可能相差一年内。',
8547
- source: '《渊海子平》起运法;交运日细算各家不一',
8548
- status: 'todo',
8549
- note: '扩展方向:补交运日的时辰级推算,并用已知案例核对。',
8550
- },
8551
10036
  ];
8552
10037
 
8553
10038
  function knowledgeFor(system, dimKey) {
@@ -8571,6 +10056,7 @@ __mods.interpret = (function () {
8571
10056
 
8572
10057
  const { HEX64, HEX_BY_CORE, TRIGRAM, YAO_POSITION, MOVING_NATURE, ZIWEI_STARS, WUXING_INDUSTRY, WUXING_ORGAN, TENGOD_STYLE, LIUREN_JIANG, TAROT_ELEMENT_FOCUS, DISCLAIMER } = __mods['interpret-data'];
8573
10058
  const { MUTAGEN } = __mods.ziwei;
10059
+ const { bodyLabels, yongshenOf } = __mods.liuyao;
8574
10060
 
8575
10061
  const DIMENSIONS = [
8576
10062
  { key: 'overall', label: '整体运势' },
@@ -8864,7 +10350,10 @@ function baziInterpret(r) {
8864
10350
  const why = (lk.reasons || []).slice(0, 2).join(',');
8865
10351
  return `${l.year} ${l.ganzhi}·${l.stemTenGod || ''}·${lk.tier || '平'}${why ? `(${why})` : ''}`;
8866
10352
  }).join(';');
8867
- return `当前大运:${pick(cur, 'ganzhi', '—')}(${curSpan})。后续两步:${nextTwo}。\n近五年流年:${years || '—'}。\n读法:大运管"这十年的大环境",流年管"今年的具体天气";两者打架时,以能否动到用神为准。`;
10353
+ const jiaoLine = r.dayun && r.dayun.jiao && r.dayun.jiao.at
10354
+ ? `起运交运时刻 ${r.dayun.jiao.at}(${r.dayun.jiao.frame};三日折一年,距${r.dayun.jiao.from.name} ${r.dayun.jiao.spanDays} 天)。每步大运在同月同日同时刻换,逐年日期见下方大运表「交运」列。\n`
10355
+ : '';
10356
+ return `${jiaoLine}当前大运:${pick(cur, 'ganzhi', '—')}(${curSpan})。后续两步:${nextTwo}。\n近五年流年:${years || '—'}。\n读法:大运管"这十年的大环境",流年管"今年的具体天气";两者打架时,以能否动到用神为准。`;
8868
10357
  })(),
8869
10358
  },
8870
10359
  {
@@ -8912,6 +10401,10 @@ function baziInterpret(r) {
8912
10401
  key: 'geju', label: '格局依据',
8913
10402
  text: (Array.isArray(frame.geju) ? frame.geju : []).map((g) => `${g.name}(${g.kind}):${g.evidence}`).join(';') || '未取到格局',
8914
10403
  },
10404
+ ...(Array.isArray(r.classics) && r.classics.length ? [{
10405
+ key: 'classics', label: '古籍原文',
10406
+ text: r.classics.map((e) => `《${CLASSIC_BOOKS[e.book].name}·${e.chapter}》「${e.quote}」——${e.plain}`).join('\n'),
10407
+ }] : []),
8915
10408
  {
8916
10409
  key: 'quant', label: '量化口径',
8917
10410
  text: `本报告的量化只到「档位」:事业看十神权重(${topName} ${top[0]?.[1] ?? '—'})、财富看财星权重(${groups['财'] ?? 0})、感情看配偶星权重(${spouseKey} ${groups[spouseKey] ?? 0});大运/流年用「大吉 / 吉 / 平 / 忌」四档。不换算金额、不给健康诊断——那需要地域、家庭、行业与体检数据,命盘本身推不出来。`,
@@ -8931,8 +10424,8 @@ function baziInterpret(r) {
8931
10424
  ];
8932
10425
  const tables = [
8933
10426
  {
8934
- key: 'dayun', title: '大运表', columns: ['大运', '年龄', '十神', '档位', '理由'],
8935
- rows: list.map((d) => [d.ganzhi, `${d.startAge}–${d.startAge + 9}`, d.stemTenGod || '', d.luck?.tier || '平', (d.luck?.reasons || []).join(',')]),
10427
+ key: 'dayun', title: '大运表', columns: ['大运', '年龄', '交运', '十神', '档位', '理由'],
10428
+ rows: list.map((d) => [d.ganzhi, `${d.startAge}–${d.startAge + 9}`, d.fromDate || '—', d.stemTenGod || '', d.luck?.tier || '平', (d.luck?.reasons || []).join(',')]),
8936
10429
  },
8937
10430
  {
8938
10431
  key: 'liunian', title: '流年表(近十年)', columns: ['年份', '干支', '十神', '档位', '与原局'],
@@ -9143,7 +10636,7 @@ function ziweiInterpret(r) {
9143
10636
  },
9144
10637
  {
9145
10638
  key: 'limits', label: '未纳入',
9146
- text: '本盘没有计算:飞星四化(宫干引发的连锁)、小限与大限的加权、流日流时的具体事项定位,以及双星组合的强弱细分。四化表取中州派常用口径,其他流派(如飞星派)结论会不同;这些不改变盘面事实,只影响轻重,所以只作方向参考。',
10639
+ text: '本盘没有计算:小限与大限的加权、流日流时的具体事项定位、双星组合的强弱细分,以及「三方四正一起飞」的复合飞化(这里只跟单一支化逐宫串连)。四化表取中州派常用口径,其他流派(如飞星派)结论会不同;这些不改变盘面事实,只影响轻重,所以只作方向参考。',
9147
10640
  },
9148
10641
  ];
9149
10642
  if (natalSihua.length) {
@@ -9160,6 +10653,38 @@ function ziweiInterpret(r) {
9160
10653
  });
9161
10654
  }
9162
10655
 
10656
+ {
10657
+ const fc = r.flyChains;
10658
+ const word = (p) => `${p}${String(p).endsWith('宫') ? '' : '宫'}`;
10659
+ if (fc) {
10660
+ const chainOf = (t) => {
10661
+ const ch = fc[t];
10662
+ if (!ch || !ch.steps.length) return `${t}:命宫这一干不化${t}星`;
10663
+ const hops = ch.steps
10664
+ .map((s) => `${word(s.from)}(${s.fromStem})化${t}${s.star}→${word(s.to)}(${s.toBranch})`)
10665
+ .join(';');
10666
+ return `${t}:${hops}${ch.closed ? `,绕回${word(ch.path[ch.path.length - 1])}成环` : ''}`;
10667
+ };
10668
+ const selfs = (r.fly || []).flatMap((p) => (p.flies || [])
10669
+ .filter((f) => f.self)
10670
+ .map((f) => `${word(p.palace)}(${p.stem})化${f.type}${f.star}落回本宫`));
10671
+ const ji = fc.忌;
10672
+ const jiRead = ji && ji.steps.length
10673
+ ? `忌这一条最值得盯:${word(ji.from)}化忌入${word(ji.steps[0].to)},${word(ji.steps[0].to)}的宫干再化忌往下一宫去,${ji.closed ? `最后绕回${word(ji.path[ji.path.length - 1])}` : '走到没有可落的星就停下'}——` +
10674
+ `这条线就是你反复着力的那一圈,` +
10675
+ `${ji.closed ? '环越短越容易在同一组宫位上打转(传统说「忌出不去」)' : '没成环表示这条力还有落点没走完'}。`
10676
+ : '命宫这一干化忌未上盘,忌线不成立。';
10677
+ sections.push({
10678
+ key: 'fly', label: '四化飞星',
10679
+ text: `生年四化说的是「一出场就带着什么」,飞星四化说的是「这一宫自己会去找谁」:宫干定出四颗化星,星落到哪一宫,这一宫的力就往哪边使。` +
10680
+ `从命宫出发,四条线各串一遍——${chainOf('禄')};${chainOf('权')};${chainOf('科')};${chainOf('忌')}。` +
10681
+ `${jiRead}` +
10682
+ `另外,四化落回本宫的是自化:${selfs.length ? selfs.join('、') + '——自己化出去又回到自己,常表现为「说得多、落得少」或一阵一阵的劲头' : '本盘没有自化'}。` +
10683
+ `这一层只描方向与牵引,不断吉凶:同样是化忌入某宫,落在生年忌上还是落在生年禄上,味道完全不同,得并着生年四化看。`,
10684
+ });
10685
+ }
10686
+ }
10687
+
9163
10688
  if (r.flow) {
9164
10689
  const f = r.flow;
9165
10690
  const at = (p) => `${p.ganzhi || '—'}${p.palace ? `落${p.palace}` : ''}`;
@@ -9261,7 +10786,7 @@ const LIUQIN_USE = {
9261
10786
  兄弟: '同事、同行、竞争者,多与破财相关',
9262
10787
  };
9263
10788
 
9264
- function liuyaoInterpret(r) {
10789
+ function liuyaoInterpret(r, ask) {
9265
10790
  const ben = isObj(r.ben) ? r.ben : {};
9266
10791
  const bian = isObj(r.bian) ? r.bian : {};
9267
10792
  const lines = Array.isArray(ben.lines) ? ben.lines : [];
@@ -9278,6 +10803,8 @@ function liuyaoInterpret(r) {
9278
10803
  const movingLiuqin = moving?.liuqin || '';
9279
10804
  const movingLiushen = moving?.liushen || '';
9280
10805
  const dayGZ = pick(r.dayGanZhi, 'name', '');
10806
+ const bd = isObj(r.body) ? r.body : {};
10807
+ const bodyLabelList = (bd && Array.isArray(bd.labels)) ? bd.labels : bodyLabels(bd);
9281
10808
  const cnt = (k) => lines.filter((l) => l.liuqin === k).length;
9282
10809
  const lineEv = (l) => (l ? `${l.position}爻${l.liuqin}${l.liushen ? '·' + l.liushen : ''}${l.moving ? '(动)' : ''}` : '—');
9283
10810
  const ev = (...xs) => xs.filter((x) => x != null && x !== '');
@@ -9299,9 +10826,82 @@ function liuyaoInterpret(r) {
9299
10826
  `⑥ 看变卦:${bian.name ? `变出「${bian.name}」,是事情后续的走向` : '本卦无变,以静卦论'}。以上每一步都能在下方「依据」里对上号。`,
9300
10827
  ].join('\n'),
9301
10828
  });
10829
+ if (bodyLabelList.length) {
10830
+ const sideOf = (o) => [o && o.inner ? '内卦' : '', o && o.outer ? '外卦' : ''].filter(Boolean).join('、');
10831
+ const fanYinSides = sideOf(bd.fanYin || {});
10832
+ const fanYinBy = ['inner', 'outer'].map((k) => bd.fanYin && bd.fanYin[k] && bd.fanYin[k].by).filter(Boolean).flat();
10833
+ const bodyText = [];
10834
+ if (bd.ben && bd.ben.chong) bodyText.push('本卦是六冲卦:六爻地支正好配成子午、寅申、辰戌三组对冲,传统读作「散」——事情起落快、成得急也散得急,急事与近病反而喜欢这股冲开的力量,拖了很久的人事则不容易凝聚。');
10835
+ if (bd.ben && bd.ben.he) bodyText.push('本卦是六合卦:六爻地支两两相合(子丑、寅亥、卯戌、辰酉、巳申、午未),传统读作「成」——容易谈拢,也容易拖住不放。');
10836
+ if (bd.bian && bd.bian.chong) bodyText.push('变卦也是六冲:往后走偏「散」,收尾容易一拍两散,也可能是速战速决。');
10837
+ else if (bd.bian && bd.bian.he) bodyText.push('变卦是六合:往后走偏「合」,收尾倾向于合拢、或者就这么延续下去。');
10838
+ if (sideOf(bd.fuYin || {})) bodyText.push(`${sideOf(bd.fuYin || {})}伏吟:卦变了,纳甲地支却没变(六十四卦里只有乾与震共用子寅辰/午申戌,所以卦伏吟只会出在乾↔震互换)——传统说「伏吟主呻吟」,人想动、事情在原地打转,宜先看清楚再迈步。`);
10839
+ if (fanYinSides) bodyText.push(`${fanYinSides}反吟:变卦与本卦相冲,来源有两种——${fanYinBy.includes('chong') ? '地支三爻全冲' : ''}${fanYinBy.includes('chong') && fanYinBy.includes('pair') ? ',或' : ''}${fanYinBy.includes('pair') ? '本卦之宫与变卦之宫在后天八卦上正对(乾巽、坎离、艮坤、震兑)' : ''}。传统说「反吟主反复」,去而复来、方案改来改去。`);
10840
+ if (bd.yaoFanYin && bd.yaoFanYin.length) bodyText.push(`动爻反吟(第${bd.yaoFanYin.join('、')}爻):变出的地支回头冲本爻,变化点本身在反复——动了又翻回来,等于绕一圈回到原处。`);
10841
+ if (bd.yaoFuYin && bd.yaoFuYin.length) bodyText.push(`动爻伏吟(第${bd.yaoFuYin.join('、')}爻):变出的地支与本爻同支,动了等于没动,想推的事会卡在同一道坎上。`);
10842
+ const jinYao = (bd.jinTui || []).filter((x) => x.type === '进');
10843
+ const tuiYao = (bd.jinTui || []).filter((x) => x.type === '退');
10844
+ if (jinYao.length) bodyText.push(`化进神(第${jinYao.map((x) => x.position).join('、')}爻:${jinYao.map((x) => `${x.from}→${x.to}`).join('、')}):变出的地支与本爻同五行、且顺着往前一步,传统读作「进」——这一爻代表的人或事有后劲,往前走比退回去顺。`);
10845
+ if (tuiYao.length) bodyText.push(`化退神(第${tuiYao.map((x) => x.position).join('、')}爻:${tuiYao.map((x) => `${x.from}→${x.to}`).join('、')}):变出的地支与本爻同五行、却倒着退一步,传统读作「退」——这一爻代表的人或事渐渐退气,别按原来的势头去推。`);
10846
+ bodyText.push('这些标签只说明卦体的动势,不直接定吉凶:同一支六冲,近病问的是速愈、久病问的是凶险;同一爻化进,也得看用神旺不旺、有没有月建日辰托着。');
10847
+ sections.push({ key: 'body', label: '卦体关系', text: bodyText.join('\n') });
10848
+ }
10849
+
10850
+ const ug = yongshenOf(r, {
10851
+ direction: (ask && ask.direction) || (r.useGod && r.useGod.direction) || 'general',
10852
+ gender: (r.useGod && r.useGod.gender) || 'male',
10853
+ });
10854
+ if (ug && ug.liuqin) {
10855
+ const useText = [];
10856
+ useText.push(`所问方向取${ug.byShi ? '世爻' : ug.liuqin}为用神(${ug.ask})${ug.direction === 'love' ? `;感情按${ug.gender === 'female' ? '女命看官鬼' : '男命看妻财'}取,性别未给时按男命算` : ''}。`);
10857
+ if (ug.found.length) {
10858
+ useText.push(`用神在卦上:${ug.found.map((f) => `${f.position}爻${f.ganzhi}${f.element}${f.liuqin}${f.liushen ? `·${f.liushen}` : ''}${f.shi ? '(持世)' : ''}${f.ying ? '(临应)' : ''}${f.moving ? '(发动)' : ''}${f.kong ? '(旬空)' : ''}`).join('、')}${ug.side ? `,落在${ug.side}` : ''}。`);
10859
+ if (ug.found.length > 1) useText.push('用神两现:传统取动者、取持世者、取旬空待填者,各派权重不同;这里只把几处都列出来,不替你选哪一爻当主用。');
10860
+ } else if (ug.fushen.length) {
10861
+ useText.push(`用神不上卦:本卦六爻里没有${ug.liuqin},按「用神不上卦则于本宫首卦内寻之」,到${String(pick(ben, 'palace', '')).replace(/宫$/, '')}宫首卦取伏神——${ug.fushen.map((f) => `${f.position}爻${f.ganzhi}${f.element},伏于飞神${f.fei ? `${f.fei.liuqin}${f.fei.ganzhi}${f.fei.element}` : '—'}之下`).join(';')}。`);
10862
+ if (ug.relationSay.length) useText.push(`飞伏生克:${ug.relationSay.join(';')}。伏神要得出才算有力,藏在忌神之下且被克,则虽有如无。`);
10863
+ } else {
10864
+ useText.push(`用神不上卦,且本宫首卦里也没找到${ug.liuqin},这一问在卦面上没有直接对应物,只能借日月或改日再占。`);
10865
+ }
10866
+
10867
+ const link = [];
10868
+ if (bd.ben && bd.ben.chong) link.push('用神在六冲卦里:六冲主散,急事与近病借这股冲开的力量,缓事、久拖的人事则不容易凝聚');
10869
+ if (bd.ben && bd.ben.he) link.push('用神在六合卦里:六合主成,容易谈拢,也容易黏住不放');
10870
+ if (bd.bian && bd.bian.chong) link.push('用神逢变六冲:往后走向偏散,收尾容易一拍两散,也可能是速战速决');
10871
+ else if (bd.bian && bd.bian.he) link.push('用神逢变六合:往后走向偏合,倾向于合拢或就这么延续');
10872
+ for (const side of ['inner', 'outer']) {
10873
+ const nm = side === 'inner' ? '内卦' : '外卦';
10874
+ if (bd.fuYin && bd.fuYin[side]) link.push(`用神在${nm}伏吟:动而地支未变,想推的事卡在原地`);
10875
+ if (bd.fanYin && bd.fanYin[side]) link.push(`用神在${nm}反吟:变卦与本卦相冲,用神这一头去而复来、定了又改`);
10876
+ }
10877
+ const jinYong = (bd.jinTui || []).filter((x) => x.type === '进' && ug.found.some((f) => f.position === x.position));
10878
+ const tuiYong = (bd.jinTui || []).filter((x) => x.type === '退' && ug.found.some((f) => f.position === x.position));
10879
+ if (jinYong.length) link.push(`用神化进神(${jinYong.map((x) => `${x.from}→${x.to}`).join('、')}):这一爻有后劲,顺着推比退回去顺`);
10880
+ if (tuiYong.length) link.push(`用神化退神(${tuiYong.map((x) => `${x.from}→${x.to}`).join('、')}):这一爻渐渐退气,别按原来的势头硬推`);
10881
+ if (ug.found.some((f) => f.kong)) link.push('用神旬空:事情先悬着,传统说空要出空、填实才算落地');
10882
+ if (ug.found.some((f) => (bd.yaoFanYin || []).includes(f.position))) link.push('用神这一爻动而反吟:变化点本身在反复,动了又翻回来');
10883
+ if (ug.found.some((f) => (bd.yaoFuYin || []).includes(f.position))) link.push('用神这一爻动而伏吟:动了等于没动,容易卡在同一道坎上');
10884
+ if (link.length) useText.push(`与卦体对起来看:${link.join(';')}。`);
10885
+ const strength = r.useGodStrength;
10886
+ if (strength && strength.lines && strength.lines.length) {
10887
+ const head = strength.month && strength.month.branch
10888
+ ? `月建 ${strength.month.branch}(${strength.month.element})`
10889
+ : '没有月建(未给起卦时间),只按日辰看';
10890
+ const body = strength.lines.map((l) => {
10891
+ const chong = l.monthChong ? ',遭月建冲(月破)'
10892
+ : l.dayChong ? (l.kong ? ',日辰冲空' : l.moving ? ',逢日辰冲' : ',日辰冲而暗动') : '';
10893
+ return `${l.position}爻${l.branch}${l.element}:月建${l.monthState}(${l.monthSay}),${l.dayRelation}(${l.daySay})${chong}`;
10894
+ });
10895
+ useText.push(`时令对用神:${head},日辰 ${strength.day ? `${strength.day.branch}(${strength.day.element})` : '—'}。${body.join(';')}。`);
10896
+ useText.push('月建看这一段时令站在哪一边,日辰看当天那股力量是扶还是克;两处同时看,只报状态,不折成分数。');
10897
+ }
10898
+ useText.push('这一层只把用神、卦体与时令对起来,应期与成败还要看动爻变化与月建日辰的进退节奏,所以这里不折成分数、不断成败。');
10899
+ sections.push({ key: 'yongshen', label: '用神落位与卦体', text: useText.join('\n') });
10900
+ }
10901
+
9302
10902
  sections.push({
9303
10903
  key: 'limits', label: '未纳入',
9304
- text: '本卦没有计算:月建对爻的旺衰(只用了日辰)、伏神与飞神的完整生克链、进神退神与反吟伏吟的细节,以及用神多重时的取舍权重。这些会改变"旺不旺"的判断,所以这里只给取象与方向。',
10904
+ text: '本卦没有计算:全卦逐爻的月建旺衰(现在只算到用神那几爻)、化进化退叠上月建日辰之后的实际力重、用神两现时该取哪一爻的权重、伏神出伏的应期。这些会改变"旺不旺"的判断,所以这里只给取象与方向。',
9305
10905
  });
9306
10906
 
9307
10907
  const dimensions = [
@@ -9315,6 +10915,7 @@ function liuyaoInterpret(r) {
9315
10915
  moving ? `动爻 ${lineEv(moving)}` : '无动爻',
9316
10916
  `日辰 ${dayGZ || '—'}`,
9317
10917
  `卦宫 ${pick(ben, 'palace', '—')}·${palaceEl || '—'}`,
10918
+ bodyLabelList.length ? `卦体 ${bodyLabelList.join('、')}` : '卦体 无六冲六合伏吟反吟',
9318
10919
  ),
9319
10920
  },
9320
10921
  {
@@ -9703,6 +11304,7 @@ const BUILDERS = {
9703
11304
  const { interpretQimen } = __mods['qimen-interpret'];
9704
11305
  const { buildAnswer, buildPlain, buildGlossary, buildFollowups, dimensionPlain } = __mods['interpret-plain'];
9705
11306
  const { knowledgeFor, caveatsFor, STATUS_LABEL } = __mods['interpret-knowledge'];
11307
+ const { CLASSIC_BOOKS } = __mods.classics;
9706
11308
 
9707
11309
  function enrichPlain(result, r, ask) {
9708
11310
  if (!result) return result;
@@ -9798,7 +11400,7 @@ function interpret(r, opts) {
9798
11400
  if (!fn) return null;
9799
11401
  let body;
9800
11402
  try {
9801
- body = fn(r);
11403
+ body = fn(r, ask);
9802
11404
  } catch (e) {
9803
11405
  return {
9804
11406
  moduleType: MODULE_TYPE[r.system] || 'natal',
@@ -10280,14 +11882,14 @@ return { matchEvent, rectifyBirth, RECTIFY_KINDS };
10280
11882
 
10281
11883
  __mods.index = (function () {
10282
11884
  const { buildBazi } = __mods.bazi;
10283
- const { buildLiuyao, tossLines } = __mods.liuyao;
11885
+ const { buildLiuyao, tossLines, bodyLabels, yongshenOf, yongshenStrength } = __mods.liuyao;
10284
11886
  const { buildZiwei, BRIGHTNESS, flowOf, asOfParts } = __mods.ziwei;
10285
11887
  const { drawTarot } = __mods.tarot;
10286
11888
  const { buildMeihua } = __mods.meihua;
10287
11889
  const { buildDaliuren } = __mods.daliuren;
10288
11890
  const { buildHepan } = __mods.hepan;
10289
11891
  const { buildZiweiHepan } = __mods['ziwei-hepan'];
10290
- const { buildQimen } = __mods.qimen;
11892
+ const { buildQimen, buildSnapshot } = __mods.qimen;
10291
11893
  const { geocodeAddress, resolveAddress } = __mods.geo;
10292
11894
  const { toJDN, ganzhiName, dayGanzhiIndex } = __mods.calendar;
10293
11895
  const { randomOf, isSeed } = __mods.random;
@@ -10302,7 +11904,9 @@ const { buildChenggu } = __mods.chenggu;
10302
11904
  const { buildXiaoliuren } = __mods.xiaoliuren;
10303
11905
  const { buildStar } = __mods.star;
10304
11906
  const { buildNumerology } = __mods.numerology;
11907
+ const { buildMbti, mbtiPaper } = __mods.mbti;
10305
11908
  const { buildConvert } = __mods.convert;
11909
+ const { classicsFor, classicsText } = __mods.classics;
10306
11910
  const { buildTrend } = __mods.trend;
10307
11911
  const { buildFengshui } = __mods.fengshui;
10308
11912
  const { buildName } = __mods.name;
@@ -10367,6 +11971,9 @@ function baziText(r) {
10367
11971
  : '';
10368
11972
  const dayun = r.dayun.list.slice(0, 8)
10369
11973
  .map((d) => `${d.startAge}岁 ${d.ganzhi}(${d.stemTenGod})${d.luck ? `·${d.luck.tier}` : ''}${d.relations && d.relations.length ? `[${d.relations.join('')}]` : ''}`).join(' ');
11974
+ const jiao = r.dayun.jiao && r.dayun.jiao.at
11975
+ ? `;交运 ${r.dayun.jiao.at}(${r.dayun.jiao.frame},距${r.dayun.jiao.from.name} ${r.dayun.jiao.spanDays} 天,三日折一年)`
11976
+ : '';
10370
11977
  const near = r.jieqi.nearBoundaryMinutes < 20
10371
11978
  ? `\n⚠ 出生时刻距「节」仅 ${r.jieqi.nearBoundaryMinutes} 分钟,而节气时刻本身有最多约 14 分钟的偏差,月柱与年柱建议人工复核。`
10372
11979
  : '';
@@ -10397,7 +12004,7 @@ function baziText(r) {
10397
12004
  shenshaLine,
10398
12005
  `真太阳时:${r.solarTime.trueSolarText}(${r.input.geo ? r.input.geo.note + ';' : ''}${r.solarTime.note}) 晚子时:${r.input.lateZi ? '23:00 后算次日' : '23:00 后仍算当日'}`,
10399
12006
  `节气:上一节 ${r.jieqi.previous.name} ${r.jieqi.previous.time} 下一节 ${r.jieqi.next.name} ${r.jieqi.next.time}`,
10400
- `大运:${r.dayun.rule}(${r.dayun.direction}),${r.dayun.startAge.years}岁${r.dayun.startAge.months}个月起运`,
12007
+ `大运:${r.dayun.rule}(${r.dayun.direction}),${r.dayun.startAge.years}岁${r.dayun.startAge.months}个月起运${jiao}`,
10401
12008
  ` ${dayun}`,
10402
12009
  `流年:${r.liunian.slice(0, 10).map((l) => `${l.year}${l.ganzhi}(${l.stemTenGod})${l.luck ? `·${l.luck.tier}` : ''}`).join(' ')}`,
10403
12010
  '(格局、用神、行运吉凶为扶抑+简化调候的量化提示,非断语。)',
@@ -10426,6 +12033,17 @@ function ziweiText(r) {
10426
12033
  : '无明显格局(按内置规则)';
10427
12034
  const daxian = (r.daxianList ?? []).slice(0, 8).map((d) =>
10428
12035
  ` ${d.branch}宫(${d.palace})${d.ages[0]}-${d.ages[1]}岁 ${d.ganzhi} 会${d.mainStars.join('') || '—'} 化 ${d.mutagenHits.map((m) => `${m.star}${m.type}→${m.palace || '未上盘'}`).join('、')}`);
12036
+ const flyLine = (() => {
12037
+ const fc = r.flyChains;
12038
+ if (!fc) return '';
12039
+ const one = (t) => {
12040
+ const ch = fc[t];
12041
+ if (!ch || !ch.steps.length) return '';
12042
+ return `${t}:${ch.path.join('→')}${ch.closed ? '(成环)' : ''}`;
12043
+ };
12044
+ const text = ['禄', '权', '科', '忌'].map(one).filter(Boolean).join(' ');
12045
+ return text ? `【命宫飞星】${text}` : '';
12046
+ })();
10429
12047
  return [
10430
12048
  `【农历】${lunarText}(月首 ${r.lunar.monthStart}) 时支:${r.hourBranch} 年干支:${r.yearGanzhi}`,
10431
12049
  `【时刻】${r.solarTime ? `${r.solarTime.civilText} → 真太阳时 ${r.solarTime.trueSolarText}${r.input.geo ? '(' + r.input.geo.note + ')' : ''}` : ''}`,
@@ -10434,6 +12052,7 @@ function ziweiText(r) {
10434
12052
  `【大限】${r.daxian.rule},${r.daxian.startAge}岁起 【小限】${r.xiaoxianRule.direction}`,
10435
12053
  `【昌曲】文昌${r.changqu.wenchang.branch}(${r.changqu.wenchang.palace}${r.changqu.wenchang.mutagen ? '·化' + r.changqu.wenchang.mutagen : ''}) 文曲${r.changqu.wenqu.branch}(${r.changqu.wenqu.palace}${r.changqu.wenqu.mutagen ? '·化' + r.changqu.wenqu.mutagen : ''})`,
10436
12054
  `   ${r.changqu.relation} 入命三方:${r.changqu.inSoulSanfang ? '是' : '否'} 吸收面:${r.changqu.absorb.label}(${r.changqu.absorb.reason})`,
12055
+ flyLine,
10437
12056
  '十二宫:',
10438
12057
  ...rows,
10439
12058
  '大限(按起运年龄排序,前 8 限,宫干四化落宫):',
@@ -10446,6 +12065,16 @@ function ziweiText(r) {
10446
12065
  ].filter(Boolean).join('\n');
10447
12066
  }
10448
12067
 
12068
+ const STATE_ORDER = ['旺', '相', '休', '囚', '死'];
12069
+ function stateLine(r) {
12070
+ const st = r.states;
12071
+ const mo = r.month;
12072
+ if (!st || !mo) return '无月令数据';
12073
+ const fmt = (g) => STATE_ORDER.filter((k) => g && Array.isArray(g[k]) && g[k].length)
12074
+ .map((k) => `${k} ${g[k].join('/')}`).join(' ');
12075
+ return `月令 ${mo.branch}(${mo.element}) 门 ${fmt(st.gate) || '—'} 星 ${fmt(st.star) || '—'} 干 ${fmt(st.stem) || '—'}`;
12076
+ }
12077
+
10449
12078
  function qimenText(r) {
10450
12079
  const P = r.palaces;
10451
12080
  const cell = (x) => `${x.palace}宫 ${x.star || '—'}/${x.gate || '—'}/${x.god || '—'}${x.kong ? ' 空' : ''}${x.ma ? ' 马' : ''}`;
@@ -10454,6 +12083,8 @@ function qimenText(r) {
10454
12083
  `【局数】${r.ju} 节气 ${r.jieqi}(${r.jieqiTime})`,
10455
12084
  `【值符】${r.zhiFu.star}(${r.zhiFu.gong}宫) 【值使】${r.zhiShi.gate}(${r.zhiShi.gong}宫) 旬首 ${r.xunshou}`,
10456
12085
  `【空亡】${r.kongWang.join('') || '—'} 驿马 ${r.ma.zhi || '—'}(${r.ma.gong || '—'}宫)`,
12086
+ `【宫位条件】击刑 ${(r.conditions && r.conditions.jiXing || []).map((x) => `${x.palace}宫${x.stem}`).join('、') || '无'} 入墓 ${(r.conditions && r.conditions.ruMu || []).map((x) => `${x.palace}宫${x.stem}`).join('、') || '无'}`,
12087
+ `【旺衰】${stateLine(r)}`,
10457
12088
  `【九宫】${P.map(cell).join(' ')}`,
10458
12089
  `※ 晚子时:${r.lateZiHourMode === 'next-day' ? '23:00 后归次日' : '23:00 后仍归当日'}(可用 lateZiHourMode / lateZi 指定)`,
10459
12090
  `※ ${r.rules[r.rules.length - 1]}`,
@@ -10555,6 +12186,25 @@ function liuyaoText(r) {
10555
12186
  ];
10556
12187
  if (r.bian) out.push(`动爻:第 ${r.cast.movingPositions.join('、')} 爻`, '变卦(六亲以变卦自身宫论):', fmt(r.bian));
10557
12188
  else out.push('六爻安静,无变卦。');
12189
+ const body = (r.body && Array.isArray(r.body.labels)) ? r.body.labels : bodyLabels(r.body);
12190
+ out.push(`卦体:${body.length ? body.join(' · ') : '无六冲、六合、伏吟、反吟'}`);
12191
+ const ug = r.useGod || yongshenOf(r, {});
12192
+ if (ug && ug.liuqin) {
12193
+ const where = ug.found.length
12194
+ ? ug.found.map((f) => `${f.position}爻${f.ganzhi}${f.element}${f.kong ? '(空)' : ''}`).join('、')
12195
+ : (ug.fushen.length ? ug.fushen.map((f) => `伏 ${f.position}爻${f.ganzhi}${f.element}(${f.relation || '飞伏关系未知'})`).join('、') : '不上卦');
12196
+ out.push(`用神:${ug.byShi ? '世爻' : ug.liuqin}(${ug.ask})→ ${where}${ug.side ? `(${ug.side})` : ''}`);
12197
+ const head = ug.byShi ? '用神 世爻' : `用神 ${ug.liuqin}`;
12198
+ const extra = ug.labels.filter((x) => x !== head && !x.startsWith(head));
12199
+ if (extra.length) out.push(`用神联动:${extra.join(' · ')}`);
12200
+ const strength = r.useGodStrength;
12201
+ if (strength && strength.labels.length) {
12202
+ const m = strength.month && strength.month.branch ? `月建 ${strength.month.branch}(${strength.month.element}) ` : '';
12203
+ out.push(`用神旺衰:${m}${strength.labels.join(' · ')}`);
12204
+ }
12205
+ } else {
12206
+ out.push('用神:未取(未指明所问之事;用「询问方向」指定后即按六亲取用)');
12207
+ }
10558
12208
  return out.filter(Boolean).join('\n');
10559
12209
  }
10560
12210
 
@@ -10564,6 +12214,11 @@ function jsonSafe(value) {
10564
12214
 
10565
12215
  function compute(args = {}) {
10566
12216
  const r = jsonSafe(computeRaw(args));
12217
+ if (r && r.system === 'bazi') {
12218
+ r.classics = classicsFor(r);
12219
+ const ct = classicsText(r.classics);
12220
+ if (ct) r.summary = `${r.summary}\n【古籍】\n${ct}`;
12221
+ }
10567
12222
  if (r && r.precision && r.precision.level === 'approx' && r.summary) {
10568
12223
  r.summary = `⚠ ${r.precision.note}\n${r.summary}`;
10569
12224
  }
@@ -10829,6 +12484,21 @@ function computeRaw(args = {}) {
10829
12484
  } catch (e) {
10830
12485
  throw fail('E_LINES', e && e.message ? e.message : e);
10831
12486
  }
12487
+ const ug = yongshenOf(result, { direction: args.direction, gender: args.gender });
12488
+ if (ug.liuqin) {
12489
+ result.useGod = ug;
12490
+ let monthBranch = null;
12491
+ if (stamp) {
12492
+ try {
12493
+ const snap = buildSnapshot({ year: stamp.year, month: stamp.month, day: stamp.day, hour: stamp.hour, minute: stamp.minute });
12494
+ monthBranch = (snap.monthGanZhi || '')[1] || null;
12495
+ } catch {
12496
+ monthBranch = null;
12497
+ }
12498
+ }
12499
+ const strength = yongshenStrength(result, ug, { monthBranch, dayBranch: result.dayGanZhi.branch });
12500
+ if (strength) result.useGodStrength = strength;
12501
+ }
10832
12502
  if (args.seed !== undefined && args.seed !== null && args.seed !== '') result.seed = Number(args.seed);
10833
12503
  return { ...result, ...(asOf ? { asOf } : {}), summary: liuyaoText(result) };
10834
12504
  }
@@ -10902,6 +12572,14 @@ function computeRaw(args = {}) {
10902
12572
  const result = buildNumerology({ year: dt.year, month: dt.month, day: dt.day });
10903
12573
  return { ...result, summary: numerologyText(result) };
10904
12574
  }
12575
+ if (system === 'mbti') {
12576
+ if (args.answers === undefined || args.answers === null) {
12577
+ const paper = mbtiPaper();
12578
+ return { ...paper, summary: mbtiPaperText(paper) };
12579
+ }
12580
+ const result = buildMbti({ answers: args.answers });
12581
+ return { ...result, summary: mbtiText(result) };
12582
+ }
10905
12583
  if (system === 'trend') {
10906
12584
  const dt = parseDateTime(args.datetime);
10907
12585
  const result = buildTrend({
@@ -10971,9 +12649,9 @@ function computeRaw(args = {}) {
10971
12649
  return { ...result, summary: nameText(result) };
10972
12650
  }
10973
12651
  if (!system) {
10974
- throw fail('E_INPUT', '缺少 system,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、trend(人生 K 线)、fengshui(风水)、name(起名)、convert(公农历互转)。');
12652
+ throw fail('E_INPUT', '缺少 system,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、mbti(十六型人格问卷)、trend(人生 K 线)、fengshui(风水)、name(起名)、convert(公农历互转)。');
10975
12653
  }
10976
- throw fail('E_SYSTEM', `未知体系「${system}」,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、trend(人生 K 线)、fengshui(风水)、name(起名)、convert(公农历互转)。`);
12654
+ throw fail('E_SYSTEM', `未知体系「${system}」,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)、almanac(黄历)、zeri(择日)、chenggu(称骨)、xiaoliuren(小六壬)、star(星座)、numerology(生命数字)、mbti(十六型人格问卷)、trend(人生 K 线)、fengshui(风水)、name(起名)、convert(公农历互转)。`);
10977
12655
  }
10978
12656
 
10979
12657
  function almanacText(r) {
@@ -11072,6 +12750,41 @@ function numerologyText(r) {
11072
12750
  ].join('\n');
11073
12751
  }
11074
12752
 
12753
+ function mbtiText(r) {
12754
+ const dims = r.dims.map((d) => {
12755
+ const side = d.picked === d.left ? d.leftSay : d.rightSay;
12756
+ const pct = Math.round(d.pct * 100);
12757
+ return `${d.left}/${d.right} 取 ${d.picked}(${d.strength} ${pct}%,净分 ${d.sum}) ${side}`;
12758
+ });
12759
+ const p = r.profile;
12760
+ const stack = Array.isArray(p.stack) && p.stack.length
12761
+ ? p.stack.map((f, i) => `${i === 0 ? '主导 ' : ''}${f.code}`).join(' → ') + `\n ${p.stack.map((f) => `${f.code}:${f.say}`).join('\n ')}`
12762
+ : '';
12763
+ return [
12764
+ `【十六型人格】${r.type}(${p.title})· ${p.keywords} 已答 ${r.answered}/${r.total} 题`,
12765
+ `一句:${p.oneLine}`,
12766
+ '【四维】',
12767
+ ...dims,
12768
+ `【优势】${p.strengths.join(';')}`,
12769
+ `【留意】${p.blindspots.join(';')}`,
12770
+ `【课题】${p.growth}`,
12771
+ `【认知功能】${stack}`,
12772
+ `【工作】${p.work}`,
12773
+ `【关系】${p.love}`,
12774
+ `【学习】${p.study}`,
12775
+ `注:${r.note}`,
12776
+ ].join('\n');
12777
+ }
12778
+
12779
+ function mbtiPaperText(paper) {
12780
+ return [
12781
+ `【十六型人格问卷】${paper.total} 题,每题按 1(完全不像我)到 5(很像我)打分`,
12782
+ ...paper.questions.map((q) => `${q.no}. ${q.text}`),
12783
+ `请把 ${paper.total} 个分数按顺序交给 answers(如 [4,2,5,3,…]);可以跳过某些题(填 null),至少答 12 题。`,
12784
+ `注:${paper.note}`,
12785
+ ].join('\n');
12786
+ }
12787
+
11075
12788
  function trendText(r) {
11076
12789
  const s = r.meta.strength;
11077
12790
  const dm = r.meta.dayMaster;
@@ -11720,6 +13433,16 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
11720
13433
  .mp-qt-y b.first{color:var(--ji);border-bottom:2px solid var(--ji);padding-bottom:1px}
11721
13434
  .mp-qt-q{margin-top:6px;font-size:12.5px;line-height:1.8;color:var(--ink2);font-family:var(--fx)}
11722
13435
  .mp-qt-q span{display:block;font-size:11px;color:var(--ink3);font-family:var(--sans);margin-top:2px}
13436
+ .mp-cl{margin-top:10px;padding-top:9px;border-top:1px dashed var(--line)}
13437
+ .mp-cl-h{display:flex;gap:8px;align-items:baseline;font-size:12.5px;margin-bottom:6px}
13438
+ .mp-cl-h b{color:var(--ink);letter-spacing:.02em}
13439
+ .mp-cl-h span{color:var(--ink3);font-size:11.5px}
13440
+ .mp-cl-e{padding:6px 0;border-top:1px dashed var(--hair)}
13441
+ .mp-cl-e:first-of-type{border-top:0;padding-top:0}
13442
+ .mp-cl-t b{font-family:var(--fx);font-size:13px;color:var(--lu);font-weight:600}
13443
+ .mp-cl-q{font-family:var(--fx);font-size:13.5px;line-height:1.95;color:var(--ink);margin-top:2px;letter-spacing:.02em}
13444
+ .mp-cl-p{font-size:12px;line-height:1.85;color:var(--ink2);margin-top:2px}
13445
+ @media print{.mp-cl{break-inside:avoid}}
11723
13446
  @media print{.mp-cli{break-inside:avoid}}
11724
13447
  .mp-nm{margin:12px 16px;border:1px solid var(--line);border-radius:8px;background:var(--card);padding:12px 14px;font-family:system-ui,sans-serif}
11725
13448
  .mp-nm-h{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;border-bottom:1px solid var(--hair);padding-bottom:8px;margin-bottom:10px}
@@ -11748,6 +13471,31 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
11748
13471
  .mp-nm-c em{font-style:normal;font-size:11.5px;line-height:1.7;color:var(--muted);font-family:var(--sans)}
11749
13472
  @media print{.mp-nm{break-inside:avoid}}
11750
13473
  .mp-quick-pre{margin:0;white-space:pre-wrap;word-break:break-word;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;line-height:1.9;color:var(--ink2)}
13474
+ .mp-mb{margin:12px 16px;border:1px solid var(--line);border-radius:8px;background:var(--card);padding:12px 14px;font-family:system-ui,sans-serif}
13475
+ .mp-mb-h{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;border-bottom:1px solid var(--hair);padding-bottom:8px;margin-bottom:9px}
13476
+ .mp-mb-h b{font-family:var(--fx);font-size:20px;letter-spacing:1px;color:var(--ink)}
13477
+ .mp-mb-h i{font-style:normal;font-size:12px;color:var(--muted)}
13478
+ .mp-mb-dims{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px 14px;margin-bottom:9px}
13479
+ .mp-mb-dim{display:grid;grid-template-columns:28px minmax(0,1fr) 28px;gap:6px;align-items:center;font-size:11.5px;color:var(--muted)}
13480
+ .mp-mb-dim b{font-size:13px;font-weight:600;color:var(--muted);font-family:var(--fx)}
13481
+ .mp-mb-dim b.on{color:var(--ink)}
13482
+ .mp-mb-bar{position:relative;height:10px;border-radius:5px;background:var(--hair);overflow:hidden}
13483
+ .mp-mb-bar em{position:absolute;top:0;bottom:0;display:block;background:var(--line)}
13484
+ .mp-mb-bar em.on{background:var(--good)}
13485
+ .mp-mb-dim i{font-style:normal;grid-column:1/-1;font-size:11px;color:var(--muted);line-height:1.6}
13486
+ .mp-mb-sec{margin:7px 0 0;font-size:12.5px;line-height:1.85;color:var(--ink2)}
13487
+ .mp-mb-sec em{font-style:normal;color:var(--lu);margin-right:6px}
13488
+ .mp-mb-two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-top:8px}
13489
+ .mp-mb-two>div{border:1px dashed var(--hair);border-radius:6px;padding:7px 9px;font-size:12.5px;line-height:1.8;color:var(--ink2)}
13490
+ .mp-mb-two b{display:block;font-size:11.5px;color:var(--lu);font-weight:600;margin-bottom:3px}
13491
+ .mp-mb-fn{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 0}
13492
+ .mp-mb-fn span{font-size:11.5px;border:1px solid var(--line);border-radius:999px;padding:1px 8px;color:var(--ink2)}
13493
+ .mp-mb-note{margin-top:9px;font-size:11px;line-height:1.7;color:var(--muted);border-top:1px solid var(--hair);padding-top:7px}
13494
+ .mp-mb-paper{margin:12px 16px;border:1px solid var(--line);border-radius:8px;background:var(--card);padding:12px 14px;font-family:system-ui,sans-serif}
13495
+ .mp-mb-paper ol{margin:6px 0 0;padding-left:20px;font-size:12.5px;line-height:1.9;color:var(--ink2)}
13496
+ .mp-mb-paper .mp-mb-note{margin-top:8px}
13497
+ @media (max-width:640px){.mp-mb-dims,.mp-mb-two{grid-template-columns:1fr}}
13498
+ @media print{.mp-mb{break-inside:avoid}}
11751
13499
  .mp-al{margin:12px 16px;border:1px solid var(--line);border-radius:8px;background:var(--card);padding:12px 14px;font-family:system-ui,sans-serif}
11752
13500
  .mp-al-h{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;border-bottom:1px solid var(--hair);padding-bottom:8px;margin-bottom:9px}
11753
13501
  .mp-al-h b{font-size:18px;color:var(--ink);font-weight:700;font-variant-numeric:tabular-nums}
@@ -12130,6 +13878,17 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
12130
13878
  ? `<div class="pp-chips">${r.daxianList.slice(0, 8).map((d) => `<span class="pp-chip" title="${esc(`${d.ganzhi} 宫干四化:` + d.mutagenHits.map((m) => `${m.star}化${m.type}→${m.palace || '未上盘'}`).join('、'))}">${d.branch} ${d.ages[0]}-${d.ages[1]}</span>`).join('')}</div>` : '';
12131
13879
  const lnHit = ln && ln.mutagenHits && ln.mutagenHits.length
12132
13880
  ? `<div class="pp-chips">${ln.mutagenHits.map((m) => `<span class="pp-chip ${m.type === '忌' ? 'warn' : m.type === '禄' ? 'ok' : ''}">${m.star}化${m.type}→${m.palace || '未上盘'}</span>`).join('')}</div>` : '';
13881
+ const flyHTML = r.flyChains ? (() => {
13882
+ const chips = ['禄', '权', '科', '忌'].map((t) => {
13883
+ const ch = r.flyChains[t];
13884
+ if (!ch || !ch.steps.length) return '';
13885
+ const path = ch.path.map((p) => String(p).replace(/宫$/, '')).join('→');
13886
+ const tip = ch.steps.map((s) => `${s.from}(${s.fromStem})化${t}${s.star}→${s.to}`).join(';');
13887
+ const cls = t === '忌' ? 'warn' : t === '禄' ? 'ok' : '';
13888
+ return `<span class="pp-chip ${cls}" title="${esc(tip)}">飞${t} ${esc(path)}${ch.closed ? '↺' : ''}</span>`;
13889
+ }).join('');
13890
+ return chips ? `<div class="pp-chips">${chips}</div>` : '';
13891
+ })() : '';
12133
13892
 
12134
13893
  const fl = r.flow;
12135
13894
  const flowHTML = fl ? (() => {
@@ -12147,7 +13906,6 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
12147
13906
  ${row(`流时 ${BR[fl.hour.hourIndex]}时`, fl.hour)}
12148
13907
  </div>`;
12149
13908
  })() : '';
12150
-
12151
13909
  pp._zw = r;
12152
13910
  return `<div class="pp">
12153
13911
  <input type="radio" name="ppm" id="ppm-ben" class="pp-r" checked>
@@ -12160,7 +13918,7 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
12160
13918
  ${body('ben', soulIdx, 'ben')}
12161
13919
  ${body('da', dxIdx, 'da')}
12162
13920
  ${body('ln', lnIdx, 'ln')}
12163
- </div>${flowHTML}${chips}${pats}${dxs}${lnHit}`;
13921
+ </div>${flowHTML}${chips}${flyHTML}${pats}${dxs}${lnHit}`;
12164
13922
  };
12165
13923
 
12166
13924
  function starLine(list) {
@@ -12319,9 +14077,35 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
12319
14077
  ? box(`变卦 ${r.bian.name}`, `${r.bian.palace} · ${r.bian.palaceElement}`, r.bian, r.bian.lines)
12320
14078
  : `<div class="mp-hy-box"><div class="mp-hy-h"><b>六爻安静</b><span>无动爻,不取变卦</span></div>
12321
14079
  <div class="pp-dim" style="font-size:12px;line-height:1.9">断卦以本卦卦辞与世应生克为主。</div></div>`;
14080
+ const bodyLabels = (r.body && Array.isArray(r.body.labels)) ? r.body.labels : [];
14081
+ const bodyRow = bodyLabels.length
14082
+ ? `<div class="pp-chips">${bodyLabels.map((l) => {
14083
+ const cls = /伏吟/.test(l) ? 'warn' : /反吟/.test(l) ? 'warn' : /化退神/.test(l) ? 'warn' : /六冲|散/.test(l) ? '' : 'ok';
14084
+ return `<span class="pp-chip ${cls}">${esc(l)}</span>`;
14085
+ }).join('')}</div>`
14086
+ : `<div class="pp-chips"><span class="pp-chip">卦体无六冲、六合、伏吟、反吟</span></div>`;
14087
+ const ug = r.useGod && r.useGod.liuqin ? r.useGod : null;
14088
+ const useRow = ug
14089
+ ? `<div class="pp-chips">${ug.labels.map((l) => {
14090
+ const cls = /空亡|退神|受克|泄气|不上卦/.test(l) ? 'warn'
14091
+ : /持世|化进|六合|得飞神生/.test(l) ? 'ok' : '';
14092
+ return `<span class="pp-chip ${cls}">${esc(l)}</span>`;
14093
+ }).join('')}</div>`
14094
+ : `<div class="pp-chips"><span class="pp-chip">用神未取(未指明所问之事)</span></div>`;
14095
+ const st = r.useGodStrength && Array.isArray(r.useGodStrength.labels) ? r.useGodStrength : null;
14096
+ const strengthRow = st && st.labels.length
14097
+ ? `<div class="pp-chips">${st.labels.map((l) => {
14098
+ const cls = /死|囚|月破|受日辰克|泄|耗/.test(l) ? 'warn'
14099
+ : /旺|相|得日辰生|比和/.test(l) ? 'ok' : '';
14100
+ return `<span class="pp-chip ${cls}">${esc(l)}</span>`;
14101
+ }).join('')}</div>`
14102
+ : '';
12322
14103
  return `<div class="pp" style="padding:14px">
12323
14104
  <div class="mp-mh-head">起卦 ${r.cast.notation.join('、')}(自下而上) 日辰 ${r.dayGanZhi.name}</div>
12324
14105
  <div class="mp-hy-wrap">${ben}${bian}</div>
14106
+ ${bodyRow}
14107
+ ${useRow}
14108
+ ${strengthRow}
12325
14109
  </div>`;
12326
14110
  };
12327
14111
 
@@ -12487,6 +14271,12 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
12487
14271
  pp.qimenFactsHTML = function (r) {
12488
14272
  if (!r || !r.palaces) return '';
12489
14273
  const row = (k, v) => `<div class="relation__row"><dt>${esc(k)}</dt><dd>${esc(v)}</dd></div>`;
14274
+ const stateCell = (g) => {
14275
+ if (!g) return '—';
14276
+ const parts = ['旺', '相', '休', '囚', '死'].filter((k) => Array.isArray(g[k]) && g[k].length)
14277
+ .map((k) => `${k} ${g[k].join('、')}`);
14278
+ return parts.join(';') || '—';
14279
+ };
12490
14280
  const rows = [
12491
14281
  ['局数', `${r.ju}${r.yuan ? `(${r.yuan})` : ''}`],
12492
14282
  ['节气', `${r.jieqi || '—'}${r.jieqiTime ? `(${r.jieqiTime})` : ''}`],
@@ -12498,6 +14288,24 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
12498
14288
  ['驿马', r.ma && r.ma.zhi ? `${r.ma.zhi}${r.ma.gong ? `(${r.ma.gong}宫)` : ''}` : '—'],
12499
14289
  ['换局规则', r.juMethodNote || r.juMethod || '—'],
12500
14290
  ['晚子时', r.lateZiHourMode === 'next-day' ? '23:00 后归次日' : '23:00 后仍归当日'],
14291
+ ['击刑', (r.conditions && r.conditions.jiXing && r.conditions.jiXing.length)
14292
+ ? r.conditions.jiXing.map((x) => `${x.palace}宫${x.stem}`).join('、') : '本盘无'],
14293
+ ['入墓', (r.conditions && r.conditions.ruMu && r.conditions.ruMu.length)
14294
+ ? r.conditions.ruMu.map((x) => `${x.palace}宫${x.stem}`).join('、') : '本盘无'],
14295
+ ['月令', r.month && r.month.branch ? `${r.month.branch}(${r.month.element})` : '—'],
14296
+ ['门旺衰', stateCell(r.states && r.states.gate)],
14297
+ ['星旺衰', stateCell(r.states && r.states.star)],
14298
+ ['干旺衰', stateCell(r.states && r.states.stem)],
14299
+ ['干宫生克', (() => {
14300
+ const order = ['干克宫', '宫克干', '干生宫', '宫生干', '比和'];
14301
+ const g = {};
14302
+ for (const p of r.palaces || []) {
14303
+ const rel = p.stemRelation || null;
14304
+ if (!rel || rel.type === 'unknown' || !p.tianPan) continue;
14305
+ (g[rel.type] = g[rel.type] || []).push(p.tianPan);
14306
+ }
14307
+ return order.filter((k) => g[k]).map((k) => k + ' ' + g[k].join('、')).join(';') || '—';
14308
+ })()],
12501
14309
  ];
12502
14310
  return `<dl class="relation mp-qm-facts" aria-label="奇门盘面事实">${rows.map(([k, v]) => row(k, v)).join('')}</dl>`;
12503
14311
  };
@@ -12521,6 +14329,7 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
12521
14329
  <div class="pp-qm-mid"><span class="tp">${p.tianPan || ''}</span><span class="star">${p.star || ''}</span></div>
12522
14330
  <div class="pp-qm-mid"><span class="dp">${p.diPan || ''}</span><span class="gate">${p.gate || ''}</span></div>
12523
14331
  <div class="pp-qm-bot"><span>${p.anGan || ''}</span><span class="no">${g}</span></div>
14332
+ <div class="pp-qm-bot"><span class="dim">${(p.stemRelation && p.stemRelation.type !== 'unknown') ? p.stemRelation.type : ''}</span><span class="dim">${p.stemState || ''}</span></div>
12524
14333
  </div>`;
12525
14334
  };
12526
14335
  return `<div class="pp mp-qm">
@@ -12657,7 +14466,7 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
12657
14466
  const tip = `<div class="cv-tip">曲线只呈现引擎已给出的行运评分(${VMIN} ~ ${VMAX}),不预测具体事件;档位与依据见下方「大运 / 流年」各节。悬停看逐年详情。</div>`;
12658
14467
 
12659
14468
  return `<div class="mp-curve">
12660
- <div class="mp-curve-h"><b>人生运势曲线</b><span>${a0}–${a1} 岁 ${r.dayun.rule},${startAge} 岁起运</span></div>
14469
+ <div class="mp-curve-h"><b>人生运势曲线</b><span>${a0}–${a1} 岁 ${r.dayun.rule},${startAge} 岁起运${(r.dayun.jiao && r.dayun.jiao.at) ? `,交运 ${r.dayun.jiao.at}` : ''}</span></div>
12661
14470
  <svg viewBox="0 0 ${W} ${H}" role="img" aria-label="人生运势曲线:${a0} 到 ${a1} 岁的大运与流年评分走势">
12662
14471
  ${bands}${axes}${dyFill}${dyLine}${lnLine}${dots}${ticks.join('')}${yLabels}${mark}${markLo}
12663
14472
  </svg>
@@ -12981,7 +14790,7 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
12981
14790
 
12982
14791
  const dy = r.dayun.list.map((d) => {
12983
14792
  const lk = d.luck || null;
12984
- return `<span class="pp-chip ${lk ? tierCls(lk.tier) : ''}" title="${esc(lk ? `${lk.tier} ${lk.score}|${(lk.reasons || []).join(';')}` : '')}">${d.startAge} ${d.ganzhi}${(d.relations && d.relations.length) ? ` <i class="pp-drel">${d.relations.join('')}</i>` : ''}${lk ? ` ${lk.tier}` : ''}</span>`;
14793
+ return `<span class="pp-chip ${lk ? tierCls(lk.tier) : ''}" title="${esc(`${d.fromDate ? `${d.fromDate} 交运;` : ''}${lk ? `${lk.tier} ${lk.score}|${(lk.reasons || []).join(';')}` : ''}`)}">${d.startAge} ${d.ganzhi}${(d.relations && d.relations.length) ? ` <i class="pp-drel">${d.relations.join('')}</i>` : ''}${lk ? ` ${lk.tier}` : ''}</span>`;
12985
14794
  }).join('');
12986
14795
  const ln = (r.liunian && r.liunian.length) ? `<div class="pp-chips">${r.liunian.slice(0, 8).map((l) => {
12987
14796
  const lk = l.luck || null;
@@ -13003,6 +14812,14 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
13003
14812
  ${sect('格局', gj)}
13004
14813
  ${sect('用神', ys)}
13005
14814
  ${cliCard}
14815
+ ${(Array.isArray(r.classics) && r.classics.length) ? `<section class="mp-cl" aria-label="古籍原文">
14816
+ <div class="mp-cl-h"><b>古籍原文</b><span>照录通行本,释义为白话自撰</span></div>
14817
+ ${r.classics.map((e) => `<div class="mp-cl-e">
14818
+ <div class="mp-cl-t"><b>${esc((e.book === 'ziping' ? '子平真诠' : '滴天髓') + '·' + e.chapter)}</b></div>
14819
+ <div class="mp-cl-q">「${esc(e.quote)}」</div>
14820
+ <div class="mp-cl-p">${esc(e.plain)}</div>
14821
+ </div>`).join('')}
14822
+ </section>` : ''}
13006
14823
  ${sect('五行与十神', `<div class="pp-chips">${wx}</div>${gods}`)}
13007
14824
  ${sect('大运', `${pp.curveHTML(r)}<div class="pp-chips">${dy}</div>`)}
13008
14825
  ${ln ? sect('流年', ln) : ''}
@@ -13012,7 +14829,7 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
13012
14829
  <span class="pp-chip" title="${r.palaces.shengong.note}">身宫 ${r.palaces.shengong.ganzhi}</span>
13013
14830
  </div>`) : ''}
13014
14831
  ${ssx}
13015
- <div style="margin-top:8px;font-size:12px;color:var(--muted)">上一节 ${r.jieqi.previous.name} ${r.jieqi.previous.time} 下一节 ${r.jieqi.next.name} ${r.jieqi.next.time} ${r.dayun.rule},${r.dayun.startAge.years}岁起运</div>
14832
+ <div style="margin-top:8px;font-size:12px;color:var(--muted)">上一节 ${r.jieqi.previous.name} ${r.jieqi.previous.time} 下一节 ${r.jieqi.next.name} ${r.jieqi.next.time} ${r.dayun.rule},${r.dayun.startAge.years}岁起运${(r.dayun.jiao && r.dayun.jiao.at) ? `(交运 ${r.dayun.jiao.at},${r.dayun.jiao.frame})` : ''}</div>
13016
14833
  </div>`;
13017
14834
  };
13018
14835
 
@@ -13137,11 +14954,56 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
13137
14954
  </section>`;
13138
14955
  };
13139
14956
 
14957
+ pp.mbtiHTML = function (r) {
14958
+ if (!r) return '';
14959
+ if (r.needAnswers && Array.isArray(r.questions)) {
14960
+ const items = r.questions.map((q) => `<li>${esc(q.text)}</li>`).join('');
14961
+ return `<section class="mp-mb-paper" aria-label="十六型人格问卷">
14962
+ <div class="mp-mb-h"><b>十六型人格问卷</b><i>${r.total} 题 · 1 完全不像 / 5 很像我</i></div>
14963
+ <ol>${items}</ol>
14964
+ <div class="mp-mb-note">${esc(r.note || '')}</div>
14965
+ </section>`;
14966
+ }
14967
+ const dims = (r.dims || []).map((d) => {
14968
+ const pct = Math.round((d.pct || 0) * 100);
14969
+ const leftOn = d.picked === d.left;
14970
+ const width = Math.max(2, pct);
14971
+ const bar = leftOn
14972
+ ? `<em class="on" style="left:0;width:${width}%"></em>`
14973
+ : `<em class="on" style="right:0;width:${width}%"></em>`;
14974
+ const say = leftOn ? d.leftSay : d.rightSay;
14975
+ return `<div class="mp-mb-dim">
14976
+ <b class="${leftOn ? 'on' : ''}">${esc(d.left)}</b>
14977
+ <div class="mp-mb-bar">${bar}</div>
14978
+ <b class="${leftOn ? '' : 'on'}">${esc(d.right)}</b>
14979
+ <i>${esc(d.name)} · 取 ${esc(d.picked)}(${esc(d.strength)} ${pct}%)· ${esc(say)}</i>
14980
+ </div>`;
14981
+ }).join('');
14982
+ const p = r.profile || {};
14983
+ const stack = (p.stack || []).map((f) => `<span>${esc(f.code)} ${esc(f.say)}</span>`).join('');
14984
+ return `<section class="mp-mb" aria-label="十六型人格结果">
14985
+ <div class="mp-mb-h"><b>${esc(r.type || '')}</b><i>${esc(p.title || '')} · ${esc(p.keywords || '')} 已答 ${r.answered || 0}/${r.total || 0} 题</i></div>
14986
+ <div class="mp-mb-dims">${dims}</div>
14987
+ <div class="mp-mb-sec"><em>一句</em>${esc(p.oneLine || '')}</div>
14988
+ <div class="mp-mb-two">
14989
+ <div><b>优势</b>${(p.strengths || []).map((s) => esc(s)).join(';')}</div>
14990
+ <div><b>留意</b>${(p.blindspots || []).map((s) => esc(s)).join(';')}</div>
14991
+ </div>
14992
+ ${stack ? `<div class="mp-mb-sec"><em>认知功能</em>主导 ${esc((p.stack[0] || {}).code || '')} → 依次展开</div><div class="mp-mb-fn">${stack}</div>` : ''}
14993
+ <div class="mp-mb-sec"><em>工作</em>${esc(p.work || '')}</div>
14994
+ <div class="mp-mb-sec"><em>关系</em>${esc(p.love || '')}</div>
14995
+ <div class="mp-mb-sec"><em>学习</em>${esc(p.study || '')}</div>
14996
+ <div class="mp-mb-sec"><em>课题</em>${esc(p.growth || '')}</div>
14997
+ <div class="mp-mb-note">${esc(r.note || '')}</div>
14998
+ </section>`;
14999
+ };
15000
+
13140
15001
  pp.quickHTML = function (r) {
13141
15002
  if (!r) return '';
13142
15003
  const LABEL = {
13143
15004
  almanac: '黄历', zeri: '择日', chenggu: '称骨', xiaoliuren: '小六壬',
13144
15005
  star: '星座', numerology: '生命数字', trend: '人生 K 线', fengshui: '风水', name: '起名', convert: '公农历转换',
15006
+ mbti: '十六型人格',
13145
15007
  };
13146
15008
  const title = LABEL[r.system] || '速查';
13147
15009
  const text = r.summary || '';
@@ -13418,6 +15280,7 @@ const TABS = [
13418
15280
  { k: 'xiaoliuren', label: '小六壬', timeLabel: '起课时刻', hint: '小六壬用农历月/日/时掐指,也可用三个随机数起课。' },
13419
15281
  { k: 'star', label: '星座', timeLabel: '', hint: '星座按公历日期给出太阳星座与当日基调;同日同星座结果固定。' },
13420
15282
  { k: 'numerology', label: '生命数字', timeLabel: '', hint: '生命数字按公历生日归约出生命路径数、生日数与态度数。' },
15283
+ { k: 'mbti', label: '人格', timeLabel: '', hint: '十六型人格是一份 MBTI 风格问卷:24 题按 1–5 打分,四维度各取一侧;只作自我参照,不是官方量表。' },
13421
15284
  { k: 'trend', label: '人生K线', timeLabel: '出生时刻', hint: '人生 K 线按大运与流年十神给逐年运势分,画成走势图。' },
13422
15285
  { k: 'fengshui', label: '风水', timeLabel: '', hint: '风水看八宅命卦、大游年四吉四凶与流年九宫飞星;也可按坐向看门主灶倾向。' },
13423
15286
  { k: 'name', label: '起名', timeLabel: '', hint: '起名按八字喜用匹配字库五行,可评估现有名字或生成候选名。' },
@@ -13437,6 +15300,7 @@ const TIME_KIND = {
13437
15300
  almanac: 'date', zeri: 'date', star: 'date', numerology: 'date', convert: 'date',
13438
15301
  chenggu: 'birth', trend: 'birth', fengshui: 'birth', name: 'birth',
13439
15302
  xiaoliuren: 'cast',
15303
+ mbti: 'none',
13440
15304
  };
13441
15305
  const SAMPLE = { date: '1990-05-12', time: '14:30' };
13442
15306
  const kindOf = (t) => TIME_KIND[t] || 'none';
@@ -13474,6 +15338,7 @@ function App() {
13474
15338
  const [leapMonth, setLeapMonth] = useState(false);
13475
15339
  const [xlrMode, setXlrMode] = useState('time');
13476
15340
  const [num1, setNum1] = useState(''); const [num2, setNum2] = useState(''); const [num3, setNum3] = useState('');
15341
+ const [answers, setAnswers] = useState('');
13477
15342
  const [toAge, setToAge] = useState('60');
13478
15343
  const [fsMode, setFsMode] = useState('natal');
13479
15344
  const [sitting, setSitting] = useState('子');
@@ -13560,6 +15425,11 @@ function App() {
13560
15425
  args.hour = Number.isFinite(hh) ? hh : 12;
13561
15426
  }
13562
15427
  }
15428
+ if (k === 'mbti') {
15429
+ delete args.datetime;
15430
+ const nums = String(answers).split(/[^0-9]+/).filter((x) => x !== '').map((x) => Number(x));
15431
+ if (nums.length) args.answers = nums;
15432
+ }
13563
15433
  if (k === 'trend') { args.gender = genderV; args.toAge = Number(toAge) || 60; args.trueSolar = trueSolar; args.lateZi = lateZi; }
13564
15434
  if (k === 'fengshui') {
13565
15435
  args.action = fsMode;
@@ -14004,6 +15874,10 @@ function App() {
14004
15874
  className: 'mp-input', type: 'number', min: 1, max: 99, value: num3,
14005
15875
  onChange: (e) => setNum3(e.target.value),
14006
15876
  })) : null,
15877
+ tab === 'mbti' ? field('24 题分数(1–5,空格或逗号分隔;留空先发问卷)', h('textarea', {
15878
+ className: 'mp-textarea', rows: 2, value: answers, placeholder: '如 5 4 3 2 2 5 4 3 5 4 3 2 4 5 3 2 1 2 4 3 5 4 4 2',
15879
+ onChange: (e) => setAnswers(e.target.value),
15880
+ })) : null,
14007
15881
  tab === 'trend' ? field('算到几岁', h('input', {
14008
15882
  className: 'mp-input', type: 'number', min: 10, max: 100, value: toAge,
14009
15883
  onChange: (e) => setToAge(e.target.value),
@@ -14109,6 +15983,7 @@ function App() {
14109
15983
  : result.kind === 'almanac' ? PP.almanacHTML(result.r)
14110
15984
  : result.kind === 'trend' ? PP.trendHTML(result.r)
14111
15985
  : result.kind === 'name' ? PP.nameHTML(result.r)
15986
+ : result.kind === 'mbti' ? PP.mbtiHTML(result.r)
14112
15987
  : PP.quickHTML(result.r),
14113
15988
  result.it,
14114
15989
  { folded: defaultFolded() },