tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
chore: refresco desde el monorepo — 128 miembros, los 76 widgets y los 11 módulos
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