gioser

tawasuyu/brahman

Rust 582TOML 222Markdown 83JSON 198635 Definitions

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>

a4d1e0dc17b6fba7522adc49e007557321778fa0 · by sergio · (5 months ago) · parent e2da24239 · browse the tree at this commit

Semantic delta · 26

crates/apps/tahuantinsuyu/src/shell.rs

crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/bridge.rs

crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/lib.rs

crates/modules/tahuantinsuyu/tahuantinsuyu-modules/src/lib.rs

4 files · +206 −3

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<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
 /// 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<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)
 // =====================================================================