use crate::data::stars::StarKey;
use crate::data::types::{EarthlyBranch, Mutagen, Palace};
use crate::models::star::Star;
use crate::pattern::view::{JI6, KONG_WANG};
use crate::pattern::{ChartView, PatternHit, PatternKey, Rule};
pub static RULES: &[Rule] = &[
Rule {
key: PatternKey::RiYueTongGong,
detect: ri_yue_tong_gong,
},
Rule {
key: PatternKey::JuRiTongGong,
detect: ju_ri_tong_gong,
},
Rule {
key: PatternKey::RiZhaoLeiMen,
detect: ri_zhao_lei_men,
},
Rule {
key: PatternKey::RiYueBingMing,
detect: ri_yue_bing_ming,
},
Rule {
key: PatternKey::RiYueFanBei,
detect: ri_yue_fan_bei,
},
Rule {
key: PatternKey::RiYueZhaoBi,
detect: ri_yue_zhao_bi,
},
Rule {
key: PatternKey::JinCanGuangHui,
detect: jin_can_guang_hui,
},
Rule {
key: PatternKey::RiYueCangHui,
detect: ri_yue_cang_hui,
},
Rule {
key: PatternKey::DanChiGuiChi,
detect: dan_chi_gui_chi,
},
Rule {
key: PatternKey::RiYueJiaMing,
detect: ri_yue_jia_ming,
},
Rule {
key: PatternKey::RiYueJiaCai,
detect: ri_yue_jia_cai,
},
Rule {
key: PatternKey::YueLangTianMen,
detect: yue_lang_tian_men,
},
Rule {
key: PatternKey::YueShengCangHai,
detect: yue_sheng_cang_hai,
},
Rule {
key: PatternKey::MingZhuChuHai,
detect: ming_zhu_chu_hai,
},
];
fn sun_and_moon<'a>(v: &ChartView<'a>, s: usize, bright: bool) -> Option<[(usize, &'a Star); 2]> {
let ok = |i: usize, k: StarKey| {
if bright {
v.sun_moon_bright(i, k)
} else {
v.sun_moon_dark(i, k)
}
};
let sun = v
.find_in_surround(s, StarKey::TaiyangMaj)
.filter(|(p, _)| ok(*p, StarKey::TaiyangMaj))?;
let moon = v
.find_in_surround(s, StarKey::TaiyinMaj)
.filter(|(p, _)| ok(*p, StarKey::TaiyinMaj))?;
Some([sun, moon])
}
fn auspicious<'a>(v: &ChartView<'a>, i: usize) -> Option<&'a Star> {
JI6.iter().find_map(|k| v.find(i, *k)).or_else(|| {
v.stars(i).find(|s| {
matches!(
v.mutagen_of(s),
Some(Mutagen::Lu | Mutagen::Quan | Mutagen::Ke)
)
})
})
}
fn ri_yue_jia(v: &ChartView, key: PatternKey, i: usize) -> Vec<PatternHit> {
let Some((ps, pm)) = v.jia(i, &[StarKey::TaiyangMaj], &[StarKey::TaiyinMaj]) else {
return vec![];
};
if v.has_any(i, &KONG_WANG) {
return vec![];
}
let (Some(sun), Some(moon), Some(ji)) = (
v.find(ps, StarKey::TaiyangMaj),
v.find(pm, StarKey::TaiyinMaj),
auspicious(v, i),
) else {
return vec![];
};
vec![v.hit(key, i, vec![(ps, sun), (pm, moon), (i, ji)])]
}
pub fn ri_yue_tong_gong(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
match (
v.find(s, StarKey::TaiyangMaj),
v.find(s, StarKey::TaiyinMaj),
) {
(Some(sun), Some(moon)) => {
vec![v.hit(PatternKey::RiYueTongGong, s, vec![(s, sun), (s, moon)])]
}
_ => vec![],
}
}
pub fn ju_ri_tong_gong(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
match (v.find(s, StarKey::TaiyangMaj), v.find(s, StarKey::JumenMaj)) {
(Some(sun), Some(ju)) => {
vec![v.hit(PatternKey::JuRiTongGong, s, vec![(s, sun), (s, ju)])]
}
_ => vec![],
}
}
pub fn ri_zhao_lei_men(v: &ChartView) -> Vec<PatternHit> {
[
(v.soul(), None),
(v.index_of(Palace::Career), Some("career")),
]
.into_iter()
.filter(|(i, _)| v.branch(*i) == EarthlyBranch::Mao)
.filter_map(|(i, variant)| {
let (sun, liang) = (
v.find(i, StarKey::TaiyangMaj)?,
v.find(i, StarKey::TianliangMaj)?,
);
let mut hit = v.hit(PatternKey::RiZhaoLeiMen, i, vec![(i, sun), (i, liang)]);
hit.variant = variant;
Some(hit)
})
.collect()
}
pub fn ri_yue_bing_ming(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
match sun_and_moon(v, s, true) {
Some(pair) => vec![v.hit(PatternKey::RiYueBingMing, s, pair.to_vec())],
None => vec![],
}
}
pub fn ri_yue_fan_bei(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
match sun_and_moon(v, s, false) {
Some(pair) => vec![v.hit(PatternKey::RiYueFanBei, s, pair.to_vec())],
None => vec![],
}
}
pub fn ri_yue_zhao_bi(v: &ChartView) -> Vec<PatternHit> {
let p = v.index_of(Palace::Property);
match (
v.find(p, StarKey::TaiyangMaj),
v.find(p, StarKey::TaiyinMaj),
) {
(Some(sun), Some(moon)) => {
vec![v.hit(PatternKey::RiYueZhaoBi, p, vec![(p, sun), (p, moon)])]
}
_ => vec![],
}
}
pub fn jin_can_guang_hui(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
if v.branch(s) != EarthlyBranch::Wu || !v.alone(s, StarKey::TaiyangMaj) {
return vec![];
}
let Some(sun) = v.find(s, StarKey::TaiyangMaj) else {
return vec![];
};
let mut hit = v.hit(PatternKey::JinCanGuangHui, s, vec![(s, sun)]);
hit.broken = !v.no_sha(s);
vec![hit]
}
pub fn ri_yue_cang_hui(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
let (Some(pair), Some(ju)) = (
sun_and_moon(v, s, false),
v.find_in_surround(s, StarKey::JumenMaj),
) else {
return vec![];
};
let mut stars = pair.to_vec();
stars.push(ju);
vec![v.hit(PatternKey::RiYueCangHui, s, stars)]
}
pub fn dan_chi_gui_chi(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
let in_soul =
v.sun_moon_bright(s, StarKey::TaiyangMaj) || v.sun_moon_bright(s, StarKey::TaiyinMaj);
match sun_and_moon(v, s, true) {
Some(pair) if in_soul => vec![v.hit(PatternKey::DanChiGuiChi, s, pair.to_vec())],
_ => vec![],
}
}
pub fn ri_yue_jia_ming(v: &ChartView) -> Vec<PatternHit> {
ri_yue_jia(v, PatternKey::RiYueJiaMing, v.soul())
}
pub fn ri_yue_jia_cai(v: &ChartView) -> Vec<PatternHit> {
ri_yue_jia(v, PatternKey::RiYueJiaCai, v.index_of(Palace::Wealth))
}
pub fn yue_lang_tian_men(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
match v.find(s, StarKey::TaiyinMaj) {
Some(moon) if v.branch(s) == EarthlyBranch::Hai => {
vec![v.hit(PatternKey::YueLangTianMen, s, vec![(s, moon)])]
}
_ => vec![],
}
}
pub fn yue_sheng_cang_hai(v: &ChartView) -> Vec<PatternHit> {
[
(v.soul(), "soul"),
(v.index_of(Palace::Property), "property"),
]
.into_iter()
.filter(|(i, _)| v.branch(*i) == EarthlyBranch::Zi)
.filter_map(|(i, variant)| {
let (moon, tong) = (
v.find(i, StarKey::TaiyinMaj)?,
v.find(i, StarKey::TiantongMaj)?,
);
let mut hit = v.hit(PatternKey::YueShengCangHai, i, vec![(i, moon), (i, tong)]);
hit.variant = Some(variant);
Some(hit)
})
.collect()
}
pub fn ming_zhu_chu_hai(v: &ChartView) -> Vec<PatternHit> {
let s = v.soul();
if v.branch(s) != EarthlyBranch::Wei || !v.is_empty(s) {
return vec![];
}
let o = v.opposite(s);
let (Some(tong), Some(ju)) = (
v.find(o, StarKey::TiantongMaj),
v.find(o, StarKey::JumenMaj),
) else {
return vec![];
};
let mut stars = vec![(o, tong), (o, ju)];
stars.extend(v.find_in_surround(s, StarKey::TaiyangMaj));
stars.extend(v.find_in_surround(s, StarKey::TaiyinMaj));
vec![v.hit(PatternKey::MingZhuChuHai, s, stars)]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::data::types::Brightness;
use crate::data::types::EarthlyBranch::*;
use crate::models::astrolabe::Astrolabe;
use crate::pattern::testutil::find_chart;
use crate::pattern::{BrightnessSource, PatternConfig};
const CFG: PatternConfig = PatternConfig {
brightness_source: BrightnessSource::Table,
borrow: true,
flow_stars: true,
};
fn view(a: &Astrolabe) -> ChartView<'_> {
ChartView::natal(a, &CFG)
}
fn miao_wang(v: &ChartView, s: usize, key: StarKey) -> bool {
v.find_in_surround(s, key)
.is_some_and(|(_, star)| star.with_brightness(&[Brightness::Miao, Brightness::Wang]))
}
fn xian(v: &ChartView, s: usize, key: StarKey) -> bool {
v.find_in_surround(s, key)
.is_some_and(|(_, star)| star.with_brightness(&[Brightness::Xian, Brightness::Bu]))
}
#[test]
fn ri_yue_tong_gong_needs_both_in_soul() {
let a = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
v.has(s, StarKey::TaiyangMaj) && v.has(s, StarKey::TaiyinMaj)
});
let v = view(&a);
let hits = ri_yue_tong_gong(&v);
let hit = hits.first().expect("hit");
assert_eq!(hit.palace, v.soul());
assert!(matches!(v.branch(v.soul()), Chou | Wei));
assert_eq!(hit.stars.len(), 2);
assert!(hit.stars.iter().all(|s| s.palace == v.soul()));
let b = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
v.has(s, StarKey::TaiyangMaj) && !v.has(s, StarKey::TaiyinMaj)
});
assert!(ri_yue_tong_gong(&view(&b)).is_empty());
}
#[test]
fn ju_ri_tong_gong_needs_sun_and_jumen_in_soul() {
let a = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
v.has(s, StarKey::TaiyangMaj) && v.has(s, StarKey::JumenMaj)
});
let v = view(&a);
let hits = ju_ri_tong_gong(&v);
assert_eq!(hits.first().expect("hit").palace, v.soul());
assert!(matches!(v.branch(v.soul()), Yin | Shen));
let b = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
v.has(s, StarKey::TaiyangMaj) && v.has(s, StarKey::TianliangMaj)
});
assert!(ju_ri_tong_gong(&view(&b)).is_empty());
}
#[test]
fn ri_zhao_lei_men_only_at_mao() {
let a = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
v.branch(s) == Mao && v.has(s, StarKey::TaiyangMaj) && v.has(s, StarKey::TianliangMaj)
});
let v = view(&a);
let hits = ri_zhao_lei_men(&v);
assert_eq!(hits.first().expect("hit").palace, v.soul());
let b = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
v.branch(s) == You && v.has(s, StarKey::TaiyangMaj) && v.has(s, StarKey::TianliangMaj)
});
assert!(ri_zhao_lei_men(&view(&b)).is_empty());
}
#[test]
fn ri_zhao_lei_men_career_variant() {
let a = find_chart(|a| {
let v = view(a);
let c = v.index_of(Palace::Career);
v.branch(c) == Mao && v.has(c, StarKey::TaiyangMaj) && v.has(c, StarKey::TianliangMaj)
});
let v = view(&a);
let hits = ri_zhao_lei_men(&v);
let hit = hits.first().expect("hit");
assert_eq!(hit.variant, Some("career"));
assert_eq!(hit.palace, v.index_of(Palace::Career));
assert_ne!(hit.palace, v.soul());
assert!(hit.stars.iter().all(|s| s.palace == hit.palace));
}
fn only_flow_auspicious(v: &ChartView, i: usize) -> bool {
!v.stars(i).any(|s| JI6.contains(&s.key))
&& !v.stars(i).any(|s| {
matches!(
v.mutagen_of(s),
Some(Mutagen::Lu | Mutagen::Quan | Mutagen::Ke)
)
})
&& auspicious(v, i).is_some()
}
#[test]
fn auspicious_accepts_flow_counterparts_in_horoscope_view() {
use crate::data::types::Scope;
let a = find_chart(|a| {
let Ok(h) = a.horoscope("2026-8-19", 3) else {
return false;
};
let v = ChartView::at(a, h.data(), Scope::Yearly, &CFG);
(0..12).any(|i| only_flow_auspicious(&v, i))
});
let h = a.horoscope("2026-8-19", 3).unwrap();
let v = ChartView::at(&a, h.data(), Scope::Yearly, &CFG);
let i = (0..12).find(|i| only_flow_auspicious(&v, *i)).unwrap();
let star = auspicious(&v, i).expect("flow auspicious");
assert!(!JI6.contains(&star.key));
}
#[test]
fn ri_yue_bing_ming_needs_both_bright_in_surround() {
let a = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
miao_wang(&v, s, StarKey::TaiyangMaj) && miao_wang(&v, s, StarKey::TaiyinMaj)
});
let v = view(&a);
let hits = ri_yue_bing_ming(&v);
let hit = hits.first().expect("hit");
assert_eq!(hit.palace, v.soul());
assert!(
hit.stars
.iter()
.all(|s| v.surround(v.soul()).contains(&s.palace))
);
let b = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
miao_wang(&v, s, StarKey::TaiyangMaj) && !miao_wang(&v, s, StarKey::TaiyinMaj)
});
assert!(ri_yue_bing_ming(&view(&b)).is_empty());
}
#[test]
fn ri_yue_bing_ming_moon_at_you_differs_by_brightness_source() {
let a = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
let Some((pm, _)) = v.find_in_surround(s, StarKey::TaiyinMaj) else {
return false;
};
v.branch(pm) == You && miao_wang(&v, s, StarKey::TaiyangMaj)
});
assert!(ri_yue_bing_ming(&view(&a)).is_empty());
let positional = PatternConfig {
brightness_source: BrightnessSource::Positional,
..CFG
};
let v = ChartView::natal(&a, &positional);
let hits = ri_yue_bing_ming(&v);
let hit = hits.first().expect("hit under positional brightness");
let moon = hit
.stars
.iter()
.find(|s| s.star == StarKey::TaiyinMaj)
.expect("moon");
assert_eq!(v.branch(moon.palace), You);
}
#[test]
fn ri_yue_fan_bei_needs_both_dark_in_surround() {
let a = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
xian(&v, s, StarKey::TaiyangMaj) && xian(&v, s, StarKey::TaiyinMaj)
});
let v = view(&a);
assert_eq!(ri_yue_fan_bei(&v).first().expect("hit").palace, v.soul());
let b = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
xian(&v, s, StarKey::TaiyangMaj) && !xian(&v, s, StarKey::TaiyinMaj)
});
assert!(ri_yue_fan_bei(&view(&b)).is_empty());
}
#[test]
fn ri_yue_zhao_bi_reports_property_palace() {
let a = find_chart(|a| {
let v = view(a);
let p = v.index_of(Palace::Property);
v.has(p, StarKey::TaiyangMaj) && v.has(p, StarKey::TaiyinMaj)
});
let v = view(&a);
let hits = ri_yue_zhao_bi(&v);
let hit = hits.first().expect("hit");
assert_eq!(hit.palace, v.index_of(Palace::Property));
assert_ne!(hit.palace, v.soul());
let b = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
v.has(s, StarKey::TaiyangMaj) && v.has(s, StarKey::TaiyinMaj)
});
assert!(ri_yue_zhao_bi(&view(&b)).is_empty());
}
#[test]
fn jin_can_guang_hui_marks_broken_by_sha() {
let a = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
v.branch(s) == Wu && v.alone(s, StarKey::TaiyangMaj) && !v.no_sha(s)
});
let v = view(&a);
let hits = jin_can_guang_hui(&v);
let hit = hits.first().expect("hit");
assert_eq!(hit.palace, v.soul());
assert!(hit.broken);
let b = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
v.branch(s) == Wu && v.alone(s, StarKey::TaiyangMaj) && v.no_sha(s)
});
assert!(!jin_can_guang_hui(&view(&b)).first().expect("hit").broken);
let c = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
v.branch(s) == Chen && v.alone(s, StarKey::TaiyangMaj)
});
assert!(jin_can_guang_hui(&view(&c)).is_empty());
}
#[test]
fn ri_yue_cang_hui_needs_fan_bei_plus_jumen() {
let a = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
xian(&v, s, StarKey::TaiyangMaj)
&& xian(&v, s, StarKey::TaiyinMaj)
&& v.in_surround(s, StarKey::JumenMaj)
});
let v = view(&a);
let hits = ri_yue_cang_hui(&v);
let hit = hits.first().expect("hit");
assert_eq!(hit.stars.len(), 3);
assert!(hit.stars.iter().any(|s| s.star == StarKey::JumenMaj));
let b = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
xian(&v, s, StarKey::TaiyangMaj)
&& xian(&v, s, StarKey::TaiyinMaj)
&& !v.in_surround(s, StarKey::JumenMaj)
});
assert!(ri_yue_cang_hui(&view(&b)).is_empty());
}
#[test]
fn dan_chi_gui_chi_needs_bright_sun_or_moon_in_soul() {
let a = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
miao_wang(&v, s, StarKey::TaiyangMaj)
&& miao_wang(&v, s, StarKey::TaiyinMaj)
&& (v.has(s, StarKey::TaiyangMaj) || v.has(s, StarKey::TaiyinMaj))
});
let v = view(&a);
assert_eq!(dan_chi_gui_chi(&v).first().expect("hit").palace, v.soul());
let b = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
miao_wang(&v, s, StarKey::TaiyangMaj)
&& miao_wang(&v, s, StarKey::TaiyinMaj)
&& !v.has(s, StarKey::TaiyangMaj)
&& !v.has(s, StarKey::TaiyinMaj)
});
assert!(!ri_yue_bing_ming(&view(&b)).is_empty());
assert!(dan_chi_gui_chi(&view(&b)).is_empty());
}
#[test]
fn ri_yue_jia_ming_needs_no_kongwang_and_an_auspicious_star() {
let a = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
v.jia(s, &[StarKey::TaiyangMaj], &[StarKey::TaiyinMaj])
.is_some()
&& !v.has_any(s, &KONG_WANG)
&& auspicious(&v, s).is_some()
});
let v = view(&a);
let hits = ri_yue_jia_ming(&v);
let hit = hits.first().expect("hit");
assert_eq!(hit.palace, v.soul());
let palaces: Vec<_> = hit.stars.iter().map(|s| s.palace).collect();
assert!(palaces.contains(&v.prev(v.soul())) && palaces.contains(&v.next(v.soul())));
let b = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
v.jia(s, &[StarKey::TaiyangMaj], &[StarKey::TaiyinMaj])
.is_some()
&& v.has_any(s, &KONG_WANG)
});
assert!(ri_yue_jia_ming(&view(&b)).is_empty());
}
#[test]
fn ri_yue_jia_cai_reports_wealth_palace() {
let a = find_chart(|a| {
let v = view(a);
let w = v.index_of(Palace::Wealth);
v.jia(w, &[StarKey::TaiyangMaj], &[StarKey::TaiyinMaj])
.is_some()
&& !v.has_any(w, &KONG_WANG)
&& auspicious(&v, w).is_some()
});
let v = view(&a);
let hits = ri_yue_jia_cai(&v);
assert_eq!(
hits.first().expect("hit").palace,
v.index_of(Palace::Wealth)
);
let b = find_chart(|a| {
let v = view(a);
let w = v.index_of(Palace::Wealth);
v.jia(w, &[StarKey::TaiyangMaj], &[StarKey::TaiyinMaj])
.is_some()
&& v.has_any(w, &KONG_WANG)
});
assert!(ri_yue_jia_cai(&view(&b)).is_empty());
}
#[test]
fn yue_lang_tian_men_only_at_hai() {
let a = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
v.branch(s) == Hai && v.has(s, StarKey::TaiyinMaj)
});
let v = view(&a);
let hits = yue_lang_tian_men(&v);
assert_eq!(hits.first().expect("hit").stars[0].star, StarKey::TaiyinMaj);
let b = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
v.branch(s) == Zi && v.has(s, StarKey::TaiyinMaj)
});
assert!(yue_lang_tian_men(&view(&b)).is_empty());
}
#[test]
fn yue_sheng_cang_hai_reports_soul_and_property_variants() {
let a = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
v.branch(s) == Zi && v.has(s, StarKey::TaiyinMaj) && v.has(s, StarKey::TiantongMaj)
});
let v = view(&a);
let hits = yue_sheng_cang_hai(&v);
let hit = hits.first().expect("hit");
assert_eq!(hit.variant, Some("soul"));
assert_eq!(hit.palace, v.soul());
let b = find_chart(|a| {
let v = view(a);
let p = v.index_of(Palace::Property);
v.branch(p) == Zi && v.has(p, StarKey::TaiyinMaj) && v.has(p, StarKey::TiantongMaj)
});
let v = view(&b);
let hits = yue_sheng_cang_hai(&v);
let hit = hits.first().expect("hit");
assert_eq!(hit.variant, Some("property"));
assert_eq!(hit.palace, v.index_of(Palace::Property));
let c = find_chart(|a| {
let v = view(a);
let zi = (0..12).find(|i| v.branch(*i) == Zi).unwrap();
v.has(zi, StarKey::TaiyinMaj)
&& v.has(zi, StarKey::TiantongMaj)
&& zi != v.soul()
&& zi != v.index_of(Palace::Property)
});
assert!(yue_sheng_cang_hai(&view(&c)).is_empty());
}
#[test]
fn ming_zhu_chu_hai_needs_empty_wei_soul_facing_tong_ju() {
let a = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
v.branch(s) == Wei
&& v.is_empty(s)
&& v.has(v.opposite(s), StarKey::TiantongMaj)
&& v.has(v.opposite(s), StarKey::JumenMaj)
});
let v = view(&a);
let hits = ming_zhu_chu_hai(&v);
let hit = hits.first().expect("hit");
assert_eq!(hit.palace, v.soul());
let sun = hit
.stars
.iter()
.find(|s| s.star == StarKey::TaiyangMaj)
.expect("sun");
let moon = hit
.stars
.iter()
.find(|s| s.star == StarKey::TaiyinMaj)
.expect("moon");
assert_eq!(v.branch(sun.palace), Mao);
assert_eq!(v.branch(moon.palace), Hai);
let b = find_chart(|a| {
let (v, s) = (view(a), view(a).soul());
v.branch(s) == Chou
&& v.is_empty(s)
&& v.has(v.opposite(s), StarKey::TiantongMaj)
&& v.has(v.opposite(s), StarKey::JumenMaj)
});
assert!(ming_zhu_chu_hai(&view(&b)).is_empty());
}
}