iztro 紫微斗数:可复刻实现的算法规格(Implementation Spec)
当前时间:2025-12-19T02:20:40.022Z
目标:仅依据本文,你可以在其它语言中从零复刻本仓库
src/里实现的紫微斗数排盘与运限算法(不含命理解读/断语)。范围:
- 本命盘(天盘)生成:命/身宫、五行局、十二宫、主星/辅星/杂耀、长生/博士/流年12神、四化、亮度。
- 中州派扩展:地盘/人盘重排与部分星曜差异。
- 运限:大限/小限/流年/流月/流日/流时落宫与“流耀”。
源码对应:核心入口在
src/astro/astro.ts,索引/落宫规则集中在src/star/location.ts。
阅读指南(如何用本文复刻)
- 先实现索引体系:第 3 节的
fixIndex/fixEarthlyBranchIndex是所有落宫计算的基座,任何一处对不齐都会导致整盘偏移。 - 复刻本命盘:按 4(命/身宫、五行局、起紫微/天府、安星)→ 5(大限/小限)→ 6(组装 12 宫与元信息)→ 7/8(命主身主、四化亮度) 的顺序实现。
- 复刻运限:在本命盘可生成后实现第 9 节;它不改变本命盘,只是基于目标日期计算“落在哪一宫”以及对应流耀。
0. 你需要先实现/引入的历法能力
本库依赖 lunar-lite。若你用其它语言复刻,需要能提供等价能力:
- 公历 ↔ 农历
solar2lunar(solarDate)→{ lunarYear, lunarMonth, lunarDay, isLeap, ... }lunar2solar(lunarDateStr, isLeapMonth)→solarDateStr
- 给定公历日期 + 时辰索引,求四柱干支(至少年/月/日/时)
getHeavenlyStemAndEarthlyBranchBySolarDate(solarDate, timeIndex, options)→{ yearly:[stem,branch], monthly:[stem,branch], daily:[stem,branch], hourly:[stem,branch] }
其中 options 至少支持:
options.year:年分界(正月初一 or 立春)options.month:月分界(正月初一 or 立春)
注意:本库把“子时”拆成早子与晚子,
timeIndex=12表示 23:00~00:00。
1. 核心常量(实现必须一致)
源码:src/data/constants.ts
1.1 天干/地支枚举(以 key 表示)
HEAVENLY_STEMS = [
'jiaHeavenly','yiHeavenly','bingHeavenly','dingHeavenly','wuHeavenly',
'jiHeavenly','gengHeavenly','xinHeavenly','renHeavenly','guiHeavenly'
]
EARTHLY_BRANCHES = [
'ziEarthly','chouEarthly','yinEarthly','maoEarthly','chenEarthly','siEarthly',
'wuEarthly','weiEarthly','shenEarthly','youEarthly','xuEarthly','haiEarthly'
]1.2 十二宫(宫名 key)
PALACES = [
'soulPalace','parentsPalace','spiritPalace','propertyPalace','careerPalace','friendsPalace',
'surfacePalace','healthPalace','wealthPalace','childrenPalace','spousePalace','siblingsPalace'
]1.3 五虎遁(定寅首)
源码:TIGER_RULE(src/data/constants.ts)
TIGER_RULE[yearStem] => stemOfYinMonth用于:
- 由“年干”求“寅月天干”(进而推命宫天干、推大限天干等)。
1.4 五行局枚举值
源码:FiveElementsClass(src/data/constants.ts)
water2nd=2, wood3rd=3, metal4th=4, earth5th=5, fire6th=62. 全局配置(会改变算法分界/流派)
源码:src/astro/astro.ts 中 config() / getConfig()
你在复刻实现里也建议保留同名配置,以保证行为一致。
2.1 配置项
yearDivide: 'normal'|'exact'- 年干支取法分界:
normal:正月初一分界exact:立春分界
- 年干支取法分界:
horoscopeDivide: 'normal'|'exact'- 运限(流年/月)取法分界(默认
exact)
- 运限(流年/月)取法分界(默认
ageDivide: 'normal'|'birthday'- 虚岁分界:
normal:自然年 +1birthday:过了农历生日才 +1
- 虚岁分界:
dayDivide: 'current'|'forward'- 晚子时(timeIndex=12)算“当天”还是“次日”
algorithm: 'default'|'zhongzhou'- 安星流派差异开关
mutagens?: Map<HeavenlyStemKey, StarKey[4]>- 四化表覆盖(顺序固定:禄 权 科 忌)
brightness?: Map<StarKey, Brightness[12]>- 庙旺落陷表覆盖(按宫位索引 0..11)
3. 统一索引约定(最重要)
3.1 宫位索引 PalaceIndex
本库所有“落宫”计算最终都要落到 0..11,并且定义:
- 寅宫为 0
- 之后顺行:卯=1,辰=2,…,丑=11
即:宫位数组 palaces[0] 对应地支“寅”。
3.2 基础工具函数
源码:src/utils/index.ts
3.2.1 fixIndex
说明:fixIndex 是全项目的取模归一函数,用来把任意整数索引归一到固定范围(默认 0..11)。所有“顺数/逆数”落宫最后都必须经过它,避免负数与越界。
function fixIndex(i, mod=12):
// 返回 [0..mod-1]
r = i % mod
if r < 0: r += mod
return r3.2.2 地支 → 宫位索引
yinEarthly 在 EARTHLY_BRANCHES 的 index 为 2。
说明:fixEarthlyBranchIndex 把地支索引转换为“寅宫=0”的宫位索引:地支数组以子起,而本库宫位以寅起,因此需要整体减去寅(=2)的偏移再取模。
function fixEarthlyBranchIndex(earthlyBranchKey):
return fixIndex(indexOf(EARTHLY_BRANCHES, earthlyBranchKey) - indexOf(EARTHLY_BRANCHES,'yinEarthly'), 12)等价:palaceIndex = (earthlyIndex - 2) mod 12。
3.2.3 小时 → 时辰索引(子时拆分)
源码:timeToIndex(src/utils/index.ts)
说明:timeToIndex 把 24 小时制转换为时辰索引,并把子时拆成早子(0)与晚子(12),以匹配本库所有按时辰落星/算日界的规则。
function timeToIndex(hour):
if hour == 0: return 0 // 00:00~01:00 早子
if hour == 23: return 12 // 23:00~00:00 晚子
return floor((hour + 1)/2)4. 公历排盘(天盘)主流程:bySolar
这一节描述的是:给定【公历日期 + 时辰 + 性别】如何得到一张完整本命盘。你可以把 bySolar 理解为一个编排器:它先算出命/身宫与五行局,再分别调用“主星/辅星/杂耀/12神/运限”的落宫算法,最后把所有结果按 12 宫索引拼装成 Astrolabe。
源码:src/astro/astro.ts bySolar()
4.1 输入/输出
输入:
solarDate: 'YYYY-M-D'timeIndex: 0..12gender: male/female 或 男/女(最终只需归一到 male/female)fixLeap: bool(默认 true)
输出:Astrolabe(十二宫 + 元数据)。类型见 src/data/types/astro.ts。
4.2 Step 0:晚子时日界处理(dayDivide)
说明:晚子时的“日界”配置。若设为 current,23:00~00:00 的晚子时仍算当天(避免日柱进位);若设为 forward,则保留晚子时语义(后续某些算法会按晚子进位到次日)。
if config.dayDivide == 'current' and timeIndex >= 12:
tIndex = 0
else:
tIndex = timeIndex含义:如果设置“晚子算当天”,则把晚子当作当天子时(不进位到次日)。
4.3 Step 1:取生年干支(yearDivide)
说明:调用历法能力取干支(四柱的一部分)。在本命盘中,这一步受 yearDivide/horoscopeDivide 影响,用于确定“生年干支”以及后续四化、神煞与运限分界。
pillars = getHeavenlyStemAndEarthlyBranchBySolarDate(solarDate, tIndex, {year: config.yearDivide})
(yearStem, yearBranch) = pillars.yearly4.4 Step 2:定命宫/身宫(getSoulAndBody)
源码:src/astro/palace.ts
4.4.1 农历月份索引(考虑闰月修正)
源码:fixLunarMonthIndex(src/utils/index.ts)
说明:fixLunarMonthIndex 把农历月转换为“正月建寅”的月份索引(寅=0)。若遇闰月且 fixLeap=true,则以 15 日为界:前半月按上月、后半月按下月(晚子时另有特殊处理)。
function fixLunarMonthIndex(solarDate, timeIndex, fixLeap):
lunar = solar2lunar(solarDate) // lunarMonth:1..12, lunarDay:1..30, isLeap
needAdd = lunar.isLeap and fixLeap and lunar.lunarDay > 15 and timeIndex != 12
return fixIndex(lunar.lunarMonth + 1 - 2 + (needAdd?1:0), 12)4.4.2 命宫/身宫索引
说明:调用历法能力取干支(四柱的一部分)。在本命盘中,这一步受 yearDivide/horoscopeDivide 影响,用于确定“生年干支”以及后续四化、神煞与运限分界。
pillars = getHeavenlyStemAndEarthlyBranchBySolarDate(solarDate, timeIndex, {year:config.yearDivide, month:config.horoscopeDivide})
(timeBranch) = pillars.hourly[1]
monthIndex = fixLunarMonthIndex(solarDate, timeIndex, fixLeap)
soulIndex = fixIndex(monthIndex - indexOf(EARTHLY_BRANCHES, timeBranch), 12)
bodyIndex = fixIndex(monthIndex + indexOf(EARTHLY_BRANCHES, timeBranch), 12)4.4.3 命宫干支
说明:命宫天干的计算链:先用“五虎遁”由年干推出寅月天干(可视作干支轮转起点),再按命宫索引在十天干上顺推得到命宫天干;命宫地支则是寅起顺排到命宫索引。
yinStem = TIGER_RULE[yearStem]
soulStemIndex = fixIndex(indexOf(HEAVENLY_STEMS, yinStem) + soulIndex, 10)
soulStem = HEAVENLY_STEMS[soulStemIndex]
soulBranch = EARTHLY_BRANCHES[ fixIndex(soulIndex + 2, 12) ]
return { soulIndex, bodyIndex, heavenlyStemOfSoul:soulStem, earthlyBranchOfSoul:soulBranch }4.5 Step 3:定五行局(getFiveElementsClass)
源码:src/astro/palace.ts
说明(getFiveElementsClass):根据【命宫(或from)干支】计算五行局。实现采用源码的“干支取数→相加超5减5→映射局数”的数学版,不依赖口诀记忆。输出为 water2nd/wood3rd/metal4th/earth5th/fire6th 之一。
function getFiveElementsClass(stemKey, branchKey):
stemNumber = floor(indexOf(HEAVENLY_STEMS, stemKey)/2) + 1
branchIdx = indexOf(EARTHLY_BRANCHES, branchKey)
branchNumber = floor(fixIndex(branchIdx, 6)/2) + 1
x = stemNumber + branchNumber
while x > 5: x -= 5
table = ['wood3rd','metal4th','water2nd','fire6th','earth5th']
return table[x-1]4.6 Step 4:安主星(紫微系/天府系)
源码:src/star/majorStar.ts + src/star/location.ts
4.6.1 紫微/天府起点:getStartIndex
说明(getStartIndex):计算紫微/天府的起星宫位。核心是用【农历日】与【局数(2..6)】求可整除点:不断增加 offset 直到 (day+offset)%局数==0,然后用商(quotient)与 offset 奇偶决定紫微落宫;天府与紫微相对。
起紫微星诀算法
- 六五四三二,酉午亥辰丑,
- 局数除日数,商数宫前走;
- 若见数无余,便要起虎口,
- 日数小於局,还直宫中守。 举例:
- 例一:27日出生木三局,以三除27,循环0次就可以整除,27➗3=9,从寅进9格,在戍安紫微。
- 例二:13日出生火六局,以六除13,最少需要加5才能整除, 18➗8=3,从寅进3格为辰,添加数为5(奇数),故要逆回五宫,在亥安紫微。
- 例三:6日出生土五局,以五除6,最少需要加4才能整除,10➗5=2,从寅进2格为卯,添加数为4(偶数),顺行4格为未,在未安紫微。
function getStartIndex(param):
lunar = solar2lunar(param.solarDate)
day = lunar.lunarDay
maxDays = getTotalDaysOfLunarMonth(param.solarDate)
if param.timeIndex == 12:
day += 1
if day > maxDays: day -= maxDays
baseStem = param.from?.heavenlyStem ?? soulStem
baseBranch = param.from?.earthlyBranch ?? soulBranch
fiveClass = getFiveElementsClass(baseStem, baseBranch)
k = FiveElementsClass[fiveClass]
offset = 0
while (day + offset) % k != 0:
offset += 1
quotient = floor((day + offset) / k) % 12
ziweiIndex = quotient - 1
if offset % 2 == 0:
ziweiIndex = ziweiIndex + offset
else:
ziweiIndex = ziweiIndex - offset
ziweiIndex = fixIndex(ziweiIndex, 12)
tianfuIndex = fixIndex(12 - ziweiIndex, 12)
return { ziweiIndex, tianfuIndex }4.6.2 落星序列
紫微系(逆行):
ziweiGroup = [
'ziweiMaj','tianjiMaj','', 'taiyangMaj','wuquMaj','tiantongMaj','', '', 'lianzhenMaj'
]天府系(顺行):
tianfuGroup = [
'tianfuMaj','taiyinMaj','tanlangMaj','jumenMaj','tianxiangMaj','tianliangMaj','qishaMaj',
'', '', '', 'pojunMaj'
]4.7 Step 5:安 14 辅星(getMinorStar)
源码:src/star/minorStar.ts + src/star/location.ts
4.7.1 左辅/右弼(按生月)
说明(getZuoYouIndex):按生月定左辅右弼:左辅从辰顺数到月数;右弼从戌逆数到月数。输入月数为 1..12,输出两个宫位索引。
function getZuoYouIndex(lunarMonth_1_to_12):
zuoIndex = fixIndex(fixEarthlyBranchIndex('chenEarthly') + (lunarMonth_1_to_12 - 1))
youIndex = fixIndex(fixEarthlyBranchIndex('xuEarthly') - (lunarMonth_1_to_12 - 1))
return {zuoIndex,youIndex}4.7.2 文昌/文曲(按时支)
说明(getChangQuIndex):按时辰定文昌文曲:文昌戌上逆时,文曲辰上顺时。输入 timeIndex(0..12),内部先 fixIndex 后计算。
function getChangQuIndex(timeIndex):
t = fixIndex(timeIndex)
changIndex = fixIndex(fixEarthlyBranchIndex('xuEarthly') - t)
quIndex = fixIndex(fixEarthlyBranchIndex('chenEarthly') + t)
return {changIndex,quIndex}4.7.3 天魁/天钺(按年干)
说明(getKuiYueIndex):按年干定天魁天钺:把年干分组映射到固定两地支(魁/钺),再转换为宫位索引。
function getKuiYueIndex(yearStem):
if yearStem in [jia, wu, geng]: return {kui:'chouEarthly', yue:'weiEarthly'}
if yearStem in [yi, ji]: return {kui:'ziEarthly', yue:'shenEarthly'}
if yearStem == xin: return {kui:'wuEarthly', yue:'yinEarthly'}
if yearStem in [bing, ding]: return {kui:'haiEarthly', yue:'youEarthly'}
if yearStem in [ren, gui]: return {kui:'maoEarthly', yue:'siEarthly'}4.7.4 禄存/擎羊/陀罗/天马(按年干/年支)
说明(getLuYangTuoMaIndex):按年干定禄存,再以前后宫推擎羊(+1)/陀罗(-1);按年支分组定天马(四马地)。输出禄/羊/陀/马四个索引。
function getLuYangTuoMaIndex(yearStem, yearBranch):
// 天马
if yearBranch in [yin,wu,xu]: ma='shenEarthly'
if yearBranch in [shen,zi,chen]:ma='yinEarthly'
if yearBranch in [si,you,chou]: ma='haiEarthly'
if yearBranch in [hai,mao,wei]: ma='siEarthly'
// 禄存
switch yearStem:
jia -> lu='yinEarthly'
yi -> lu='maoEarthly'
bing/wu -> lu='siEarthly'
ding/ji -> lu='wuEarthly'
geng -> lu='shenEarthly'
xin -> lu='youEarthly'
ren -> lu='haiEarthly'
gui -> lu='ziEarthly'
luIndex = fixEarthlyBranchIndex(lu)
maIndex = fixEarthlyBranchIndex(ma)
return {luIndex, maIndex, yangIndex:fixIndex(luIndex+1), tuoIndex:fixIndex(luIndex-1)}4.7.5 火星/铃星(按年支+时支)
说明(getHuoLingIndex):按年支先定火星/铃星“子时起点宫”,再顺数到出生时辰(加 timeIndex)。
function getHuoLingIndex(yearBranch, timeIndex):
t = fixIndex(timeIndex)
if yearBranch in [yin,wu,xu]:
huoStart='chouEarthly'; lingStart='maoEarthly'
if yearBranch in [shen,zi,chen]:
huoStart='yinEarthly'; lingStart='xuEarthly'
if yearBranch in [si,you,chou]:
huoStart='maoEarthly'; lingStart='xuEarthly'
if yearBranch in [hai,mao,wei]:
huoStart='youEarthly'; lingStart='xuEarthly'
return {
huoIndex: fixIndex(fixEarthlyBranchIndex(huoStart) + t),
lingIndex: fixIndex(fixEarthlyBranchIndex(lingStart) + t)
}4.7.6 地空/地劫(按时支)
说明(getKongJieIndex):按时辰定地空地劫:以亥宫为基点,地劫顺数、地空逆数到时辰。
function getKongJieIndex(timeIndex):
t = fixIndex(timeIndex)
hai = fixEarthlyBranchIndex('haiEarthly')
return { kongIndex: fixIndex(hai - t), jieIndex: fixIndex(hai + t) }4.8 Step 6:安杂耀(getAdjectiveStar)
源码:src/star/adjectiveStar.ts + src/star/location.ts
实现建议:先实现 5 个索引函数,再逐颗落星:
getYearlyStarIndex(param):年系杂耀(最复杂)getMonthlyStarIndex(solarDate, timeIndex, fixLeap):月系杂耀getDailyStarIndex(solarDate, timeIndex, fixLeap):日系杂耀getTimelyStarIndex(timeIndex):时系杂耀getLuanXiIndex(yearBranch):红鸾/天喜
4.8.1 红鸾/天喜(按年支)
说明(getLuanXiIndex):按年支定红鸾:卯上起子逆数到太岁;天喜为其对宫(+6)。
function getLuanXiIndex(yearBranch):
hongluanIndex = fixIndex(fixEarthlyBranchIndex('maoEarthly') - indexOf(EARTHLY_BRANCHES, yearBranch))
tianxiIndex = fixIndex(hongluanIndex + 6)
return {hongluanIndex,tianxiIndex}4.8.2 月系索引
说明(getMonthlyStarIndex):月系杂耀落宫:先把公历换算出“月索引(寅为0)”,再按源码映射表/线性偏移计算月解、天姚、天刑、阴煞、天月、天巫。
function getMonthlyStarIndex(solarDate, timeIndex, fixLeap):
monthIndex = fixLunarMonthIndex(solarDate, timeIndex, fixLeap)
mapJieshen = ['shenEarthly','xuEarthly','ziEarthly','yinEarthly','chenEarthly','wuEarthly']
yuejieIndex = fixIndex(fixEarthlyBranchIndex(mapJieshen[floor(monthIndex/2)]))
tianyaoIndex = fixIndex(fixEarthlyBranchIndex('chouEarthly') + monthIndex)
tianxingIndex = fixIndex(fixEarthlyBranchIndex('youEarthly') + monthIndex)
yinshaMap = ['yinEarthly','ziEarthly','xuEarthly','shenEarthly','wuEarthly','chenEarthly']
yinshaIndex = fixIndex(fixEarthlyBranchIndex(yinshaMap[monthIndex % 6]))
tianyueMap = ['xuEarthly','siEarthly','chenEarthly','yinEarthly','weiEarthly','maoEarthly','haiEarthly','weiEarthly','yinEarthly','wuEarthly','xuEarthly','yinEarthly']
tianyueIndex = fixIndex(fixEarthlyBranchIndex(tianyueMap[monthIndex]))
tianwuMap = ['siEarthly','shenEarthly','yinEarthly','haiEarthly']
tianwuIndex = fixIndex(fixEarthlyBranchIndex(tianwuMap[monthIndex % 4]))
return {yuejieIndex,tianyaoIndex,tianxingIndex,yinshaIndex,tianyueIndex,tianwuIndex}4.8.3 日系索引
说明:日系杂耀需要一个“日索引”。本库规则:晚子时(timeIndex>=12)算当天的第 lunarDay 日,否则算 lunarDay-1(相当于把早子归入前一日的尾部)。
function fixLunarDayIndex(lunarDay, timeIndex):
return (timeIndex >= 12) ? lunarDay : (lunarDay - 1)
// 说明(getDailyStarIndex):日系杂耀落宫:以“日索引(dayIndex)”为核心(晚子时不减1),由左辅/右弼/文昌/文曲起初一顺/逆推到生日,得到三台/八座/恩光/天贵。
function getDailyStarIndex(solarDate, timeIndex, fixLeap):
lunarDay = solar2lunar(solarDate).lunarDay
monthIndex = fixLunarMonthIndex(solarDate, timeIndex, fixLeap)
(zuoIndex,youIndex) = getZuoYouIndex(monthIndex + 1)
(changIndex,quIndex) = getChangQuIndex(timeIndex)
dayIndex = fixLunarDayIndex(lunarDay, timeIndex)
santaiIndex = fixIndex((zuoIndex + dayIndex) % 12)
bazuoIndex = fixIndex((youIndex - dayIndex) % 12)
enguangIndex = fixIndex(((changIndex + dayIndex) % 12) - 1)
tianguiIndex = fixIndex(((quIndex + dayIndex) % 12) - 1)
return {santaiIndex,bazuoIndex,enguangIndex,tianguiIndex}4.8.4 时系索引
说明(getTimelyStarIndex):时系杂耀落宫:台辅(午起)与封诰(寅起)顺数到出生时辰(加 timeIndex)。
function getTimelyStarIndex(timeIndex):
t = fixIndex(timeIndex)
taifuIndex = fixIndex(fixEarthlyBranchIndex('wuEarthly') + t)
fenggaoIndex = fixIndex(fixEarthlyBranchIndex('yinEarthly') + t)
return {taifuIndex,fenggaoIndex}4.8.5 年系索引(完整公式)
源码:getYearlyStarIndex(param)
说明(getYearlyStarIndex):年系杂耀落宫总汇:以 horoscopeDivide 分界取流年干支,再结合命/身宫索引、固定映射表与线性偏移计算多颗年系星(华盖/咸池/孤寡/天厨/旬空等),并处理中州派差异。
function getYearlyStarIndex(param):
// 年系杂耀使用 horoscopeDivide 作为年分界
pillars = getHeavenlyStemAndEarthlyBranchBySolarDate(param.solarDate, param.timeIndex, {year: config.horoscopeDivide})
(yearStem, yearBranch) = pillars.yearly
(soulIndex, bodyIndex) = getSoulAndBody({solarDate:param.solarDate,timeIndex:param.timeIndex,fixLeap:param.fixLeap}).(soulIndex,bodyIndex)
stemIdx = indexOf(HEAVENLY_STEMS, yearStem)
branchIdx = indexOf(EARTHLY_BRANCHES, yearBranch)
// 华盖/咸池
(huagaiIndex,xianchiIndex) = getHuagaiXianchiIndex(yearBranch)
// 孤辰/寡宿
(guchenIndex,guasuIndex) = getGuGuaIndex(yearBranch)
tiancaiIndex = fixIndex(soulIndex + branchIdx)
tianshouIndex = fixIndex(bodyIndex + branchIdx)
tianchuMap = ['siEarthly','wuEarthly','ziEarthly','siEarthly','wuEarthly','shenEarthly','yinEarthly','wuEarthly','youEarthly','haiEarthly']
tianchuIndex = fixIndex(fixEarthlyBranchIndex(tianchuMap[stemIdx]))
posuiMap = ['siEarthly','chouEarthly','youEarthly']
posuiIndex = fixIndex(fixEarthlyBranchIndex(posuiMap[branchIdx % 3]))
feilianMap = ['shenEarthly','youEarthly','xuEarthly','siEarthly','wuEarthly','weiEarthly','yinEarthly','maoEarthly','chenEarthly','haiEarthly','ziEarthly','chouEarthly']
feilianIndex = fixIndex(fixEarthlyBranchIndex(feilianMap[branchIdx]))
longchiIndex = fixIndex(fixEarthlyBranchIndex('chenEarthly') + branchIdx)
fenggeIndex = fixIndex(fixEarthlyBranchIndex('xuEarthly') - branchIdx)
tiankuIndex = fixIndex(fixEarthlyBranchIndex('wuEarthly') - branchIdx)
tianxuIndex = fixIndex(fixEarthlyBranchIndex('wuEarthly') + branchIdx)
tianguanMap = ['weiEarthly','chenEarthly','siEarthly','yinEarthly','maoEarthly','youEarthly','haiEarthly','youEarthly','xuEarthly','wuEarthly']
tianguanIndex = fixIndex(fixEarthlyBranchIndex(tianguanMap[stemIdx]))
tianfuMap = ['youEarthly','shenEarthly','ziEarthly','haiEarthly','maoEarthly','yinEarthly','wuEarthly','siEarthly','wuEarthly','siEarthly']
tianfuIndex = fixIndex(fixEarthlyBranchIndex(tianfuMap[stemIdx]))
tiandeIndex = fixIndex(fixEarthlyBranchIndex('youEarthly') + branchIdx)
yuedeIndex = fixIndex(fixEarthlyBranchIndex('siEarthly') + branchIdx)
tiankongIndex = fixIndex(fixEarthlyBranchIndex(yearBranch) + 1)
jieluMap = ['shenEarthly','wuEarthly','chenEarthly','yinEarthly','ziEarthly']
kongwangMap = ['youEarthly','weiEarthly','siEarthly','maoEarthly','chouEarthly']
jieluIndex = fixIndex(fixEarthlyBranchIndex(jieluMap[stemIdx % 5]))
kongwangIndex = fixIndex(fixEarthlyBranchIndex(kongwangMap[stemIdx % 5]))
xunkongIndex = fixIndex(
fixEarthlyBranchIndex(yearBranch)
+ indexOf(HEAVENLY_STEMS,'guiHeavenly')
- indexOf(HEAVENLY_STEMS, yearStem)
+ 1
)
yinyang = branchIdx % 2
if yinyang != (xunkongIndex % 2):
xunkongIndex = fixIndex(xunkongIndex + 1)
jiekongIndex = (yinyang == 0) ? jieluIndex : kongwangIndex
jieshaAdjIndex = getJieshaAdjIndex(yearBranch)
nianjieIndex = getNianjieIndex(yearBranch)
dahaoAdjIndex = getDahaoIndex(yearBranch)
// 天伤/天使(相对命宫)
tianshangIndex = fixIndex(indexOf(PALACES,'friendsPalace') + soulIndex)
tianshiIndex = fixIndex(indexOf(PALACES,'healthPalace') + soulIndex)
genderParity = (param.gender == 'male') ? 0 : 1
sameYinyang = (yinyang == genderParity)
if config.algorithm == 'zhongzhou' and !sameYinyang:
swap(tianshiIndex, tianshangIndex)
return {...all indices...}并实现以下子函数:
说明(getHuagaiXianchiIndex):按年支分组映射华盖/咸池到固定地支,再转为宫位索引。
function getHuagaiXianchiIndex(yearBranch):
if yearBranch in [yin,wu,xu]: hg='xuEarthly'; xc='maoEarthly'
if yearBranch in [shen,zi,chen]:hg='chenEarthly';xc='youEarthly'
if yearBranch in [si,you,chou]: hg='chouEarthly';xc='wuEarthly'
if yearBranch in [hai,wei,mao]: hg='weiEarthly'; xc='ziEarthly'
return {huagaiIndex:fixIndex(fixEarthlyBranchIndex(hg)), xianchiIndex:fixIndex(fixEarthlyBranchIndex(xc))}
// 说明(getGuGuaIndex):按年支分组映射孤辰/寡宿到固定地支,再转为宫位索引。
function getGuGuaIndex(yearBranch):
if yearBranch in [yin,mao,chen]: gu='siEarthly'; gua='chouEarthly'
if yearBranch in [si,wu,wei]: gu='shenEarthly';gua='chenEarthly'
if yearBranch in [shen,you,xu]: gu='haiEarthly'; gua='weiEarthly'
if yearBranch in [hai,zi,chou]: gu='yinEarthly'; gua='xuEarthly'
return {guchenIndex:fixIndex(fixEarthlyBranchIndex(gu)), guasuIndex:fixIndex(fixEarthlyBranchIndex(gua))}
// 说明(getJieshaAdjIndex):(中州派用)按年支分四组返回劫杀固定宫位索引(0/3/6/9)。
function getJieshaAdjIndex(yearBranch):
if yearBranch in [shen,zi,chen]: return 3
if yearBranch in [hai,mao,wei]: return 6
if yearBranch in [yin,wu,xu]: return 9
if yearBranch in [si,you,chou]: return 0
// 说明(getNianjieIndex):按年支定年解:戌上起子逆数到太岁(源码用 12 项映射表实现)。
function getNianjieIndex(yearBranch):
branchIdx = indexOf(EARTHLY_BRANCHES, yearBranch)
map = ['xuEarthly','youEarthly','shenEarthly','weiEarthly','wuEarthly','siEarthly','chenEarthly','maoEarthly','yinEarthly','chouEarthly','ziEarthly','haiEarthly']
return fixIndex(fixEarthlyBranchIndex(map[branchIdx]))
// 说明(getDahaoIndex):(中州派用)按年支先取“对冲位”,再按阴阳移位规则折算到宫位索引(源码用映射表 + -2 对齐实现)。
function getDahaoIndex(yearBranch):
branchIdx = indexOf(EARTHLY_BRANCHES, yearBranch)
matched = ['weiEarthly','wuEarthly','youEarthly','shenEarthly','haiEarthly','xuEarthly','chouEarthly','ziEarthly','maoEarthly','yinEarthly','siEarthly','chenEarthly'][branchIdx]
return fixIndex(indexOf(EARTHLY_BRANCHES, matched) - 2)4.9 Step 7:长生12神 / 博士12神 / 流年12神
源码:src/star/decorativeStar.ts
4.9.1 长生12神
起点(按五行局数值):
- 2(水二局)→ 申
- 3(木三局)→ 亥
- 4(金四局)→ 巳
- 5(土五局)→ 申
- 6(火六局)→ 寅
序列:
['changsheng','muyu','guandai','linguan','diwang','shuai','bing','si','mu','jue','tai','yang']顺逆:若 genderYinYang == yearBranchYinYang 顺行,否则逆行。
4.9.2 博士12神
起点:禄存宫 luIndex。
序列:
['boshi','lishi','qinglong','xiaohao','jiangjun','zhoushu','faylian','xishen','bingfu','dahao','fubing','guanfu']顺逆同上。
4.9.3 流年12神(岁前/将前)
岁前12神:从 fixEarthlyBranchIndex(yearBranch) 起顺排。
algorithm=='zhongzhou':将dahao替换为suipo。
将前12神:先按年支确定将星起点:
- 寅午戌→午,申子辰→子,巳酉丑→酉,亥卯未→卯 再从该起点顺排固定序列。
5. 起大限/小限:getHoroscope
这一节负责把“时间(年龄)”映射为“宫位”:
- 大限:每 10 年一宫,从命宫起,按【性别阴阳 vs 年支阴阳】决定顺逆;并且会给每个大限配上对应的干支。
- 小限:每 1 年一宫,先由年支决定起点宫,再按男女顺逆把年龄序列分配到 12 宫。
源码:src/astro/palace.ts getHoroscope(param)
5.1 输入/输出
输入:AstrolabeParam
solarDate, timeIndex, gender, fixLeap- 可选:
from:{heavenlyStem, earthlyBranch}(地盘/人盘重排时使用;不传则按本命命宫干支)
输出:
decadals[12]:每宫对应的大限{ range:[startAge, startAge+9], heavenlyStem, earthlyBranch }ages[12]:每宫对应的小限年龄数组(10 个:1,13,25,...)
5.2 大限(十年一宫)精确算法
规则:
- 大限从命宫起,每十年一宫。
- 阳男阴女顺行,阴男阳女逆行。
- 起运年龄 = 五行局数值(2..6)。
源码等价伪代码:
说明(getHoroscope):同时计算大限与小限:大限以命宫起、每十年一宫,并给出每宫大限干支;小限按年支定起点宫后把年龄序列映射到 12 宫(男顺女逆)。
function getHoroscope(param):
decadals = array(12)
// 1) 生年干支(用于起大限干支)
yearly = getHeavenlyStemAndEarthlyBranchBySolarDate(param.solarDate, param.timeIndex, {year: config.yearDivide}).yearly
(yearStem, yearBranch) = yearly
// 2) 命/身宫(用于命宫起运)
(soulIndex, _, soulStem, soulBranch) = getSoulAndBody(param)
// 3) 五行局:若传 from 则以 from 起局,否则以命宫起局
baseStem = param.from?.heavenlyStem ?? soulStem
baseBranch = param.from?.earthlyBranch ?? soulBranch
fiveClass = getFiveElementsClass(baseStem, baseBranch)
startAge = FiveElementsClass[fiveClass] // 2..6
// 4) 判断顺逆:源码用 “性别阴阳 == 年支阴阳” 来判顺行
// yearBranchYinYang 可直接由 earthlyBranches[yearBranch].yinYang 获取
yearBranchYinYang = earthlyBranches[yearBranch].yinYang
genderYinYang = (param.gender == 'male') ? '阳' : '阴'
forward = (genderYinYang == yearBranchYinYang)
// 5) 五虎遁取“寅起始天干”(用于推每宫的大限干支)
startStem = TIGER_RULE[yearStem]
for i in 0..11:
idx = forward ? fixIndex(soulIndex + i) : fixIndex(soulIndex - i)
// i 为第几个大限(第0个、1个...),rangeStart 逐个 +10
rangeStart = startAge + 10*i
stemIndex = fixIndex(indexOf(HEAVENLY_STEMS, startStem) + idx, 10)
branchIndex = fixIndex(indexOf(EARTHLY_BRANCHES, 'yinEarthly') + idx, 12)
decadals[idx] = {
range: [rangeStart, rangeStart+9],
heavenlyStem: HEAVENLY_STEMS[stemIndex],
earthlyBranch: EARTHLY_BRANCHES[branchIndex]
}
// ages 在 5.3 计算
return {decadals, ages}5.3 小限(每年一宫)精确算法
5.3.1 年支定小限起点(getAgeIndex)
源码:src/utils/index.ts getAgeIndex(yearBranch)
说明(getAgeIndex):由年支确定小限起点宫(四局分组:寅午戌/申子辰/巳酉丑/亥卯未),返回宫位索引。
function getAgeIndex(yearBranch):
if yearBranch in [yin,wu,xu]: return fixEarthlyBranchIndex('chenEarthly')
if yearBranch in [shen,zi,chen]:return fixEarthlyBranchIndex('xuEarthly')
if yearBranch in [si,you,chou]: return fixEarthlyBranchIndex('weiEarthly')
if yearBranch in [hai,mao,wei]: return fixEarthlyBranchIndex('chouEarthly')5.3.2 将年龄序列映射到 12 宫
源码等价伪代码:
说明:这是小限“年龄序列 → 宫位”的最终映射:先得到起点宫 ageIdx,再为每个 i 生成年龄序列 [i+1, i+13, i+25, ...],最后按男顺女逆把序列放入 12 宫数组。
ageIdx = getAgeIndex(yearBranch)
ages = array(12)
for i in 0..11:
ageList = []
for j in 0..9:
ageList.push(12*j + i + 1)
// 男顺女逆(源码用 gender key 判断)
idx = (param.gender == 'male') ? fixIndex(ageIdx + i) : fixIndex(ageIdx - i)
ages[idx] = ageList6. bySolar:从输入到星盘对象的可复刻流程
上一节给了逐步公式,这一节把它串成真正可落地的“实现流程”。建议你实现时就按这里的顺序组织代码:先计算一次性的全局量(命/身宫、起星点、各类星曜数组、运限数组),再循环 12 次构造宫对象;这样最不容易写错索引。
源码:src/astro/astro.ts bySolar()
6.1 总览
你可以把 bySolar 视为 3 段:
- 计算所有“全局一次性结果”(命/身宫、星曜数组、运限数组等)
- 循环 12 次组装宫位对象(每宫天干地支 + 落星 + 运限)
- 组装星盘元信息(干支历、星座生肖、命主身主、五行局等)
6.2 关键步骤伪代码
说明:bySolar 是“天盘排盘编排器”的完整流水线伪代码:它串起生年干支、命身宫、各类安星函数、12神、大限小限,并最终组装成 12 宫数组与星盘元信息。
function bySolar(solarDate, timeIndex, gender, fixLeap=true):
// Step0: 晚子时日界
if config.dayDivide == 'current' and timeIndex >= 12:
tIndex = 0
else:
tIndex = timeIndex
// Step1: 生年干支(yearDivide)
yearly = getHeavenlyStemAndEarthlyBranchBySolarDate(solarDate, tIndex, {year: config.yearDivide}).yearly
(yearStem, yearBranch) = yearly
// Step2: 命/身宫与命宫干支
(soulIndex, bodyIndex, soulStem, soulBranch) = getSoulAndBody({solarDate, timeIndex:tIndex, gender, fixLeap})
// Step3: 12宫宫名(命宫旋转)
palaceNames = getPalaceNames(soulIndex)
// Step4: 星曜(每项返回 12 个数组,对应 12 宫)
majorStars = getMajorStar({solarDate, timeIndex:tIndex, fixLeap})
minorStars = getMinorStar(solarDate, tIndex, fixLeap)
adjectiveStars = getAdjectiveStar({solarDate, timeIndex:tIndex, gender, fixLeap})
// Step5: 12神
changsheng12 = getchangsheng12({solarDate, timeIndex:tIndex, gender, fixLeap})
boshi12 = getBoShi12(solarDate, gender)
(jiangqian12, suiqian12) = getYearly12(solarDate)
// Step6: 大限/小限
(decadals, ages) = getHoroscope({solarDate, timeIndex:tIndex, gender, fixLeap})
// Step7: 循环生成 12 宫
palaces = []
for i in 0..11:
palaceStem = HEAVENLY_STEMS[ fixIndex(indexOf(HEAVENLY_STEMS, soulStem) - soulIndex + i, 10) ]
palaceBranch = EARTHLY_BRANCHES[ fixIndex(2 + i, 12) ]
palaces.push({
index: i,
name: palaceNames[i],
isBodyPalace: (i == bodyIndex),
isOriginalPalace: (palaceBranch not in ['ziEarthly','chouEarthly']) and (palaceStem == yearStem),
heavenlyStem: palaceStem,
earthlyBranch: palaceBranch,
majorStars: majorStars[i],
minorStars: minorStars[i],
adjectiveStars: adjectiveStars[i],
changsheng12: changsheng12[i],
boshi12: boshi12[i],
jiangqian12: jiangqian12[i],
suiqian12: suiqian12[i],
decadal: decadals[i],
ages: ages[i]
})
// Step8: 命宫/身宫地支(注意:宫位从寅起,所以 +2 对齐)
soulPalaceBranch = EARTHLY_BRANCHES[fixIndex(soulIndex + 2)]
bodyPalaceBranch = EARTHLY_BRANCHES[fixIndex(bodyIndex + 2)]
// Step9: 命主/身主
if config.algorithm == 'zhongzhou':
soulStar = earthlyBranches[yearBranch].soul
else:
soulStar = earthlyBranches[soulPalaceBranch].soul
bodyStar = earthlyBranches[yearBranch].body
// Step10: 五行局(以命宫干支)
fiveElementsClass = getFiveElementsClass(soulStem, soulBranch)
return Astrolabe{...}7. 命主/身主(soul/body)
源码:src/data/earthlyBranches.ts + src/astro/astro.ts
- 身主:按生年支:
earthlyBranches[yearBranch].body - 命主:
- 中州派:按生年支(
earthlyBranches[yearBranch].soul) - 通行:按命宫地支(
earthlyBranches[soulPalaceBranch].soul)
- 中州派:按生年支(
这两个字段本质都是“星 key”,其映射表在
src/data/earthlyBranches.ts。
8. 四化与亮度(庙旺落陷)可复刻实现
四化与亮度都是“附着在星曜上的属性”:
- 四化:由【某个天干】决定一组【禄/权/科/忌 对应的四颗星】,然后星曜会根据自己是否在该组中得到对应化。
- 亮度:由【星曜在 12 宫中的位置】决定其状态(庙旺落陷等)。
源码:src/utils/index.ts + src/data/heavenlyStems.ts + src/data/stars.ts
8.1 四化表(禄权科忌)
默认:heavenlyStems[stem].mutagen(长度固定 4,对应【禄 权 科 忌】)
可覆盖:config.mutagens[stem]
说明(getTargetMutagens):四化配置读取:优先使用 config.mutagens 覆盖表,否则回退到默认 heavenlyStems[stem].mutagen。输出为长度 4 的星曜数组(禄权科忌对应星)。
function getTargetMutagens(stem):
return config.mutagens[stem] ?? heavenlyStems[stem].mutagen
// 说明(getMutagen):给定【星曜 + 天干】,判断该星在四化表中的位置并返回对应化(禄/权/科/忌);不在表内返回空。
function getMutagen(starKey, stem):
target = getTargetMutagens(stem) // 4 items
idx = indexOf(target, starKey)
if idx < 0: return ''
return MUTAGEN[idx] // MUTAGEN 的顺序与源码一致:['lu','quan','ke','ji'](实现时只需保持同序)
// 说明(getMutagensByHeavenlyStem):直接返回某天干的四化“对应星曜”列表(长度 4,顺序禄权科忌)。
function getMutagensByHeavenlyStem(stem):
return getTargetMutagens(stem)8.2 亮度表
默认:STARS_INFO[star].brightness[12]
可覆盖:config.brightness[star]
说明(getBrightness):根据【星曜 + 宫位索引】读取亮度数组(可被 config.brightness 覆盖),返回该宫位的亮度字符串。
function getBrightness(starKey, palaceIndex):
arr = config.brightness[starKey] ?? STARS_INFO[starKey].brightness
if arr is empty: return ''
return arr[fixIndex(palaceIndex)]9. 运限(大限/小限/流年/流月/流日/流时)完整算法
运限不是重新排一张盘,而是在本命盘的 12 宫坐标系上:对给定目标日期,算出此刻的【虚岁】、落在哪个【大限/小限宫】、以及【流年/月/日/时】各自落宫。落宫索引算出后,再用“以该宫为命宫旋转的宫名表”生成该运限的十二宫命名,并叠加对应的“流耀”。
源码:src/astro/FunctionalAstrolabe.ts _getHoroscopeBySolarDate()
9.1 输入
- 本命盘对象
astrolabe(含solarDate与rawDates) targetDate(默认当前时间)- 可选
timeIndex(不传则timeToIndex(hour))
9.2 目标日期干支(horoscopeDivide)
说明:运限计算的第一步:把目标日期时间转换为时辰索引(或使用调用方显式传入的时辰),然后按 horoscopeDivide 取目标日期的年/月/日/时干支。
convertTimeIndex = timeToIndex(hour(targetDate))
useTimeIndex = timeIndex ?? convertTimeIndex
pillars = getHeavenlyStemAndEarthlyBranchBySolarDate(targetDate, useTimeIndex, {
year: config.horoscopeDivide,
month: config.horoscopeDivide
})
(yearly, monthly, daily, hourly) = (pillars.yearly, pillars.monthly, pillars.daily, pillars.hourly)9.3 虚岁 nominalAge(ageDivide)
源码等价伪代码:
说明:虚岁计算:先把出生与目标日期都转为农历,再按配置决定“自然年+1”或“过了农历生日才+1”。输出 nominalAge 用于定位大限/小限。
birth = solar2lunar(astrolabe.solarDate)
date = solar2lunar(targetDate)
nominalAge = date.lunarYear - birth.lunarYear
if config.ageDivide == 'birthday':
// 过了农历生日才 +1
if (
(date.lunarYear == birth.lunarYear and date.lunarMonth == birth.lunarMonth and date.lunarDay > birth.lunarDay)
or (date.lunarMonth > birth.lunarMonth)
):
nominalAge += 1
else:
// 自然年分界直接 +1
nominalAge += 19.4 大限落宫(含童限 fallback)
说明:以下伪代码给出该步骤的精确计算公式与中间变量含义;即使不实现同名函数,也应保持等价的输入输出关系与取模规则。
// 1) 先按本命盘里每宫的 decadal.range 查找
find palaceIndex such that nominalAge in astrolabe.palaces[palaceIndex].decadal.range
if not found:
// 2) 童限:1..6 岁映射到固定宫名
mapping = ['命宫','财帛','疾厄','夫妻','福德','官禄']
palaceName = mapping[nominalAge - 1]
palaceIndex = astrolabe.palace(palaceName).index
isChildhood = true
else:
isChildhood = false9.5 小限落宫
说明:小限定位:在本命盘 12 宫中查找哪个宫的 ages 序列包含 nominalAge,该宫即为当年的小限宫。
find ageIndex such that nominalAge in astrolabe.palaces[ageIndex].ages9.6 流年/流月/流日/流时落宫索引
说明:流年/流月/流日/流时落宫:流年由目标年支直接折算索引;流月用“流年地支逆数到生月宫,再顺数到生时宫为正月起点”的公式实现,并额外处理出生与当月的闰月前后半月;流日/流时在流月基础上继续顺推。
yearlyIndex = fixEarthlyBranchIndex(yearly[1])
birthLeapAdd = (birth.isLeap and birth.lunarDay > 15) ? 1 : 0
currLeapAdd = (date.isLeap and date.lunarDay > 15) ? 1 : 0
birthHourBranch = astrolabe.rawDates.chineseDate.hourly[1]
monthlyIndex = fixIndex(
yearlyIndex
- (birth.lunarMonth + birthLeapAdd)
+ indexOf(EARTHLY_BRANCHES, birthHourBranch)
+ (date.lunarMonth + currLeapAdd)
)
dailyIndex = fixIndex(monthlyIndex + date.lunarDay - 1)
hourlyIndex = fixIndex(dailyIndex + indexOf(EARTHLY_BRANCHES, hourly[1]))9.7 运限“十二宫旋转”与四化星
任何 scope(大限/小限/流年/月/日/时)得到 index 后:
说明:以下伪代码给出该步骤的精确计算公式与中间变量含义;即使不实现同名函数,也应保持等价的输入输出关系与取模规则。
palaceNames = getPalaceNames(index)
mutagenStars = getMutagensByHeavenlyStem(scopeStem)9.8 运限流耀(魁钺昌曲禄羊陀马鸾喜)
源码:src/star/horoscopeStar.ts
核心:索引算法与“辅星索引”一致,但“流昌流曲”改为按天干映射(不是按时辰)。
9.8.1 getChangQuIndexByHeavenlyStem(stem)
源码:src/star/location.ts getChangQuIndexByHeavenlyStem
说明:以下伪代码给出该步骤的精确计算公式与中间变量含义;即使不实现同名函数,也应保持等价的输入输出关系与取模规则。
if stem==jia: chang=si, qu=you
if stem==yi: chang=wu, qu=shen
if stem in [bing,wu]: chang=shen, qu=wu
if stem in [ding,ji]: chang=you, qu=si
if stem==geng: chang=hai, qu=mao
if stem==xin: chang=zi, qu=yin
if stem==ren: chang=yin, qu=zi
if stem==gui: chang=mao, qu=hai
return {changIndex:fixIndex(fixEarthlyBranchIndex(chang)), quIndex:fixIndex(fixEarthlyBranchIndex(qu))}9.8.2 getHoroscopeStar(stem, branch, scope)
索引来源:
- 魁/钺:
getKuiYueIndex(stem) - 昌/曲:
getChangQuIndexByHeavenlyStem(stem) - 禄/羊/陀/马:
getLuYangTuoMaIndex(stem, branch) - 鸾/喜:
getLuanXiIndex(branch)
特别:若 scope == 'yearly',还会额外加入“年解”:
nianjieIndex = getNianjieIndex(branch)
注意:源码对不同 scope 只是改变“星名显示前缀”(运昌/流昌/日昌等);落宫索引完全一致。
9.9 运限对象最终结构
- decadal/yearly/monthly/daily/hourly:包含
stars = getHoroscopeStar(scopeStem, scopeBranch, scope) - age:不带
stars - yearly:额外带
yearlyDecStar = getYearly12(targetDate)
10. 地盘/人盘重排(中州派扩展)
地盘/人盘可以理解为:不改变出生时间,但更换“五行局起点的干支”,从而让主星/长生/大限等围绕新的命宫重新计算。实现上是“先生成天盘,再选择某宫(身宫/福德)作为新的命宫起点,覆盖更新部分字段”。
源码:withOptions() + rearrangeAstrolable()
- 地盘:取身宫干支作为
from,重排 - 人盘:取福德宫干支作为
from,重排
重排覆盖:宫名、主星、长生12神、大限/小限、身宫标记、五行局等(与源码一致)。
11. 源码速查
- 天盘入口:
src/astro/astro.tsbySolar() - 命/身宫、五行局、大限/小限:
src/astro/palace.ts - 主星:
src/star/majorStar.ts - 辅星/杂耀索引:
src/star/location.ts - 辅星:
src/star/minorStar.ts - 杂耀组装:
src/star/adjectiveStar.ts - 长生/博士/流年12神:
src/star/decorativeStar.ts - 运限:
src/astro/FunctionalAstrolabe.ts - 运限流耀:
src/star/horoscopeStar.ts