chore: rename tahuantinsuyu → cosmobiologia
Rename clean del proyecto astrológico antes de empezar el módulo
web (fase 2 = server axum, fase 3 = cliente WASM). Hacerlo ahora
ahorra refactor de URLs, package.json, paths de assets HTML y
deploy configs que aparecerían con el nombre en cuanto exista el
server.
Mecánica:
- `git mv` de los 10 crates de módulo + 2 apps:
* `crates/modules/tahuantinsuyu/` → `cosmobiologia/`
* `crates/modules/tahuantinsuyu/tahuantinsuyu-*` →
`cosmobiologia/cosmobiologia-*`
* `crates/apps/tahuantinsuyu` y `tahuantinsuyu-cli` análogos.
- Sed sobre todos los `.rs` y `.toml`: `tahuantinsuyu` →
`cosmobiologia` (cubre crate names, deps paths, use
statements, ProjectDirs literals, binary names).
- Workspace `Cargo.toml`: members con paths nuevos.
- Memoria del proyecto (`~/.claude/.../memory/project_*.md`)
actualizada.
Cero leftovers: `grep -rn tahuantinsuyu --include="*.rs"
--include="*.toml" crates/` devuelve vacío.
DB & XDG: clean slate. La nueva app arranca con DB vacía en
`$XDG_DATA_HOME/cosmobiologia/charts.db`. Si tenías cartas
guardadas, viven todavía en `~/.local/share/tahuantinsuyu/` —
las podés migrar manualmente con un `cp`.
IDs UI inalterados: el prefijo `tts-` de gpui ElementIds queda
igual (cosmético, no afecta funcionalidad). Cambiarlo a `cb-`
ahora sería 3-4 líneas más de sed pero ningún beneficio
operativo.
Tests: 20 verdes (10 shell + 10 render math). Compila full:
`cargo check -p cosmobiologia` OK.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,955 @@
|
||||
//! `cosmobiologia-modules` — registry de módulos astrológicos.
|
||||
//!
|
||||
//! Cada tipo de astrología (natal, tránsito, progresión, sinastría,
|
||||
//! Uraniano, …) es un **módulo** que declara:
|
||||
//!
|
||||
//! - Qué `Layer`s aporta al `RenderModel`.
|
||||
//! - Qué `Control`s expone al panel inferior (toggles, sliders, selects).
|
||||
//! - Hotkeys opcionales.
|
||||
//! - Si su cómputo es lazy (sólo cuando se activa) o eager.
|
||||
//!
|
||||
//! El registry es un `Vec<&dyn Module>` estático: el canvas consulta
|
||||
//! "para esta `ChartKind`, ¿qué módulos están disponibles?" y el panel
|
||||
//! pinta sus controles. Activar / desactivar persiste en
|
||||
//! `ModuleState` (en la store).
|
||||
//!
|
||||
//! Esta fase 1 trae el trait + un módulo `NatalModule` de placeholder.
|
||||
//! En fases posteriores agregamos Transit, Progression, Synastry,
|
||||
//! Composite, SolarArc, Uranian, FixedStars, Dignities, Lots…
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use cosmobiologia_engine::Layer;
|
||||
use cosmobiologia_model::{Chart, ChartKind};
|
||||
|
||||
// =====================================================================
|
||||
// Trait Module
|
||||
// =====================================================================
|
||||
|
||||
/// Una capa de astrología enchufable.
|
||||
///
|
||||
/// `Send + Sync` para que el registry sea estático y se pueda consultar
|
||||
/// desde cualquier thread (el cómputo pesado va a un background executor).
|
||||
pub trait Module: Send + Sync {
|
||||
/// Identidad estable del módulo. Coincide con `ModuleState.module_id`
|
||||
/// en la store.
|
||||
fn id(&self) -> &'static str;
|
||||
|
||||
/// Etiqueta amigable para el panel.
|
||||
fn label(&self) -> &'static str;
|
||||
|
||||
/// Breve descripción para tooltip.
|
||||
fn description(&self) -> &'static str;
|
||||
|
||||
/// Para qué tipos de carta tiene sentido este módulo. El panel filtra
|
||||
/// con esto al armar la lista de toggles disponibles.
|
||||
fn applies_to(&self, kind: ChartKind) -> bool;
|
||||
|
||||
/// Si el módulo está activado por default al crear una carta.
|
||||
fn enabled_by_default(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Controles que aporta al panel inferior.
|
||||
fn controls(&self) -> Vec<Control> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Computa las capas que este módulo aporta al RenderModel de
|
||||
/// `chart`. La engine la llama solo si el módulo está activado
|
||||
/// para esa carta.
|
||||
///
|
||||
/// Devuelve `Vec` (no Option) — un módulo puede no aportar capas
|
||||
/// si su config interna lo apaga (ej. "Uranian: mostrar simetría
|
||||
/// = false"); en ese caso retorna `Vec::new()`.
|
||||
fn compute_layers(&self, chart: &Chart, config: &serde_json::Value) -> Vec<Layer>;
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Controls expuestos al panel
|
||||
// =====================================================================
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Control {
|
||||
Toggle {
|
||||
key: String,
|
||||
label: String,
|
||||
default: bool,
|
||||
hotkey: Option<String>,
|
||||
},
|
||||
Slider {
|
||||
key: String,
|
||||
label: String,
|
||||
min: f64,
|
||||
max: f64,
|
||||
step: f64,
|
||||
default: f64,
|
||||
},
|
||||
Select {
|
||||
key: String,
|
||||
label: String,
|
||||
options: Vec<SelectOption>,
|
||||
default: String,
|
||||
},
|
||||
/// Texto libre — útil para etiquetas, comentarios.
|
||||
TextInput {
|
||||
key: String,
|
||||
label: String,
|
||||
default: String,
|
||||
},
|
||||
/// Picker dinámico de una carta de la DB. Las opciones las inyecta
|
||||
/// el host (Shell) en el panel — el módulo solo declara la
|
||||
/// existencia del control. Valor emitido en `ControlChanged` =
|
||||
/// `Value::String(chart_id)` cuando se selecciona, `Value::Null`
|
||||
/// cuando se vuelve a "automático".
|
||||
ChartPicker {
|
||||
key: String,
|
||||
label: String,
|
||||
},
|
||||
/// Botón sin estado — el click dispara un `PanelEvent::Action`
|
||||
/// con `key`. El panel lo pinta como pill clickeable. Útil para
|
||||
/// "Guardar como carta libre" en los módulos overlay con
|
||||
/// transformación (RS, progresión, solar arc, GR).
|
||||
Action {
|
||||
key: String,
|
||||
label: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SelectOption {
|
||||
pub value: String,
|
||||
pub label: String,
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Registry
|
||||
// =====================================================================
|
||||
|
||||
/// Lista estática de módulos disponibles. La app los registra al boot.
|
||||
pub struct Registry {
|
||||
modules: Vec<Box<dyn Module>>,
|
||||
}
|
||||
|
||||
impl Registry {
|
||||
/// Registry con todos los módulos built-in. La app llama esto al
|
||||
/// boot y luego usa `find()` / `for_kind()` para consultar.
|
||||
pub fn with_builtins() -> Self {
|
||||
let mut r = Self { modules: Vec::new() };
|
||||
r.register(Box::new(natal::NatalModule));
|
||||
r.register(Box::new(transit::TransitModule));
|
||||
r.register(Box::new(progression::ProgressionModule));
|
||||
r.register(Box::new(solar_arc::SolarArcModule));
|
||||
r.register(Box::new(synastry::SynastryModule));
|
||||
r.register(Box::new(planetary_return::PlanetaryReturnModule));
|
||||
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.register(Box::new(topocentric::TopocentricModule));
|
||||
r.register(Box::new(primary_directions::PrimaryDirectionsModule));
|
||||
r
|
||||
}
|
||||
|
||||
pub fn register(&mut self, m: Box<dyn Module>) {
|
||||
self.modules.push(m);
|
||||
}
|
||||
|
||||
pub fn all(&self) -> &[Box<dyn Module>] {
|
||||
&self.modules
|
||||
}
|
||||
|
||||
pub fn find(&self, id: &str) -> Option<&dyn Module> {
|
||||
self.modules
|
||||
.iter()
|
||||
.find(|m| m.id() == id)
|
||||
.map(|m| m.as_ref())
|
||||
}
|
||||
|
||||
pub fn for_kind(&self, kind: ChartKind) -> Vec<&dyn Module> {
|
||||
self.modules
|
||||
.iter()
|
||||
.filter(|m| m.applies_to(kind))
|
||||
.map(|m| m.as_ref())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// NatalModule — placeholder fase 1
|
||||
// =====================================================================
|
||||
|
||||
pub mod natal {
|
||||
use super::*;
|
||||
use cosmobiologia_engine::compute_mock;
|
||||
|
||||
pub struct NatalModule;
|
||||
|
||||
impl Module for NatalModule {
|
||||
fn id(&self) -> &'static str {
|
||||
"natal"
|
||||
}
|
||||
fn label(&self) -> &'static str {
|
||||
"Carta natal"
|
||||
}
|
||||
fn description(&self) -> &'static str {
|
||||
"Posiciones natales, casas y aspectos."
|
||||
}
|
||||
fn applies_to(&self, kind: ChartKind) -> bool {
|
||||
matches!(kind, ChartKind::Natal)
|
||||
}
|
||||
fn enabled_by_default(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn controls(&self) -> Vec<Control> {
|
||||
vec![
|
||||
Control::Toggle {
|
||||
key: "show_sign_dial".into(),
|
||||
label: "Dial zodiacal".into(),
|
||||
default: true,
|
||||
hotkey: Some("D".into()),
|
||||
},
|
||||
Control::Toggle {
|
||||
key: "show_houses".into(),
|
||||
label: "Casas".into(),
|
||||
default: true,
|
||||
hotkey: Some("H".into()),
|
||||
},
|
||||
Control::Toggle {
|
||||
key: "show_aspects".into(),
|
||||
label: "Aspectos".into(),
|
||||
default: true,
|
||||
hotkey: Some("X".into()),
|
||||
},
|
||||
Control::Toggle {
|
||||
key: "show_bodies".into(),
|
||||
label: "Cuerpos".into(),
|
||||
default: true,
|
||||
hotkey: Some("P".into()),
|
||||
},
|
||||
Control::Toggle {
|
||||
key: "show_coords".into(),
|
||||
label: "Coordenadas (grado°min')".into(),
|
||||
default: true,
|
||||
hotkey: Some("C".into()),
|
||||
},
|
||||
// Filtros de aspectos: cambian QUÉ se computa, no QUÉ
|
||||
// se pinta del render. Recompose al togglear.
|
||||
Control::Toggle {
|
||||
key: "aspect_majors".into(),
|
||||
label: "Mayores (☌ ☍ △ □ ⚹)".into(),
|
||||
default: true,
|
||||
hotkey: None,
|
||||
},
|
||||
Control::Toggle {
|
||||
key: "aspect_minors".into(),
|
||||
label: "Menores (quincunx, semi-…)".into(),
|
||||
default: false,
|
||||
hotkey: None,
|
||||
},
|
||||
Control::Slider {
|
||||
key: "orb_multiplier".into(),
|
||||
label: "Multiplicador de orbe".into(),
|
||||
min: 0.25,
|
||||
max: 2.5,
|
||||
step: 0.25,
|
||||
default: 1.0,
|
||||
},
|
||||
Control::Toggle {
|
||||
key: "show_dignities".into(),
|
||||
label: "Dignidades esenciales (+ · − *)".into(),
|
||||
default: false,
|
||||
hotkey: None,
|
||||
},
|
||||
Control::Slider {
|
||||
key: "harmonic".into(),
|
||||
label: "Armónico".into(),
|
||||
min: 1.0,
|
||||
max: 20.0,
|
||||
step: 1.0,
|
||||
default: 1.0,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn compute_layers(&self, chart: &Chart, _cfg: &serde_json::Value) -> Vec<Layer> {
|
||||
// Fase 1: delega al mock de la engine para que la UI tenga
|
||||
// algo que pintar. Fase 3 reemplaza con `engine::compute`
|
||||
// contra `eternal-astrology`.
|
||||
compute_mock(chart).layers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// TransitModule — overlay del cielo del momento sobre la carta natal
|
||||
// =====================================================================
|
||||
|
||||
pub mod transit {
|
||||
use super::*;
|
||||
|
||||
/// Anillo externo con las posiciones planetarias del **instante
|
||||
/// actual** (reloj de pared) sobre el sujeto natal, más las
|
||||
/// cross-aspects natal × transit. La engine despacha al pipeline
|
||||
/// `PipelineRequest::Transit` cuando este módulo está activo en el
|
||||
/// `module_configs` del shell.
|
||||
pub struct TransitModule;
|
||||
|
||||
impl Module for TransitModule {
|
||||
fn id(&self) -> &'static str {
|
||||
"transit"
|
||||
}
|
||||
fn label(&self) -> &'static str {
|
||||
"Tránsitos"
|
||||
}
|
||||
fn description(&self) -> &'static str {
|
||||
"Cielo del momento sobre la natal + cross aspects."
|
||||
}
|
||||
fn applies_to(&self, kind: ChartKind) -> bool {
|
||||
// Por ahora solo overlay sobre cartas natales — más adelante
|
||||
// podríamos overlayar tránsitos sobre Progresiones, etc.
|
||||
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: Some("T".into()),
|
||||
},
|
||||
Control::Action {
|
||||
key: "save_as_free".into(),
|
||||
label: "💾 Guardar tránsito como carta libre".into(),
|
||||
},
|
||||
]
|
||||
}
|
||||
fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec<Layer> {
|
||||
// Las capas de tránsito se construyen en la engine vía
|
||||
// `PipelineRequest::Transit` porque necesitan acceso a la
|
||||
// NatalChart cruda + EphemerisSession. Este método queda
|
||||
// como no-op — el módulo es puramente declarativo.
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// ProgressionModule — progresión secundaria (día por año)
|
||||
// =====================================================================
|
||||
|
||||
pub mod progression {
|
||||
use super::*;
|
||||
|
||||
/// Anillo interno con la carta progresada (método secundario,
|
||||
/// "un día de efemérides = un año de vida") + cross aspects natal ×
|
||||
/// progresada. La engine lo despacha vía
|
||||
/// `PipelineRequest::SecondaryProgression { target_age_years }`.
|
||||
pub struct ProgressionModule;
|
||||
|
||||
impl Module for ProgressionModule {
|
||||
fn id(&self) -> &'static str {
|
||||
"progression"
|
||||
}
|
||||
fn label(&self) -> &'static str {
|
||||
"Progresión secundaria"
|
||||
}
|
||||
fn description(&self) -> &'static str {
|
||||
"Día-por-año: avanza la carta a la edad actual."
|
||||
}
|
||||
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,
|
||||
},
|
||||
// El default (30.0) es un placeholder — el shell empuja
|
||||
// la edad actual del sujeto al cargar una carta vía
|
||||
// panel.set_slider("progression", "target_age_years",
|
||||
// current_age).
|
||||
Control::Slider {
|
||||
key: "target_age_years".into(),
|
||||
label: "Edad objetivo (años)".into(),
|
||||
min: 0.0,
|
||||
max: 120.0,
|
||||
step: 0.25,
|
||||
default: 30.0,
|
||||
},
|
||||
Control::Action {
|
||||
key: "save_as_free".into(),
|
||||
label: "💾 Guardar progresada como carta libre".into(),
|
||||
},
|
||||
]
|
||||
}
|
||||
fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec<Layer> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// SynastryModule — bi-wheel con otra carta hermana del contacto actual
|
||||
// =====================================================================
|
||||
|
||||
pub mod synastry {
|
||||
use super::*;
|
||||
|
||||
/// Pone la carta del partner en el anillo externo (compartido con
|
||||
/// Transit — mutuamente excluyentes) y dibuja las cross aspects
|
||||
/// natal × partner. El shell elige el partner: la primera carta
|
||||
/// hermana del mismo contacto. Si no hay hermana, el request se
|
||||
/// salta silenciosamente.
|
||||
pub struct SynastryModule;
|
||||
|
||||
impl Module for SynastryModule {
|
||||
fn id(&self) -> &'static str {
|
||||
"synastry"
|
||||
}
|
||||
fn label(&self) -> &'static str {
|
||||
"Sinastría"
|
||||
}
|
||||
fn description(&self) -> &'static str {
|
||||
"Bi-wheel con la primera carta hermana del contacto."
|
||||
}
|
||||
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,
|
||||
},
|
||||
Control::ChartPicker {
|
||||
key: "partner_chart_id".into(),
|
||||
label: "Partner".into(),
|
||||
},
|
||||
]
|
||||
}
|
||||
fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec<Layer> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// PlanetaryReturnModule — retornos de cualquier cuerpo a su pos natal
|
||||
// =====================================================================
|
||||
|
||||
pub mod planetary_return {
|
||||
use super::*;
|
||||
|
||||
/// Computa la carta natal completa al instante del próximo retorno
|
||||
/// del cuerpo elegido. Sun = anual (cumpleaños), Moon = mensual,
|
||||
/// Júpiter/Saturno = generacionales. Comparte el outer ring con
|
||||
/// Transit y Synastry — mutuamente excluyentes a nivel de Shell.
|
||||
pub struct PlanetaryReturnModule;
|
||||
|
||||
impl Module for PlanetaryReturnModule {
|
||||
fn id(&self) -> &'static str {
|
||||
"planetary_return"
|
||||
}
|
||||
fn label(&self) -> &'static str {
|
||||
"Retornos planetarios"
|
||||
}
|
||||
fn description(&self) -> &'static str {
|
||||
"Carta del próximo retorno (Sol, Luna, Júpiter, Saturno…)."
|
||||
}
|
||||
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,
|
||||
},
|
||||
Control::Select {
|
||||
key: "body".into(),
|
||||
label: "Cuerpo".into(),
|
||||
default: "sun".into(),
|
||||
options: vec![
|
||||
SelectOption { value: "sun".into(), label: "Sol".into() },
|
||||
SelectOption { value: "moon".into(), label: "Luna".into() },
|
||||
SelectOption { value: "mercury".into(), label: "Mercurio".into() },
|
||||
SelectOption { value: "venus".into(), label: "Venus".into() },
|
||||
SelectOption { value: "mars".into(), label: "Marte".into() },
|
||||
SelectOption { value: "jupiter".into(), label: "Júpiter".into() },
|
||||
SelectOption { value: "saturn".into(), label: "Saturno".into() },
|
||||
SelectOption { value: "uranus".into(), label: "Urano".into() },
|
||||
SelectOption { value: "neptune".into(), label: "Neptuno".into() },
|
||||
SelectOption { value: "pluto".into(), label: "Plutón".into() },
|
||||
],
|
||||
},
|
||||
Control::Slider {
|
||||
key: "target_age_years".into(),
|
||||
label: "Edad del retorno".into(),
|
||||
min: 0.0,
|
||||
max: 120.0,
|
||||
step: 1.0,
|
||||
default: 30.0,
|
||||
},
|
||||
// Offset adicional para Moon return (saltar ~28d entre
|
||||
// retornos lunares) o ajuste fino del Solar return.
|
||||
Control::Slider {
|
||||
key: "shift_days".into(),
|
||||
label: "Shift días (lunar nav)".into(),
|
||||
min: -180.0,
|
||||
max: 180.0,
|
||||
step: 1.0,
|
||||
default: 0.0,
|
||||
},
|
||||
// Botón: captura la carta del retorno actual (cuerpo +
|
||||
// edad) como FreeChart con label `{contacto} rs-{N}`
|
||||
// (o `lunar-{N}` etc. según el cuerpo). El usuario
|
||||
// luego decide si guardarla en un contacto.
|
||||
Control::Action {
|
||||
key: "save_as_free".into(),
|
||||
label: "💾 Guardar retorno como carta libre".into(),
|
||||
},
|
||||
]
|
||||
}
|
||||
fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec<Layer> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// CompositeModule — carta compuesta (midpoint Davison) con un partner
|
||||
// =====================================================================
|
||||
|
||||
pub mod composite {
|
||||
use super::*;
|
||||
|
||||
/// Carta compuesta entre la natal y otra carta — cada placement es
|
||||
/// el midpoint angular del par. Mismo ChartPicker que sinastría
|
||||
/// para elegir el partner.
|
||||
pub struct CompositeModule;
|
||||
|
||||
impl Module for CompositeModule {
|
||||
fn id(&self) -> &'static str {
|
||||
"composite"
|
||||
}
|
||||
fn label(&self) -> &'static str {
|
||||
"Composite"
|
||||
}
|
||||
fn description(&self) -> &'static str {
|
||||
"Carta compuesta con otro sujeto (midpoint Davison)."
|
||||
}
|
||||
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,
|
||||
},
|
||||
Control::ChartPicker {
|
||||
key: "partner_chart_id".into(),
|
||||
label: "Partner".into(),
|
||||
},
|
||||
]
|
||||
}
|
||||
fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec<Layer> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// SolarArcModule — Solar Arc dirigido (true progressed Sun)
|
||||
// =====================================================================
|
||||
|
||||
pub mod solar_arc {
|
||||
use super::*;
|
||||
|
||||
/// Cada planeta y cusp natal se desplaza por el mismo arco
|
||||
/// (≈ 1° por año de vida, calculado como el delta del Sol
|
||||
/// progresado secundario). Anillo interno bien adentro + cross
|
||||
/// aspects natal × dirigida.
|
||||
pub struct SolarArcModule;
|
||||
|
||||
impl Module for SolarArcModule {
|
||||
fn id(&self) -> &'static str {
|
||||
"solar_arc"
|
||||
}
|
||||
fn label(&self) -> &'static str {
|
||||
"Solar Arc"
|
||||
}
|
||||
fn description(&self) -> &'static str {
|
||||
"Dirección por arco solar — uniforme, ≈1°/año."
|
||||
}
|
||||
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,
|
||||
},
|
||||
Control::Slider {
|
||||
key: "target_age_years".into(),
|
||||
label: "Edad objetivo (años)".into(),
|
||||
min: 0.0,
|
||||
max: 120.0,
|
||||
step: 0.25,
|
||||
default: 30.0,
|
||||
},
|
||||
]
|
||||
}
|
||||
fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec<Layer> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// MidpointsModule — puntos medios entre cuerpos natales (Sol/Luna)
|
||||
// =====================================================================
|
||||
|
||||
pub mod midpoints {
|
||||
use super::*;
|
||||
|
||||
/// Computa midpoints entre los cuerpos natales (filtrado a los que
|
||||
/// involucran Sol o Luna, ~10 puntos) y los renderea como pequeños
|
||||
/// puntos en un anillo interior. Hovering muestra los dos cuerpos
|
||||
/// que originan el midpoint.
|
||||
pub struct MidpointsModule;
|
||||
|
||||
impl Module for MidpointsModule {
|
||||
fn id(&self) -> &'static str {
|
||||
"midpoints"
|
||||
}
|
||||
fn label(&self) -> &'static str {
|
||||
"Midpoints"
|
||||
}
|
||||
fn description(&self) -> &'static str {
|
||||
"Puntos medios que involucran al Sol o a la Luna."
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn registry_finds_builtins() {
|
||||
let r = Registry::with_builtins();
|
||||
assert!(r.find("natal").is_some());
|
||||
assert!(r.find("transit").is_some());
|
||||
assert!(r.find("progression").is_some());
|
||||
assert!(r.find("solar_arc").is_some());
|
||||
assert!(r.find("synastry").is_some());
|
||||
assert!(r.find("planetary_return").is_some());
|
||||
assert!(r.find("midpoints").is_some());
|
||||
assert!(r.find("composite").is_some());
|
||||
assert!(r.find("uranian").is_some());
|
||||
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)
|
||||
// =====================================================================
|
||||
|
||||
pub mod uranian {
|
||||
use super::*;
|
||||
|
||||
/// Detecta "ejes" del dial uraniano: grupos de cuerpos natales cuya
|
||||
/// longitud módulo 90 cae dentro de una tolerancia. Los grupos
|
||||
/// resultantes se listan en el footer del canvas. La visualización
|
||||
/// geométrica del dial completo de 90° queda para una fase futura.
|
||||
pub struct UranianModule;
|
||||
|
||||
impl Module for UranianModule {
|
||||
fn id(&self) -> &'static str {
|
||||
"uranian"
|
||||
}
|
||||
fn label(&self) -> &'static str {
|
||||
"Uraniano (90°)"
|
||||
}
|
||||
fn description(&self) -> &'static str {
|
||||
"Ejes del dial uraniano — cuerpos en la misma posición mod 90."
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// TopocentricModule — capa "ascensional" (paralaje + Polich-Page)
|
||||
// =====================================================================
|
||||
|
||||
pub mod topocentric {
|
||||
use super::*;
|
||||
|
||||
/// Capa topocéntrica que convive con la natal geocéntrica: cada
|
||||
/// planeta se re-proyecta a longitud eclíptica topocéntrica (con
|
||||
/// paralaje horizontal por cuerpo) y las casas se calculan con el
|
||||
/// sistema Polich-Page. El shift es visible en la Luna (~1°),
|
||||
/// modesto en interiores cerca de oposición, e imperceptible en
|
||||
/// exteriores. La engine despacha al pipeline
|
||||
/// `PipelineRequest::Topocentric` cuando este módulo está activo.
|
||||
pub struct TopocentricModule;
|
||||
|
||||
impl Module for TopocentricModule {
|
||||
fn id(&self) -> &'static str {
|
||||
"topocentric"
|
||||
}
|
||||
fn label(&self) -> &'static str {
|
||||
"Topocéntrico (ascensional)"
|
||||
}
|
||||
fn description(&self) -> &'static str {
|
||||
"Paralaje horizontal por cuerpo + casas Polich-Page."
|
||||
}
|
||||
fn applies_to(&self, kind: ChartKind) -> bool {
|
||||
matches!(kind, ChartKind::Natal)
|
||||
}
|
||||
fn enabled_by_default(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn controls(&self) -> Vec<Control> {
|
||||
vec![Control::Toggle {
|
||||
key: "enabled".into(),
|
||||
label: "Activar".into(),
|
||||
default: true,
|
||||
hotkey: None,
|
||||
}]
|
||||
}
|
||||
fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec<Layer> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// PrimaryDirectionsModule — GR dual-ring (Direct + Converse)
|
||||
// =====================================================================
|
||||
|
||||
pub mod primary_directions {
|
||||
use super::*;
|
||||
|
||||
/// Direcciones Primarias del Sistema GR (García Rosas): cada
|
||||
/// cuerpo natal se proyecta en dos rings — directa (rotación
|
||||
/// diurna forward) y conversa (rotación inversa). El usuario
|
||||
/// scrubea `target_age_years` para ver el movimiento en vivo.
|
||||
/// Útil para rectificación: un evento real debe coincidir con
|
||||
/// arcos directos y conversos consistentes si la hora natal es
|
||||
/// correcta.
|
||||
pub struct PrimaryDirectionsModule;
|
||||
|
||||
impl Module for PrimaryDirectionsModule {
|
||||
fn id(&self) -> &'static str {
|
||||
"primary_directions"
|
||||
}
|
||||
fn label(&self) -> &'static str {
|
||||
"Direcciones primarias (GR)"
|
||||
}
|
||||
fn description(&self) -> &'static str {
|
||||
"Dual-ring directas + conversas para rectificación en vivo."
|
||||
}
|
||||
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,
|
||||
},
|
||||
Control::Slider {
|
||||
key: "target_age_years".into(),
|
||||
label: "Edad (años)".into(),
|
||||
min: 0.0,
|
||||
max: 120.0,
|
||||
step: 0.05,
|
||||
default: 30.0,
|
||||
},
|
||||
Control::Select {
|
||||
key: "key".into(),
|
||||
label: "Clave (arco/año)".into(),
|
||||
default: "naibod".into(),
|
||||
options: vec![
|
||||
SelectOption {
|
||||
value: "naibod".into(),
|
||||
label: "Naibod (0°59'08\"/año)".into(),
|
||||
},
|
||||
SelectOption {
|
||||
value: "ptolemy".into(),
|
||||
label: "Ptolomeo (1°/año)".into(),
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec<Layer> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user