diff --git a/crates/apps/tahuantinsuyu/src/shell.rs b/crates/apps/tahuantinsuyu/src/shell.rs index 2a680c0..6b31c23 100644 --- a/crates/apps/tahuantinsuyu/src/shell.rs +++ b/crates/apps/tahuantinsuyu/src/shell.rs @@ -383,6 +383,12 @@ impl Shell { if module_enabled(&self.module_configs, "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 let Some(partner) = self.resolve_composite_partner() { requests.push(PipelineRequest::Composite { diff --git a/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/bridge.rs b/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/bridge.rs index 68ebf8b..7192559 100644 --- a/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/bridge.rs +++ b/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/bridge.rs @@ -9,7 +9,7 @@ use std::sync::OnceLock; use std::time::Instant; 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, 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 { + let lots = all_lots(natal) + .map_err(|e| EngineError::Eternal(format!("all_lots: {:?}", e)))?; + let glyphs: Vec = 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 = 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 /// marker en `glyph.dignity_marker`. Usa `essential_dignity(body, sign)` /// — los cuerpos modernos quedan sin marker. diff --git a/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/lib.rs b/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/lib.rs index e4f17ed..a649793 100644 --- a/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/lib.rs +++ b/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/lib.rs @@ -313,6 +313,17 @@ pub enum PipelineRequest { /// fórmulas analíticas. La visualización geométrica completa del /// dial de 90° queda pendiente para una fase posterior. 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é diff --git a/crates/modules/tahuantinsuyu/tahuantinsuyu-modules/src/lib.rs b/crates/modules/tahuantinsuyu/tahuantinsuyu-modules/src/lib.rs index fd35da8..a0948f9 100644 --- a/crates/modules/tahuantinsuyu/tahuantinsuyu-modules/src/lib.rs +++ b/crates/modules/tahuantinsuyu/tahuantinsuyu-modules/src/lib.rs @@ -140,6 +140,8 @@ impl Registry { r.register(Box::new(midpoints::MidpointsModule)); r.register(Box::new(composite::CompositeModule)); r.register(Box::new(uranian::UranianModule)); + r.register(Box::new(lots::LotsModule)); + r.register(Box::new(fixed_stars::FixedStarsModule)); r } @@ -665,12 +667,99 @@ mod tests { assert!(r.find("midpoints").is_some()); assert!(r.find("composite").is_some()); assert!(r.find("uranian").is_some()); - // Natal kind tiene 9 módulos aplicables. - assert_eq!(r.for_kind(ChartKind::Natal).len(), 9); + assert!(r.find("lots").is_some()); + 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()); } } +// ===================================================================== +// 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 { + vec![Control::Toggle { + key: "enabled".into(), + label: "Activar".into(), + default: false, + hotkey: None, + }] + } + fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec { + 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 { + vec![Control::Toggle { + key: "enabled".into(), + label: "Activar".into(), + default: false, + hotkey: None, + }] + } + fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec { + Vec::new() + } + } +} + // ===================================================================== // UranianModule — ejes del dial uraniano de 90° (versión textual) // =====================================================================