feat(tahuantinsuyu): fase 5 — overlay de tránsitos (bi-wheel natal × ahora)

Activá el toggle "Tránsitos (ahora)" en el panel (o hotkey [T] sobre
el wheel): la engine computa una segunda NatalChart al instante
SystemTime::now() con el mismo observer y dibuja un anillo externo de
planet glyphs encima del natal, más las cross-aspects entre ambos
charts (sólo mayores). Las líneas cross van del ring de cuerpos
natales al ring externo de tránsitos, con stroke más fino y opacidad
más baja para no taparle el ojo a las aspectos natal-natal.

- engine/bridge.rs: extraídas build_eternal_inputs y
  compute_natal_chart como helpers reutilizables. Nueva
  compute_with_transits(chart, offset, transit_at) que llama
  find_synastry_aspects entre natal y transit (AspectKind::MAJORS).
  Atajo compute_with_transits_at_now usa ESInstant::now(). Las capas
  extra van con module_id = "transit" y LayerKind::Outer /
  LayerKind::Aspects para que el canvas las distinga.
- engine/lib.rs: re-export de compute_with_transits_at_now con el
  mismo fallback al mock cuando feature `eternal-bridge` está off.
- canvas: nueva Radii::transits = 0.82, layout del wheel re-balanceado
  (houses_outer 0.78, houses_inner 0.66, bodies 0.58, aspects 0.50)
  para hacer lugar al anillo externo sin colisiones. paint_wheel:
  detecta layers de transit por module_id, pinta dots + glifos en el
  anillo nuevo + anillos guía sutiles. paint_cross_aspect_line con
  stroke 0.7 entre los dos radios. Glyph overlay para Outer ring con
  alpha 0.9 y font_size más chico que el natal. Hotkey [T] en
  on_key_down toggle LayerKind::Outer.
- modules: NatalModule.controls() agrega toggle show_transits con
  hotkey [T] (default false — no recomputar transits si nadie pidió).
- shell: nuevo show_transits flag. render_current despacha entre
  compute_at_offset y compute_with_transits_at_now según el flag.
  on_panel_event traduce ControlChanged show_transits a flip + redraw.
  on_canvas_event: el toggle de LayerKind::Outer dispara show_transits
  flip + render (no es un visibility toggle puro).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-17 10:24:36 +00:00
parent 360797132e
commit 4d14a4495f
5 changed files with 320 additions and 39 deletions
+39 -8
View File
@@ -22,7 +22,7 @@ use gpui::{
use tahuantinsuyu_canvas::{
AstrologyCanvas, CanvasEvent, CanvasMode, ThumbnailItem, ThumbnailScope,
};
use tahuantinsuyu_engine::{LayerKind, compute_at_offset};
use tahuantinsuyu_engine::{LayerKind, compute_at_offset, compute_with_transits_at_now};
use tahuantinsuyu_model::{Chart, TreeSelection};
use tahuantinsuyu_panel::{ControlPanel, PanelEvent};
use tahuantinsuyu_store::Store;
@@ -44,6 +44,9 @@ pub struct Shell {
/// recomputarla con time-offsets sin re-leer la DB cada vez.
current_chart: Option<Chart>,
current_offset_minutes: i64,
/// Overlay de tránsitos al instante actual sobre la natal. Disparado
/// por el toggle `show_transits` del panel o la hotkey `[T]`.
show_transits: bool,
}
impl Shell {
@@ -78,6 +81,7 @@ impl Shell {
panel,
current_chart: None,
current_offset_minutes: 0,
show_transits: false,
}
}
@@ -164,12 +168,20 @@ impl Shell {
let Some(chart) = self.current_chart.as_ref() else {
return;
};
let render = match compute_at_offset(chart, self.current_offset_minutes) {
let result = if self.show_transits {
compute_with_transits_at_now(chart, self.current_offset_minutes)
} else {
compute_at_offset(chart, self.current_offset_minutes)
};
let render = match result {
Ok(r) => r,
Err(e) => {
eprintln!(
"[shell] compute_at_offset {} (+{}min): {}",
chart.id, self.current_offset_minutes, e
"[shell] compute {}{} (+{}min): {}",
chart.id,
if self.show_transits { " +transits" } else { "" },
self.current_offset_minutes,
e
);
return;
}
@@ -193,8 +205,17 @@ impl Shell {
}
}
CanvasEvent::LayerVisibilityChanged { kind, visible } => {
// Sync el panel para que el toggle visual coincida con
// lo que disparó el hotkey en el canvas.
// El toggle de Outer (hotkey [T]) significa "transit
// overlay" — no es solo un layer hide, dispara un
// recompute distinto. El resto son visibility puros.
if matches!(kind, LayerKind::Outer) {
self.show_transits = *visible;
self.panel.update(cx, |p, cx| {
p.set_toggle("natal", "show_transits", *visible, cx)
});
self.render_current(cx);
return;
}
let key = match kind {
LayerKind::SignDial => "show_sign_dial",
LayerKind::Houses => "show_houses",
@@ -213,9 +234,19 @@ impl Shell {
fn on_panel_event(&mut self, ev: &PanelEvent, cx: &mut Context<Self>) {
match ev {
PanelEvent::ControlChanged { module_id, key, value } => {
PanelEvent::ControlChanged {
module_id, key, value,
} => {
let visible = value.as_bool().unwrap_or(true);
if module_id == "natal" {
if key == "show_transits" {
self.show_transits = visible;
self.canvas.update(cx, |c, cx| {
c.set_layer_visible(LayerKind::Outer, visible, cx)
});
self.render_current(cx);
return;
}
let kind = match key.as_str() {
"show_sign_dial" => Some(LayerKind::SignDial),
"show_houses" => Some(LayerKind::Houses),
@@ -230,7 +261,7 @@ impl Shell {
}
}
PanelEvent::ModuleToggled { .. } => {
// Fase 5: encender/apagar módulos enteros (Transit,
// Fase 6: encender/apagar módulos enteros (Progression,
// Synastry, Uranian).
}
}