feat(tahuantinsuyu): anti-solapamiento de glyphs + selector Naibod/Ptolomeo

Tres mejoras de UX para manejar conjunciones (stelium) y dar más
control sobre el sistema GR:

1. `spread_angles(angles, min_sep_deg)`: reposiciona angularmente
   los glyphs adyacentes para que ningún par caiga más cerca que
   el threshold visual (derivado del ancho del label pill al
   radio del ring). Iterativo (≤60 pasos), re-ordena cada
   iteración para preservar el orden circular, devuelve también
   `residual` ∈ [0,1] = fracción de presión no resuelta. Las
   posiciones REALES no se tocan — solo afecta la geometría
   visual del glyph. 5 tests cubren: empty, separados intactos,
   cluster cerrado, orden preservado, cluster infactible.

2. Aplicación al render de Bodies (natal/topo/pd/outer): cada
   layer pasa por spread_angles antes de iterar glyphs. Si
   residual queda alta, los discos y fonts se encogen
   proporcionalmente (0.55..1.0×) y los coord labels se omiten —
   evita pillas montadas sobre el bloque.

3. `find_clusters(angles, threshold_deg)`: detecta grupos
   angularmente cercanos (incluye wrap-around 359°→1°). Glyphs en
   cluster de ≥3 miembros NO llevan coord label individual;
   en su lugar, al final del loop se pinta UN solo label
   compartido con los símbolos concatenados (ej. "☉ ☿ ♀  14°56'")
   posicionado en el centroide angular del cluster. El usuario
   sigue viendo cada planeta con su disco, pero no se ahoga en
   pills superpuestas.

4. Selector Naibod/Ptolomeo en PrimaryDirectionsModule via
   `Control::Select`. Default Naibod (0°59'08.33″/año, moderno).
   El shell extrae `module_configs["primary_directions"]["key"]`
   y lo pasa en `PipelineRequest::PrimaryDirections { key }`;
   el bridge mapea string → `DirectionKey` y pasa al cómputo.
   El overlay meta muestra qué clave se usó: "GR Direcciones ·
   30.5a · Naibod".

Tests: 16 verdes (6 shell + 5 spread + 5 coord).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-18 18:38:51 +00:00
parent a7214e0498
commit a92fa15777
5 changed files with 343 additions and 20 deletions
@@ -390,12 +390,31 @@ pub fn compose(
"Topocéntrico (Polich-Page)".into(),
);
}
crate::PipelineRequest::PrimaryDirections { target_age_years } => {
build_primary_directions_overlay(&natal, *target_age_years, &mut render);
crate::PipelineRequest::PrimaryDirections {
target_age_years,
key,
} => {
let dkey = match key.as_str() {
"ptolemy" => EDirectionKey::Ptolemy,
_ => EDirectionKey::Naibod,
};
build_primary_directions_overlay(
&natal,
*target_age_years,
dkey,
&mut render,
);
push_overlay_meta(
&mut render,
"primary_directions",
format!("GR Direcciones · {:.1}a", target_age_years),
format!(
"GR Direcciones · {:.1}a · {}",
target_age_years,
match dkey {
EDirectionKey::Naibod => "Naibod",
EDirectionKey::Ptolemy => "Ptolomeo",
}
),
);
}
}
@@ -578,9 +597,9 @@ fn build_topocentric_overlay(
fn build_primary_directions_overlay(
natal: &NatalChart,
target_age_years: f64,
key: EDirectionKey,
render: &mut RenderModel,
) {
let key = EDirectionKey::Naibod;
let eps = natal.obliquity_rad;
let project = |dir: PrimaryDirection| -> Vec<Glyph> {
@@ -338,8 +338,12 @@ pub enum PipelineRequest {
/// 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.
///
/// `key` controla la conversión arco↔año: "naibod" (default
/// moderno, 0°59'08.33″/año) o "ptolemy" (clásica, 1°/año).
PrimaryDirections {
target_age_years: f64,
key: String,
},
}