dsh-metaphysics 0.62.4 → 0.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -362,7 +362,7 @@ h3::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--li
362
362
  <div class="wrap">
363
363
  <header>
364
364
  <div class="brand-row">
365
- <div class="brand"><span class="seal" aria-hidden="true">命</span><span class="brand-txt">命理排盘<span class="ver">v0.62.4</span><small>八字 · 紫微 · 六爻 · 梅花 · 六壬 · 奇门 · 合盘 · 塔罗 全部在本机计算</small></span></div>
365
+ <div class="brand"><span class="seal" aria-hidden="true">命</span><span class="brand-txt">命理排盘<span class="ver">v0.63.0</span><small>八字 · 紫微 · 六爻 · 梅花 · 六壬 · 奇门 · 合盘 · 塔罗 全部在本机计算</small></span></div>
366
366
  <div class="view-switch">
367
367
  <span class="hint">熟练了可以换回平铺</span>
368
368
  <span class="seg" role="group" aria-label="首屏样式">
@@ -653,7 +653,7 @@ h3::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--li
653
653
  <h3>对拍:把典籍当期望值</h3>
654
654
  <ul><li>六爻:京房八宫卦序与世应,64 卦全表;旬空六旬表</li><li>六壬:日干寄宫、中气换将;紫微:纳音定五行局、紫微天府相照</li><li>卦例:《增删卜易》9 条 +《卜筮正宗》128 条(93 成对 + 35 静卦),核本变卦 / 动爻数 / 卦宫 / 缺爻 / 旬空</li><li>奇门:20 个时辰 × 9 宫,核四柱 / 局数 / 值符 / 值使 / 星门神 / 天盘干</li></ul>
655
655
  <h3>工程验证</h3>
656
- <ul><li>测试:155 个测试文件 / 8376 条断言;七道闸(回归 · 断言基线 · 测试可失败自检 · HTML 注入面 · 工程约定 · 产物宿主形态 · 随机扫射)</li><li>可复现:seed / asOf 显式;同一输入同一输出</li><li>口径:晚子时、真太阳时、奇门三流派、闰月归月都可切换,并有字段级差异对照</li></ul>
656
+ <ul><li>测试:164 个测试文件 / 8794 条断言;七道闸(回归 · 断言基线 · 测试可失败自检 · HTML 注入面 · 工程约定 · 产物宿主形态 · 随机扫射)</li><li>可复现:seed / asOf 显式;同一输入同一输出</li><li>口径:晚子时、真太阳时、奇门三流派、闰月归月都可切换,并有字段级差异对照</li></ul>
657
657
 
658
658
  <h3>已知边界(不猜)</h3>
659
659
  <ul><li>六爻应期(具体哪天应验)不做;卦例只录动爻个数,不录爻位</li><li>财富/健康只给结构判断与档位,不给金额区间、不作诊断</li><li>紫微飞星已做「单一支化逐宫追到成环」与「三方四正 16 条按落宫归堆」,但都未叠大限流年四化、不折吉凶;八字交运日已能算到日/时,但未与第三方排盘案例批量对拍</li></ul>
@@ -983,7 +983,7 @@ const LABELS = [
983
983
  [/^dayun\.jiao\./, '起运'],
984
984
  [/^dayun\.toNextJieDays$/, '起运时间'],
985
985
  [/^dayun\.list\.(\d+)\.ganzhi$/, '大运干支'],
986
- [/^dayun\.startAge\./, '起运时间'],
986
+ [/^dayun\.startAge(Exact)?\./, '起运时间'],
987
987
  [/^dayun\./, '大运评分'],
988
988
  [/^liunian\./, '流年'],
989
989
  [/^jieqi\./, '节气边界'],
@@ -4996,6 +4996,229 @@ function classicsText(rows) {
4996
4996
  return { CLASSIC_BOOKS, CLASSICS, classicOf, suiyunClassicOf, classicsFor, classicsText };
4997
4997
  })();
4998
4998
 
4999
+ __mods.structure = (function () {
5000
+ // 八大地支结构(炳祥课堂口径)
5001
+ //
5002
+ // 定位:只做「静态结构 + 宫位权重」的只读标注,不改旺衰、不改喜忌。
5003
+ // 课堂口径来源:炳祥八字课程笔记(内部资料,不随包发布);
5004
+ // 这里把「四个地支落在身强阵营还是身弱阵营」按年支定 A 档(年支那一档记 A,另一档记 B),
5005
+ // 于是八种排布可以写成 AAAA / ABBB / AAAB / AABA / ABAA / AABB / ABAB / ABBA,
5006
+ // 再从「异类那一支的宫位权重」读出静态从格概率:
5007
+ // 年支独异 11 分 → 80% 从;时支独异 16 分 → 50-60% 从;
5008
+ // 日支独异 27 分 → 20% 从;月支独异 36 分 → 99% 不从。
5009
+ //
5010
+ // 注意:这是**静态**概率。三合、三会、六合把某一支「合走」之后,阵营可能整体改写,
5011
+ // 那时要以合化后的阵营重新排结构;课堂原话也是「静态条件之外,生克合化的演化会更多」。
5012
+
5013
+ const STEM_ELEMENT = {
5014
+ 甲: '木', 乙: '木', 丙: '火', 丁: '火', 戊: '土',
5015
+ 己: '土', 庚: '金', 辛: '金', 壬: '水', 癸: '水',
5016
+ };
5017
+ const GENERATES = { 木: '火', 火: '土', 土: '金', 金: '水', 水: '木' };
5018
+
5019
+ /** 干支宫位权重:天干合计 10、地支合计 90(日干为参照点,计 0) */
5020
+ const PILLAR_WEIGHT = {
5021
+ stem: { year: 2, month: 5, day: 0, hour: 3 },
5022
+ branch: { year: 11, month: 36, day: 27, hour: 16 },
5023
+ };
5024
+
5025
+ /** 藏干固定百分比(课堂口径,同一支内合计 100) */
5026
+ const HIDDEN_PCT = {
5027
+ 子: { 癸: 100 },
5028
+ 丑: { 己: 20, 辛: 40, 癸: 40 },
5029
+ 寅: { 甲: 65, 丙: 30, 戊: 5 },
5030
+ 卯: { 乙: 100 },
5031
+ 辰: { 戊: 45, 乙: 30, 癸: 25 },
5032
+ 巳: { 丙: 65, 戊: 20, 庚: 15 },
5033
+ 午: { 丁: 90, 己: 10 },
5034
+ 未: { 己: 55, 丁: 40, 乙: 5 },
5035
+ 申: { 庚: 65, 壬: 30, 戊: 5 },
5036
+ 酉: { 辛: 100 },
5037
+ 戌: { 戊: 55, 辛: 25, 丁: 20 },
5038
+ 亥: { 壬: 70, 甲: 30 },
5039
+ };
5040
+
5041
+ // 得地 / 半得地 / 不得地。数值只作标签与说明,不参与旺衰打分。
5042
+ // 半得地一律不计入身强阵营(课堂:土日主见辰「不能当得地看」;戌对火日主须午戌合/寅午戌才得地)。
5043
+ const SUPPORT = {
5044
+ 木: { 得地: '亥子寅卯', 半: { 辰: 0.5, 未: 0.2 } },
5045
+ 火: { 得地: '寅卯巳午未', 半: { 戌: 0 } },
5046
+ 土: { 得地: '巳午未戌', 半: { 寅: 0.2, 辰: 0.5 } },
5047
+ 金: { 得地: '丑酉申戌辰', 半: { 未: 0.1, 巳: 0.1 } },
5048
+ 水: { 得地: '申亥子丑', 半: { 辰: 0.5 } },
5049
+ };
5050
+
5051
+ const PILLARS = ['year', 'month', 'day', 'hour'];
5052
+ const LABEL = { year: '年', month: '月', day: '日', hour: '时' };
5053
+
5054
+ /** 静态从格概率(%)。null 表示课堂只说「有可能」,未给数字。 */
5055
+ const CONG_PROB = {
5056
+ AAAA: { prob: 99, range: '≈99%', word: '四支同党,基本为真从' },
5057
+ ABBB: { prob: 80, range: '≈80%', word: '年支独异,极易从' },
5058
+ AAAB: { prob: 55, range: '50-60%', word: '时支独异,从与不从各半' },
5059
+ AABA: { prob: 20, range: '≈20%', word: '日支独异,不易从' },
5060
+ ABAA: { prob: 1, range: '≈1%', word: '月支独异,几乎不从' },
5061
+ AABB: { prob: 0, range: '0%', word: '二对二最平衡,无从格' },
5062
+ ABAB: { prob: 0, range: '0%', word: '二对二,无从格' },
5063
+ ABBA: { prob: null, range: '原文未给', word: '二对二,原文只说「有可能从格」' },
5064
+ };
5065
+
5066
+ /** 日主在某一支的得地程度 */
5067
+ function supportOf(dayStem, branch) {
5068
+ const el = STEM_ELEMENT[dayStem];
5069
+ const table = SUPPORT[el];
5070
+ if (!table) return { level: '不得地', weight: 0 };
5071
+ if (table.得地.includes(branch)) return { level: '得地', weight: 1 };
5072
+ const half = table.半[branch];
5073
+ if (half !== undefined) return { level: '半得地', weight: half };
5074
+ return { level: '不得地', weight: 0 };
5075
+ }
5076
+
5077
+ /** 天干属于身强阵营(印比)还是身弱阵营(食伤财官杀) */
5078
+ function stemCamp(dayStem, stem) {
5079
+ const me = STEM_ELEMENT[dayStem];
5080
+ const el = STEM_ELEMENT[stem];
5081
+ if (!me || !el) return 'B';
5082
+ return el === me || GENERATES[el] === me ? 'A' : 'B';
5083
+ }
5084
+
5085
+ /**
5086
+ * 八大地支结构。
5087
+ * @param {object} pillars 四柱(时柱可为 null)
5088
+ * @param {string} dayStem 日干
5089
+ */
5090
+ function branchStructureOf(pillars, dayStem) {
5091
+ const keys = PILLARS.filter((k) => pillars[k] && pillars[k].branch);
5092
+ if (!keys.length) return null;
5093
+
5094
+ const yearCamp = branchCamp(pillars.year.branch, dayStem);
5095
+ const branches = keys.map((k) => {
5096
+ const branch = pillars[k].branch;
5097
+ const sup = supportOf(dayStem, branch);
5098
+ const camp = sup.weight >= 1 ? 'A' : 'B';
5099
+ return {
5100
+ key: k,
5101
+ label: LABEL[k],
5102
+ branch,
5103
+ camp: camp === yearCamp ? 'A' : 'B',
5104
+ support: sup.level,
5105
+ supportWeight: sup.weight,
5106
+ weight: PILLAR_WEIGHT.branch[k],
5107
+ };
5108
+ });
5109
+
5110
+ // 年支那一档记 A;四支不足时(无时辰)只报实际存在的三支,code 相应变短
5111
+ const code = branches.map((b) => b.camp).join('');
5112
+ const prob = CONG_PROB[code] || null;
5113
+
5114
+ const aCamp = supportOf(dayStem, pillars.year.branch).weight >= 1 ? '身强阵营' : '身弱阵营';
5115
+ const bCamp = aCamp === '身强阵营' ? '身弱阵营' : '身强阵营';
5116
+
5117
+ const sum = (camp, field) => branches.filter((b) => b.camp === camp).reduce((n, b) => n + b[field], 0);
5118
+ const branchBal = { A: sum('A', 'weight'), B: sum('B', 'weight') };
5119
+ const branchStrong = aCamp === '身强阵营' ? branchBal.A : branchBal.B;
5120
+ const branchWeak = aCamp === '身强阵营' ? branchBal.B : branchBal.A;
5121
+
5122
+ const stemKeys = ['year', 'month', 'hour'].filter((k) => pillars[k] && pillars[k].stem);
5123
+ const stemBal = { A: 0, B: 0 };
5124
+ for (const k of stemKeys) stemBal[stemCamp(dayStem, pillars[k].stem)] += PILLAR_WEIGHT.stem[k];
5125
+
5126
+ const weakDays = branches.filter((b) => b.support !== '得地');
5127
+ const aCount = branches.filter((b) => b.camp === 'A').length;
5128
+ const bCount = branches.length - aCount;
5129
+ // 异类=人数少的那一档;二对二没有异类
5130
+ const minority = aCount === 0 || bCount === 0 ? null : aCount < bCount ? 'A' : bCount < aCount ? 'B' : null;
5131
+ const odd = minority ? branches.filter((b) => b.camp === minority) : [];
5132
+
5133
+ const balance = {
5134
+ aCamp,
5135
+ branch: branchBal,
5136
+ stem: stemBal,
5137
+ total: { A: branchBal.A + stemBal.A, B: branchBal.B + stemBal.B },
5138
+ };
5139
+
5140
+ const shapeText = !minority
5141
+ ? (aCount === branches.length || bCount === branches.length
5142
+ ? `四支同党(均属${aCamp})`
5143
+ : `二对二:${branches.filter((b) => b.camp === 'A').map((b) => b.label).join('、')}支属${aCamp},`
5144
+ + `${branches.filter((b) => b.camp === 'B').map((b) => b.label).join('、')}支属${bCamp}`)
5145
+ : `异类为${odd.map((b) => `${b.label}支(${b.weight} 分)`).join('、')}`;
5146
+
5147
+ const detail = [
5148
+ `四支${code}(${branches.map((b) => `${b.label}支${b.branch}${b.support}`).join('、')})`,
5149
+ shapeText,
5150
+ `地支 身强${branchStrong} : 身弱${branchWeak},加天干 ${balance.total[aCamp === '身强阵营' ? 'A' : 'B']}:${balance.total[aCamp === '身强阵营' ? 'B' : 'A']}`,
5151
+ ];
5152
+
5153
+ // 没有时柱时概率表不适用:AAAA 之类需要四支齐全才成立
5154
+ const complete = branches.length === 4;
5155
+
5156
+ return {
5157
+ code,
5158
+ complete,
5159
+ aCamp,
5160
+ congProb: complete && prob ? prob.prob : null,
5161
+ congRange: complete && prob ? prob.range : '三柱不判结构概率',
5162
+ word: complete && prob ? prob.word : '时柱暂缺,只列三支结构,不套从格概率',
5163
+ branches,
5164
+ balance,
5165
+ odd: odd.map((b) => ({ key: b.key, label: b.label, branch: b.branch, weight: b.weight })),
5166
+ halfSupported: weakDays.filter((b) => b.support === '半得地').map((b) => `${b.label}支${b.branch}`),
5167
+ detail: detail.join(';'),
5168
+ method: '按年支定 A 档,四支分两营;异类支的宫位权重决定静态从格概率(炳祥课堂口径,非唯一流派)',
5169
+ note: '静态结构。三合/三会/六合把某一支合走时,应以合化后的阵营重排;概率只作提示,不直接断从格。',
5170
+ };
5171
+ }
5172
+
5173
+ function branchCamp(branch, dayStem) {
5174
+ return supportOf(dayStem, branch).weight >= 1 ? 'A' : 'B';
5175
+ }
5176
+
5177
+ /**
5178
+ * 宫位权重的百分比视图(炳祥课程第 2.1 节)。
5179
+ *
5180
+ * 天干合计 10(年 2 / 月 5 / 日 0 / 时 3),地支合计 90(年 11 / 月 36 / 日 27 / 时 16),
5181
+ * 两表相加正好 100,所以「按柱合计」本身就是百分比:年 13%、月 41%、日 27%、时 19%。
5182
+ * 这里只做展示层换算,不改 bazi.js 的旺衰评分,也不参与取格与喜忌。
5183
+ *
5184
+ * 三柱模式(未给出生时刻)时,时柱是正午占位:权重照表列出,但合计只有 81,
5185
+ * 不重新归一化——权重是固定口径,归一化会把占位时柱洗成实有。
5186
+ */
5187
+ function weightPctOf(pillars = {}, { hourUnknown = false } = {}) {
5188
+ const present = PILLARS.filter((k) => pillars[k] && pillars[k].branch);
5189
+ const complete = !hourUnknown && present.length === 4;
5190
+ // 三柱模式:时柱是正午占位,不计入合计;其余缺柱同理
5191
+ const counted = complete ? PILLARS.slice() : present.filter((k) => !(k === 'hour' && hourUnknown));
5192
+ const stem = {};
5193
+ const branch = {};
5194
+ const pct = {};
5195
+ for (const k of PILLARS) {
5196
+ stem[k] = PILLAR_WEIGHT.stem[k];
5197
+ branch[k] = PILLAR_WEIGHT.branch[k];
5198
+ pct[k] = counted.includes(k) ? stem[k] + branch[k] : 0;
5199
+ }
5200
+ const totalStem = counted.reduce((n, k) => n + stem[k], 0);
5201
+ const totalBranch = counted.reduce((n, k) => n + branch[k], 0);
5202
+ const all = totalStem + totalBranch;
5203
+ const rows = counted.map((k) => `${LABEL[k]} ${pct[k]}%(干${stem[k]}+支${branch[k]})`).join('、');
5204
+ const missing = counted.length < 4 ? `;${PILLARS.filter((k) => !counted.includes(k)).map((k) => LABEL[k]).join('、')}柱未计` : '';
5205
+ return {
5206
+ complete,
5207
+ stem,
5208
+ branch,
5209
+ pct,
5210
+ total: { stem: totalStem, branch: totalBranch, all },
5211
+ detail: `宫位权重合计 ${all}:${rows}${missing}`,
5212
+ method: '炳祥课程宫位权重:天干 2/5/0/3、地支 11/36/27/16,合计 100;日干为参照点计 0。只作展示换算,不改旺衰评分',
5213
+ note: complete
5214
+ ? '月令 41% 为四柱之首(课堂称事业宫);日 27%、时 19%、年 13%'
5215
+ : '时柱暂缺,权重表照列,但本盘合计不足 100;不重新归一化',
5216
+ };
5217
+ }
5218
+
5219
+ return { PILLAR_WEIGHT, HIDDEN_PCT, supportOf, branchStructureOf, weightPctOf };
5220
+ })();
5221
+
4999
5222
  __mods.geju = (function () {
5000
5223
  // 八字格局、用神与行运评分
5001
5224
  //
@@ -5135,6 +5358,15 @@ function monthPattern(pillars, dayStem, monthBranch) {
5135
5358
  if (!use) use = hui || base;
5136
5359
  const usedBy = exposedKey ? '透干' : (hui ? '会支' : '本气');
5137
5360
  const exposedPos = exposedKey ? { year: '年', month: '月', hour: '时' }[exposedKey] : null;
5361
+ // 评分元数据:只给下面 patternScoreOf 用,不参与取格本身
5362
+ const metaOf = (c, by, key) => ({
5363
+ stem: c.stem,
5364
+ god: c.god,
5365
+ pos: c.pos,
5366
+ usedBy: by,
5367
+ exposedKey: key || null,
5368
+ exposedPos: key ? { year: '年', month: '月', hour: '时' }[key] : null,
5369
+ });
5138
5370
 
5139
5371
  // 建禄只认日干的禄位;戊土辰月、己土未月这类「本气比肩但不在禄位」的杂气月不再冒称建禄
5140
5372
  if (base.god === '比肩' && LUSHEN_BRANCH[dayStem] === monthBranch) {
@@ -5142,6 +5374,7 @@ function monthPattern(pillars, dayStem, monthBranch) {
5142
5374
  name: '建禄格',
5143
5375
  kind: '月令格',
5144
5376
  evidence: `月令${monthBranch}为日干${dayStem}之禄(本气${base.stem}比肩),身坐禄地`,
5377
+ ...metaOf(base, '本气', null),
5145
5378
  };
5146
5379
  }
5147
5380
  if (base.god === '劫财') {
@@ -5150,15 +5383,17 @@ function monthPattern(pillars, dayStem, monthBranch) {
5150
5383
  name: isYangStem(dayStem) ? '阳刃格' : '月刃格',
5151
5384
  kind: '月令格',
5152
5385
  evidence: `月令${monthBranch}为日干${dayStem}之刃(本气${base.stem}劫财)${isYangStem(dayStem) ? '' : ',阴干统称月刃'}`,
5386
+ ...metaOf(base, '本气', null),
5153
5387
  };
5154
5388
  }
5155
- return { name: '月劫格', kind: '月令格', evidence: `月令${monthBranch}本气${base.stem}为劫财` };
5389
+ return { name: '月劫格', kind: '月令格', evidence: `月令${monthBranch}本气${base.stem}为劫财`, ...metaOf(base, '本气', null) };
5156
5390
  }
5157
5391
  if (usedBy === '本气' && base.god === '比肩') {
5158
5392
  return {
5159
5393
  name: '比肩格',
5160
5394
  kind: '月令格',
5161
5395
  evidence: `月令${monthBranch}藏${hidden.join('')},本气${base.stem}为比肩,但${dayStem}禄在${LUSHEN_BRANCH[dayStem]}、不在${monthBranch},属杂气月比肩当权`,
5396
+ ...metaOf(base, '本气', null),
5162
5397
  };
5163
5398
  }
5164
5399
  // 子平十六:「透乙则用月劫」——杂气月由透干或会支取到劫财时,仍作月劫论
@@ -5168,6 +5403,7 @@ function monthPattern(pillars, dayStem, monthBranch) {
5168
5403
  name: '月劫格',
5169
5404
  kind: '月令格',
5170
5405
  evidence: `月令${monthBranch}藏${hidden.join('')},${use.pos}${use.stem}(劫财)${via},劫财为用,作月劫论`,
5406
+ ...metaOf(use, usedBy, exposedKey),
5171
5407
  };
5172
5408
  }
5173
5409
  const name = `${use.god}格`;
@@ -5179,7 +5415,146 @@ function monthPattern(pillars, dayStem, monthBranch) {
5179
5415
  } else {
5180
5416
  ex = `月令${monthBranch}藏${hidden.join('')},本气${base.stem}(${base.god})未透干,以月令本气取格`;
5181
5417
  }
5182
- return { name, kind: '月令格', evidence: ex };
5418
+ return { name, kind: '月令格', evidence: ex, ...metaOf(use, usedBy, exposedKey) };
5419
+ }
5420
+
5421
+ // ── 成格评分(炳祥高级第 16 课口径)───────────────────────────
5422
+ // 只读评分:基础分按取格档位,再减「透干被合 30」「用神太弱 20」,60 分为及格线。
5423
+ // 不改 name / evidence / geju 三态,也不参与旺衰与喜忌。
5424
+ const ZHUANQI_BRANCHES = ['子', '午', '卯', '酉'];
5425
+ const BASE_BY_TIER = {
5426
+ 专气透: 90,
5427
+ 本气透: 70,
5428
+ 中气透: 50,
5429
+ 余气透: 20,
5430
+ 同五行透: 5,
5431
+ 会支取用: 60,
5432
+ 月令不透: 40,
5433
+ };
5434
+ const HE_PENALTY = 30;
5435
+ const WEAK_PENALTY = 20;
5436
+ const PATTERN_PASS = 60;
5437
+ const OUTER_PATTERNS = ['建禄格', '阳刃格', '月刃格', '月劫格'];
5438
+
5439
+ /** 天干五合:找与 stem 相合的另一干(合而不化也算) */
5440
+ function stemHeWith(stem, pillars) {
5441
+ for (const key of ['year', 'month', 'day', 'hour']) {
5442
+ const p = pillars[key];
5443
+ if (!p || !p.stem || p.stem === stem) continue;
5444
+ if (STEM_HE[stem + p.stem] || STEM_HE[p.stem + stem]) {
5445
+ return { pillar: POS_LABEL[key], key, stem: p.stem };
5446
+ }
5447
+ }
5448
+ return null;
5449
+ }
5450
+
5451
+ /** 用神太弱:无本气通根,且(被天干克 或 全局占比 <18%) */
5452
+ function patternWeakWith(pattern, pillars, wuxing = {}) {
5453
+ const stem = pattern.stem;
5454
+ const el = STEM_ELEMENT[stem];
5455
+ const hasBaseRoot = ['year', 'month', 'day', 'hour'].some((key) => {
5456
+ const p = pillars[key];
5457
+ if (!p) return false;
5458
+ return (HIDDEN[p.branch] || [])[0] === stem;
5459
+ });
5460
+ const total = Object.values(wuxing).reduce((n, v) => n + (Number(v) || 0), 0);
5461
+ const pct = total > 0 ? Math.round(((Number(wuxing[el]) || 0) / total) * 1000) / 10 : 0;
5462
+ const overcome = ['year', 'month', 'day', 'hour'].some((key) => {
5463
+ const p = pillars[key];
5464
+ if (!p || !p.stem || p.stem === stem) return false;
5465
+ const otherEl = STEM_ELEMENT[p.stem];
5466
+ return otherEl && OVERCOMES[otherEl] === el;
5467
+ });
5468
+ const weak = !hasBaseRoot && (pct < 18 || overcome);
5469
+ const reasons = [
5470
+ hasBaseRoot ? `有本气通根(${stem})` : '四支无本气通根',
5471
+ `全局 ${pct}%`,
5472
+ overcome ? '且被天干克' : '',
5473
+ ].filter(Boolean);
5474
+ return {
5475
+ weak,
5476
+ pct,
5477
+ hasBaseRoot,
5478
+ overcome,
5479
+ detail: reasons.join(','),
5480
+ };
5481
+ }
5482
+
5483
+ /**
5484
+ * 月令格成格评分。
5485
+ * @param {object} pattern monthPattern() 的返回(需 stamp / pos / usedBy)
5486
+ * @param {object} pillars 四柱
5487
+ * @param {string} dayStem 日干
5488
+ * @param {string} monthBranch 月支
5489
+ * @param {object} wuxing 五行分值
5490
+ */
5491
+ function patternScoreOf(pattern, pillars, dayStem, monthBranch, wuxing = {}) {
5492
+ if (!pattern) return null;
5493
+ if (pattern.kind !== '月令格' || OUTER_PATTERNS.includes(pattern.name)) {
5494
+ return {
5495
+ applicable: false,
5496
+ base: null,
5497
+ baseLabel: null,
5498
+ deductions: [],
5499
+ score: null,
5500
+ pass: null,
5501
+ grade: null,
5502
+ threshold: PATTERN_PASS,
5503
+ detail: `${pattern.name}属外格(建禄/羊刃/月劫),课程不按正八格的透干档位计成格分,故不套 −30 / −20。`,
5504
+ method: '只评正八格月令格;建禄/羊刃/月劫属外格,不评分',
5505
+ };
5506
+ }
5507
+
5508
+ const zhi = pattern.monthBranch || monthBranch;
5509
+ let baseLabel;
5510
+ if (pattern.usedBy === '透干') {
5511
+ if (ZHUANQI_BRANCHES.includes(zhi) && pattern.pos === '本气') baseLabel = '专气透';
5512
+ else baseLabel = `${pattern.pos}透`;
5513
+ } else if (pattern.usedBy === '会支') {
5514
+ baseLabel = '会支取用';
5515
+ } else {
5516
+ // 月令本气未透:若同五行异阴阳之干透出,按课里「同五行透」档(<10%)计
5517
+ const baseEl = STEM_ELEMENT[pattern.stem];
5518
+ const sameEl = ['year', 'month', 'hour']
5519
+ .map((k) => pillars[k] && pillars[k].stem)
5520
+ .find((s) => s && s !== pattern.stem && STEM_ELEMENT[s] === baseEl);
5521
+ baseLabel = sameEl ? '同五行透' : '月令不透';
5522
+ }
5523
+ const base = BASE_BY_TIER[baseLabel] ?? 40;
5524
+
5525
+ const deductions = [];
5526
+ const he = pattern.usedBy === '透干' ? stemHeWith(pattern.stem, pillars) : null;
5527
+ if (he) {
5528
+ deductions.push({
5529
+ reason: '透干被合',
5530
+ points: HE_PENALTY,
5531
+ detail: `${pattern.stem}与${he.pillar}干${he.stem}五合(合而不化也按被合论)`,
5532
+ });
5533
+ }
5534
+ const weak = patternWeakWith(pattern, pillars, wuxing);
5535
+ if (weak.weak) {
5536
+ deductions.push({ reason: '用神太弱', points: WEAK_PENALTY, detail: weak.detail });
5537
+ }
5538
+
5539
+ const deducted = deductions.reduce((n, d) => n + d.points, 0);
5540
+ const score = Math.max(0, base - deducted);
5541
+ const pass = score >= PATTERN_PASS;
5542
+ const detail = `基础分 ${base}(${baseLabel})`
5543
+ + `${deductions.length ? `,${deductions.map((d) => `${d.reason} −${d.points}(${d.detail})`).join(',')}` : ',无减项'}`
5544
+ + `,成格分 ${score}/100,${pass ? `过 ${PATTERN_PASS} 分线,按课里作成格论` : `不及 ${PATTERN_PASS} 分线,按课里作不成格论`}。`;
5545
+
5546
+ return {
5547
+ applicable: true,
5548
+ base,
5549
+ baseLabel,
5550
+ deductions,
5551
+ score,
5552
+ pass,
5553
+ grade: pass ? '成格' : '不成格',
5554
+ threshold: PATTERN_PASS,
5555
+ detail,
5556
+ method: '基础分按取格档位(专气透 90 / 本气透 70 / 中气透 50 / 余气透 20 / 同五行透 5 / 会支取用 60 / 月令不透 40);透干被合 −30、用神太弱 −20;60 分及格。只读标注,不改取格与既有三态。',
5557
+ };
5183
5558
  }
5184
5559
 
5185
5560
  // ── 特殊格 / 组合 ────────────────────────────────────────────
@@ -5343,6 +5718,170 @@ function tongguanOf(favor = [], avoid = [], wuxing = {}) {
5343
5718
  return null;
5344
5719
  }
5345
5720
 
5721
+ // ── 病药(炳祥高级第 6 课口径)───────────────────────────────
5722
+ // 只做只读标注:病=最强忌神,药=能克忌神的喜神(其次取泄),不改喜忌结论。
5723
+ // 门槛按课里:正格忌神 >35%、从格 >15%;从格能量放大,10% ≈ 正格 30-40%。
5724
+ const BING_THRESHOLD = { 正格: 35, 从格: 15 };
5725
+
5726
+ function bingyaoOf({ dayStem, strength, wuxing = {}, yongshen = {}, geju = [] }) {
5727
+ const roles = elementRoles(dayStem);
5728
+ const roleName = (el) => (el === roles.me ? '比劫' : el === roles.inMe ? '印'
5729
+ : el === roles.shi ? '食伤' : el === roles.cai ? '财' : '官杀');
5730
+ const w = (el) => Math.round((Number(wuxing[el]) || 0) * 10) / 10;
5731
+ const total = Object.values(wuxing).reduce((n, v) => n + (Number(v) || 0), 0);
5732
+ const pctOf = (el) => (total > 0 ? Math.round((w(el) / total) * 1000) / 10 : 0);
5733
+ const desc = (x) => `${x.element}(${x.role}·${x.pct}%)`;
5734
+
5735
+ const cong = (geju || []).find((g) => g.kind === '从格');
5736
+ const threshold = cong ? BING_THRESHOLD.从格 : BING_THRESHOLD.正格;
5737
+
5738
+ // 从格的喜忌与扶抑相反。现有 yongshenOf 只按旺衰给扶抑喜忌,没有吃 geju,
5739
+ // 这里在病药层单列一份反转后的喜忌,避免把从格的喜神当忌神;不改 yongshen 本身。
5740
+ let favorEls = yongshen.favor || [];
5741
+ let avoidEls = yongshen.avoid || [];
5742
+ let source = '扶抑喜忌';
5743
+ if (cong) {
5744
+ // 按从格类型分:从财/从杀忌印比;从儿忌印与官杀(比劫生食伤不忌);从强忌财官食伤。
5745
+ if (cong.name === '从强格') {
5746
+ favorEls = [roles.inMe, roles.me];
5747
+ avoidEls = [roles.cai, roles.guan, roles.shi];
5748
+ } else if (cong.name === '从儿格') {
5749
+ favorEls = [roles.shi, roles.cai];
5750
+ avoidEls = [roles.inMe, roles.guan];
5751
+ } else if (cong.name === '从杀格') {
5752
+ favorEls = [roles.guan, roles.cai];
5753
+ avoidEls = [roles.me, roles.inMe];
5754
+ } else {
5755
+ favorEls = [roles.cai, roles.guan, roles.shi];
5756
+ avoidEls = [roles.me, roles.inMe];
5757
+ }
5758
+ source = '从格反转';
5759
+ }
5760
+
5761
+ const sick = avoidEls
5762
+ .map((el) => ({ element: el, role: roleName(el), pct: pctOf(el) }))
5763
+ .sort((a, b) => b.pct - a.pct)[0] || null;
5764
+
5765
+ const base = {
5766
+ threshold,
5767
+ isCong: !!cong,
5768
+ congName: cong ? cong.name : null,
5769
+ favorSource: source,
5770
+ sick: sick || null,
5771
+ remedy: null,
5772
+ level: '无病',
5773
+ method: '病=最强忌神;正格 >35%、从格 >15% 才算病;从格先按喜忌反转取忌;药优先取克忌神的喜神,其次取泄;只作标注,不改喜忌',
5774
+ };
5775
+
5776
+ if (!sick || sick.pct <= threshold) {
5777
+ return {
5778
+ ...base,
5779
+ detail: sick
5780
+ ? `忌神最重为${desc(sick)},未过${cong ? '从格' : '正格'} ${threshold}% 门槛,按炳祥口径作无病论。`
5781
+ : `本盘无忌神可论,按炳祥口径作无病论。`,
5782
+ };
5783
+ }
5784
+
5785
+ const favor = favorEls
5786
+ .map((el) => ({ element: el, role: roleName(el), pct: pctOf(el) }))
5787
+ .filter((x) => x.pct > 0)
5788
+ .sort((a, b) => b.pct - a.pct);
5789
+ const over = (a, b) => OVERCOMES[a] === b; // a 克 b
5790
+
5791
+ let remedy = null;
5792
+ let mode = null;
5793
+ let note = '';
5794
+
5795
+ if (sick.role === '印') {
5796
+ // 印为忌:财克印最优,但财力须 ≥ 食伤的 50%;不足则只能用食伤硬抗(安慰剂)。
5797
+ const cai = favor.find((f) => f.role === '财');
5798
+ const shi = favor.find((f) => f.role === '食伤');
5799
+ if (cai && pctOf(roles.cai) >= pctOf(roles.shi) * 0.5) {
5800
+ remedy = cai;
5801
+ mode = '财克印';
5802
+ } else if (shi && pctOf(roles.shi) >= 1) {
5803
+ remedy = shi;
5804
+ mode = '食伤硬抗(安慰剂)';
5805
+ note = `财力${pctOf(roles.cai)}% 不足食伤的 50%(食伤${pctOf(roles.shi)}%),按课里只能以食伤硬抗。`;
5806
+ } else {
5807
+ note = `财、食伤都不足以制印(财${pctOf(roles.cai)}%、食伤${pctOf(roles.shi)}%),按课里属有病无药。`;
5808
+ }
5809
+ } else {
5810
+ // 克 > 泄:先找克忌神的喜神,再退而取「忌神所生」的喜神(泄其势)。
5811
+ const ke = favor.find((f) => over(f.element, sick.element));
5812
+ if (ke) {
5813
+ remedy = ke;
5814
+ mode = '克';
5815
+ } else {
5816
+ const xie = favor.find((f) => GENERATES[sick.element] === f.element);
5817
+ if (xie) {
5818
+ remedy = xie;
5819
+ mode = '泄';
5820
+ note = `喜神中无克${sick.element}者,退取${xie.element}泄其势(课里「克 > 泄」,泄力次之)。`;
5821
+ }
5822
+ }
5823
+ }
5824
+
5825
+ const level = remedy ? '有病有药' : '有病无药';
5826
+ const detail = [
5827
+ `病为${desc(sick)},已过${cong ? `从格(${cong.name})` : '正格'} ${threshold}% 门槛。`,
5828
+ remedy
5829
+ ? `药取${desc(remedy)},以「${mode}」制病${remedy.pct < sick.pct ? `;药力弱于病(${remedy.pct}% < ${sick.pct}%)` : ''}。`
5830
+ : `喜神中无可制之药,按炳祥口径属有病无药。`,
5831
+ note,
5832
+ ].filter(Boolean).join('');
5833
+
5834
+ return { ...base, level, remedy, mode, detail };
5835
+ }
5836
+
5837
+ // ── 闲神(炳祥高级第 9 课口径)───────────────────────────────
5838
+ // 狭义闲神=食伤与官杀中较弱的那一个;4% 最佳、<10% 合格、>=10% 显忌。
5839
+ // 显性(该五行透干)×3 只用于文案排序,不改分值,也不参与旺衰/喜忌。
5840
+ function xianshenOf({ dayStem, wuxing = {}, pillars = {}, yongshen = {} }) {
5841
+ const roles = elementRoles(dayStem);
5842
+ const w = (el) => Math.round((Number(wuxing[el]) || 0) * 10) / 10;
5843
+ const total = Object.values(wuxing).reduce((n, v) => n + (Number(v) || 0), 0);
5844
+ const pctOf = (el) => (total > 0 ? Math.round((w(el) / total) * 1000) / 10 : 0);
5845
+
5846
+ const candidates = [
5847
+ { role: '食伤', element: roles.shi },
5848
+ { role: '官杀', element: roles.guan },
5849
+ ].map((x) => ({ ...x, pct: pctOf(x.element) }));
5850
+ const idle = candidates.sort((a, b) => a.pct - b.pct)[0];
5851
+ const band = idle.pct <= 4 ? '最佳' : idle.pct < 10 ? '合格' : '显忌';
5852
+
5853
+ const stems = ['year', 'month', 'day', 'hour']
5854
+ .map((k) => pillars[k] && pillars[k].stem)
5855
+ .filter(Boolean);
5856
+ const visible = stems.some((s) => STEM_ELEMENT[s] === idle.element);
5857
+ const favorRoles = yongshen.favorRoles || [];
5858
+ const avoidRoles = yongshen.avoidRoles || [];
5859
+ const stance = favorRoles.includes(idle.role) ? '喜' : avoidRoles.includes(idle.role) ? '忌' : '中性';
5860
+
5861
+ const level = band === '显忌' ? '闲神显忌' : band === '最佳' ? '闲神得位' : '闲神合格';
5862
+ const detail = [
5863
+ `狭义闲神取${idle.element}(${idle.role},全局 ${idle.pct}%),是食伤与官杀中较弱的一个。`,
5864
+ band === '最佳'
5865
+ ? '4% 上下,正是课里说的最佳区间。'
5866
+ : band === '合格'
5867
+ ? '不足 10%,按课里算合格,不作显忌。'
5868
+ : '超过 10%,课里按忌神论:不是大凶,是别扭、乱使劲、干了很多事没结果。',
5869
+ visible ? `此神透干(显性),按课里「显性 ×3」在文案里优先提示;分值不改。` : '此神未透干,属暗闲神,影响更轻。',
5870
+ `在现有喜忌里为${stance}。`,
5871
+ ].join('');
5872
+
5873
+ return {
5874
+ idle,
5875
+ band,
5876
+ level,
5877
+ visible,
5878
+ stance,
5879
+ displayWeight: Math.round(idle.pct * (visible ? 3 : 1) * 10) / 10,
5880
+ detail,
5881
+ method: '闲神=食伤与官杀中较弱者;4% 最佳 / <10% 合格 / >=10% 显忌;显性 ×3 只作文案排序,不改分值与喜忌',
5882
+ };
5883
+ }
5884
+
5346
5885
  // ── 用神 / 喜忌 ──────────────────────────────────────────────
5347
5886
 
5348
5887
 
@@ -5545,21 +6084,36 @@ function scoreLuck(ganzhi, frame) {
5545
6084
  function analyzeBaziFrame({ pillars, dayStem, monthBranch, strength, wuxing }) {
5546
6085
  const gods = godStats(pillars, dayStem);
5547
6086
  const month = monthPattern(pillars, dayStem, monthBranch);
6087
+ // 成格评分:只给月令格挂分,外格返回 applicable:false;随后续 specialPatterns 一起回传
6088
+ const patternScore = patternScoreOf(month, pillars, dayStem, monthBranch, wuxing);
6089
+ if (patternScore && patternScore.applicable) {
6090
+ month.score = patternScore.score;
6091
+ month.scoreDetail = patternScore;
6092
+ }
5548
6093
  const special = specialPatterns(pillars, dayStem, monthBranch, strength, wuxing, gods);
5549
6094
  const geju = [month, ...special];
5550
6095
  const yongshen = yongshenOf(dayStem, monthBranch, strength, wuxing, Object.values(pillars).map((p) => p.branch));
6096
+ // 病药:只读标注,复用上面的喜忌,不改结论
6097
+ const bingyao = bingyaoOf({ dayStem, strength, wuxing, yongshen, geju });
6098
+ // 闲神:只读标注
6099
+ const xianshen = xianshenOf({ dayStem, wuxing, pillars, yongshen });
5551
6100
  return {
5552
6101
  geju,
5553
6102
  primary: month,
5554
6103
  primaryName: month.name,
5555
6104
  godStats: gods,
5556
6105
  yongshen,
6106
+ bingyao,
6107
+ xianshen,
6108
+ patternScore,
5557
6109
  dayStem,
5558
6110
  monthBranch,
6111
+ // 八大地支结构:静态结构 + 宫位权重,仅作标注,不参与取格与喜忌
6112
+ structure: strength.branchStructure || null,
5559
6113
  };
5560
6114
  }
5561
6115
 
5562
- return { tenGod, elementRoles, tongguanOf, scoreLuck, analyzeBaziFrame };
6116
+ return { tenGod, elementRoles, patternScoreOf, tongguanOf, bingyaoOf, xianshenOf, scoreLuck, analyzeBaziFrame };
5563
6117
  })();
5564
6118
 
5565
6119
  __mods.bazi = (function () {
@@ -5573,6 +6127,7 @@ const { bool } = (__mods.bool || {});
5573
6127
  const { nayinOf } = (__mods.nayin || {});
5574
6128
  const { SANHE } = (__mods.sanhe || {});
5575
6129
  const { analyzeBaziFrame, scoreLuck } = (__mods.geju || {});
6130
+ const { branchStructureOf, weightPctOf } = (__mods.structure || {});
5576
6131
 
5577
6132
  // 地支藏干(本气 / 中气 / 余气)
5578
6133
  // 巳的顺序是「丙庚戊」而不是「丙戊庚」:庚长生于巳,是中气;戊是辰月余气,排末位。
@@ -5708,7 +6263,38 @@ function rootedWeightOf(pillars, el, factor) {
5708
6263
  return w;
5709
6264
  }
5710
6265
 
5711
- /** 干支关系:五合、相冲、相克;六合、三合半合、三会半会拱会、冲、害、破、刑 */
6266
+ // ── 暗合(盲派/现代命理的补充定义)───────────────────────────
6267
+ // 子平正法只有天干五合;暗合只作取象与风水参考,不参与旺衰与格局。
6268
+ // 通合:亥午、寅丑(藏干双合);禄合:卯申、午亥、巳子;午亥与通合重叠时只报通合。
6269
+ // 干支暗合取课里明列的六组;自合只取辰(戊癸)。
6270
+ const ANHE_TONG = [['亥', '午'], ['寅', '丑']];
6271
+ const ANHE_LU = [['卯', '申'], ['午', '亥'], ['巳', '子']];
6272
+ const ANHE_GANZHI = new Set(['甲午', '丁亥', '戊子', '辛巳', '壬午', '癸巳']);
6273
+ const ANHE_ZIHE = new Set(['辰']);
6274
+
6275
+ function anheOf(pillars, keys) {
6276
+ const out = [];
6277
+ const branches = keys.map((k) => pillars[k].branch);
6278
+ const has = (a, b) => branches.includes(a) && branches.includes(b);
6279
+ const seen = new Set();
6280
+ const push = (text) => { if (!seen.has(text)) { seen.add(text); out.push(text); } };
6281
+
6282
+ for (const [a, b] of ANHE_TONG) if (has(a, b)) push(`${a}${b}通合(藏干双合)`);
6283
+ for (const [a, b] of ANHE_LU) {
6284
+ if (!has(a, b)) continue;
6285
+ if (seen.has(`${a}${b}通合(藏干双合)`) || seen.has(`${b}${a}通合(藏干双合)`)) continue;
6286
+ push(`${a}${b}禄合`);
6287
+ }
6288
+ for (const k of keys) {
6289
+ const pl = pillars[k];
6290
+ const gz = pl.ganzhi || `${pl.stem}${pl.branch}`;
6291
+ if (ANHE_GANZHI.has(gz)) push(`${gz}干支暗合`);
6292
+ if (ANHE_ZIHE.has(pl.branch)) push(`${gz}自合`);
6293
+ }
6294
+ return out;
6295
+ }
6296
+
6297
+ /** 干支关系:五合、相冲、相克;六合、三合半合、三会半会拱会、冲、害、破、刑;暗合另列 */
5712
6298
  function buildRelations(pillars, keys = ['year', 'month', 'day', 'hour']) {
5713
6299
  const stems = keys.map((k) => pillars[k].stem);
5714
6300
  const branches = keys.map((k) => pillars[k].branch);
@@ -5789,7 +6375,12 @@ function buildRelations(pillars, keys = ['year', 'month', 'day', 'hour']) {
5789
6375
  }
5790
6376
  return out;
5791
6377
  };
5792
- return { stems: uniq(stemOut), branches: uniq(branchOut) };
6378
+ return {
6379
+ stems: uniq(stemOut),
6380
+ branches: uniq(branchOut),
6381
+ anhe: anheOf(pillars, keys),
6382
+ anheNote: '暗合只作取象与风水参考,不参与旺衰与格局;只出现一对时按课里可忽略',
6383
+ };
5793
6384
  }
5794
6385
 
5795
6386
  /** 十神:以日干为我 */
@@ -5968,6 +6559,15 @@ function buildBazi(input) {
5968
6559
  // 四舍五入到 12 个月时必须进位,否则显示「X 岁 12 个月起运」
5969
6560
  if (startAgeMonths >= 12) { startAgeMonths -= 12; startAgeYears += 1; }
5970
6561
 
6562
+ // 并列的截断口径:年/月取整后,余数继续往下拆。1 月 = 0.25 日 = 6 时,
6563
+ // 所以月内余数 ×30 得「日」、日余 ×24 得「时」——与 startAge 的四舍五入同源、不互相覆盖。
6564
+ const exactYears = Math.floor(totalMonths / 12);
6565
+ const exactRemMonths = totalMonths - exactYears * 12;
6566
+ const exactMonths = Math.floor(exactRemMonths);
6567
+ const exactRemDays = (exactRemMonths - exactMonths) * 30;
6568
+ const exactDays = Math.floor(exactRemDays);
6569
+ const exactHours = Math.floor((exactRemDays - exactDays) * 24);
6570
+
5971
6571
  // 交运:三日折一年。
5972
6572
  // 古法三条换算是同一比例的三种说法——3 日折 1 年、1 日折 4 个月、1 时辰折 10 日:
5973
6573
  // 把「出生 → 起运那一侧的节令」的真实间隔按 365.2422/3(≈121.7474)倍放大,
@@ -6134,6 +6734,11 @@ function buildBazi(input) {
6134
6734
  },
6135
6735
  };
6136
6736
 
6737
+ // 八大地支结构(炳祥课堂口径):只读标注,不参与上面的旺衰打分
6738
+ strength.branchStructure = branchStructureOf(pillars, dayStem);
6739
+ // 宫位权重百分比视图:纯展示换算,与上面的评分口径并列,不改分值
6740
+ strength.weightPct = weightPctOf(pillars, { hourUnknown });
6741
+
6137
6742
  // ── 格局 / 用神 / 行运 ─────────────────────────────────────
6138
6743
  const frame = analyzeBaziFrame({ pillars, dayStem, monthBranch, strength, wuxing });
6139
6744
 
@@ -6292,6 +6897,10 @@ function buildBazi(input) {
6292
6897
  direction: forward ? '顺行' : '逆行',
6293
6898
  rule: `${isYangYear ? '阳' : '阴'}年${gender === 'male' ? '男' : '女'}${forward ? '顺排' : '逆排'}`,
6294
6899
  startAge: { years: startAgeYears, months: startAgeMonths },
6900
+ startAgeExact: {
6901
+ years: exactYears, months: exactMonths, days: exactDays, hours: exactHours,
6902
+ caliber: '截断到年月日时',
6903
+ },
6295
6904
  toNextJieDays: Math.round(spanDays * 100) / 100,
6296
6905
  // 交运时刻(日/时辰级):上方 startAge 是四舍五入到月的旧字段,两者同源、不互相覆盖
6297
6906
  jiao: {
@@ -6373,6 +6982,20 @@ function buildBazi(input) {
6373
6982
  // 旺衰与格局:内部仍按正午时柱跑(否则没有可比的基准),但必须说清这是占位值,
6374
6983
  // 不能写成「时柱不参与任何判定」——那与 strength/frame 实际用了时柱相矛盾
6375
6984
  out.strength.precision = 'three-pillars';
6985
+ // 结构同此:第四支是正午占位,不套从格概率,免得把编出来的时支说成实有
6986
+ if (out.strength.branchStructure) {
6987
+ const bs = out.strength.branchStructure;
6988
+ bs.placeholderHour = true;
6989
+ bs.congProb = null;
6990
+ bs.congRange = '三柱不判结构概率';
6991
+ bs.word = '时柱暂缺,第四支为正午占位,不套从格概率';
6992
+ bs.note = '时柱暂缺:第四支为占位值,只列结构形态,不据此断从格;补上出生时刻后可能改变';
6993
+ bs.detail = `${bs.detail}(时支为占位值)`;
6994
+ }
6995
+ if (out.strength.weightPct) {
6996
+ out.strength.weightPct.placeholderHour = true;
6997
+ out.strength.weightPct.note = '时柱暂缺:时柱权重不计入合计(本盘 81%),不重新归一化;补上出生时刻后再按 100% 看';
6998
+ }
6376
6999
  out.notes = [
6377
7000
  '时柱暂缺:未提供出生时刻,时柱不对外给出,也不计入神煞与干支关系',
6378
7001
  '旺衰、格局、用神、调候暂以正午时柱估算,补上出生时刻后可能改变',
@@ -14614,6 +15237,15 @@ const JU_NUMBERS_MAP = {
14614
15237
  寒露: '693', 立冬: '693', 处暑: '147', 霜降: '582', 小雪: '582', 大雪: '471',
14615
15238
  };
14616
15239
 
15240
+ /** 供测试与文档引用:二十四节气三元局数的只读视图(B14 与《奇门遁甲元灵经》卷一逐格对拍) */
15241
+ const QIMEN_JU_TABLE = Object.freeze({ ...JU_NUMBERS_MAP });
15242
+
15243
+ /** 供测试与文档引用:九星八门定宫的只读视图(B14 与元灵经卷一、秘笈总序逐宫对拍) */
15244
+ const QIMEN_BASE_TABLES = Object.freeze({
15245
+ stars: Object.freeze({ ...BASIC_STARS }),
15246
+ gates: Object.freeze({ ...BASIC_GATES }),
15247
+ });
15248
+
14617
15249
  // ── 历法适配:把本插件的时间轴转成奇门引擎要的 snapshot ──
14618
15250
  const xunOf = (gz) => {
14619
15251
  const idx = indexOfGz(gz);
@@ -15186,7 +15818,7 @@ function buildQimen(input) {
15186
15818
 
15187
15819
 
15188
15820
 
15189
- return { buildSnapshot, wuxingRelation, gatePalaceRelation, stemPalaceRelation, jiXingOf, ruMuOf, liuYiRuMuOf, QIMEN_CONDITION_TABLES, monthState, monthElementOf, groupByState, QIMEN_STATE_TABLES, xunShouLiuYi, buildQimen };
15821
+ return { QIMEN_JU_TABLE, QIMEN_BASE_TABLES, buildSnapshot, wuxingRelation, gatePalaceRelation, stemPalaceRelation, jiXingOf, ruMuOf, liuYiRuMuOf, QIMEN_CONDITION_TABLES, monthState, monthElementOf, groupByState, QIMEN_STATE_TABLES, xunShouLiuYi, buildQimen };
15190
15822
  })();
15191
15823
 
15192
15824
  __mods['qimen-interpret'] = (function () {
@@ -16948,10 +17580,14 @@ function buildXiaoliuren(input) {
16948
17580
  const info = PALACES[p3];
16949
17581
  return {
16950
17582
  system: 'xiaoliuren',
16951
- meta: { mode: mode === 'time' ? '时间起课' : '报数起课', question: input.question || null, originText },
17583
+ // question 放在顶层,不放进 meta:句柄按设计不收 question(它不影响盘面),
17584
+ // 放进 meta 会让「句柄回放」与「原始调用」的 meta 不一致(回放后变 null),
17585
+ // 看起来像盘变了。其余体系也都是把 question 挂顶层。
17586
+ meta: { mode: mode === 'time' ? '时间起课' : '报数起课', originText },
17587
+ question: input.question || null,
16952
17588
  steps: [p1, p2, p3],
16953
17589
  result: { palace: p3, luck: info.luck, text: info.text },
16954
- sources: ['掌诀口径:通行马前课(白话提炼,非原文照录)'],
17590
+ sources: ['掌诀口径:通行马前口诀(白话提炼,非原文照录)'],
16955
17591
  disclaimer: '小六壬为轻量快占,属民俗娱乐参考;重要事项请综合现实信息判断。',
16956
17592
  };
16957
17593
  }
@@ -19258,36 +19894,36 @@ const KNOWLEDGE = [
19258
19894
  system: 'qimen', dims: ['overall'],
19259
19895
  gist: '冬至后阳遁、夏至后阴遁,局数由节气与三元定。',
19260
19896
  detail: '一年分阴阳两遁,各九局;局数取决于当前节气与上中下三元(符头定元)。取局流派有拆补、超神接气、置闰三种,同一时刻可能排出不同局数——这不是算错,是流派差异。',
19261
- source: '《烟波钓叟歌》;拆补与置闰见《奇门遁甲统宗》',
19897
+ source: '《烟波钓叟歌》;拆补与置闰见《奇门遁甲统宗》;符头定三元与二十四节气局数见《奇门遁甲元灵经》卷一;超神接气与阳遁仪顺奇逆见《奇门遁甲秘笈大全》总序',
19262
19898
  status: 'variant',
19263
- note: '三种取局法在节气交接前后差异最明显,本插件同时实现三种,可在参数里指定。',
19899
+ note: '三种取局法在节气交接前后差异最明显,本插件同时实现三种,可在参数里指定。元灵经卷一「阴阳各局」的二十四节气三元局数与引擎表逐格相符(24/24),三元符头「甲己干加子午卯酉为上元」亦与引擎定局口径同;秘笈总序「符速节迟…用超神之法,节先符后…行节气之方」正是超神与接气两名的由来。',
19264
19900
  },
19265
19901
  {
19266
19902
  id: 'qimen-jixing',
19267
19903
  system: 'qimen', dims: ['overall', 'work'],
19268
19904
  gist: '六仪落刑宫叫击刑,是宫位条件不是判决。',
19269
19905
  detail: '六仪按所配地支相刑:甲子戊落震三(子刑卯)、甲戌己落坤二(戌刑未)、甲申庚落艮八(申刑寅)、甲午辛落离九(午自刑)、甲辰壬落巽四(辰自刑)、甲寅癸落巽四(寅刑巳)。天盘六仪落到自己那一宫即击刑,取象是受制、掣肘、旧账翻出来,多用来量这个符号本身使不使得上力。',
19270
- source: '《烟波钓叟歌》刑格注;《奇门遁甲统宗》',
19906
+ source: '《烟波钓叟歌》刑格注;《奇门遁甲统宗》;六仪配旬首见《奇门遁甲元灵经》卷一,落宫表见《奇门遁甲秘笈大全》卷十五·六仪刑击格',
19271
19907
  status: 'variant',
19272
- note: '有流派只论天盘六仪击刑,也有把地盘一并算的;本插件只判天盘,且只作宫位条件,不自动断用神受制。',
19908
+ note: '有流派只论天盘六仪击刑,也有把地盘一并算的;本插件只判天盘,且只作宫位条件,不自动断用神受制。秘笈卷十五「甲子戊直符加三宫,甲戌己值符加二宫,甲申庚加八宫,甲午辛加九宫,甲辰壬加四宫,甲寅癸值符加四宫」与引擎六格逐一相符。',
19273
19909
  },
19274
19910
  {
19275
19911
  id: 'qimen-rumu',
19276
19912
  system: 'qimen', dims: ['overall'],
19277
19913
  gist: '天盘干落墓宫叫入墓:三奇与六仪各按五行墓库判。',
19278
19914
  detail: '按五行墓库推,乙、丁两个阴干从《玄机赋》逆行取墓说:乙墓戌(乾六)、丙墓戌(乾六)、丁墓丑(艮八);六仪中戊墓戌(乾六)、己庚墓丑(艮八)、辛壬墓辰(巽四)、癸墓未(坤二)。天盘干落到自己的墓宫即入墓。取象是收束、藏起来、事情压住不动;好坏要看所问之事——问收藏、问了结,入墓未必是坏事。',
19279
- source: '《奇门遁甲统宗》三奇入墓条;阴干逆行取墓见其中「玄机赋」;六仪入墓见《奇门遁甲秘笈大全》卷十五;五行墓库见《五行大义》',
19915
+ source: '《奇门遁甲统宗》三奇入墓条;阴干逆行取墓见其中「玄机赋」;三奇入墓歌见《奇门遁甲秘笈大全》卷十五(同卷时墓格只戊、壬、丙三句与本盘相符,辛、乙两句相异);五行墓库见《五行大义》',
19280
19916
  status: 'variant',
19281
- note: '各家对入墓范围说法不一:有只论三奇,也有把六仪、八门、九星一并论墓的;本插件判天盘三奇与六仪,八门、九星暂不入墓。墓宫取法同样有异说——通行另有一说按五行墓库直取(乙 木墓未、丁 火墓戌),本盘依《玄机赋》阴干逆行说(乙墓戌、丁墓丑),与引擎 qimen.js 的 RU_MU 表同源。',
19917
+ note: '各家对入墓范围说法不一:有只论三奇,也有把六仪、八门、九星一并论墓的;本插件判天盘三奇与六仪,八门、九星暂不入墓。墓宫取法同样有异说——通行另有一说按五行墓库直取(乙 木墓未、丁 火墓戌),本盘依《玄机赋》阴干逆行说(乙墓戌、丁墓丑),与引擎 qimen.js 的 RU_MU 表同源。秘笈卷十五「三奇入墓歌」作乙丙临乾、丁临艮,与本盘三奇同;但同卷「时墓格」作辛丑墓艮、乙未墓坤,与本盘辛墓辰巽、乙墓戌乾不同,「奇墓格」又作乙到临坤——一书之内三说并存,照录并列,不拿它改引擎表。',
19282
19918
  },
19283
19919
  {
19284
19920
  id: 'qimen-ganying',
19285
19921
  system: 'qimen', dims: ['overall', 'work'],
19286
19922
  gist: '十干克应看天盘干加地盘干:同宫两干相遇,先取成象,再与门星神合看。',
19287
19923
  detail: '十干克应把天盘干与地盘干的组合收成 100 组,每组一个格名与一段取象。例如甲加丙「青龙返首」主大吉大利,甲加庚「值符飞宫」主吉事不成,乙加辛「青龙逃走」主失财逃亡。它回答的是「这两股气碰在一起像什么」,不单独回答「事情成不成」;同一组落在不同门、星、神与旺衰下,轻重会变。',
19288
- source: '《奇门十干应克》通行 100 组表;《奇门遁甲元灵经》卷二「天盘加地盘吉凶」存甲、乙、丙三组异文',
19924
+ source: '《奇门十干应克》通行 100 组表;《奇门遁甲元灵经》卷二「天盘加地盘吉凶」存甲、乙、丙三组异文;格名与取象另见《奇门遁甲秘笈大全》总序',
19289
19925
  status: 'variant',
19290
- note: '100 组表各家措辞不一,本插件取通行表;元灵经只存三组且文字不同,两本并列、不合并。十干克应只作取象,不自动折吉凶。',
19926
+ note: '100 组表各家措辞不一,本插件取通行表;元灵经只存三组(各 9 条,三组都缺「加戊」,页面到丙加癸为止)且文字不同,两本并列、不合并。元灵经甲加甲「吉凶不定,惟看门如何。门吉则吉,门凶则凶」正是本卡「先取成象、再与门星神合看」的原文依据;同格异文如青龙返首(总序作「反首」)、乙加辛青龙逃走三处断语各别(财物废坠/主财失逃亡/奴仆拐带六畜皆伤)。已录原文只证这几组与这一层口径,不证 100 组表全文。十干克应只作取象,不自动折吉凶。',
19291
19927
  },
19292
19928
  {
19293
19929
  id: 'qimen-wangshuai',
@@ -19302,9 +19938,9 @@ const KNOWLEDGE = [
19302
19938
  system: 'qimen', dims: ['overall', 'work'],
19303
19939
  gist: '旺衰是助力,不单独定吉凶:门旺而宫克门仍受制。',
19304
19940
  detail: '月令旺衰只回答「这个符号自身有多少气」,不回答「事情成不成」。取象时要和宫位关系叠起来看:门旺但同宫门克宫(门迫),力量被宫位掣肘;门囚但宫生门,仍得宫助。星同理,旺星落宫若被克,也难全其用。',
19305
- source: '《奇门遁甲统宗》;《烟波钓叟歌》门宫生克注',
19941
+ source: '《奇门遁甲统宗》;《烟波钓叟歌》门宫生克注;「门尅宫为迫,宫尅门为制,门生宫为和,宫生门为义」见《奇门遁甲秘笈大全》卷十五·迫制和义格,八门迫宫见同卷门迫歌;休门到离为迫见《奇门遁甲元灵经》卷二·八门值事',
19306
19942
  status: 'variant',
19307
- note: '有流派按时令而非月令论门星旺衰,也有以落宫五行当令者;本插件统一按月支五行判,并在解读里明确只作强度参考。',
19943
+ note: '有流派按时令而非月令论门星旺衰,也有以落宫五行当令者;本插件统一按月支五行判,并在解读里明确只作强度参考。门宫四类关系(迫/制/和/义)与引擎 gatePalaceRelation 一一对应,门迫歌八格全是门克宫、逐格对拍相符;古籍在迫制之后直接下吉凶(「吉门被迫吉不就,凶门被迫祸重重」),本插件只取落宫条件,不自动折吉凶。',
19308
19944
  },
19309
19945
  {
19310
19946
  id: 'qimen-gan-shengke',
@@ -19924,6 +20560,22 @@ const CITATION_BOOKS = {
19924
20560
  author: '清·官修(页首未署撰人)',
19925
20561
  base: 'https://www.luckclub.cn/qimen/007/',
19926
20562
  },
20563
+ // ── B14 新增书目(2026-11-08):把覆盖表里「只有书名」的三项升级成有原文
20564
+ yichuan: {
20565
+ name: '易传',
20566
+ author: '佚名(战国至秦汉解易论文集,本页未署撰人)',
20567
+ base: 'https://www.luckclub.cn/yijing/002/', // 本站只存系辞上下、说卦、序卦、杂卦五篇,彖象文言未收
20568
+ },
20569
+ yuanling: {
20570
+ name: '奇门遁甲元灵经',
20571
+ author: '清·隐溪居士(序末自署光绪九年癸未)',
20572
+ base: 'https://www.luckclub.cn/qimen/005/',
20573
+ },
20574
+ miji: {
20575
+ name: '奇门遁甲秘笈大全',
20576
+ author: '旧题汉·诸葛亮撰、明·刘伯温辑(分类页所载,正文未署)',
20577
+ base: 'https://www.luckclub.cn/qimen/003/', // 又名《金函奇门遁甲秘笈全书》《奇门大全》;页内「干」即乾、「直符」即值符
20578
+ },
19927
20579
  };
19928
20580
 
19929
20581
  // 引文定位表:id / 书名 / 卷章 / 链接。原文照录与本站自撰白话在 citations-fuwen.js。
@@ -20941,6 +21593,109 @@ const CITATIONS = [
20941
21593
  chapter: '卷下 集说篇',
20942
21594
  url: 'https://www.luckclub.cn/daliuren/003/004/',
20943
21595
  },
21596
+ // ── B14:奇门两本「只有书名」升级为有原文(2026-11-08,逐句过底本子串)
21597
+ // 这 16 条不是泛泛补书史:每一条都对得上引擎已有的一张表或一层判定,且逐格对拍过——
21598
+ // 元灵经卷一 24 节气三元局数 == JU_NUMBERS_MAP(24/24);三元符头 == 引擎定局说明;
21599
+ // 卷一六仪配旬首 == XUN_SHOU_TO_FULL_MAP;卷二「惟看门如何」正是克应卡「不单独断吉凶」的原文依据;
21600
+ // 秘笈卷十五六仪刑击格 == JI_XING(6/6);门迫歌八格全是门克宫;迫制和义格 == gatePalaceRelation 前四类;
21601
+ // 三奇入墓歌乙丙临乾丁临艮 == RU_MU;时墓格只戊壬丙三句相符、辛乙两句相异,照实并列不代改。
21602
+ // 易传十篇与本站判定层对不上(方位表只在 fengshui.js,而地相无知识卡),全部走附文库只入索引页。
21603
+ {
21604
+ id: 'yl-yinyang',
21605
+ book: 'yuanling',
21606
+ chapter: '卷一·阴阳遁',
21607
+ url: 'https://www.luckclub.cn/qimen/005/004/',
21608
+ },
21609
+ {
21610
+ id: 'yl-jushu',
21611
+ book: 'yuanling',
21612
+ chapter: '卷一·阴阳各局(附一卦管三节)',
21613
+ url: 'https://www.luckclub.cn/qimen/005/004/',
21614
+ },
21615
+ {
21616
+ id: 'yl-futou',
21617
+ book: 'yuanling',
21618
+ chapter: '卷一·三元符头',
21619
+ url: 'https://www.luckclub.cn/qimen/005/004/',
21620
+ },
21621
+ {
21622
+ id: 'yl-liuyi',
21623
+ book: 'yuanling',
21624
+ chapter: '卷一·六仪',
21625
+ url: 'https://www.luckclub.cn/qimen/005/004/',
21626
+ },
21627
+ {
21628
+ id: 'yl-ganying-jia',
21629
+ book: 'yuanling',
21630
+ chapter: '卷二·天盘加地盘吉凶',
21631
+ url: 'https://www.luckclub.cn/qimen/005/005/',
21632
+ },
21633
+ {
21634
+ id: 'yl-ganying-bing',
21635
+ book: 'yuanling',
21636
+ chapter: '卷二·天盘加地盘吉凶',
21637
+ url: 'https://www.luckclub.cn/qimen/005/005/',
21638
+ },
21639
+ {
21640
+ id: 'yl-ganying-yixin',
21641
+ book: 'yuanling',
21642
+ chapter: '卷二·天盘加地盘吉凶',
21643
+ url: 'https://www.luckclub.cn/qimen/005/005/',
21644
+ },
21645
+ {
21646
+ id: 'yl-bamen',
21647
+ book: 'yuanling',
21648
+ chapter: '卷二·八门值事',
21649
+ url: 'https://www.luckclub.cn/qimen/005/005/',
21650
+ },
21651
+ {
21652
+ id: 'mj-jiuxing',
21653
+ book: 'miji',
21654
+ chapter: '卷十五·六仪刑击格',
21655
+ url: 'https://www.luckclub.cn/qimen/003/015/',
21656
+ },
21657
+ {
21658
+ id: 'mj-menpo',
21659
+ book: 'miji',
21660
+ chapter: '卷十五·门迫歌',
21661
+ url: 'https://www.luckclub.cn/qimen/003/015/',
21662
+ },
21663
+ {
21664
+ id: 'mj-pozhi',
21665
+ book: 'miji',
21666
+ chapter: '卷十五·迫制和义格',
21667
+ url: 'https://www.luckclub.cn/qimen/003/015/',
21668
+ },
21669
+ {
21670
+ id: 'mj-rumu',
21671
+ book: 'miji',
21672
+ chapter: '卷十五·三奇入墓歌',
21673
+ url: 'https://www.luckclub.cn/qimen/003/015/',
21674
+ },
21675
+ {
21676
+ id: 'mj-shimu',
21677
+ book: 'miji',
21678
+ chapter: '卷十五·时墓格',
21679
+ url: 'https://www.luckclub.cn/qimen/003/015/',
21680
+ },
21681
+ {
21682
+ id: 'mj-ganying',
21683
+ book: 'miji',
21684
+ chapter: '总序',
21685
+ url: 'https://www.luckclub.cn/qimen/003/001/',
21686
+ },
21687
+ {
21688
+ id: 'mj-futou',
21689
+ book: 'miji',
21690
+ chapter: '总序',
21691
+ url: 'https://www.luckclub.cn/qimen/003/001/',
21692
+ },
21693
+ {
21694
+ id: 'mj-yinyang',
21695
+ book: 'miji',
21696
+ chapter: '总序',
21697
+ url: 'https://www.luckclub.cn/qimen/003/001/',
21698
+ },
20944
21699
  ];
20945
21700
 
20946
21701
  const BOOK_OF = {
@@ -21045,13 +21800,18 @@ const CARD_CITATIONS = {
21045
21800
  // ── 其他体系
21046
21801
  'meihua-tiyong': { refs: ['mh-tiyong', 'mh-tiyong-shengke', 'mh-dongjing'] },
21047
21802
  'daliuren-sike': { refs: ['dl-jigong', 'dl-zeike', 'rty-sike', 'rzn-rushi', 'rzn-richen', 'rg-tigang', 'b13-rty-sike', 'b13-rty-jiuke'] },
21048
- 'qimen-yinyang': { refs: ['qm-yinyang', 'qmf-yinyang', 'ydq-liuyi'] },
21049
- 'qimen-jixing': { refs: ['qm-jixing'] },
21050
- 'qimen-rumu': { refs: ['qm-rumu'] },
21803
+ 'qimen-yinyang': { refs: ['qm-yinyang', 'qmf-yinyang', 'ydq-liuyi', 'yl-yinyang', 'yl-jushu', 'yl-futou', 'mj-futou', 'mj-yinyang'] },
21804
+ 'qimen-jixing': { refs: ['qm-jixing', 'yl-liuyi', 'mj-jiuxing'] },
21805
+ 'qimen-rumu': { refs: ['qm-rumu', 'mj-rumu', 'mj-shimu'] },
21051
21806
  'qimen-wangshuai': { refs: ['qm-wangshuai'] },
21052
- 'qimen-wangshuai-yong': { refs: ['qm-men-gong'] },
21807
+ 'qimen-wangshuai-yong': { refs: ['qm-men-gong', 'yl-bamen', 'mj-menpo', 'mj-pozhi'] },
21053
21808
  'qimen-gan-shengke': { status: 'general', book: '奇门遁甲统宗', chapter: '卷十二·玄机赋(下)', reason: '底本有「奇仪与日干」生克条,未见单列「干与落宫」生克条;干宫取象为通行读法,不拿十神条文冒充' },
21054
- 'qimen-ganying': { status: 'general', book: '奇门十干应克 / 奇门遁甲元灵经', chapter: '通行 100 组表 / 卷二·天盘加地盘吉凶', reason: '100 组为通行表汇编,元灵经只存甲、乙、丙三组,非单条原文' },
21809
+ // B14:原为 general(「100 组表非单条原文」)。元灵经卷二「天盘加地盘吉凶」与秘笈总序录到原文后改 refs,
21810
+ // 但 refs 只证「先取成象、再与门星神合看」这一层口径与甲丙/乙辛等组的格名异文,不证通行 100 组表全文。
21811
+ 'qimen-ganying': {
21812
+ refs: ['yl-ganying-jia', 'yl-ganying-bing', 'yl-ganying-yixin', 'mj-ganying'],
21813
+ note: '元灵经卷二「天盘加地盘吉凶」只存天盘甲、乙、丙三组(各 9 条,三组都缺「加戊」,页面到「丙加地盘癸」为止,丁以下七组未收),秘笈总序另举六组格名;已录原文只证「先取成象、再与门星神合看」这一层口径与这几组的格名异文,不证通行 100 组表全文,其余七十馀组仍取通行表汇编、无单条原文。三处同格异文(青龙返首/反首、青龙逃走的三种断语)并列,不合并。',
21814
+ },
21055
21815
  'tarot-orientation': { status: 'general', book: '韦特塔罗', chapter: '逆位读法', reason: '韦特体系无中文古籍原文,逆位读法属近代流派' },
21056
21816
  'tarot-correspondence': { status: 'general', book: 'Liber 777 一脉的公开对应表', chapter: '小阿卡纳 2-10 的旬位与十源质', reason: '属近代西方神秘学对应表,无中文古籍原文;本插件按三处独立公开资料互核后取数字牌旬位,宫廷牌两派映射冲突故不配星座' },
21057
21817
  };
@@ -21612,7 +22372,7 @@ function baziInterpret(r) {
21612
22372
  `**用神**:喜${join(favor) || '—'}(${join(ys.favorRoles || []) || '—'}),忌${join(avoid) || '—'}(${join(ys.avoidRoles || []) || '—'})。${neutral.length ? `(${join(neutral.map((n) => n.element))} 近零,按中性偏忌看)` : ''}${frameToneOf(level, favorRolesByWeight)}`,
21613
22373
  `**格局**:${primary}(${pick(frame.primary, 'evidence', '')})${(Array.isArray(frame.geju) ? frame.geju : []).filter((g) => g.name && g.name !== primary).length
21614
22374
  ? `,另有${frame.geju.map((g) => g.name).filter((n) => n && n !== primary).join('、')}`
21615
- : ''}。`,
22375
+ : ''}${frame.patternScore && frame.patternScore.applicable ? `。成格评分 ${frame.patternScore.score}/100(${frame.patternScore.grade})` : ''}。`,
21616
22376
  ].join('\n');
21617
22377
 
21618
22378
  const portraitText = [
@@ -21695,7 +22455,12 @@ function baziInterpret(r) {
21695
22455
  const jiaoLine = r.dayun && r.dayun.jiao && r.dayun.jiao.at
21696
22456
  ? `起运交运时刻 ${r.dayun.jiao.at}(${r.dayun.jiao.frame};三日折一年,距${r.dayun.jiao.from.name} ${r.dayun.jiao.spanDays} 天)。每步大运在同月同日同时刻换,逐年日期见下方大运表「交运」列。\n`
21697
22457
  : '';
21698
- return `${jiaoLine}当前大运:${pick(cur, 'ganzhi', '—')}(${curSpan})。后续两步:${nextTwo}。\n近五年流年:${years || '—'}。\n读法:大运管"这十年的大环境",流年管"今年的具体天气";两者打架时,以能否动到用神为准。`;
22458
+ // 精确起运岁:与「几岁几个月」的口语写法并列。对上第三方排盘时差的就是日、时这两位。
22459
+ const sae = r.dayun && r.dayun.startAgeExact;
22460
+ const exactLine = sae && Number.isInteger(sae.years)
22461
+ ? `起运岁精确值:${sae.years} 年 ${sae.months} 个月 ${sae.days} 天 ${sae.hours} 小时(${sae.caliber})。与摘要「${r.dayun.startAge.years}岁${r.dayun.startAge.months}个月」同源:后者四舍五入到月,差一个月的边界常出在这里——第三方给到日/时的,按这一行核对。\n`
22462
+ : '';
22463
+ return `${jiaoLine}${exactLine}当前大运:${pick(cur, 'ganzhi', '—')}(${curSpan})。后续两步:${nextTwo}。\n近五年流年:${years || '—'}。\n读法:大运管"这十年的大环境",流年管"今年的具体天气";两者打架时,以能否动到用神为准。`;
21699
22464
  })(),
21700
22465
  },
21701
22466
  { key: 'years', label: '流年时间轴', text: yearsText },
@@ -21770,20 +22535,87 @@ function baziInterpret(r) {
21770
22535
  }] : []),
21771
22536
  {
21772
22537
  key: 'geju', label: '格局依据',
21773
- text: (Array.isArray(frame.geju) ? frame.geju : []).map((g) =>
21774
- `${g.name}(${g.kind}${g.status ? `·${g.status}` : ''}):${g.evidence}${Array.isArray(g.breaks) && g.breaks.length ? `;破:${g.breaks.join(';')}` : ''}`).join(';') || '未取到格局',
22538
+ text: ((Array.isArray(frame.geju) ? frame.geju : []).map((g) =>
22539
+ `${g.name}(${g.kind}${g.status ? `·${g.status}` : ''}):${g.evidence}${Array.isArray(g.breaks) && g.breaks.length ? `;破:${g.breaks.join(';')}` : ''}`).join(';') || '未取到格局')
22540
+ + (frame.patternScore ? `\n成格评分:${frame.patternScore.detail}` : ''),
21775
22541
  },
22542
+ ...(frame.bingyao ? [{
22543
+ key: 'bingyao', label: '病药',
22544
+ text: `【${frame.bingyao.level}】${frame.bingyao.detail}\n口径:${frame.bingyao.method}`,
22545
+ }] : []),
22546
+ ...(frame.xianshen ? [{
22547
+ key: 'xianshen', label: '闲神',
22548
+ text: `【${frame.xianshen.level}】${frame.xianshen.detail}\n口径:${frame.xianshen.method}`,
22549
+ }] : []),
22550
+ // 八大地支结构:与旺衰分并列的第二套静态视图。算出来却没人看得见的字段等于没做,
22551
+ // 所以这里单独给一节,写清「它是什么、跟上面旺衰分的区别、能不能据此断从格」。
22552
+ ...(r.strength && r.strength.branchStructure ? [{
22553
+ key: 'structure', label: '地支结构',
22554
+ text: (() => {
22555
+ const b = r.strength.branchStructure;
22556
+ const cols = b.branches.map((x) => `${x.label}支${x.branch}(${x.support}·${x.camp} 档 ${x.weight} 分${b.placeholderHour && x.key === 'hour' ? '·占位' : ''})`).join(';');
22557
+ const odd = b.odd && b.odd.length ? `异类:${b.odd.map((x) => `${x.label}支${x.branch}(${x.weight} 分)`).join('、')}。` : '四支二对二或同党,无异类。';
22558
+ const bal = b.balance || {};
22559
+ return `【${b.code}】${b.detail}\n`
22560
+ + `逐支:${cols}\n`
22561
+ + `${odd}${b.halfSupported && b.halfSupported.length ? `半得地:${b.halfSupported.join('、')}(按 0.2–0.5 折算,不计入身强阵营)。` : ''}\n`
22562
+ + `地支 身强 ${bal.branch ? bal.branch.A : '—'} : 身弱 ${bal.branch ? bal.branch.B : '—'},加天干后 ${bal.total ? `${bal.total.A}:${bal.total.B}` : '—'}(宫位权重年 11 / 月 36 / 日 27 / 时 16)。\n`
22563
+ + `${b.word}${b.congProb != null ? `——静态从格概率 ${b.congRange}。` : '。'}\n`
22564
+ + `口径:${b.method}\n`
22565
+ + `与「日主强弱」的关系:这是并列的第二套静态视图,不改上面的旺衰评分,也不参与取格与喜忌——两者是同一组四支的两种读法,不是先后两步。\n`
22566
+ + `边界:${b.note}`;
22567
+ })(),
22568
+ }] : []),
22569
+ // 暗合:另列不并。这一条最容易被人当成「合」读进旺衰,所以把边界写在节里。
22570
+ ...(r.relations && Array.isArray(r.relations.anhe) && r.relations.anhe.length ? [{
22571
+ key: 'anhe', label: '暗合',
22572
+ text: `${r.relations.anhe.join('\n')}\n`
22573
+ + `口径:${r.relations.anheNote || '暗合只作取象,不参与旺衰与格局。'}\n`
22574
+ + `边界:暗合与上面的「地支关系」(三合、三会、六合、六冲、刑、害)是两条线,本插件不把它们合并计分;`
22575
+ + `${r.relations.anhe.length === 1 ? '本盘只出现一对,按课里口径可以直接忽略。' : `本盘共 ${r.relations.anhe.length} 对,可作取象参考,但仍不折旺衰。`}`,
22576
+ }] : []),
22577
+ (() => {
22578
+ const ss = r.shensha;
22579
+ const cols = { year: '年', month: '月', day: '日', hour: '时' };
22580
+ if (!ss || !Array.isArray(ss.groups) || !ss.groups.length) {
22581
+ return {
22582
+ key: 'shensha', label: '神煞',
22583
+ text: '本盘未见常用神煞。按炳祥课里的口径,神煞是枝叶,只作锦上添花,不单独定吉凶;真正的吉凶仍看格局、旺衰与行运。',
22584
+ };
22585
+ }
22586
+ const names = Array.isArray(ss.names) ? ss.names : [];
22587
+ const rows = ss.groups.map((g) => `${g.cat}:${(g.items || []).map((it) => {
22588
+ const where = (it.pillars || []).map((k) => cols[k] || k).join('、');
22589
+ return `${it.name}${where ? `(${where})` : ''}`;
22590
+ }).join('、')}`).join(';');
22591
+ const core = ['驿马', '羊刃', '桃花', '华盖'].filter((n) => names.includes(n));
22592
+ const hasTianyi = names.includes('天乙贵人');
22593
+ return {
22594
+ key: 'shensha', label: '神煞',
22595
+ text: `命中神煞:${rows}。\n`
22596
+ + '按炳祥课里的口径,真正拿来取象的只有驿马、羊刃、桃花、华盖四颗'
22597
+ + `${core.length ? `,本盘见 ${core.join('、')}` : ',本盘这四颗都未见'}`
22598
+ + ';其余一律只作锦上添花。'
22599
+ + (hasTianyi
22600
+ ? '天乙贵人尤其是添头:课里给的口径是「无贵人概率 5/6⁴ < 50%,单独断贵人准确率 <30%」,不能拿来定吉凶。'
22601
+ : '天乙贵人即使出现也只是添头:课里统计其单独断贵人准确率 <30%,不能拿来定吉凶。')
22602
+ + '\n神煞永远是枝叶:它只提示「像不像」,不决定「成不成」。吉凶仍以格局、旺衰、喜忌与行运为主,神煞不参与本盘的旺衰与格局判定。',
22603
+ };
22604
+ })(),
21776
22605
  ...(Array.isArray(r.classics) && r.classics.length ? [{
21777
22606
  key: 'classics', label: '古籍原文',
21778
22607
  text: r.classics.map((e) => `《${CLASSIC_BOOKS[e.book].name}·${e.chapter}》「${e.quote}」——${e.plain}`).join('\n'),
21779
22608
  }] : []),
21780
22609
  {
21781
22610
  key: 'quant', label: '量化口径',
21782
- text: `本报告的量化只到「档位」:事业看十神权重(${topName} ${top[0]?.[1] ?? '—'})、财富看财星权重(${groups['财'] ?? 0})、感情看配偶星权重(${spouseKey} ${groups[spouseKey] ?? 0});大运/流年用「大吉 / 吉 / 平 / 忌」四档。不换算金额、不给健康诊断——那需要地域、家庭、行业与体检数据,命盘本身推不出来。`,
22611
+ text: `本报告的量化只到「档位」:事业看十神权重(${topName} ${top[0]?.[1] ?? '—'})、财富看财星权重(${groups['财'] ?? 0})、感情看配偶星权重(${spouseKey} ${groups[spouseKey] ?? 0});大运/流年用「大吉 / 吉 / 平 / 忌」四档。不换算金额、不给健康诊断——那需要地域、家庭、行业与体检数据,命盘本身推不出来。`
22612
+ + (r.strength?.weightPct
22613
+ ? `\n宫位权重(炳祥课里干 2/5/0/3、支 11/36/27/16 的百分比视图):${r.strength.weightPct.detail}。这组数只说明四柱各占多少,不改上面的旺衰评分。`
22614
+ : ''),
21783
22615
  },
21784
22616
  {
21785
22617
  key: 'limits', label: '未纳入',
21786
- text: '本盘没有计算:流月与流日、神煞的加权轻重、地支藏干的动态引化(合冲后再论)。取用按「调候优先、扶抑校验」;调候由月令与全局寒暖燥湿判定,并按《穷通宝鉴》120 格给出用神顺序(整理表口径,原文摘句照录,异文处已标 note);各派取法仍有分歧;真太阳时未含历史时区与夏令时。把这些补上会改变"强弱"的边际判断,所以这里只给方向,不下断语。',
22618
+ text: '本盘没有计算:流月与流日、神煞的分值折算(解读侧只按课里口径作降权提示,不折成分值)、地支藏干的动态引化(合冲后再论)。取用按「调候优先、扶抑校验」;调候由月令与全局寒暖燥湿判定,并按《穷通宝鉴》120 格给出用神顺序(整理表口径,原文摘句照录,异文处已标 note);各派取法仍有分歧;真太阳时未含历史时区与夏令时。把这些补上会改变"强弱"的边际判断,所以这里只给方向,不下断语。',
21787
22619
  },
21788
22620
  ];
21789
22621
 
@@ -26363,6 +27195,13 @@ function baziText(r) {
26363
27195
  const jiao = r.dayun.jiao && r.dayun.jiao.at
26364
27196
  ? `;交运 ${r.dayun.jiao.at}(${r.dayun.jiao.frame},距${r.dayun.jiao.from.name} ${r.dayun.jiao.spanDays} 天,三日折一年)`
26365
27197
  : '';
27198
+ // 起运岁精确值(截断到年月日时):上面 startAge 是四舍五入到月的口语写法,两者同源、并列给出。
27199
+ // 第三方对拍里截断到月 23/23 全中、四舍五入只有 15/23——差别就在这串日时上,所以必须看得见。
27200
+ const sae = r.dayun.startAgeExact;
27201
+ const startAgeExactLine = sae && Number.isInteger(sae.years)
27202
+ ? `起运岁(精确):${sae.years} 年 ${sae.months} 个月 ${sae.days} 天 ${sae.hours} 小时`
27203
+ + `(${sae.caliber};上面「${r.dayun.startAge.years}岁${r.dayun.startAge.months}个月」是四舍五入到月,两者同源)`
27204
+ : '';
26366
27205
  // 阈值 20 分钟:与 NAOJ 历要项对拍实测,本引擎节气最多偏早 13.5 分钟(Meeus 低精度上限约 14 分钟)
26367
27206
  const near = r.jieqi.nearBoundaryMinutes < 20
26368
27207
  ? `\n⚠ 出生时刻距「节」仅 ${r.jieqi.nearBoundaryMinutes} 分钟,而节气时刻本身有最多约 14 分钟的偏差,月柱与年柱建议人工复核。`
@@ -26395,6 +27234,22 @@ function baziText(r) {
26395
27234
  (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})` : ''),
26396
27235
  `十神权重:${Object.entries(r.frame.godStats.groups).map(([k, v]) => `${k}${v}`).join(' ')}`,
26397
27236
  ] : [];
27237
+ // 八大地支结构(炳祥课静态口径):算得出但没有输出通道的字段等于没做,这里补一行摘要。
27238
+ // 只报「哪几支成党、异类是哪支、静态从格概率」,与上面旺衰分是两套并列口径,不互相覆盖。
27239
+ // 三柱模式第四支是正午占位:照实标「占位」,不把它说成实有的时支。
27240
+ const bs = r.strength && r.strength.branchStructure;
27241
+ const bsCols = bs ? bs.branches.map((b) => `${b.label}${b.branch}${b.camp}${bs.placeholderHour && b.key === 'hour' ? '·占位' : ''}`).join('、') : '';
27242
+ // 注意 bs.word 本身已含三柱说明(bazi.js 的 hourUnknown 分支会改写它),这里不重复追加
27243
+ const bsTail = bs && !bs.placeholderHour && !bs.complete ? ' 时柱暂缺,只列三支' : '';
27244
+ const branchStructureLine = bs
27245
+ ? `地支结构:${bs.placeholderHour ? '三支+占位' : '四支'} ${bs.code}(${bsCols}) `
27246
+ + `${bs.word}${bs.congProb != null ? `(静态从格 ${bs.congRange})` : ''}${bsTail}`
27247
+ : '';
27248
+ // 暗合:另列不并入合冲刑害。只出现一对时按课里口径「可忽略」,所以照实写出来但不升级为断语。
27249
+ const anhe = (r.relations && Array.isArray(r.relations.anhe)) ? r.relations.anhe : [];
27250
+ const anheLine = anhe.length
27251
+ ? `暗合:${anhe.join(' ')}(另列,不并入上面的合冲刑害;${anhe.length === 1 ? '只一对,按口径可忽略' : `共 ${anhe.length} 对`})`
27252
+ : '';
26398
27253
  const hourMissing = r.hourUnknown === true || !P.hour;
26399
27254
  return [
26400
27255
  `【四柱】${P.year.ganzhi} ${P.month.ganzhi} ${P.day.ganzhi}${P.hour ? ` ${P.hour.ganzhi}` : '(时柱暂缺)'}`,
@@ -26404,14 +27259,17 @@ function baziText(r) {
26404
27259
  `五行:${wuxing}`,
26405
27260
  ...(palaces ? [palaces] : []),
26406
27261
  `日主强弱:${r.strength.level}(${r.strength.score}) ${r.strength.detail}`,
27262
+ ...(branchStructureLine ? [branchStructureLine] : []),
26407
27263
  ...gejuLine,
26408
27264
  `天干关系:${(r.relations && r.relations.stems.length) ? r.relations.stems.join(' ') : '—'}`,
26409
27265
  `地支关系:${(r.relations && r.relations.branches.length) ? r.relations.branches.join(' ') : '—'}`,
27266
+ ...(anheLine ? [anheLine] : []),
26410
27267
  shenshaLine,
26411
27268
  `真太阳时:${r.solarTime.trueSolarText}(${r.input.geo ? r.input.geo.note + ';' : ''}${r.solarTime.note}) 晚子时:${r.input.lateZi ? '23:00 后算次日' : '23:00 后仍算当日'}`,
26412
27269
  `节气:上一节 ${r.jieqi.previous.name} ${r.jieqi.previous.time} 下一节 ${r.jieqi.next.name} ${r.jieqi.next.time}`,
26413
27270
  ...(renyuanLine ? [renyuanLine] : []),
26414
27271
  `大运:${r.dayun.rule}(${r.dayun.direction}),${r.dayun.startAge.years}岁${r.dayun.startAge.months}个月起运${jiao}`,
27272
+ ...(startAgeExactLine ? [startAgeExactLine] : []),
26415
27273
  ` ${dayun}`,
26416
27274
  `流年:${r.liunian.slice(0, 10).map((l) => `${l.year}${l.ganzhi}(${l.stemTenGod})${l.luck ? `·${l.luck.tier}` : ''}`).join(' ')}`,
26417
27275
  (() => {
@@ -28038,8 +28896,10 @@ function chengguText(r) {
28038
28896
 
28039
28897
  /** 小六壬文本 */
28040
28898
  function xiaoliurenText(r) {
28899
+ // 所问从顶层读(引擎把 question 挂顶层,不进 meta——meta 要能被句柄完整复现)
28900
+ const q = r.question || (r.meta && r.meta.question) || null;
28041
28901
  return [
28042
- `【小六壬·${r.meta.mode}】${r.meta.question ? `所问:${r.meta.question}` : ''}`,
28902
+ `【小六壬·${r.meta.mode}】${q ? `所问:${q}` : ''}`,
28043
28903
  r.meta.originText,
28044
28904
  `落宫:${r.steps.join(' → ')}`,
28045
28905
  `断语:${r.result.palace}(${r.result.luck}) ${r.result.text}`,
@@ -28339,7 +29199,7 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, parseDate
28339
29199
  })();
28340
29200
 
28341
29201
  window.Engine = __mods.index;
28342
- window.__MP_VER = "0.62.4";
29202
+ window.__MP_VER = "0.63.0";
28343
29203
  </script>
28344
29204
  <script>
28345
29205
  // 两个 UI 共用的表单规则:宿主面板(src/app.js)与独立页(src/web-ui.js)。
@@ -28455,16 +29315,22 @@ window.__MP_VER = "0.62.4";
28455
29315
  --line:#c8c1b2;--dimline:#b9b2a4;--hair:#e9e2d3;
28456
29316
  /* 底色略沉作「桌面」,--card 跳到近白:原先 bg 与 card 只差 1/255,卡片感等于零。
28457
29317
  同一色相里塞 9 个几乎相等的浅色,谁都浮不起来。 */
28458
- --bg:#f6f1e6;--card:#fffdf8;--band:#fbf7ee;--hover:#f3ede1;--hover2:#ece5d6;
28459
- --tri:#f3e8cd;--soul:#fae4dc;--sel:#fceee7;--link:#f9ece0;
28460
- --red:#b3261e;--red-rgb:179,38,30;--lu:#1f6f3f;--lu-rgb:31,111,63;
28461
- --quan:#8a5a00;--quan-rgb:138,90,0;--ke:#1f4e79;--ke-rgb:31,78,121;--ji:#b3261e;
29318
+ /* 水墨风:宣纸白为底、淡墨分层;米黄系整体降饱和,纸感靠 --band 的微暖留一点 */
29319
+ --bg:#f4f2ec;--card:#fdfdfb;--band:#f7f5ef;--hover:#eeece5;--hover2:#e6e4dc;
29320
+ --tri:#eef0ea;--soul:#f7ece9;--sel:#f5efe9;--link:#f0efe9;
29321
+ --red:#a02c26;--red-rgb:160,44,38;--lu:#3d5a52;--lu-rgb:61,90,82;
29322
+ --quan:#6e5a2e;--quan-rgb:110,90,46;--ke:#3a5568;--ke-rgb:58,85,104;--ji:#a02c26;
28462
29323
  --br-good:#4c7a5c;--br-bad:#9c5b56;--on-accent:#fff;--wx-metal:#8a6b1f;
28463
- --tg-bi:#4a6f8a;--tg-shi:#4c7a5c;--tg-cai:#8a6b1f;--tg-guan:#9c5b56;--tg-yin:#6d5f83;
29324
+ --tg-bi:#4e5d6b;--tg-shi:#55685c;--tg-cai:#6e5a2e;--tg-guan:#8a4a42;--tg-yin:#5d5570;
28464
29325
  /* 之前 mp-cli / mp-qt / mp-cl / 起名卡里引用了 --ink3、--fx、--sans,却从未定义:
28465
29326
  var() 取不到值 → 整条声明作废,字色与字形全靠继承,楷体、弱化灰都成了摆设。 */
28466
29327
  --ink3:#8a8378;--fx:"Kaiti SC",KaiTi,"STKaiti","Songti SC",serif;--sans:system-ui,-apple-system,"Microsoft YaHei",sans-serif;--kai:"Kaiti SC",KaiTi,"STKaiti",serif;
28467
- --shadow:0 1px 2px rgba(35,28,18,.05),0 18px 40px -30px rgba(35,28,18,.42)}
29328
+ --shadow:0 1px 2px rgba(35,28,18,.05),0 18px 40px -30px rgba(35,28,18,.42),
29329
+ /* 动效与圆角令牌:全站同一节奏(借鉴 wallpaper-engine 的 --we-dur/--we-ease 约定),
29330
+ reduced-motion 时整体关停,见文件尾的全局 @media。 */
29331
+ --dur:150ms;--dur-fast:100ms;--ease:cubic-bezier(.32,.72,.24,1);
29332
+ --r-sm:6px;--r-md:10px;--r-lg:14px;
29333
+ --glow:0 0 0 1px rgba(var(--lu-rgb),.25),0 0 12px rgba(var(--lu-rgb),.12)}
28468
29334
  /* 变量与 .mp-it 共享 —— 「解析与详解」是 .pp 的兄弟节点、不在 .pp 内部,
28469
29335
  变量只挂在 .pp 上时它拿不到 --card/--muted/--red,头部底色会变透明、边框会退回黑色。 */
28470
29336
  .pp{border:1px solid var(--line);background:var(--bg);color:var(--ink);font-family:"Songti SC","SimSun","Microsoft YaHei",serif;font-size:12.5px;line-height:1.55;
@@ -28475,31 +29341,45 @@ window.__MP_VER = "0.62.4";
28475
29341
  .pp,.mp-it,.mp-al,.mp-cg,.mp-cl,.mp-cli,.mp-kline,.mp-mb,.mp-mb-paper,.mp-nm,.mp-quick,.mp-cdiff,.mp-shadow,.mp-trace-card,.mp-trace-layer,.mp-prof{--ink:#eae5d9;--ink2:#d5cfc2;--muted:#aca598;--label:#a49c8e;--faint:#928b7e;
28476
29342
  --line:#514a3e;--dimline:#5f5849;--hair:#2f2b23;
28477
29343
  /* 与浅色同理:底更沉、卡片更亮,让层级在深底上也能立起来 */
28478
- --bg:#12110c;--card:#211f18;--band:#282419;--hover:#2e2a1f;--hover2:#383322;
28479
- --tri:#332913;--soul:#3a231c;--sel:#43271f;--link:#33241c;
28480
- --red:#f08569;--red-rgb:240,133,105;--lu:#74c795;--lu-rgb:116,199,149;
28481
- --quan:#e2a95c;--quan-rgb:226,169,92;--ke:#86bcea;--ke-rgb:134,188,234;--ji:#f08569;
29344
+ /* 暗色水墨:墨底宣纸灰字,降暖提灰 */
29345
+ --bg:#131311;--card:#1f1f1c;--band:#26261f;--hover:#2c2c26;--hover2:#35352c;
29346
+ --tri:#2a2e28;--soul:#332521;--sel:#322d27;--link:#2c2b25;
29347
+ --red:#e08a7a;--red-rgb:224,138,122;--lu:#8fb8a8;--lu-rgb:143,184,168;
29348
+ --quan:#c9a96a;--quan-rgb:201,169,106;--ke:#8aadc0;--ke-rgb:138,173,192;--ji:#e08a7a;
28482
29349
  --br-good:#79c79a;--br-bad:#e09a8c;--on-accent:#14130f;--wx-metal:#dcb273;
28483
29350
  --tg-bi:#93b7cf;--tg-shi:#8fbb9d;--tg-cai:#d3ae62;--tg-guan:#d98d84;--tg-yin:#b4a4d0;
28484
- --ink3:#8b8478;--shadow:0 1px 2px rgba(0,0,0,.5),0 18px 40px -30px rgba(0,0,0,.9)}
29351
+ --ink3:#8b8478;
29352
+ --dur:150ms;--dur-fast:100ms;--ease:cubic-bezier(.32,.72,.24,1);
29353
+ --r-sm:6px;--r-md:10px;--r-lg:14px;
29354
+ --glow:0 0 0 1px rgba(var(--lu-rgb),.3),0 0 14px rgba(var(--lu-rgb),.16);
29355
+ --shadow:0 1px 2px rgba(0,0,0,.5),0 18px 40px -30px rgba(0,0,0,.9)}
28485
29356
  }
28486
29357
  .pp *{box-sizing:border-box}
28487
29358
  .pp-grid{display:grid;grid-template-columns:repeat(4,1fr);grid-template-rows:repeat(4,minmax(150px,auto));gap:0;border:1px solid var(--line)}
28488
- .pp-cell{border-right:1px solid var(--line);border-bottom:1px solid var(--line);padding:7px 8px;display:flex;flex-direction:column;position:relative;min-height:150px;background:var(--bg)}
29359
+ .pp-cell{border-right:1px solid var(--line);border-bottom:1px solid var(--line);padding:7px 8px;display:flex;flex-direction:column;position:relative;min-height:150px;background:var(--bg);
29360
+ transition:background var(--dur) var(--ease),box-shadow var(--dur) var(--ease)}
29361
+ .pp-cell:hover,.pp-cell:focus-visible{background:var(--card);box-shadow:var(--glow);z-index:1}
29362
+ /* 命宫:双环选中态(借鉴 wallpaper-engine 色板的「发丝内环 + accent 外环」,比裸描边读得清) */
29363
+ .pp-soul{box-shadow:inset 0 0 0 2px rgba(var(--red-rgb),.35);background:linear-gradient(180deg,rgba(var(--red-rgb),.05),transparent 60%)}
29364
+ .pp-soul:hover,.pp-soul:focus-visible{background:var(--card);box-shadow:inset 0 0 0 2px rgba(var(--red-rgb),.4),var(--glow)}
28489
29365
  /* 右边框按「视觉列」去,不能按 nth-child:格子用 grid-column 绝对定位,DOM 顺序不等于列序 */
28490
29366
  .pp-cell.pp-lastcol{border-right:none}
28491
29367
  .pp-cell.pp-lastrow{border-bottom:none}
28492
- .pp-center{grid-column:2/4;grid-row:2/4;border-right:1px solid var(--line);border-bottom:1px solid var(--line);padding:16px 18px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;text-align:center;background:var(--bg)}
29368
+ .pp-center{grid-column:2/4;grid-row:2/4;border-right:1px solid var(--line);border-bottom:1px solid var(--line);padding:16px 18px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;text-align:center;position:relative;
29369
+ background:var(--bg)}
29370
+ .pp-center::before{content:"";position:absolute;inset:12px 16px;border:1px solid var(--hair);border-radius:var(--r-md);pointer-events:none}
29371
+ .pp-center::after{content:"";position:absolute;inset:13px 17px;border:1px solid var(--line);border-radius:var(--r-md);opacity:.4;pointer-events:none}
28493
29372
  .pp-center .ct-t1{font-size:17px;font-weight:700;letter-spacing:.05em;margin-bottom:6px}
28494
29373
  .pp-center .ct-t2{font-size:12.5px;color:var(--ink)}
28495
29374
  .pp-center .ct-t3{font-size:14px;letter-spacing:.35em;margin:6px 0;font-weight:600}
28496
29375
  .pp-center .ct-t4{font-size:12.5px;color:var(--muted)}
28497
29376
  .pp-stars{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:0 6px;font-size:13px;line-height:1.6;min-height:36px}
28498
29377
  .pp-st{white-space:nowrap}
28499
- .pp-st.main{font-weight:700}
29378
+ .pp-st.main{font-weight:700;font-size:13px;letter-spacing:.04em;text-shadow:0 0 .5px currentColor}
28500
29379
  .pp-st.good{color:var(--lu)}
28501
29380
  .pp-st.bad{color:var(--ji)}
28502
- .pp-st.aux{color:var(--muted)}
29381
+ .pp-st.bad::before{content:"·";font-weight:900;margin-right:1px}
29382
+ .pp-st.aux{color:var(--muted);font-size:11.5px}
28503
29383
  .pp-m{display:inline-block;font-style:normal;font-size:12px;line-height:1;padding:0 2px;border-radius:2px;color:var(--on-accent);margin-left:1px;vertical-align:1px}
28504
29384
  .pp-m.lu{background:var(--lu)}.pp-m.quan{background:var(--quan)}.pp-m.ke{background:var(--ke)}.pp-m.ji{background:var(--ji)}
28505
29385
  /* 大限/流年四化:只留色框不填充,与生年四化的实心块区分 */
@@ -28510,7 +29390,9 @@ window.__MP_VER = "0.62.4";
28510
29390
  .pp-m.self.lu{color:var(--lu)}.pp-m.self.quan{color:var(--quan)}.pp-m.self.ke{color:var(--ke)}.pp-m.self.ji{color:var(--ji)}
28511
29391
  /* 庙旺得利平不陷:星名后的等级小字。刻意压低视觉权重(灰为主、陷才微红),
28512
29392
  避免与四化的实心彩色方块抢层次 */
28513
- .pp-b{font-style:normal;font-size:11.5px;margin-left:4px;color:var(--muted);letter-spacing:0;opacity:.9}
29393
+ .pp-b{font-style:normal;font-size:10px;margin-left:3px;padding:0 3px;border-radius:999px;color:var(--muted);letter-spacing:0;opacity:.92;background:var(--band)}
29394
+ .pp-b.good{color:var(--br-good)}
29395
+ .pp-b.bad{color:var(--br-bad)}
28514
29396
  .pp-b.good{color:var(--br-good)}
28515
29397
  .pp-b.mid{color:var(--muted)}
28516
29398
  .pp-b.bad{color:var(--br-bad)}
@@ -28573,6 +29455,12 @@ window.__MP_VER = "0.62.4";
28573
29455
  .pp-str{margin-top:10px;font-size:13px;line-height:1.8}
28574
29456
  .pp-str b{color:var(--red);font-size:15px;margin:0 4px}
28575
29457
  .pp-drel{font-style:normal;color:var(--red);font-size:12px}
29458
+ /* 八大地支结构:与日主强弱并列的第二套口径,用中性色,不与主结论抢色 */
29459
+ .pp-struct{margin-top:6px;font-size:12.5px;line-height:1.75;color:var(--ink2)}
29460
+ .pp-struct .pp-st-code{font-weight:700;letter-spacing:.06em;color:var(--ink);margin-right:6px}
29461
+ .pp-struct .pp-st-odd{color:var(--quan)}
29462
+ .pp-anhe{margin-top:6px;font-size:12.5px;line-height:1.75;color:var(--ink2)}
29463
+ .pp-anhe b{color:var(--muted);font-weight:400;margin-right:8px}
28576
29464
  .pp-dim{color:var(--muted)}
28577
29465
  /* 岁运提示下的原文:比正文再轻一档,只做出处,不抢结论 */
28578
29466
  .pp-sy-src{margin-top:6px;font-size:11.5px;line-height:1.55}
@@ -28589,6 +29477,39 @@ window.__MP_VER = "0.62.4";
28589
29477
  #ppm-ben:checked~.pp-modes label[for="ppm-ben"],#ppm-da:checked~.pp-modes label[for="ppm-da"],#ppm-ln:checked~.pp-modes label[for="ppm-ln"]{background:var(--red);color:var(--on-accent)}
28590
29478
  .pp-tri{background:var(--tri)}
28591
29479
  .pp-soul{background:var(--soul)}
29480
+ /* 三视图切换(学文墨天机:飞星/三合/四化)——真分派,不是同一张盘的显隐开关:
29481
+ 四化派(默认):生年四化实心 + 大限/流年空心 + 自化 + 飞星链 chips;
29482
+ 飞星派:每宫格内显示「宫干四化飞出」逐条(禄→某宫…),图例藏大限/流年四化;
29483
+ 三合派:只看星曜庙旺与宫位结构,所有四化徽章(含生年)、自化、飞星链全部藏起。 */
29484
+ .pp-schools{border-top:none}
29485
+ .pp-schools label{min-height:38px;padding:2px 16px;font-size:12px}
29486
+ #ppm-sc-sihe:checked~.pp-schools label[for="ppm-sc-sihe"],
29487
+ #ppm-sc-feixing:checked~.pp-schools label[for="ppm-sc-feixing"],
29488
+ #ppm-sc-sanhe:checked~.pp-schools label[for="ppm-sc-sanhe"]{background:var(--lu);color:var(--on-accent)}
29489
+ /* 飞星派专属:宫内宫干四化行,只在飞星模式展开 */
29490
+ .pp-fx{display:none;margin-top:3px;font-size:10.5px;line-height:1.55;color:var(--muted);border-top:1px dashed var(--hair);padding-top:2px}
29491
+ .pp-fx i{font-style:normal;white-space:nowrap;margin-right:4px}
29492
+ .pp-fx i em{font-style:normal;color:var(--faint)}
29493
+ #ppm-sc-feixing:checked~.pp-body .pp-fx{display:block}
29494
+ #ppm-sc-feixing:checked~.pp-body .pp-m.view,
29495
+ #ppm-sc-feixing:checked~.pp-body .pp-viewhua{display:none}
29496
+ /* 三合派:四化信息整体退场(生年/自化/大限流年/图例/飞星链 chips) */
29497
+ #ppm-sc-sanhe:checked~.pp-body .pp-m,
29498
+ #ppm-sc-sanhe:checked~.pp-body .pp-zh,
29499
+ #ppm-sc-sanhe:checked~.pp-body .pp-viewhua,
29500
+ #ppm-sc-sanhe:checked~.pp-flywrap{display:none}
29501
+ #ppm-sc-sihe:checked~.pp-flywrap{display:block}
29502
+ #ppm-sc-feixing:checked~.pp-flywrap{display:block}
29503
+ /* 自化图示(文墨式图例行):箭头颜色跟四化色走 */
29504
+ .pp-zhlegend{display:flex;gap:10px;align-items:center;justify-content:center;font-size:12px;color:var(--muted);margin-top:4px}
29505
+ .pp-zhlegend i{font-style:normal;font-weight:600}
29506
+ .pp-zhlegend .z-lu{color:var(--lu)}.pp-zhlegend .z-quan{color:var(--quan)}
29507
+ .pp-zhlegend .z-ke{color:var(--ke)}.pp-zhlegend .z-ji{color:var(--ji)}
29508
+ /* 纸纹:feTurbulence 纤维噪点叠加在盘面底色上,浅色偏灰纤维、暗色同用(低透明度不抢字) */
29509
+ .pp-paper{position:relative}
29510
+ .pp::before{content:"";position:absolute;inset:0;pointer-events:none;z-index:0;opacity:.5;
29511
+ background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='2' seed='7'/%3E%3CfeColorMatrix values='0 0 0 0 0.32 0 0 0 0 0.29 0 0 0 0 0.24 0 0 0 0.10 0'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23p)'/%3E%3C/svg%3E")}
29512
+ .pp>*{position:relative;z-index:1}
28592
29513
  .pp-zh{margin-left:4px;white-space:nowrap}
28593
29514
  .pp-borrow{font-size:9.5px;color:var(--muted);margin-top:1px;letter-spacing:.02em}
28594
29515
 
@@ -28599,9 +29520,13 @@ window.__MP_VER = "0.62.4";
28599
29520
  .pp-cell:hover{background:var(--hover)}
28600
29521
  /* 键盘可达:宫格是 div+tabindex,焦点必须看得见(原先把 outline 关了却没补替代) */
28601
29522
  .pp-cell:focus-visible{box-shadow:inset 0 0 0 2px var(--red)}
28602
- .pp-cell.pp-sel{background:var(--sel);box-shadow:inset 0 0 0 2px rgba(var(--red-rgb),.5)}
29523
+ .pp-cell.pp-sel{background:var(--sel);box-shadow:inset 0 0 0 2px rgba(var(--red-rgb),.55)}
29524
+ /* 三方四正虚线:ink 色细虚线,端点宫由 pp-sel/pp-link 底色呼应 */
29525
+ .pp-sf{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:2}
29526
+ .pp-sf line{stroke:var(--ink);stroke-width:1.2px;stroke-dasharray:3 2.4;opacity:.38;vector-effect:non-scaling-stroke}
29527
+ .pp-sf circle{fill:var(--ink);opacity:.45}
28603
29528
  /* 选中宫的财帛/官禄/迁移三宫:能一眼看出命宫牵着哪几宫 */
28604
- .pp-cell.pp-link{background:var(--link);box-shadow:inset 0 0 0 1.5px rgba(var(--red-rgb),.22)}
29529
+ .pp-cell.pp-link{background:var(--link);box-shadow:inset 0 0 0 1.5px rgba(var(--red-rgb),.3)}
28605
29530
  .pp-detail{display:none;border-top:1px solid var(--line);background:var(--card);padding:11px 14px 13px;font-family:system-ui,sans-serif}
28606
29531
  .pp-detail.open{display:block}
28607
29532
  .pp-detail .dt-dh{display:flex;align-items:center;flex-wrap:wrap;gap:4px 10px;padding-bottom:6px;margin-bottom:7px;border-bottom:1px dashed var(--line)}
@@ -29794,6 +30719,16 @@ window.__MP_VER = "0.62.4";
29794
30719
  .mp-prof-bar button.ghost{background:transparent;color:var(--muted);border-color:var(--line)}
29795
30720
  .mp-prof-bar .mp-prof-tip{font-size:11.5px;color:var(--muted)}
29796
30721
  @media (max-width:360px){.mp-prof-list{grid-template-columns:repeat(auto-fill,minmax(140px,1fr))}}
30722
+
30723
+ /* 减少动态效果偏好:本面板所有过渡一律退化为即时(不覆盖用户系统设置) */
30724
+ @media (prefers-reduced-motion: reduce){
30725
+ .pp *,.mp-it *,.mp-al *,.mp-btn,.mp-chip{transition:none!important;animation:none!important}
30726
+ }
30727
+ /* 大表行 hover:只提底色不加位移,行高稳定不被打断 */
30728
+ .pp table tbody tr{transition:background var(--dur-fast) var(--ease)}
30729
+ .pp table tbody tr:hover{background:var(--hover)}
30730
+ /* 结果卡阴影分层:默认最轻,hover 时浮起一档(只对可交互的卡,避免整页抖动) */
30731
+ .pp{transition:box-shadow var(--dur) var(--ease)}
29797
30732
  `;
29798
30733
  const pp = (window.__PP = window.__PP || {});
29799
30734
  pp.CSS = CSS;
@@ -29895,7 +30830,7 @@ window.__MP_VER = "0.62.4";
29895
30830
  const fx = (n) => ((n % 12) + 12) % 12;
29896
30831
  const isTrine = (b, m) => b !== m && [fx(m + 4), fx(m + 8), fx(m + 6)].includes(b);
29897
30832
 
29898
- function cellHTML(p, soulIdx, view, allPalaces, viewHua) {
30833
+ function cellHTML(p, soulIdx, view, allPalaces, viewHua, flyByBranch) {
29899
30834
  const pos = POS[p.branch] || [1, 1];
29900
30835
  const b = BR.indexOf(p.branch);
29901
30836
  const cls = ['pp-cell'];
@@ -29924,6 +30859,7 @@ window.__MP_VER = "0.62.4";
29924
30859
  <div class="pp-stars">${starsHTML(p.stars, viewHua)}${zh}</div>
29925
30860
  ${borrow}
29926
30861
  <div class="pp-ages">${agesHTML(p.xiaoxian.age)}</div>
30862
+ ${fxLineHTML(p, flyByBranch)}
29927
30863
  <div class="pp-foot">
29928
30864
  <div class="ft-l">
29929
30865
  <span class="ft-dx">${ss.boshi} ${p.daxian.start}-${p.daxian.end}</span>
@@ -29939,6 +30875,15 @@ window.__MP_VER = "0.62.4";
29939
30875
  </div>`;
29940
30876
  }
29941
30877
 
30878
+ /* 飞星派专属:本宫宫干四化飞到哪一宫,逐条摊在宫格内(只此模式展开,CSS 控显隐)。
30879
+ 宫数据不带 mutagenHits,从 r.daxianList 借:同一宫干,飞化等价。 */
30880
+ function fxLineHTML(p, flyByBranch) {
30881
+ const hits = (flyByBranch && flyByBranch[p.branch]) || [];
30882
+ if (!hits.length) return '';
30883
+ const items = hits.map((m) => `<i class="${MUT[m.type] || ''}" title="${esc(m.star)}化${esc(m.type)}">${esc(m.type)}<em>→${esc(m.palace || '未上盘')}</em></i>`).join('');
30884
+ return `<div class="pp-fx">${esc(p.ganzhi)}宫干 ${items}</div>`;
30885
+ }
30886
+
29942
30887
  function sizhuOf(r) {
29943
30888
  try {
29944
30889
  const E = window.Engine || (window.__PP && window.__PP.Engine);
@@ -29969,7 +30914,9 @@ window.__MP_VER = "0.62.4";
29969
30914
  const dxObj = (r.daxianList || []).find((d) => d.branch === dxPalace.branch) || null;
29970
30915
  const huaByView = { da: huaMap(dxObj && dxObj.mutagenHits), ln: huaMap(ln && ln.mutagenHits) };
29971
30916
 
29972
- const grid = (m, kind) => r.palaces.map((p) => cellHTML(p, m, kind, r.palaces, huaByView[kind] || null)).join('');
30917
+ const flyByBranch = {};
30918
+ for (const d of r.daxianList || []) flyByBranch[d.branch] = d.mutagenHits || [];
30919
+ const grid = (m, kind) => r.palaces.map((p) => cellHTML(p, m, kind, r.palaces, huaByView[kind] || null, flyByBranch)).join('');
29973
30920
  const center = (m, kind) => {
29974
30921
  const mode = kind === 'da'
29975
30922
  ? `<div class="ct-t1">大限 ${dxPalace.ganzhi} ${dxPalace.daxian.start}-${dxPalace.daxian.end}岁</div>`
@@ -30067,14 +31014,22 @@ window.__MP_VER = "0.62.4";
30067
31014
  <input type="radio" name="ppm" id="ppm-ben" class="pp-r" checked>
30068
31015
  <input type="radio" name="ppm" id="ppm-da" class="pp-r">
30069
31016
  <input type="radio" name="ppm" id="ppm-ln" class="pp-r">
31017
+ <input type="radio" name="ppm-sc" id="ppm-sc-sihe" class="pp-r" checked>
31018
+ <input type="radio" name="ppm-sc" id="ppm-sc-feixing" class="pp-r">
31019
+ <input type="radio" name="ppm-sc" id="ppm-sc-sanhe" class="pp-r">
30070
31020
  <div class="pp-modes">
30071
31021
  <label for="ppm-ben">本命</label><label for="ppm-da">大限</label><label for="ppm-ln">流年</label>
30072
31022
  <span class="pp-soon">点任一宫位看详情</span>
30073
31023
  </div>
31024
+ <div class="pp-modes pp-schools">
31025
+ <label for="ppm-sc-sihe">四化</label><label for="ppm-sc-feixing">飞星</label><label for="ppm-sc-sanhe">三合</label>
31026
+ </div>
30074
31027
  ${body('ben', soulIdx, 'ben')}
30075
31028
  ${body('da', dxIdx, 'da')}
30076
31029
  ${body('ln', lnIdx, 'ln')}
30077
- </div>${flowHTML}${chips}${flyHTML}${flyCompositeHTML}${pats}${dxs}${lnHit}${zyHTML}`;
31030
+ <div class="pp-zhlegend">自化图示 <i class="z-lu">→禄</i><i class="z-quan">→权</i><i class="z-ke">→科</i><i class="z-ji">→忌</i></div>
31031
+ <div class="pp-flywrap">${flyHTML}${flyCompositeHTML}</div>
31032
+ </div>${flowHTML}${chips}${pats}${dxs}${lnHit}${zyHTML}`;
30078
31033
  };
30079
31034
 
30080
31035
  // ── 宫位详情 ────────────────────────────────────────────────
@@ -30149,6 +31104,69 @@ window.__MP_VER = "0.62.4";
30149
31104
  body.querySelectorAll('.pp-cell[aria-expanded="true"]').forEach((c) => c.setAttribute('aria-expanded', 'false'));
30150
31105
  }
30151
31106
 
31107
+ /* 三方四正连线(文墨天机式):点击宫位后,把本宫—对宫—财帛—官禄四点连成虚线。
31108
+ 实现是纯 SVG overlay:绝对定位在 .pp-body 里、pointer-events:none 不挡点击,
31109
+ 线段端点由宫位格在 grid 上的行列号换算成百分比坐标,窗口缩放自动跟随。 */
31110
+ const SF_POS = { /* 与十二宫 POS 同构:地支 → [row, col](1-based,含中宫 2/3) */ };
31111
+ function drawSanfang(body, pi) {
31112
+ clearSanfang(body);
31113
+ const grid = body.querySelector('.pp-grid');
31114
+ if (!grid) return;
31115
+ const targets = [pi, (pi + 6) % 12, (pi + 4) % 12, (pi + 8) % 12];
31116
+ /* 文墨式连线:线只走中宫,不穿周边宫位。
31117
+ 每个宫往中宫一侧的「边中点」取锚:上排宫取下边中点、下排宫取上边中点,
31118
+ 左列宫取右边中点、右列宫取左边中点。中宫占 col 2~3 / row 2~3(即 25%~75%),
31119
+ 锚点恰好落在中宫边界上,折线自然被中宫「接住」。 */
31120
+ const anchorOf = (b) => {
31121
+ const cell = body.querySelector('.pp-cell[data-pi="' + b + '"]');
31122
+ if (!cell) return null;
31123
+ const r = Number(cell.style.gridRow), c = Number(cell.style.gridColumn);
31124
+ /* 锚点 = 该宫「朝中宫的那条竖边」上的点:
31125
+ x 永远落在中宫边界(25 或 75),y 取该宫行的中心。
31126
+ 这样线段从中宫边界出发,全部活动范围被限制在中宫矩形内,
31127
+ 不会横穿左右两侧的宫位(文墨的线也只在中间那块动)。 */
31128
+ // 左右两列宫:x 取中宫左右边界,y 取该宫行的中心(row 2/3 → y 37.5/62.5,都在中宫内)
31129
+ // 上排/下排的角宫(row 1/4)按所属左右列同样处理——y 可能越界,
31130
+ // 所以统一把 y 压回中宫范围(25~75 的 1/4、3/4 分位)。
31131
+ const yy = Math.min(75, Math.max(25, (r - 0.5) * 25));
31132
+ if (c === 1) return [25, yy];
31133
+ return [75, yy];
31134
+ };
31135
+ const pts = targets.map(anchorOf);
31136
+ if (pts.some((x) => !x)) return;
31137
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
31138
+ svg.setAttribute("class", "pp-sf");
31139
+ svg.setAttribute("viewBox", "0 0 100 100");
31140
+ svg.setAttribute("preserveAspectRatio", "none");
31141
+ const mk = (x1, y1, x2, y2) => {
31142
+ const l = document.createElementNS("http://www.w3.org/2000/svg", "line");
31143
+ l.setAttribute("x1", x1); l.setAttribute("y1", y1);
31144
+ l.setAttribute("x2", x2); l.setAttribute("y2", y2);
31145
+ svg.appendChild(l);
31146
+ };
31147
+ /* 文墨的三角/菱形画法:四个锚点首尾相连成闭环——
31148
+ 本宫 → 对宫(对角线,过中心)→ 三合1 → 三合2 → 回本宫。
31149
+ 两条对角交叉线 + 两条边线,落在中宫的形状就是一个斜菱形,
31150
+ 视觉上与文墨天机的三角形连线一致。 */
31151
+ mk(pts[0][0], pts[0][1], pts[1][0], pts[1][1]);
31152
+ mk(pts[1][0], pts[1][1], pts[2][0], pts[2][1]);
31153
+ mk(pts[2][0], pts[2][1], pts[3][0], pts[3][1]);
31154
+ mk(pts[3][0], pts[3][1], pts[0][0], pts[0][1]);
31155
+ // 端点小圆点:本宫用实心红点,其余三方用墨点
31156
+ for (let i = 0; i < 4; i++) {
31157
+ const c = document.createElementNS("http://www.w3.org/2000/svg", "circle");
31158
+ c.setAttribute("cx", pts[i][0]); c.setAttribute("cy", pts[i][1]); c.setAttribute("r", i === 0 ? 0.9 : 0.7);
31159
+ if (i === 0) c.setAttribute("style", "fill:var(--red);opacity:.85");
31160
+ svg.appendChild(c);
31161
+ }
31162
+ grid.style.position = "relative";
31163
+ grid.appendChild(svg);
31164
+ }
31165
+ function clearSanfang(body) {
31166
+ const old = body.querySelector("svg.pp-sf");
31167
+ if (old) old.remove();
31168
+ }
31169
+
30152
31170
  // 全局委托:面板与独立页都不需要额外初始化,panel-kit 一加载就生效
30153
31171
  function bindZiwei() {
30154
31172
  if (bindZiwei.done || typeof document === 'undefined' || !document.addEventListener) return;
@@ -30161,6 +31179,7 @@ window.__MP_VER = "0.62.4";
30161
31179
  // 再点同一宫 → 收起
30162
31180
  if (cell.classList.contains('pp-sel')) {
30163
31181
  clearMarks(body);
31182
+ clearSanfang(body);
30164
31183
  detail.classList.remove('open');
30165
31184
  detail.innerHTML = '';
30166
31185
  return;
@@ -30168,6 +31187,7 @@ window.__MP_VER = "0.62.4";
30168
31187
  clearMarks(body);
30169
31188
  const pi = Number(cell.dataset.pi);
30170
31189
  const soulIdx = Number(body.dataset.soul);
31190
+ drawSanfang(body, pi);
30171
31191
  const sel = body.querySelectorAll('.pp-cell');
30172
31192
  sel.forEach((c) => {
30173
31193
  const ci = Number(c.dataset.pi);
@@ -30184,6 +31204,7 @@ window.__MP_VER = "0.62.4";
30184
31204
  const body = t.closest('.pp-body');
30185
31205
  if (body) {
30186
31206
  clearMarks(body);
31207
+ clearSanfang(body);
30187
31208
  const d = body.querySelector('.pp-detail');
30188
31209
  if (d) { d.classList.remove('open'); d.innerHTML = ''; }
30189
31210
  return;
@@ -31195,6 +32216,25 @@ window.__MP_VER = "0.62.4";
31195
32216
  const syRow = sy.length ? `<div class="pp-tl pp-dim">岁运提示(只标干支变动,不判吉凶)</div><div class="pp-chips">${sy.map((l) =>
31196
32217
  `<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}` : '';
31197
32218
  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>` : '';
32219
+ // 八大地支结构:只读标注,与上面旺衰分并列。算出来却没人看得见等于没做,所以面板必须给这一行。
32220
+ const bs = r.strength && r.strength.branchStructure;
32221
+ const structHTML = bs ? (() => {
32222
+ const cols = bs.branches.map((b) => `${esc(b.label)}${esc(b.branch)}<i class="pp-drel">·${esc(b.camp)}</i>${bs.placeholderHour && b.key === 'hour' ? '<span class="pp-dim">占位</span>' : ''}`).join(' ');
32223
+ const odd = bs.odd && bs.odd.length ? `<span class="pp-st-odd">异类 ${bs.odd.map((x) => `${esc(x.label)}${esc(x.branch)}(${x.weight}分)`).join('、')}</span>` : '<span class="pp-st-odd">无异类</span>';
32224
+ const prob = bs.congProb != null ? ` 从格 ${esc(bs.congRange)}` : ' 不套从格概率';
32225
+ const bal = bs.balance || {};
32226
+ const balText = bal.branch ? ` 支 ${bal.branch.A}:${bal.branch.B} 合干 ${bal.total ? `${bal.total.A}:${bal.total.B}` : '—'}` : '';
32227
+ return `<div class="pp-struct" title="${esc(`${bs.method};${bs.note}`)}">`
32228
+ + `<span class="pp-st-code">${esc(bs.code)}</span>${cols} ${odd}${prob}${balText}`
32229
+ + `<span class="pp-dim"> 静态结构,只作标注,不改上面的旺衰评分</span>`
32230
+ + `</div>`;
32231
+ })() : '';
32232
+ // 暗合:另列不并。一对时照课里口径标「可忽略」,不让它看起来像有效合局。
32233
+ const anheList = (r.relations && Array.isArray(r.relations.anhe)) ? r.relations.anhe : [];
32234
+ const anheHTML = anheList.length
32235
+ ? `<div class="pp-anhe" title="${esc(r.relations.anheNote || '')}"><b>暗合</b>${anheList.map((x) => esc(x)).join(' ')}`
32236
+ + `<span class="pp-dim"> 另列,不并入上面的合冲刑害;${anheList.length === 1 ? '只一对,按口径可忽略' : `共 ${anheList.length} 对`}</span></div>`
32237
+ : '';
31198
32238
  const ssGroups = (r.shensha && r.shensha.groups) || [];
31199
32239
  const ssx = ssGroups.length ? `
31200
32240
  <div class="pp-ssx">
@@ -31208,6 +32248,8 @@ window.__MP_VER = "0.62.4";
31208
32248
  <div class="pp-rel"><div><b>天干</b>${(r.relations && r.relations.stems.length) ? r.relations.stems.join(' · ') : '—'}</div>
31209
32249
  <div><b>地支</b>${(r.relations && r.relations.branches.length) ? r.relations.branches.join(' · ') : '—'}</div></div>
31210
32250
  ${str}
32251
+ ${structHTML}
32252
+ ${anheHTML}
31211
32253
  ${sect('格局', gj)}
31212
32254
  ${sect('用神', ys)}
31213
32255
  ${cliCard}
@@ -31230,7 +32272,8 @@ window.__MP_VER = "0.62.4";
31230
32272
  ${(!r.palaces.minggong || !r.palaces.shengong) ? '<span class="pp-chip" title="命宫与身宫由生月与生时支合数定宫;时柱暂缺,这两宫不排">命宫·身宫 时柱暂缺不排</span>' : ''}
31231
32273
  </div>`) : ''}
31232
32274
  ${ssx}
31233
- <div style="margin-top:8px;font-size:12px;color:var(--muted)">上一节 ${r.jieqi.previous.name} ${r.jieqi.previous.time} 下一节 ${r.jieqi.next.name} ${r.jieqi.next.time} ${r.dayun.rule},${r.dayun.startAge.years}岁起运${(r.dayun.jiao && r.dayun.jiao.at) ? `(交运 ${r.dayun.jiao.at},${r.dayun.jiao.frame})` : ''}</div>
32275
+ <div style="margin-top:8px;font-size:12px;color:var(--muted)">上一节 ${r.jieqi.previous.name} ${r.jieqi.previous.time} 下一节 ${r.jieqi.next.name} ${r.jieqi.next.time} ${r.dayun.rule},${r.dayun.startAge.years}岁起运${(r.dayun.jiao && r.dayun.jiao.at) ? `(交运 ${r.dayun.jiao.at},${r.dayun.jiao.frame})` : ''}${(r.dayun.startAgeExact && Number.isInteger(r.dayun.startAgeExact.years))
32276
+ ? ` 精确 ${r.dayun.startAgeExact.years}年${r.dayun.startAgeExact.months}月${r.dayun.startAgeExact.days}天${r.dayun.startAgeExact.hours}时(截断到日时;上面是四舍五入到月)` : ''}</div>
31234
32277
  </div>`;
31235
32278
  };
31236
32279