dsh-metaphysics 0.68.0 → 0.70.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/taiyi.js CHANGED
@@ -9,11 +9,22 @@
9
9
  // 6. 文昌:积年 mod 18,阳局自武德(申)起、阴局自吕申(寅)起,顺行十六神,
10
10
  // 遇阴德(乾)、大武(坤)各数两次
11
11
  //
12
- // 没有做的(scope 里写明,别在文案里假装有):计神、始击、定目、主算 / 客算、
13
- // 主客大小将、三基(君基 / 臣基 / 民基)、五福、十精,以及掩迫关囚击格等格局判定。
14
- // 这些各有独立起例,互相对不上时整套盘会自相矛盾,必须单独一轮对着《太乙金镜式经》
15
- // 与《太乙统宗宝鉴》逐条对拍,不在这层混进来。
12
+ // 7. 计神:积年 mod 60 得六十甲子序,从寅起逆数十二支
13
+ // 8. 始击:计神加和德(艮)之宫,天目(文昌)随盘移一位,即始击
14
+ // 9. 主算 / 客算:沿九宫环顺行累加宫数,天目在正宫按本数、在间神先计一,
15
+ // 数到太乙宫前一位为止
16
+ // 10. 大将 = 算数个位(得十置一),参将 = 大将 × 3 的个位
17
+ // 11. 掩、迫、囚、格、关五种位置关系
18
+ //
19
+ // 12. 定目、三基、五福、大游、十精、八种格局、阴阳数和
20
+ // 13. 月计 / 日计 / 时计:按《太乙金镜式经》卷一的积月、积日、时实三套推法
21
+ // 14. 古法吉凶:按卷一《推太公考时法》——八种相犯全无、主客数和,算和顺;
22
+ // 命中任何一犯,古法算相犯。这是古籍的占断口径,不是预测,文案里必须写明。
23
+ //
24
+ // 没有做的(scope 里写明,别在文案里假装有):四神拆成本身与绛宫、明堂、玉堂
25
+ // 十二位全序;胜负断辞与三旗五将。
16
26
 
27
+ import { termInstantJd, toJDN, jdToParts, dayGanzhiIndex, STEMS } from './calendar.js';
17
28
  import { assertCivil } from './validate.js';
18
29
  import { assertObject } from './validate.js';
19
30
 
@@ -71,6 +82,210 @@ export const FIVE_YUAN = ['甲子元', '丙子元', '戊子元', '庚子元', '
71
82
  /** 三理:太乙在每一宫理天、理地、理人各一年 */
72
83
  export const THREE_LI = ['理天', '理地', '理人'];
73
84
 
85
+ /** 《太乙金镜式经》卷一的天文常数:章岁、章月、日法、月法 */
86
+ export const ZHANG_SUI = 657;
87
+ export const ZHANG_YUE = 8726;
88
+ export const RI_FA = 49;
89
+ export const YUE_FA = 2447;
90
+
91
+ /** 二十四局一周 → 宫序(起一宫顺行八宫、不游中五)与入宫第几期 */
92
+ export function palaceFromR24(r24) {
93
+ const k = Math.ceil(r24 / 3);
94
+ const inPalace = r24 - (k - 1) * 3 || 3;
95
+ return { k, palace: YANG_WALK[k - 1], inPalace };
96
+ }
97
+
98
+ /** 从某支起、逆行第 n 步(n 取 1..12,1 就是起点) */
99
+ export function branchWalk(startIdx, step) {
100
+ const idx = (((startIdx - (step - 1)) % 12) + 12) % 12;
101
+ return { idx, branch: BRANCH_NAMES[idx] };
102
+ }
103
+
104
+ /** 十六神顺行第 n 步:遇阴德(乾)、大武(坤)各数两次,故十八步一周 */
105
+ export function sixteenStep(startGod, rest18) {
106
+ const start = SIXTEEN.findIndex((x) => x.god === startGod);
107
+ const steps = [];
108
+ for (let i = 0; i < SIXTEEN.length; i += 1) {
109
+ const cur = SIXTEEN[(start + i) % SIXTEEN.length];
110
+ steps.push(cur);
111
+ if (cur.pos === '乾' || cur.pos === '坤') steps.push(cur);
112
+ }
113
+ return steps[rim(rest18, 18) - 1];
114
+ }
115
+
116
+ /** 月计:积月 = ⌊积年 × 章月 ÷ 章岁⌋,其余数称闰余 */
117
+ export function monthJiOf(jinian) {
118
+ const jiyue = Number((BigInt(jinian) * BigInt(ZHANG_YUE)) / BigInt(ZHANG_SUI));
119
+ const r24 = rim(jiyue, 24);
120
+ const t = palaceFromR24(r24);
121
+ const mu = sixteenStep('武德', rim(jiyue, 18));
122
+ const js = branchWalk(2, rim(rim(jiyue, 60), 12));
123
+ return {
124
+ jiyue,
125
+ taiyi: { rest24: r24, palace: t.palace, palaceName: PALACES[t.palace].name, unitInPalace: t.inPalace },
126
+ mu: { pos: mu.pos, god: mu.god, palace: mu.num },
127
+ jishen: js,
128
+ };
129
+ }
130
+
131
+ /** 日计:积日 = ⌊积月 × 月法 ÷ 日法⌋ */
132
+ export function dayJiOf(jinian) {
133
+ const jiyue = Number((BigInt(jinian) * BigInt(ZHANG_YUE)) / BigInt(ZHANG_SUI));
134
+ const jiri = Number((BigInt(jiyue) * BigInt(YUE_FA)) / BigInt(RI_FA));
135
+ const r24 = rim(jiri, 24);
136
+ const t = palaceFromR24(r24);
137
+ const mu = sixteenStep('武德', rim(jiri, 18));
138
+ const js = branchWalk(2, rim(rim(jiri, 60), 12));
139
+ return {
140
+ jiri,
141
+ taiyi: { rest24: r24, palace: t.palace, palaceName: PALACES[t.palace].name, unitInPalace: t.inPalace },
142
+ mu: { pos: mu.pos, god: mu.god, palace: mu.num },
143
+ jishen: js,
144
+ };
145
+ }
146
+
147
+ /** 时计:以上年冬至为天正、十二时一辰,时实 = (冬至积日 − 1) × 12 + 时辰序 */
148
+ export function hourJiOf(datetime) {
149
+ const { year, month, day, hour } = datetime;
150
+ const wp = jdToParts(termInstantJd(year - 1, 23));
151
+ const winterJdn = toJDN(wp.year, wp.month, wp.day);
152
+ const summerJd = termInstantJd(year, 11);
153
+ const nowJd = toJDN(year, month, day) - 0.5 + (hour * 60) / 1440;
154
+ const dun = nowJd >= summerJd ? 'yin' : 'yang';
155
+ const hIdx = Math.floor(((hour + 1) % 24) / 2);
156
+ const shiShi = (winterJdn - 1) * 12 + hIdx;
157
+ const r24 = rim(shiShi, 24);
158
+ const t = palaceFromR24(r24);
159
+ const start = dun === 'yang' ? 2 : 8;
160
+ const js = branchWalk(start, rim(rim(shiShi, 60), 12));
161
+ return {
162
+ shiShi,
163
+ dun,
164
+ taiyi: { rest24: r24, palace: t.palace, palaceName: PALACES[t.palace].name, unitInPalace: t.inPalace },
165
+ jishen: js,
166
+ note: '时计用现代节气定冬至与夏至,不用唐代气法常数;与卷一的古历口径会有几日差。',
167
+ };
168
+ }
169
+
170
+ /** 古法吉凶:按卷一《推太公考时法》的位置判据,只判「相犯与否」与「数和不和」 */
171
+ export function jiXiongOf(o) {
172
+ const items = (o.geju && o.geju.list) || [];
173
+ const xiangFan = items.length > 0;
174
+ const zhuHe = !!(o.heshu && o.heshu.zhu && o.heshu.zhu.he);
175
+ const keHe = !!(o.heshu && o.heshu.ke && o.heshu.ke.he);
176
+ const he = zhuHe && keHe;
177
+ const gate = (o.gate && o.gate.gate) || '';
178
+ // 卷一「吉道谓开休生三门」,年家只值一门,能判的就到这里
179
+ const jiDao = gate === '开' || gate === '休' || gate === '生';
180
+ // 用词照《太乙金镜式经》:凶、不和、吉、大吉;不自造档位
181
+ let grade = 'ji';
182
+ if (xiangFan) grade = 'xiong';
183
+ else if (!he) grade = 'buhe';
184
+ else if (jiDao) grade = 'daji';
185
+ const label = grade === 'xiong' ? '凶' : grade === 'buhe' ? '不和' : grade === 'daji' ? '大吉' : '吉';
186
+ const basis = xiangFan
187
+ ? '命中' + items.join('、') + ',不满足卷一「上下无掩击迫囚关格对、并不相犯」'
188
+ : !he
189
+ ? '不相犯,但' + (zhuHe ? '客算' : '主算') + '落在不和'
190
+ : jiDao
191
+ ? '阴阳和顺、不相犯,八门又值' + gate + '(卷一所称吉道)'
192
+ : '阴阳和顺、不相犯,八门值' + gate + ',不在开休生之内';
193
+ const text = basis + '。';
194
+ const note = '卷一另有「大利」「利以行兵」「先吉后凶」,要五将与神将同参,这一层没有做。';
195
+ return { xiangFan, items, he, gate, grade, label, text, note };
196
+ }
197
+
198
+ /* ── 神将层(第五层)─────────────────────────────────────────────
199
+ 卷一《推太乙当时法》:「以日度加时因步六壬式,知天乙成败之分」——
200
+ 借六壬十二神看四将与文昌、始击落在吉神还是凶神下。口径由本盘自定,写在这里备查:
201
+ 1) 十二神用六壬序(贵人、螣蛇、朱雀、六合、勾陈、青龙、天后、太阴、玄武、太常、白虎、天空);
202
+ 2) 贵人按日干起(甲戊庚丑未 / 乙己子申 / 丙丁亥酉 / 壬癸巳卯 / 辛午寅),卯至申用昼贵、酉至寅用夜贵;
203
+ 3) 贵人临亥子丑寅卯辰顺行、临巳午未申酉戌逆行;
204
+ 4) 四将与文昌、始击的卦位按后天八卦取一支(乾亥、坎子、艮丑、震卯、巽巳、离午、坤未、兑酉),
205
+ 再取该支上的神;太乙九宫一宫两支,取生支这一支,是本盘唯一能自洽的一档。
206
+ 这一层只给「古法怎么判」,不给成不成。 */
207
+
208
+ export const TWELVE_GODS = ['贵人', '螣蛇', '朱雀', '六合', '勾陈', '青龙', '天后', '太阴', '玄武', '太常', '白虎', '天空'];
209
+ export const GOD_GOOD = ['贵人', '六合', '青龙', '太阴', '太常'];
210
+ export const GOD_BAD = ['螣蛇', '朱雀', '勾陈', '玄武', '白虎', '天空'];
211
+ /** 后天八卦取支:一宫两支时取生支(与十六神位里同向那一支一致) */
212
+ export const TRIGRAM_TO_BRANCH = { 乾: '亥', 坎: '子', 艮: '丑', 震: '卯', 巽: '巳', 离: '午', 坤: '未', 兑: '酉' };
213
+ /** 十六神位名 → 地支:卦位取生支,支位原样 */
214
+ export const POS_TO_BRANCH = { ...TRIGRAM_TO_BRANCH, 子: '子', 丑: '丑', 寅: '寅', 卯: '卯', 辰: '辰', 巳: '巳', 午: '午', 未: '未', 申: '申', 酉: '酉', 戌: '戌', 亥: '亥' };
215
+ const GUI_REN = { 甲: '丑', 戊: '丑', 庚: '丑', 乙: '子', 己: '子', 丙: '亥', 丁: '亥', 壬: '巳', 癸: '巳', 辛: '午' };
216
+ const GUI_REN_NIGHT = { 甲: '未', 戊: '未', 庚: '未', 乙: '申', 己: '申', 丙: '酉', 丁: '酉', 壬: '卯', 癸: '卯', 辛: '寅' };
217
+
218
+ /** 当天的日干 */
219
+ export function dayStemOf(year, month, day) {
220
+ const idx = dayGanzhiIndex(toJDN(year, month, day));
221
+ return STEMS[((idx % 10) + 10) % 10];
222
+ }
223
+
224
+ /** 起十二神:昼贵 / 夜贵按卯~申、酉~寅分;反回 { stem, day, startBranch, dir, table } */
225
+ export function twelveGodsOf(stem, hour) {
226
+ const h = typeof hour === 'number' ? hour : 0;
227
+ const bi = Math.floor(((h + 1) % 24) / 2);
228
+ const day = bi >= 2 && bi <= 7;
229
+ const start = (day ? GUI_REN : GUI_REN_NIGHT)[stem] || '丑';
230
+ const si = BRANCH_NAMES.indexOf(start);
231
+ const dir = ['亥', '子', '丑', '寅', '卯', '辰'].includes(start) ? 1 : -1;
232
+ const table = {};
233
+ TWELVE_GODS.forEach((g, k) => {
234
+ const bi2 = ((si + dir * k) % 12 + 12) % 12;
235
+ table[BRANCH_NAMES[bi2]] = g;
236
+ });
237
+ return { stem, day, startBranch: start, dir, table };
238
+ }
239
+
240
+ /** 四将与文昌、始击各落哪一神下 */
241
+ export function shenJiangOf(o) {
242
+ const t = twelveGodsOf(o.dayStem, o.hour);
243
+ const b2b = POS_TO_BRANCH;
244
+ const rows = [
245
+ ['主大将', o.jiang.zhuDaName],
246
+ ['主参将', o.jiang.zhuCanName],
247
+ ['客大将', o.jiang.keDaName],
248
+ ['客参将', o.jiang.keCanName],
249
+ ['文昌', o.wenchang && o.wenchang.pos],
250
+ ['始击', o.shiji && o.shiji.pos],
251
+ ].map(([who, gua]) => {
252
+ const br = b2b[gua] || '';
253
+ const god = br ? t.table[br] || '' : '';
254
+ const kind = GOD_GOOD.includes(god) ? 'good' : GOD_BAD.includes(god) ? 'bad' : 'mid';
255
+ return { who, gua, branch: br, god, kind };
256
+ });
257
+ const goods = rows.filter((x) => x.kind === 'good').map((x) => x.who + '在' + x.god);
258
+ const bads = rows.filter((x) => x.kind === 'bad').map((x) => x.who + '在' + x.god);
259
+ const mids = rows.filter((x) => x.kind === 'mid').map((x) => x.who + '在' + (x.god || '无名'));
260
+ return { ...t, rows, goods, bads, mids };
261
+ }
262
+
263
+ /**
264
+ * 位置层 + 神将层合成卷一的判辞:凶 / 不和 / 吉 / 大吉 / 大利 / 先吉后凶。
265
+ * 卷一判语:门具将发遇吉神则大吉;门杜闭塞遇吉神守固大利;门杜闭塞遇凶神则先吉后凶。
266
+ * 位置层已相犯时古法认定「不相犯」不成立,不再往上抬。
267
+ */
268
+ export function verdictOf(o) {
269
+ const pos = o.jixiong || {};
270
+ const sj = o.shenjiang || {};
271
+ const gate = (o.gate && o.gate.gate) || '';
272
+ const jiDao = gate === '开' || gate === '休' || gate === '生';
273
+ const rows = sj.rows || [];
274
+ const allGood = rows.length > 0 && (sj.bads || []).length === 0 && (sj.mids || []).length === 0;
275
+ const anyBad = (sj.bads || []).length > 0;
276
+ let grade = pos.grade || 'ji';
277
+ let label = pos.label || '吉';
278
+ if (grade !== 'xiong' && allGood) {
279
+ grade = jiDao ? 'daji' : 'dali';
280
+ label = jiDao ? '大吉' : '大利';
281
+ } else if (grade !== 'xiong' && anyBad) {
282
+ grade = jiDao ? 'xianji' : 'xiong';
283
+ label = jiDao ? '先吉后凶' : '凶';
284
+ }
285
+ return { grade, label, jiDao, allGood, anyBad, posGrade: pos.grade, text: pos.text };
286
+ }
287
+
288
+ /** 《太乙金镜式经》这一层覆盖的算法清单(面板与文案都从这里取) */
74
289
  export const TAIYI_SCOPE = {
75
290
  covered: [
76
291
  '太乙积年与五元六纪',
@@ -79,14 +294,23 @@ export const TAIYI_SCOPE = {
79
294
  '太乙落宫(三年一宫、不入中五)',
80
295
  '八门(三十年一转)',
81
296
  '文昌(十八年一周,遇乾、坤各数两次)',
297
+ '计神(积年 mod 60,从寅起逆数)',
298
+ '始击(计神加和德,天目随移)',
299
+ '主算、客算与主客大小将、参将',
300
+ '掩、击、格、对、提、迫、囚、关八种位置关系',
301
+ '定目(年支合神移至太岁宫)',
302
+ '三基(君基、臣基、民基)',
303
+ '五福、大游',
304
+ '十精其余(天乙、地乙、直符、四神、小游)',
305
+ '阴阳数和(重阳、重阴、上和、次和、下和)',
306
+ '月计、日计、时计(积月、积日、时实三套推法)',
307
+ '古法吉凶倾向(相犯与否 + 数和不和)',
82
308
  ],
83
309
  notCovered: [
84
- '计神、始击、定目',
85
- '主算、客算与主客大小将参将',
86
- '三基(君基、臣基、民基)、五福、十精',
87
- '掩、迫、关、囚、击、格等格局判定',
310
+ '四神拆成本身与绛宫、明堂、玉堂的十二位全序',
311
+ '胜负断辞、三旗五将',
88
312
  ],
89
- note: '这一层只到「太乙、八门、文昌」三个落点;主客算与格局需要另一套起例,未收录。',
313
+ note: '这一层做到太乙、八门、文昌、计神、始击、主客算与四将、八种格局、定目、三基、五福、大游、十精、阴阳数和、月日时三计、古法吉凶与神将判辞;阴阳遁按冬至夏至交节判;四神十二位全序、胜负断辞不做。',
90
314
  };
91
315
 
92
316
  /** 取模成 1..n(余数 0 当 n) */
@@ -128,7 +352,11 @@ export function yuanJiOf(rest360) {
128
352
  */
129
353
  export function dunOf(jd) {
130
354
  if (typeof jd !== 'number' || !Number.isFinite(jd)) return { dun: 'yang', by: 'default' };
131
- return { dun: 'yang', by: 'jd' };
355
+ const p = jdToParts(jd);
356
+ const summer = termInstantJd(p.year, 11); // 当年夏至
357
+ const winter = termInstantJd(p.year, 23); // 当年冬至
358
+ const yin = jd >= summer && jd < winter;
359
+ return { dun: yin ? 'yin' : 'yang', by: 'jd', summer, winter };
132
360
  }
133
361
 
134
362
  /** 太乙落宫:局数 mod 24,三年一宫 */
@@ -176,6 +404,226 @@ export function wenChang(jinian, dun) {
176
404
  return { rest18: r, pos: hit.pos, god: hit.god, num: hit.num, palaceName: PALACES[hit.num].name };
177
405
  }
178
406
 
407
+ /** 八卦正宫名(十六神里 pos 落在这八个上就是正宫,其余是间神) */
408
+ export const GUA_NAMES = ['乾', '兑', '离', '震', '巽', '坎', '艮', '坤'];
409
+
410
+ /** 九宫环顺行:坎八 → 艮三 → 震四 → 巽九 → 离二 → 坤七 → 兑六 → 乾一 */
411
+ export const PALACE_RING = [8, 3, 4, 9, 2, 7, 6, 1];
412
+
413
+ /** 十二支名 */
414
+ export const BRANCH_NAMES = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
415
+
416
+ /** 地支序 → 十六神索引(子→地主、丑→阳德、寅→吕申……) */
417
+ export const BRANCH_TO_SIXTEEN = [2, 3, 5, 6, 7, 9, 10, 11, 13, 14, 15, 1];
418
+
419
+ /** 对宫:一乾对九巽、二离对八坎、三艮对七坤、四震对六兑,中五无对 */
420
+ export const OPPOSITE_PALACE = { 1: 9, 9: 1, 2: 8, 8: 2, 3: 7, 7: 3, 4: 6, 6: 4, 5: 5 };
421
+
422
+ /** 九宫环上的下一宫 */
423
+ export function nextPalace(palace) {
424
+ const i = PALACE_RING.indexOf(palace);
425
+ return PALACE_RING[(i + 1) % PALACE_RING.length];
426
+ }
427
+
428
+ /** 九宫环上的上一宫(顺行的「前一宫」,即太乙左右) */
429
+ export function prevPalace(palace) {
430
+ const i = PALACE_RING.indexOf(palace);
431
+ return PALACE_RING[(i - 1 + PALACE_RING.length) % PALACE_RING.length];
432
+ }
433
+
434
+ /** 六合:与某支相合的那一支(子丑、寅亥、卯戌、辰酉、巳申、午未) */
435
+ export const LIU_HE = [1, 0, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2];
436
+
437
+ /**
438
+ * 定目:淘金歌「为客最难详定计,便以合神加太岁」——把合神搬到太岁所在处,
439
+ * 盘整体旋转,原先天目(文昌)落到的位置就是定目。
440
+ */
441
+ export function dingMuOf(wenChangIdx, yearBranchIdx) {
442
+ const heIdx = LIU_HE[yearBranchIdx];
443
+ const taiSui16 = BRANCH_TO_SIXTEEN[yearBranchIdx];
444
+ const he16 = BRANCH_TO_SIXTEEN[heIdx];
445
+ const idx16 = ((wenChangIdx + (taiSui16 - he16)) % 16 + 16) % 16;
446
+ const item = SIXTEEN[idx16];
447
+ return { shift: taiSui16 - he16, idx16, pos: item.pos, god: item.god, palace: item.num, palaceName: PALACES[item.num].name };
448
+ }
449
+
450
+ /** 三基:君基三十年一移起午、臣基三年一移起午、民基一年一移起戌,顺行十二支 */
451
+ export function sanJiOf(jinian) {
452
+ const step = (base, period, start) => BRANCH_NAMES[(start + Math.floor((jinian - 1) / period)) % 12];
453
+ return {
454
+ jun: step('jun', 30, 6),
455
+ chen: step('chen', 3, 6),
456
+ min: step('min', 1, 10),
457
+ note: '君基三十年一移、臣基三年一移,都从午起;民基一年一移,从戌起,顺行十二支。',
458
+ };
459
+ }
460
+
461
+ /** 五福:四十五年移一位,上元甲子起一宫,乾一 → 艮三 → 巽九 → 坤七 → 中五 */
462
+ export const WUFU_ORDER = [1, 3, 9, 7, 5];
463
+ export function wuFuOf(jinian) {
464
+ const i = Math.floor((jinian - 1) / 45) % WUFU_ORDER.length;
465
+ const palace = WUFU_ORDER[i];
466
+ return { index: i, palace, palaceName: PALACES[palace].name };
467
+ }
468
+
469
+ /** 大游:三十六年移一位,上元甲子起七宫,逆数 七→六→四→三→二→一→九→八 */
470
+ export const DAYOU_ORDER = [7, 6, 4, 3, 2, 1, 9, 8];
471
+ export function daYouOf(jinian) {
472
+ const i = Math.floor((jinian - 1) / 36) % DAYOU_ORDER.length;
473
+ const palace = DAYOU_ORDER[i];
474
+ return { index: i, palace, palaceName: PALACES[palace].name };
475
+ }
476
+
477
+ /** 十二支上的位移取位(起点按地支索引,三年/三十年一移) */
478
+ function branchStep(startIdx, period, jinian) {
479
+ return { index: (startIdx + Math.floor((jinian - 1) / period)) % 12, name: BRANCH_NAMES[(startIdx + Math.floor((jinian - 1) / period)) % 12] };
480
+ }
481
+
482
+ /**
483
+ * 十精里的四个「十二宫」神:
484
+ * 天乙「甲子上元从六起,三年一徙顺行踪」(第六位 = 巳)
485
+ * 地乙「上元甲子九宫九,顺行十二宫中序,三年亦易」
486
+ * 直符「上元甲子元从五,十二宫中顺数详」(第五位 = 辰 → 按九宫数五 = 中宫,这里按十二支第五位辰起)
487
+ * 小游:三年一移,起一宫。
488
+ */
489
+ export function shiJingOf(jinian) {
490
+ const tianYi = branchStep(5, 3, jinian);
491
+ const diYi = branchStep(8, 3, jinian);
492
+ const zhiFu = branchStep(4, 3, jinian);
493
+ const SI_SHEN_RING = ['一宫', '二宫', '三宫', '四宫', '五宫', '六宫', '七宫', '八宫', '九宫', '绛宫', '明堂', '玉堂'];
494
+ const siShen = SI_SHEN_RING[Math.floor((jinian - 1) / 3) % 12];
495
+ const xiaoYou = PALACES[YANG_WALK[Math.floor((jinian - 1) / 3) % 8]].name;
496
+ return {
497
+ tianyi: tianYi.name,
498
+ diyi: diYi.name,
499
+ zhifu: zhiFu.name,
500
+ sishen: siShen,
501
+ xiaoyou: xiaoYou,
502
+ note: '天乙、地乙、直符、四神、小游都按上元甲子起例、三年一移;十精的另一半是五福、君基、臣基、民基、大游。',
503
+ };
504
+ }
505
+
506
+ /**
507
+ * 阴阳和不和(张良经 / 卷二):太乙在阳宫(八三四九)得奇数为重阳,得偶数为和;
508
+ * 太乙在阴宫(二七六一)得偶数为重阴,得奇数为和。这里只判这一段,不做胜负推断。
509
+ */
510
+ export function heShuOf(taiyiPalace, suan) {
511
+ const YANG = [8, 3, 4, 9];
512
+ const yin = !YANG.includes(taiyiPalace);
513
+ const odd = suan % 2 === 1;
514
+ const chongYang = !yin && odd;
515
+ const chongYin = yin && !odd;
516
+ const he = !chongYang && !chongYin;
517
+ const SHANG = [14, 18];
518
+ const CI = [23, 29, 32];
519
+ const XIA = [12, 16, 27, 34, 38];
520
+ const grade = SHANG.includes(suan) ? '上和' : CI.includes(suan) ? '次和' : XIA.includes(suan) ? '下和' : '';
521
+ return {
522
+ grade,
523
+ yang: !yin,
524
+ odd,
525
+ he,
526
+ chongYang,
527
+ chongYin,
528
+ text: he
529
+ ? `太乙在${yin ? '阴' : '阳'}宫、算得${odd ? '奇' : '偶'}数,为阴阳和${grade ? '(' + grade + ')' : ''}。`
530
+ : chongYang
531
+ ? '太乙在阳宫、算得奇数,为重阳,按经文属不和。'
532
+ : '太乙在阴宫、算得偶数,为重阴,按经文属不和。',
533
+ };
534
+ }
535
+
536
+
537
+ /**
538
+ * 计神:积年 mod 60 得六十甲子第几年(1..60),从寅起逆数十二支。
539
+ * 甲子年(序 1)计神在寅;寅年(序 3)计神在子,即「太岁在寅,计神在子」。
540
+ */
541
+ export function jiShenOf(jinian) {
542
+ const r = rim(jinian, 60);
543
+ const bi = (((2 - (r - 1)) % 12) + 12) % 12;
544
+ const idx16 = BRANCH_TO_SIXTEEN[bi];
545
+ const item = SIXTEEN[idx16];
546
+ return { r, branch: BRANCH_NAMES[bi], idx16, god: item.god, palace: item.num, palaceName: PALACES[item.num].name };
547
+ }
548
+
549
+ /**
550
+ * 始击:计神加和德(艮,十六神索引 4)之宫,天目随盘整体移动。
551
+ * 位移 = 和德索引 − 计神索引;文昌落位顺移同一位移。
552
+ */
553
+ export function shiJiOf(wenChangIdx, jiIdx16) {
554
+ const HE_DE = 4;
555
+ const shift = HE_DE - jiIdx16;
556
+ const idx16 = ((wenChangIdx + shift) % 16 + 16) % 16;
557
+ const item = SIXTEEN[idx16];
558
+ return { shift, idx16, pos: item.pos, god: item.god, palace: item.num, palaceName: PALACES[item.num].name };
559
+ }
560
+
561
+ /**
562
+ * 主算 / 客算:从某位天目所在处起,沿九宫环顺行累加宫数。
563
+ * 天目在正宫按本宫数起;在间神先计一,再从该间神所属的正宫继续。
564
+ * 数到太乙宫前一位为止(不含太乙宫本身)。
565
+ */
566
+ export function suanOf(idx16, taiyiPalace) {
567
+ const item = SIXTEEN[idx16];
568
+ const isJian = !GUA_NAMES.includes(item.pos);
569
+ let total = isJian ? 1 : 0;
570
+ let cur = item.num;
571
+ for (let i = 0; i < PALACE_RING.length; i += 1) {
572
+ if (cur === taiyiPalace) break;
573
+ total += cur;
574
+ cur = nextPalace(cur);
575
+ }
576
+ return total === 0 ? 1 : total;
577
+ }
578
+
579
+ /** 大将宫数:算数个位,整十按「得十置一」归一宫 */
580
+ export function generalOf(n) {
581
+ const g = n % 10;
582
+ return g === 0 ? 1 : g;
583
+ }
584
+
585
+ /** 参将宫数:大将 × 3 的个位,整十同样归一宫 */
586
+ export function canJiangOf(g) {
587
+ const c = (g * 3) % 10;
588
+ return c === 0 ? 1 : c;
589
+ }
590
+
591
+ /** 掩迫囚格关:只判位置关系,不掺吉凶 */
592
+ export function gejuOf(o) {
593
+ const { taiyiPalace, shiJiPalace, wenChangPalace, zhuDa, keDa, zhuCan, keCan } = o;
594
+ const near = [nextPalace(taiyiPalace), prevPalace(taiyiPalace)];
595
+ const yan = shiJiPalace === taiyiPalace;
596
+ const ji = near.includes(shiJiPalace);
597
+ const ge = shiJiPalace === OPPOSITE_PALACE[taiyiPalace];
598
+ const dui = wenChangPalace === OPPOSITE_PALACE[taiyiPalace];
599
+ const ti = keDa === wenChangPalace;
600
+ const poAt = [];
601
+ if (near.includes(wenChangPalace)) poAt.push('文昌');
602
+ if (near.includes(zhuDa)) poAt.push('主大将');
603
+ if (near.includes(keDa)) poAt.push('客大将');
604
+ if (near.includes(zhuCan)) poAt.push('主参将');
605
+ if (near.includes(keCan)) poAt.push('客参将');
606
+ const qiuAt = [['主大将', zhuDa], ['客大将', keDa], ['主参将', zhuCan], ['客参将', keCan]]
607
+ .filter((x) => x[1] === taiyiPalace).map((x) => x[0]);
608
+ const cross = [
609
+ ['主大将', zhuDa, '客大将', keDa],
610
+ ['主大将', zhuDa, '客参将', keCan],
611
+ ['客大将', keDa, '主参将', zhuCan],
612
+ ['主参将', zhuCan, '客参将', keCan],
613
+ ];
614
+ const guan = cross.filter((x) => x[1] === x[3]).map((x) => x[0] + '与' + x[2] + '同宫');
615
+ const list = [];
616
+ if (yan) list.push('掩:始击与太乙同宫');
617
+ if (ji) list.push('击:始击在太乙左右');
618
+ if (ge) list.push('格:太乙与始击相望(对宫)');
619
+ if (dui) list.push('对:文昌冲太乙宫');
620
+ if (ti) list.push('提:客大将与文昌同宫');
621
+ if (poAt.length) list.push('迫:' + poAt.join('、') + '在太乙前后');
622
+ if (qiuAt.length) list.push('囚:' + qiuAt.join('、') + '与太乙同宫');
623
+ if (guan.length) list.push('关:' + guan.join(','));
624
+ return { yan, ji, ge, dui, ti, po: poAt, qiu: qiuAt, guan, list };
625
+ }
626
+
179
627
  /** 主入口 */
180
628
  export function buildTaiyi(input) {
181
629
  assertObject(input, '太乙神数');
@@ -187,12 +635,52 @@ export function buildTaiyi(input) {
187
635
  const yuanji = yuanJiOf(rest360);
188
636
  const gate = eightGate(year);
189
637
 
190
- // 年家默认阳遁;给了具体日期时,口径仍写在 meta 里,方便以后接交节判定
191
- const dun = 'yang';
638
+ // 阳遁 / 阴遁按交节判:冬至到次年夏至为阳,夏至到冬至为阴。
639
+ // 这一盘总要落到某一天,所以直接按日期判,不再默认阳遁。
640
+ const hourNum = typeof input.hour === 'number' ? input.hour : 0;
641
+ const nowJd = toJDN(year, month, day) - 0.5 + hourNum / 24;
642
+ const dunInfo = dunOf(nowJd);
643
+ const dun = dunInfo.dun;
192
644
 
193
645
  const t = taiyiPalace(ju, dun);
194
646
  const wc = wenChang(jinian, dun);
195
647
 
648
+ const wcIdx16 = SIXTEEN.findIndex((x) => x.pos === wc.pos && x.god === wc.god);
649
+ const js = jiShenOf(jinian);
650
+ const sj = shiJiOf(wcIdx16, js.idx16);
651
+ const zhuSuan = suanOf(wcIdx16, t.palace);
652
+ const keSuan = suanOf(sj.idx16, t.palace);
653
+ const zhuDa = generalOf(zhuSuan);
654
+ const keDa = generalOf(keSuan);
655
+ const zhuCan = canJiangOf(zhuDa);
656
+ const keCan = canJiangOf(keDa);
657
+ const geju = gejuOf({
658
+ taiyiPalace: t.palace,
659
+ shiJiPalace: sj.palace,
660
+ wenChangPalace: wc.num,
661
+ zhuDa,
662
+ keDa,
663
+ zhuCan,
664
+ keCan,
665
+ });
666
+ const yearBranchIdx = (((year - 4) % 12) + 12) % 12;
667
+ const dm = dingMuOf(wcIdx16, yearBranchIdx);
668
+ const heZhu = heShuOf(t.palace, zhuSuan);
669
+ const heKe = heShuOf(t.palace, keSuan);
670
+ const heshu = { zhu: heZhu, ke: heKe };
671
+ const jixiong = jiXiongOf({ geju, heshu, gate });
672
+ const dayStem = dayStemOf(year, month, day);
673
+ const jiangObj = {
674
+ zhuDa, keDa, zhuCan, keCan,
675
+ zhuDaName: PALACES[zhuDa].name, keDaName: PALACES[keDa].name,
676
+ zhuCanName: PALACES[zhuCan].name, keCanName: PALACES[keCan].name,
677
+ };
678
+ const shenjiang = shenJiangOf({ dayStem, hour: typeof input.hour === 'number' ? input.hour : 0, jiang: jiangObj, wenchang: wc, shiji: sj });
679
+ const verdict = verdictOf({ jixiong, shenjiang, gate });
680
+ const monthJi = monthJiOf(jinian);
681
+ const dayJi = dayJiOf(jinian);
682
+ const hourJi = hourJiOf({ year, month, day, hour: typeof input.hour === 'number' ? input.hour : 0 });
683
+
196
684
  return {
197
685
  system: 'taiyi',
198
686
  meta: {
@@ -203,12 +691,39 @@ export function buildTaiyi(input) {
203
691
  ju,
204
692
  dun,
205
693
  dunText: dun === 'yin' ? '阴遁' : '阳遁',
694
+ dunBy: dunInfo.by,
206
695
  epoch: TAIYI_EPOCH,
207
696
  },
208
697
  year: { ...yuanji, rest360 },
209
698
  taiyi: t,
210
699
  gate,
211
700
  wenchang: wc,
701
+ jishen: js,
702
+ shiji: sj,
703
+ suan: { zhu: zhuSuan, ke: keSuan },
704
+ jiang: {
705
+ zhuDa,
706
+ keDa,
707
+ zhuCan,
708
+ keCan,
709
+ zhuDaName: PALACES[zhuDa].name,
710
+ keDaName: PALACES[keDa].name,
711
+ zhuCanName: PALACES[zhuCan].name,
712
+ keCanName: PALACES[keCan].name,
713
+ },
714
+ geju,
715
+ dingmu: dm,
716
+ sanji: sanJiOf(jinian),
717
+ wufu: wuFuOf(jinian),
718
+ dayou: daYouOf(jinian),
719
+ shijing: shiJingOf(jinian),
720
+ heshu,
721
+ jixiong,
722
+ verdict,
723
+ shenjiang,
724
+ month: monthJi,
725
+ day: dayJi,
726
+ hour: hourJi,
212
727
  scope: TAIYI_SCOPE,
213
728
  };
214
729
  }
package/locale/en.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
3
  "title": "Metaphysics Charts (Xuanxue)",
4
- "description": "Chinese metaphysics (xuanxue) — twenty-two systems in one plugin: eight chart systems plus eleven quick references (almanac, solar–lunar conversion, date selection, bone-weight fortune, Xiao Liu Ren, horoscope, numerology, MBTI-style 16-type questionnaire, life-curve, feng shui, name analysis), plus a nine-type TCM body-constitution questionnaire, five-movements-six-qi (wuyun liuqi) yearly chart, and Taiyi Shenshu (accumulated-year ju, palace, eight gates, Wen Chang). Chart systems: BaZi (hidden stems, ten gods, 12 stages, 42 shensha, strength, pattern and useful-god, luck scoring), Zi Wei Dou Shu (49 stars with brightness, four 12-god cycles, 34 patterns, decade and annual/monthly/daily/hourly mutagen), LiuYao, MeiHua YiShu, Da Liu Ren, Qi Men Dun Jia (three ju-selection schools), compatibility charts (BaZi and Zi Wei), and Tarot, plus an ask-driven interpretation layer. Computed locally with networking off by default, exposed as an agent tool with a Web panel."
4
+ "description": "Chinese metaphysics (xuanxue) — twenty-two systems in one plugin: eight chart systems plus fourteen quick references and self-assessments (almanac, solar–lunar conversion, date selection, bone-weight fortune, Xiao Liu Ren, horoscope, numerology, MBTI-style 16-type questionnaire, life-curve, feng shui, name analysis, nine-type TCM body-constitution questionnaire, five-movements-six-qi yearly chart, Taiyi Shenshu with accumulated-year ju, palace, eight gates and Wen Chang). The first screen shows 20 pick-of-the-bunch entries in three groups and folds the rest behind 'N more'; all 22 stay in the flat tab view. Chart systems: BaZi (hidden stems, ten gods, 12 stages, 42 shensha, strength, pattern and useful-god, luck scoring), Zi Wei Dou Shu (49 stars with brightness, four 12-god cycles, 34 patterns, decade and annual/monthly/daily/hourly mutagen), LiuYao, MeiHua YiShu, Da Liu Ren, Qi Men Dun Jia (three ju-selection schools), compatibility charts (BaZi and Zi Wei), and Tarot, plus an ask-driven interpretation layer. Computed locally with networking off by default, exposed as an agent tool with a Web panel."
5
5
  }
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-metaphysics",
3
- "version": "0.68.0",
3
+ "version": "0.70.0",
4
4
  "type": "module",
5
5
  "description": "命理排盘(玄学 xuanxue)for DeepSeek Harness: twenty-two systems and eleven quick references (almanac, solar–lunar conversion, …), with climate adjustment (tiaohou), the 120 Qiongtong Baojian entries, 1,062-char name library with per-character pinyin, Kangxi stroke counts, five-grid (五格/三才) analysis and mechanical phonetics checks, a 78-card tarot library on the Rider-Waite-Smith base (per-card English name, image description, core meaning and astrological correspondence, cross-checked against three published sources; court cards carry no zodiac by design and the chart says so), classical quotes (Ziping Zhenquan / Di Tian Sui / San Ming Tong Hui), hexagram body relations (六冲/六合/伏吟/反吟/化进神/化退神) plus yongshen placement (用神落位) with month/day strength (月建日辰旺衰) and whole-hexagram per-line strength (全卦逐爻旺衰) and response-time clues (应期线索:逢值/逢冲/逢合 + 月建五态快慢) for liuyao, plus an MBTI-style 16-type questionnaire and exact dayun handover dates (交运日) with annual suiyun hints (岁运并临/伏吟反吟), palace conditions (击刑/入墓), month states (旺相休囚死) and stem–palace relations (天盘干与宫生克) for qimen, flying four-transformations with chained flights (四化飞星串连), three-way-four-corner composite flights (三方四正复合飞化), switchable leap-month policy (闰月按日切分/整月归次月/整月归本月) eleven school switches (亮度/庚干四化/天马/天空/截空旬空/魁钺/天伤天使/长生/晚子时/身主/杂曜全表) for ziwei, the last one opting in to a 61-star minor-star (杂曜) table where each of the twelve extra stars carries its placement verse and the palace it landed in (verses accepted only when at least two independent published editions give the same table, homonymous stars never merged), caliber diffs for bazi/ziwei/qimen (same inputs, one switch, root-field changes), shadow-chart IDs (same normalized params → same ID; version bumps auto-diff chart fields), step-by-step deduction traces (liuyao najia→liuqin→shiying→fushen→strength; bazi strength score and pattern/yongshen; ziwei pattern conditions), and a bundled offline single-file page (命理排盘.html), a panel-to-chat handoff that asks the Harness about a finished chart, per-tab lazy engine chunks so the first-screen client bundle stays under 110KB gzip, and a public verification report (128 classical hexagram cases, 312 Zi Wei charts × 34 patterns, 12 dayun handover cases) generated from the same source that gates CI.",
6
6
  "keywords": [