feat(tahuantinsuyu): fase 27 — Lots helenísticos + 9 fixed stars
Dos módulos astrológicos pluggables más: - LotsModule: 7 Arabic Parts vía `all_lots(natal)` (Fortune, Spirit, Eros, Necessity, Courage, Victory, Nemesis). Glifos `lot:Fo` en ring 0.54, hover muestra el nombre completo. - FixedStarsModule: 9 estrellas notables (Aldebaran, Regulus, Antares, Fomalhaut, Spica, Sirius, Algol, Vega, Pollux) con longitudes tropicales J2000 + precesión general de 50.29″/año proyectada al año natal. Marcadores `✦Xxx` en ring 1.04. Registry pasa de 9 a 11 módulos; test actualizado. Sin cambios de esquema en RenderModel — los `LayerKind::Lots` y `LayerKind::FixedStars` ya existían. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -383,6 +383,12 @@ impl Shell {
|
|||||||
if module_enabled(&self.module_configs, "uranian") {
|
if module_enabled(&self.module_configs, "uranian") {
|
||||||
requests.push(PipelineRequest::Uranian);
|
requests.push(PipelineRequest::Uranian);
|
||||||
}
|
}
|
||||||
|
if module_enabled(&self.module_configs, "lots") {
|
||||||
|
requests.push(PipelineRequest::Lots);
|
||||||
|
}
|
||||||
|
if module_enabled(&self.module_configs, "fixed_stars") {
|
||||||
|
requests.push(PipelineRequest::FixedStars);
|
||||||
|
}
|
||||||
if module_enabled(&self.module_configs, "composite") {
|
if module_enabled(&self.module_configs, "composite") {
|
||||||
if let Some(partner) = self.resolve_composite_partner() {
|
if let Some(partner) = self.resolve_composite_partner() {
|
||||||
requests.push(PipelineRequest::Composite {
|
requests.push(PipelineRequest::Composite {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use std::sync::OnceLock;
|
|||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use eternal_astrology::{
|
use eternal_astrology::{
|
||||||
composite, find_aspects, find_synastry_aspects, next_return, secondary_progression,
|
all_lots, composite, find_aspects, find_synastry_aspects, next_return, secondary_progression,
|
||||||
solar_arc_true, Aspect, AspectKind as EAspectKind, BirthData, BodySet, ChartConfig,
|
solar_arc_true, Aspect, AspectKind as EAspectKind, BirthData, BodySet, ChartConfig,
|
||||||
HouseSystem as EHouseSystem, NatalChart, OrbTable, Zodiac as EZodiac,
|
HouseSystem as EHouseSystem, NatalChart, OrbTable, Zodiac as EZodiac,
|
||||||
};
|
};
|
||||||
@@ -356,6 +356,18 @@ pub fn compose(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
crate::PipelineRequest::Lots => {
|
||||||
|
let count = build_lots_overlay(&natal, &mut render)?;
|
||||||
|
push_overlay_meta(&mut render, "lots", format!("Lots · {}", count));
|
||||||
|
}
|
||||||
|
crate::PipelineRequest::FixedStars => {
|
||||||
|
let count = build_fixed_stars_overlay(chart, &mut render);
|
||||||
|
push_overlay_meta(
|
||||||
|
&mut render,
|
||||||
|
"fixed_stars",
|
||||||
|
format!("Estrellas fijas · {}", count),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1104,6 +1116,91 @@ fn push_overlay_meta(render: &mut RenderModel, module_id: &str, label: String) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper: agrega al `RenderModel` los Lots arábigos clásicos
|
||||||
|
/// (helenísticos) — Fortune, Spirit, Eros, Necessity, Courage, Victory,
|
||||||
|
/// Nemesis. Cada uno se renderea como un glifo `lot:Fo` en el anillo
|
||||||
|
/// `0.54` (entre midpoints y cuerpos progresados). Retorna la cantidad
|
||||||
|
/// de lots renderizados.
|
||||||
|
fn build_lots_overlay(
|
||||||
|
natal: &NatalChart,
|
||||||
|
render: &mut RenderModel,
|
||||||
|
) -> Result<usize, EngineError> {
|
||||||
|
let lots = all_lots(natal)
|
||||||
|
.map_err(|e| EngineError::Eternal(format!("all_lots: {:?}", e)))?;
|
||||||
|
let glyphs: Vec<Glyph> = lots
|
||||||
|
.iter()
|
||||||
|
.map(|l| {
|
||||||
|
let name = l.name.map(|n| n.label()).unwrap_or("Lot");
|
||||||
|
// Tres-letras compactas para no recargar la rueda.
|
||||||
|
let abbrev: String = name.chars().take(2).collect();
|
||||||
|
Glyph {
|
||||||
|
deg: l.longitude.longitude_deg() as f32,
|
||||||
|
symbol: format!("lot:{}", abbrev),
|
||||||
|
annotation: Some(name.to_string()),
|
||||||
|
retrograde: false,
|
||||||
|
house: Some(l.house_number),
|
||||||
|
dignity_marker: None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let count = glyphs.len();
|
||||||
|
render.layers.push(Layer {
|
||||||
|
module_id: "lots".into(),
|
||||||
|
kind: LayerKind::Lots,
|
||||||
|
ring: 0.54,
|
||||||
|
z: 13,
|
||||||
|
geometry: Geometry::GlyphsOnly,
|
||||||
|
glyphs,
|
||||||
|
});
|
||||||
|
Ok(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper: agrega al `RenderModel` 9 estrellas fijas notables. Las
|
||||||
|
/// longitudes están en J2000 ecliptica tropical; aplicamos precesión
|
||||||
|
/// general de 50.29″/año hacia adelante hasta el año natal — basta
|
||||||
|
/// para el orbe de conjunción de ±1.5° con que se interpretan.
|
||||||
|
fn build_fixed_stars_overlay(chart: &Chart, render: &mut RenderModel) -> usize {
|
||||||
|
// (símbolo, nombre, longitud tropical J2000 en grados)
|
||||||
|
const STARS: &[(&str, &str, f64)] = &[
|
||||||
|
("✦Ald", "Aldebaran", 69.79), // 09°47′ Gem
|
||||||
|
("✦Reg", "Regulus", 149.83), // 29°50′ Leo
|
||||||
|
("✦Ant", "Antares", 249.77), // 09°46′ Sag
|
||||||
|
("✦Fom", "Fomalhaut", 333.87), // 03°52′ Pis
|
||||||
|
("✦Spi", "Spica", 203.84), // 23°50′ Lib
|
||||||
|
("✦Sir", "Sirius", 104.10), // 14°06′ Can
|
||||||
|
("✦Alg", "Algol", 56.18), // 26°10′ Tau
|
||||||
|
("✦Veg", "Vega", 285.31), // 15°19′ Cap
|
||||||
|
("✦Pol", "Pollux", 113.27), // 23°16′ Can
|
||||||
|
];
|
||||||
|
let years_from_j2000 = (chart.birth_data.year - 2000) as f64;
|
||||||
|
// 50.29″/año ≈ 0.01397°/año de precesión en longitud eclíptica.
|
||||||
|
let precession_deg = years_from_j2000 * (50.29 / 3600.0);
|
||||||
|
let glyphs: Vec<Glyph> = STARS
|
||||||
|
.iter()
|
||||||
|
.map(|(sym, name, j2000_deg)| {
|
||||||
|
let lon = (j2000_deg + precession_deg).rem_euclid(360.0) as f32;
|
||||||
|
Glyph {
|
||||||
|
deg: lon,
|
||||||
|
symbol: (*sym).to_string(),
|
||||||
|
annotation: Some((*name).to_string()),
|
||||||
|
retrograde: false,
|
||||||
|
house: None,
|
||||||
|
dignity_marker: None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let count = glyphs.len();
|
||||||
|
render.layers.push(Layer {
|
||||||
|
module_id: "fixed_stars".into(),
|
||||||
|
kind: LayerKind::FixedStars,
|
||||||
|
ring: 1.04,
|
||||||
|
z: 16,
|
||||||
|
geometry: Geometry::GlyphsOnly,
|
||||||
|
glyphs,
|
||||||
|
});
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
/// Decora cada Glyph de Bodies (module_id="natal") con su dignity
|
/// Decora cada Glyph de Bodies (module_id="natal") con su dignity
|
||||||
/// marker en `glyph.dignity_marker`. Usa `essential_dignity(body, sign)`
|
/// marker en `glyph.dignity_marker`. Usa `essential_dignity(body, sign)`
|
||||||
/// — los cuerpos modernos quedan sin marker.
|
/// — los cuerpos modernos quedan sin marker.
|
||||||
|
|||||||
@@ -313,6 +313,17 @@ pub enum PipelineRequest {
|
|||||||
/// fórmulas analíticas. La visualización geométrica completa del
|
/// fórmulas analíticas. La visualización geométrica completa del
|
||||||
/// dial de 90° queda pendiente para una fase posterior.
|
/// dial de 90° queda pendiente para una fase posterior.
|
||||||
Uranian,
|
Uranian,
|
||||||
|
/// `module_id = "lots"` — Lots arábigos (helenísticos) calculados
|
||||||
|
/// via `eternal_astrology::compute_lot`: Fortune, Spirit, Eros,
|
||||||
|
/// Necessity, Courage, Victory, Nemesis. Renderea cada lot como
|
||||||
|
/// un texto pequeño en el ring de bodies natales.
|
||||||
|
Lots,
|
||||||
|
/// `module_id = "fixed_stars"` — overlay con ~9 estrellas fijas
|
||||||
|
/// notables (Aldebaran, Regulus, Antares, Fomalhaut, Spica,
|
||||||
|
/// Sirius, Algol, Vega, Pollux). Posiciones tropicales J2000
|
||||||
|
/// aproximadas + precesión simple (~50.29″/año). Renderea como
|
||||||
|
/// marcadores chicos justo afuera del sign dial.
|
||||||
|
FixedStars,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Opciones que afectan la pasada natal (qué aspectos pintar, qué
|
/// Opciones que afectan la pasada natal (qué aspectos pintar, qué
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ impl Registry {
|
|||||||
r.register(Box::new(midpoints::MidpointsModule));
|
r.register(Box::new(midpoints::MidpointsModule));
|
||||||
r.register(Box::new(composite::CompositeModule));
|
r.register(Box::new(composite::CompositeModule));
|
||||||
r.register(Box::new(uranian::UranianModule));
|
r.register(Box::new(uranian::UranianModule));
|
||||||
|
r.register(Box::new(lots::LotsModule));
|
||||||
|
r.register(Box::new(fixed_stars::FixedStarsModule));
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -665,12 +667,99 @@ mod tests {
|
|||||||
assert!(r.find("midpoints").is_some());
|
assert!(r.find("midpoints").is_some());
|
||||||
assert!(r.find("composite").is_some());
|
assert!(r.find("composite").is_some());
|
||||||
assert!(r.find("uranian").is_some());
|
assert!(r.find("uranian").is_some());
|
||||||
// Natal kind tiene 9 módulos aplicables.
|
assert!(r.find("lots").is_some());
|
||||||
assert_eq!(r.for_kind(ChartKind::Natal).len(), 9);
|
assert!(r.find("fixed_stars").is_some());
|
||||||
|
// Natal kind tiene 11 módulos aplicables.
|
||||||
|
assert_eq!(r.for_kind(ChartKind::Natal).len(), 11);
|
||||||
assert!(r.for_kind(ChartKind::Synastry).is_empty());
|
assert!(r.for_kind(ChartKind::Synastry).is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// LotsModule — Lots helenísticos (Fortune, Spirit, Eros, …)
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
pub mod lots {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Calcula los 7 Lots arábigos clásicos via eternal-astrology y
|
||||||
|
/// los renderea como pequeños labels en un ring justo debajo de
|
||||||
|
/// los cuerpos natales. Hover muestra el nombre completo.
|
||||||
|
pub struct LotsModule;
|
||||||
|
|
||||||
|
impl Module for LotsModule {
|
||||||
|
fn id(&self) -> &'static str {
|
||||||
|
"lots"
|
||||||
|
}
|
||||||
|
fn label(&self) -> &'static str {
|
||||||
|
"Lots (helenísticos)"
|
||||||
|
}
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
"Fortune, Spirit, Eros, Necessity, Courage, Victory, Nemesis."
|
||||||
|
}
|
||||||
|
fn applies_to(&self, kind: ChartKind) -> bool {
|
||||||
|
matches!(kind, ChartKind::Natal)
|
||||||
|
}
|
||||||
|
fn enabled_by_default(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn controls(&self) -> Vec<Control> {
|
||||||
|
vec![Control::Toggle {
|
||||||
|
key: "enabled".into(),
|
||||||
|
label: "Activar".into(),
|
||||||
|
default: false,
|
||||||
|
hotkey: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec<Layer> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// FixedStarsModule — 9 estrellas astrológicamente notables
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
pub mod fixed_stars {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// 9 estrellas fijas (Aldebaran, Regulus, Antares, Fomalhaut,
|
||||||
|
/// Spica, Sirius, Algol, Vega, Pollux) con posición tropical
|
||||||
|
/// aproximada (J2000 + precesión simple). Marcadores chicos en el
|
||||||
|
/// margen exterior del sign dial.
|
||||||
|
pub struct FixedStarsModule;
|
||||||
|
|
||||||
|
impl Module for FixedStarsModule {
|
||||||
|
fn id(&self) -> &'static str {
|
||||||
|
"fixed_stars"
|
||||||
|
}
|
||||||
|
fn label(&self) -> &'static str {
|
||||||
|
"Estrellas fijas"
|
||||||
|
}
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
"9 estrellas notables — conjunciones con planetas natales."
|
||||||
|
}
|
||||||
|
fn applies_to(&self, kind: ChartKind) -> bool {
|
||||||
|
matches!(kind, ChartKind::Natal)
|
||||||
|
}
|
||||||
|
fn enabled_by_default(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn controls(&self) -> Vec<Control> {
|
||||||
|
vec![Control::Toggle {
|
||||||
|
key: "enabled".into(),
|
||||||
|
label: "Activar".into(),
|
||||||
|
default: false,
|
||||||
|
hotkey: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec<Layer> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// UranianModule — ejes del dial uraniano de 90° (versión textual)
|
// UranianModule — ejes del dial uraniano de 90° (versión textual)
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user