feat(tahuantinsuyu): GR dual-ring + topo ascensional pegado al dial + coords
Dos cambios mayores que cierran el sistema GR/ascensional:
1. Reordenamiento radial — la capa ascensional (topocéntrico
Polich-Page) se ubica AHORA pegada al sign dial, y la
geocéntrica clásica queda más adentro. Layout outer→inner:
- sign_dial (1.00 → 0.88)
- topo_houses_outer (0.875) / topo_houses_inner (0.79) ← P-P pegadas al zodiaco
- topocentric (0.755) ← planetas topo con coords
- transits (0.71)
- houses_outer (0.66) / houses_inner (0.54) ← Placidus geo
- midpoints (0.50) / bodies (0.47) / bodies_inner (0.44) ← natal geo con coords
- pd_direct (0.495) / pd_converse (0.425) ← dual-ring GR
- aspects (0.41) / progression (0.36) / solar_arc (0.30)
Topocéntrico default ON (era OFF en la fase previa).
Coord labels ahora se pintan también en planetas topocéntricos
(label hacia adentro, no afuera, para no chocar con casas P-P).
2. Sistema GR Direcciones Primarias (dual-ring):
- Nuevo `PipelineRequest::PrimaryDirections { target_age_years }`.
- `build_primary_directions_overlay` proyecta cada cuerpo natal
con `directed_longitude` (key Naibod) en dos direcciones —
directa y conversa — y emite dos Layer Bodies con
`module_id` "pd_direct" / "pd_converse".
- Canvas: nuevos `pd_direct` y `pd_converse` en Radii; en el
render de Bodies disco más chico y alpha 0.80. Los dos anillos
se marcan con punteado fino que "abraza" el cinturón natal
por afuera y por adentro — el natal queda en el centro.
- Nuevo `PrimaryDirectionsModule` con toggle + slider de edad
(0..120, step 0.05a). Activable desde el panel.
Tests: 6 shell + 5 coord siguen verdes; el motor matemático
(eternal-astrology directed_longitude) y house system Polich-Page
están testeados desde el commit `e385ab2` en eternal.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -550,6 +550,12 @@ impl Shell {
|
||||
if module_enabled(&self.module_configs, "topocentric") {
|
||||
requests.push(PipelineRequest::Topocentric);
|
||||
}
|
||||
if module_enabled(&self.module_configs, "primary_directions") {
|
||||
let age = self.module_age_or_current("primary_directions");
|
||||
requests.push(PipelineRequest::PrimaryDirections {
|
||||
target_age_years: age,
|
||||
});
|
||||
}
|
||||
if module_enabled(&self.module_configs, "composite") {
|
||||
if let Some(partner) = self.resolve_composite_partner() {
|
||||
requests.push(PipelineRequest::Composite {
|
||||
|
||||
@@ -1115,22 +1115,25 @@ fn render_wheel(
|
||||
if matches!(layer.kind, LayerKind::Bodies) {
|
||||
let is_natal = layer.module_id == "natal";
|
||||
let is_topo = layer.module_id == "topocentric";
|
||||
let is_pd_direct = layer.module_id == "pd_direct";
|
||||
let is_pd_converse = layer.module_id == "pd_converse";
|
||||
let is_pd = is_pd_direct || is_pd_converse;
|
||||
let ring = radii.body_ring(&layer.module_id);
|
||||
let alpha = if is_natal {
|
||||
1.0
|
||||
} else if is_topo {
|
||||
0.75
|
||||
} else if is_pd {
|
||||
0.80
|
||||
} else {
|
||||
0.88
|
||||
};
|
||||
// Topocéntrico va con disco un poco más chico que el
|
||||
// natal, y con desaturación implícita en `alpha`. El
|
||||
// shift respecto al natal es lo que el ojo lee, no el
|
||||
// tamaño individual.
|
||||
let font_size = (if is_natal {
|
||||
18.0
|
||||
} else if is_topo {
|
||||
15.0
|
||||
} else if is_pd {
|
||||
13.0
|
||||
} else {
|
||||
14.0
|
||||
}) * s;
|
||||
@@ -1138,6 +1141,8 @@ fn render_wheel(
|
||||
26.0
|
||||
} else if is_topo {
|
||||
22.0
|
||||
} else if is_pd {
|
||||
20.0
|
||||
} else {
|
||||
22.0
|
||||
}) * s;
|
||||
@@ -1163,23 +1168,31 @@ fn render_wheel(
|
||||
));
|
||||
|
||||
// Coord label: grado dentro del signo + glyph del
|
||||
// signo, pintado justo afuera del disco del
|
||||
// planeta (radialmente). Sólo en natal (los
|
||||
// overlays ya cargan info en su badge / tooltip).
|
||||
if show_coords && is_natal {
|
||||
// signo, pintado afuera del disco del planeta
|
||||
// (radialmente). Se pinta para el natal (afuera)
|
||||
// y para el topocéntrico (más afuera aún, hacia
|
||||
// el sign dial) — los dos sistemas conviven con
|
||||
// sus coords. Otros overlays (progression, solar
|
||||
// arc) usan badges en el footer.
|
||||
if show_coords && (is_natal || is_topo) {
|
||||
let coord = format_coord_compact(g.deg);
|
||||
let label_r = ring + disk_size * 0.7;
|
||||
// Topo: label hacia ADENTRO (entre planeta y
|
||||
// casas P-P arriba); natal: label hacia
|
||||
// AFUERA (entre planeta y casas Placidus).
|
||||
let label_r = if is_topo {
|
||||
ring - disk_size * 0.7
|
||||
} else {
|
||||
ring + disk_size * 0.7
|
||||
};
|
||||
let (lx, ly) = polar_to_screen(g.deg, asc, rot_offset, label_r);
|
||||
wheel = wheel.child(
|
||||
coord_label(
|
||||
cx_center + lx,
|
||||
cy_center + ly,
|
||||
coord.into(),
|
||||
theme.fg_muted,
|
||||
halo_bg,
|
||||
9.5 * s,
|
||||
),
|
||||
);
|
||||
wheel = wheel.child(coord_label(
|
||||
cx_center + lx,
|
||||
cy_center + ly,
|
||||
coord.into(),
|
||||
theme.fg_muted,
|
||||
halo_bg,
|
||||
9.5 * s,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1604,27 +1617,40 @@ fn format_offset(minutes: i64) -> String {
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Radii {
|
||||
// Layout outer→inner. La capa **ascensional** (sistema topocéntrico
|
||||
// Polich-Page) va pegada al sign dial — su lógica nace del eje
|
||||
// ASC/MC y de la ascensión recta del observador, conceptualmente
|
||||
// hermana del zodiaco. La capa **geocéntrica** clásica
|
||||
// (casas Placidus + planetas tropicales sin paralaje) vive más
|
||||
// adentro, alrededor del cinturón de planetas natales.
|
||||
sign_outer: f32,
|
||||
sign_inner: f32,
|
||||
/// Anillo exterior de las casas Polich-Page (topocéntricas), pegado
|
||||
/// al sign dial. Los cusps llegan hasta el `sign_inner`.
|
||||
topo_houses_outer: f32,
|
||||
topo_houses_inner: f32,
|
||||
/// Carril de planetas topocéntricos — apenas dentro del anillo
|
||||
/// de casas P-P. Lleva sus propios coord labels.
|
||||
topocentric: f32,
|
||||
/// Anillo de glifos de tránsito (cuando el overlay está activo).
|
||||
transits: f32,
|
||||
/// Casas geocéntricas (Placidus default) — más adentro que las
|
||||
/// topocéntricas, claramente diferenciables.
|
||||
houses_outer: f32,
|
||||
houses_inner: f32,
|
||||
/// Anillo de midpoints — entre bodies natales y houses_inner.
|
||||
/// Anillo de midpoints — entre cuerpos geocéntricos y `houses_inner`.
|
||||
midpoints: f32,
|
||||
/// Anillo principal de cuerpos natales — donde se posan los
|
||||
/// glyphs. Junto con `bodies_inner` forman el "cinturón" de los
|
||||
/// planetas (doble línea visual).
|
||||
/// Anillo principal de cuerpos natales geocéntricos.
|
||||
bodies: f32,
|
||||
/// Borde interior del cinturón de planetas. Marca dónde "termina"
|
||||
/// la zona de cuerpos y empieza la zona de aspectos.
|
||||
/// Borde interior del cinturón de planetas geocéntricos.
|
||||
bodies_inner: f32,
|
||||
/// Cuerpos topocéntricos (capa "ascensional") — un poco hacia
|
||||
/// adentro de `bodies` para que un mismo planeta se vea como
|
||||
/// "doble glyph": natal afuera, topocéntrico justo dentro. En
|
||||
/// Luna la separación angular es visible (~1°); en exteriores
|
||||
/// los dos glyphs se superponen casi exactamente.
|
||||
topocentric: f32,
|
||||
/// Direcciones Primarias DIRECTAS (Sistema GR): ring exterior del
|
||||
/// "abrazo" GR — justo afuera del cinturón natal.
|
||||
pd_direct: f32,
|
||||
/// Direcciones Primarias CONVERSAS (Sistema GR): ring interior —
|
||||
/// el cinturón natal queda entre `pd_direct` (afuera) y
|
||||
/// `pd_converse` (dentro), formando el dual-ring de rectificación.
|
||||
pd_converse: f32,
|
||||
/// Anillo interno con cuerpos progresados (overlay opcional).
|
||||
progression: f32,
|
||||
/// Anillo más interno con cuerpos dirigidos por Solar Arc.
|
||||
@@ -1632,9 +1658,7 @@ struct Radii {
|
||||
/// Anillo de carta compuesta (midpoint Davison) con un partner.
|
||||
composite: f32,
|
||||
/// Círculo donde anclan las líneas de aspecto entre cuerpos
|
||||
/// natales. Justo dentro del cinturón de planetas, no en el
|
||||
/// centro — así las líneas conectan cuerpos cercanos al ring
|
||||
/// donde se ven, no atraviesan toda la rueda.
|
||||
/// natales. Justo dentro del cinturón de planetas.
|
||||
aspects: f32,
|
||||
}
|
||||
|
||||
@@ -1643,28 +1667,36 @@ impl Radii {
|
||||
Self {
|
||||
sign_outer: r,
|
||||
sign_inner: r * 0.88,
|
||||
transits: r * 0.82,
|
||||
houses_outer: r * 0.78,
|
||||
houses_inner: r * 0.66,
|
||||
midpoints: r * 0.62,
|
||||
bodies: r * 0.60,
|
||||
// bodies_inner cerca de bodies — los dos anillos juntos
|
||||
// forman un "carril" estrecho que delimita la franja de
|
||||
// planetas, no dos líneas separadas que confunden.
|
||||
bodies_inner: r * 0.57,
|
||||
// Topocéntrico justo bajo el carril natal: los dos
|
||||
// glyphs comparten ancho visual, el shift relativo
|
||||
// (Luna en particular) se lee como "el natal apunta a
|
||||
// este grado, el topo a este otro".
|
||||
topocentric: r * 0.555,
|
||||
// aspects justo bajo el carril de cuerpos. Las líneas
|
||||
// de aspecto entran a este radio, pero el círculo en sí
|
||||
// no se pinta — son las líneas las que importan, no
|
||||
// un anillo extra que sume ruido.
|
||||
aspects: r * 0.54,
|
||||
progression: r * 0.46,
|
||||
solar_arc: r * 0.38,
|
||||
composite: r * 0.30,
|
||||
// Ascensional (Polich-Page): pegado al sign dial.
|
||||
topo_houses_outer: r * 0.875,
|
||||
topo_houses_inner: r * 0.79,
|
||||
// Carril topocéntrico de planetas: justo dentro de las
|
||||
// casas P-P. Lleva sus coord labels al borde interior.
|
||||
topocentric: r * 0.755,
|
||||
// Tránsitos: ring intermedio entre las dos coronas (topo y
|
||||
// geo), apenas debajo del topocéntrico.
|
||||
transits: r * 0.71,
|
||||
// Capa geocéntrica clásica más adentro — claramente
|
||||
// separada del bloque ascensional.
|
||||
houses_outer: r * 0.66,
|
||||
houses_inner: r * 0.54,
|
||||
midpoints: r * 0.50,
|
||||
bodies: r * 0.47,
|
||||
bodies_inner: r * 0.44,
|
||||
// Dual-ring GR — abraza el cinturón natal por afuera y por
|
||||
// adentro. Para Saturno los dos rings caen casi en la misma
|
||||
// posición angular (poca rotación diurna afecta su lon en
|
||||
// años humanos); para luminarias los rings divergen
|
||||
// visiblemente y leen "directa rumbo a este grado, conversa
|
||||
// hacia este otro".
|
||||
pd_direct: r * 0.495,
|
||||
pd_converse: r * 0.425,
|
||||
// aspects justo bajo el carril natal — las líneas entran
|
||||
// a este radio sin pintar el círculo (sería ruido extra).
|
||||
aspects: r * 0.41,
|
||||
progression: r * 0.36,
|
||||
solar_arc: r * 0.30,
|
||||
composite: r * 0.24,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1676,6 +1708,8 @@ impl Radii {
|
||||
"composite" => self.composite,
|
||||
"midpoints" => self.midpoints,
|
||||
"topocentric" => self.topocentric,
|
||||
"pd_direct" => self.pd_direct,
|
||||
"pd_converse" => self.pd_converse,
|
||||
_ => self.bodies,
|
||||
}
|
||||
}
|
||||
@@ -1762,15 +1796,31 @@ fn paint_wheel(
|
||||
let house_color = with_alpha(house_base, 0.85);
|
||||
stroke_circle_3d(window, cx, cy, radii.houses_outer, 1.1, house_color, theme);
|
||||
stroke_circle_3d(window, cx, cy, radii.houses_inner, 1.1, house_color, theme);
|
||||
// Si hay capa topocéntrica activa, pintar también sus dos
|
||||
// anillos (con stroke más sutil que el geocéntrico, para que
|
||||
// se lea como "sistema ascensional" sin competir).
|
||||
if layers
|
||||
.iter()
|
||||
.any(|l| matches!(l.kind, LayerKind::Houses) && l.module_id == "topocentric")
|
||||
{
|
||||
let topo_color = with_alpha(house_base, 0.55);
|
||||
stroke_circle(window, cx, cy, radii.topo_houses_outer, 0.8, topo_color);
|
||||
stroke_circle(window, cx, cy, radii.topo_houses_inner, 0.8, topo_color);
|
||||
}
|
||||
|
||||
for layer in layers {
|
||||
if matches!(layer.kind, LayerKind::Houses) {
|
||||
let is_topo = layer.module_id == "topocentric";
|
||||
let (r_in, r_out) = if is_topo {
|
||||
(radii.topo_houses_inner, radii.topo_houses_outer)
|
||||
} else {
|
||||
(radii.houses_inner, radii.houses_outer)
|
||||
};
|
||||
if let Geometry::Ring { cusps_deg } = &layer.geometry {
|
||||
for (i, c) in cusps_deg.iter().enumerate() {
|
||||
let is_angle = i == 0 || i == 3 || i == 6 || i == 9;
|
||||
let color = if is_topo {
|
||||
with_alpha(house_base, 0.55)
|
||||
with_alpha(house_base, 0.60)
|
||||
} else if is_angle {
|
||||
palette.angle_highlight
|
||||
} else {
|
||||
@@ -1779,27 +1829,44 @@ fn paint_wheel(
|
||||
let width = if is_angle && !is_topo { 2.0 } else { 0.8 };
|
||||
if is_topo {
|
||||
// Topocéntrico: cusp como línea punteada
|
||||
// en su propio anillo (un poco más
|
||||
// adentro que las casas geocéntricas) →
|
||||
// se distingue como sistema alternativo.
|
||||
let (xi, yi) = polar_to_screen(
|
||||
*c,
|
||||
ascendant_deg,
|
||||
rot_offset_deg,
|
||||
radii.houses_inner - 4.0,
|
||||
);
|
||||
let (xo, yo) = polar_to_screen(
|
||||
*c,
|
||||
ascendant_deg,
|
||||
rot_offset_deg,
|
||||
radii.houses_inner - 28.0,
|
||||
);
|
||||
// en su propio anillo cercano al sign
|
||||
// dial — se distingue del Placidus
|
||||
// geocéntrico por el dash pattern y la
|
||||
// ubicación más exterior.
|
||||
paint_segment(
|
||||
window,
|
||||
cx + xi,
|
||||
cy + yi,
|
||||
cx + xo,
|
||||
cy + yo,
|
||||
cx
|
||||
+ polar_to_screen(
|
||||
*c,
|
||||
ascendant_deg,
|
||||
rot_offset_deg,
|
||||
r_in,
|
||||
)
|
||||
.0,
|
||||
cy
|
||||
+ polar_to_screen(
|
||||
*c,
|
||||
ascendant_deg,
|
||||
rot_offset_deg,
|
||||
r_in,
|
||||
)
|
||||
.1,
|
||||
cx
|
||||
+ polar_to_screen(
|
||||
*c,
|
||||
ascendant_deg,
|
||||
rot_offset_deg,
|
||||
r_out,
|
||||
)
|
||||
.0,
|
||||
cy
|
||||
+ polar_to_screen(
|
||||
*c,
|
||||
ascendant_deg,
|
||||
rot_offset_deg,
|
||||
r_out,
|
||||
)
|
||||
.1,
|
||||
color,
|
||||
Some((3.0, 2.5)),
|
||||
1.0,
|
||||
@@ -1812,8 +1879,8 @@ fn paint_wheel(
|
||||
*c,
|
||||
ascendant_deg,
|
||||
rot_offset_deg,
|
||||
radii.houses_inner,
|
||||
radii.houses_outer,
|
||||
r_in,
|
||||
r_out,
|
||||
color,
|
||||
width,
|
||||
);
|
||||
@@ -1866,6 +1933,35 @@ fn paint_wheel(
|
||||
0.6,
|
||||
with_alpha(palette.dial_ring, 0.35),
|
||||
);
|
||||
// GR dual-ring: si las capas de direcciones primarias están
|
||||
// presentes, marcar sus anillos para que el visual lea como
|
||||
// "abrazo" del cinturón natal. La directa va punteada,
|
||||
// la conversa también — la diferencia entre las dos es la
|
||||
// ubicación radial (afuera vs adentro del cinturón natal).
|
||||
let has_pd = layers.iter().any(|l| {
|
||||
matches!(l.kind, LayerKind::Bodies)
|
||||
&& (l.module_id == "pd_direct" || l.module_id == "pd_converse")
|
||||
});
|
||||
if has_pd {
|
||||
let pd_color = with_alpha(palette.angle_highlight, 0.50);
|
||||
for r in [radii.pd_direct, radii.pd_converse] {
|
||||
// Pintamos el anillo como tramo punteado fino: 24
|
||||
// segmentos cortos a lo largo del círculo.
|
||||
let steps = 96;
|
||||
for i in 0..steps {
|
||||
if i % 2 != 0 {
|
||||
continue;
|
||||
}
|
||||
let a0 = (i as f32) / (steps as f32) * std::f32::consts::TAU;
|
||||
let a1 = ((i + 1) as f32) / (steps as f32) * std::f32::consts::TAU;
|
||||
let x0 = cx + r * a0.cos();
|
||||
let y0 = cy + r * a0.sin();
|
||||
let x1 = cx + r * a1.cos();
|
||||
let y1 = cy + r * a1.sin();
|
||||
paint_segment(window, x0, y0, x1, y1, pd_color, None, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Aspectos. Cada module_id usa su par de radios — natal-natal
|
||||
|
||||
@@ -9,10 +9,11 @@ use std::sync::{Arc, OnceLock};
|
||||
use std::time::Instant;
|
||||
|
||||
use eternal_astrology::{
|
||||
all_lots, composite, find_aspects, find_synastry_aspects, next_return, secondary_progression,
|
||||
solar_arc_true, topocentric_ecliptic, Aspect, AspectKind as EAspectKind, BirthData, BodySet,
|
||||
ChartConfig, HouseSystem as EHouseSystem, Houses as EHouses, NatalChart, OrbTable,
|
||||
Zodiac as EZodiac,
|
||||
all_lots, composite, directed_longitude, find_aspects, find_synastry_aspects, next_return,
|
||||
primary_direction::PrimaryDirection, secondary_progression, solar_arc_true, topocentric_ecliptic,
|
||||
Aspect, AspectKind as EAspectKind, BirthData, BodySet, ChartConfig,
|
||||
DirectionKey as EDirectionKey, HouseSystem as EHouseSystem, Houses as EHouses, NatalChart,
|
||||
OrbTable, Zodiac as EZodiac,
|
||||
};
|
||||
use eternal_sky::{Ayanamsha, Body, EphemerisSession, Instant as ESInstant, Observer, SessionConfig};
|
||||
|
||||
@@ -389,6 +390,14 @@ pub fn compose(
|
||||
"Topocéntrico (Polich-Page)".into(),
|
||||
);
|
||||
}
|
||||
crate::PipelineRequest::PrimaryDirections { target_age_years } => {
|
||||
build_primary_directions_overlay(&natal, *target_age_years, &mut render);
|
||||
push_overlay_meta(
|
||||
&mut render,
|
||||
"primary_directions",
|
||||
format!("GR Direcciones · {:.1}a", target_age_years),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,6 +567,69 @@ fn build_topocentric_overlay(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// GR dual-ring de Direcciones Primarias: a la edad pedida, cada
|
||||
/// cuerpo natal se proyecta dos veces — directa (rotación diurna
|
||||
/// forward, anillo afuera) y conversa (rotación inversa, anillo
|
||||
/// dentro). En rectificación, los dos rings se ven simultáneamente
|
||||
/// y si un evento real cayó cerca de un ángulo, debe aparecer
|
||||
/// "cruzado" con ambos arcos coincidentes — eso valida la hora.
|
||||
///
|
||||
/// Usa el key Naibod (0°59'08″/año) como default — convención GR.
|
||||
fn build_primary_directions_overlay(
|
||||
natal: &NatalChart,
|
||||
target_age_years: f64,
|
||||
render: &mut RenderModel,
|
||||
) {
|
||||
let key = EDirectionKey::Naibod;
|
||||
let eps = natal.obliquity_rad;
|
||||
|
||||
let project = |dir: PrimaryDirection| -> Vec<Glyph> {
|
||||
natal
|
||||
.placements
|
||||
.iter()
|
||||
.map(|p| {
|
||||
let new_lon_rad = directed_longitude(
|
||||
p.right_ascension_rad,
|
||||
p.declination_rad,
|
||||
target_age_years,
|
||||
dir,
|
||||
key,
|
||||
eps,
|
||||
);
|
||||
let new_lon_deg = new_lon_rad.to_degrees() as f32;
|
||||
Glyph {
|
||||
deg: new_lon_deg,
|
||||
symbol: body_symbol(p.body).into(),
|
||||
annotation: Some(format!("{:.2}°", new_lon_deg)),
|
||||
retrograde: p.longitude_rate_rad_per_day < 0.0,
|
||||
house: None,
|
||||
dignity_marker: None,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
let direct_glyphs = project(PrimaryDirection::Direct);
|
||||
let converse_glyphs = project(PrimaryDirection::Converse);
|
||||
|
||||
render.layers.push(Layer {
|
||||
module_id: "pd_direct".into(),
|
||||
kind: LayerKind::Bodies,
|
||||
ring: 0.0,
|
||||
z: 10,
|
||||
geometry: Geometry::GlyphsOnly,
|
||||
glyphs: direct_glyphs,
|
||||
});
|
||||
render.layers.push(Layer {
|
||||
module_id: "pd_converse".into(),
|
||||
kind: LayerKind::Bodies,
|
||||
ring: 0.0,
|
||||
z: 11,
|
||||
geometry: Geometry::GlyphsOnly,
|
||||
glyphs: converse_glyphs,
|
||||
});
|
||||
}
|
||||
|
||||
fn build_progression_overlay(
|
||||
natal: &NatalChart,
|
||||
target_age_years: f64,
|
||||
|
||||
@@ -333,6 +333,14 @@ pub enum PipelineRequest {
|
||||
/// (~1° de shift); imperceptible en planetas exteriores. La capa
|
||||
/// convive con la natal geocéntrica como overlay comparativo.
|
||||
Topocentric,
|
||||
/// `module_id = "pd_direct"` + `"pd_converse"` — Direcciones
|
||||
/// Primarias del Sistema GR (García Rosas). Cada cuerpo natal se
|
||||
/// proyecta dos veces: hacia adelante en el tiempo diurno
|
||||
/// (direct) y hacia atrás (converse). Los dos resultados a la
|
||||
/// edad pedida pintan un dual-ring para rectificación en vivo.
|
||||
PrimaryDirections {
|
||||
target_age_years: f64,
|
||||
},
|
||||
}
|
||||
|
||||
/// Opciones que afectan la pasada natal (qué aspectos pintar, qué
|
||||
|
||||
@@ -143,6 +143,7 @@ impl Registry {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -840,13 +841,13 @@ pub mod topocentric {
|
||||
matches!(kind, ChartKind::Natal)
|
||||
}
|
||||
fn enabled_by_default(&self) -> bool {
|
||||
false
|
||||
true
|
||||
}
|
||||
fn controls(&self) -> Vec<Control> {
|
||||
vec![Control::Toggle {
|
||||
key: "enabled".into(),
|
||||
label: "Activar".into(),
|
||||
default: false,
|
||||
default: true,
|
||||
hotkey: None,
|
||||
}]
|
||||
}
|
||||
@@ -855,3 +856,59 @@ pub mod topocentric {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// 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,
|
||||
},
|
||||
]
|
||||
}
|
||||
fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec<Layer> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user