gioser

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 660Markdown 302TOML 157RON 312382 Definitions

⎇ main llimphi-voxel-studio/src/lab.rs Rust · 124 lines · 4.7 KB History Who touched what raw

chore: refresco desde el monorepo — 128 miembros, los 76 widgets y los 11 módulos b3f12e136 · Sergio ·

1use super::*;
2
3/// **Laboratorio de una ley `Ecuacion`**: un campo escalar 2D corriendo la ecuación
4/// autorada, para *ver* qué hace sin depender de materiales ni biomas. El heatmap del
5/// campo visible se pinta en el centro del nivel Leyes. Se reconstruye cuando cambia
6/// la ley (`ley_id`) o su estructura (`gen`); los parámetros se leen en vivo al pasar.
7pub(crate) struct LawLab {
8    /// Ley que refleja.
9    pub(crate) ley_id: u64,
10    /// Generación estructural con la que se construyó (campos/fuentes).
11    pub(crate) gen: u64,
12    /// Estado de los campos.
13    pub(crate) engine: FieldEngine,
14    /// Programa compilado (o el error de parseo, para mostrarlo).
15    pub(crate) program: Result<Program, String>,
16    /// Campo que se visualiza en el heatmap.
17    pub(crate) vis: usize,
18}
19
20impl LawLab {
21    /// Construye el laboratorio para una ley `Ecuacion` y lo siembra.
22    pub(crate) fn build(ley_id: u64, gen: u64, campos: &[FieldDef], program: Result<Program, String>, vis: usize) -> Self {
23        let mut engine = FieldEngine::new([LAB_DIM, 1, LAB_DIM], campos.to_vec());
24        seed_engine(&mut engine);
25        let vis = vis.min(campos.len().saturating_sub(1));
26        Self { ley_id, gen, engine, program, vis }
27    }
28
29    /// Reinicia los campos a su siembra (sin recompilar).
30    pub(crate) fn reseed(&mut self) {
31        seed_engine(&mut self.engine);
32    }
33
34    /// Avanza `LAB_SUBSTEPS` sub‑pasos si el programa compiló.
35    pub(crate) fn step(&mut self, params: &[f32]) {
36        if let Ok(prog) = &self.program {
37            for _ in 0..LAB_SUBSTEPS {
38                self.engine.step(prog, params, 1.0);
39            }
40        }
41    }
42}
43
44/// (Re)construye el laboratorio si la ley seleccionada es una `Ecuacion` y cambió
45/// (por id o por generación estructural). Compila **sólo al reconstruir**, no cada
46/// cuadro. Deja `model.lab = None` si el nivel no tiene una ley por ecuación.
47pub(crate) fn ensure_lab(model: &mut Model) {
48    let id = model.sel[0];
49    let is_ec = matches!(
50        model.project.ley(id).map(|l| &l.kind),
51        Some(LeyKind::Ecuacion { campos, .. }) if !campos.is_empty()
52    );
53    if !is_ec {
54        model.lab = None;
55        return;
56    }
57    let need = match &model.lab {
58        Some(lab) => lab.ley_id != id || lab.gen != model.lab_gen,
59        None => true,
60    };
61    if !need {
62        return;
63    }
64    let (campos, program) = match &model.project.ley(id).unwrap().kind {
65        k @ LeyKind::Ecuacion { campos, .. } => (campos.clone(), k.compile_ecuacion().unwrap()),
66        _ => unreachable!("is_ec ya lo garantiza"),
67    };
68    let vis = model.lab.as_ref().map(|x| x.vis).unwrap_or(0);
69    model.lab = Some(LawLab::build(id, model.lab_gen, &campos, program, vis));
70}
71
72/// (Re)construye el grafo de nodos a partir de las fórmulas de la ley seleccionada.
73pub(crate) fn rebuild_eq_graph(model: &mut Model) {
74    let built = sel_ley(model).and_then(|l| match &l.kind {
75        LeyKind::Ecuacion { fuentes, .. } => {
76            l.kind.ecuacion_symbols().map(|sym| EqGraph::from_fuentes(fuentes, &sym))
77        }
78        _ => None,
79    });
80    model.eq_graph = built;
81    model.eq_graph_gen = model.lab_gen;
82}
83
84/// Recompila el grafo a fórmulas y las escribe en la ley. Bump de `lab_gen` (para que
85/// el laboratorio recompile) marcando el grafo como en‑sync (no se reconstruye solo).
86pub(crate) fn sync_graph_to_fuentes(model: &mut Model) {
87    let Some(sym) = sel_ley(model).and_then(|l| l.kind.ecuacion_symbols()) else {
88        return;
89    };
90    let n = sym.campos.len();
91    let fuentes = match &model.eq_graph {
92        Some(g) => g.to_fuentes(n, &sym),
93        None => return,
94    };
95    if let Some(l) = sel_ley_mut(model) {
96        if let LeyKind::Ecuacion { fuentes: f, .. } = &mut l.kind {
97            *f = fuentes;
98        }
99    }
100    model.lab_gen += 1;
101    model.eq_graph_gen = model.lab_gen;
102}
103
104/// Siembra un motor de campo: cada campo a su `init` + una mancha central (para
105/// disparar difusión/reacción). La mancha = 60 % del rango, en un cuadro central.
106pub(crate) fn seed_engine(engine: &mut FieldEngine) {
107    let dim = engine.dim();
108    let defs: Vec<(f32, f32)> = engine.fields().iter().map(|d| (d.min, d.max)).collect();
109    let (cx, cz) = (dim[0] / 2, dim[2] / 2);
110    let r = (dim[0] / 8).max(2);
111    for (f, (mn, mx)) in defs.iter().enumerate() {
112        let blob = mn + 0.6 * (mx - mn);
113        for z in cz.saturating_sub(r)..(cz + r).min(dim[2]) {
114            for x in cx.saturating_sub(r)..(cx + r).min(dim[0]) {
115                engine.set(f as u16, x, 0, z, blob);
116            }
117        }
118    }
119}
120
121// =============================================================================
122//  Niveles de composición (rail izquierdo)
123// =============================================================================
124