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:
@@ -75,6 +75,10 @@ pub struct RenderModel {
|
||||
/// cerrados primero). La UI lo usa para la lista textual.
|
||||
#[serde(default)]
|
||||
pub aspect_summary: Vec<AspectSummary>,
|
||||
/// Grupos uranianos detectados (cuerpos en la misma posición mod 90).
|
||||
/// Vacío sino se activó el módulo Uranian.
|
||||
#[serde(default)]
|
||||
pub uranian_groups: Vec<UranianGroup>,
|
||||
}
|
||||
|
||||
/// Etiqueta legible de un overlay para el footer del canvas. La engine
|
||||
@@ -87,6 +91,23 @@ pub struct OverlayMeta {
|
||||
pub label: String,
|
||||
}
|
||||
|
||||
/// Grupo de cuerpos natales que caen en la misma posición del
|
||||
/// dial uraniano de 90° (su longitud zodiacal módulo 90 es igual o
|
||||
/// muy cercana). En la astrología uraniana esto es una "fórmula" o
|
||||
/// "axis" — los cuerpos están en correspondencia simbólica directa
|
||||
/// porque comparten un cuadrante simétrico.
|
||||
///
|
||||
/// Solo se emiten grupos con 2+ miembros (los singletons no son
|
||||
/// fórmulas). La engine los ordena por proximidad al ε de tolerancia.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UranianGroup {
|
||||
/// Identificadores agnósticos de los cuerpos en el grupo
|
||||
/// (ej. `["sun", "jupiter", "saturn"]`).
|
||||
pub bodies: Vec<String>,
|
||||
/// Posición en el dial de 90° (la longitud módulo 90).
|
||||
pub mod90_deg: f64,
|
||||
}
|
||||
|
||||
/// Resumen textual de un aspecto para listas legibles. La engine lo
|
||||
/// emite en paralelo con las `LineSeg` de la capa de aspectos, así
|
||||
/// el canvas no tiene que re-derivar nombres de cuerpos desde grados.
|
||||
@@ -274,6 +295,13 @@ pub enum PipelineRequest {
|
||||
Composite {
|
||||
partner_chart: Box<Chart>,
|
||||
},
|
||||
/// `module_id = "uranian"` — calcula los "ejes" del dial uraniano
|
||||
/// de 90°: agrupa los cuerpos natales cuya longitud módulo 90 cae
|
||||
/// dentro de una tolerancia (~2°). El resultado se publica en
|
||||
/// `RenderModel.uranian_groups` para que la UI lo liste como
|
||||
/// fórmulas analíticas. La visualización geométrica completa del
|
||||
/// dial de 90° queda pendiente para una fase posterior.
|
||||
Uranian,
|
||||
}
|
||||
|
||||
/// Opciones que afectan la pasada natal (qué aspectos pintar, qué
|
||||
@@ -400,6 +428,7 @@ pub fn compute_mock(chart: &Chart) -> RenderModel {
|
||||
layers: vec![sign_dial],
|
||||
overlays: Vec::new(),
|
||||
aspect_summary: Vec::new(),
|
||||
uranian_groups: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user