dsh-metaphysics 0.59.1 → 0.61.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +1550 -1378
  2. package/README.md +17 -14
  3. package/client.almanac.js +60 -59
  4. package/client.bazi.js +118 -109
  5. package/client.chenggu.js +61 -60
  6. package/client.convert.js +60 -59
  7. package/client.daliuren.js +117 -116
  8. package/client.fengshui.js +62 -61
  9. package/client.hepan.js +117 -108
  10. package/client.js +422 -341
  11. package/client.liuyao.js +122 -120
  12. package/client.mbti.js +63 -62
  13. package/client.name.js +71 -70
  14. package/client.numerology.js +63 -62
  15. package/client.qimen.js +114 -113
  16. package/client.star.js +66 -65
  17. package/client.tarot.js +118 -117
  18. package/client.trend.js +112 -111
  19. package/client.xiaoliuren.js +77 -76
  20. package/client.zeri.js +66 -65
  21. package/client.ziwei.js +121 -112
  22. package/engine/calendar.js +8 -2
  23. package/engine/citations.js +4 -0
  24. package/engine/diff.js +3 -0
  25. package/engine/dst.js +28 -1
  26. package/engine/event-prompt.js +7 -1
  27. package/engine/followup.js +16 -1
  28. package/engine/handle.js +6 -2
  29. package/engine/index.js +101 -16
  30. package/engine/interpret-knowledge.js +21 -0
  31. package/engine/interpret.js +7 -3
  32. package/engine/liuyao.js +4 -0
  33. package/engine/lunar.js +10 -11
  34. package/engine/meihua.js +3 -0
  35. package/engine/name-data.js +24 -78
  36. package/engine/name-extra.js +415 -0
  37. package/engine/name.js +36 -9
  38. package/engine/plain-summary.js +5 -1
  39. package/engine/random.js +9 -1
  40. package/engine/rectify.js +24 -3
  41. package/engine/surname-data.js +249 -0
  42. package/engine/tarot-cards.js +143 -0
  43. package/engine/tarot.js +74 -27
  44. package/engine/trend.js +29 -20
  45. package/engine/yijing-text.js +792 -0
  46. package/engine/zeri-shensha.js +958 -0
  47. package/engine/zeri.js +151 -37
  48. package/engine/ziwei-zuoyao.js +313 -0
  49. package/engine/ziwei.js +42 -3
  50. package/index.js +35 -13
  51. package/package.json +5 -4
  52. package//345/221/275/347/220/206/346/216/222/347/233/230.html +4090 -285
@@ -196,13 +196,19 @@ export function termInstantJd(year, index) {
196
196
  export function jdToParts(jdLocal) {
197
197
  const jdn = Math.floor(jdLocal + 0.5);
198
198
  const frac = jdLocal + 0.5 - jdn;
199
- const totalMinutes = Math.round(frac * 1440);
199
+ // 按「整分钟向下取」而不是四舍五入:23:59:45 一进位就成了 1440 分,
200
+ // 旧写法把小时取模、日序不动,于是这一瞬时塌回「当日 00:00」——
201
+ // 日柱退一天、partsToJd 往返回来差将近一天(晚子时换日与真太阳时跨午夜正好踩在刀口上)。
202
+ // 向下取后 hour/minute 与 jdn 永远自洽:拆出来的那一天,就是这一刻所在的那一天。
203
+ // 但向下取会被浮点噪声咬住:JD 里的 13:00 常算成 779.99999 分,直接 floor 就是 12:59,
204
+ // 未时变成午时。先归到最近的整秒(输入精度到分,误差远小于一秒),再向下取整分。
205
+ const totalMinutes = Math.min(1439, Math.floor(Math.round(frac * 86400) / 60));
200
206
  const { year, month, day } = fromJDN(jdn);
201
207
  return {
202
208
  year,
203
209
  month,
204
210
  day,
205
- hour: Math.floor(totalMinutes / 60) % 24,
211
+ hour: Math.floor(totalMinutes / 60),
206
212
  minute: totalMinutes % 60,
207
213
  };
208
214
  }
@@ -590,6 +590,9 @@ export const CARD_CITATIONS = {
590
590
  'ziwei-feixing': { status: 'general', book: '紫微斗数全书 / 飞星派', chapter: '四化篇', reason: '宫干飞化属飞星派传法,非《全书》单条原文' },
591
591
  'ziwei-sanfang-fei': { status: 'general', book: '紫微斗数全书 / 飞星派', chapter: '四化篇', reason: '三方四正同飞属飞星派读法,未定位到单条原文' },
592
592
  'ziwei-jichuanji': { status: 'general', book: '紫微斗数全书 / 飞星派', chapter: '四化篇', reason: '忌转忌串连属飞星派读法,未定位到单条原文' },
593
+ // 2026-11-02 复核:维基文库本《紫微斗数全书》卷一是赋文与格局、卷二是安身命例,均无杂曜安星诀;
594
+ // 十二颗杂曜的诀文来自近代通行安星诀的多处并核,按口径只作通行说,不冒充《全书》原文。
595
+ 'ziwei-zaoyao': { status: 'general', book: '通行安星诀(中州派讲义 / 安星口诀本)', chapter: '杂曜安星诀', reason: '诀文见多处近代通行辑本且逐颗互核一致,但未定位到古籍单条原文,故不署《全书》' },
593
596
  // ── 六爻:增删卜易(维基文库本,繁体照录)
594
597
  'liuyao-yongshen': { refs: ['zs-yongshen'] },
595
598
  'liuyao-fushen': { refs: ['zs-fushen'] },
@@ -619,6 +622,7 @@ export const CARD_CITATIONS = {
619
622
  'qimen-gan-shengke': { status: 'general', book: '奇门遁甲统宗', chapter: '卷十二·玄机赋(下)', reason: '底本有「奇仪与日干」生克条,未见单列「干与落宫」生克条;干宫取象为通行读法,不拿十神条文冒充' },
620
623
  'qimen-ganying': { status: 'general', book: '奇门十干应克 / 奇门遁甲元灵经', chapter: '通行 100 组表 / 卷二·天盘加地盘吉凶', reason: '100 组为通行表汇编,元灵经只存甲、乙、丙三组,非单条原文' },
621
624
  'tarot-orientation': { status: 'general', book: '韦特塔罗', chapter: '逆位读法', reason: '韦特体系无中文古籍原文,逆位读法属近代流派' },
625
+ 'tarot-correspondence': { status: 'general', book: 'Liber 777 一脉的公开对应表', chapter: '小阿卡纳 2-10 的旬位与十源质', reason: '属近代西方神秘学对应表,无中文古籍原文;本插件按三处独立公开资料互核后取数字牌旬位,宫廷牌两派映射冲突故不配星座' },
622
626
  };
623
627
 
624
628
  export function allCitations() {
package/engine/diff.js CHANGED
@@ -336,6 +336,9 @@ export const CALIBER_SWITCHES = {
336
336
  ziweiSchoolSwitch('shenzhuSchool', '身主:子午皆火星 / 子铃午火', '身主', 'common', 'verse', {
337
337
  common: '子午皆火星', verse: '子铃午火(安身主诀逐字)',
338
338
  }),
339
+ ziweiSchoolSwitch('zuoyaoSchool', '杂曜:49 颗简盘 / 61 颗全表', '杂曜全表', 'minimal', 'full', {
340
+ minimal: '49 颗(简盘)', full: '61 颗(加十二颗杂曜)',
341
+ }),
339
342
  ],
340
343
  qimen: [
341
344
  {
package/engine/dst.js CHANGED
@@ -34,6 +34,26 @@ export function inDstSpan(t) {
34
34
  return true;
35
35
  }
36
36
 
37
+ /**
38
+ * 换表当天绕不开的两个钟面区间:
39
+ * - 起算日 02:00–02:59:钟表从 02:00 直接拨到 03:00,这个读数在当年根本不存在;
40
+ * - 终止日 01:00–01:59:钟表往回拨一小时,同一读数会走两遍,对应两个真实时刻。
41
+ * 两者都不是「算错了」,而是输入本身有歧义,需要告诉调用方我们按哪种读法折的。
42
+ * @param {{year:number,month:number,day:number,hour:number,minute?:number}} t
43
+ * @returns {'gap'|'twice'|null}
44
+ */
45
+ export function dstAmbiguity(t) {
46
+ const span = DST_SPANS[t.year];
47
+ if (!span) return null;
48
+ const [sy, sm, sd] = span[0].split('-').map(Number);
49
+ const [ey, em, ed] = span[1].split('-').map(Number);
50
+ const n = ORD(t.year, t.month, t.day);
51
+ const minutes = t.hour * 60 + (t.minute || 0);
52
+ if (n === ORD(sy, sm, sd) && minutes >= 120 && minutes < 180) return 'gap';
53
+ if (n === ORD(ey, em, ed) && minutes >= 60 && minutes < 120) return 'twice';
54
+ return null;
55
+ }
56
+
37
57
  /**
38
58
  * 夏令时校正:钟面时间减 1 小时(跨日正确回退)。
39
59
  * @param {{year:number,month:number,day:number,hour:number,minute?:number}} t
@@ -53,10 +73,17 @@ export function applyDst(t) {
53
73
  day = daysInMonth(year, month);
54
74
  }
55
75
  }
76
+ const amb = dstAmbiguity(t);
56
77
  return {
57
78
  year, month, day, hour, minute,
58
79
  applied: true,
59
- note: `检出自 ${DST_SPANS[t.year][0]} 至 ${DST_SPANS[t.year][1]} 的中国夏令时:出生钟面时间已减 1 小时校正`,
80
+ note: `检出自 ${DST_SPANS[t.year][0]} 至 ${DST_SPANS[t.year][1]} 的中国夏令时:出生钟面时间已减 1 小时校正`
81
+ + (amb === 'gap'
82
+ ? ';这一天 02:00–03:00 钟表直接跳过,所给钟面时间在当年并不存在——这里按「拨快后的读数」回退一小时,'
83
+ + '若登记的本是标准时间,请传 dst:false 重排。'
84
+ : amb === 'twice'
85
+ ? ';这一天 01:00–02:00 因回拨走了两遍,同一钟面时间对应两个真实时刻——这里按夏令时的那一遍(回退一小时)取。'
86
+ : ''),
60
87
  };
61
88
  }
62
89
 
@@ -112,7 +112,13 @@ function tarotUse(r) {
112
112
  name: c.position || '牌位',
113
113
  symbol: `${c.name}(${c.orientation || (c.upright ? '正位' : '逆位')})`,
114
114
  why: `${c.positionHint || '这一位代表它所标的那个阶段'};牌位决定它代表什么,牌面只说明它是什么样子。`,
115
- fact: `${c.core || ''}${(c.keywords && c.keywords.length) ? ` 关键词:${c.keywords.join('、')}` : ''}`,
115
+ // 逐张库的三层:占星对应(宫廷牌没有,就给角色)、牌面画了什么、核心与关键词
116
+ fact: [
117
+ c.core || '',
118
+ c.astro ? `占星对应:${c.astro}` : (c.role ? `牌位角色:${c.role}` : ''),
119
+ c.image ? `牌面:${c.image}` : '',
120
+ (c.keywords && c.keywords.length) ? `关键词:${c.keywords.join('、')}` : '',
121
+ ].filter(Boolean).join(' '),
116
122
  }));
117
123
  return cards.length ? cards : [{ name: '牌面', symbol: '—', why: '没有抽到牌', fact: '' }];
118
124
  }
@@ -204,7 +204,7 @@ export const READING_ORDERS = Object.freeze({
204
204
  meihua: ['所问与体用', '本卦/互卦/变卦', '五行生克', '应期与依据'],
205
205
  daliuren: ['天地盘(月将/贵人)', '四课', '三传', '课体与神将', '依据'],
206
206
  qimen: ['四柱与局数', '九宫(星门神/天盘干)', '用神落宫', '门宫·干宫·十干克应', '旺衰·击刑入墓', '依据'],
207
- tarot: ['问题与牌阵', '逐牌(正逆/元素)', '牌间关系', '综合倾向', '边界'],
207
+ tarot: ['问题与牌阵', '逐牌(正逆/牌面/元素对应)', '牌间关系', '综合倾向', '边界'],
208
208
  fengshui: ['坐向与元运', '运盘/山盘/向盘', '旺山旺向', '取象与边界'],
209
209
  });
210
210
 
@@ -261,6 +261,21 @@ export function askPrompt(r, opts = {}) {
261
261
  '',
262
262
  '——盘面摘要——',
263
263
  summary || '(这张盘没有摘要字段,请先让我重新排一次。)',
264
+ // 六爻/梅花带《周易》原文(engine/yijing-text.js 照录),别的外部模型才不至于只凭白话大意发挥;
265
+ // 读的是结果里已算好的 r.jing,这里不引原文库,ASK 栈因此不必多背 12KB。
266
+ ...(String((r && r.jing) || '').trim() ? [
267
+ '',
268
+ '——《周易》经文原文(本插件按三份底本互校照录,〔一作…〕是异文,不是改写)——',
269
+ String(r.jing).trim(),
270
+ ] : []),
271
+ // 紫微开了杂曜全表时同理:外部模型看到十二个杂曜名字,不知道它们是怎么定宫的,
272
+ // 很容易当成随意标注的装饰星。把安星诀一起递过去,它才有余力判断这颗落在这宫合不合情理。
273
+ ...(r && r.zuoyao && r.zuoyao.verses ? [
274
+ '',
275
+ `——紫微杂曜安星诀(本盘开了全表,${r.zuoyao.placed.length} 颗已按下列诀安入;诀文出多处近代通行辑本并核,属通行说,不是《紫微斗数全书》原文)——`,
276
+ String(r.zuoyao.verses).trim(),
277
+ `未另立:${(r.zuoyao.skipped || []).join(';')}`,
278
+ ] : []),
264
279
  '',
265
280
  '——回答要求——',
266
281
  '读者不懂命理。按下面的骨架答;任何一句他看不懂,就算没写完:',
package/engine/handle.js CHANGED
@@ -23,7 +23,7 @@ const PREFIX = 'mc1.';
23
23
  * 反过来,seed / asOf / direction / 各流派开关都必须留下——少了它们就复现不出同一张盘。
24
24
  */
25
25
  const SKIP = new Set([
26
- 'handle', 'trace', 'shadow', 'shadowVersion', 'diff', 'withInterpret', 'cross',
26
+ 'handle', 'trace', 'shadow', 'shadowVersion', 'diff', 'withInterpret', 'detail', 'cross',
27
27
  'verifyPast', 'rectify', 'events', 'ask', 'followup', 'question', 'geocode', 'caliber',
28
28
  // 一句话改参是把原话翻成补丁:补丁已经落在其余参数里,原话本身不进句柄,
29
29
  // 否则同一张盘换个说法就换一个句柄,回放时还会再套一遍补丁。
@@ -171,6 +171,9 @@ export function decodeHandle(handle) {
171
171
  return payload;
172
172
  }
173
173
 
174
+ /** 不能作为增量键的名字:`out['__proto__'] = v` 走的是 setter,会把 out 的原型换掉 */
175
+ const KEY_BLOCKED = new Set(['__proto__', 'constructor', 'prototype']);
176
+
174
177
  /**
175
178
  * 把 handle 与增量参数并起来。没有 handle 时原样返回(深一层的浅拷贝,避免改调用方对象)。
176
179
  * 增量优先:{ handle, asOf: 2030 } 只覆盖 asOf,其余沿用原参数。
@@ -183,7 +186,8 @@ export function resolveHandle(args = {}) {
183
186
  // 显式的 undefined 当作「没传」:否则 { handle, datetime: undefined } 会把句柄里的日期抹掉
184
187
  const out = { ...base };
185
188
  for (const [k, v] of Object.entries(rest)) {
186
- if (v !== undefined) out[k] = v;
189
+ if (v === undefined || KEY_BLOCKED.has(k)) continue;
190
+ out[k] = v;
187
191
  }
188
192
  return out;
189
193
  }
package/engine/index.js CHANGED
@@ -64,6 +64,7 @@ function requireGender(value, who = '') {
64
64
  const ZIWEI_SCHOOL_KEYS = [
65
65
  'brightnessSchool', 'gengYearSihua', 'tianmaSchool', 'tiankongSchool', 'jiekongSchool',
66
66
  'kuiyueSchool', 'tianshiTianshang', 'changshengSchool', 'lateZishi', 'shenzhuSchool',
67
+ 'zuoyaoSchool',
67
68
  ];
68
69
 
69
70
  /** 紫微流派参数原样透传;非法值交给引擎按默认口径回退 */
@@ -76,13 +77,17 @@ function ziweiSchoolArgs(args) {
76
77
  return out;
77
78
  }
78
79
 
79
- /** 解析 'YYYY-MM-DDTHH:mm' / 'YYYY-MM-DD HH:mm' / 'YYYY-MM-DD' */
80
+ /** 解析 'YYYY-MM-DDTHH:mm' / 'YYYY-MM-DD HH:mm' / 'YYYY-MM-DD'
81
+ * 可以带秒(用户常直接粘 ISO 串),但引擎的民用时间口径到分钟:秒只校验、不参与换算。 */
80
82
  export function parseDateTime(text) {
81
83
  if (typeof text !== 'string' || !text.trim()) {
82
84
  throw fail('E_INPUT', '缺少 datetime(公历日期时间),例如 1990-05-12T14:30');
83
85
  }
84
86
  const m = text.trim().match(/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[T\s](\d{1,2}):(\d{1,2})(?::(\d{1,2}))?)?$/);
85
87
  if (!m) throw fail('E_INPUT', `无法解析 datetime:「${text}」,请用 1990-05-12T14:30 这种格式`);
88
+ // 秒位以前只捕获不校验:\d{1,2} 放过 0–99,于是「T03:04:99」被静默折成 03:04。
89
+ // 与 1990-02-30 同一类问题——非法输入悄悄变成一个合法时刻,盘面照样排出,没人知道。
90
+ if (m[6] !== undefined && Number(m[6]) > 59) throw fail('E_INPUT', `秒超出范围:${m[6]}`);
86
91
  const out = {
87
92
  year: Number(m[1]), month: Number(m[2]), day: Number(m[3]),
88
93
  hour: Number(m[4] ?? 0), minute: Number(m[5] ?? 0),
@@ -202,6 +207,7 @@ function ziweiText(r) {
202
207
  brightnessSchool: '亮度', gengYearSihua: '庚干四化', tianmaSchool: '天马', tiankongSchool: '天空',
203
208
  jiekongSchool: '截空', kuiyueSchool: '魁钺', tianshiTianshang: '天伤天使',
204
209
  changshengSchool: '长生', lateZishi: '晚子时', shenzhuSchool: '身主',
210
+ zuoyaoSchool: '杂曜全表',
205
211
  };
206
212
  const diff = Object.entries(labels)
207
213
  .filter(([key]) => r.schools[key] && r.schools[key] !== ZIWEI_SCHOOL_DEFAULTS[key])
@@ -393,7 +399,11 @@ function tarotText(r) {
393
399
  `【牌阵】${r.spread}${r.castAt ? ` 抽牌于 ${r.castAt}` : ''}`,
394
400
  ];
395
401
  for (const c of r.cards) {
396
- lines.push(` ${c.position}:${c.name}(${c.orientation}) ${c.keywords.join('、')}`);
402
+ // 英文名与占星对应来自逐张库;宫廷牌没有占星,就顶出它的角色
403
+ const ast = c.astro ? ` ${c.astro}` : (c.role ? ` ${c.role}` : '');
404
+ const en = c.en ? ` [${c.en}]` : '';
405
+ lines.push(` ${c.position}:${c.name}${en}(${c.orientation})${ast} ${c.keywords.join('、')}`);
406
+ if (c.image) lines.push(`    牌面:${c.image}`);
397
407
  }
398
408
  if (r.reading && r.reading.length) {
399
409
  lines.push('【解读】');
@@ -517,7 +527,7 @@ function jsonSafe(value) {
517
527
  // ── 影子盘:同一组归一化参数 + 同一版本 → 同一个短 ID ──────────────
518
528
  // 只收影响盘面的参数:地址换成有效经度后不再把原文算进去,问事/方向按体系保留。
519
529
  const SHADOW_IGNORE = new Set([
520
- 'shadow', 'shadowVersion', 'diff', 'trace', 'traceError', 'withInterpret', 'cross', 'verifyPast', 'rectify', 'events',
530
+ 'shadow', 'shadowVersion', 'diff', 'trace', 'traceError', 'withInterpret', 'detail', 'cross', 'verifyPast', 'rectify', 'events',
521
531
  'geocode', 'ask', 'followup', 'caliber', 'seed', 'asOf', 'handle', 'revise',
522
532
  ]);
523
533
 
@@ -756,6 +766,8 @@ export function compute(input = {}) {
756
766
  args = { ...args };
757
767
  delete args.partner2;
758
768
  }
769
+ // 经度整形 + 守卫(非有限 / 超范围 → E_RANGE;数字串 → 数字)
770
+ args = normalizeLongitudes(args);
759
771
  const r = jsonSafe(computeRaw(args));
760
772
  // 「所问之事」以前只有六爻和塔罗会落到结果里,梅花/六壬/奇门/小六壬都丢掉了:
761
773
  // 于是第一屏想说「你问的是……」却拿不到那句话。这里统一补齐,已有的值不动。
@@ -804,11 +816,15 @@ ${r.summary}`;
804
816
  ${r.summary}`;
805
817
  }
806
818
  // 显式要求时才附加解析模块,避免默认输出过长;「所问之事」与「询问方向」一并传进去
819
+ const tier = detailOf(args.detail);
807
820
  if (args.withInterpret) {
808
821
  r.interpret = interpret(r, {
809
822
  question: typeof args.question === 'string' ? args.question : '',
810
823
  direction: typeof args.direction === 'string' ? args.direction : 'general',
811
824
  });
825
+ // 档位只改「回给模型的文本」,不改解析对象:面板拿到的仍是完整的 interpret。
826
+ // 键名不用 detail / tier:合盘结果里 r.detail 是详批对象、流年行里 r.tier 是档位,都会撞。
827
+ if (tier === 'lean') r.interpretTier = 'lean';
812
828
  }
813
829
  // 八字 × 紫微交叉验证:同一份出生信息再排另一套盘,并列两边对同一件事的说法
814
830
  // 交叉验证要再排另一套盘:时辰未知时紫微排不出来,这条支路直接跳过,
@@ -904,9 +920,13 @@ ${r.summary}`;
904
920
  return r;
905
921
  }
906
922
 
907
- /** 解析与详解的紧凑文本(给模型看,不塞整块 JSON) */
908
- export function interpretText(i) {
923
+ /** 解析与详解的紧凑文本(给模型看,不塞整块 JSON)
924
+ * detail:'full'(默认,与历史输出逐字一致)或 'lean'(只给结论、你问的那条与三张表,
925
+ * 长论述与「规则·出处」折成一行指针)。省的是回给模型的文本,不改盘面、不改解析对象本身。 */
926
+ export function interpretText(i, detail) {
909
927
  if (!i) return '';
928
+ const lean = detail === 'lean';
929
+ const LONG_SECTION = 300;
910
930
  const out = [`【${i.title || '解析与详解'}】${i.ask && i.ask.echo ? i.ask.echo : ''}`];
911
931
  // 直答放在最前:先回答「你问的事」,再说依据,避免读者读到最后才知道结论;
912
932
  // 没填问题时就不该喊「答所问」——改为「先看结论」,否则是一句假话
@@ -919,16 +939,27 @@ export function interpretText(i) {
919
939
  }
920
940
  // 解析生成失败时不能只返回一个空壳:盘面数据仍然可用,但要让调用方知道"这段没生成出来"
921
941
  if (i.error) out.push(`⚠ 解析生成失败:${i.error}(上方盘面数据本身可用)`);
922
- for (const d of i.dimensions || []) {
942
+ const allDims = i.dimensions || [];
943
+ // 问了方向就只给那一条:其余四条在 full 档里,模型要展开随时可以再排一次
944
+ const dims = lean && allDims.some((d) => d && d.focused) ? allDims.filter((d) => d && d.focused) : allDims;
945
+ for (const d of dims) {
923
946
  const evidence = Array.isArray(d.evidence) && d.evidence.length ? `\n 依据:${d.evidence.join(';')}${d.confidence ? ` 置信度:${d.confidence}` : ''}` : '';
924
947
  const plain = d.plain ? `(${d.plain})` : '';
925
948
  // 知识条目:「为什么这样断」+出处+可信度标注,是结论与依据之外的第三层
926
- const know = (d.knowledge || [])
927
- .map((k) => `\n 规则·${k.gist}〔${k.statusLabel}〕${k.detail} 出处:${k.source}${k.note ? ` 注:${k.note}` : ''}`)
928
- .join('');
949
+ const know = lean
950
+ ? ((d.knowledge || []).length ? `\n 规则 ${d.knowledge.length} 条未展开(detail:"full" 给出原文与出处)` : '')
951
+ : (d.knowledge || [])
952
+ .map((k) => `\n 规则·${k.gist}〔${k.statusLabel}〕${k.detail} 出处:${k.source}${k.note ? ` 注:${k.note}` : ''}`)
953
+ .join('');
929
954
  out.push(`${d.label}:${plain}${d.text}${d.advice ? ` 建议:${d.advice}` : ''}${evidence}${know}`);
930
955
  }
931
- for (const s of i.sections || []) out.push(`${s.label}:${s.text}`);
956
+ const secs = i.sections || [];
957
+ const longSecs = lean ? secs.filter((s) => String(s.text || '').length > LONG_SECTION) : [];
958
+ for (const s of secs) {
959
+ if (lean && String(s.text || '').length > LONG_SECTION) continue;
960
+ out.push(`${s.label}:${s.text}`);
961
+ }
962
+ if (longSecs.length) out.push(`(另有 ${longSecs.length} 节长论述未展开:${longSecs.map((s) => s.label).join('、')}。需要原文与出处时传 detail:"full")`);
932
963
  // 三张表:大运 / 流年 / 触发事件(纯文本对齐,工具输出也能直接读)
933
964
  for (const t of i.tables || []) {
934
965
  if (!Array.isArray(t.rows) || !t.rows.length) continue;
@@ -952,7 +983,7 @@ export function interpretText(i) {
952
983
 
953
984
  /** 对照时不参与计算的参数:它们只影响解析/附加模块,不影响盘面 */
954
985
  // shadow / trace 是结果的派生展示,不是盘面口径;带进来会让影子盘回显把每组开关都算成「有变化」
955
- const CALIBER_IGNORE_ARGS = ['cross', 'verifyPast', 'withInterpret', 'question', 'direction', 'ask', 'geocode', 'rectify', 'events', 'shadow', 'trace'];
986
+ const CALIBER_IGNORE_ARGS = ['cross', 'verifyPast', 'withInterpret', 'detail', 'question', 'direction', 'ask', 'geocode', 'rectify', 'events', 'shadow', 'trace'];
956
987
 
957
988
  /**
958
989
  * 口径对照:同一组输入,只把某一个口径开关换成它的另一档,逐字段列出变化。
@@ -1020,15 +1051,62 @@ const JU_METHODS = ['chaibu', 'chaoshen', 'zhirun'];
1020
1051
  const TAROT_COUNTS = [1, 3, 5];
1021
1052
  const LATE_ZI_MODES = ['same-day', 'next-day'];
1022
1053
 
1054
+ /**
1055
+ * 经度守卫:入口只判 `typeof === 'number'`,于是 JSON 里的 1e999(= Infinity)
1056
+ * 会一路走到真太阳时里炸成裸 TypeError,181 这类超范围值则静默排出另一张盘。
1057
+ * 两者都比报错坏:一个把堆栈式文案交给模型,一个把错结果当成对结果说出去。
1058
+ */
1059
+ function checkLongitude(value, who = '') {
1060
+ if (value === undefined || value === null || value === '') return undefined;
1061
+ const n = Number(value);
1062
+ const tag = who ? `${who}的` : '';
1063
+ if (!Number.isFinite(n)) throw fail('E_RANGE', `${tag}经度不是有限数值:收到「${value}」。经度写十进制数字即可,例如北京 116.4`);
1064
+ if (n < -180 || n > 180) throw fail('E_RANGE', `${tag}经度要在 −180 到 180 之间(东经为正),收到 ${n}`);
1065
+ return n;
1066
+ }
1067
+
1068
+ /**
1069
+ * 把主方 / 对方 / 第三方的经度统一成数字。
1070
+ * 下游按 `typeof === 'number'` 取值,所以 "86.0"(模型常给带引号的数)会被静默丢掉,
1071
+ * 少一次真太阳时修正而结果里毫无痕迹——这里先整形,再交给校验过的值。
1072
+ */
1073
+ function normalizeLongitudes(args) {
1074
+ const self = checkLongitude(args.longitude, '');
1075
+ const p = args.partner && typeof args.partner === 'object' ? checkLongitude(args.partner.longitude, '对方') : undefined;
1076
+ const p2 = args.partner2 && typeof args.partner2 === 'object' ? checkLongitude(args.partner2.longitude, '第三方') : undefined;
1077
+ const dirty = (self !== undefined && args.longitude !== self)
1078
+ || (p !== undefined && args.partner.longitude !== p)
1079
+ || (p2 !== undefined && args.partner2.longitude !== p2);
1080
+ if (!dirty) return args;
1081
+ const out = { ...args };
1082
+ if (self !== undefined) out.longitude = self;
1083
+ if (p !== undefined) out.partner = { ...out.partner, longitude: p };
1084
+ if (p2 !== undefined) out.partner2 = { ...out.partner2, longitude: p2 };
1085
+ return out;
1086
+ }
1087
+
1088
+ /** 输出档位:缺省与 'full' 都是历史全文;'lean' 只回结论与你问的那一条,省模型上下文。
1089
+ * 其它值以前会被当成「缺省」静默放过,写了 detail:'brief' 的人以为自己省了钱,其实没有。 */
1090
+ function detailOf(value) {
1091
+ if (value === undefined || value === null || value === '' || value === 'full') return 'full';
1092
+ if (value === 'lean') return 'lean';
1093
+ throw fail('E_INPUT', `detail 只能是 'full' 或 'lean':收到「${String(value).slice(0, 24)}」`);
1094
+ }
1095
+
1023
1096
  /** asOf 只接受能解析成真实日期的字符串;非法值以前会静默回退到「此刻」 */
1024
1097
  function checkAsOf(asOf) {
1025
1098
  if (asOf === undefined || asOf === null || asOf === '') return undefined;
1026
1099
  const parts = asOfParts(asOf);
1027
1100
  if (!parts) throw fail('E_ASOF', `无法解析 asOf:「${asOf}」,请用 2026-10-22T16:00 这种格式`);
1028
- const { year, month, day } = parts;
1101
+ const { year, month, day, hour, minute } = parts;
1029
1102
  if (!(month >= 1 && month <= 12) || !(day >= 1 && day <= daysInMonth(year, month))) {
1030
1103
  throw fail('E_ASOF', `asOf 指向的日期不存在:${asOf}`);
1031
1104
  }
1105
+ // 时分以前不校验:asOf: '2026-06-01T25:99' 会被接受并原样回显,
1106
+ // 而小时又按 mod 24 折回当日子时——流日/流时静默错位,比报错更坏。
1107
+ // 与 parseDateTime 同一把尺:给了就必须在范围内,缺省才由下游补默认值。
1108
+ if (hour !== undefined && !(hour >= 0 && hour <= 23)) throw fail('E_ASOF', `asOf 的小时要在 0–23 之间:${asOf}`);
1109
+ if (minute !== undefined && !(minute >= 0 && minute <= 59)) throw fail('E_ASOF', `asOf 的分钟要在 0–59 之间:${asOf}`);
1032
1110
  return parts;
1033
1111
  }
1034
1112
 
@@ -1074,7 +1152,7 @@ function computeRaw(args = {}) {
1074
1152
  // 流月/流日/流时的基准时刻:默认本机此刻,可用 asOf 指定以便复现
1075
1153
  asOf,
1076
1154
  });
1077
- return { ...result, precision: dt.precision, summary: ziweiText(result) };
1155
+ return { ...result, ...(asOf ? { asOf } : {}), precision: dt.precision, summary: ziweiText(result) };
1078
1156
  }
1079
1157
  if (system === 'tarot') {
1080
1158
  const count = args.count === undefined || args.count === null || args.count === '' ? 3 : Number(args.count);
@@ -1469,7 +1547,10 @@ function zeriText(r) {
1469
1547
  ? `【择日·${r.meta.task}】候选 ${r.meta.days} 天:${r.meta.candidates.join('、')}${r.meta.shengXiao ? ` 当事人属${r.meta.shengXiao}` : '(未给属相,未做冲煞排除)'}`
1470
1548
  : `【择日·${r.meta.task}】${r.meta.startDate} 起 ${r.meta.days} 天${r.meta.shengXiao ? ` 当事人属${r.meta.shengXiao}` : '(未给属相,未做冲煞排除)'}`;
1471
1549
  const top = r.days.slice().sort((a, b) => b.score - a.score).slice(0, cand ? 12 : 5)
1472
- .map((d) => `· ${d.date} ${d.weekday} ${d.verdict}(${d.score}分) 宜${d.yi.slice(0, 3).join('/')} ${d.reasons.join(';') || '无特别加减分'}`);
1550
+ .map((d) => {
1551
+ const g = d.grade ? `,卷十${d.grade.label}·${d.grade.decision}` : '';
1552
+ return `· ${d.date} ${d.weekday} ${d.verdict}(${d.score}分${g}) 宜${d.yi.slice(0, 3).join('/')} ${d.reasons.join(';') || '无特别加减分'}`;
1553
+ });
1473
1554
  const bestLine = cand
1474
1555
  ? (r.compare.best
1475
1556
  ? `这一组里最优:${r.compare.best.date}(${r.compare.best.score}分,${r.compare.best.verdict})`
@@ -1485,6 +1566,10 @@ function zeriText(r) {
1485
1566
  cand ? '候选日逐日:' : '逐日排序:',
1486
1567
  ...top,
1487
1568
  ...(cand ? [`比较口径:${r.compare.note}`] : []),
1569
+ `神煞依据:${r.shenshaRef.text};起法 ${r.shenshaRef.volumes.join('、')}。未实现起法:${r.shenshaRef.unsupported.join('、')}(不计分,逐日 shensha.unsupported 列出)。`,
1570
+ ...(r.shenshaRef.juan10
1571
+ ? [`卷十依据:${r.shenshaRef.juan10.text}(${r.shenshaRef.juan10.corpusLines});${r.shenshaRef.juan10.thresholdsNote}`]
1572
+ : []),
1488
1573
  `口径:${r.meta.scope}`,
1489
1574
  ].join('\n');
1490
1575
  }
@@ -1684,7 +1769,7 @@ function nameText(r) {
1684
1769
  : `【五格】${wu ? wu.reason : '未算'}`;
1685
1770
  const ph = r.phonetics;
1686
1771
  const phLine = ph && ph.available
1687
- ? `【音韵】${ph.chars.map((x) => `${x.char}${x.py}${x.tone}`).join(' ')}|声调 ${ph.tonePattern}|${ph.level}${ph.issues.length ? `:${ph.issues.map((i) => i.text).join(';')}` : ''}`
1772
+ ? `【音韵】${ph.chars.map((x) => `${x.char}${x.py}${x.tone}`).join(' ')}|声调 ${ph.tonePattern}|${ph.level}${(ph.alt || []).length ? `|姓又读 ${ph.alt.map((x) => `${x.char}${x.py}${x.tone}`).join('、')}` : ''}${ph.issues.length ? `:${ph.issues.map((i) => i.text).join(';')}` : ''}`
1688
1773
  : `【音韵】${ph ? ph.reason : '未算'}`;
1689
1774
  return [
1690
1775
  `【名字评估】${r.fullName}`,
@@ -1733,7 +1818,7 @@ export function renderText(result) {
1733
1818
  + result.trace.map((t, i) => `${i + 1}. ${t.field}|${t.result}\n 规则:${t.rule} 出处:${t.source}`).join('\n'));
1734
1819
  }
1735
1820
  if (result.interpret) {
1736
- const extra = interpretText(result.interpret);
1821
+ const extra = interpretText(result.interpret, result.interpretTier);
1737
1822
  if (extra) parts.push(extra);
1738
1823
  }
1739
1824
  if (result.cross) parts.push(`【八字 × 紫微 交叉验证】\n${crossText(result.cross)}`);
@@ -252,6 +252,18 @@ export const KNOWLEDGE = [
252
252
  status: 'variant',
253
253
  note: '串连几跳为止、是否允许借对宫飞化,各派不一;本插件只跟单一支化逐宫走,走满 12 宫或成环即停。',
254
254
  },
255
+ {
256
+ id: 'ziwei-zaoyao',
257
+ // 挂在 work / health 不是随手加的:这十二颗里被真正用到的就那几组——
258
+ // 天官、天福、天厨主贵显与俸禄(看事业、公职),天月、解神、阴煞主病符与消灾(看疾厄)。
259
+ // overall 已有三张占位,这张在 overall 排不上号,所以按落宫实质挂后两维。
260
+ system: 'ziwei', dims: ['overall', 'work', 'health'],
261
+ gist: '杂曜多而轻:全表六十余颗,本盘默认只排 49 颗,开开关才补十二颗。',
262
+ detail: '通行全表的杂曜按「定宫依据」分四路:解神、天巫、天月、阴煞只看生月(如解神「正二在申三四在戍,五六在子七八在寅」);天官、天福、天厨只看生年干(如天厨「甲丁食蛇口,乙戊辛马方」);蜚廉、天德、年解只看生年支(天德「起酉宫,顺至生年」,年解「戌宫逆行去」);天才、天寿借命宫与身宫——自命宫/身宫起子年,顺数到本生年支安之。十二颗各有各的诀,没有一条通用规则能推出来,所以只能逐颗查表。它们的作用是补细节(口舌、病符、贵显、寿年、香火),不改主星与四化搭出的骨架,这也是默认盘不排它们的原因:星一多,容易被当成一条条独立的吉凶判决。',
263
+ source: '通行安星诀:王亭之《中州派紫微斗数初级讲义·安星法》、网络《紫微斗数安星法口诀》本、《全书安星法》转录本,三处并核',
264
+ status: 'settled',
265
+ note: '同名不同星不合并:年支系的蜚廉≠博士十二神的飞廉,按年安的天德≠岁前十二神的天德,年解≠按月安的解神(月解)。龙德、月德虽可按年支安,但与岁前神煞同名异宫,本库不另立。转抄例子有一处与自家诀文相抵(天才:命宫在午、戌年生人,按诀应在辰宫,却写「巳宫安天才」),判为笔误,一律以诀文为准。',
266
+ },
255
267
 
256
268
  // ── 六爻 ──────────────────────────────────────────────
257
269
  {
@@ -464,6 +476,15 @@ export const KNOWLEDGE = [
464
476
  status: 'variant',
465
477
  note: '逆位是否参与解读本身就有分歧(有牌阵传统完全不用逆位)。本插件按削弱加内向综合取象,并在开头声明。',
466
478
  },
479
+ {
480
+ id: 'tarot-correspondence',
481
+ system: 'tarot', dims: ['overall'],
482
+ gist: '小牌 2-10 按花色三分相排旬位,首牌只归生命树源质,宫廷牌不配星座。',
483
+ detail: '通行表把四组花色各配一个元素三分相(权杖火=白羊/狮子/射手,圣杯水=巨蟹/天蝎/双鱼,宝剑风=天秤/水瓶/双子,星币土=摩羯/金牛/处女):数字 2-4、5-7、8-10 依次落进这三个星座,同组三张各占该星座的第一、二、三旬;旬主星按迦勒底连续序(白羊第一旬起火星,其后土→木→火→日→金→水→月轮下去)定,例:权杖二=火星在白羊第一旬、权杖四=金星在白羊第三旬、圣杯九=木星在双鱼第二旬。四张首牌对应生命树的 Kether 到 Malkuth 之外的「元素之根源」,只归源质不配单一星座。',
484
+ source: 'RWS(1909)牌面;牌义取自黄金黎明体系(Liber 777 一脉的公开对应表,与三处独立资料互核)',
485
+ status: 'variant',
486
+ note: '两处不合并:一是旬主星表本身有两派——本库按迦勒底连续序(白羊一旬起火星,巨蟹一旬起金星),另一派把每个星座的第一旬归本星座守护星(巨蟹一旬起月亮),两派在半个黄道上给出的旬主不同,本库取 777/黄金黎明一路因为小牌的星座-行星对应就是按它排的;二是宫廷牌(侍从/骑士/王后/国王)在公开资料里有两套互相冲突的映射:一套按「国王=本位宫、王后=固定宫、骑士=变动宫」,另一套按黄金黎明四元素层级(骑士=火、国王=风、王后=土、侍从=水)推星座,两派在多数花色上把国王与王后对调。本插件两派都不合并:数字牌取连续序标注旬位,宫廷牌只给元素与角色定位,星座与主星留空。',
487
+ },
467
488
  {
468
489
  id: 'bazi-dayun-jiaoqi',
469
490
  system: 'bazi', dims: [],
@@ -1081,7 +1081,7 @@ function ziweiInterpret(r) {
1081
1081
  // ══════════════════════════════════════════════════════════
1082
1082
  // 卦象通用:卦辞 / 爻辞 / 动爻 / 生活落点
1083
1083
  // ══════════════════════════════════════════════════════════
1084
- function hexSections({ benName, benUpper, benLower, bianName, bianUpper, bianLower, movingLine, moving }) {
1084
+ function hexSections({ benName, benUpper, benLower, bianName, bianUpper, bianLower, movingLine, moving, jing: jingText }) {
1085
1085
  const ben = hexInfo(benName, benUpper, benLower);
1086
1086
  const sections = [
1087
1087
  {
@@ -1100,6 +1100,10 @@ function hexSections({ benName, benUpper, benLower, bianName, bianUpper, bianLow
1100
1100
  text: `动爻变化之后得到「${bian.full}」,${bian.meaning}。变卦往往指向事情后来的样子:从「${ben.full}」走到「${bian.full}」,可以理解为局面会从${ben.scene}转为${bian.scene}。`,
1101
1101
  });
1102
1102
  }
1103
+ // 经文原文由排盘层算好挂进结果(engine/yijing-text.js),这里只做摆放:
1104
+ // 先读白话大意,再对原文,最后才落到生活里。
1105
+ const jing = String(jingText || '');
1106
+ if (jing) sections.push({ key: 'jingText', label: '经文原文', text: jing });
1103
1107
  sections.push({
1104
1108
  key: 'scene', label: '落到生活里',
1105
1109
  text: `把这一卦放进日常:${ben.scene}。${bianName ? `而后续的方向是${hexInfo(bianName, bianUpper, bianLower).scene}。` : ''}你可以对照着看看,眼下哪件事最像这个描述。`,
@@ -1126,7 +1130,7 @@ function liuyaoInterpret(r, ask) {
1126
1130
  const { sections, ben: benInfo, bian: bianInfo } = hexSections({
1127
1131
  benName: ben.name, benUpper: ben.upper, benLower: ben.lower,
1128
1132
  bianName: bian.name, bianUpper: bian.upper, bianLower: bian.lower,
1129
- moving,
1133
+ moving, jing: r.jing,
1130
1134
  });
1131
1135
 
1132
1136
  const shiLine = lines.find((l) => l.position === ben.shi);
@@ -1383,7 +1387,7 @@ function meihuaInterpret(r) {
1383
1387
  const { sections, ben: benInfo, bian: bianInfo } = hexSections({
1384
1388
  benName: ben.name, benUpper: ben.upper, benLower: ben.lower,
1385
1389
  bianName: bian.name, bianUpper: bian.upper, bianLower: bian.lower,
1386
- moving: movingLine,
1390
+ moving: movingLine, jing: r.jing,
1387
1391
  });
1388
1392
 
1389
1393
  const bodyEl = pick(bu.body, 'element', '');
package/engine/liuyao.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // 六爻(纳甲)引擎:摇卦、装卦、六亲、六神、世应、变卦
2
2
  import { STEMS, BRANCHES, toJDN, ganzhiName, dayGanzhiIndex, sexagenaryIndex, xunKongOf } from './calendar.js';
3
+ import { jingText } from './yijing-text.js';
3
4
 
4
5
  // 八卦三位(自下而上,1 = 阳)
5
6
  const TRIGRAM_BITS = {
@@ -580,6 +581,9 @@ export function buildLiuyao(input = {}) {
580
581
  body.labels = bodyLabels(body);
581
582
  return body;
582
583
  })(),
584
+ // 经文原文在排盘时就算好挂上来,解释层只负责摆位置:
585
+ // 这样 yijing-text 只进六爻/梅花那一个分块,不必为了原文把 12KB 塞进 8 个分块。
586
+ jing: jingText({ benName: ben.name, bianName: bian ? bian.name : '', movingPositions: moving.map((i) => i + 1) }),
583
587
  };
584
588
  }
585
589
 
package/engine/lunar.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // 农历(定朔):朔望月计算 + 冬至所在月为十一月 + 无中气置闰
2
2
  import {
3
- toJDN, jdToParts, termInstantJd, deltaTSeconds, localOffsetFor, OFFSET_MODERN,
3
+ toJDN, jdToParts, termInstantJd, deltaTSeconds, localOffsetFor,
4
4
  } from './calendar.js';
5
5
 
6
6
  const SYNODIC = 29.530588861;
@@ -63,12 +63,11 @@ export function dayNumberOf(jd) {
63
63
  return Math.floor(jd + 0.5);
64
64
  }
65
65
 
66
- // 1929 年前后的时区切换常量与换算统一放在 calendar.js(节气与农历共用)
67
- /** 把 calendar 给出的「东八区轴」时刻换成当时的地方轴 */
68
- function toHistoricalLocal(jdBeijing) {
69
- const jdUt = jdBeijing - OFFSET_MODERN;
70
- return jdUt + localOffsetFor(jdUt);
71
- }
66
+ // 时区切换常量与换算统一在 calendar.js:termInstantJd 与 newMoonJd 都已经落在
67
+ // 「当时的本地时间轴」上(1929 前北京地方平时,之后东八区),两边直接比日序就行。
68
+ // 以前这里再套一次 localOffset 把节气拉回本地轴,等于对同一个量移了两次时:
69
+ // 净偏移约 −12 分,节气时刻一旦落在午夜前后十几分钟内就少算一天,
70
+ // 中气归属错一个月,置闰跟着整月错位(1917 报闰三、真历表闰二;1922 报闰六、真历表闰五)。
72
71
 
73
72
  function kNear(jdDay) {
74
73
  return Math.round((jdDay + 0.5 - NEW_MOON_EPOCH) / SYNODIC);
@@ -78,7 +77,7 @@ function kNear(jdDay) {
78
77
  function hasZhongQi(year, startDay, endDay) {
79
78
  for (let y = year - 1; y <= year + 1; y++) {
80
79
  for (let i = 1; i < 24; i += 2) {
81
- const jdLocal = toHistoricalLocal(termInstantJd(y, i));
80
+ const jdLocal = termInstantJd(y, i);
82
81
  const d = dayNumberOf(jdLocal);
83
82
  if (d >= startDay && d < endDay) return { index: i, jd: jdLocal };
84
83
  }
@@ -93,9 +92,9 @@ function hasZhongQi(year, startDay, endDay) {
93
92
  export function solarToLunar(year, month, day) {
94
93
  const targetDay = toJDN(year, month, day);
95
94
 
96
- const wsDayThisYear = dayNumberOf(toHistoricalLocal(termInstantJd(year, 23)));
95
+ const wsDayThisYear = dayNumberOf(termInstantJd(year, 23));
97
96
  const wsYear = targetDay < wsDayThisYear ? year - 1 : year;
98
- const wsDay = dayNumberOf(toHistoricalLocal(termInstantJd(wsYear, 23)));
97
+ const wsDay = dayNumberOf(termInstantJd(wsYear, 23));
99
98
 
100
99
  // 十一月朔:日序不晚于冬至的最后一个朔日
101
100
  let k = kNear(wsDay);
@@ -105,7 +104,7 @@ export function solarToLunar(year, month, day) {
105
104
  // ── 置闰前提(国标口径)──
106
105
  // 只有「两个冬至月之间有 13 个朔望月」才需要置闰,且取其中第一个无中气的月。
107
106
  // 若只有 12 个月,即使某月无中气也不闰(1985 正月、2053 正月就是这种反例)。
108
- const nextWsDay = dayNumberOf(toHistoricalLocal(termInstantJd(wsYear + 1, 23)));
107
+ const nextWsDay = dayNumberOf(termInstantJd(wsYear + 1, 23));
109
108
  let kNext = k;
110
109
  while (dayNumberOf(newMoonJd(kNext + 1)) <= nextWsDay) kNext++;
111
110
  const cycleMonths = kNext - k;
package/engine/meihua.js CHANGED
@@ -3,6 +3,7 @@ import { BRANCHES } from './calendar.js';
3
3
  import { solarToLunar } from './lunar.js';
4
4
  import { fail } from './fail.js';
5
5
  import { hexagramFromBits } from './liuyao.js';
6
+ import { jingText } from './yijing-text.js';
6
7
 
7
8
  // 先天八卦数:乾1 兑2 离3 震4 巽5 坎6 艮7 坤8
8
9
  const TRI_NAME = ['', '乾', '兑', '离', '震', '巽', '坎', '艮', '坤'];
@@ -109,6 +110,8 @@ export function buildMeihua(input = {}) {
109
110
  bian: { name: bian.name, upper: bian.upper, lower: bian.lower },
110
111
  cuo: { name: cuo.name },
111
112
  zong: { name: zong.name },
113
+ // 经文原文在排盘时算好挂上,解释层只摆位置(同 liuyao.js,见 engine/yijing-text.js)
114
+ jing: jingText({ benName: ben.name, huName: hu.name, bianName: bian.name, movingPositions: [moving] }),
112
115
  bodyUse: {
113
116
  body: { trigram: bodyTri, element: TRI_ELEMENT[bodyTri] },
114
117
  use: { trigram: useTri, element: TRI_ELEMENT[useTri] },