gioser

tawasuyu/brahman

Rust 574TOML 218Markdown 68JSON 198352 Sutichaykuna

⎇ 0ada1050f7ea45109d7ce6d97c37c8c3344eb0e3 crates/modules/cosmobiologia/cosmobiologia-render/src/lib.rs Rust · 249 siq'ikuna · 9.1 KB Ñawpa willay Pi imata llamkarqan ch'uya

feat(cosmobiologia): rectificador automático — escaneo GR (núcleo) 0ada1050f · sergio ·

1//! `cosmobiologia-render` — modelo y matemática de render
2//! **agnósticos de surface**. Lo consumen tanto el canvas gpui
3//! (nativo, render Vulkan/Metal) como el cliente web (WASM, render
4//! SVG / Canvas2D). Cualquier mejora del layout / spread / cluster /
5//! coords vive acá una sola vez y aparece en ambos clientes.
6//!
7//! ## Por qué un crate aparte
8//!
9//! `cosmobiologia-engine` arrastra `eternal-sky` (VSOP2013 + I/O de
10//! tablas) que **no compila a WASM** sin empaquetar 30+ MB de
11//! efemérides. Los tipos del `RenderModel` en sí son serde puro y
12//! sí compilan a WASM — extraerlos a este crate libera al cliente
13//! web de la dependencia transitiva.
14//!
15//! ## Capas
16//!
17//! 1. **Modelo de render** — `RenderModel`, `Layer`, `Glyph`,
18//!    `LineSeg`, `Geometry`, `LayerKind`. Estructuras serde-friendly
19//!    que el engine emite y los clients consumen.
20//! 2. **Matemática agnóstica** *(módulos siguientes, no en esta primera
21//!    versión)* — `polar_to_screen`, `spread_angles`, `find_clusters`,
22//!    `format_coord_compact`, `Radii`. Migran desde el canvas gpui.
23//! 3. **`DrawCommand`** *(módulo siguiente)* — primitivas de pintura
24//!    (line, circle, glyph, pill) que cada surface traduce a su API.
25
26#![forbid(unsafe_code)]
27#![warn(rust_2018_idioms)]
28
29use serde::{Deserialize, Serialize};
30
31pub use cosmobiologia_model::{Chart, ChartId, ChartKind};
32
33pub mod draw;
34pub mod gr;
35pub mod harmonic;
36pub mod math;
37pub mod palette;
38
39pub use draw::{
40    compose_wheel, draw_commands_to_svg, CompositionOpts, DrawCommand, Rgba, TextAnchor,
41};
42pub use gr::{compute_gr_triggers, convergencia_minima, GrDirection, GrTrigger};
43pub use harmonic::apply_harmonic;
44pub use math::{
45    find_clusters, format_coord_compact, polar_to_screen, spread_angles, Radii,
46};
47pub use palette::Palette;
48
49// =====================================================================
50// RenderModel — lo que el client renderea
51// =====================================================================
52
53/// Resultado agnóstico de un cómputo astrológico, listo para renderizar.
54/// El canvas gpui y el cliente web lo consumen idénticamente: el engine
55/// computa (en nativo, con eternal) y publica este struct.
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct RenderModel {
58    pub chart_id: ChartId,
59    pub chart_kind: ChartKind,
60    pub title: String,
61    #[serde(default)]
62    pub subtitle: Option<String>,
63    pub compute_ms: u64,
64
65    // ─── Ángulos del chart (grados eclípticos, 0..360) ───────────────
66    /// Ascendente — punto fijo de rotación del lienzo. La rueda se gira
67    /// de modo que el Asc cae a las 9 (lado izquierdo).
68    pub ascendant_deg: f32,
69    pub midheaven_deg: f32,
70    pub descendant_deg: f32,
71    pub imum_coeli_deg: f32,
72
73    /// Capas a pintar. Orden = z-order ascendente.
74    pub layers: Vec<Layer>,
75    /// Metadata humana por overlay activo (transit, progresión,
76    /// sinastría, retorno...). Vacío para una carta natal pura. La UI
77    /// la pinta como badges en el footer.
78    #[serde(default)]
79    pub overlays: Vec<OverlayMeta>,
80    /// Lista paralela a las LineSeg de aspectos — uno por aspecto
81    /// natal o cross. Ordenado por `orb_deg` ascendente (los más
82    /// cerrados primero). La UI lo usa para la lista textual.
83    #[serde(default)]
84    pub aspect_summary: Vec<AspectSummary>,
85    /// Grupos uranianos detectados (cuerpos en la misma posición mod 90).
86    /// Vacío sino se activó el módulo Uranian.
87    #[serde(default)]
88    pub uranian_groups: Vec<UranianGroup>,
89    /// Triggers del Sistema GR (direcciones primarias). Poblado sólo
90    /// cuando el módulo `primary_directions` está activo; ordenado por
91    /// `orb_deg` ascendente. La UI lo lista en el HUD de rectificación
92    /// y resalta los `event = true` (convergencias directo+converso).
93    #[serde(default)]
94    pub gr_triggers: Vec<GrTrigger>,
95    /// Orden de la carta armónica activa. `1` = carta natal pura.
96    #[serde(default = "default_harmonic")]
97    pub harmonic: u32,
98    /// Espectro de fuerza armónica: índice `i` = fuerza de la armónica
99    /// `i + 1`. Vacío salvo en modo armónico (`harmonic > 1`). La UI
100    /// lo pinta como histograma para guiar qué armónico mirar.
101    #[serde(default)]
102    pub harmonic_spectrum: Vec<f32>,
103}
104
105/// Default serde del campo `harmonic`: 1 (carta natal sin transformar).
106fn default_harmonic() -> u32 {
107    1
108}
109
110/// Etiqueta legible de un overlay para el footer del canvas. La engine
111/// la pushea desde cada `build_*_overlay`; el canvas solo lee y pinta.
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct OverlayMeta {
114    pub module_id: String,
115    /// Etiqueta corta — ej. "Tránsito ahora", "Progresión 38.2a",
116    /// "Sinastría · Ana", "Saturn return 29a".
117    pub label: String,
118}
119
120/// Grupo de cuerpos natales que caen en la misma posición del
121/// dial uraniano de 90° (su longitud zodiacal módulo 90 es igual o
122/// muy cercana). En la astrología uraniana esto es una "fórmula" o
123/// "axis" — los cuerpos están en correspondencia simbólica directa
124/// porque comparten un cuadrante simétrico.
125///
126/// Solo se emiten grupos con 2+ miembros (los singletons no son
127/// fórmulas). La engine los ordena por proximidad al ε de tolerancia.
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct UranianGroup {
130    /// Identificadores agnósticos de los cuerpos en el grupo
131    /// (ej. `["sun", "jupiter", "saturn"]`).
132    pub bodies: Vec<String>,
133    /// Posición en el dial de 90° (la longitud módulo 90).
134    pub mod90_deg: f64,
135}
136
137/// Resumen textual de un aspecto para listas legibles. La engine lo
138/// emite en paralelo con las `LineSeg` de la capa de aspectos, así
139/// el canvas no tiene que re-derivar nombres de cuerpos desde grados.
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct AspectSummary {
142    /// Module al que pertenece — "natal", "transit", "synastry",
143    /// "progression", "solar_arc", "planetary_return".
144    pub module_id: String,
145    /// Identificador agnóstico del cuerpo "a" — "sun", "moon", etc.
146    pub from_body: String,
147    pub to_body: String,
148    /// Identificador del aspecto — "conjunction", "trine", etc.
149    pub kind: String,
150    pub orb_deg: f64,
151    /// `Some(true)` = applying, `Some(false)` = separating. `None` para
152    /// cross-aspects (sinastría/return) donde no se computa.
153    #[serde(default)]
154    pub applying: Option<bool>,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct Layer {
159    pub module_id: String,
160    pub kind: LayerKind,
161    /// Radio normalizado [0, 1] sobre el lienzo — el canvas lo convierte
162    /// a píxeles. Permite stack de anillos.
163    pub ring: f32,
164    #[serde(default)]
165    pub z: i32,
166    pub geometry: Geometry,
167    #[serde(default)]
168    pub glyphs: Vec<Glyph>,
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
172#[serde(rename_all = "snake_case")]
173pub enum LayerKind {
174    SignDial,
175    Houses,
176    Bodies,
177    Aspects,
178    Lots,
179    FixedStars,
180    Midpoints,
181    Outer,
182    Custom,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
186pub enum Geometry {
187    GlyphsOnly,
188    /// Anillo dividido en sectores. `cusps_deg` son los grados
189    /// zodiacales donde van las divisiones radiales.
190    Ring { cusps_deg: Vec<f32> },
191    Lines(Vec<LineSeg>),
192    Points(Vec<PointMark>),
193}
194
195#[derive(Debug, Clone, Default, Serialize, Deserialize)]
196pub struct LineSeg {
197    /// Grados zodiacales del extremo "a".
198    pub from_deg: f32,
199    /// Grados zodiacales del extremo "b".
200    pub to_deg: f32,
201    /// Categoría simbólica (`"conjunction"`, `"trine"`, …) — el theme la
202    /// resuelve a color.
203    pub kind: String,
204    pub opacity: f32,
205    /// Cuerpo en el extremo "a" — populado para LineSegs de aspectos
206    /// (natal × natal, cross con overlays). Vacío en `Default::default`
207    /// para serde back-compat.
208    #[serde(default)]
209    pub from_body: String,
210    /// Cuerpo en el extremo "b".
211    #[serde(default)]
212    pub to_body: String,
213    /// Orb absoluto en grados (para tooltips).
214    #[serde(default)]
215    pub orb_deg: f32,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct PointMark {
220    pub deg: f32,
221    pub label: String,
222    pub tag: String,
223}
224
225#[derive(Debug, Clone, Default, Serialize, Deserialize)]
226pub struct Glyph {
227    /// Grado eclíptico [0, 360).
228    pub deg: f32,
229    /// Glyph simbólico — el theme/canvas lo mapea a unicode o imagen.
230    /// Ej: `"sun"`, `"moon"`, `"aries"`, `"asc"`, `"mc"`.
231    pub symbol: String,
232    #[serde(default)]
233    pub annotation: Option<String>,
234    #[serde(default)]
235    pub retrograde: bool,
236    #[serde(default)]
237    pub house: Option<u8>,
238    /// Marker de dignidad esencial, set solo cuando
239    /// `NatalOptions::show_dignities` está activo: `"+"` (domicilio),
240    /// `"·"` (exaltación), `"−"` (exilio), `"*"` (caída).
241    #[serde(default)]
242    pub dignity_marker: Option<String>,
243}
244
245/// Módulos overlay que pintan en el mismo slot (outer ring del wheel)
246/// y por lo tanto son **mutuamente excluyentes** a nivel de UI: al
247/// prender uno, el shell debe apagar los otros. Single source of truth
248/// — el shell y el canvas leen de acá en vez de hardcodear listas.
249pub const OUTER_RING_MODULES: &[&str] = &["transit", "synastry", "planetary_return"];