feat(tahuantinsuyu): fase 7 — progresión secundaria como overlay (prueba de la arquitectura)

Valida que el refactor de fase 6 escala: agregar un overlay nuevo
(progresión secundaria, "día por año") tocó exactamente lo predicho —
una variante en PipelineRequest, un helper en bridge, un módulo
declarativo en `progression`, una línea en build_requests, y el canvas
para pintarlo. Cero cambios en el flujo de eventos del Shell.

- engine: PipelineRequest::SecondaryProgression { target_age_years: f64 }
  + build_progression_overlay(natal, age, render) que delega en
  eternal_astrology::secondary_progression(natal, session, age), pinta
  los placements progresados en un anillo interno (ring 0.48), y suma
  cross-aspects natal × progresada vía find_synastry_aspects (sólo
  mayores, opacidad × 0.7). z = 6/7 — sobre las capas natal y
  transit.
- modules: progression::ProgressionModule con id "progression", toggle
  "Activar" (sin hotkey por ahora). Registry::with_builtins lo agrega.
  El test pasó de 2 a 3 módulos para ChartKind::Natal.
- shell: build_requests detecta progression.enabled, calcula la edad
  decimal desde StoredBirthData y SystemTime::now() (current_age_years
  helper, aproximación tropical) y arma el request con esa edad.
  El resto del flujo del shell se mantiene — la abstracción funciona.
- canvas: Radii agrega `progression: r * 0.48`, `aspects` shrunk a
  `r * 0.38` para hacer lugar. Helper aspect_endpoints(module_id)
  resuelve el par (r_from, r_to) según natal/transit/progression.
  paint_wheel pinta dots progresados con alpha 0.85 + anillo guía
  sutil que delimita el slot. Glyph overlay pinta planet symbols en
  el ring de progresión con font_size 14 y box 20 (menores que el
  natal para diferenciar visualmente).

Probarlo: en el panel, activar "Progresión secundaria" — verás los
planetas progresados en un anillo interno con su retrogradación
marcada, y líneas de aspectos que cruzan desde el ring de cuerpos
natales hacia el ring progresivo. Combinable con tránsitos: ambos
overlays apilan capas en orden bodies → transits, sin colisiones.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-17 10:42:25 +00:00
parent d4761bf238
commit 42e09fd7cd
5 changed files with 226 additions and 24 deletions
+24
View File
@@ -178,6 +178,14 @@ impl Shell {
if module_enabled(&self.module_configs, "transit") {
requests.push(PipelineRequest::Transit);
}
if module_enabled(&self.module_configs, "progression") {
if let Some(chart) = self.current_chart.as_ref() {
let age = current_age_years(&chart.birth_data);
requests.push(PipelineRequest::SecondaryProgression {
target_age_years: age,
});
}
}
requests
}
@@ -301,6 +309,22 @@ impl Shell {
// Helpers de module_configs
// =====================================================================
/// Edad en años decimales desde el nacimiento hasta el reloj actual.
/// Aproximación: ignora la TZ de nacimiento (no afecta a resolución de
/// año) y usa una fracción de año tropical sobre los segundos Unix.
fn current_age_years(birth: &tahuantinsuyu_model::StoredBirthData) -> f64 {
use std::time::{SystemTime, UNIX_EPOCH};
let now_secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs_f64())
.unwrap_or(0.0);
let birth_year_frac = birth.year as f64
+ (birth.month.saturating_sub(1) as f64) / 12.0
+ (birth.day.saturating_sub(1) as f64) / 365.25;
let now_year_frac = 1970.0 + now_secs / (365.2422 * 86400.0);
(now_year_frac - birth_year_frac).max(0.0)
}
fn module_enabled(cfgs: &HashMap<String, serde_json::Value>, id: &str) -> bool {
cfgs.get(id)
.and_then(|c| c.get("enabled"))