dsh-metaphysics 0.14.3 → 0.16.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.
@@ -1,342 +1,339 @@
1
- // 大六壬:天地盘、四课、九宗门三传。
2
- // 算法移植自本地 MIT 授权插件 @dhicoc/dsh-chinese-traditional-wisdom-skill 的
3
- // daliurenEngine.ts(仅取 classic 流派:贵人顺逆以天盘临方判定)。
4
- import { toJDN, dayGanzhiIndex, ganzhiName, termWindow, formatJd } from './calendar.js';
5
-
6
- const GAN = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
7
- const ZHI = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
8
-
9
- const TIAN_JIANG = ['贵', '蛇', '雀', '合', '勾', '龙', '空', '虎', '常', '玄', '阴', '后'];
10
- const JIANG_FULL = {
11
- 贵: '贵人', 蛇: '螣蛇', 雀: '朱雀', 合: '六合', 勾: '勾陈', 龙: '青龙',
12
- 空: '天空', 虎: '白虎', 常: '太常', 玄: '玄武', 阴: '太阴', 后: '天后',
13
- };
14
- const YUE_JIANG_NAME = {
15
- 亥: '登明', 戌: '河魁', 酉: '从魁', 申: '传送', 未: '小吉', 午: '胜光',
16
- 巳: '太乙', 辰: '天罡', 卯: '太冲', 寅: '功曹', 丑: '大吉', 子: '神后',
17
- };
18
-
19
- const GAN_WUXING = { 甲: '木', 乙: '木', 丙: '火', 丁: '火', 戊: '土', 己: '土', 庚: '金', 辛: '金', 壬: '水', 癸: '水' };
20
- const ZHI_WUXING = { 子: '水', 丑: '土', 寅: '木', 卯: '木', 辰: '土', 巳: '火', 午: '火', 未: '土', 申: '金', 酉: '金', 戌: '土', 亥: '水' };
21
-
22
- const SHENG = { 金: '水', 水: '木', 木: '火', 火: '土', 土: '金' };
23
- const KE = { 金: '木', 木: '土', 土: '水', 水: '火', 火: '金' };
24
-
25
- function wuxingRelation(shang, xia) {
26
- if (shang === xia) return '比和';
27
- if (KE[shang] === xia) return '克';
28
- if (KE[xia] === shang) return '被克';
29
- if (SHENG[shang] === xia) return '生';
30
- if (SHENG[xia] === shang) return '被生';
31
- return '比和';
32
- }
33
-
34
- const LIUQIN_MAP = { 被生: '父母', 生: '子孙', 克: '妻财', 比和: '兄弟', 被克: '官鬼' };
35
-
36
- const GAN_JI_GONG = { 甲: '寅', 乙: '辰', 丙: '巳', 丁: '未', 戊: '巳', 己: '未', 庚: '申', 辛: '戌', 壬: '亥', 癸: '丑' };
37
-
38
- const YANG_POS = ['巳', '午', '未', '申', '酉', '戌'];
39
-
40
- const JIEQI_YUE_JIANG = {
41
- 雨水: '亥', 惊蛰: '亥', 春分: '戌', 清明: '戌', 谷雨: '酉', 立夏: '酉',
42
- 小满: '申', 芒种: '申', 夏至: '未', 小暑: '未', 大暑: '午', 立秋: '午',
43
- 处暑: '巳', 白露: '巳', 秋分: '辰', 寒露: '辰', 霜降: '卯', 立冬: '卯',
44
- 小雪: '寅', 大雪: '寅', 冬至: '丑', 小寒: '丑', 大寒: '子', 立春: '子',
45
- };
46
-
47
- const GUIREN_START = {
48
- 甲: { day: '丑', night: '未' }, 戊: { day: '丑', night: '未' }, 庚: { day: '丑', night: '未' },
49
- 乙: { day: '子', night: '申' }, 己: { day: '子', night: '申' },
50
- 丙: { day: '亥', night: '酉' }, 丁: { day: '亥', night: '酉' },
51
- 壬: { day: '巳', night: '卯' }, 癸: { day: '巳', night: '卯' },
52
- 辛: { day: '午', night: '寅' },
53
- };
54
-
55
- const DAY_NIGHT_MAP = {
56
- 卯: '昼', 辰: '昼', 巳: '昼', 午: '昼', 未: '昼', 申: '昼',
57
- 酉: '夜', 戌: '夜', 亥: '夜', 子: '夜', 丑: '夜', 寅: '夜',
58
- };
59
-
60
- // ── 神煞(只收各派无争议、六壬断课常用的几项) ──
61
- const RI_LU = { 甲: '寅', 乙: '卯', 丙: '巳', 丁: '午', 戊: '巳', 己: '午', 庚: '申', 辛: '酉', 壬: '亥', 癸: '子' };
62
- const YANG_REN = { 甲: '卯', 乙: '辰', 丙: '午', 丁: '未', 戊: '午', 己: '未', 庚: '酉', 辛: '戌', 壬: '子', 癸: '丑' };
63
- const SAN_HE = { 申: '水', 子: '水', 辰: '水', 寅: '火', 午: '火', 戌: '火', 巳: '金', 酉: '金', 丑: '金', 亥: '木', 卯: '木', 未: '木' };
64
- const SAN_HE_SHEN = {
65
- 水: { 驿马: '寅', 桃花: '酉', 华盖: '辰', 劫煞: '巳', 亡神: '亥' },
66
- 火: { 驿马: '申', 桃花: '卯', 华盖: '戌', 劫煞: '亥', 亡神: '巳' },
67
- 金: { 驿马: '亥', 桃花: '午', 华盖: '丑', 劫煞: '寅', 亡神: '申' },
68
- 木: { 驿马: '巳', 桃花: '子', 华盖: '未', 劫煞: '申', 亡神: '寅' },
69
- };
70
-
71
- /** 按日干支算神煞 */
72
- export function calcShensha(dayGz) {
73
- const gan = dayGz[0];
74
- const zhi = dayGz[1];
75
- const out = [];
76
- const push = (name, branch, note) => {
77
- if (!out.some((x) => x.name === name)) out.push({ name, branch, note });
78
- };
79
- if (RI_LU[gan]) push('日禄', RI_LU[gan], '日干临官之位');
80
- if (YANG_REN[gan]) push('羊刃', YANG_REN[gan], '日干帝旺之位');
81
- const trio = SAN_HE_SHEN[SAN_HE[zhi]];
82
- if (trio) {
83
- push('驿马', trio.驿马, '日支三合冲位,主迁动');
84
- push('桃花', trio.桃花, '咸池,主情缘');
85
- push('华盖', trio.华盖, '主孤高、技艺');
86
- push('劫煞', trio.劫煞, '主意外耗损');
87
- push('亡神', trio.亡神, '主失脱、心神不定');
88
- }
89
- return out;
90
- }
91
-
92
- const XUN_KONG = {
93
- 甲子: ['戌', '亥'], 甲戌: ['申', '酉'], 甲申: ['午', '未'],
94
- 甲午: ['辰', '巳'], 甲辰: ['寅', '卯'], 甲寅: ['子', '丑'],
95
- };
96
-
97
- const JIA_ZI_60 = Array.from({ length: 60 }, (_, i) => GAN[i % 10] + ZHI[i % 12]);
98
-
99
- const ZHI_CHONG = { 子: '午', 午: '子', 丑: '未', 未: '丑', 寅: '申', 申: '寅', 卯: '酉', 酉: '卯', 辰: '戌', 戌: '辰', 巳: '亥', 亥: '巳' };
100
- const YI_MA = { 寅: '申', 午: '申', 戌: '申', 申: '寅', 子: '寅', 辰: '寅', 巳: '亥', 酉: '亥', 丑: '亥', 亥: '巳', 卯: '巳', 未: '巳' };
101
-
102
- const BA_ZHUAN_DAYS = ['壬子', '甲寅', '乙卯', '丁巳', '己未', '庚申', '辛酉', '癸亥'];
103
- const GAN_ZHI_SAME_POSITION = ['甲寅', '丁未', '己未', '庚申', '癸丑'];
104
-
105
- const yinYang = (s) => (['甲', '丙', '戊', '庚', '壬'].includes(s) || ['子', '寅', '辰', '午', '申', '戌'].includes(s) ? '阳' : '阴');
106
-
107
- function tianDiPan(jieqiName, hourZhi, dayGan) {
108
- const yueJiang = JIEQI_YUE_JIANG[jieqiName] ?? '子';
109
- const hourIndex = ZHI.indexOf(hourZhi);
110
- const yueJiangIndex = ZHI.indexOf(yueJiang);
111
- const tianPan = new Array(12);
112
- for (let i = 0; i < 12; i++) {
113
- tianPan[(hourIndex + i) % 12] = ZHI[(yueJiangIndex + i) % 12];
114
- }
115
- const dayNight = DAY_NIGHT_MAP[hourZhi] ?? '昼';
116
- const cfg = GUIREN_START[dayGan];
117
- const guirenStart = dayNight === '昼' ? cfg.day : cfg.night;
118
- const guirenIndex = ZHI.indexOf(guirenStart);
119
- const guirenTianZhi = tianPan[guirenIndex];
120
- const isReverse = YANG_POS.includes(guirenTianZhi);
121
- const tianJiang = new Array(12);
122
- if (isReverse) {
123
- const rev = ['贵', '后', '阴', '玄', '常', '虎', '空', '龙', '勾', '合', '雀', '蛇'];
124
- for (let i = 0; i < 12; i++) tianJiang[(guirenIndex + i) % 12] = rev[i];
125
- } else {
126
- for (let i = 0; i < 12; i++) tianJiang[(guirenIndex + i) % 12] = TIAN_JIANG[i];
127
- }
128
- const diToTian = {};
129
- const diToJiang = {};
130
- for (let i = 0; i < 12; i++) {
131
- diToTian[ZHI[i]] = tianPan[i];
132
- diToJiang[ZHI[i]] = tianJiang[i];
133
- }
134
- return { tianPan, diToTian, diToJiang, yueJiang, dayNight, guirenStart, isReverse };
135
- }
136
-
137
- function keRelation(shang, xia) {
138
- const sw = ZHI_WUXING[shang] ?? GAN_WUXING[shang] ?? '土';
139
- const xw = ZHI_WUXING[xia] ?? GAN_WUXING[xia] ?? '土';
140
- const rel = wuxingRelation(sw, xw);
141
- return { 克: '上克下', 被克: '下贼上', 生: '上生下', 被生: '下生上', 比和: '比和' }[rel] ?? '比和';
142
- }
143
-
144
- function siKe(dayGan, dayZhi, tdp) {
145
- const { diToTian, diToJiang } = tdp;
146
- const jiGong = GAN_JI_GONG[dayGan];
147
- const yi = diToTian[jiGong];
148
- const er = diToTian[yi];
149
- const san = diToTian[dayZhi];
150
- const si = diToTian[san];
151
- const mk = (pos, shang, xia) => ({
152
- position: pos,
153
- shang,
154
- xia,
155
- jiang: diToJiang[GAN_JI_GONG[xia] ?? xia] ?? '',
156
- relation: keRelation(shang, xia),
157
- });
158
- return [mk(1, yi, dayGan), mk(2, er, yi), mk(3, san, dayZhi), mk(4, si, san)];
159
- }
160
-
161
- function analyze(ke) {
162
- const s = { shangKeXia: 0, xiaZeiShang: 0 };
163
- for (const k of ke) {
164
- if (k.relation === '上克下') s.shangKeXia++;
165
- else if (k.relation === '下贼上') s.xiaZeiShang++;
166
- }
167
- return s;
168
- }
169
-
170
- function xunKong(dayGz, zhi) {
171
- const idx = JIA_ZI_60.indexOf(dayGz);
172
- if (idx < 0) return null;
173
- const head = JIA_ZI_60[Math.floor(idx / 10) * 10];
174
- return (XUN_KONG[head] || []).includes(zhi) ? zhi : null;
175
- }
176
-
177
- function chuan(pos, zhi, tdp, dayGan, dayGz) {
178
- const rel = wuxingRelation(GAN_WUXING[dayGan] ?? '土', ZHI_WUXING[zhi] ?? '土');
179
- return {
180
- position: pos,
181
- zhi,
182
- jiang: tdp.diToJiang[zhi] ?? '',
183
- jiangFull: JIANG_FULL[tdp.diToJiang[zhi]] ?? '',
184
- liuQin: LIUQIN_MAP[rel] ?? '兄弟',
185
- xunKong: xunKong(dayGz, zhi),
186
- };
187
- }
188
-
189
- function checkFanYin(tdp) {
190
- let cnt = 0;
191
- for (let i = 0; i < 12; i++) if (ZHI_CHONG[ZHI[i]] === tdp.diToTian[ZHI[i]]) cnt++;
192
- return cnt >= 6;
193
- }
194
-
195
- function sheHaiDepth(shang, xia) {
196
- const sw = ZHI_WUXING[shang] ?? '土';
197
- const start = ZHI.indexOf(xia);
198
- const end = ZHI.indexOf(shang);
199
- let cnt = 0;
200
- let cur = start;
201
- while (cur !== end) {
202
- if (wuxingRelation(sw, ZHI_WUXING[ZHI[cur]]) === '克') cnt++;
203
- cur = (cur - 1 + 12) % 12;
204
- }
205
- return cnt;
206
- }
207
-
208
- function multipleZeiKe(ke, dayGan, target) {
209
- const m = ke.filter((k) => k.relation === target);
210
- if (m.length === 1) return { zhi: m[0].shang, geJu: '贼克', detail: target === '下贼上' ? '重审' : '元首' };
211
- const yy = yinYang(dayGan);
212
- const same = m.filter((k) => yinYang(k.shang) === yy);
213
- if (same.length === 1) return { zhi: same[0].shang, geJu: '比用', detail: '知一' };
214
- const pool = same.length ? same : m;
215
- const ranked = pool.map((k) => ({ k, d: sheHaiDepth(k.shang, k.xia) })).sort((a, b) => b.d - a.d);
216
- const tied = ranked.length >= 2 && ranked[0].d === ranked[1].d;
217
- return { zhi: ranked[0].k.shang, geJu: '涉害', detail: tied ? '察微' : '见机' };
218
- }
219
-
220
- function noZeiKe(ke, tdp, dayGan) {
221
- const dw = GAN_WUXING[dayGan] ?? '土';
222
- for (const k of ke) {
223
- const rel = wuxingRelation(ZHI_WUXING[k.shang] ?? '土', dw);
224
- if (rel === '克') return { zhi: k.shang, geJu: '遥克', detail: '蒿矢' };
225
- if (rel === '被克') return { zhi: k.shang, geJu: '遥克', detail: '弹射' };
226
- }
227
- return { zhi: tdp.diToTian['酉'], geJu: '昴星', detail: yinYang(dayGan) === '阳' ? '虎视' : '冬蛇掩目' };
228
- }
229
-
230
- function fanYin(ke, dayGan, dayZhi, stats, isFB) {
231
- if (isFB) return { zhi: YI_MA[dayZhi] ?? '寅', detail: '绝嗣' };
232
- for (const rel of ['下贼上', '上克下']) {
233
- const m = ke.filter((k) => k.relation === rel);
234
- if (m.length) {
235
- const yy = yinYang(dayGan);
236
- const same = m.filter((k) => yinYang(k.shang) === yy);
237
- return { zhi: (same[0] ?? m[0]).shang, detail: '无依' };
238
- }
239
- }
240
- return { zhi: YI_MA[dayZhi] ?? '寅', detail: '无亲' };
241
- }
242
-
243
- /**
244
- * 排大六壬课
245
- * @param {object} input { year, month, day, hour, minute, lateZi? }
246
- */
247
- export function buildDaliuren(input) {
248
- const { year, month, day, hour = 0, minute = 0 } = input;
249
- const lateZi = input.lateZi !== false;
250
- const jdnBase = toJDN(year, month, day);
251
- const jdn = lateZi && hour >= 23 ? jdnBase + 1 : jdnBase;
252
- const dayIdx = dayGanzhiIndex(jdn);
253
- const dayGz = ganzhiName(dayIdx).name;
254
- const dayGan = dayGz[0];
255
- const dayZhi = dayGz[1];
256
- const hourZhi = ZHI[Math.floor(((hour + 1) % 24) / 2)];
257
-
258
- // 月将:取出生时刻之前最近的节气(中气换将)
259
- const jd = jdnBase - 0.5 + (hour * 60 + minute) / 1440;
260
- const terms = termWindow(year).filter((t) => t.jd <= jd);
261
- const last = terms.length ? terms[terms.length - 1] : null;
262
- const jieqiName = last ? last.name : '冬至';
263
-
264
- const tdp = tianDiPan(jieqiName, hourZhi, dayGan);
265
- const ke = siKe(dayGan, dayZhi, tdp);
266
-
267
- const stats = analyze(ke);
268
- const isFuYin = tdp.yueJiang === hourZhi;
269
- const isFanYin = checkFanYin(tdp);
270
- const isBaZhuan = BA_ZHUAN_DAYS.includes(dayGz);
271
- const isSamePos = GAN_ZHI_SAME_POSITION.includes(dayGz);
272
- const isFanYinBaZhuan = isSamePos && stats.xiaZeiShang === 4;
273
-
274
- let chu;
275
- let geJu;
276
- let detail;
277
- if (isFanYin || isFanYinBaZhuan) {
278
- const r = fanYin(ke, dayGan, dayZhi, stats, isFanYinBaZhuan);
279
- chu = r.zhi; geJu = '返吟'; detail = r.detail;
280
- } else if (isFuYin) {
281
- chu = yinYang(dayGan) === '阳' ? GAN_JI_GONG[dayGan] : dayZhi;
282
- geJu = '伏吟'; detail = yinYang(dayGan) === '阳' ? '自任' : '杜传';
283
- } else if (isBaZhuan && stats.shangKeXia === 0 && stats.xiaZeiShang === 0) {
284
- const i = ZHI.indexOf(dayZhi);
285
- chu = ZHI[(i + (yinYang(dayGan) === '阳' ? 2 : -2) + 12) % 12];
286
- geJu = '八专'; detail = '帷簿';
287
- } else if (stats.xiaZeiShang === 1 && stats.shangKeXia === 0) {
288
- chu = ke.find((k) => k.relation === '下贼上').shang; geJu = '贼克'; detail = '重审';
289
- } else if (stats.shangKeXia === 1 && stats.xiaZeiShang === 0) {
290
- chu = ke.find((k) => k.relation === '上克下').shang; geJu = '贼克'; detail = '元首';
291
- } else if (stats.xiaZeiShang >= 1) {
292
- const r = multipleZeiKe(ke, dayGan, '下贼上');
293
- chu = r.zhi; geJu = r.geJu; detail = r.detail;
294
- } else if (stats.shangKeXia >= 2) {
295
- const r = multipleZeiKe(ke, dayGan, '上克下');
296
- chu = r.zhi; geJu = r.geJu; detail = r.detail;
297
- } else if (stats.shangKeXia === 0 && stats.xiaZeiShang === 0) {
298
- const r = noZeiKe(ke, tdp, dayGan);
299
- chu = r.zhi; geJu = r.geJu; detail = r.detail;
300
- } else {
301
- chu = ke[0].shang; geJu = '贼克'; detail = '元首';
302
- }
303
-
304
- let zhong;
305
- let mo;
306
- if (isFanYin) {
307
- zhong = ZHI_CHONG[chu];
308
- mo = ZHI_CHONG[zhong];
309
- } else {
310
- zhong = tdp.diToTian[chu];
311
- mo = tdp.diToTian[zhong];
312
- }
313
-
314
- return {
315
- system: 'daliuren',
316
- input: { year, month, day, hour, minute },
317
- dayGanZhi: dayGz,
318
- hourZhi,
319
- dayNight: tdp.dayNight,
320
- jieqi: { name: jieqiName, time: last ? formatJd(last.jd) : null },
321
- yueJiang: { zhi: tdp.yueJiang, name: YUE_JIANG_NAME[tdp.yueJiang] },
322
- guiren: { zhi: tdp.guirenStart, reverse: tdp.isReverse },
323
- pan: ZHI.map((d, i) => ({
324
- di: d,
325
- tian: tdp.tianPan[i],
326
- jiang: tdp.diToJiang[d],
327
- jiangFull: JIANG_FULL[tdp.diToJiang[d]],
328
- })),
329
- siKe: ke.map((k) => ({ ...k, jiangFull: JIANG_FULL[k.jiang] ?? '' })),
330
- sanChuan: {
331
- chu: chuan('初传', chu, tdp, dayGan, dayGz),
332
- zhong: chuan('中传', zhong, tdp, dayGan, dayGz),
333
- mo: chuan('末传', mo, tdp, dayGan, dayGz),
334
- },
335
- shensha: calcShensha(dayGz),
336
- geJu: { name: geJu, detail },
337
- };
338
- }
339
-
340
- export { ZHI, GAN, TIAN_JIANG, JIANG_FULL, JIEQI_YUE_JIANG, GAN_JI_GONG };
341
-
342
-
1
+ // 大六壬:天地盘、四课、九宗门三传。
2
+ // 算法移植自本地 MIT 授权插件 @dhicoc/dsh-chinese-traditional-wisdom-skill 的
3
+ // daliurenEngine.ts(仅取 classic 流派:贵人顺逆以天盘临方判定)。
4
+ import { toJDN, dayGanzhiIndex, ganzhiName, termWindow, formatJd, xunKongOf } from './calendar.js';
5
+ import { assertCivil } from './validate.js';
6
+ import { SANHE_ELEMENT } from './sanhe.js';
7
+
8
+ const GAN = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
9
+ const ZHI = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
10
+
11
+ const TIAN_JIANG = ['贵', '蛇', '雀', '合', '勾', '龙', '空', '虎', '常', '玄', '阴', '后'];
12
+ const JIANG_FULL = {
13
+ 贵: '贵人', 蛇: '螣蛇', 雀: '朱雀', 合: '六合', 勾: '勾陈', 龙: '青龙',
14
+ 空: '天空', 虎: '白虎', 常: '太常', 玄: '玄武', 阴: '太阴', 后: '天后',
15
+ };
16
+ const YUE_JIANG_NAME = {
17
+ 亥: '登明', 戌: '河魁', 酉: '从魁', 申: '传送', 未: '小吉', 午: '胜光',
18
+ 巳: '太乙', 辰: '天罡', 卯: '太冲', 寅: '功曹', 丑: '大吉', 子: '神后',
19
+ };
20
+
21
+ const GAN_WUXING = { 甲: '木', 乙: '木', 丙: '火', 丁: '火', 戊: '土', 己: '土', 庚: '金', 辛: '金', 壬: '水', 癸: '水' };
22
+ const ZHI_WUXING = { 子: '水', 丑: '土', 寅: '木', 卯: '木', 辰: '土', 巳: '火', 午: '火', 未: '土', 申: '金', 酉: '金', 戌: '土', 亥: '水' };
23
+
24
+ const SHENG = { 金: '水', 水: '木', 木: '火', 火: '土', 土: '金' };
25
+ const KE = { 金: '木', 木: '土', 土: '水', 水: '火', 火: '金' };
26
+
27
+ function wuxingRelation(shang, xia) {
28
+ if (shang === xia) return '比和';
29
+ if (KE[shang] === xia) return '克';
30
+ if (KE[xia] === shang) return '被克';
31
+ if (SHENG[shang] === xia) return '生';
32
+ if (SHENG[xia] === shang) return '被生';
33
+ return '比和';
34
+ }
35
+
36
+ const LIUQIN_MAP = { 被生: '父母', 生: '子孙', 克: '妻财', 比和: '兄弟', 被克: '官鬼' };
37
+
38
+ const GAN_JI_GONG = { 甲: '寅', 乙: '辰', 丙: '巳', 丁: '未', 戊: '巳', 己: '未', 庚: '申', 辛: '戌', 壬: '亥', 癸: '丑' };
39
+
40
+ const YANG_POS = ['巳', '午', '未', '申', '酉', '戌'];
41
+
42
+ const JIEQI_YUE_JIANG = {
43
+ 雨水: '亥', 惊蛰: '亥', 春分: '戌', 清明: '戌', 谷雨: '酉', 立夏: '酉',
44
+ 小满: '申', 芒种: '申', 夏至: '未', 小暑: '未', 大暑: '午', 立秋: '午',
45
+ 处暑: '巳', 白露: '巳', 秋分: '辰', 寒露: '辰', 霜降: '卯', 立冬: '卯',
46
+ 小雪: '寅', 大雪: '寅', 冬至: '丑', 小寒: '丑', 大寒: '子', 立春: '子',
47
+ };
48
+
49
+ const GUIREN_START = {
50
+ 甲: { day: '丑', night: '未' }, 戊: { day: '丑', night: '未' }, 庚: { day: '丑', night: '未' },
51
+ 乙: { day: '子', night: '申' }, 己: { day: '子', night: '申' },
52
+ 丙: { day: '亥', night: '酉' }, 丁: { day: '亥', night: '酉' },
53
+ 壬: { day: '巳', night: '卯' }, 癸: { day: '巳', night: '卯' },
54
+ 辛: { day: '午', night: '寅' },
55
+ };
56
+
57
+ const DAY_NIGHT_MAP = {
58
+ 卯: '昼', 辰: '昼', 巳: '昼', 午: '昼', 未: '昼', 申: '昼',
59
+ 酉: '夜', 戌: '夜', 亥: '夜', 子: '夜', 丑: '夜', 寅: '夜',
60
+ };
61
+
62
+ // ── 神煞(只收各派无争议、六壬断课常用的几项) ──
63
+ const RI_LU = { 甲: '寅', 乙: '卯', 丙: '巳', 丁: '午', 戊: '巳', 己: '午', 庚: '申', 辛: '酉', 壬: '亥', 癸: '子' };
64
+ const YANG_REN = { 甲: '卯', 乙: '辰', 丙: '午', 丁: '未', 戊: '午', 己: '未', 庚: '酉', 辛: '戌', 壬: '子', 癸: '丑' };
65
+ const SAN_HE_SHEN = {
66
+ 水: { 驿马: '寅', 桃花: '酉', 华盖: '辰', 劫煞: '巳', 亡神: '亥' },
67
+ 火: { 驿马: '申', 桃花: '卯', 华盖: '戌', 劫煞: '亥', 亡神: '巳' },
68
+ 金: { 驿马: '亥', 桃花: '午', 华盖: '丑', 劫煞: '寅', 亡神: '申' },
69
+ 木: { 驿马: '巳', 桃花: '子', 华盖: '未', 劫煞: '申', 亡神: '寅' },
70
+ };
71
+
72
+ /** 按日干支算神煞 */
73
+ export function calcShensha(dayGz) {
74
+ const gan = dayGz[0];
75
+ const zhi = dayGz[1];
76
+ const out = [];
77
+ const push = (name, branch, note) => {
78
+ if (!out.some((x) => x.name === name)) out.push({ name, branch, note });
79
+ };
80
+ if (RI_LU[gan]) push('日禄', RI_LU[gan], '日干临官之位');
81
+ if (YANG_REN[gan]) push('羊刃', YANG_REN[gan], '日干帝旺之位');
82
+ const trio = SAN_HE_SHEN[SANHE_ELEMENT[zhi]];
83
+ if (trio) {
84
+ push('驿马', trio.驿马, '日支三合冲位,主迁动');
85
+ push('桃花', trio.桃花, '咸池,主情缘');
86
+ push('华盖', trio.华盖, '主孤高、技艺');
87
+ push('劫煞', trio.劫煞, '主意外耗损');
88
+ push('亡神', trio.亡神, '主失脱、心神不定');
89
+ }
90
+ return out;
91
+ }
92
+
93
+ // 旬空不再自建表:统一调用 calendar.xunKongOf(原表与本模块各一份,属三处重复实现之一)
94
+
95
+ const JIA_ZI_60 = Array.from({ length: 60 }, (_, i) => GAN[i % 10] + ZHI[i % 12]);
96
+
97
+ const ZHI_CHONG = { 子: '午', 午: '子', 丑: '未', 未: '丑', 寅: '申', 申: '寅', 卯: '酉', 酉: '卯', 辰: '戌', 戌: '辰', 巳: '亥', 亥: '巳' };
98
+ const YI_MA = { 寅: '申', 午: '申', 戌: '申', 申: '寅', 子: '寅', 辰: '寅', 巳: '亥', 酉: '亥', 丑: '亥', 亥: '巳', 卯: '巳', 未: '巳' };
99
+
100
+ const BA_ZHUAN_DAYS = ['壬子', '甲寅', '乙卯', '丁巳', '己未', '庚申', '辛酉', '癸亥'];
101
+ const GAN_ZHI_SAME_POSITION = ['甲寅', '丁未', '己未', '庚申', '癸丑'];
102
+
103
+ const yinYang = (s) => (['甲', '丙', '戊', '庚', '壬'].includes(s) || ['子', '寅', '辰', '午', '申', '戌'].includes(s) ? '阳' : '阴');
104
+
105
+ function tianDiPan(jieqiName, hourZhi, dayGan) {
106
+ const yueJiang = JIEQI_YUE_JIANG[jieqiName] ?? '子';
107
+ const hourIndex = ZHI.indexOf(hourZhi);
108
+ const yueJiangIndex = ZHI.indexOf(yueJiang);
109
+ const tianPan = new Array(12);
110
+ for (let i = 0; i < 12; i++) {
111
+ tianPan[(hourIndex + i) % 12] = ZHI[(yueJiangIndex + i) % 12];
112
+ }
113
+ const dayNight = DAY_NIGHT_MAP[hourZhi] ?? '昼';
114
+ const cfg = GUIREN_START[dayGan];
115
+ const guirenStart = dayNight === '昼' ? cfg.day : cfg.night;
116
+ const guirenIndex = ZHI.indexOf(guirenStart);
117
+ const guirenTianZhi = tianPan[guirenIndex];
118
+ const isReverse = YANG_POS.includes(guirenTianZhi);
119
+ const tianJiang = new Array(12);
120
+ if (isReverse) {
121
+ const rev = ['贵', '后', '阴', '玄', '常', '虎', '空', '龙', '勾', '合', '雀', '蛇'];
122
+ for (let i = 0; i < 12; i++) tianJiang[(guirenIndex + i) % 12] = rev[i];
123
+ } else {
124
+ for (let i = 0; i < 12; i++) tianJiang[(guirenIndex + i) % 12] = TIAN_JIANG[i];
125
+ }
126
+ const diToTian = {};
127
+ const diToJiang = {};
128
+ for (let i = 0; i < 12; i++) {
129
+ diToTian[ZHI[i]] = tianPan[i];
130
+ diToJiang[ZHI[i]] = tianJiang[i];
131
+ }
132
+ return { tianPan, diToTian, diToJiang, yueJiang, dayNight, guirenStart, isReverse };
133
+ }
134
+
135
+ function keRelation(shang, xia) {
136
+ const sw = ZHI_WUXING[shang] ?? GAN_WUXING[shang] ?? '土';
137
+ const xw = ZHI_WUXING[xia] ?? GAN_WUXING[xia] ?? '土';
138
+ const rel = wuxingRelation(sw, xw);
139
+ return { 克: '上克下', 被克: '下贼上', 生: '上生下', 被生: '下生上', 比和: '比和' }[rel] ?? '比和';
140
+ }
141
+
142
+ function siKe(dayGan, dayZhi, tdp) {
143
+ const { diToTian, diToJiang } = tdp;
144
+ const jiGong = GAN_JI_GONG[dayGan];
145
+ const yi = diToTian[jiGong];
146
+ const er = diToTian[yi];
147
+ const san = diToTian[dayZhi];
148
+ const si = diToTian[san];
149
+ const mk = (pos, shang, xia) => ({
150
+ position: pos,
151
+ shang,
152
+ xia,
153
+ jiang: diToJiang[GAN_JI_GONG[xia] ?? xia] ?? '',
154
+ relation: keRelation(shang, xia),
155
+ });
156
+ return [mk(1, yi, dayGan), mk(2, er, yi), mk(3, san, dayZhi), mk(4, si, san)];
157
+ }
158
+
159
+ function analyze(ke) {
160
+ const s = { shangKeXia: 0, xiaZeiShang: 0 };
161
+ for (const k of ke) {
162
+ if (k.relation === '上克下') s.shangKeXia++;
163
+ else if (k.relation === '下贼上') s.xiaZeiShang++;
164
+ }
165
+ return s;
166
+ }
167
+
168
+ function xunKong(dayGz, zhi) {
169
+ // 该支是否落空:由日干支推旬空两支后比对
170
+ return xunKongOf(dayGz).includes(zhi) ? zhi : null;
171
+ }
172
+
173
+ function chuan(pos, zhi, tdp, dayGan, dayGz) {
174
+ const rel = wuxingRelation(GAN_WUXING[dayGan] ?? '土', ZHI_WUXING[zhi] ?? '土');
175
+ return {
176
+ position: pos,
177
+ zhi,
178
+ jiang: tdp.diToJiang[zhi] ?? '',
179
+ jiangFull: JIANG_FULL[tdp.diToJiang[zhi]] ?? '',
180
+ liuQin: LIUQIN_MAP[rel] ?? '兄弟',
181
+ xunKong: xunKong(dayGz, zhi),
182
+ };
183
+ }
184
+
185
+ function checkFanYin(tdp) {
186
+ let cnt = 0;
187
+ for (let i = 0; i < 12; i++) if (ZHI_CHONG[ZHI[i]] === tdp.diToTian[ZHI[i]]) cnt++;
188
+ return cnt >= 6;
189
+ }
190
+
191
+ function sheHaiDepth(shang, xia) {
192
+ const sw = ZHI_WUXING[shang] ?? '土';
193
+ const start = ZHI.indexOf(xia);
194
+ const end = ZHI.indexOf(shang);
195
+ let cnt = 0;
196
+ let cur = start;
197
+ while (cur !== end) {
198
+ if (wuxingRelation(sw, ZHI_WUXING[ZHI[cur]]) === '克') cnt++;
199
+ cur = (cur - 1 + 12) % 12;
200
+ }
201
+ return cnt;
202
+ }
203
+
204
+ function multipleZeiKe(ke, dayGan, target) {
205
+ const m = ke.filter((k) => k.relation === target);
206
+ if (m.length === 1) return { zhi: m[0].shang, geJu: '贼克', detail: target === '下贼上' ? '重审' : '元首' };
207
+ const yy = yinYang(dayGan);
208
+ const same = m.filter((k) => yinYang(k.shang) === yy);
209
+ if (same.length === 1) return { zhi: same[0].shang, geJu: '比用', detail: '知一' };
210
+ const pool = same.length ? same : m;
211
+ const ranked = pool.map((k) => ({ k, d: sheHaiDepth(k.shang, k.xia) })).sort((a, b) => b.d - a.d);
212
+ const tied = ranked.length >= 2 && ranked[0].d === ranked[1].d;
213
+ return { zhi: ranked[0].k.shang, geJu: '涉害', detail: tied ? '察微' : '见机' };
214
+ }
215
+
216
+ function noZeiKe(ke, tdp, dayGan) {
217
+ const dw = GAN_WUXING[dayGan] ?? '土';
218
+ for (const k of ke) {
219
+ const rel = wuxingRelation(ZHI_WUXING[k.shang] ?? '土', dw);
220
+ if (rel === '克') return { zhi: k.shang, geJu: '遥克', detail: '蒿矢' };
221
+ if (rel === '被克') return { zhi: k.shang, geJu: '遥克', detail: '弹射' };
222
+ }
223
+ return { zhi: tdp.diToTian['酉'], geJu: '昴星', detail: yinYang(dayGan) === '阳' ? '虎视' : '冬蛇掩目' };
224
+ }
225
+
226
+ function fanYin(ke, dayGan, dayZhi, stats, isFB) {
227
+ if (isFB) return { zhi: YI_MA[dayZhi] ?? '寅', detail: '绝嗣' };
228
+ for (const rel of ['下贼上', '上克下']) {
229
+ const m = ke.filter((k) => k.relation === rel);
230
+ if (m.length) {
231
+ const yy = yinYang(dayGan);
232
+ const same = m.filter((k) => yinYang(k.shang) === yy);
233
+ return { zhi: (same[0] ?? m[0]).shang, detail: '无依' };
234
+ }
235
+ }
236
+ return { zhi: YI_MA[dayZhi] ?? '寅', detail: '无亲' };
237
+ }
238
+
239
+ /**
240
+ * 排大六壬课
241
+ * @param {object} input { year, month, day, hour, minute, lateZi? }
242
+ */
243
+ export function buildDaliuren(input) {
244
+ assertCivil(input);
245
+ const { year, month, day, hour = 0, minute = 0 } = input;
246
+ const lateZi = input.lateZi !== false;
247
+ const jdnBase = toJDN(year, month, day);
248
+ const jdn = lateZi && hour >= 23 ? jdnBase + 1 : jdnBase;
249
+ const dayIdx = dayGanzhiIndex(jdn);
250
+ const dayGz = ganzhiName(dayIdx).name;
251
+ const dayGan = dayGz[0];
252
+ const dayZhi = dayGz[1];
253
+ const hourZhi = ZHI[Math.floor(((hour + 1) % 24) / 2)];
254
+
255
+ // 月将:取出生时刻之前最近的节气(中气换将)
256
+ const jd = jdnBase - 0.5 + (hour * 60 + minute) / 1440;
257
+ const terms = termWindow(year).filter((t) => t.jd <= jd);
258
+ const last = terms.length ? terms[terms.length - 1] : null;
259
+ const jieqiName = last ? last.name : '冬至';
260
+
261
+ const tdp = tianDiPan(jieqiName, hourZhi, dayGan);
262
+ const ke = siKe(dayGan, dayZhi, tdp);
263
+
264
+ const stats = analyze(ke);
265
+ const isFuYin = tdp.yueJiang === hourZhi;
266
+ const isFanYin = checkFanYin(tdp);
267
+ const isBaZhuan = BA_ZHUAN_DAYS.includes(dayGz);
268
+ const isSamePos = GAN_ZHI_SAME_POSITION.includes(dayGz);
269
+ const isFanYinBaZhuan = isSamePos && stats.xiaZeiShang === 4;
270
+
271
+ let chu;
272
+ let geJu;
273
+ let detail;
274
+ if (isFanYin || isFanYinBaZhuan) {
275
+ const r = fanYin(ke, dayGan, dayZhi, stats, isFanYinBaZhuan);
276
+ chu = r.zhi; geJu = '返吟'; detail = r.detail;
277
+ } else if (isFuYin) {
278
+ chu = yinYang(dayGan) === '阳' ? GAN_JI_GONG[dayGan] : dayZhi;
279
+ geJu = '伏吟'; detail = yinYang(dayGan) === '阳' ? '自任' : '杜传';
280
+ } else if (isBaZhuan && stats.shangKeXia === 0 && stats.xiaZeiShang === 0) {
281
+ const i = ZHI.indexOf(dayZhi);
282
+ chu = ZHI[(i + (yinYang(dayGan) === '阳' ? 2 : -2) + 12) % 12];
283
+ geJu = '八专'; detail = '帷簿';
284
+ } else if (stats.xiaZeiShang === 1 && stats.shangKeXia === 0) {
285
+ chu = ke.find((k) => k.relation === '下贼上').shang; geJu = '贼克'; detail = '重审';
286
+ } else if (stats.shangKeXia === 1 && stats.xiaZeiShang === 0) {
287
+ chu = ke.find((k) => k.relation === '上克下').shang; geJu = '贼克'; detail = '元首';
288
+ } else if (stats.xiaZeiShang >= 1) {
289
+ const r = multipleZeiKe(ke, dayGan, '下贼上');
290
+ chu = r.zhi; geJu = r.geJu; detail = r.detail;
291
+ } else if (stats.shangKeXia >= 2) {
292
+ const r = multipleZeiKe(ke, dayGan, '上克下');
293
+ chu = r.zhi; geJu = r.geJu; detail = r.detail;
294
+ } else if (stats.shangKeXia === 0 && stats.xiaZeiShang === 0) {
295
+ const r = noZeiKe(ke, tdp, dayGan);
296
+ chu = r.zhi; geJu = r.geJu; detail = r.detail;
297
+ } else {
298
+ chu = ke[0].shang; geJu = '贼克'; detail = '元首';
299
+ }
300
+
301
+ let zhong;
302
+ let mo;
303
+ if (isFanYin) {
304
+ zhong = ZHI_CHONG[chu];
305
+ mo = ZHI_CHONG[zhong];
306
+ } else {
307
+ zhong = tdp.diToTian[chu];
308
+ mo = tdp.diToTian[zhong];
309
+ }
310
+
311
+ return {
312
+ system: 'daliuren',
313
+ input: { year, month, day, hour, minute },
314
+ dayGanZhi: dayGz,
315
+ hourZhi,
316
+ dayNight: tdp.dayNight,
317
+ jieqi: { name: jieqiName, time: last ? formatJd(last.jd) : null },
318
+ yueJiang: { zhi: tdp.yueJiang, name: YUE_JIANG_NAME[tdp.yueJiang] },
319
+ guiren: { zhi: tdp.guirenStart, reverse: tdp.isReverse },
320
+ pan: ZHI.map((d, i) => ({
321
+ di: d,
322
+ tian: tdp.tianPan[i],
323
+ jiang: tdp.diToJiang[d],
324
+ jiangFull: JIANG_FULL[tdp.diToJiang[d]],
325
+ })),
326
+ siKe: ke.map((k) => ({ ...k, jiangFull: JIANG_FULL[k.jiang] ?? '' })),
327
+ sanChuan: {
328
+ chu: chuan('初传', chu, tdp, dayGan, dayGz),
329
+ zhong: chuan('中传', zhong, tdp, dayGan, dayGz),
330
+ mo: chuan('末传', mo, tdp, dayGan, dayGz),
331
+ },
332
+ shensha: calcShensha(dayGz),
333
+ geJu: { name: geJu, detail },
334
+ };
335
+ }
336
+
337
+ export { ZHI, GAN, TIAN_JIANG, JIANG_FULL, JIEQI_YUE_JIANG, GAN_JI_GONG };
338
+
339
+