dsh-metaphysics 0.21.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/engine/name.js CHANGED
@@ -116,6 +116,115 @@ export const CHAR_LIB = [
116
116
  ['月', '木', '明月', 'f'], ['言', '木', '言语,信', 'n'],
117
117
  ['和', '水', '和顺', 'n'], ['安', '土', '安宁', 'n'],
118
118
  ['宁', '火', '安宁', 'f'],
119
+ // ── 扩充:木 ──
120
+ ['林', '木', '林木成片,茂盛有荫', 'n'], ['森', '木', '众木成林,森然', 'n'],
121
+ ['枝', '木', '枝条,生发', 'n'], ['果', '木', '果实,有成', 'n'],
122
+ ['禾', '木', '禾苗,五谷初生', 'n'], ['秀', '木', '禾吐穗,秀美出众', 'n'],
123
+ ['穗', '木', '谷穗,丰实', 'n'], ['稼', '木', '庄稼,耕作有成', 'm'],
124
+ ['颖', '木', '禾颖,聪颖', 'n'], ['苗', '木', '苗裔,初生有生机', 'n'],
125
+ ['菁', '木', '菁华,草木茂盛', 'f'], ['菡', '木', '荷花含苞', 'f'],
126
+ ['蕴', '木', '蕴积,含而不露', 'f'], ['蕾', '木', '花蕾,待放', 'f'],
127
+ ['薇', '木', '蔷薇,细而韧', 'f'], ['薏', '木', '薏苡,清润', 'f'],
128
+ ['菀', '木', '草木茂盛', 'f'], ['荷', '木', '荷花,出淤泥', 'f'],
129
+ ['莲', '木', '莲花,清直', 'f'], ['菱', '木', '菱角,水生', 'f'],
130
+ ['苇', '木', '芦苇,柔韧', 'n'], ['荻', '木', '荻草,秋声', 'n'],
131
+ ['筠', '木', '竹皮,竹之青', 'n'], ['笛', '木', '竹笛,清音', 'n'],
132
+ ['杉', '木', '杉木,挺拔', 'n'], ['柠', '木', '柠檬,清新', 'f'],
133
+ ['桢', '木', '桢干,栋梁材', 'm'], ['樾', '木', '树荫,庇下', 'm'],
134
+ ['柘', '木', '柘木,坚韧', 'n'], ['权', '木', '权衡,执中', 'm'],
135
+ ['材', '木', '木材,成材', 'm'], ['杭', '木', '苏杭,舟行', 'n'],
136
+ ['枚', '木', '衔枚,静而进', 'n'], ['梧', '木', '梧桐,栖凤', 'n'],
137
+ ['竹', '木', '竹,有节而虚心', 'n'], ['简', '木', '简朴,简静', 'n'],
138
+ ['策', '木', '策谋,鞭策', 'm'], ['筹', '木', '筹算,谋划', 'm'],
139
+ ['芬', '木', '芬芳', 'f'], ['芳', '木', '芳华', 'f'],
140
+ ['花', '木', '花,明媚', 'f'], ['菊', '木', '菊花,晚节', 'f'],
141
+ ['兰', '木', '兰花,幽香', 'f'], ['芝', '木', '芝兰,瑞草', 'f'],
142
+ ['芹', '木', '芹献,谦诚', 'f'], ['荃', '木', '荃草,香草', 'f'],
143
+ ['萃', '木', '荟萃,聚合', 'n'], ['蓁', '木', '蓁蓁,草木盛', 'n'],
144
+ ['荞', '木', '荞麦,实而不华', 'f'], ['莞', '木', '莞尔,浅笑', 'f'],
145
+ ['萦', '木', '萦绕,绵长', 'f'], ['蔚', '木', '蔚然,文采盛', 'n'],
146
+ ['芃', '木', '芃芃,草木茂', 'n'], ['芩', '木', '黄芩,草本', 'f'],
147
+ ['苡', '木', '薏苡,清润', 'f'], ['莱', '木', '莱草,野而秀', 'n'],
148
+ ['萌', '木', '萌芽,生机', 'n'], ['葆', '木', '永葆,守而不失', 'n'],
149
+ ['萧', '木', '萧然,清疏', 'n'], ['樟', '木', '樟木,香而久', 'n'],
150
+ ['樱', '木', '樱花,明丽', 'f'], ['栎', '木', '栎树,坚实', 'n'],
151
+ ['棹', '木', '舟棹,行水', 'n'], ['楸', '木', '楸树,高直', 'n'],
152
+ ['槐', '木', '槐树,三公之位', 'n'], ['橙', '木', '橙,明暖', 'n'],
153
+ ['檀', '木', '檀香,沉静', 'n'],
154
+ // ── 扩充:火 ──
155
+ ['丰', '火', '丰盛、丰盈(繁体豐从豆,归火)', 'n'], ['日', '火', '太阳,光明', 'm'],
156
+ ['阳', '火', '阳光,向阳而生', 'm'], ['光', '火', '光明', 'n'],
157
+ ['亮', '火', '明亮,爽朗', 'm'], ['明', '火', '光明、明理', 'n'],
158
+ ['晶', '火', '晶莹,三光', 'f'], ['晴', '火', '晴朗,天开', 'f'],
159
+ ['夏', '火', '夏天,盛大', 'n'], ['智', '火', '智慧,明达', 'm'],
160
+ ['卓', '火', '卓越,卓立', 'm'], ['乐', '火', '和乐', 'n'],
161
+ ['恺', '火', '恺悌,和乐', 'm'], ['恬', '火', '恬静', 'f'],
162
+ ['甜', '火', '甜美', 'f'], ['亭', '火', '亭亭,端立', 'f'],
163
+ ['俊', '火', '俊朗,才俊', 'm'], ['丙', '火', '天干丙,阳火', 'm'],
164
+ ['丁', '火', '天干丁,阴火', 'n'], ['鼎', '火', '鼎立,厚重', 'm'],
165
+ ['陶', '火', '陶然,自得', 'n'], ['熹', '火', '熹微,晨光', 'n'],
166
+ ['炀', '火', '火旺,炽盛', 'm'], ['烜', '火', '烜赫,显明', 'm'],
167
+ ['暻', '火', '日光', 'n'], ['晖', '火', '日晖,光彩', 'n'],
168
+ ['晔', '火', '光晔,明盛', 'n'], ['旻', '火', '秋天,天高', 'm'],
169
+ ['晏', '火', '晏然,安和', 'n'], ['晓', '火', '拂晓,天将明', 'f'],
170
+ ['暄', '火', '温暖,日暖', 'f'], ['昆', '火', '昆山,同也', 'm'],
171
+ ['伶', '火', '伶俐,聪敏', 'f'],
172
+ // ── 扩充:土 ──
173
+ ['山', '土', '山,稳重', 'm'], ['岸', '土', '岸然,独立', 'm'],
174
+ ['岑', '土', '小山,高而静', 'n'], ['巍', '土', '巍峨,高大', 'm'],
175
+ ['嵩', '土', '嵩山,高峻', 'm'], ['岭', '土', '山岭,绵延', 'n'],
176
+ ['奥', '土', '深奥,蕴藉', 'm'], ['宜', '土', '宜然,合度', 'n'],
177
+ ['容', '土', '包容,容仪', 'n'], ['意', '土', '心意,诚意', 'f'],
178
+ ['依', '土', '依倚,亲近', 'f'], ['悠', '土', '悠然,从容', 'f'],
179
+ ['维', '土', '维系,纲维', 'n'], ['纬', '土', '经纬,有条', 'n'],
180
+ ['永', '土', '永恒,久长', 'n'], ['远', '土', '高远', 'm'],
181
+ ['辰', '土', '星辰、辰时', 'm'], ['壁', '土', '壁立,端正', 'm'],
182
+ ['田', '土', '田野,耕获', 'm'], ['由', '土', '由来,自得', 'n'],
183
+ ['画', '土', '图画,文采', 'f'], ['延', '土', '延展,绵长', 'm'],
184
+ ['建', '土', '建立,树也', 'm'], ['音', '土', '音声,和鸣', 'f'],
185
+ ['韵', '土', '韵致,余响', 'f'], ['玮', '土', '美玉,珍奇', 'm'],
186
+ ['雍', '土', '雍容,和穆', 'm'], ['懿', '土', '懿德,美善', 'f'],
187
+ // ── 扩充:金 ──
188
+ ['金', '金', '金,坚而贵', 'm'], ['秋', '金', '秋收、成熟(秋属金,肃而收)', 'n'],
189
+ ['书', '金', '书卷,文墨', 'n'], ['舒', '金', '舒展,从容', 'n'],
190
+ ['善', '金', '善良,善成', 'n'], ['信', '金', '诚信,符信', 'm'],
191
+ ['修', '金', '修远,修身', 'm'], ['素', '金', '素雅,本色', 'f'],
192
+ ['静', '金', '安静,沉静', 'f'], ['心', '金', '心志,中藏', 'f'],
193
+ ['新', '金', '新意,日新', 'n'], ['正', '金', '正直,端正', 'm'],
194
+ ['世', '金', '世代,绵延', 'm'], ['少', '金', '少年,少壮', 'm'],
195
+ ['西', '金', '西方,金位', 'n'], ['白', '金', '洁白,素净', 'n'],
196
+ ['辛', '金', '天干辛,阴金', 'n'], ['成', '金', '成器,成事', 'm'],
197
+ ['利', '金', '利落,锋利', 'm'], ['剑', '金', '剑,刚锐', 'm'],
198
+ ['刚', '金', '刚强,不屈', 'm'], ['则', '金', '法则,有度', 'm'],
199
+ ['初', '金', '初心,开端', 'n'], ['硕', '金', '硕大,学殖', 'm'],
200
+ ['尚', '金', '崇尚,尚贤', 'n'], ['臣', '金', '臣,佐事', 'm'],
201
+ ['川', '金', '川流,通达', 'n'], ['舟', '金', '舟行,济渡', 'n'],
202
+ ['州', '金', '州,聚落', 'n'], ['祖', '金', '祖德,源远', 'm'],
203
+ ['祥', '金', '吉祥,祥瑞', 'n'], ['禄', '金', '福禄,俸给', 'm'],
204
+ ['禧', '金', '禧庆,福', 'n'], ['珍', '金', '珍宝,珍重', 'f'],
205
+ ['宣', '金', '宣明,布达', 'n'], ['序', '金', '秩序,庠序', 'n'],
206
+ ['谦', '金', '谦逊,有终', 'm'], ['赞', '金', '赞美,佐成', 'm'],
207
+ ['超', '金', '超越,越过', 'm'], ['锴', '金', '精铁,坚刚', 'm'],
208
+ // ── 扩充:水 ──
209
+ ['水', '水', '水,润下', 'n'], ['冰', '水', '冰清,澄澈', 'f'],
210
+ ['洁', '水', '洁净,洁身', 'f'], ['浩', '水', '浩大,广远', 'm'],
211
+ ['瀚', '水', '浩瀚,无涯', 'm'], ['渊', '水', '渊深,有本', 'm'],
212
+ ['澜', '水', '波澜,壮阔', 'n'], ['潮', '水', '潮涌,有信', 'n'],
213
+ ['溪', '水', '溪流,清浅', 'f'], ['湘', '水', '湘水,清深', 'f'],
214
+ ['渝', '水', '渝水,守而不变', 'n'], ['深', '水', '深厚,深藏', 'm'],
215
+ ['湛', '水', '湛然,清深', 'm'], ['汇', '水', '汇聚,会归', 'n'],
216
+ ['洲', '水', '洲渚,水中地', 'n'], ['湾', '水', '水湾,曲处', 'f'],
217
+ ['云', '水', '云行,舒卷', 'n'], ['风', '水', '风雅,流动', 'n'],
218
+ ['北', '水', '北方,水位', 'n'], ['冬', '水', '冬藏,静敛', 'f'],
219
+ ['玄', '水', '玄远,幽深', 'n'], ['子', '水', '子,初生', 'm'],
220
+ ['平', '水', '平和,坦易', 'n'], ['方', '水', '方正,有隅', 'n'],
221
+ ['帆', '水', '风帆,顺行', 'n'], ['航', '水', '航行,济远', 'm'],
222
+ ['敏', '水', '敏思,捷悟', 'f'], ['慧', '水', '慧心,明彻', 'f'],
223
+ ['惠', '水', '惠泽,施与', 'f'], ['霈', '水', '霈雨,丰沛', 'm'],
224
+ ['灏', '水', '灏气,广大', 'm'], ['湄', '水', '水湄,岸边', 'f'],
225
+ ['滨', '水', '水滨,近水', 'n'], ['洛', '水', '洛水,清流', 'n'],
226
+ ['津', '水', '津渡,要处', 'n'], ['泊', '水', '淡泊,自守', 'm'],
227
+ ['沧', '水', '沧海,苍茫', 'm'], ['浦', '水', '浦,水滨', 'n'],
119
228
  ];
120
229
 
121
230
  const EL_INDEX = new Map();
@@ -123,6 +232,24 @@ for (const c of CHAR_LIB) {
123
232
  if (!EL_INDEX.has(c[0])) EL_INDEX.set(c[0], { c: c[0], el: c[1], meaning: c[2], g: c[3], order: EL_INDEX.size });
124
233
  }
125
234
 
235
+ /** 用户可自带某字的五行(字库未收时用,按你信的那派算;传了就算「用户指定」)。 */
236
+ function userElement(input, ch) {
237
+ const map = input && input.charElements;
238
+ if (!map || typeof map !== 'object') return null;
239
+ const v = map[ch];
240
+ return ALL.includes(v) ? v : null;
241
+ }
242
+
243
+ /** 喜用五行里挑几个可替换的常用字(同性别倾向优先),给未收录字一个落点。 */
244
+ function suggestFor(element, genderPref, exclude, count = 3) {
245
+ const pool = CHAR_LIB.filter((c) => c[1] === element && !exclude.has(c[0]));
246
+ pool.sort((a, b) => {
247
+ const fit = (x) => (x[3] === genderPref || x[3] === 'n' ? 1 : 0);
248
+ return fit(b) - fit(a);
249
+ });
250
+ return pool.slice(0, count).map((c) => ({ char: c[0], element: c[1], meaning: c[2] }));
251
+ }
252
+
126
253
  const KIND_LABEL = { m: '男', f: '女', n: '中' };
127
254
 
128
255
  /** 喜用五行(简化判定:旺衰 + 生克泄耗)。 */
@@ -178,23 +305,44 @@ export function assessName(input) {
178
305
  baziNote = { dayMaster: r.dayMaster, solarNote: r.solarTime ? r.solarTime.note : '' };
179
306
  }
180
307
 
308
+ const genderPref = input.gender === 'female' ? 'f' : 'm';
309
+ const taken = new Set([surname, ...given]);
310
+ const favPool = favorable.list.length ? favorable.list : ALL.slice();
181
311
  const chars = [...given].map((ch) => {
182
312
  const info = charInfo(ch);
183
- const inFav = info ? favorable.list.includes(info.el) : null;
313
+ const given = userElement(input, ch);
314
+ if (info) {
315
+ return {
316
+ char: ch, element: info.el, meaning: info.meaning,
317
+ genderHint: KIND_LABEL[info.g], favorable: hasBirth ? favorable.list.includes(info.el) : null,
318
+ source: 'lib', note: null,
319
+ };
320
+ }
321
+ if (given) {
322
+ return {
323
+ char: ch, element: given, meaning: '字库未收录;五行按你指定的「' + given + '」计',
324
+ genderHint: null, favorable: hasBirth ? favorable.list.includes(given) : null,
325
+ source: 'user', note: '此字五行由调用方指定,不是字库推断',
326
+ };
327
+ }
184
328
  return {
185
- char: ch,
186
- element: info ? info.el : '未收录',
187
- meaning: info ? info.meaning : '字库未收录,未推断五行',
188
- genderHint: info ? KIND_LABEL[info.g] : null,
189
- favorable: inFav,
329
+ char: ch, element: '未收录', meaning: '字库未收录,未推断五行(不猜)',
330
+ genderHint: null, favorable: null, source: 'none',
331
+ note: hasBirth ? '若想贴合命局,可换用下面的同喜用字' : '字库未收录:给出生辰后可以看同喜用的替换字',
332
+ suggest: hasBirth
333
+ ? favPool.flatMap((el) => suggestFor(el, genderPref, taken, 2))
334
+ : [],
190
335
  };
191
336
  });
192
- const known = chars.filter((c) => c.element !== '未收录').length;
337
+ const known = chars.filter((c) => c.source === 'lib').length;
338
+ const unknown = chars.filter((c) => c.source === 'none').length;
193
339
  const favCount = chars.filter((c) => c.favorable === true).length;
340
+ const judged = chars.length - unknown;
194
341
  let verdict;
195
342
  if (!hasBirth) verdict = '只做了字义与五行归类;给出生年月日才能判断用字是否合喜用。';
196
- else if (favCount === chars.length && chars.length > 0) verdict = `用字都落在喜用(${favorable.list.join('、')})上,与命局方向一致。`;
197
- else if (favCount > 0) verdict = `部分用字合喜用(${favCount}/${chars.length}),其余可再斟酌;喜用为 ${favorable.list.join('、')}。`;
343
+ else if (unknown === chars.length) verdict = `两字都不在字库里,没法判断合不合喜用(喜用为 ${favorable.list.join('、')})。下面给了可替换的常用字,或把你自己认定的五行传进来。`;
344
+ else if (favCount === judged && judged > 0) verdict = `用字都落在喜用(${favorable.list.join('、')})上,与命局方向一致。`;
345
+ else if (favCount > 0) verdict = `部分用字合喜用(${favCount}/${judged}),其余可再斟酌;喜用为 ${favorable.list.join('、')}。`;
198
346
  else verdict = `用字未落在喜用(${favorable.list.join('、')})上;不必然是坏名,但若想贴合命局可换字再看。`;
199
347
 
200
348
  return {
@@ -212,10 +360,10 @@ export function assessName(input) {
212
360
  },
213
361
  } : {}),
214
362
  chars,
215
- stats: { chars: chars.length, known, favorableCount: favCount },
363
+ stats: { chars: chars.length, known, unknown, favorableCount: favCount, libSize: CHAR_LIB.length },
216
364
  verdict,
217
365
  notes: [
218
- '字之五行按通行字义/偏旁归类,自建字库有限:未收录的字不猜五行,如实标注。',
366
+ `字之五行按通行字义/偏旁归类,自建字库共 ${CHAR_LIB.length} 字(常用名用字为主);未收录的字不猜五行,如实标注,「用户指定」是你传入的归类。`,
219
367
  '不做五格剖象(需康熙字典笔画),也不做音韵格律判断——谐音、拗口请自己读三遍或用方言读一遍。',
220
368
  '起名只做用字参考,不替父母做决定。',
221
369
  ],
@@ -267,19 +415,41 @@ export function generateNames(input) {
267
415
  elements: [a[1], b[1]],
268
416
  allFavorable: fav.list.includes(a[1]) && fav.list.includes(b[1]),
269
417
  genderScore: gScore,
270
- order: i * 1000 + j,
418
+ order: (i * 7 + j * 13) % 9973, // 打散字库顺序,避免候选清一色都是前几个字
271
419
  });
272
420
  }
273
421
  }
274
- scored.sort((x, y) => (y.allFavorable - x.allFavorable) || (y.genderScore - x.genderScore) || (x.order - y.order));
422
+ const mixed = (c) => (c.elements[0] !== c.elements[1] ? 1 : 0); // 两字异行,比同气堆叠更有层次
423
+ scored.sort((x, y) => (y.allFavorable - x.allFavorable) || (mixed(y) - mixed(x)) || (y.genderScore - x.genderScore) || (x.order - y.order));
424
+ // 去重 + 用字轮换:先收「两个字都没出现过」的候选,再放宽——避免 20 个候选里 13 个都用同一个「梓」
275
425
  const out = [];
276
- const used = new Set();
277
- for (const cand of scored) {
278
- if (out.length >= limit) break;
426
+ const usedNames = new Set();
427
+ const usedChars = new Set([surname]);
428
+ const usedPairs = new Set();
429
+ const take = (cand, strict, freshPair) => {
279
430
  const key = [...cand.name].sort().join('');
280
- if (used.has(key)) continue;
281
- used.add(key);
431
+ if (usedNames.has(key)) return false;
432
+ if (strict && cand.chars.some((x) => usedChars.has(x.char))) return false;
433
+ const pair = cand.elements.join('');
434
+ if (freshPair && usedPairs.has(pair)) return false;
435
+ usedNames.add(key);
436
+ usedPairs.add(pair);
437
+ cand.chars.forEach((x) => usedChars.add(x.char));
282
438
  out.push({ rank: out.length + 1, name: surname + cand.name, given: cand.name, chars: cand.chars });
439
+ return true;
440
+ };
441
+ // 三轮:①新字 + 新五行组合 ②新字 ③放开——先保证候选之间不撞字、不撞组合
442
+ for (const cand of scored) {
443
+ if (out.length >= limit) break;
444
+ take(cand, true, true);
445
+ }
446
+ for (const cand of scored) {
447
+ if (out.length >= limit) break;
448
+ take(cand, true, false);
449
+ }
450
+ for (const cand of scored) {
451
+ if (out.length >= limit) break;
452
+ take(cand, false, false);
283
453
  }
284
454
  return {
285
455
  system: 'name',
@@ -289,7 +459,7 @@ export function generateNames(input) {
289
459
  bazi: { dayMaster: r.dayMaster, strength, favorable: fav.list, basis: fav.basis },
290
460
  candidates: out,
291
461
  notes: [
292
- `字库共 ${CHAR_LIB.length} 字,只在喜用五行(${fav.list.join('、')})里选字;未覆盖的生僻好字可手工评估。`,
462
+ `字库共 ${CHAR_LIB.length} 字,只在喜用五行(${fav.list.join('、')})里选字;未覆盖的生僻好字可用 charElements 指定五行后评估。`,
293
463
  '候选按合喜用与性别倾向确定性排序,不是「吉名」判定;音韵与方言谐音请自行读念检查。',
294
464
  '不做五格剖象(需康熙字典笔画)。',
295
465
  ],
@@ -33,6 +33,19 @@ const YONG_SHEN = {
33
33
  travel: { label: '出行', primary: ['九天'], secondary: ['驿马'], note: '出行以九天为主,兼看驿马' },
34
34
  };
35
35
 
36
+ /** 天盘干与宫的落宫关系统计:干克宫 3 宫(辛)… */
37
+ function stemRelationLine(r) {
38
+ const groups = new Map();
39
+ for (const p of r.palaces || []) {
40
+ const rel = p.stemRelation || null;
41
+ if (!rel || rel.type === 'unknown' || !p.tianPan) continue;
42
+ if (!groups.has(rel.type)) groups.set(rel.type, []);
43
+ groups.get(rel.type).push(`${p.palace}宫${p.tianPan}`);
44
+ }
45
+ const order = ['干克宫', '宫克干', '干生宫', '宫生干', '比和'];
46
+ return order.filter((k) => groups.has(k)).map((k) => `${k} ${groups.get(k).join('、')}`).join(';') || '—';
47
+ }
48
+
36
49
  const REL_TEXT = {
37
50
  生: '主体力量流向环境或趋势',
38
51
  克: '主体对环境或趋势构成压力、有改造意图',
@@ -128,15 +141,37 @@ export function interpretQimen(r, opts = {}) {
128
141
  else if (rel.type === '宫制门') controlled.push(`${p.palace}宫(宫${rel.palace}克${p.gate}${rel.gate})`);
129
142
  }
130
143
  const kongCells = r.palaces.filter((p) => p.kong).map((p) => `${p.palace}宫`);
144
+ const jiXing = (r.conditions && r.conditions.jiXing) || [];
145
+ const ruMu = (r.conditions && r.conditions.ruMu) || [];
131
146
  sections.push({
132
147
  key: 'conditions',
133
148
  label: '宫位条件',
134
149
  text: `门迫(同宫内门克宫):${forced.length ? forced.join('、') : '本盘无'};`
135
150
  + `宫制门(宫克门,属「制」不为迫):${controlled.length ? controlled.join('、') : '无'}。`
151
+ + `击刑(天盘六仪落刑宫):${jiXing.length ? jiXing.map((x) => `${x.palace}宫${x.stem}(${x.basis})`).join('、') : '本盘无'};`
152
+ + `入墓(天盘三奇落墓宫):${ruMu.length ? ruMu.map((x) => `${x.palace}宫${x.stem}(${x.basis})`).join('、') : '本盘无'}。`
136
153
  + (kongCells.length ? `空亡:${kongCells.join('、')}——落空之宫事多不实。` : '无落空之宫。')
137
- + `门迫只作宫位条件,不自动等同于用神受制。`,
154
+ + `这三项都只作宫位条件:门迫、击刑、入墓不自动等同于用神受制,取象时看它们落在哪个符号上。`,
138
155
  });
139
156
 
157
+ // ⑤b 月令旺衰:门、星、天盘干按月支五行判五态(与门迫这类宫位关系分开)
158
+ const mo = r.month || null;
159
+ const st = r.states || null;
160
+ if (mo && st) {
161
+ const order = ['旺', '相', '休', '囚', '死'];
162
+ const line = (g) => order.filter((k) => g && Array.isArray(g[k]) && g[k].length)
163
+ .map((k) => `${k} ${g[k].join('、')}`).join(';') || '—';
164
+ sections.push({
165
+ key: 'states',
166
+ label: '月令旺衰',
167
+ text: `月令 ${mo.branch}(${mo.element}):`
168
+ + `门 ${line(st.gate)}。星 ${line(st.star)}。天盘干 ${line(st.stem)}。`
169
+ + `旺为同月令之气,相为月令所生,休为生月令,囚为克月令,死为月令所克。`
170
+ + `干宫的落宫关系另算:${stemRelationLine(r)}。`
171
+ + `这一层只说明门、星、干自身力量强弱,不合吉凶:门旺而宫克门,仍受宫制;星旺而落宫死地,也难全其用。`,
172
+ });
173
+ }
174
+
140
175
  // ⑥ 条件式倾向
141
176
  if (unresolved.length) {
142
177
  sections.push({
@@ -151,7 +186,7 @@ export function interpretQimen(r, opts = {}) {
151
186
  key: 'tendency',
152
187
  label: '最终倾向',
153
188
  text: `在当前输入、规则集与所问之事下:若以「${primary.key}」为用神,用神宫对值符宫为「${rel}」,整体${tone};`
154
- + `若${ys.label}实际取另一个候选,或时间范围不同,解读边界随之变化。未决项:月令旺衰未纳入本规则集。`,
189
+ + `若${ys.label}实际取另一个候选,或时间范围不同,解读边界随之变化。未决项:天盘干与地盘的十干克应(100 组)未纳入本规则集,干宫生克只作落宫条件。`,
155
190
  });
156
191
  }
157
192
 
package/engine/qimen.js CHANGED
@@ -1,4 +1,4 @@
1
- // 时家奇门(转盘,拆补法定局)。
1
+ // 时家奇门(转盘,拆补法定局)。
2
2
  // 算法移植自 MIT 许可的 qimendunjia-standalone@0.1.0(作者:见其 LICENSE),
3
3
  // 保留其定局/排盘口径(符头定三元、值符值使推演、天盘随值符旋转、中五宫寄坤二),
4
4
  // 只把历法层换成本插件自己的 calendar/lunar 模块——节气、干支、农历都走同一套已验证的源码。
@@ -179,6 +179,108 @@ export function gatePalaceRelation(gate, palace) {
179
179
  if (SHENG[pe] === ge) return { type: '宫生门', gate: ge, palace: pe, note: `${pe}生${ge}` };
180
180
  return { type: '比和', gate: ge, palace: pe, note: `${ge}与${pe}比和` };
181
181
  }
182
+
183
+ // ── 天盘干:五行、与所落宫的生克 ─────────────────────────────
184
+ // 天盘干(三奇六仪,甲隐于六仪,盘面上不出现甲)与所落宫五行的生克,
185
+ // 是与门宫关系并列的一层「干—宫」关系,只作落宫条件,不折吉凶。
186
+ const STEM_ELEMENT = {
187
+ 甲: '木', 乙: '木', 丙: '火', 丁: '火', 戊: '土',
188
+ 己: '土', 庚: '金', 辛: '金', 壬: '水', 癸: '水',
189
+ };
190
+
191
+ /** 天盘干与其所落宫的关系:干克宫 / 宫克干 / 干生宫 / 宫生干 / 比和 */
192
+ export function stemPalaceRelation(stem, palace) {
193
+ const se = STEM_ELEMENT[stem];
194
+ const pe = PALACE_ELEMENT[palace];
195
+ if (!se || !pe) return { type: 'unknown', stem: se || null, palace: pe || null, note: '缺少干或宫的五行,无法判断' };
196
+ if (KE[se] === pe) return { type: '干克宫', stem: se, palace: pe, note: `${se}克${pe},天盘干克宫` };
197
+ if (KE[pe] === se) return { type: '宫克干', stem: se, palace: pe, note: `${pe}克${se},宫克天盘干` };
198
+ if (SHENG[se] === pe) return { type: '干生宫', stem: se, palace: pe, note: `${se}生${pe},干泄气于宫` };
199
+ if (SHENG[pe] === se) return { type: '宫生干', stem: se, palace: pe, note: `${pe}生${se},宫来生干` };
200
+ return { type: '比和', stem: se, palace: pe, note: `${se}与${pe}比和` };
201
+ }
202
+
203
+ // ── 六仪击刑 / 三奇入墓 ─────────────────────────────────────
204
+ // 两者都是「天盘干落到某个宫就成立」的硬条件,与门迫一样只作宫位条件,不下吉凶。
205
+ // 六仪击刑按地支相刑推:子刑卯、戌刑未、申刑寅、午自刑、辰自刑、寅刑巳;
206
+ // 三奇入墓按五行墓库推:乙木墓未(未在坤二宫)、丙丁火墓戌(戌在乾六宫)。
207
+ const JI_XING = {
208
+ 戊: { palace: '3', zhi: '卯', basis: '甲子戊,子刑卯' },
209
+ 己: { palace: '2', zhi: '未', basis: '甲戌己,戌刑未' },
210
+ 庚: { palace: '8', zhi: '寅', basis: '甲申庚,申刑寅' },
211
+ 辛: { palace: '9', zhi: '午', basis: '甲午辛,午自刑' },
212
+ 壬: { palace: '4', zhi: '辰', basis: '甲辰壬,辰自刑' },
213
+ 癸: { palace: '4', zhi: '巳', basis: '甲寅癸,寅刑巳' },
214
+ };
215
+ const RU_MU = {
216
+ 乙: { palace: '2', zhi: '未', basis: '乙木墓于未,未在坤二宫' },
217
+ 丙: { palace: '6', zhi: '戌', basis: '丙火墓于戌,戌在乾六宫' },
218
+ 丁: { palace: '6', zhi: '戌', basis: '丁火墓于戌,戌在乾六宫' },
219
+ };
220
+
221
+ /** 天盘六仪落刑宫 → [{palace, stem, zhi, basis}] */
222
+ export function jiXingOf(palaces) {
223
+ const out = [];
224
+ for (const p of palaces || []) {
225
+ const hit = JI_XING[p.tianPan];
226
+ if (hit && hit.palace === String(p.palace)) out.push({ palace: p.palace, stem: p.tianPan, zhi: hit.zhi, basis: hit.basis });
227
+ }
228
+ return out;
229
+ }
230
+
231
+ /** 天盘三奇入墓 → [{palace, stem, zhi, basis}] */
232
+ export function ruMuOf(palaces) {
233
+ const out = [];
234
+ for (const p of palaces || []) {
235
+ const hit = RU_MU[p.tianPan];
236
+ if (hit && hit.palace === String(p.palace)) out.push({ palace: p.palace, stem: p.tianPan, zhi: hit.zhi, basis: hit.basis });
237
+ }
238
+ return out;
239
+ }
240
+
241
+ /** 供测试与文档引用:两张表的只读视图 */
242
+ export const QIMEN_CONDITION_TABLES = Object.freeze({
243
+ jiXing: Object.freeze(Object.fromEntries(Object.entries(JI_XING).map(([k, v]) => [k, { ...v }]))),
244
+ ruMu: Object.freeze(Object.fromEntries(Object.entries(RU_MU).map(([k, v]) => [k, { ...v }]))),
245
+ });
246
+
247
+ // ── 月令旺衰:门与星对月令的五态 ─────────────────────────────
248
+ // 五行在月令的旺相休囚死:与月令同气为旺,月令生之为相,生月令为休,
249
+ // 克月令为囚,被月令克为死。只作「力量强弱」的事实,不折成吉凶。
250
+ const BRANCH_ELEMENT = {
251
+ 子: '水', 丑: '土', 寅: '木', 卯: '木', 辰: '土', 巳: '火',
252
+ 午: '火', 未: '土', 申: '金', 酉: '金', 戌: '土', 亥: '水',
253
+ };
254
+ /** 旺相休囚死:element 在 monthElement 月令中的状态 */
255
+ export function monthState(element, monthElement) {
256
+ if (!element || !monthElement) return 'unknown';
257
+ if (element === monthElement) return '旺';
258
+ if (SHENG[monthElement] === element) return '相';
259
+ if (SHENG[element] === monthElement) return '休';
260
+ if (KE[element] === monthElement) return '囚';
261
+ if (KE[monthElement] === element) return '死';
262
+ return 'unknown';
263
+ }
264
+ /** 月支 → 月令五行 */
265
+ export function monthElementOf(branch) {
266
+ return BRANCH_ELEMENT[branch] || null;
267
+ }
268
+ /** 把一组 {name, element} 按状态归堆 → {旺:[…],相:[…],休:[…],囚:[…],死:[…]} */
269
+ export function groupByState(items, monthElement) {
270
+ const out = { 旺: [], 相: [], 休: [], 囚: [], 死: [] };
271
+ for (const it of items || []) {
272
+ const st = monthState(it.element, monthElement);
273
+ if (out[st]) out[st].push(it.name);
274
+ }
275
+ return out;
276
+ }
277
+ /** 供测试与文档引用:门/星五行与月支五行的只读视图 */
278
+ export const QIMEN_STATE_TABLES = Object.freeze({
279
+ gate: Object.freeze({ ...GATE_ELEMENT }),
280
+ star: Object.freeze({ ...STAR_ELEMENT }),
281
+ branch: Object.freeze({ ...BRANCH_ELEMENT }),
282
+ stem: Object.freeze({ ...STEM_ELEMENT }),
283
+ });
182
284
  function getDiPan(type, num) {
183
285
  const result = {};
184
286
  let gong = num;
@@ -360,6 +462,10 @@ export function buildQimen(input) {
360
462
  const maZhi = maMap[hourGz[1]] || '';
361
463
  const maGong = ZHI_TO_GONG[maZhi] || '';
362
464
 
465
+ // 月令:门与星的力量强弱都按月支五行判,与局数、宫位分开记
466
+ const monthBranch = (snapshot.monthGanZhi || '')[1] || '';
467
+ const monthElement = BRANCH_ELEMENT[monthBranch] || null;
468
+
363
469
  // 中五宫单列(天禽寄坤二,门神为空),与参照实现的九宫结构对齐
364
470
  const centerPalace = {
365
471
  palace: '5',
@@ -372,7 +478,11 @@ export function buildQimen(input) {
372
478
  palaceElement: PALACE_ELEMENT['5'],
373
479
  gateElement: null,
374
480
  starElement: STAR_ELEMENT['天禽'],
481
+ stemElement: STEM_ELEMENT[tianPan['5']] || null,
375
482
  gateRelation: { type: 'unknown', note: '中宫无门,不参与门宫关系' },
483
+ stemRelation: stemPalaceRelation(tianPan['5'], '5'),
484
+ starState: monthState(STAR_ELEMENT['天禽'], monthElement),
485
+ stemState: monthState(STEM_ELEMENT[tianPan['5']], monthElement),
376
486
  kong: false,
377
487
  ma: false,
378
488
  center: true,
@@ -389,7 +499,12 @@ export function buildQimen(input) {
389
499
  palaceElement: PALACE_ELEMENT[gong],
390
500
  gateElement: GATE_ELEMENT[bm.baMen[gong]] || null,
391
501
  starElement: STAR_ELEMENT[jx.jiuXing[gong]] || null,
502
+ stemElement: STEM_ELEMENT[tianPan[gong]] || null,
392
503
  gateRelation: gatePalaceRelation(bm.baMen[gong], gong),
504
+ stemRelation: stemPalaceRelation(tianPan[gong], gong),
505
+ gateState: monthState(GATE_ELEMENT[bm.baMen[gong]], monthElement),
506
+ starState: monthState(STAR_ELEMENT[jx.jiuXing[gong]], monthElement),
507
+ stemState: monthState(STEM_ELEMENT[tianPan[gong]], monthElement),
393
508
  kong: kongGong.includes(gong),
394
509
  ma: maGong === gong,
395
510
  }));
@@ -418,6 +533,18 @@ export function buildQimen(input) {
418
533
  zhiShi: { gate: bm.zhiShiMen, gong: bm.zhiShiGong },
419
534
  kongWang: kongZhi,
420
535
  ma: { zhi: maZhi, gong: maGong },
536
+ // 宫位硬条件(天盘干定宫):六仪击刑、三奇入墓。只列成立项,不排序、不打分
537
+ conditions: {
538
+ jiXing: jiXingOf(palaces),
539
+ ruMu: ruMuOf(palaces),
540
+ },
541
+ // 月令与门/星旺衰:按月支五行判旺相休囚死,只报强弱、不折吉凶
542
+ month: { branch: monthBranch, element: monthElement },
543
+ states: {
544
+ gate: groupByState(palaces.filter((p) => p.gate).map((p) => ({ name: p.gate, element: p.gateElement })), monthElement),
545
+ star: groupByState(palaces.map((p) => ({ name: p.star, element: p.starElement })), monthElement),
546
+ stem: groupByState(palaces.filter((p) => p.tianPan).map((p) => ({ name: p.tianPan, element: p.stemElement })), monthElement),
547
+ },
421
548
  palaces,
422
549
  centerPalace,
423
550
  rules: [
package/engine/ziwei.js CHANGED
@@ -524,6 +524,8 @@ export function buildZiwei(input) {
524
524
 
525
525
  chart.patterns = detectPatterns(chart);
526
526
  chart.daxianList = daxianList(chart);
527
+ chart.fly = flyStars(chart);
528
+ chart.flyChains = flyChains(chart, { from: '命宫' });
527
529
  enrichLiunian(chart);
528
530
  // 流月/流日/流时:默认按本机当前时刻,可用 asOf 指定(如 "2026-10-22T16:00")
529
531
  chart.flow = flowOf(chart, input.asOf);
@@ -716,6 +718,83 @@ export function daxianList(chart) {
716
718
  .sort((a, b) => a.ages[0] - b.ages[0]);
717
719
  }
718
720
 
721
+ // ── 四化飞星:宫干四化飞向哪一宫,以及「串连」────────────────────
722
+ // 生年四化看「这颗星先天带着什么」,飞星四化看「这一宫自己会去找谁」:
723
+ // 宫干定出四个化星,星曜落在哪一宫,就是这一宫的禄/权/科/忌飞向哪里。
724
+ // 串连(忌转忌、科转科…)是同一支化一路跟下去:命宫化忌入 A 宫,A 宫的宫干再化忌入 B 宫……
725
+ // 走到回到已访问过的宫就成环(传统讲的「忌出」「自化」都在这一步显现)。
726
+
727
+ export const FLY_TYPES = ['禄', '权', '科', '忌'];
728
+
729
+ // 某天干下某一化的星曜;表里没有就返回 null(不会凭印象补)
730
+ export function flyStarOf(stem, type) {
731
+ const map = MUTAGEN[stem];
732
+ if (!map) return null;
733
+ for (const [star, t] of Object.entries(map)) if (t === type) return star;
734
+ return null;
735
+ }
736
+
737
+ // 12 宫各自的四化落点(一次算全,供面板与解读共用)
738
+ export function flyStars(chart) {
739
+ const palaces = chart.palaces || [];
740
+ return palaces.map((p) => ({
741
+ palace: p.palace,
742
+ branch: p.branch,
743
+ stem: p.stem,
744
+ flies: FLY_TYPES.map((type) => {
745
+ const star = flyStarOf(p.stem, type);
746
+ const b = star ? starBranchOf(chart, star) : -1;
747
+ const target = b >= 0 ? palaces.find((x) => x.branch === BRANCHES[b]) : null;
748
+ return {
749
+ type,
750
+ star,
751
+ to: target ? target.palace : null,
752
+ toBranch: target ? target.branch : null,
753
+ // 落回本宫即自化:这一宫自己化出去又回到自己
754
+ self: !!(target && target.palace === p.palace),
755
+ };
756
+ }),
757
+ }));
758
+ }
759
+
760
+ // 从某一宫出发,跟着同一支化逐跳;ended 说明为什么停:
761
+ // cycle=回到走过的宫(成环)、no-star=该干这一化无星、off-chart=星未上盘、max=走满上限
762
+ export function flyChain(chart, { from = '命宫', type = '忌', maxSteps = 12 } = {}) {
763
+ const palaces = chart.palaces || [];
764
+ const start = palaces.find((p) => p.palace === from) || palaces[0];
765
+ if (!start) return null;
766
+ const steps = [];
767
+ const seen = new Set([start.palace]);
768
+ let cur = start;
769
+ let ended = 'max';
770
+ for (let i = 0; i < Math.max(1, maxSteps); i++) {
771
+ const star = flyStarOf(cur.stem, type);
772
+ if (!star) { ended = 'no-star'; break; }
773
+ const b = starBranchOf(chart, star);
774
+ if (b < 0) { ended = 'off-chart'; break; }
775
+ const to = palaces.find((x) => x.branch === BRANCHES[b]);
776
+ steps.push({ from: cur.palace, fromBranch: cur.branch, fromStem: cur.stem, star, type, to: to.palace, toBranch: to.branch });
777
+ if (seen.has(to.palace)) { ended = 'cycle'; break; }
778
+ seen.add(to.palace);
779
+ cur = to;
780
+ }
781
+ return {
782
+ from: start.palace,
783
+ type,
784
+ steps,
785
+ path: [start.palace, ...steps.map((s) => s.to)],
786
+ ended,
787
+ closed: ended === 'cycle',
788
+ };
789
+ }
790
+
791
+ // 某一宫的四支化各串一条(默认命宫)
792
+ export function flyChains(chart, { from = '命宫', maxSteps = 12 } = {}) {
793
+ const out = {};
794
+ for (const type of FLY_TYPES) out[type] = flyChain(chart, { from, type, maxSteps });
795
+ return out;
796
+ }
797
+
719
798
  // ── 流年四化落宫 ────────────────────────────────────────────
720
799
 
721
800
  export function enrichLiunian(chart) {