feat(tahuantinsuyu): fase 21 — background compute + UranianModule
Cierre del brief original — última pieza visual (Uraniano) + perf. ## #1 — Compute en background thread render_current() pasa de bloqueante a async. La pipeline corre en cx.background_executor().spawn (no UI thread), y al terminar el update vuelve al UI vía cx.spawn. Sin esto, un drag del slider con muchos overlays bloquea el frame por hasta 200ms. Cancelación: Shell gana `render_seq: u64`. Cada render_current() incrementa el counter y captura su número; el closure async compara antes de aplicar. Si llegó un compute más nuevo en el medio (drag rápido), el viejo se descarta — evita el race donde un cómputo lento sobrescribe uno reciente y rápido. Inputs al background: Chart clonado + offset + Vec<PipelineRequest> + NatalOptions. La sesión VSOP2013 sigue siendo `static OnceLock` read-only, accesible desde cualquier thread. ## #11 — UranianModule (versión textual) Cierra la última pieza del brief original. Toggle "Uraniano (90°)" en el panel; engine detecta cuerpos natales cuya longitud módulo 90 cae dentro de ε=2° y los agrupa como "ejes". Footer renderea cada grupo como pill con los unicodes (☉ ♃ · 14.3°) bajo el header "Ejes uranianos (90°)". El algoritmo: 1. mod90 = longitude.rem_euclid(90.0) para cada placement 2. Sort por mod90 ascendente 3. Walk lineal agrupando entradas con diff(mod90) ≤ ε 4. Wrap-around check: el primer y último grupo se mergean si abarcan el cierre del dial (88→2 = solo 4° de diff modular) 5. Solo emite grupos con 2+ miembros (singletons no son fórmulas) - engine: PipelineRequest::Uranian + UranianGroup struct + build_uranian_groups helper. RenderModel gana uranian_groups field. push_overlay_meta tipo "Uraniano · N ejes" o "sin ejes". - modules: uranian::UranianModule (toggle "Activar"). Registry pasa a 9 módulos para ChartKind::Natal. Test actualizado. - shell: build_requests detecta uranian.enabled, pushea PipelineRequest::Uranian (sin parámetros). - canvas: footer agrega sección "Ejes uranianos (90°)" con pills arriba de la lista de aspectos — border angle_highlight para invitar a la lectura. La visualización geométrica completa del dial de 90° con árbol de simetría al hover queda para una fase posterior — esta versión textual cubre el caso analítico (ver qué cuerpos están "en relación uraniana") sin requerir un canvas secundario. cargo check verde, 8 tests engine + 1 test modules (9 módulos aplicables a ChartKind::Natal) verdes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -139,6 +139,7 @@ impl Registry {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -663,8 +664,52 @@ mod tests {
|
||||
assert!(r.find("planetary_return").is_some());
|
||||
assert!(r.find("midpoints").is_some());
|
||||
assert!(r.find("composite").is_some());
|
||||
// Natal kind tiene 8 módulos aplicables.
|
||||
assert_eq!(r.for_kind(ChartKind::Natal).len(), 8);
|
||||
assert!(r.find("uranian").is_some());
|
||||
// Natal kind tiene 9 módulos aplicables.
|
||||
assert_eq!(r.for_kind(ChartKind::Natal).len(), 9);
|
||||
assert!(r.for_kind(ChartKind::Synastry).is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user