dsh-metaphysics 0.46.5 → 0.50.5

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.
@@ -14,17 +14,16 @@
14
14
  <meta name="twitter:card" content="summary_large_image">
15
15
  <style>
16
16
  :root{--bg:#f3eee4;--panel:#fffdf8;--panel2:#f8f3e9;--line:#d6cbb7;--ink:#1a1916;--ink2:#3f3a33;--dim:#6f6a60;--gold:#a72c23;--gold-deep:#7d1f18;--good:#1f6f3f;--bad:#a72c23;--warn:#8a5a00;
17
+ /* 强调面(选中 tab、主按钮等实心底)与压在上面的文字色,两套配色各给一组 */
18
+ --accent-bg:#a72c23;--accent-bg2:#7d1f18;--on-accent:#fff;--on-accent-soft:rgba(255,255,255,.84);
17
19
  --radius:12px;--shadow:0 1px 2px rgba(35,28,18,.05),0 18px 40px -30px rgba(35,28,18,.42);--ring:0 0 0 3px rgba(167,44,35,.16)}
18
20
  /* 深色:跟随系统。只换变量,元素规则一条不改。@media screen 限定 → 打印永远浅色 */
19
21
  @media screen and (prefers-color-scheme:dark){
20
22
  :root{--bg:#12110d;--panel:#1b1913;--panel2:#221f18;--line:#3c372e;--ink:#e7e2d6;--ink2:#d5cfc2;--dim:#9b9488;--gold:#e2755f;--gold-deep:#b8503f;--good:#67b98a;--bad:#e2755f;--warn:#d9a052;
23
+ /* 深色下 --gold 是浅珊瑚,实心底若沿用原色,白字与深字都够不着 4.5:1;
24
+ 所以强调面单独取一档更亮的珊瑚,压深色字(见 test/theme.test.mjs 的对比度复算) */
25
+ --accent-bg:#f0a38e;--accent-bg2:#e2755f;--on-accent:#14130f;--on-accent-soft:rgba(20,19,15,.84);
21
26
  --shadow:0 1px 2px rgba(0,0,0,.5),0 18px 40px -30px rgba(0,0,0,.9);--ring:0 0 0 3px rgba(226,117,95,.2)}
22
- /* 深色下强调色是浅色,这些实心底上的文字得反过来 */
23
- .tab.on,.seg button.on,.go{color:#14130f}
24
- .tool.on,.tool.on b{color:#14130f}
25
- .tool.on span{color:rgba(20,19,15,.72)}
26
- .mb-o.on b,.mb-o.on span,.mb-btn.primary{color:#14130f}
27
- .err{background:rgba(226,117,95,.14)}
28
27
  }
29
28
  *{box-sizing:border-box}
30
29
  html{-webkit-text-size-adjust:100%}
@@ -42,7 +41,7 @@ header{display:flex;flex-direction:column;align-items:stretch;gap:12px;margin-bo
42
41
  .brand-row{display:flex;align-items:center;justify-content:space-between;gap:10px 16px;flex-wrap:wrap}
43
42
  .brand{display:flex;align-items:center;gap:12px;font-size:22px;font-weight:700;letter-spacing:.14em;color:var(--gold);line-height:1.3}
44
43
  .brand .seal{display:inline-flex;align-items:center;justify-content:center;flex:none;width:40px;height:40px;border-radius:11px;
45
- background:linear-gradient(145deg,var(--gold),var(--gold-deep));color:#fff;font:700 20px/1 "Songti SC","SimSun",serif;letter-spacing:0;
44
+ background:linear-gradient(145deg,var(--accent-bg),var(--accent-bg2));color:var(--on-accent);font:700 20px/1 "Songti SC","SimSun",serif;letter-spacing:0;
46
45
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.22),0 8px 18px -10px var(--gold)}
47
46
  .brand-txt{display:block;min-width:0}
48
47
  .brand-txt small{display:block;font-size:12.5px;letter-spacing:.02em;color:var(--dim);font-weight:400;margin-top:3px}
@@ -53,7 +52,7 @@ header{display:flex;flex-direction:column;align-items:stretch;gap:12px;margin-bo
53
52
  .tab{display:flex;align-items:center;justify-content:center;text-align:center;padding:7px 10px;border-radius:999px;border:1px solid transparent;background:color-mix(in srgb,var(--panel) 62%,transparent);color:var(--dim);cursor:pointer;font-size:13.5px;font-family:inherit;white-space:nowrap;
54
53
  transition:background .16s ease,color .16s ease,border-color .16s ease,box-shadow .16s ease}
55
54
  .tab:hover{background:var(--panel);border-color:var(--line);color:var(--ink)}
56
- .tab.on{background:linear-gradient(145deg,var(--gold),var(--gold-deep));border-color:transparent;color:#fff;font-weight:600;box-shadow:0 8px 18px -12px var(--gold)}
55
+ .tab.on{background:linear-gradient(145deg,var(--accent-bg),var(--accent-bg2));border-color:transparent;color:var(--on-accent);font-weight:600;box-shadow:0 8px 18px -12px var(--gold)}
57
56
  /* 首屏按「想干什么」分组:19 个 tab 平铺时,第一次来的人得先猜「梅花」「称骨」是什么。
58
57
  卡片只是另一套入口,点击仍走同一个 setTab;熟练用户可一键回到平铺(19 个 tab 原样保留)。 */
59
58
  .view-switch{display:flex;align-items:center;gap:8px}
@@ -83,9 +82,9 @@ header{display:flex;flex-direction:column;align-items:stretch;gap:12px;margin-bo
83
82
  .tool b{font-size:13.5px;font-weight:600;letter-spacing:.02em}
84
83
  .tool span{font-size:11.5px;line-height:1.4;color:var(--dim)}
85
84
  .tool:hover{border-color:var(--gold);background:var(--panel)}
86
- .tool.on{border-color:transparent;background:linear-gradient(145deg,var(--gold),var(--gold-deep));box-shadow:0 8px 18px -12px var(--gold)}
87
- .tool.on b{color:#fff}
88
- .tool.on span{color:rgba(255,255,255,.84)}
85
+ .tool.on{border-color:transparent;background:linear-gradient(145deg,var(--accent-bg),var(--accent-bg2));box-shadow:0 8px 18px -12px var(--gold)}
86
+ .tool.on b{color:var(--on-accent)}
87
+ .tool.on span{color:var(--on-accent-soft)}
89
88
  /* 奇数个的那一组:末项铺满整行,避免右下留一个孤格 */
90
89
  .tool.wide{grid-column:1/-1}
91
90
  /* 手机端每组先露 4 个,其余折在「还有 N 个」后面:三组全展开会把表单挤到第二屏 */
@@ -130,15 +129,15 @@ body.view-flat .start{display:none}
130
129
  .mb-o b{font-size:14px;font-weight:600;color:var(--ink2);font-variant-numeric:tabular-nums}
131
130
  .mb-o span{font-size:11.5px;line-height:1.3}
132
131
  .mb-o:hover{border-color:color-mix(in srgb,var(--line) 40%,var(--gold))}
133
- .mb-o.on{border-color:transparent;background:linear-gradient(145deg,var(--gold),var(--gold-deep));box-shadow:0 8px 16px -12px var(--gold)}
134
- .mb-o.on b,.mb-o.on span{color:#fff}
132
+ .mb-o.on{border-color:transparent;background:linear-gradient(145deg,var(--accent-bg),var(--accent-bg2));box-shadow:0 8px 16px -12px var(--gold)}
133
+ .mb-o.on b,.mb-o.on span{color:var(--on-accent)}
135
134
  .mb-o:focus-within{outline:2px solid var(--gold);outline-offset:2px}
136
135
  .mb-nav{display:flex;align-items:center;gap:8px;margin-top:12px;flex-wrap:wrap}
137
136
  .mb-btn{padding:8px 16px;border-radius:9px;border:1px solid var(--line);background:var(--panel2);color:var(--ink);font-family:inherit;font-size:13.5px;cursor:pointer;
138
137
  transition:border-color .14s ease,background .14s ease,color .14s ease}
139
138
  .mb-btn:hover:not(:disabled){border-color:var(--gold)}
140
139
  .mb-btn:disabled{opacity:.42;cursor:not-allowed}
141
- .mb-btn.primary{border-color:transparent;background:linear-gradient(145deg,var(--gold),var(--gold-deep));color:#fff;font-weight:600;box-shadow:0 10px 20px -14px var(--gold)}
140
+ .mb-btn.primary{border-color:transparent;background:linear-gradient(145deg,var(--accent-bg),var(--accent-bg2));color:var(--on-accent);font-weight:600;box-shadow:0 10px 20px -14px var(--gold)}
142
141
  .mb-btn.primary:hover:not(:disabled){filter:brightness(1.05)}
143
142
  .mb-hint{margin-left:auto;font-size:12.5px;color:var(--dim);line-height:1.6}
144
143
  .mb-hint.warn{color:var(--bad);font-weight:600}
@@ -146,8 +145,8 @@ body.view-flat .start{display:none}
146
145
  /* 必填未填:边框转红,提示复用 .err 的口径 */
147
146
  .field.invalid textarea{border-color:var(--bad);background:rgba(224,102,95,.06)}
148
147
  .field.check input{min-width:0;width:18px;height:18px;accent-color:var(--gold);padding:0}
149
- .go{margin-left:0;grid-column:4;justify-self:end;align-self:end;padding:11px 24px;border-radius:9px;border:none;color:#fff;font-weight:600;cursor:pointer;font-family:inherit;font-size:15px;
150
- background:linear-gradient(145deg,var(--gold),var(--gold-deep));box-shadow:0 10px 22px -14px var(--gold),inset 0 1px 0 rgba(255,255,255,.18);
148
+ .go{margin-left:0;grid-column:4;justify-self:end;align-self:end;padding:11px 24px;border-radius:9px;border:none;color:var(--on-accent);font-weight:600;cursor:pointer;font-family:inherit;font-size:15px;
149
+ background:linear-gradient(145deg,var(--accent-bg),var(--accent-bg2));box-shadow:0 10px 22px -14px var(--gold),inset 0 1px 0 rgba(255,255,255,.18);
151
150
  transition:transform .14s ease,filter .14s ease,box-shadow .14s ease}
152
151
  .go:hover{filter:brightness(1.06);transform:translateY(-1px);box-shadow:0 14px 26px -14px var(--gold),inset 0 1px 0 rgba(255,255,255,.22)}
153
152
  .go:active{transform:translateY(0);filter:brightness(.98)}
@@ -167,7 +166,7 @@ h3::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--li
167
166
  .seg{display:inline-flex;border:1px solid var(--line);border-radius:9px;overflow:hidden;background:var(--panel2)}
168
167
  .seg button{padding:7px 14px;background:transparent;border:none;color:var(--dim);font-size:13.5px;font-family:inherit;cursor:pointer;transition:background .14s ease,color .14s ease}
169
168
  .seg button:hover{color:var(--ink)}
170
- .seg button.on{background:linear-gradient(145deg,var(--gold),var(--gold-deep));color:#fff;font-weight:600}
169
+ .seg button.on{background:linear-gradient(145deg,var(--accent-bg),var(--accent-bg2));color:var(--on-accent);font-weight:600}
171
170
  /* 结果入场:只做一次轻微上浮,不做循环动画 */
172
171
  #out{animation:mp-rise .3s cubic-bezier(.2,.7,.3,1) both}
173
172
  @keyframes mp-rise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
@@ -190,7 +189,7 @@ h3::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--li
190
189
  .askbox{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:16px 0 0;padding:11px 13px;border:1px dashed var(--line);border-radius:10px;background:color-mix(in srgb,var(--panel) 55%,transparent)}
191
190
  .askbox input{flex:1 1 240px;min-width:0;background:var(--panel2);border:1px solid var(--line);color:var(--ink);border-radius:8px;padding:8px 12px;font-size:14px;font-family:inherit;outline:none}
192
191
  .askbox input:focus{border-color:var(--gold);box-shadow:var(--ring)}
193
- .askbox button{padding:8px 14px;border:1px solid transparent;border-radius:8px;background:var(--gold);color:#fff;font:inherit;font-size:13px;cursor:pointer}
192
+ .askbox button{padding:8px 14px;border:1px solid transparent;border-radius:8px;background:var(--accent-bg);color:var(--on-accent);font:inherit;font-size:13px;cursor:pointer}
194
193
  .askbox button:hover{filter:brightness(1.08)}
195
194
  .askbox .hint{font-size:12px;color:var(--dim)}
196
195
  .method{max-width:860px;margin:0 auto;padding:24px 18px 40px;font-size:14px;line-height:1.9;color:var(--ink2)}
@@ -205,7 +204,7 @@ h3::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--li
205
204
  .mp-verify-row{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:13px;padding:3px 0}
206
205
  .mp-verify-btns{display:flex;gap:4px}
207
206
  .mp-chip{font-size:12px;padding:2px 9px;border-radius:999px;border:1px solid var(--line);background:transparent;color:var(--dim);cursor:pointer;font-family:inherit}
208
- .mp-chip.on{background:var(--gold);color:#fff;border-color:transparent}
207
+ .mp-chip.on{background:var(--accent-bg);color:var(--on-accent);border-color:transparent}
209
208
  .mp-verify-foot{display:flex;align-items:flex-start;gap:8px;margin-top:8px;flex-wrap:wrap}
210
209
  .mp-verify-fit{font-size:12.5px;color:var(--dim);line-height:1.7;flex:1;min-width:0}
211
210
  .mp-rect-row{display:flex;gap:6px;margin-bottom:6px}
@@ -264,8 +263,15 @@ h3::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--li
264
263
  .bar{grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;padding:14px}
265
264
  .field{width:100%;min-width:0}
266
265
  .field input,.field select,.field textarea,#addr,#changing,#direction{width:100%;min-width:0;padding:11px 12px;font-size:16px;min-height:44px}
266
+ /* 农历:子表单独占整行,方向分段按钮不再把「农历 → 公历」折成两行;
267
+ 闰月改用 .field.check 的小号勾选框,不再被 .field input 的 width:100% 拉成大方块 */
268
+ #qk-convert{grid-column:1/-1}
269
+ #qk-convert .seg button{white-space:nowrap}
267
270
  #for-ask{flex-direction:column;align-items:stretch}
268
- .field.check{padding-bottom:0}
271
+ /* .field input 的 44px 高度是给文本框的;勾选框继承后会变成 18×44 的长条,
272
+ 触摸区改由外层 label 撑(点文字也能勾),方块本身保持 18px */
273
+ .field.check{min-height:44px;padding-bottom:0;gap:10px}
274
+ .field.check input{min-height:0;height:18px}
269
275
  #mh-num{flex-wrap:wrap}
270
276
  #mh-num input{flex:1 1 30%;width:auto}
271
277
  .seg{display:flex;width:100%}
@@ -338,7 +344,7 @@ h3::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--li
338
344
  <div class="wrap">
339
345
  <header>
340
346
  <div class="brand-row">
341
- <div class="brand"><span class="seal" aria-hidden="true">命</span><span class="brand-txt">命理排盘<span class="ver">v0.46.5</span><small>八字 · 紫微 · 六爻 · 梅花 · 六壬 · 奇门 · 合盘 · 塔罗 全部在本机计算</small></span></div>
347
+ <div class="brand"><span class="seal" aria-hidden="true">命</span><span class="brand-txt">命理排盘<span class="ver">v0.50.5</span><small>八字 · 紫微 · 六爻 · 梅花 · 六壬 · 奇门 · 合盘 · 塔罗 全部在本机计算</small></span></div>
342
348
  <div class="view-switch">
343
349
  <span class="hint">熟练了可以换回平铺</span>
344
350
  <span class="seg" role="group" aria-label="首屏样式">
@@ -527,9 +533,7 @@ h3::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--li
527
533
  </div>
528
534
  <div id="qk-convert-lunar" style="display:none">
529
535
  <label class="field">农历日期<input id="lunardate" placeholder="如 2026-9-17"></label>
530
- <label class="field" style="flex-direction:row;gap:6px;align-items:center">
531
- <input id="leapm" type="checkbox"> 闰月
532
- </label>
536
+ <label class="field check">闰月<input id="leapm" type="checkbox"></label>
533
537
  </div>
534
538
  </div>
535
539
  <div id="qk-xiaoliuren" style="display:none">
@@ -607,7 +611,7 @@ h3::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--li
607
611
  <h3>对拍:把典籍当期望值</h3>
608
612
  <ul><li>六爻:京房八宫卦序与世应,64 卦全表;旬空六旬表</li><li>六壬:日干寄宫、中气换将;紫微:纳音定五行局、紫微天府相照</li><li>卦例:《增删卜易》9 条 +《卜筮正宗》128 条(93 成对 + 35 静卦),核本变卦 / 动爻数 / 卦宫 / 缺爻 / 旬空</li></ul>
609
613
  <h3>工程验证</h3>
610
- <ul><li>测试:94 个测试文件 / 3643 条断言;七道闸(回归 · 断言基线 · 测试可失败自检 · HTML 注入面 · 工程约定 · 产物宿主形态 · 随机扫射)</li><li>可复现:seed / asOf 显式;同一输入同一输出</li><li>口径:晚子时、真太阳时、奇门三流派、闰月归月都可切换,并有字段级差异对照</li></ul>
614
+ <ul><li>测试:103 个测试文件 / 4593 条断言;七道闸(回归 · 断言基线 · 测试可失败自检 · HTML 注入面 · 工程约定 · 产物宿主形态 · 随机扫射)</li><li>可复现:seed / asOf 显式;同一输入同一输出</li><li>口径:晚子时、真太阳时、奇门三流派、闰月归月都可切换,并有字段级差异对照</li></ul>
611
615
  <h3>已知边界(不猜)</h3>
612
616
  <ul><li>六爻应期(具体哪天应验)不做;卦例只录动爻个数,不录爻位</li><li>财富/健康只给结构判断与档位,不给金额区间、不作诊断</li><li>紫微飞星已做「单一支化逐宫追到成环」与「三方四正 16 条按落宫归堆」,但都未叠大限流年四化、不折吉凶;八字交运日已能算到日/时,但未与第三方排盘案例批量对拍</li></ul>
613
617
  <p class="note">内容用于传统文化学习与自我探索,不构成医疗、投资、法律或人事决策依据。</p>
@@ -636,6 +640,312 @@ const fail = (code, message) => {
636
640
  return { fail };
637
641
  })();
638
642
 
643
+ __mods.diff = (function () {
644
+ // 口径差异:同一组参数在不同流派口径下排出的盘,逐字段比对
645
+ //
646
+ // 用途:口径(晚子时换日、真太阳时、拆补/超神接气/置闰、闰月归月)是命理圈争执最多的地方。
647
+ // 多数工具切换后只给新结果,用户不知道「哪里变了」。这里把变化摊开:
648
+ // 变化字段路径 + 旧值 → 新值 + 一句话总结。
649
+ //
650
+ // 只做结构化比对,不改动任何引擎的输出。
651
+
652
+ /** 比对时忽略的字段:文案、时间戳、口径回显、内部儒略日数字 */
653
+ const IGNORE_KEYS = new Set([
654
+ 'summary', 'interpret', 'castAt', 'asOf', 'precision', 'disclaimer', 'note',
655
+ 'trueSolarText', 'civilText', 'offsetMinutes',
656
+ 'input', // 只是把调用参数回显出来,不是盘面变化
657
+ 'jd', // 内部儒略日,人类不可读;对外展示用 time 等格式化字段
658
+ // 奇门把「这次用了哪个取局流派 / 晚子时归哪一天」写在结果里;换了开关它必然变,
659
+ // 但它只是口径回显,不是盘面。不剔掉的话,两张完全相同的盘会被报成「2 处差异」。
660
+ 'juMethod', 'juMethodNote', 'lateZiHourMode',
661
+ // 紫微把「闰月按哪种政策起盘」回显在 lunar 里;非闰月盘换政策时它也会变,
662
+ // 但农历月份与宫位一字未动,不该报成差异。
663
+ 'leapPolicy',
664
+ ]);
665
+
666
+ const isObject = (v) => v !== null && typeof v === 'object';
667
+
668
+ /**
669
+ * 结构化差异
670
+ * @returns {{path:string, from:any, to:any}[]}
671
+ */
672
+ function diffObjects(a, b, path = '', out = []) {
673
+ if (a === b) return out;
674
+ if (!isObject(a) || !isObject(b)) {
675
+ out.push({ path: path || '(root)', from: a, to: b });
676
+ return out;
677
+ }
678
+ const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
679
+ for (const k of keys) {
680
+ if (IGNORE_KEYS.has(k)) continue;
681
+ const p = path ? `${path}.${k}` : k;
682
+ const va = a[k];
683
+ const vb = b[k];
684
+ if (isObject(va) && isObject(vb)) {
685
+ diffObjects(va, vb, p, out);
686
+ } else if (JSON.stringify(va) !== JSON.stringify(vb)) {
687
+ out.push({ path: p, from: va, to: vb });
688
+ }
689
+ }
690
+ return out;
691
+ }
692
+
693
+ /** 路径 → 盘面栏位名;未命中细分规则时退回所属大类 */
694
+ const LABELS = [
695
+ [/^pillars\.year\.ganzhi$/, '年柱'],
696
+ [/^pillars\.month\.ganzhi$/, '月柱'],
697
+ [/^pillars\.day\.ganzhi$/, '日柱'],
698
+ [/^pillars\.hour\.ganzhi$/, '时柱'],
699
+ [/^pillars\.\w+\.hidden/, '藏干'],
700
+ [/^pillars\.\w+\.stemTenGod$/, '十神'],
701
+ [/^pillars\.\w+\.kongwang/, '空亡'],
702
+ [/^pillars\.\w+\.dishi$|^pillars\.\w+\.zizuo$/, '十二长生'],
703
+ [/^pillars\.\w+\.nayin$/, '纳音'],
704
+ [/^dayMaster\./, '日主'],
705
+ [/^palaces\.(taiyuan|minggong|shengong)\./, '命身宫'],
706
+ [/^palaces\.\d+\.(palace|branch|stem|ganzhi)$/, '宫位'],
707
+ [/^palaces\.\d+\.stars\.\d+\.(name|brightness|type)$/, '星曜'],
708
+ [/^palaces\.\d+\.shensha\./, '神煞'],
709
+ [/^palaces\.\d+\.(daxian|xiaoxian)\./, '大限'],
710
+ [/^palaces\.\d+\.sanfang\./, '三方四正'],
711
+ [/^palaces\.\d+\./, '宫位'],
712
+ [/^palaces\./, '命身宫'],
713
+ [/^soul\./, '命宫'],
714
+ [/^body\./, '身宫'],
715
+ [/^doujun\./, '流年斗君'],
716
+ [/^siZhu\.year$/, '年柱'],
717
+ [/^siZhu\.month$/, '月柱'],
718
+ [/^siZhu\.day$/, '日柱'],
719
+ [/^siZhu\.time$/, '时柱'],
720
+ [/^ju$|^juNumber$/, '局数'],
721
+ [/^yuan$/, '三元'],
722
+ [/^dun$/, '阴阳遁'],
723
+ [/^daxian(List)?\./, '大限'],
724
+ [/^fly(Chains|Composite)?\./, '飞星'],
725
+ [/^pattern/, '格局'],
726
+ [/^schools\./, '流派回显'],
727
+ [/^dayun\.jiao\./, '起运'],
728
+ [/^dayun\.toNextJieDays$/, '起运时间'],
729
+ [/^dayun\.list\.(\d+)\.ganzhi$/, '大运干支'],
730
+ [/^dayun\.startAge\./, '起运时间'],
731
+ [/^dayun\./, '大运评分'],
732
+ [/^liunian\./, '流年'],
733
+ [/^jieqi\./, '节气边界'],
734
+ [/^solarTime\./, '真太阳时'],
735
+ [/^dayGanZhi$/, '日辰'],
736
+ [/^hourZhi$/, '时支'],
737
+ [/^yueJiang\./, '月将'],
738
+ [/^xunKong$/, '旬空'],
739
+ [/^lines\.(\d+)\./, '爻'],
740
+ [/^shensha\./, '神煞'],
741
+ [/^frame\./, '格局用神'],
742
+ [/^strength\./, '旺衰'],
743
+ [/^wuxing\./, '五行计数'],
744
+ [/^relations\./, '干支关系'],
745
+ [/^input\./, '输入回显'],
746
+ ];
747
+
748
+ /** 大类:用于把几百处连带变化归成几句话 */
749
+ const CATEGORY = [
750
+ [/^pillars\./, '四柱'],
751
+ [/^shensha\./, '神煞'],
752
+ [/^dayun\./, '大运'],
753
+ [/^liunian\./, '流年'],
754
+ [/^frame\./, '格局用神'],
755
+ [/^strength\./, '旺衰'],
756
+ [/^wuxing\./, '五行计数'],
757
+ [/^dayMaster\./, '日主'],
758
+ [/^relations\./, '干支关系'],
759
+ [/^palaces\.(taiyuan|minggong|shengong)\./, '命身宫'],
760
+ [/^palaces\.\d+\./, '宫位'],
761
+ [/^palaces\./, '命身宫'],
762
+ [/^soul\.|^body\.|^doujun\./, '命身宫'],
763
+ [/^siZhu\./, '四柱'],
764
+ [/^ju$|^juNumber$|^yuan$|^dun$/, '局数'],
765
+ [/^daxian/, '大限'],
766
+ [/^fly/, '飞星'],
767
+ [/^pattern/, '格局'],
768
+ [/^jieqi\./, '节气'],
769
+ [/^solarTime\./, '真太阳时'],
770
+ [/^input\./, '输入回显'],
771
+ [/^lines\./, '爻'],
772
+ ];
773
+
774
+ /**
775
+ * 根因字段:真正被口径直接改动的干支/局数/爻。
776
+ * 其余几百处都是从这里派生出去的——把这层关系讲清楚,才是「口径对照」的价值。
777
+ */
778
+ const ROOT = [
779
+ [/^pillars\.(year|month|day|hour)\.ganzhi$/, '柱'],
780
+ [/^palaces\.(minggong|shengong)\.ganzhi$/, '命身宫'],
781
+ [/^soul\.ganzhi$/, '命宫'],
782
+ [/^body\.branch$/, '身宫'],
783
+ [/^siZhu\.(year|month|day|time)$/, '柱'],
784
+ [/^dayun\.jiao\.at$/, '起运'],
785
+ [/^dayGanZhi$/, '日辰'],
786
+ [/^hourZhi$/, '时支'],
787
+ [/^ju$|^juNumber$|^juName$/, '局数'],
788
+ [/^dun$/, '阴阳遁'],
789
+ [/^lines\.\d+\.(ganzhi|yinYang|name)$/, '爻'],
790
+ [/^yueJiang\.(name|zhi)$/, '月将'],
791
+ ];
792
+
793
+ function labelOf(path) {
794
+ for (const [re, label] of LABELS) {
795
+ if (re.test(path)) return label;
796
+ }
797
+ return categoryOf(path) || path;
798
+ }
799
+
800
+ function categoryOf(path) {
801
+ for (const [re, cat] of CATEGORY) {
802
+ if (re.test(path)) return cat;
803
+ }
804
+ return '';
805
+ }
806
+
807
+ function rootOf(path) {
808
+ for (const [re, label] of ROOT) {
809
+ if (re.test(path)) return label;
810
+ }
811
+ return '';
812
+ }
813
+
814
+ /** 自然排序:行运类按序号,其余按字符串 */
815
+ function sortDiffs(diffs) {
816
+ return [...diffs].sort((x, y) => x.path.localeCompare(y.path, 'zh-Hans-CN', { numeric: true }));
817
+ }
818
+
819
+ /**
820
+ * 把一组变化归纳成因果式一句话:
821
+ * 「根因:日柱 丙子 → 乙亥;连带影响 四柱、神煞、大运、流年 等 6 类(共 407 处)」
822
+ */
823
+ function summarize(diffs) {
824
+ if (!diffs.length) return '本盘不受此口径影响:所有字段一致。';
825
+ const roots = diffs.filter((d) => rootOf(d.path));
826
+ const cats = [...new Set(diffs.map((d) => categoryOf(d.path)).filter(Boolean))];
827
+ const head = roots.length
828
+ ? `根因 ${roots.length} 处:` + roots.slice(0, 4)
829
+ .map((d) => `${labelOf(d.path)} ${formatValue(d.from)}→${formatValue(d.to)}`).join('、')
830
+ + (roots.length > 4 ? ' 等' : '')
831
+ : '无单一根因';
832
+ const tail = cats.length
833
+ ? `;连带影响 ${cats.slice(0, 6).join('、')}${cats.length > 6 ? ' 等' : ''} ${cats.length} 类,共 ${diffs.length} 处`
834
+ : `;共 ${diffs.length} 处`;
835
+ return head + tail + '。';
836
+ }
837
+
838
+ const formatValue = (v) => {
839
+ if (v === undefined) return '—';
840
+ if (v === null) return 'null';
841
+ if (typeof v === 'string') return v;
842
+ if (Array.isArray(v)) return v.join('') || '[]';
843
+ if (typeof v === 'object') return JSON.stringify(v);
844
+ return String(v);
845
+ };
846
+
847
+ /**
848
+ * 文本版对照:先列根因,再列连带变化(默认最多 12 处,其余按类归并)
849
+ */
850
+ function renderDiff(diffs, { title = '', limit = 12 } = {}) {
851
+ const lines = [];
852
+ if (title) lines.push(`【${title}】`);
853
+ lines.push(summarize(diffs));
854
+ if (!diffs.length) return lines.join('\n');
855
+
856
+ const roots = sortDiffs(diffs.filter((d) => rootOf(d.path)));
857
+ const rest = sortDiffs(diffs.filter((d) => !rootOf(d.path)));
858
+
859
+ if (roots.length) {
860
+ lines.push('');
861
+ lines.push(' 根因(口径直接改动):');
862
+ for (const d of roots) {
863
+ lines.push(` ${labelOf(d.path).padEnd(6)} ${formatValue(d.from)} → ${formatValue(d.to)}`);
864
+ }
865
+ }
866
+ if (rest.length) {
867
+ lines.push('');
868
+ lines.push(` 连带变化(${rest.length} 处,展示前 ${Math.min(limit, rest.length)} 处):`);
869
+ for (const d of rest.slice(0, limit)) {
870
+ lines.push(` ${labelOf(d.path).padEnd(6)} ${formatValue(d.from)} → ${formatValue(d.to)}`);
871
+ }
872
+ if (rest.length > limit) {
873
+ const byCat = {};
874
+ for (const d of rest.slice(limit)) {
875
+ const c = categoryOf(d.path) || '其他';
876
+ byCat[c] = (byCat[c] || 0) + 1;
877
+ }
878
+ lines.push(' 其余:' + Object.entries(byCat).map(([c, n]) => `${c} ${n} 处`).join('、'));
879
+ }
880
+ }
881
+ return lines.join('\n');
882
+ }
883
+
884
+ /**
885
+ * 口径开关表:每个体系 2–3 组「只差一个维度」的对照。
886
+ * read 取当前口径(没传时按引擎默认),alt 给另一口径,label 是给人看的短名。
887
+ * UI、CLI 与工具参数 diff 共用这一份,默认值不在别处再抄一遍。
888
+ */
889
+ const CALIBER_SWITCHES = {
890
+ bazi: [
891
+ {
892
+ key: 'lateZi',
893
+ title: '晚子时:23:00 后是否换日',
894
+ short: '晚子时',
895
+ read: (a) => a.lateZi !== false,
896
+ alt: (v) => !v,
897
+ label: (v) => (v ? '换日' : '不换日'),
898
+ },
899
+ {
900
+ key: 'trueSolar',
901
+ title: '真太阳时:是否按出生地经度修正',
902
+ short: '真太阳时',
903
+ read: (a) => a.trueSolar !== false,
904
+ alt: (v) => !v,
905
+ label: (v) => (v ? '修正' : '不修正'),
906
+ },
907
+ ],
908
+ ziwei: [
909
+ {
910
+ key: 'leapMonth',
911
+ title: '闰月归月:按日切分 / 整月归次月',
912
+ short: '闰月归月',
913
+ read: (a) => (a.leapMonth === 'next' || a.leapMonth === 'prev' ? a.leapMonth : 'split'),
914
+ alt: (v) => (v === 'split' ? 'next' : 'split'),
915
+ label: (v) => ({ split: '按日切分', next: '整月归次月', prev: '整月归本月' }[v] || v),
916
+ },
917
+ {
918
+ key: 'trueSolar',
919
+ title: '真太阳时:是否按出生地经度修正',
920
+ short: '真太阳时',
921
+ read: (a) => a.trueSolar !== false,
922
+ alt: (v) => !v,
923
+ label: (v) => (v ? '修正' : '不修正'),
924
+ },
925
+ ],
926
+ qimen: [
927
+ {
928
+ key: 'juMethod',
929
+ title: '取局流派:拆补 / 超神接气 / 置闰',
930
+ short: '取局流派',
931
+ read: (a) => (['chaibu', 'chaoshen', 'zhirun'].includes(a.juMethod) ? a.juMethod : 'chaibu'),
932
+ alt: (v) => (v === 'chaibu' ? 'zhirun' : 'chaibu'),
933
+ label: (v) => ({ chaibu: '拆补法', chaoshen: '超神接气', zhirun: '置闰法' }[v] || v),
934
+ },
935
+ {
936
+ key: 'lateZiHourMode',
937
+ title: '晚子时:23:00 后归当日 / 次日',
938
+ short: '晚子时归日',
939
+ read: (a) => (a.lateZiHourMode === 'next-day' ? 'next-day' : 'same-day'),
940
+ alt: (v) => (v === 'next-day' ? 'same-day' : 'next-day'),
941
+ label: (v) => (v === 'next-day' ? '归次日' : '归当日'),
942
+ },
943
+ ],
944
+ };
945
+
946
+ return { diffObjects, labelOf, categoryOf, rootOf, sortDiffs, summarize, formatValue, renderDiff, CALIBER_SWITCHES };
947
+ })();
948
+
639
949
  __mods.validate = (function () {
640
950
  // 引擎边界的入参守卫。
641
951
  //
@@ -3549,6 +3859,16 @@ const CLASSICS = [
3549
3859
  plain: '成格不等于一路顺:成中带忌(官逢财又逢伤、官被合之类)就打了折;败格也未必无救——印来解伤、合来清煞,败中还能转成。',
3550
3860
  url: 'https://www.diancang.xyz/xuanxuewushu/zipingzhenquan/53629.html',
3551
3861
  },
3862
+ {
3863
+ id: 'zp-zaji',
3864
+ book: 'ziping',
3865
+ chapter: '十六、论杂气如何取用',
3866
+ scene: '用神',
3867
+ tags: [],
3868
+ quote: '何谓透干?如甲生辰月,透戊则用偏财,透癸则用正印,透乙则用月劫是也。何谓会支?如甲生辰月,逢申与子会局,则用水印是也。一透则一用,兼透则兼用,透而又会,则透与会并用。',
3869
+ plain: '四墓(辰戌丑未)所藏不止一气,所以叫杂气:取用先看哪一藏干透到天干,透一个用一个;没有透干,再看地支有没有与月支会成三合局,会成什么局就取局中那一气。透干与会支同时成立时两样并用。原文对「兼透」只给原则,没有给取舍次序。',
3870
+ url: 'https://www.diancang.xyz/xuanxuewushu/zipingzhenquan/53636.html',
3871
+ },
3552
3872
  {
3553
3873
  id: 'zp-bianhua',
3554
3874
  book: 'ziping',
@@ -3825,6 +4145,33 @@ const CLASSICS = [
3825
4145
  plain: '从财格要财会成局、日干无气;一见根气就破格,原文用「命损无猜」这种重话写明。以丁生酉月、柱多庚辛为例:只能弃命相从,行北方财官旺地为入格,南行则灾。',
3826
4146
  url: 'https://www.diancang.xyz/xuanxuewushu/sanmingtonghui/7652.html',
3827
4147
  },
4148
+ // —— 三命通会 · 总论岁运:岁运提示一节的原文出处。
4149
+ // 原文的「返吟 / 伏吟」按「岁运与日柱相对 / 岁运压日」说,与本插件「干支相同为伏吟、天克地冲为反吟」
4150
+ // 不是同一套定义;口径照本插件,原文照录不改写,两者在 plain 里并列说明。
4151
+ // 这一条不进五条上限的取条表:它只在盘面有岁运信号时随「岁运提示」一起出现。
4152
+ {
4153
+ id: 'smt-suiyun',
4154
+ book: 'sanming',
4155
+ chapter: '卷二·总论岁运',
4156
+ scene: '岁运',
4157
+ tags: [],
4158
+ quote: '行运以生月为运元,最怕行运与太岁冲克,若岁运冲月必祸;若岁运与日相对,谓之返吟,岁运压日,谓之伏吟,二者不利六亲,非横破财,不为吉兆。',
4159
+ plain: '大运以生月为起点,最怕与流年(太岁)冲克:原文说岁运冲月为祸,又把「岁运与日柱相对」叫返吟、「岁运压日」叫伏吟。这两句是按「对日柱」说的,与本插件「干支相同为伏吟、天克地冲为反吟」不是同一套定义(本插件取后者);末句「不利六亲、破财」是旧说的重话,只作变动提示,不作断语。',
4160
+ url: 'https://www.diancang.xyz/xuanxuewushu/sanmingtonghui/7539.html',
4161
+ },
4162
+ // —— 三命通会 · 卷二 · 论人元司事:人元司权一节的原文出处。
4163
+ // 此本的日数分法与本插件采用的通行 30 日表不同(寅月作 5/5/20、卯月作 7/23,本插件作 7/7/16 与 10/20),
4164
+ // 两表各照原样并列,不改判。与「总论岁运」一样,不进五条上限的取条表:只在人元司权一节随结果出现。
4165
+ {
4166
+ id: 'smt-renyuan',
4167
+ book: 'sanming',
4168
+ chapter: '卷二·论人元司事',
4169
+ scene: '人元',
4170
+ tags: [],
4171
+ quote: '故支吕所藏者主命,谓之人元,名为司事之神,以命术言之为月令。如正月建寅,寅中有艮土用事五日,丙火长生五日,甲木二十日;二月建卯,卯中有甲木用事七日,乙木二十三日;',
4172
+ plain: '地支所藏为「人元」,按月令分日轮值。三命通会这一本给的是另一套日数:寅月艮土(戊土)五日、丙火五日、甲木二十日,卯月甲七日、乙二十三日。它与本插件采用的通行 30 日表(寅月戊7丙7甲16,即 7/7/16;卯月甲10乙20,即 10/20)不是同一套分法;两表各照原样并列,本插件仍按通行表给「第几日谁当权」,不改取格。',
4173
+ url: 'https://www.diancang.xyz/xuanxuewushu/sanmingtonghui/7529.html',
4174
+ },
3828
4175
  ];
3829
4176
 
3830
4177
  const BY_ID = CLASSICS.reduce((m, e) => { m[e.id] = e; return m; }, {});
@@ -3861,6 +4208,14 @@ function classicOf(id) {
3861
4208
  return BY_ID[id] || null;
3862
4209
  }
3863
4210
 
4211
+ // 岁运提示的原文:盘面出现岁运并临 / 运年反吟 / 流年伏吟反吟时随该节给出,不占五条上限。
4212
+ // 没有信号就不给——这段原文讲的正是冲克,没冲没克时挂出来只会误导。
4213
+ function suiyunClassicOf(r) {
4214
+ const liunian = r && Array.isArray(r.liunian) ? r.liunian : [];
4215
+ const hit = liunian.some((l) => l && l.suiyun && Array.isArray(l.suiyun.signals) && l.suiyun.signals.length);
4216
+ return hit ? classicOf('smt-suiyun') : null;
4217
+ }
4218
+
3864
4219
  function tagOf(gejuName) {
3865
4220
  const name = String(gejuName || '');
3866
4221
  if (GEJU_KEY[name]) return GEJU_KEY[name];
@@ -3879,7 +4234,8 @@ function chapterTitle(e) {
3879
4234
  }
3880
4235
 
3881
4236
  // 根据一张八字结果挑原文:特殊结构(专旺/从格)先取单章,再月令格(子平真诠 + 三命通会对照),
3882
- // 再调候(寒暖/燥湿),最后「用神总纲」一位按盘取:有化气格取论用神变化、见组合取论用神成败救应、其余取通用总纲;去重、限量。
4237
+ // 再调候(寒暖/燥湿),最后「用神总纲」一位按盘四选一:有化气格取论用神变化、辰戌丑未月取论杂气如何取用、
4238
+ // 见组合取论用神成败救应、其余取通用总纲;去重、限量。
3883
4239
  function classicsFor(r, limit = 5) {
3884
4240
  if (!r) return [];
3885
4241
  const out = [];
@@ -3917,12 +4273,16 @@ function classicsFor(r, limit = 5) {
3917
4273
  if (tgEntry) {
3918
4274
  push(tgEntry, `本盘忌${tg.foe}克用${tg.use},局中${tg.bridge}${tg.weight}分可作通关,故取${CLASSIC_BOOKS.ditian.name}「通关」一条`);
3919
4275
  }
3920
- // 用神总纲位只放一条:同一个格,化气看「变化」、有组合看「成败救应」,都没有才回到四吉四凶总纲。
3921
- // 不另加位次,是因为格局两条 + 气候两条正好占满 5 条上限;总纲与具体结构二选一,避免泛论挤掉针对这张盘的话。
4276
+ // 用神总纲位只放一条,按盘四选一:化气看「变化」、四墓杂气看「杂气取用」、有组合看「成败救应」,
4277
+ // 都没有才回到四吉四凶总纲。不另加位次,是因为格局两条 + 气候两条正好占满 5 条上限。
3922
4278
  const huagi = gejuList.find((g) => g && g.kind === '化气格');
3923
4279
  const combo = gejuList.find((g) => g && g.kind === '组合');
4280
+ const monthBranch = r.monthBranch || (r.frame && r.frame.monthBranch) || (r.pillars && r.pillars.month && r.pillars.month.branch);
4281
+ const zaMonth = ['辰', '未', '戌', '丑'].includes(monthBranch);
3924
4282
  if (huagi) {
3925
4283
  push(classicOf('zp-bianhua'), `本盘列有「${huagi.name}」:用神由月令合化而变,总纲一位改取${CLASSIC_BOOKS.ziping.name}论用神变化`);
4284
+ } else if (zaMonth) {
4285
+ push(classicOf('zp-zaji'), `月令${monthBranch}属四墓杂气,取用先看透干、再看会支,总纲一位取${CLASSIC_BOOKS.ziping.name}论杂气如何取用`);
3926
4286
  } else if (combo) {
3927
4287
  push(classicOf('zp-chengbai'), `本盘另有「${combo.name}」组合:成格还要看带忌与救应,总纲一位改取${CLASSIC_BOOKS.ziping.name}论用神成败救应`);
3928
4288
  } else {
@@ -3936,7 +4296,7 @@ function classicsText(rows) {
3936
4296
  return rows.map((e) => `《${CLASSIC_BOOKS[e.book].name}·${e.chapter}》「${e.quote}」`).join('\n');
3937
4297
  }
3938
4298
 
3939
- return { CLASSIC_BOOKS, CLASSICS, classicOf, classicsFor, classicsText };
4299
+ return { CLASSIC_BOOKS, CLASSICS, classicOf, suiyunClassicOf, classicsFor, classicsText };
3940
4300
  })();
3941
4301
 
3942
4302
  __mods.geju = (function () {
@@ -3951,6 +4311,7 @@ __mods.geju = (function () {
3951
4311
 
3952
4312
  const { judgeClimate, arbitrate } = __mods.tiaohou;
3953
4313
  const { qtOf } = __mods.qiongtong;
4314
+ const { sanheGroupOf } = __mods.sanhe;
3954
4315
 
3955
4316
  const STEMS = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
3956
4317
  const BRANCHES = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
@@ -4029,6 +4390,25 @@ function elementRoles(dayStem) {
4029
4390
 
4030
4391
  // ── 月令格 ───────────────────────────────────────────────────
4031
4392
 
4393
+ // 四墓(杂气)月:辰戌丑未所藏多、用神不一,取用先看透干,再看会支。
4394
+ // 会支只认三合局——子平真诠十六「逢申与子会局」的例子即三合,三会方与兼透兼用仍不纳入。
4395
+ const ZA_MONTHS = ['辰', '未', '戌', '丑'];
4396
+
4397
+ function huiCandidate(pillars, monthBranch, cands) {
4398
+ if (!ZA_MONTHS.includes(monthBranch)) return null;
4399
+ const group = sanheGroupOf(monthBranch);
4400
+ if (!group) return null;
4401
+ const others = ['year', 'day', 'hour'];
4402
+ const need = group.branches.filter((b) => b !== monthBranch);
4403
+ if (!need.every((b) => others.some((k) => pillars[k].branch === b))) return null;
4404
+ const cand = cands.find((c) => STEM_ELEMENT[c.stem] === group.element);
4405
+ if (!cand) return null;
4406
+ const where = others
4407
+ .filter((k) => need.includes(pillars[k].branch))
4408
+ .map((k) => `${({ year: '年', day: '日', hour: '时' })[k]}支${pillars[k].branch}`);
4409
+ return { ...cand, group, where };
4410
+ }
4411
+
4032
4412
  function monthPattern(pillars, dayStem, monthBranch) {
4033
4413
  const hidden = HIDDEN[monthBranch] || [];
4034
4414
  const cands = hidden.map((stem, i) => ({
@@ -4040,10 +4420,13 @@ function monthPattern(pillars, dayStem, monthBranch) {
4040
4420
  const others = ['year', 'month', 'hour'];
4041
4421
  const exposedKey = others.find((k) => cands.some((c) => c.stem === pillars[k].stem));
4042
4422
  const base = cands[0];
4043
- const use = exposedKey ? cands.find((c) => c.stem === pillars[exposedKey].stem) : base;
4423
+ const hui = exposedKey ? null : huiCandidate(pillars, monthBranch, cands);
4424
+ const use = exposedKey ? cands.find((c) => c.stem === pillars[exposedKey].stem) : (hui || base);
4425
+ const usedBy = exposedKey ? '透干' : (hui ? '会支' : '本气');
4044
4426
  const exposedPos = exposedKey ? { year: '年', month: '月', hour: '时' }[exposedKey] : null;
4045
4427
 
4046
- if (base.god === '比肩') {
4428
+ // 建禄只认日干的禄位;戊土辰月、己土未月这类「本气比肩但不在禄位」的杂气月不再冒称建禄
4429
+ if (base.god === '比肩' && LUSHEN_BRANCH[dayStem] === monthBranch) {
4047
4430
  return {
4048
4431
  name: '建禄格',
4049
4432
  kind: '月令格',
@@ -4060,10 +4443,31 @@ function monthPattern(pillars, dayStem, monthBranch) {
4060
4443
  }
4061
4444
  return { name: '月劫格', kind: '月令格', evidence: `月令${monthBranch}本气${base.stem}为劫财` };
4062
4445
  }
4446
+ if (usedBy === '本气' && base.god === '比肩') {
4447
+ return {
4448
+ name: '比肩格',
4449
+ kind: '月令格',
4450
+ evidence: `月令${monthBranch}藏${hidden.join('')},本气${base.stem}为比肩,但${dayStem}禄在${LUSHEN_BRANCH[dayStem]}、不在${monthBranch},属杂气月比肩当权`,
4451
+ };
4452
+ }
4453
+ // 子平十六:「透乙则用月劫」——杂气月由透干或会支取到劫财时,仍作月劫论
4454
+ if (usedBy !== '本气' && use.god === '劫财') {
4455
+ const via = exposedKey ? `透于${exposedPos}干` : `${hui.where.join('、')}会成${hui.group.element}局`;
4456
+ return {
4457
+ name: '月劫格',
4458
+ kind: '月令格',
4459
+ evidence: `月令${monthBranch}藏${hidden.join('')},${use.pos}${use.stem}(劫财)${via},劫财为用,作月劫论`,
4460
+ };
4461
+ }
4063
4462
  const name = `${use.god}格`;
4064
- const ex = exposedKey
4065
- ? `月令${monthBranch}藏${hidden.join('')},${use.pos}${use.stem}(${use.god})透于${exposedPos}干`
4066
- : `月令${monthBranch}藏${hidden.join('')},本气${base.stem}(${base.god})未透干,以月令本气取格`;
4463
+ let ex;
4464
+ if (exposedKey) {
4465
+ ex = `月令${monthBranch}藏${hidden.join('')},${use.pos}${use.stem}(${use.god})透于${exposedPos}干`;
4466
+ } else if (hui) {
4467
+ ex = `月令${monthBranch}藏${hidden.join('')},本气${base.stem}(${base.god})未透干,而${hui.where.join('、')}会成${hui.group.element}局,取${use.pos}${use.stem}(${use.god})为用`;
4468
+ } else {
4469
+ ex = `月令${monthBranch}藏${hidden.join('')},本气${base.stem}(${base.god})未透干,以月令本气取格`;
4470
+ }
4067
4471
  return { name, kind: '月令格', evidence: ex };
4068
4472
  }
4069
4473
 
@@ -4178,11 +4582,17 @@ function yongshenOf(dayStem, monthBranch, strength, wuxing, branches = []) {
4178
4582
  const me = roles.me;
4179
4583
  const weak = strength.level === '身弱' || strength.level === '偏弱';
4180
4584
  const strong = strength.level === '身强' || strength.level === '偏强';
4585
+ const roleName = (el) => (el === me ? '比劫' : el === roles.inMe ? '印'
4586
+ : el === roles.shi ? '食伤' : el === roles.cai ? '财' : '官杀');
4587
+ const ROLE_VERB = { 比劫: '帮身', 印: '生身', 食伤: '泄秀', 财: '耗', 官杀: '制' };
4181
4588
 
4182
4589
  const baseFavor = weak ? [roles.inMe, me] : strong ? [roles.shi, roles.cai, roles.guan] : [roles.inMe, me, roles.shi];
4183
4590
  const baseAvoid = weak ? [roles.shi, roles.cai, roles.guan] : strong ? [me, roles.inMe] : [roles.guan, roles.cai];
4184
4591
  let favor = baseFavor.slice();
4185
4592
  let avoid = baseAvoid.slice();
4593
+ // 调候所忌若正好是扶抑所喜(弱土遇燥的火、寒湿盘的强土遇水),两边不能同时列。
4594
+ // 本表保留在喜用、不列入忌,并单列「有争议」;否则摘要会出现同一行既喜又忌。
4595
+ const disputed = [];
4186
4596
 
4187
4597
  // 先判气候,再决定要不要让调候压过扶抑
4188
4598
  const climate = judgeClimate({ monthBranch, dayStem, wuxing, branches });
@@ -4193,27 +4603,39 @@ function yongshenOf(dayStem, monthBranch, strength, wuxing, branches = []) {
4193
4603
  const needEl = climate.need.element;
4194
4604
  if (!favor.includes(needEl)) favor.unshift(needEl);
4195
4605
  avoid = avoid.filter((e) => e !== needEl);
4196
- if (climate.avoidElement && !avoid.includes(climate.avoidElement)) avoid.unshift(climate.avoidElement);
4606
+ const badEl = climate.avoidElement;
4607
+ if (badEl && favor.includes(badEl)) {
4608
+ disputed.push({
4609
+ element: badEl,
4610
+ favorRole: roleName(badEl),
4611
+ favorVerb: ROLE_VERB[roleName(badEl)],
4612
+ climate: climate.label,
4613
+ note: `调候忌${badEl}(${climate.label}),但它在扶抑里为${roleName(badEl)};本表保留在喜用,不列入忌。`,
4614
+ });
4615
+ } else if (badEl && !avoid.includes(badEl)) {
4616
+ avoid.unshift(badEl);
4617
+ }
4197
4618
  }
4198
4619
 
4199
- const roleName = (el) => (el === me ? '比劫' : el === roles.inMe ? '印'
4200
- : el === roles.shi ? '食伤' : el === roles.cai ? '财' : '官杀');
4201
4620
  const expand = {
4202
4621
  比劫: ['比肩', '劫财'], 印: ['正印', '偏印'], 食伤: ['食神', '伤官'],
4203
4622
  财: ['正财', '偏财'], 官杀: ['正官', '七杀'],
4204
4623
  };
4205
4624
  const favorTenGods = Array.from(new Set(favor.flatMap((el) => expand[roleName(el)] || [])));
4206
4625
  const avoidTenGods = Array.from(new Set(avoid.flatMap((el) => expand[roleName(el)] || [])));
4626
+ const disputedTenGods = Array.from(new Set(disputed.flatMap((d) => expand[roleName(d.element)] || [])));
4207
4627
  const tongguan = tongguanOf(favor, avoid, wuxing);
4208
4628
 
4629
+ const describe = (el) => `${el}(${roleName(el)}·${ROLE_VERB[roleName(el)]})`;
4630
+ const disputedText = disputed
4631
+ .map((d) => `${d.element}(扶抑为${d.favorRole}·${d.favorVerb};${d.climate}盘调候忌${d.element},本表保留在喜用)`)
4632
+ .join('、');
4209
4633
  const detail = [
4210
4634
  `日主${dayStem}${me},${strength.level}(${strength.score} 分):`,
4211
- weak
4212
- ? `喜${roles.inMe}(印)生身、${me}(比劫)帮身;忌${roles.shi}(食伤)泄、${roles.cai}(财)耗、${roles.guan}(官杀)克。`
4213
- : strong
4214
- ? `喜${roles.shi}(食伤)泄秀、${roles.cai}(财)耗、${roles.guan}(官杀)制;忌${me}(比劫)与${roles.inMe}(印)再助。`
4215
- : '身中和,先取调候与通关,喜忌随大运流年动态变化。',
4216
- `调候:${climate.label}(${(climate.evidence || []).join('、')})${climate.need ? `,取${climate.need.stem}${climate.need.element},忌${climate.avoidElement}` : ',气候平和'}。`,
4635
+ `喜${favor.map(describe).join('、') || '—'};忌${avoid.map(describe).join('、') || '—'}。`,
4636
+ disputed.length ? `有争议:${disputedText}。` : '',
4637
+ !weak && !strong ? '身中和,先取调候与通关,喜忌随大运流年动态变化。' : '',
4638
+ `调候:${climate.label}(${(climate.evidence || []).join('、')})${climate.need ? `,取${climate.need.stem}${climate.need.element},忌${climate.avoidElement}${disputed.some((d) => d.element === climate.avoidElement) ? '(有争议)' : ''}` : ',气候平和'}。`,
4217
4639
  qt ? `穷通宝鉴:${qt.month}${dayStem}${me},用神${qt.yong.join('、')}(首取${qt.primary})。` : '',
4218
4640
  tongguan
4219
4641
  ? `通关:忌${tongguan.foe}克用${tongguan.use},五行以${tongguan.bridge}为关;局中${tongguan.level === '可通'
@@ -4233,10 +4655,12 @@ function yongshenOf(dayStem, monthBranch, strength, wuxing, branches = []) {
4233
4655
  level: strength.level,
4234
4656
  favor,
4235
4657
  avoid,
4658
+ disputed,
4236
4659
  favorRoles: favor.map(roleName),
4237
4660
  avoidRoles: avoid.map(roleName),
4238
4661
  favorTenGods,
4239
4662
  avoidTenGods,
4663
+ disputedTenGods,
4240
4664
  tiaohou: { ...climate, ...arb, element: climate.need ? climate.need.element : null },
4241
4665
  climate,
4242
4666
  qt,
@@ -4382,6 +4806,24 @@ const HIDDEN_STEMS = {
4382
4806
  亥: ['壬', '甲'],
4383
4807
  };
4384
4808
 
4809
+ // 人元司权的 30 日表:余气 → 中气 → 本气,按「节」当日起算。
4810
+ // 各家对日数分法不一(另有 7/7/16、9/3/18 等),这里固定通行 30 日表;
4811
+ // 只作解释层的一层细节,不参与取格。亥月的余气戊不在本柱藏干表中,两表各照原样。
4812
+ const RENYUAN_TABLE = {
4813
+ 寅: [['戊', 7], ['丙', 7], ['甲', 16]],
4814
+ 卯: [['甲', 10], ['乙', 20]],
4815
+ 辰: [['乙', 9], ['癸', 3], ['戊', 18]],
4816
+ 巳: [['戊', 5], ['庚', 9], ['丙', 16]],
4817
+ 午: [['丙', 10], ['己', 9], ['丁', 11]],
4818
+ 未: [['丁', 9], ['乙', 3], ['己', 18]],
4819
+ 申: [['戊', 7], ['壬', 3], ['庚', 20]],
4820
+ 酉: [['庚', 10], ['辛', 20]],
4821
+ 戌: [['辛', 9], ['丁', 3], ['戊', 18]],
4822
+ 亥: [['戊', 7], ['甲', 5], ['壬', 18]],
4823
+ 子: [['壬', 10], ['癸', 20]],
4824
+ 丑: [['癸', 9], ['辛', 3], ['己', 18]],
4825
+ };
4826
+
4385
4827
  const STEM_ELEMENT = {
4386
4828
  甲: '木', 乙: '木', 丙: '火', 丁: '火', 戊: '土',
4387
4829
  己: '土', 庚: '金', 辛: '金', 壬: '水', 癸: '水',
@@ -4558,6 +5000,45 @@ function applyTrueSolar(jd, longitude) {
4558
5000
  };
4559
5001
  }
4560
5002
 
5003
+ /**
5004
+ * 人元司权:生在本月第几日、哪一藏干当权。
5005
+ * 日数按「节令日」与出生日的日差计,节当日为第 1 日;固定 30 日表,
5006
+ * 第 31 日以后不再硬分摊,按本气算并标 beyond。
5007
+ * @param {string} monthBranch 月支(寅…丑)
5008
+ * @param {number} birthJdn 出生日的儒略日数(含真太阳时修正后的日)
5009
+ * @param {number} termJd 本月「节」的儒略日
5010
+ */
5011
+ function renyuanOf(monthBranch, birthJdn, termJd) {
5012
+ const table = RENYUAN_TABLE[monthBranch];
5013
+ if (!table) return null;
5014
+ const segments = [];
5015
+ let from = 1;
5016
+ for (const [stem, days] of table) {
5017
+ segments.push({ stem, element: STEM_ELEMENT[stem], from, to: from + days - 1 });
5018
+ from += days;
5019
+ }
5020
+ const tableDays = from - 1;
5021
+ const day = Math.max(1, Math.floor(birthJdn) - Math.floor(termJd + 0.5) + 1);
5022
+ const beyond = day > tableDays;
5023
+ const idx = beyond ? segments.length - 1 : segments.findIndex((s) => day >= s.from && day <= s.to);
5024
+ const seg = segments[idx] || segments[segments.length - 1];
5025
+ const next = segments[idx + 1] || null;
5026
+ return {
5027
+ month: monthBranch,
5028
+ day,
5029
+ tableDays,
5030
+ segments,
5031
+ active: seg.stem,
5032
+ activeElement: seg.element,
5033
+ segment: { from: seg.from, to: seg.to },
5034
+ next: next ? { stem: next.stem, element: next.element, from: next.from, inDays: next.from - day } : null,
5035
+ // 司权表与本柱藏干表口径不同:亥月的余气戊只出现在司权表里
5036
+ inHidden: (HIDDEN_STEMS[monthBranch] || []).includes(seg.stem),
5037
+ beyond,
5038
+ note: '人元司权按「节」当日起算,节当日为第 1 日,日差用本盘所用时刻(含真太阳时)所在日计;本插件固定 30 日表(余气 → 中气 → 本气),第 31 日以后按本气算。只作解释层的一层细节,不改取格。',
5039
+ };
5040
+ }
5041
+
4561
5042
  /**
4562
5043
  * 排四柱八字
4563
5044
  * @param {object} input { year,month,day,hour,minute, gender:'male'|'female', longitude?, lateZi?:boolean, trueSolar?:boolean }
@@ -4844,6 +5325,16 @@ function buildBazi(input) {
4844
5325
  } else if (tianKeDiChong(w.ganzhi, l.ganzhi)) {
4845
5326
  signals.push({ type: '运年反吟', target: `第${w.step}步大运`, detail: `大运${w.ganzhi}与流年${l.ganzhi}天克地冲` });
4846
5327
  }
5328
+ // 大运与本命四柱的伏吟/反吟:整步大运都在,逐年列出,不按流年另算一次
5329
+ for (const key of ['year', 'month', 'day', 'hour']) {
5330
+ const g = pillars[key].ganzhi;
5331
+ if (!g) continue;
5332
+ if (w.ganzhi === g) {
5333
+ signals.push({ type: '大运伏吟', target: `第${w.step}步大运·${NATAL_LABEL[key]}`, detail: `大运${w.ganzhi}与${NATAL_LABEL[key]}同干支` });
5334
+ } else if (tianKeDiChong(w.ganzhi, g)) {
5335
+ signals.push({ type: '大运反吟', target: `第${w.step}步大运·${NATAL_LABEL[key]}`, detail: `大运${w.ganzhi}与${NATAL_LABEL[key]}${g}天克地冲` });
5336
+ }
5337
+ }
4847
5338
  }
4848
5339
  for (const key of ['year', 'month', 'day', 'hour']) {
4849
5340
  const g = pillars[key].ganzhi;
@@ -4857,7 +5348,7 @@ function buildBazi(input) {
4857
5348
  l.suiyun = {
4858
5349
  dayun: active.map((w) => ({ step: w.step, ganzhi: w.ganzhi })),
4859
5350
  signals,
4860
- note: '只标干支重复与天克地冲这类变动信号,不判吉凶;大运按该年立春到次年立春的区间取,交运落在年内时前后两步并列',
5351
+ note: '只标干支重复与天克地冲这类变动信号,不判吉凶;大运按该年立春到次年立春的区间取,交运落在年内时前后两步并列;大运与本命四柱的伏吟/反吟整步大运都在,逐年重复列出',
4861
5352
  };
4862
5353
  }
4863
5354
 
@@ -4880,6 +5371,10 @@ function buildBazi(input) {
4880
5371
  const bodyStemIdx = (yinStem + ((bodyIdx - 2 + 12) % 12)) % 10;
4881
5372
  const shengong = ganzhiName(sexagenaryIndex(bodyStemIdx, bodyIdx));
4882
5373
 
5374
+ // 人元司权:按节令到出生日的日差定当权藏干(节当日为第 1 日)
5375
+ const renyuan = renyuanOf(BRANCHES[monthBranchIdx], jdnBase, prevJieTerm.jd);
5376
+ if (renyuan) renyuan.term = { name: prevJieTerm.name, time: formatJd(prevJieTerm.jd) };
5377
+
4883
5378
  return {
4884
5379
  system: 'bazi',
4885
5380
  relations: buildRelations(pillars),
@@ -4912,6 +5407,7 @@ function buildBazi(input) {
4912
5407
  // 与 NAOJ 历要项对拍实测节气最多偏 13.5 分钟,20 分钟留出余量。
4913
5408
  nearBoundaryMinutes: Math.round(Math.min(jd - prevJieTerm.jd, nextJieTerm.jd - jd) * 1440),
4914
5409
  },
5410
+ renyuan,
4915
5411
  pillars,
4916
5412
  dayMaster: { stem: dayStem, element: STEM_ELEMENT[dayStem], yinYang: isYangStem(dayStem) ? '阳' : '阴' },
4917
5413
  wuxing,
@@ -4944,7 +5440,7 @@ function buildBazi(input) {
4944
5440
  };
4945
5441
  }
4946
5442
 
4947
- return { HIDDEN_STEMS, STEM_ELEMENT, BRANCH_ELEMENT, buildRelations, tenGod, nayin, applyTrueSolar, buildBazi };
5443
+ return { HIDDEN_STEMS, RENYUAN_TABLE, STEM_ELEMENT, BRANCH_ELEMENT, buildRelations, tenGod, nayin, applyTrueSolar, renyuanOf, buildBazi };
4948
5444
  })();
4949
5445
 
4950
5446
  __mods.liuyao = (function () {
@@ -5016,6 +5512,7 @@ const LIUSHEN_START = { 甲: 0, 乙: 0, 丙: 1, 丁: 1, 戊: 2, 己: 3, 庚: 4,
5016
5512
  const ZHI_CHONG_PAIRS = [['子', '午'], ['丑', '未'], ['寅', '申'], ['卯', '酉'], ['辰', '戌'], ['巳', '亥']];
5017
5513
  const ZHI_HE_PAIRS = [['子', '丑'], ['寅', '亥'], ['卯', '戌'], ['辰', '酉'], ['巳', '申'], ['午', '未']];
5018
5514
  const CHONG_OF = Object.fromEntries(ZHI_CHONG_PAIRS.flatMap(([a, b]) => [[a, b], [b, a]]));
5515
+ const HE_OF = Object.fromEntries(ZHI_HE_PAIRS.flatMap(([a, b]) => [[a, b], [b, a]]));
5019
5516
  const TRIGRAM_OPPOSITE = { 乾: '巽', 巽: '乾', 坎: '离', 离: '坎', 艮: '坤', 坤: '艮', 震: '兑', 兑: '震' };
5020
5517
 
5021
5518
  // ── 化进神 / 化退神 ────────────────────────────────────────────────
@@ -5881,7 +6378,106 @@ function jinTuiPower(result, opts = {}) {
5881
6378
  };
5882
6379
  }
5883
6380
 
5884
- return { jinTuiOf, hexBody, bodyRelations, bodyLabels, HEXAGRAM_COUNT, hexagramFromBits, YONGSHEN_RULES, yongshenOf, tossLines, buildLiuyao, zhiElementOf, monthStateOf, dayRelationOf, yongshenStrength, FUSHEN_VERDICTS, fushenAccess, hexagramStrength, jinTuiPower };
6381
+ // ── 应期线索:只列「该留意哪一支」,不给日期 ─────────────────────────
6382
+ // 知识卡 liuyao-yingqi 的承诺是「只作时间窗口的提示」:传统取用神逢值、
6383
+ // 逢冲、逢合之期,旬空另等出空、冲空。这里只把这些地支列出来,再按月建给
6384
+ // 一个快慢方向;不换算成日期、不排先后、不判忌神受制,也不叠动变连环。
6385
+ const YINGQI_TEMPO = ['偏速', '偏缓', '待解', '未定'];
6386
+ const TEMPO_SAY = {
6387
+ 偏速: '用神得月令之气,应期偏近',
6388
+ 偏缓: '用神失月令之气,应期偏远',
6389
+ 待解: '有月破或旬空的用神,先等条件解除',
6390
+ 未定: '快慢不定(缺月建,或用神两现而旺衰不一)',
6391
+ };
6392
+ const TRIGGER_ORDER = ['逢值', '逢冲', '逢合', '出空填实', '冲空'];
6393
+
6394
+ /**
6395
+ * 应期线索:用神逢值、逢冲、逢合,旬空另标出空与冲空。
6396
+ * @param {object} result buildLiuyao 的结果
6397
+ * @param {object} useGod yongshenOf 的结果
6398
+ * @param {{monthBranch?: string}} opts
6399
+ */
6400
+ function yingqiWindow(result, useGod, opts = {}) {
6401
+ if (!useGod || !useGod.liuqin) return null;
6402
+ const found = Array.isArray(useGod.found) ? useGod.found : [];
6403
+ const fushen = Array.isArray(useGod.fushen) ? useGod.fushen : [];
6404
+ const viaFushen = !found.length && fushen.length > 0;
6405
+ const source = found.length ? found : fushen;
6406
+ if (!source.length) return null;
6407
+ const monthBranch = opts.monthBranch || null;
6408
+ const monthElement = monthBranch ? zhiElementOf(monthBranch) : null;
6409
+ const kong = Array.isArray(result && result.xunKong)
6410
+ ? result.xunKong
6411
+ : (result && typeof result.xunKong === 'string' ? result.xunKong.split('') : []);
6412
+
6413
+ const items = source.map((l) => {
6414
+ const branch = l.branch;
6415
+ const element = l.element || zhiElementOf(branch);
6416
+ const monthState = monthStateOf(element, monthElement);
6417
+ const isEmpty = kong.includes(branch);
6418
+ const monthChong = !!(monthBranch && CHONG_OF[branch] === monthBranch);
6419
+ const tempo = monthChong ? '待解'
6420
+ : isEmpty ? '待解'
6421
+ : monthState === 'unknown' ? '未定'
6422
+ : (monthState === '旺' || monthState === '相') ? '偏速' : '偏缓';
6423
+ const tempoSay = monthChong ? `遭${monthBranch}冲(月破),先待出月或填实`
6424
+ : isEmpty ? '用神旬空,先待出空填实'
6425
+ : monthState === 'unknown' ? '缺月建,快慢不定'
6426
+ : `月建${monthState},${tempo === '偏速' ? '应期偏近' : '应期偏远'}`;
6427
+ const triggers = [
6428
+ { branch, labels: ['逢值'] },
6429
+ { branch: CHONG_OF[branch] || null, labels: ['逢冲'] },
6430
+ { branch: HE_OF[branch] || null, labels: ['逢合'] },
6431
+ ];
6432
+ if (isEmpty) {
6433
+ triggers[0].labels.push('出空填实');
6434
+ triggers[1].labels.push('冲空');
6435
+ }
6436
+ return {
6437
+ position: l.position,
6438
+ branch,
6439
+ element,
6440
+ monthState,
6441
+ monthChong,
6442
+ kong: isEmpty,
6443
+ tempo,
6444
+ tempoSay,
6445
+ triggers,
6446
+ };
6447
+ });
6448
+
6449
+ // 用神两现时各爻的支可能不同:按支归并标签,不替谁选主用
6450
+ const merged = new Map();
6451
+ for (const it of items) {
6452
+ for (const t of it.triggers) {
6453
+ if (!t.branch) continue;
6454
+ const cur = merged.get(t.branch) || { branch: t.branch, labels: [] };
6455
+ for (const lb of t.labels) if (!cur.labels.includes(lb)) cur.labels.push(lb);
6456
+ merged.set(t.branch, cur);
6457
+ }
6458
+ }
6459
+ const triggers = [...merged.values()].map((t) => ({
6460
+ branch: t.branch,
6461
+ labels: [...t.labels].sort((a, b) => TRIGGER_ORDER.indexOf(a) - TRIGGER_ORDER.indexOf(b)),
6462
+ }));
6463
+ const tempos = items.map((x) => x.tempo);
6464
+ const tempo = tempos.includes('待解') ? '待解'
6465
+ : tempos.every((t) => t === '偏速') ? '偏速'
6466
+ : tempos.every((t) => t === '偏缓') ? '偏缓' : '未定';
6467
+
6468
+ return {
6469
+ month: monthBranch ? { branch: monthBranch, element: monthElement } : null,
6470
+ viaFushen,
6471
+ tempo,
6472
+ tempoSay: TEMPO_SAY[tempo],
6473
+ items,
6474
+ triggers,
6475
+ labels: [`应期${tempo}`, ...triggers.map((t) => `${t.labels.join('/')}${t.branch}`)],
6476
+ note: '应期只给「该留意哪一支」与快慢方向:用神逢值、逢冲、逢合,旬空另标出空与冲空。不换算成日期、不排先后、不判忌神受制;近事以日计、远事以月计,用哪把尺由事情远近决定,本插件不代选。',
6477
+ };
6478
+ }
6479
+
6480
+ return { jinTuiOf, hexBody, bodyRelations, bodyLabels, HEXAGRAM_COUNT, hexagramFromBits, YONGSHEN_RULES, yongshenOf, tossLines, buildLiuyao, zhiElementOf, monthStateOf, dayRelationOf, yongshenStrength, FUSHEN_VERDICTS, fushenAccess, hexagramStrength, jinTuiPower, YINGQI_TEMPO, yingqiWindow };
5885
6481
  })();
5886
6482
 
5887
6483
  __mods.ziwei = (function () {
@@ -11050,9 +11646,10 @@ __mods.almanac = (function () {
11050
11646
  // 提供:农历日期、年/月/日干支(年按立春界,另列春节界生肖)、节气、建除十二神、
11051
11647
  // 冲煞(冲什么生肖 / 煞在何方)、财神与喜神方位、建除简表宜忌、每日一卦。
11052
11648
  //
11053
- // 诚实边界:本插件不依赖 lunar-javascript,逐日神煞(吉神凶煞)、二十八宿值日、
11054
- // 彭祖百忌、胎神等全量黄历数据未收录——输出里如实标注「未收录」,不臆造。
11055
- // 宜忌只按「建除十二神」通行简表给出(传统择日的第一层判据),不是逐日全量黄历。
11649
+ // 诚实边界:本插件不依赖 lunar-javascript;逐日全量吉神凶煞与彭祖百忌未收录——
11650
+ // 输出里如实标注「未收录」,不臆造。值日十二神、二十八宿、胎神、吉神方位按通行表收录
11651
+ // (见返回值的 scope)。宜忌只按「建除十二神」通行简表给出(传统择日的第一层判据),
11652
+ // 不是逐日全量黄历。
11056
11653
  const { solarToLunar } = __mods.lunar;
11057
11654
  const { nayinOf } = __mods.nayin;
11058
11655
  const { toJDN, fromJDN, dayGanzhiIndex, ganzhiName, termWindow, termInstantJd, partsToJd, STEMS, BRANCHES, TERM_NAMES } = __mods.calendar;
@@ -11155,7 +11752,7 @@ function tianShenOf(branch, anchorBranch) {
11155
11752
  return { name, type, luck: type === '黄道' ? '吉' : '凶' };
11156
11753
  }
11157
11754
 
11158
- /** 二十八宿:值日宿按「日支 + 星期」轮转(四组各七宿)。 */
11755
+ /** 二十八宿:值日宿按「日支 + 星期」轮转(四组各七宿)。宿名按通行「宿·五行·动物」写法(壁水貐、胃土雉)。 */
11159
11756
  const MANSIONS = {
11160
11757
  角: { luck: '吉', zheng: '木', animal: '蛟', gong: '东' }, 亢: { luck: '凶', zheng: '金', animal: '龙', gong: '东' },
11161
11758
  氐: { luck: '凶', zheng: '土', animal: '貉', gong: '东' }, 房: { luck: '吉', zheng: '日', animal: '兔', gong: '东' },
@@ -11163,9 +11760,9 @@ const MANSIONS = {
11163
11760
  箕: { luck: '吉', zheng: '水', animal: '豹', gong: '东' }, 斗: { luck: '吉', zheng: '木', animal: '獬', gong: '北' },
11164
11761
  牛: { luck: '凶', zheng: '金', animal: '牛', gong: '北' }, 女: { luck: '凶', zheng: '土', animal: '蝠', gong: '北' },
11165
11762
  虚: { luck: '凶', zheng: '日', animal: '鼠', gong: '北' }, 危: { luck: '凶', zheng: '月', animal: '燕', gong: '北' },
11166
- 室: { luck: '吉', zheng: '火', animal: '猪', gong: '北' }, 壁: { luck: '吉', zheng: '水', animal: '獝', gong: '北' },
11763
+ 室: { luck: '吉', zheng: '火', animal: '猪', gong: '北' }, 壁: { luck: '吉', zheng: '水', animal: '貐', gong: '北' },
11167
11764
  奎: { luck: '凶', zheng: '木', animal: '狼', gong: '西' }, 娄: { luck: '吉', zheng: '金', animal: '狗', gong: '西' },
11168
- 胃: { luck: '吉', zheng: '土', animal: '彘', gong: '西' }, 昴: { luck: '凶', zheng: '日', animal: '鸡', gong: '西' },
11765
+ 胃: { luck: '吉', zheng: '土', animal: '雉', gong: '西' }, 昴: { luck: '凶', zheng: '日', animal: '鸡', gong: '西' },
11169
11766
  毕: { luck: '吉', zheng: '月', animal: '乌', gong: '西' }, 觜: { luck: '凶', zheng: '火', animal: '猴', gong: '西' },
11170
11767
  参: { luck: '吉', zheng: '水', animal: '猿', gong: '西' }, 井: { luck: '吉', zheng: '木', animal: '犴', gong: '南' },
11171
11768
  鬼: { luck: '凶', zheng: '金', animal: '羊', gong: '南' }, 柳: { luck: '凶', zheng: '土', animal: '獐', gong: '南' },
@@ -12634,6 +13231,19 @@ function jinTuiPlain(r) {
12634
13231
  return `动爻的进退还要看时令:${jp.lines.map((l) => `${l.position}爻${l.from}→${l.to}${l.type},${sayOf(l)}`).join(';')}。`;
12635
13232
  }
12636
13233
 
13234
+ // 应期线索 → 一句白话:只给快慢与可留意的支,不换算日期
13235
+ function yingqiPlain(r) {
13236
+ const yq = r && r.yingqi;
13237
+ if (!yq || !Array.isArray(yq.triggers) || !yq.triggers.length) return '';
13238
+ const when = yq.tempo === '偏速' ? '大体偏近,近事更容易先见分晓'
13239
+ : yq.tempo === '偏缓' ? '大体偏远,别指望立刻见分晓'
13240
+ : yq.tempo === '待解' ? '眼下还有月破或旬空,得先等条件解除'
13241
+ : '缺月建,快慢暂不定';
13242
+ const branches = yq.triggers.map((t) => t.branch).join('、');
13243
+ const fu = yq.viaFushen ? '(用神是伏神,先要出得来)' : '';
13244
+ return `应期上${when};传统会留意${branches}这几个地支${fu}——这只是提示,不换算成具体哪天。`;
13245
+ }
13246
+
12637
13247
  // 全卦逐爻旺衰 → 一句白话:整张卦的气偏在哪一头(与用神那一句分开说)
12638
13248
  function hexStrengthPlain(hs) {
12639
13249
  if (!hs || !hs.counts) return '';
@@ -12744,7 +13354,7 @@ function buildAnswer(r, ask, ctx = {}) {
12744
13354
  head + `先说结论:本卦是${s(ben.name, '—')}` +
12745
13355
  `${ben.palace ? `(${ben.palace})` : ''}` +
12746
13356
  `${moving.length ? `,第 ${moving.join('、')} 爻动,变为${s(bian.name, '—')}` : ',六爻安静没有动爻'}。` +
12747
- `${plainBody}${usePlainOf(r.useGod, r.fushenAccess)}${useStrengthPlain(r.useGodStrength)}${jinTuiPlain(r)}${hexStrengthPlain(r.hexStrength)}`,
13357
+ `${plainBody}${usePlainOf(r.useGod, r.fushenAccess)}${useStrengthPlain(r.useGodStrength)}${jinTuiPlain(r)}${yingqiPlain(r)}${hexStrengthPlain(r.hexStrength)}`,
12748
13358
  '断卦的落点在「世爻」(代表你)和「动爻」(事情的推动点),下面「看盘顺序」就按这个次序展开。',
12749
13359
  ].join('');
12750
13360
  }
@@ -12770,6 +13380,9 @@ function buildPlain(r, ask, ctx = {}) {
12770
13380
  const ys = (r.frame && r.frame.yongshen) || {};
12771
13381
  const favor = join(ys.favor);
12772
13382
  const avoid = join(ys.avoid);
13383
+ const dis = Array.isArray(ys.disputed) && ys.disputed.length
13384
+ ? `有一点要说明:${ys.disputed.map((d) => d.element).join('、')}在扶抑里算${ys.disputed.map((d) => d.favorRole).join('、')}(能${ys.disputed.map((d) => d.favorVerb).join('、')}),按调候看又偏忌(${ys.disputed.map((d) => `${d.climate}盘忌${d.element}`).join('、')}),两派说法不一;实际用在什么时候,看季节和环境取舍,不必当成绝对的好或坏。`
13385
+ : '';
12773
13386
  const strength = STRENGTH_PLAIN[st.level] || '力量偏中性';
12774
13387
  const tiaohou = ys.tiaohou && ys.tiaohou.note ? `另外,${ys.tiaohou.note},所以「暖一点、亮一点」的环境通常更舒服。` : '';
12775
13388
  const sy = (r.liunian || []).find((l) => l.suiyun && Array.isArray(l.suiyun.signals) && l.suiyun.signals.length);
@@ -12778,7 +13391,7 @@ function buildPlain(r, ask, ctx = {}) {
12778
13391
  : '';
12779
13392
  return `你的命局属于「${s(st.level, '中性')}」:${strength}。` +
12780
13393
  `${favor ? `能补${favor}的环境(行业、方位、作息、相处的人)会让你省力;${avoid ? `而${avoid}偏重的场合容易空耗,能绕就绕。` : ''}` : ''}` +
12781
- tiaohou + syPlain;
13394
+ dis + tiaohou + syPlain;
12782
13395
  }
12783
13396
  if (r.system === 'ziwei') {
12784
13397
  const body = r.body || {};
@@ -12812,7 +13425,7 @@ function buildPlain(r, ask, ctx = {}) {
12812
13425
  const plainBody = bodyPlainOf(labels);
12813
13426
  return `${s(ben.name, '这一卦')}讲的是一个「${moving.length ? '正在动、还会变' : '暂时不动、先看清'}」的局面——` +
12814
13427
  `${moving.length ? '有动爻,说明事情还有转圜空间,不是死局。' : '六爻安静,说明当下更该观察而不是硬推。'}` +
12815
- `${plainBody}${usePlainOf(r.useGod, r.fushenAccess)}${useStrengthPlain(r.useGodStrength)}${jinTuiPlain(r)}${hexStrengthPlain(r.hexStrength)}`;
13428
+ `${plainBody}${usePlainOf(r.useGod, r.fushenAccess)}${useStrengthPlain(r.useGodStrength)}${jinTuiPlain(r)}${yingqiPlain(r)}${hexStrengthPlain(r.hexStrength)}`;
12816
13429
  }
12817
13430
  return '';
12818
13431
  }
@@ -12931,10 +13544,10 @@ const KNOWLEDGE = [
12931
13544
  id: 'bazi-yueling-siquan',
12932
13545
  system: 'bazi', dims: ['overall', 'career'],
12933
13546
  gist: '月令不是「一个月」,是当月最有权的那一个字。',
12934
- detail: '月令藏干按本气、中气、余气分日用事,本气当权的时间最长。所以判旺衰先看日主在月令是得令还是失令——得令者未看全局,已有三分底气。',
12935
- source: '《子平真诠》论用神;人元司权分日之说见《渊海子平》',
13547
+ detail: '月令藏干按本气、中气、余气分日用事,本气当权的时间最长。所以判旺衰先看日主在月令是得令还是失令——得令者未看全局,已有三分底气。本插件按 30 日表算出「生在本月第几日、哪一干当权」,给的是月内轮值这一层细节;旺衰与取格仍以月令本气、透干与会支为主,不随轮值改判。三命通会卷二·论人元司事另列一套日数(寅月五日/五日/二十日),与本插件取的通行表不同;原文并列,不改判。',
13548
+ source: '《子平真诠》论用神;人元司权分日之说见《渊海子平》;三命通会卷二·论人元司事(典藏网 7529)另列一套日数',
12936
13549
  status: 'variant',
12937
- note: '各家对中气、余气各主几日分法不同(有 7/7/16、9/3/18 等多说),本插件只取「本气当权」这一层,未细分日数。',
13550
+ note: '各家对中气、余气各主几日分法不同(有 7/7/16、9/3/18 等多说);本插件固定通行 30 日表(如寅月戊7丙7甲16),节当日为第 1 日,第 31 日以后按本气算、不硬分摊。三命通会此本寅月作 5/5/20、卯月作 7/23,与通行表不同,两表各照原样。司权表与本柱藏干表口径不同(子月壬、卯月甲、午月丙、酉月庚、亥月戊只出现在司权表里),两表各照原样;取格不随轮值改判。',
12938
13551
  },
12939
13552
  {
12940
13553
  id: 'bazi-tonggen-tougan',
@@ -12982,10 +13595,10 @@ const KNOWLEDGE = [
12982
13595
  id: 'bazi-suiyun',
12983
13596
  system: 'bazi', dims: ['overall', 'career'],
12984
13597
  gist: '大运管十年基调,流年管当年触发。',
12985
- detail: '大运像换了条路,流年像这条路上某一段的天气。岁运并临(当值大运与流年干支相同)、运年反吟(大运与流年天克地冲)、流年伏吟(流年与原局某柱干支相同)与流年反吟(流年与原局某柱天克地冲),是传统上标记「变动较大」的年份,属于提示而非断语。本插件逐年标出四类信号:当值大运按该年立春到次年立春的区间取,交运落在年内时前后两步并列;只标干支重复与对冲,不折分数。',
12986
- source: '岁运并临、伏吟反吟见《三命通会》《渊海子平》',
13598
+ detail: '大运像换了条路,流年像这条路上某一段的天气。岁运并临(当值大运与流年干支相同)、运年反吟(大运与流年天克地冲)、大运伏吟(大运与原局某柱干支相同)、大运反吟(大运与原局某柱天克地冲)、流年伏吟(流年与原局某柱干支相同)、流年反吟(流年与原局某柱天克地冲),是传统上标记「变动较大」的年份,属于提示而非断语。本插件逐年标出六类信号:当值大运按该年立春到次年立春的区间取,交运落在年内时前后两步并列;大运与本命四柱的伏吟/反吟整步大运都在,会连续多年重复列出;只标干支重复与对冲,不折分数。原文里「返吟」指岁运与日柱相对、「伏吟」指岁运压日,与本插件「干支相同为伏吟、天克地冲为反吟」不是同一套定义,两者并列、不合并。',
13599
+ source: '《三命通会》卷二·总论岁运(https://www.diancang.xyz/xuanxuewushu/sanmingtonghui/7539.html);岁运并临之说出自《渊海子平》',
12987
13600
  status: 'variant',
12988
- note: '「岁运并临主大凶」是流传最广也最被滥用的说法,本插件只作变动提示,不断吉凶;大运与原局之间的伏吟反吟未逐年标注,四柱十神层面的引化也未叠入。',
13601
+ note: '「岁运并临主大凶」是流传最广也最被滥用的说法,本插件只作变动提示,不断吉凶;大运与本命四柱的伏吟/反吟会连续多年出现,不逐年加重;四柱十神层面的引化也未叠入。',
12989
13602
  },
12990
13603
  {
12991
13604
  id: 'bazi-liuqin',
@@ -13041,6 +13654,15 @@ const KNOWLEDGE = [
13041
13654
  status: 'variant',
13042
13655
  note: '化气格成立条件各家宽严不一(是否要求月令为化神、日主是否允许有根),本插件只列条件、附原文,不据此断定化气已成。',
13043
13656
  },
13657
+ {
13658
+ id: 'bazi-zaji-quyong',
13659
+ system: 'bazi', dims: ['overall', 'career'],
13660
+ gist: '辰戌丑未是杂气月:先看透干,再看会支。',
13661
+ detail: '四墓(辰戌丑未)每个字都藏着不止一气,用神不一,所以叫杂气。子平真诠的取法是:藏干透到天干的先用(甲生辰月,透戊用偏财、透癸用正印、透乙用月劫);没有透干,再看地支有没有与月支会成三合局(辰月逢申子会水,就取水印)。本插件按「透干 → 三合会支 → 本气」的次序取格,依据里写明走的是哪一条,并把《论杂气如何取用》附在结果里。',
13662
+ source: '《子平真诠》十六、论杂气如何取用',
13663
+ status: 'variant',
13664
+ note: '原文说「兼透则兼用」,但只给一个格名时总要落到一个藏干;本插件只取最先透出的那一干,会支也只认三合、不认三会方,且不判原文所说的「有情 / 无情」,取舍次序各家另有说法。',
13665
+ },
13044
13666
  {
13045
13667
  id: 'bazi-zhuanwang-yuanwen',
13046
13668
  system: 'bazi', dims: ['overall', 'career'],
@@ -13206,10 +13828,10 @@ const KNOWLEDGE = [
13206
13828
  id: 'liuyao-yingqi',
13207
13829
  system: 'liuyao', dims: ['overall', 'career'],
13208
13830
  gist: '应期是「什么时候见分晓」,不是「哪天」。',
13209
- detail: '近事以日计、远事以月计;常见取法是等用神逢值、逢冲、逢合之期,或等忌神受制之期。应期法各家互有出入,同一卦不同取法可以差出很远,所以只作时间窗口的提示。',
13831
+ detail: '近事以日计、远事以月计;常见取法是等用神逢值、逢冲、逢合之期,或等忌神受制之期。本插件只做前一半:把用神(不上卦则取伏神)的逢值、逢冲、逢合三支列出,旬空另标出空与冲空,再按月建五态给「偏速 / 偏缓 / 待解 / 未定」一个快慢方向;忌神受制与具体日期仍未做。应期法各家互有出入,同一卦不同取法可以差出很远,所以只作时间窗口的提示。',
13210
13832
  source: '《卜筮正宗》应期章;《增删卜易》',
13211
13833
  status: 'variant',
13212
- note: '应期取法(值日、冲、合、填实、旬空出空)各家优先级不同,本插件不做具体日期推算。',
13834
+ note: '应期取法(值日、冲、合、填实、旬空出空)各家优先级不同,本插件只并列触发支、不排先后,也不换算日期;近事以日计、远事以月计,用哪把尺由事情远近决定,不代选。',
13213
13835
  },
13214
13836
  {
13215
13837
  id: 'liuyao-liushen',
@@ -13366,10 +13988,10 @@ const KNOWLEDGE = [
13366
13988
  id: 'liuyao-yingqi-exact',
13367
13989
  system: 'liuyao', dims: [],
13368
13990
  gist: '具体应期(哪一天应验)本插件尚未推算。',
13369
- detail: '应期需要综合用神旺衰、动变、旬空出空、日月冲合多层条件,且各家取法不同。当前只给时间窗口的方向性提示,不给具体日期——与其给一个看起来精确、实则拍脑袋的日子,不如留白。',
13991
+ detail: '应期需要综合用神旺衰、动变、旬空出空、日月冲合多层条件,且各家取法不同。当前只给时间窗口的方向性提示(逢值 / 逢冲 / 逢合的触发支,加一个快慢方向),不给具体日期——与其给一个看起来精确、实则拍脑袋的日子,不如留白。',
13370
13992
  source: '待补:《卜筮正宗》应期章逐条实现 + 已录 128 条卦例对拍(源 138 条)',
13371
13993
  status: 'todo',
13372
- note: '扩展方向:用《卜筮正宗》卦例里有明确应验日期的条目做回归,先验证再输出。',
13994
+ note: '扩展方向:用《卜筮正宗》卦例里有明确应验日期的条目做回归,先验证再输出。0.50.0 已给出触发支与快慢方向,仍不输出日期。',
13373
13995
  },
13374
13996
  ];
13375
13997
 
@@ -13988,6 +14610,7 @@ function baziInterpret(r) {
13988
14610
  const level = pick(st, 'level', '中和');
13989
14611
  const favor = Array.isArray(ys.favor) ? ys.favor : [];
13990
14612
  const avoid = Array.isArray(ys.avoid) ? ys.avoid : [];
14613
+ const disputed = Array.isArray(ys.disputed) ? ys.disputed : [];
13991
14614
  const primary = pick(frame, 'primaryName', '未取定');
13992
14615
  const gender = pick(r.input, 'gender', '');
13993
14616
  const isMale = gender === 'male';
@@ -14213,6 +14836,21 @@ function baziInterpret(r) {
14213
14836
  `⑤ 落到维度:工作看喜用五行与十神权重;感情看配偶星与日支;健康看最旺与最弱的五行。五维度都只是这套推导的应用,不是另一套算法。`,
14214
14837
  ].join('\n'),
14215
14838
  },
14839
+ ...(r.renyuan ? [(() => {
14840
+ const ry = r.renyuan;
14841
+ const table = ry.segments.map((s) => `${s.stem}${s.from}–${s.to}`).join(' / ');
14842
+ const nextSeg = ry.next ? `,第 ${ry.next.from} 日起交 ${ry.next.stem}${ry.next.element}` : '';
14843
+ const outside = ry.inHidden ? '' : ` 表里的 ${ry.active} 不在本柱藏干表中(属余气口径),两表各照原样,不互相改写。`;
14844
+ const beyond = ry.beyond ? ' 你生在第 31 日以后,已超出 30 日表,按本气算,不硬分摊。' : '';
14845
+ const ryc = r.renyuanClassic || classicOf('smt-renyuan');
14846
+ const src = ryc ? `\n原文:《${CLASSIC_BOOKS[ryc.book].name}·${ryc.chapter}》「${ryc.quote}」\n白话:${ryc.plain}` : '';
14847
+ return {
14848
+ key: 'renyuan', label: '人元司权',
14849
+ text: `生于${ry.month}月第 ${ry.day} 日(从${ry.term ? ry.term.name : '节'}起算,节当日为第 1 日,日差按本盘所用时刻所在日计):这一日由 **${ry.active}${ry.activeElement}** 司权,第 ${ry.segment.from}–${ry.segment.to} 日属它${nextSeg}。\n`
14850
+ + '月令这一个月里,余气、中气、本气轮值,不是整月同一个五行当值——所以「月令是什么」,还要看生在第几日。\n'
14851
+ + `**口径**:本插件固定通行 30 日表(${table});另有 7/7/16、9/3/18 等分法,三命通会此本又作 5/5/20(见下原文),各家不一。分日只作解释层的一层细节,**不改取格**:取格仍按本气、透干与会支判定。${outside}${beyond}` + src,
14852
+ };
14853
+ })()] : []),
14216
14854
  {
14217
14855
  key: 'timeline', label: '时间窗',
14218
14856
  text: (() => {
@@ -14233,6 +14871,8 @@ function baziInterpret(r) {
14233
14871
  { key: 'advice', label: '专业建议', text: adviceText },
14234
14872
  (() => {
14235
14873
  const hits = (r.liunian || []).filter((l) => l.suiyun && Array.isArray(l.suiyun.signals) && l.suiyun.signals.length);
14874
+ const e = suiyunClassicOf(r);
14875
+ const src = e ? `\n原文:《${CLASSIC_BOOKS[e.book].name}·${e.chapter}》「${e.quote}」\n白话:${e.plain}` : '';
14236
14876
  return {
14237
14877
  key: 'suiyun', label: '岁运提示',
14238
14878
  text: hits.length
@@ -14240,8 +14880,8 @@ function baziInterpret(r) {
14240
14880
  const stem = (l.suiyun.dayun || []).map((d) => `第${d.step}步${d.ganzhi}`).join('、');
14241
14881
  return `${l.year} ${l.ganzhi}(${stem || '未到起运'}):${l.suiyun.signals.map((s) => s.detail).join(';')}`;
14242
14882
  }).join('\n') +
14243
- '\n读法:岁运并临、运年反吟与流年伏吟/反吟只说明干支层面重复或对冲,传统上当作「变动感较强」的信号;本插件不据此判吉凶,要落到具体年份,仍看该年是否动到用神。'
14244
- : '近十年没有岁运并临、运年反吟或流年伏吟/反吟。「岁运并临主大凶」这类说法流传很广,本插件只把它当变动提示,没有命中就不作额外解读。',
14883
+ '\n读法:岁运并临、运年反吟、大运与流年的伏吟/反吟只说明干支层面重复或对冲,传统上当作「变动感较强」的信号;本插件不据此判吉凶,要落到具体年份,仍看该年是否动到用神。大运与本命四柱的伏吟/反吟整步大运都在,会连续多年出现,不当成逐年加重。' + src
14884
+ : '近十年没有岁运并临、运年反吟或大运/流年伏吟、反吟。「岁运并临主大凶」这类说法流传很广,本插件只把它当变动提示,没有命中就不作额外解读。',
14245
14885
  };
14246
14886
  })(),
14247
14887
  {
@@ -14278,7 +14918,7 @@ function baziInterpret(r) {
14278
14918
  ...(cli ? [{
14279
14919
  key: 'climate', label: '寒暖燥湿',
14280
14920
  text: `${cli.narration}${cli.need
14281
- ? `调候取${cli.need.stem}${cli.need.element},忌${cli.avoidElement};${cli.clash ? '与扶抑冲突,本盘以调候为先' : '与扶抑同向'}`
14921
+ ? `调候取${cli.need.stem}${cli.need.element},忌${cli.avoidElement}${disputed.some((d) => d.element === cli.avoidElement) ? '(有争议:该行在扶抑中为喜,见用神节)' : ''};${cli.clash ? '与扶抑冲突,本盘以调候为先' : disputed.some((d) => d.element === cli.avoidElement) ? '调候所忌在扶抑里为喜,两说并存' : '与扶抑同向'}`
14282
14922
  : '气候平和,调候不作主取'}。`,
14283
14923
  }] : []),
14284
14924
  ...(ys.qt ? [{
@@ -15025,10 +15665,32 @@ function liuyaoInterpret(r, ask) {
15025
15665
  useText.push(`时令对用神:${head},日辰 ${strength.day ? `${strength.day.branch}(${strength.day.element})` : '—'}。${body.join(';')}。`);
15026
15666
  useText.push('月建看这一段时令站在哪一边,日辰看当天那股力量是扶还是克;两处同时看,只报状态,不折成分数。');
15027
15667
  }
15028
- useText.push('这一层只把用神、卦体与时令对起来,应期与成败还要看动爻变化与月建日辰的进退节奏,所以这里不折成分数、不断成败。');
15668
+ useText.push('这一层只把用神、卦体与时令对起来,不折成分数、不断成败;应期另在「应期线索」一节只给触发支与快慢。');
15029
15669
  sections.push({ key: 'yongshen', label: '用神落位与卦体', text: useText.join('\n') });
15030
15670
  }
15031
15671
 
15672
+ // 应期线索:只给触发支与快慢方向,不给日期
15673
+ {
15674
+ const yq = r.yingqi;
15675
+ if (yq && Array.isArray(yq.items) && yq.items.length) {
15676
+ const rows = yq.items.map((it) => {
15677
+ const flags = [
15678
+ it.monthState && it.monthState !== 'unknown' ? `月建${it.monthState}` : '',
15679
+ it.monthChong ? '月破' : '',
15680
+ it.kong ? '旬空' : '',
15681
+ ].filter(Boolean).join('、');
15682
+ return `${it.position}爻${it.branch}${it.element}${flags ? `(${flags})` : ''}:${it.tempoSay}`;
15683
+ });
15684
+ sections.push({
15685
+ key: 'yingqi',
15686
+ label: '应期线索',
15687
+ text: `应期快慢:${yq.tempoSay}。${rows.join(';')}。可留意的地支:${yq.triggers.map((t) => `${t.branch}(${t.labels.join('、')})`).join(';')}。` +
15688
+ `${yq.viaFushen ? '用神不上卦,这里的支取自伏神;伏神要先得出才算数。' : ''}` +
15689
+ '这一节只把传统的「逢值、逢冲、逢合」与旬空的出空、冲空摆出来,不换算成具体日期、不排先后、也不判忌神受制;近事以日计、远事以月计,用哪把尺取决于事情本身。',
15690
+ });
15691
+ }
15692
+ }
15693
+
15032
15694
  // 全卦逐爻旺衰:不挑用神,六爻都过一遍,看整张卦的气压在哪一头
15033
15695
  {
15034
15696
  const hs = r.hexStrength;
@@ -15058,7 +15720,7 @@ function liuyaoInterpret(r, ask) {
15058
15720
 
15059
15721
  sections.push({
15060
15722
  key: 'limits', label: '未纳入',
15061
- text: '本卦没有计算:用神两现时该取哪一爻的权重、伏神出伏的应期(出伏条件已判,应期仍未做)。化进/化退的力重只按月建五态给「得令/失令」这类档位,不折成加权分数,也不叠动变连环;这些都会改变"旺不旺"的判断,所以这里只给取象与方向。',
15723
+ text: '本卦没有计算:用神两现时该取哪一爻的权重、应期的具体日期(只给逢值/逢冲/逢合的触发支与快慢方向)、忌神受制的时点、动变连环。化进/化退的力重只按月建五态给「得令/失令」这类档位,不折成加权分数;这些都会改变"旺不旺"的判断,所以这里只给取象与方向。',
15062
15724
  });
15063
15725
 
15064
15726
  const dimensions = [
@@ -15484,7 +16146,7 @@ const BUILDERS = {
15484
16146
  const { interpretQimen } = __mods['qimen-interpret'];
15485
16147
  const { buildAnswer, buildPlain, buildGlossary, buildFollowups, dimensionPlain } = __mods['interpret-plain'];
15486
16148
  const { knowledgeFor, caveatsFor, STATUS_LABEL } = __mods['interpret-knowledge'];
15487
- const { CLASSIC_BOOKS } = __mods.classics;
16149
+ const { CLASSIC_BOOKS, suiyunClassicOf, classicOf } = __mods.classics;
15488
16150
 
15489
16151
  /**
15490
16152
  * 给任意体系的解析结果补上「白话四层」:直答 / 说人话 / 每维一句话 / 追问建议。
@@ -16012,16 +16674,33 @@ function crossCheck(bazi, zw) {
16012
16674
  const bad = ['忌', '大忌'];
16013
16675
  const baziGood = good.includes(t);
16014
16676
  const baziBad = bad.includes(t);
16677
+ const baziKnown = t !== '—';
16015
16678
  const ziweiHeavy = sha >= 2;
16679
+ const ziweiKnown = !!d;
16016
16680
  let stance = '有张力';
16017
- let note = '一边顺、一边压,按「大方向跟顺的一边、具体动作避开压的年份」处理';
16018
- if ((baziGood && !ziweiHeavy) || (baziBad && ziweiHeavy) || (!baziGood && !baziBad && !ziweiHeavy)) {
16681
+ let note = '';
16682
+ if (!baziKnown || !ziweiKnown) {
16683
+ // 一边没取到就谈不上「同向 / 有张力」——只并列现有的一侧,并写清缺的是哪边
16684
+ stance = '互补';
16685
+ const missing = !baziKnown && !ziweiKnown
16686
+ ? '八字当前大运与紫微当前大限都没取到'
16687
+ : !baziKnown
16688
+ ? `八字当前大运未取到(${t}),只有紫微一侧可参考`
16689
+ : '紫微当前大限未取到,只有八字一侧可参考';
16690
+ note = `${missing}:这一项只作提醒,不下两边对照的结论`;
16691
+ } else if (baziGood && ziweiHeavy) {
16692
+ note = '八字这边顺、紫微大限煞星偏多:大方向可参考八字,具体动作按紫微标出的位置收一收';
16693
+ } else if (baziBad && !ziweiHeavy) {
16694
+ note = '八字这边偏紧、紫微大限没见到明显煞星:先按八字收着走,紫微一侧的平静不等于没有压力';
16695
+ } else if (!baziGood && !baziBad && ziweiHeavy) {
16696
+ note = '八字大运为平、紫微大限煞星偏多:只把紫微这一侧当作提醒,不据此判断阶段好坏';
16697
+ } else {
16019
16698
  stance = '同向';
16020
16699
  note = '两边对当前阶段的松紧判断接近';
16021
16700
  }
16022
16701
  push('rhythm', '当前节奏',
16023
16702
  `大运档位 ${t}`,
16024
- `${d ? `大限在${d.palace}` : '大限未取到'}${sha ? `;煞星 ${sha} 颗` : ';无煞星'}`,
16703
+ d ? `大限在${d.palace}${sha ? `;煞星 ${sha} 颗` : ';无煞星'}` : '大限未取到',
16025
16704
  stance, note);
16026
16705
  }
16027
16706
 
@@ -16036,8 +16715,17 @@ function crossText(cross) {
16036
16715
  .map((i) => `· ${i.label}:八字——${i.bazi};紫微——${i.ziwei};判断:${i.stance}(${i.note})`)
16037
16716
  .join('\n');
16038
16717
  }
16718
+
16719
+ /**
16720
+ * 交叉验证分节。文本给 Markdown/工具输出,items 给面板画对照卡——
16721
+ * 同一份数据两个去处,免得面板再去拆文本。
16722
+ */
16723
+ function crossSection(cross) {
16724
+ const items = cross && Array.isArray(cross.items) ? cross.items : [];
16725
+ return { key: 'crossSystem', label: '八字 × 紫微 交叉验证', text: crossText(cross), items };
16726
+ }
16039
16727
 
16040
- return { crossCheck, crossText };
16728
+ return { crossCheck, crossText, crossSection };
16041
16729
  })();
16042
16730
 
16043
16731
  __mods.verify = (function () {
@@ -16246,7 +16934,7 @@ return { matchEvent, rectifyBirth, RECTIFY_KINDS };
16246
16934
  __mods.index = (function () {
16247
16935
  // 统一入口:解析参数、分派到各体系、生成可读摘要
16248
16936
  const { buildBazi } = __mods.bazi;
16249
- const { buildLiuyao, tossLines, bodyLabels, yongshenOf, yongshenStrength, fushenAccess, hexagramStrength, jinTuiPower } = __mods.liuyao;
16937
+ const { buildLiuyao, tossLines, bodyLabels, yongshenOf, yongshenStrength, fushenAccess, hexagramStrength, jinTuiPower, yingqiWindow } = __mods.liuyao;
16250
16938
  const { buildZiwei, BRIGHTNESS, flowOf, asOfParts, ZIWEI_SCHOOL_DEFAULTS } = __mods.ziwei;
16251
16939
  const { drawTarot } = __mods.tarot;
16252
16940
  const { buildMeihua } = __mods.meihua;
@@ -16259,7 +16947,7 @@ const { toJDN, ganzhiName, dayGanzhiIndex } = __mods.calendar;
16259
16947
  const { randomOf, isSeed } = __mods.random;
16260
16948
  const { interpret, DIRECTIONS, DIRECTION_DEFAULT } = __mods.interpret;
16261
16949
  const { followup, askPrompt } = __mods.followup;
16262
- const { crossCheck, crossText } = __mods.crosscheck;
16950
+ const { crossCheck, crossText, crossSection } = __mods.crosscheck;
16263
16951
  const { pastSignals, verifyFit } = __mods.verify;
16264
16952
  const { rectifyBirth } = __mods.rectify;
16265
16953
  const { buildAlmanac } = __mods.almanac;
@@ -16270,7 +16958,8 @@ const { buildStar } = __mods.star;
16270
16958
  const { buildNumerology } = __mods.numerology;
16271
16959
  const { buildMbti, mbtiPaper } = __mods.mbti;
16272
16960
  const { buildConvert } = __mods.convert;
16273
- const { classicsFor, classicsText } = __mods.classics;
16961
+ const { classicsFor, classicsText, suiyunClassicOf, classicOf, CLASSIC_BOOKS } = __mods.classics;
16962
+ const { diffObjects, summarize, sortDiffs, labelOf, rootOf, categoryOf, formatValue, CALIBER_SWITCHES } = __mods.diff;
16274
16963
  const { buildTrend } = __mods.trend;
16275
16964
  const { buildFengshui } = __mods.fengshui;
16276
16965
  const { buildName } = __mods.name;
@@ -16372,12 +17061,22 @@ function baziText(r) {
16372
17061
  ? `神煞(${r.shensha.count} 见):` + r.shensha.groups.map((g) =>
16373
17062
  `${g.cat}|` + g.items.map((i) => `${i.name}(${i.pillars.map((k) => SHORT[k]).join('')})`).join('、')).join(';')
16374
17063
  : '神煞:—';
17064
+ const ry = r.renyuan;
17065
+ const rySrc = classicOf('smt-renyuan');
17066
+ const renyuanLine = ry
17067
+ ? `人元:${ry.month}月第${ry.day}日,${ry.active}${ry.activeElement}司权(${ry.segments.map((s) => `${s.stem}${s.from}–${s.to}`).join(' ')})${ry.beyond ? ';已过 30 日表,按本气算' : ''}${rySrc ? `|原文见《${CLASSIC_BOOKS[rySrc.book].name}·${rySrc.chapter}》` : ''}`
17068
+ : '';
17069
+ const ysd = (r.frame && r.frame.yongshen && r.frame.yongshen.disputed) || [];
17070
+ const disText = ysd.length
17071
+ ? ` 有争议:${ysd.map((d) => `${d.element}(${d.favorRole};${d.climate}盘调候忌${d.element})`).join('、')}`
17072
+ : '';
17073
+ const disEls = new Set(ysd.map((d) => d.element));
16375
17074
  const gejuLine = r.frame ? [
16376
17075
  `格局:${r.frame.primary ? `${r.frame.primary.name}(${r.frame.primary.evidence})` : '—'}`,
16377
17076
  `   ${r.frame.geju.map((g) => `${g.name}[${g.kind}]`).join(' ')}`,
16378
- `用神:喜${r.frame.yongshen.favor.join('')}(${r.frame.yongshen.favorTenGods.join('、')}) 忌${r.frame.yongshen.avoid.join('')}(${r.frame.yongshen.avoidTenGods.join('、')}) ${r.frame.yongshen.method}`,
17077
+ `用神:喜${r.frame.yongshen.favor.join('')}(${r.frame.yongshen.favorTenGods.join('、')}) 忌${r.frame.yongshen.avoid.join('')}(${r.frame.yongshen.avoidTenGods.join('、')})${disText} ${r.frame.yongshen.method}`,
16379
17078
  `   ${r.frame.yongshen.detail}`,
16380
- `寒暖:${r.climate ? `${r.climate.label}(${(r.climate.evidence || []).join('、')})${r.climate.need ? ` 调候取${r.climate.need.stem}${r.climate.need.element} 忌${r.climate.avoidElement}` : ''}` : '—'}` +
17079
+ `寒暖:${r.climate ? `${r.climate.label}(${(r.climate.evidence || []).join('、')})${r.climate.need ? ` 调候取${r.climate.need.stem}${r.climate.need.element} 忌${r.climate.avoidElement}${disEls.has(r.climate.avoidElement) ? '(有争议)' : ''}` : ''}` : '—'}` +
16381
17080
  (r.frame.yongshen.qt ? ` 穷通:${r.frame.yongshen.qt.month}${r.frame.yongshen.qt.stem}取${r.frame.yongshen.qt.yong.join('、')}(首取${r.frame.yongshen.qt.primary})` : ''),
16382
17081
  `十神权重:${Object.entries(r.frame.godStats.groups).map(([k, v]) => `${k}${v}`).join(' ')}`,
16383
17082
  ] : [];
@@ -16394,15 +17093,18 @@ function baziText(r) {
16394
17093
  shenshaLine,
16395
17094
  `真太阳时:${r.solarTime.trueSolarText}(${r.input.geo ? r.input.geo.note + ';' : ''}${r.solarTime.note}) 晚子时:${r.input.lateZi ? '23:00 后算次日' : '23:00 后仍算当日'}`,
16396
17095
  `节气:上一节 ${r.jieqi.previous.name} ${r.jieqi.previous.time} 下一节 ${r.jieqi.next.name} ${r.jieqi.next.time}`,
17096
+ ...(renyuanLine ? [renyuanLine] : []),
16397
17097
  `大运:${r.dayun.rule}(${r.dayun.direction}),${r.dayun.startAge.years}岁${r.dayun.startAge.months}个月起运${jiao}`,
16398
17098
  ` ${dayun}`,
16399
17099
  `流年:${r.liunian.slice(0, 10).map((l) => `${l.year}${l.ganzhi}(${l.stemTenGod})${l.luck ? `·${l.luck.tier}` : ''}`).join(' ')}`,
16400
17100
  (() => {
16401
17101
  const hits = r.liunian.filter((l) => l.suiyun && l.suiyun.signals && l.suiyun.signals.length);
16402
- if (!hits.length) return '岁运提示:近十年无岁运并临、运年反吟或流年伏吟/反吟(只标干支,不判吉凶)';
16403
- const label = (s) => `${s.type}${s.type.startsWith('流年') && s.target ? `(${s.target})` : ''}`;
17102
+ if (!hits.length) return '岁运提示:近十年无岁运并临、运年反吟或大运/流年伏吟、反吟(只标干支,不判吉凶)';
17103
+ const label = (s) => `${s.type}${(s.type.startsWith('流年') || s.type.startsWith('大运')) && s.target ? `(${s.target})` : ''}`;
17104
+ const e = suiyunClassicOf(r);
17105
+ const src = e ? `|古籍见《${CLASSIC_BOOKS[e.book].name}·${e.chapter}》` : '';
16404
17106
  return '岁运提示(只标干支变动,不判吉凶):' + hits
16405
- .map((l) => `${l.year}${l.ganzhi} ${l.suiyun.signals.map(label).join('/')}`).join(';');
17107
+ .map((l) => `${l.year}${l.ganzhi} ${l.suiyun.signals.map(label).join('/')}`).join(';') + src;
16406
17108
  })(),
16407
17109
  '(格局、用神、行运吉凶为扶抑+简化调候的量化提示,非断语。)',
16408
17110
  ].join('\n') + near;
@@ -16654,6 +17356,11 @@ function liuyaoText(r) {
16654
17356
  if (jt && Array.isArray(jt.lines) && jt.lines.length) {
16655
17357
  out.push(`进退力重:${jt.lines.map((l) => `${l.position}爻${l.from}→${l.to}${l.type}·${l.verdict}`).join(' · ')}`);
16656
17358
  }
17359
+ // 应期线索:只列地支与快慢,不给日期
17360
+ const yq = r.yingqi;
17361
+ if (yq && Array.isArray(yq.triggers) && yq.triggers.length) {
17362
+ out.push(`应期线索:${yq.tempo} · ${yq.triggers.map((t) => `${t.labels.join('/')}${t.branch}`).join(' · ')}(只列地支,不出日期)`);
17363
+ }
16657
17364
  return out.filter(Boolean).join('\n');
16658
17365
  }
16659
17366
 
@@ -16674,6 +17381,14 @@ function compute(args = {}) {
16674
17381
  r.classics = classicsFor(r);
16675
17382
  const ct = classicsText(r.classics);
16676
17383
  if (ct) r.summary = `${r.summary}\n【古籍】\n${ct}`;
17384
+ // 岁运提示的原文不占五条上限:只在盘面真有岁运信号时挂到结果上,摘要、解析层与面板各自取用
17385
+ const syc = suiyunClassicOf(r);
17386
+ if (syc) r.suiyunClassic = { ...syc, bookName: CLASSIC_BOOKS[syc.book].name };
17387
+ // 人元司事的原文同样不进五条取条表:每张八字盘都有人元,随「人元司权」一节给出
17388
+ if (r.renyuan) {
17389
+ const ryc = classicOf('smt-renyuan');
17390
+ if (ryc) r.renyuanClassic = { ...ryc, bookName: CLASSIC_BOOKS[ryc.book].name };
17391
+ }
16677
17392
  }
16678
17393
  // 超出 ΔT 覆盖范围时把精度提示顶到摘要最前,别让远期盘悄悄出错
16679
17394
  if (r && r.precision && r.precision.level === 'approx' && r.summary) {
@@ -16708,7 +17423,7 @@ function compute(args = {}) {
16708
17423
  : buildBazi({ ...common, lateZi: args.lateZi !== false, nowYear: asOf ? asOf.year : undefined });
16709
17424
  r.cross = crossCheck(r.system === 'bazi' ? r : other, r.system === 'ziwei' ? r : other);
16710
17425
  if (r.interpret) {
16711
- r.interpret.sections.push({ key: 'crossSystem', label: '八字 × 紫微 交叉验证', text: crossText(r.cross) });
17426
+ r.interpret.sections.push(crossSection(r.cross));
16712
17427
  }
16713
17428
  }
16714
17429
  // 验前事:把过去若干年的流年信号摊开(只读,不改盘)
@@ -16794,6 +17509,69 @@ function interpretText(i) {
16794
17509
  return out.join('\n');
16795
17510
  }
16796
17511
 
17512
+ /** 对照时不参与计算的参数:它们只影响解析/附加模块,不影响盘面 */
17513
+ const CALIBER_IGNORE_ARGS = ['cross', 'verifyPast', 'withInterpret', 'question', 'direction', 'ask', 'geocode', 'rectify', 'events'];
17514
+
17515
+ /**
17516
+ * 口径对照:同一组输入,只把某一个口径开关换成它的另一档,逐字段列出变化。
17517
+ * 这一步不新增断法,也不改盘;返回根因字段、分类计数与一句话总结,供面板/独立页/工具共用。
17518
+ */
17519
+ function caliberCompare(args = {}, opts = {}) {
17520
+ const system = opts.system || args.system;
17521
+ const switches = opts.switches || CALIBER_SWITCHES[system] || [];
17522
+ if (!switches.length) return { system, switches: [], affected: 0, total: 0 };
17523
+ const base = { ...args };
17524
+ for (const k of CALIBER_IGNORE_ARGS) delete base[k];
17525
+ const rows = switches.map((sw) => {
17526
+ const current = sw.read(base);
17527
+ const alt = sw.alt(current);
17528
+ let diffs = [];
17529
+ let error = '';
17530
+ try {
17531
+ diffs = diffObjects(
17532
+ compute({ ...base, [sw.key]: current }),
17533
+ compute({ ...base, [sw.key]: alt }),
17534
+ );
17535
+ } catch (e) {
17536
+ error = (e && e.message) || String(e);
17537
+ }
17538
+ const roots = sortDiffs(diffs.filter((d) => rootOf(d.path))).map((d) => ({
17539
+ path: d.path,
17540
+ label: labelOf(d.path),
17541
+ from: formatValue(d.from),
17542
+ to: formatValue(d.to),
17543
+ }));
17544
+ const counts = new Map();
17545
+ for (const d of diffs) {
17546
+ const c = categoryOf(d.path) || '其他';
17547
+ counts.set(c, (counts.get(c) || 0) + 1);
17548
+ }
17549
+ const categories = [...counts.entries()]
17550
+ .map(([name, count]) => ({ name, count }))
17551
+ .sort((a, b) => b.count - a.count || a.name.localeCompare(b.name, 'zh-Hans-CN'));
17552
+ return {
17553
+ key: sw.key,
17554
+ title: sw.title,
17555
+ short: sw.short,
17556
+ current,
17557
+ alt,
17558
+ currentLabel: sw.label(current),
17559
+ altLabel: sw.label(alt),
17560
+ total: diffs.length,
17561
+ summary: error ? '' : summarize(diffs),
17562
+ roots,
17563
+ categories,
17564
+ error,
17565
+ };
17566
+ });
17567
+ return {
17568
+ system,
17569
+ switches: rows,
17570
+ affected: rows.filter((r) => !r.error && r.total > 0).length,
17571
+ total: rows.reduce((n, r) => n + r.total, 0),
17572
+ };
17573
+ }
17574
+
16797
17575
  const JU_METHODS = ['chaibu', 'chaoshen', 'zhirun'];
16798
17576
  const TAROT_COUNTS = [1, 3, 5];
16799
17577
  const LATE_ZI_MODES = ['same-day', 'next-day'];
@@ -16991,6 +17769,9 @@ function computeRaw(args = {}) {
16991
17769
  // 伏神出伏条件:飞伏生克叠月建日辰,只判条件、不出应期
16992
17770
  const chufu = fushenAccess(result, ug, { monthBranch, dayBranch: result.dayGanZhi.branch });
16993
17771
  if (chufu) result.fushenAccess = chufu;
17772
+ // 应期线索:只列逢值/逢冲/逢合与旬空的出空/冲空,再给快慢方向,不换算日期
17773
+ const yq = yingqiWindow(result, ug, { monthBranch });
17774
+ if (yq) result.yingqi = yq;
16994
17775
  }
16995
17776
  if (args.seed !== undefined && args.seed !== null && args.seed !== '') result.seed = Number(args.seed);
16996
17777
  return { ...result, ...(asOf ? { asOf } : {}), summary: liuyaoText(result) };
@@ -17416,6 +18197,15 @@ function renderText(result) {
17416
18197
  if (extra) parts.push(extra);
17417
18198
  }
17418
18199
  if (result.cross) parts.push(`【八字 × 紫微 交叉验证】\n${crossText(result.cross)}`);
18200
+ if (result.caliber && Array.isArray(result.caliber.switches) && result.caliber.switches.length) {
18201
+ const c = result.caliber;
18202
+ parts.push('【口径对照】同一组输入,只换一个口径(不判哪套更准,也不改盘面)\n'
18203
+ + c.switches.map((s) => {
18204
+ if (s.error) return `· ${s.title}:未能对照(${s.error})`;
18205
+ if (!s.total) return `· ${s.title}:${s.currentLabel} → ${s.altLabel},本盘无变化`;
18206
+ return `· ${s.title}:${s.currentLabel} → ${s.altLabel}\n ${s.summary}`;
18207
+ }).join('\n'));
18208
+ }
17419
18209
  if (result.followup) {
17420
18210
  const f = result.followup;
17421
18211
  parts.push(`【追问】${f.question}\n${f.answer}${f.evidence && f.evidence.length ? `\n 依据:${f.evidence.join(';')} 置信度:${f.confidence}` : ''}`);
@@ -17455,7 +18245,7 @@ function renderText(result) {
17455
18245
 
17456
18246
 
17457
18247
 
17458
- return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAddress, resolveAddress, buildBazi, buildLiuyao, tossLines, followup, askPrompt, pastSignals, verifyFit, rectifyBirth, crossCheck, crossText, buildAlmanac, buildZeri, buildChenggu, buildXiaoliuren, buildStar, buildNumerology, buildConvert, buildTrend, buildFengshui, buildName, parseDateTime, compute, interpretText, renderText };
18248
+ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAddress, resolveAddress, buildBazi, buildLiuyao, tossLines, followup, askPrompt, pastSignals, verifyFit, rectifyBirth, crossCheck, crossText, crossSection, buildAlmanac, buildZeri, buildChenggu, buildXiaoliuren, buildStar, buildNumerology, buildConvert, buildTrend, buildFengshui, buildName, parseDateTime, compute, interpretText, caliberCompare, renderText };
17459
18249
  })();
17460
18250
 
17461
18251
  window.Engine = __mods.index;
@@ -17505,7 +18295,7 @@ window.Engine = __mods.index;
17505
18295
  // 共用渲染层:紫微/八字/六爻/梅花/六壬/塔罗 -> HTML 字符串。面板与独立页面共用同一份。
17506
18296
  (function () {
17507
18297
  const CSS = `
17508
- .pp,.mp-it,.mp-al,.mp-cg,.mp-cl,.mp-cli,.mp-kline,.mp-mb,.mp-mb-paper,.mp-nm,.mp-quick{--ink:#1b1b1b;--ink2:#3f3a33;--muted:#6f6a60;--label:#8a8378;--faint:#a9a296;
18298
+ .pp,.mp-it,.mp-al,.mp-cg,.mp-cl,.mp-cli,.mp-kline,.mp-mb,.mp-mb-paper,.mp-nm,.mp-quick,.mp-cdiff{--ink:#1b1b1b;--ink2:#3f3a33;--muted:#6f6a60;--label:#8a8378;--faint:#a9a296;
17509
18299
  --line:#c8c1b2;--dimline:#b9b2a4;--hair:#e9e2d3;
17510
18300
  /* 底色略沉作「桌面」,--card 跳到近白:原先 bg 与 card 只差 1/255,卡片感等于零。
17511
18301
  同一色相里塞 9 个几乎相等的浅色,谁都浮不起来。 */
@@ -17526,7 +18316,7 @@ window.Engine = __mods.index;
17526
18316
  /* 深色:只换变量,不动任何一条选择器。用 @media screen 限定,
17527
18317
  打印永远走浅色 —— 深色主题下直接打印会得到一整页耗墨的深底 PDF。 */
17528
18318
  @media screen and (prefers-color-scheme:dark){
17529
- .pp,.mp-it,.mp-al,.mp-cg,.mp-cl,.mp-cli,.mp-kline,.mp-mb,.mp-mb-paper,.mp-nm,.mp-quick{--ink:#eae5d9;--ink2:#d5cfc2;--muted:#aca598;--label:#a49c8e;--faint:#928b7e;
18319
+ .pp,.mp-it,.mp-al,.mp-cg,.mp-cl,.mp-cli,.mp-kline,.mp-mb,.mp-mb-paper,.mp-nm,.mp-quick,.mp-cdiff{--ink:#eae5d9;--ink2:#d5cfc2;--muted:#aca598;--label:#a49c8e;--faint:#928b7e;
17530
18320
  --line:#514a3e;--dimline:#5f5849;--hair:#2f2b23;
17531
18321
  /* 与浅色同理:底更沉、卡片更亮,让层级在深底上也能立起来 */
17532
18322
  --bg:#12110c;--card:#211f18;--band:#282419;--hover:#2e2a1f;--hover2:#383322;
@@ -17623,6 +18413,8 @@ window.Engine = __mods.index;
17623
18413
  .pp-str b{color:var(--red);font-size:15px;margin:0 4px}
17624
18414
  .pp-drel{font-style:normal;color:var(--red);font-size:12px}
17625
18415
  .pp-dim{color:var(--muted)}
18416
+ /* 岁运提示下的原文:比正文再轻一档,只做出处,不抢结论 */
18417
+ .pp-sy-src{margin-top:6px;font-size:11.5px;line-height:1.55}
17626
18418
  .pp-r{display:none}
17627
18419
  .pp-modes{display:flex;align-items:center;border-bottom:1px solid var(--line);font-family:system-ui,sans-serif}
17628
18420
  /* min-height:44px —— 视图切换是本盘唯一的主要控件,触摸目标必须够。
@@ -17936,7 +18728,8 @@ window.Engine = __mods.index;
17936
18728
  /* —— 解析与详解 —— */
17937
18729
  /* 变量由上面的 .pp,.mp-it 共享块提供。这里原先写过 --ink:var(--ink) 这类自引用声明,
17938
18730
  自引用会让变量变成 guaranteed-invalid,反而把继承值也挡掉了(--card 因此彻底失效)。 */
17939
- .mp-it{margin-top:14px;border:1px solid var(--line);background:var(--bg);color:var(--ink);border-radius:12px;overflow:hidden;box-shadow:var(--shadow);
18731
+ /* container-type:dsh 侧栏是「窗口宽、报告窄」,@media 不触发;下面 ≤640 的表格/目录规则另挂一份 @container */
18732
+ .mp-it{margin-top:14px;border:1px solid var(--line);background:var(--bg);color:var(--ink);border-radius:12px;overflow:hidden;box-shadow:var(--shadow);container-type:inline-size;
17940
18733
  font-family:"Songti SC","SimSun","Microsoft YaHei",serif;font-size:13px;line-height:1.75}
17941
18734
  .mp-it *{box-sizing:border-box}
17942
18735
  /* 标题栏即折叠开关:整栏可点,箭头旋转表示状态 */
@@ -18037,6 +18830,66 @@ window.Engine = __mods.index;
18037
18830
  .mp-it-conf.c-强{color:var(--lu);border-color:rgba(var(--lu-rgb),.4)}
18038
18831
  .mp-it-conf.c-中{color:var(--label);border-color:var(--line)}
18039
18832
  .mp-it-conf.c-弱{color:var(--red);border-color:rgba(var(--red-rgb),.4)}
18833
+ /* 交叉验证:一个方面一张对照卡,八字与紫微左右并列,判断做成语气标签 */
18834
+ .mp-cross{display:flex;flex-direction:column;gap:8px;font-family:system-ui,sans-serif}
18835
+ .mp-cross-row{border:1px solid var(--line);border-radius:8px;background:var(--card);padding:9px 11px}
18836
+ .mp-cross-h{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
18837
+ .mp-cross-l{font-size:13px;font-weight:700;color:var(--ink);letter-spacing:.02em}
18838
+ .mp-cross-tag{font-size:11px;line-height:1.5;padding:1px 8px;border:1px solid currentColor;border-radius:999px;color:var(--muted)}
18839
+ .mp-cross-tag.t-good{color:var(--lu)}
18840
+ .mp-cross-tag.t-watch{color:var(--quan)}
18841
+ .mp-cross-tag.t-alt{color:var(--ke)}
18842
+ .mp-cross-b{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:4px 14px;margin-top:7px}
18843
+ .mp-cross-cell{font-size:12.5px;line-height:1.75;color:var(--ink2)}
18844
+ .mp-cross-cell b{display:block;font-size:10.5px;letter-spacing:.1em;color:var(--muted);font-weight:600;margin-bottom:1px}
18845
+ .mp-cross-note{margin-top:7px;padding-top:6px;border-top:1px dashed var(--line);font-size:12px;line-height:1.7;color:var(--muted)}
18846
+ /* 口径对照:结果下方一块可折叠的「换一个开关会变哪几个字」。
18847
+ 默认收起,summary 一行给出「几组影响此盘」;展开后先列根因,再列连带分类。
18848
+ 与盘面同一套变量,深色下只换变量;summary 保持 44px 触摸目标。 */
18849
+ .mp-cdiff{margin:12px 0 0;border:1px solid var(--line);border-radius:10px;background:var(--card);overflow:hidden;font-family:system-ui,sans-serif}
18850
+ .mp-cdiff>summary{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-height:44px;padding:8px 13px;cursor:pointer;list-style:none;color:var(--ink2);font-size:12.5px;line-height:1.6}
18851
+ .mp-cdiff>summary::-webkit-details-marker{display:none}
18852
+ .mp-cdiff>summary::before{content:'▸';color:var(--red);font-size:12px}
18853
+ .mp-cdiff[open]>summary::before{content:'▾'}
18854
+ .mp-cdiff-title{font-size:13px;font-weight:700;letter-spacing:.06em;color:var(--red)}
18855
+ .mp-cdiff-lead{color:var(--muted)}
18856
+ .mp-cdiff-more{margin-left:auto;color:var(--label);font-size:11.5px;white-space:nowrap}
18857
+ .mp-cdiff[open] .mp-cdiff-more{display:none}
18858
+ .mp-cdiff-body{padding:0 13px 12px;border-top:1px dashed var(--line)}
18859
+ .mp-cdiff-row{padding:9px 0;border-bottom:1px dashed var(--line)}
18860
+ .mp-cdiff-row:last-of-type{border-bottom:none}
18861
+ .mp-cdiff-h{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}
18862
+ .mp-cdiff-h b{font-size:12.5px;color:var(--ink);font-weight:700}
18863
+ .mp-cdiff-count{margin-left:auto;font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums}
18864
+ .mp-cdiff-count.on{color:var(--red)}
18865
+ .mp-cdiff-ab{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:5px;font-size:11.5px;color:var(--muted)}
18866
+ .mp-cdiff-pill{border:1px solid var(--line);border-radius:999px;padding:1px 8px;background:var(--band)}
18867
+ .mp-cdiff-pill.now{color:var(--lu);border-color:rgba(var(--lu-rgb),.4);background:color-mix(in srgb,var(--lu) 10%,transparent)}
18868
+ .mp-cdiff-pill.alt{color:var(--red);border-color:rgba(var(--red-rgb),.4);background:color-mix(in srgb,var(--red) 8%,transparent)}
18869
+ .mp-cdiff-arrow{color:var(--dimline)}
18870
+ .mp-cdiff-roots{display:flex;flex-direction:column;gap:3px;margin-top:6px;font-size:12.5px;line-height:1.7}
18871
+ .mp-cdiff-root{display:flex;align-items:baseline;gap:6px;flex-wrap:wrap}
18872
+ .mp-cdiff-k{color:var(--label);font-size:11px;letter-spacing:.08em;min-width:2.2em}
18873
+ .mp-cdiff-from{color:var(--ink2)}
18874
+ .mp-cdiff-to{color:var(--ink);font-weight:700}
18875
+ .mp-cdiff-cats{display:flex;flex-wrap:wrap;gap:4px 6px;margin-top:6px}
18876
+ .mp-cdiff-cat{font-size:11px;color:var(--muted);border:1px solid var(--line);border-radius:999px;padding:0 7px;background:var(--band)}
18877
+ .mp-cdiff-same{font-size:12px;color:var(--muted);margin-top:4px}
18878
+ .mp-cdiff-note{margin-top:8px;padding-top:7px;border-top:1px dashed var(--line);font-size:11.5px;line-height:1.7;color:var(--muted)}
18879
+ @media (max-width:640px){
18880
+ .mp-cdiff>summary{padding:8px 11px}
18881
+ /* 窄屏收起态只留标题与一句话总结;「展开」提示折行会把 summary 撑到 91px */
18882
+ .mp-cdiff-more{display:none}
18883
+ .mp-cdiff-body{padding:0 11px 10px}
18884
+ .mp-cdiff-h b{font-size:12px}
18885
+ .mp-cdiff-count{margin-left:0}
18886
+ }
18887
+ @media print{
18888
+ .mp-cdiff:not([open])>*{display:block!important}
18889
+ .mp-cdiff>summary{cursor:auto}
18890
+ .mp-cdiff-more{display:none}
18891
+ .mp-cdiff{break-inside:avoid}
18892
+ }
18040
18893
  /* 三张表:横向可滚动,避免窄栏撑破 */
18041
18894
  .mp-tables{border-top:1px solid var(--line);padding:12px 16px 4px;background:var(--card)}
18042
18895
  .mp-table-wrap{margin-bottom:12px}
@@ -18044,7 +18897,8 @@ window.Engine = __mods.index;
18044
18897
  .mp-table-scroll{overflow-x:auto;border:1px solid var(--line);border-radius:8px}
18045
18898
  .mp-table{width:100%;border-collapse:collapse;font-size:12px;line-height:1.6;color:var(--ink2);font-family:system-ui,sans-serif}
18046
18899
  .mp-table th,.mp-table td{padding:5px 9px;text-align:left;vertical-align:top;border-bottom:1px solid var(--line);white-space:nowrap}
18047
- .mp-table th{background:var(--band);color:var(--label);font-weight:600;font-size:11px;letter-spacing:.06em}
18900
+ /* 表头 11px 小字:用 ink2 而不是 label,浅色下 label 对 band 只有 3.5:1,读不清 */
18901
+ .mp-table th{background:var(--band);color:var(--ink2);font-weight:600;font-size:11px;letter-spacing:.06em}
18048
18902
  .mp-table tbody tr:last-child td{border-bottom:none}
18049
18903
  .mp-table tbody tr:nth-child(even) td{background:rgba(0,0,0,.02)}
18050
18904
  /* 打印:表格不撕页、目录隐藏、折叠的内容要展开 */
@@ -18056,6 +18910,56 @@ window.Engine = __mods.index;
18056
18910
  .mp-table-wrap,.mp-it-sec,.mp-it-dim{break-inside:avoid}
18057
18911
  .mp-table-wrap{page-break-inside:avoid}
18058
18912
  }
18913
+ /* —— ≤640px:大运表 6 列排到 748px,窄屏只能看到左边一段且没有「还能滑」的提示,
18914
+ 这里改成一列一张卡;目录横滑成胶囊、证据摘要与名词解释的点击区抬到 44px —— */
18915
+ @media (max-width:640px){
18916
+ .mp-tables{padding:10px 11px 2px}
18917
+ .mp-table-wrap{margin-bottom:14px}
18918
+ .mp-table-scroll{overflow:visible;border:0;border-radius:0}
18919
+ .mp-table{display:block;width:100%}
18920
+ .mp-table thead{display:none}
18921
+ .mp-table tbody{display:block}
18922
+ .mp-table tr{display:block;border:1px solid var(--line);border-radius:8px;background:var(--card);padding:7px 10px;margin:0 0 8px}
18923
+ .mp-table td{display:grid;grid-template-columns:56px minmax(0,1fr);gap:8px;border-bottom:0;padding:2px 0;white-space:normal;font-size:12.5px;line-height:1.65}
18924
+ .mp-table td::before{content:attr(data-label);color:var(--muted);font-size:11px;line-height:1.9}
18925
+ .mp-table td:first-child{display:block;padding-bottom:3px;font-size:13px;font-weight:700;color:var(--ink)}
18926
+ .mp-table td:first-child::before{content:none}
18927
+ .mp-table tbody tr:nth-child(even) td{background:none}
18928
+ .mp-table tbody tr:last-child{margin-bottom:0}
18929
+ /* 对照卡:两列并排在 342px 里每边只剩 164px,竖排后标签跟正文走 */
18930
+ .mp-cross-b{grid-template-columns:1fr;gap:6px}
18931
+ .mp-cross-cell b{display:inline;margin-right:6px;letter-spacing:.06em}
18932
+ .mp-toc{flex-wrap:nowrap;overflow-x:auto;gap:8px;margin:10px 0 0;padding:6px 11px;border:0;border-bottom:1px solid var(--line);border-radius:0;scrollbar-width:none}
18933
+ .mp-toc::-webkit-scrollbar{display:none}
18934
+ .mp-toc a{flex:0 0 auto;display:inline-flex;align-items:center;min-height:44px;padding:0 12px;border:1px solid var(--line);border-radius:999px;color:var(--lu);text-decoration:none}
18935
+ .mp-toc a:hover{color:var(--ink)}
18936
+ .mp-it-ev>summary{display:flex;align-items:center;min-height:44px;padding:0 9px}
18937
+ .mp-it-gloss summary{display:flex;align-items:center;min-height:44px;margin-bottom:2px}
18938
+ }
18939
+ /* 容器查询版(dsh 侧栏 420px、窗口 1400px 时 @media 不会触发):与上面同规则,不另立一套口径 */
18940
+ @container (max-width:640px){
18941
+ .mp-tables{padding:10px 11px 2px}
18942
+ .mp-table-wrap{margin-bottom:14px}
18943
+ .mp-table-scroll{overflow:visible;border:0;border-radius:0}
18944
+ .mp-table{display:block;width:100%}
18945
+ .mp-table thead{display:none}
18946
+ .mp-table tbody{display:block}
18947
+ .mp-table tr{display:block;border:1px solid var(--line);border-radius:8px;background:var(--card);padding:7px 10px;margin:0 0 8px}
18948
+ .mp-table td{display:grid;grid-template-columns:56px minmax(0,1fr);gap:8px;border-bottom:0;padding:2px 0;white-space:normal;font-size:12.5px;line-height:1.65}
18949
+ .mp-table td::before{content:attr(data-label);color:var(--muted);font-size:11px;line-height:1.9}
18950
+ .mp-table td:first-child{display:block;padding-bottom:3px;font-size:13px;font-weight:700;color:var(--ink)}
18951
+ .mp-table td:first-child::before{content:none}
18952
+ .mp-table tbody tr:nth-child(even) td{background:none}
18953
+ .mp-table tbody tr:last-child{margin-bottom:0}
18954
+ .mp-cross-b{grid-template-columns:1fr;gap:6px}
18955
+ .mp-cross-cell b{display:inline;margin-right:6px;letter-spacing:.06em}
18956
+ .mp-toc{flex-wrap:nowrap;overflow-x:auto;gap:8px;margin:10px 0 0;padding:6px 11px;border:0;border-bottom:1px solid var(--line);border-radius:0;scrollbar-width:none}
18957
+ .mp-toc::-webkit-scrollbar{display:none}
18958
+ .mp-toc a{flex:0 0 auto;display:inline-flex;align-items:center;min-height:44px;padding:0 12px;border:1px solid var(--line);border-radius:999px;color:var(--lu);text-decoration:none}
18959
+ .mp-toc a:hover{color:var(--ink)}
18960
+ .mp-it-ev>summary{display:flex;align-items:center;min-height:44px;padding:0 9px}
18961
+ .mp-it-gloss summary{display:flex;align-items:center;min-height:44px;margin-bottom:2px}
18962
+ }
18059
18963
  /* 追问结果 */
18060
18964
  .mp-fu{margin:12px 16px;border:1px solid var(--line);border-left:3px solid var(--lu);border-radius:8px;background:var(--card);padding:10px 12px;font-size:13px;line-height:1.8;color:var(--ink2)}
18061
18965
  .mp-fu.miss{border-left-color:var(--red)}
@@ -18931,6 +19835,14 @@ window.Engine = __mods.index;
18931
19835
  return `<span class="pp-chip ${cls}">伏神${esc(l)}</span>`;
18932
19836
  }).join('')}</div>`
18933
19837
  : '';
19838
+ // 应期线索:只摆触发支与快慢方向,不写日期
19839
+ const yq = r.yingqi && Array.isArray(r.yingqi.labels) ? r.yingqi : null;
19840
+ const yingqiRow = yq && yq.labels.length
19841
+ ? `<div class="pp-chips">${yq.labels.map((l) => {
19842
+ const cls = /应期偏速/.test(l) ? 'ok' : /应期待解/.test(l) ? 'warn' : '';
19843
+ return `<span class="pp-chip ${cls}" title="只列地支,不出日期">${esc(l)}</span>`;
19844
+ }).join('')}</div>`
19845
+ : '';
18934
19846
  // 化进/化退的力重:方向之外再叠月建日辰,单列一排,免与卦体标签混读
18935
19847
  const jp = r.jinTuiPower && Array.isArray(r.jinTuiPower.labels) ? r.jinTuiPower : null;
18936
19848
  const jinRow = jp && jp.labels.length
@@ -18955,6 +19867,7 @@ window.Engine = __mods.index;
18955
19867
  ${useRow}
18956
19868
  ${strengthRow}
18957
19869
  ${fushenRow}
19870
+ ${yingqiRow}
18958
19871
  ${jinRow}
18959
19872
  ${hexRow}
18960
19873
  </div>`;
@@ -19693,12 +20606,16 @@ window.Engine = __mods.index;
19693
20606
  const ys = fr.yongshen ? `
19694
20607
  <div class="pp-tl">喜 ${fr.yongshen.favor.map(el).join('')} 忌 ${fr.yongshen.avoid.map(el).join('')} <span class="pp-dim">${esc(fr.yongshen.method)}</span></div>
19695
20608
  <div class="pp-chips">${fr.yongshen.favorTenGods.map((t) => `<span class="pp-chip ok">${esc(t)}</span>`).join('')}${fr.yongshen.avoidTenGods ? fr.yongshen.avoidTenGods.map((t) => `<span class="pp-chip warn">${esc(t)}</span>`).join('') : ''}</div>
20609
+ ${(fr.yongshen.disputed || []).length
20610
+ ? `<div class="pp-tl pp-dim">有争议:${fr.yongshen.disputed.map((d) => `${esc(d.element)}(扶抑为${esc(d.favorRole)}·${esc(d.favorVerb)},${esc(d.climate)}盘调候忌${esc(d.element)})`).join('、')} 保留在喜用,不列入忌</div>`
20611
+ : ''}
19696
20612
  <div class="pp-tl pp-dim">${esc(fr.yongshen.detail)}</div>` : '';
19697
20613
  const cli = r.climate || (fr.yongshen && fr.yongshen.climate) || null;
19698
20614
  const cliCard = cli ? (() => {
19699
20615
  const pct = Math.max(2, Math.min(98, Math.round(((Number(cli.temp) || 0) + 3) / 6 * 100)));
19700
20616
  const need = cli.need ? `<em>调候取</em><b>${esc(cli.need.stem + cli.need.element)}</b>` : '<em>气候平和</em>';
19701
- const avoid = cli.need ? `<em>忌</em><b>${esc(cli.avoidElement)}</b>` : '';
20617
+ const cliDis = !!(fr.yongshen && Array.isArray(fr.yongshen.disputed)) && fr.yongshen.disputed.some((d) => d.element === cli.avoidElement);
20618
+ const avoid = cli.need ? `<em>忌</em><b>${esc(cli.avoidElement)}</b>${cliDis ? '<span class="pp-dim">(有争议)</span>' : ''}` : '';
19702
20619
  const ev = (cli.evidence || []).length ? `<em>依据</em><span>${esc(cli.evidence.join('、'))}</span>` : '';
19703
20620
  const pri = cli.priority === '调候优先' ? '<span class="mp-cli-p">调候优先</span>' : '<span class="mp-cli-p">扶抑为主</span>';
19704
20621
  const qt = r.frame && r.frame.yongshen && r.frame.yongshen.qt;
@@ -19730,8 +20647,10 @@ window.Engine = __mods.index;
19730
20647
  return `<span class="pp-chip ${lk ? tierCls(lk.tier) : ''}" title="${esc(lk ? `${lk.tier} ${lk.score}|${(lk.reasons || []).join(';')}${syTip}` : syTip.replace(/^|/, ''))}">${l.year} ${l.ganzhi}${lk ? ` ${lk.tier}` : ''}</span>`;
19731
20648
  }).join('')}</div>` : '';
19732
20649
  const sy = (r.liunian || []).filter((l) => l.suiyun && Array.isArray(l.suiyun.signals) && l.suiyun.signals.length);
20650
+ const syc = r.suiyunClassic;
20651
+ const sySrc = syc ? `<div class="pp-dim pp-sy-src" title="${esc(syc.plain || '')}">《${esc(syc.bookName || '')}·${esc(syc.chapter || '')}》「${esc(syc.quote || '')}」</div>` : '';
19733
20652
  const syRow = sy.length ? `<div class="pp-tl pp-dim">岁运提示(只标干支变动,不判吉凶)</div><div class="pp-chips">${sy.map((l) =>
19734
- `<span class="pp-chip" title="${esc(`${l.suiyun.signals.map((x) => x.detail).join(';')}|${l.suiyun.note || ''}`)}">${l.year} ${esc(l.suiyun.signals.map((x) => `${x.type}${x.type.startsWith('流年') && x.target ? `·${x.target}` : ''}`).join('/'))}</span>`).join('')}</div>` : '';
20653
+ `<span class="pp-chip" title="${esc(`${l.suiyun.signals.map((x) => x.detail).join(';')}|${l.suiyun.note || ''}`)}">${l.year} ${esc(l.suiyun.signals.map((x) => `${x.type}${(x.type.startsWith('流年') || x.type.startsWith('大运')) && x.target ? `·${x.target}` : ''}`).join('/'))}</span>`).join('')}</div>${sySrc}` : '';
19735
20654
  const str = r.strength ? `<div class="pp-str">日主强弱:<b>${r.strength.level}</b>${r.strength.score} <span class="pp-dim">${r.strength.detail}</span></div>` : '';
19736
20655
  const ssGroups = (r.shensha && r.shensha.groups) || [];
19737
20656
  const ssx = ssGroups.length ? `
@@ -19772,6 +20691,21 @@ window.Engine = __mods.index;
19772
20691
  // ── 解析与详解:把 interpret() 的结构化结果渲染成卡片 ──
19773
20692
  const MT_LABEL = { natal: '命盘解析', divination: '卦象解析', card: '牌阵解析' };
19774
20693
 
20694
+ // 交叉验证的「判断」只有三档,各给一种语气色;未知值走中性色,不猜
20695
+ const STANCE_TONE = { 同向: 'good', 有张力: 'watch', 互补: 'alt' };
20696
+ // 一栏一个方面:标题带判断标签,八字与紫微并列,注解放最后一行,省得五段长句连读
20697
+ const crossHTML = (items) => `<div class="mp-cross">${items.map((c) => {
20698
+ const tone = STANCE_TONE[c.stance] || 'plain';
20699
+ return `<div class="mp-cross-row">
20700
+ <div class="mp-cross-h"><span class="mp-cross-l">${esc(c.label)}</span><span class="mp-cross-tag t-${tone}">${esc(c.stance || '—')}</span></div>
20701
+ <div class="mp-cross-b">
20702
+ <div class="mp-cross-cell"><b>八字</b><span>${esc(c.bazi || '—')}</span></div>
20703
+ <div class="mp-cross-cell"><b>紫微</b><span>${esc(c.ziwei || '—')}</span></div>
20704
+ </div>
20705
+ ${c.note ? `<div class="mp-cross-note">${esc(c.note)}</div>` : ''}
20706
+ </div>`;
20707
+ }).join('')}</div>`;
20708
+
19775
20709
  /**
19776
20710
  * @param {object} it interpret() 的结果
19777
20711
  * @param {object} [opt] { folded 是否收起 } —— 桌面默认展开、竖屏默认收起由调用方按视口决定
@@ -19803,8 +20737,8 @@ window.Engine = __mods.index;
19803
20737
  ? `<div class="mp-tables">${it.tables.filter((t) => Array.isArray(t.rows) && t.rows.length).map((t) => `<div class="mp-table-wrap">
19804
20738
  <div class="mp-table-h">${esc(t.title)}</div>
19805
20739
  <div class="mp-table-scroll"><table class="mp-table">
19806
- <thead><tr>${t.columns.map((c) => `<th>${esc(c)}</th>`).join('')}</tr></thead>
19807
- <tbody>${t.rows.map((row) => `<tr>${row.map((c) => `<td>${esc(c)}</td>`).join('')}</tr>`).join('')}</tbody>
20740
+ <thead><tr>${t.columns.map((c) => `<th scope="col">${esc(c)}</th>`).join('')}</tr></thead>
20741
+ <tbody>${t.rows.map((row) => `<tr>${row.map((c, ci) => `<td data-label="${esc(t.columns[ci] || '')}">${esc(c)}</td>`).join('')}</tr>`).join('')}</tbody>
19808
20742
  </table></div>
19809
20743
  </div>`).join('')}</div>`
19810
20744
  : '';
@@ -19816,7 +20750,7 @@ window.Engine = __mods.index;
19816
20750
  const secs = (it.sections && it.sections.length)
19817
20751
  ? `<div class="mp-it-secs">${it.sections.map((s) => `<div class="mp-it-sec${s.tone ? ` tone-${esc(s.tone)}` : ''}" id="mp-sec-${esc(s.key)}">
19818
20752
  <div class="mp-it-sh">${esc(s.label)}</div>
19819
- <div class="mp-it-stext">${richText(s.text)}</div>
20753
+ <div class="mp-it-stext">${(Array.isArray(s.items) && s.items.length) ? crossHTML(s.items) : richText(s.text)}</div>
19820
20754
  </div>`).join('')}</div>`
19821
20755
  : '';
19822
20756
  const tags = [it.systemLabel, MT_LABEL[it.moduleType] || it.moduleType].filter(Boolean);
@@ -19878,6 +20812,47 @@ window.Engine = __mods.index;
19878
20812
  return (html || '') + pp.interpretHTML(it, opt);
19879
20813
  };
19880
20814
 
20815
+ /**
20816
+ * 口径对照卡:数据来自 Engine.caliberCompare(同一组输入只换一个开关)。
20817
+ * 默认收起,summary 先给「几组影响此盘」;展开后列根因与连带分类。
20818
+ */
20819
+ pp.caliberHTML = function (caliber) {
20820
+ const sws = caliber && Array.isArray(caliber.switches) ? caliber.switches.filter((s) => s && s.key) : [];
20821
+ if (!sws.length) return '';
20822
+ const affected = sws.filter((s) => !s.error && s.total > 0);
20823
+ const lead = affected.length
20824
+ ? `${sws.length} 组开关中 ${affected.length} 组影响此盘:${affected.map((s) => s.short || s.title).join('、')}。`
20825
+ : `本盘对 ${sws.length} 组开关都不敏感:换成另一档也不变。`;
20826
+ const rootsHTML = (row) => {
20827
+ if (row.error) return `<div class="mp-cdiff-same">未能对照:${esc(row.error)}</div>`;
20828
+ if (!row.total) return '<div class="mp-cdiff-same">无变化 · 此盘不受这一开关影响</div>';
20829
+ const roots = Array.isArray(row.roots) ? row.roots : [];
20830
+ if (!roots.length) return `<div class="mp-cdiff-same">无单一根因;逐字段差异 ${Number(row.total) || 0} 处</div>`;
20831
+ return `<div class="mp-cdiff-roots">${roots.map((r) => `<div class="mp-cdiff-root">`
20832
+ + `<span class="mp-cdiff-k">${esc(r.label)}</span><span class="mp-cdiff-from">${esc(r.from)}</span>`
20833
+ + `<span class="mp-cdiff-arrow" aria-hidden="true">→</span><span class="mp-cdiff-to">${esc(r.to)}</span></div>`).join('')}</div>`;
20834
+ };
20835
+ const catsHTML = (row) => {
20836
+ const cats = Array.isArray(row.categories) ? row.categories : [];
20837
+ if (row.error || !row.total || !cats.length) return '';
20838
+ const shown = cats.slice(0, 6);
20839
+ return `<div class="mp-cdiff-cats">${shown.map((c) => `<span class="mp-cdiff-cat">${esc(c.name)} ${Number(c.count) || 0} 处</span>`).join('')}`
20840
+ + (cats.length > shown.length ? `<span class="mp-cdiff-cat">另 ${cats.length - shown.length} 类</span>` : '') + '</div>';
20841
+ };
20842
+ const rows = sws.map((row) => `<div class="mp-cdiff-row">`
20843
+ + `<div class="mp-cdiff-h"><b>${esc(row.title)}</b>`
20844
+ + `<span class="mp-cdiff-count${row.total > 0 ? ' on' : ''}">${row.error ? '未能对照' : row.total ? `${Number(row.total) || 0} 处` : '无变化'}</span></div>`
20845
+ + `<div class="mp-cdiff-ab"><span class="mp-cdiff-pill now">当前 · ${esc(row.currentLabel)}</span>`
20846
+ + `<span class="mp-cdiff-arrow" aria-hidden="true">→</span><span class="mp-cdiff-pill alt">换成 · ${esc(row.altLabel)}</span></div>`
20847
+ + `${rootsHTML(row)}${catsHTML(row)}</div>`).join('');
20848
+ return `<details class="mp-cdiff" aria-label="口径对照">
20849
+ <summary><span class="mp-cdiff-title">口径对照</span><span class="mp-cdiff-lead">${esc(lead)}</span><span class="mp-cdiff-more">展开逐字差异</span></summary>
20850
+ <div class="mp-cdiff-body">${rows}
20851
+ <div class="mp-cdiff-note">同一组输入、只换一个开关;只列变化字段与根因,不判哪套口径更准,也不改上方盘面。</div>
20852
+ </div>
20853
+ </details>`;
20854
+ };
20855
+
19881
20856
  /**
19882
20857
  * 黄历卡片:日期/农历 + 干支值日 + 宜忌 + 四格(二十八宿/胎神/纳音/冲煞)
19883
20858
  * + 吉神方位 + 时辰吉凶 + 吉配生肖 + 每日一卦。数据全由引擎给,渲染只做排版。
@@ -20443,11 +21418,12 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
20443
21418
  it = window.Engine.interpret ? window.Engine.interpret(last.r, askArgs()) : (last.r.interpret || null);
20444
21419
  } catch (e) { it = null; }
20445
21420
  // 交叉验证结果由 compute 挂在盘面对象上,这里补进解析分节,保证与真实报告同一渲染路径
20446
- if (it && last.r && last.r.cross && window.Engine.crossText) {
20447
- it.sections = [...(it.sections || []), { key: 'crossSystem', label: '八字 × 紫微 交叉验证', text: window.Engine.crossText(last.r.cross) }];
21421
+ if (it && last.r && last.r.cross && window.Engine.crossSection) {
21422
+ it.sections = [...(it.sections || []), window.Engine.crossSection(last.r.cross)];
20448
21423
  }
20449
21424
  el('out').innerHTML = castAtHTML(last.tab, last.timed, last.date, last.time)
20450
21425
  + window.__PP.resultWithInterpret(last.html, it, { folded: defaultFolded() })
21426
+ + window.__PP.caliberHTML(last.caliber)
20451
21427
  + followupBar()
20452
21428
  + (last.tab === 'bazi' ? `<div id="vbox">${verifyHTML()}</div><div id="rectbox">${rectifyHTML()}</div>` : '')
20453
21429
  + exportBar();
@@ -20672,7 +21648,7 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
20672
21648
  if (tab === 'convert') {
20673
21649
  args.mode = convMode;
20674
21650
  if (convMode === 'lunar2solar') {
20675
- const m = /^(d{4})-(d{1,2})-(d{1,2})$/.exec(String(el('lunardate').value).trim());
21651
+ const m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(String(el('lunardate').value).trim());
20676
21652
  if (!m) { el('out').innerHTML = '<div class="err">农历转公历请按「农历年-月-日」填写,例如 2026-9-17;闰月单独勾选。</div>'; return; }
20677
21653
  args.lunarYear = Number(m[1]); args.lunarMonth = Number(m[2]); args.lunarDay = Number(m[3]);
20678
21654
  args.isLeapMonth = !!(el('leapm') && el('leapm').checked);
@@ -20724,6 +21700,11 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
20724
21700
  });
20725
21701
  } catch (e) { trend = null; }
20726
21702
  }
21703
+ // 口径对照:只对 bazi / ziwei / qimen 有开关表;额外排 2–3 张盘,失败不影响主结果
21704
+ let caliber = null;
21705
+ if (window.Engine.caliberCompare) {
21706
+ try { caliber = window.Engine.caliberCompare(args); } catch (e) { caliber = null; }
21707
+ }
20727
21708
  const html = (lineWarn ? `<div class="note">${lineWarn}</div>` : '') + (tab === 'ziwei' ? window.__PP.ziweiHTML(r)
20728
21709
  : tab === 'bazi' ? (window.__PP.baziHTML(r) + (trend ? window.__PP.trendHTML(trend) : ''))
20729
21710
  : tab === 'hepan' ? (r.chart === 'ziwei' ? window.__PP.ziweiHepanHTML(r) : window.__PP.hepanHTML(r))
@@ -20739,7 +21720,7 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
20739
21720
  : tab === 'mbti' ? window.__PP.mbtiHTML(r)
20740
21721
  : tab === 'fengshui' ? window.__PP.fengshuiHTML(r)
20741
21722
  : window.__PP.quickHTML(r));
20742
- last = { r, html, tab, date, time, timed, trend };
21723
+ last = { r, html, tab, date, time, timed, trend, caliber };
20743
21724
  paint();
20744
21725
  } catch (e) {
20745
21726
  last = null;
@@ -20820,12 +21801,14 @@ document.head.appendChild(Object.assign(document.createElement('style'), { textC
20820
21801
  const fc = el('for-count');
20821
21802
  if (fc) fc.style.display = tab === 'tarot' ? '' : 'none';
20822
21803
  const castLabel = tab === 'qimen';
20823
- const dateLabel = tab === 'almanac' || tab === 'zeri' || tab === 'star' || tab === 'numerology';
21804
+ // 与 TIME_KIND 的 date 类共用一处口径:农历(convert)原先漏在名单外,标签错写成「起卦日期」
21805
+ const dateLabel = kindOf(tab) === 'date';
20824
21806
  el('lbl-date').textContent = birth ? '出生日期(公历)' : dateLabel ? '日期(公历)' : (castLabel ? '起局日期(公历)' : '起卦日期(公历)');
20825
21807
  el('lbl-time').textContent = birth ? '出生时刻' : (castLabel ? '起局时刻' : '起卦时刻');
20826
21808
  const qk = el('for-quick');
20827
21809
  if (qk) {
20828
21810
  el('qk-zeri').style.display = tab === 'zeri' ? '' : 'none';
21811
+ el('qk-convert').style.display = tab === 'convert' ? '' : 'none';
20829
21812
  el('qk-xiaoliuren').style.display = tab === 'xiaoliuren' ? '' : 'none';
20830
21813
  el('qk-trend').style.display = tab === 'trend' ? '' : 'none';
20831
21814
  el('qk-fengshui').style.display = tab === 'fengshui' ? '' : 'none';