use super::*; /// **Laboratorio de una ley `Ecuacion`**: un campo escalar 2D corriendo la ecuación /// autorada, para *ver* qué hace sin depender de materiales ni biomas. El heatmap del /// campo visible se pinta en el centro del nivel Leyes. Se reconstruye cuando cambia /// la ley (`ley_id`) o su estructura (`gen`); los parámetros se leen en vivo al pasar. pub(crate) struct LawLab { /// Ley que refleja. pub(crate) ley_id: u64, /// Generación estructural con la que se construyó (campos/fuentes). pub(crate) gen: u64, /// Estado de los campos. pub(crate) engine: FieldEngine, /// Programa compilado (o el error de parseo, para mostrarlo). pub(crate) program: Result, /// Campo que se visualiza en el heatmap. pub(crate) vis: usize, } impl LawLab { /// Construye el laboratorio para una ley `Ecuacion` y lo siembra. pub(crate) fn build(ley_id: u64, gen: u64, campos: &[FieldDef], program: Result, vis: usize) -> Self { let mut engine = FieldEngine::new([LAB_DIM, 1, LAB_DIM], campos.to_vec()); seed_engine(&mut engine); let vis = vis.min(campos.len().saturating_sub(1)); Self { ley_id, gen, engine, program, vis } } /// Reinicia los campos a su siembra (sin recompilar). pub(crate) fn reseed(&mut self) { seed_engine(&mut self.engine); } /// Avanza `LAB_SUBSTEPS` sub‑pasos si el programa compiló. pub(crate) fn step(&mut self, params: &[f32]) { if let Ok(prog) = &self.program { for _ in 0..LAB_SUBSTEPS { self.engine.step(prog, params, 1.0); } } } } /// (Re)construye el laboratorio si la ley seleccionada es una `Ecuacion` y cambió /// (por id o por generación estructural). Compila **sólo al reconstruir**, no cada /// cuadro. Deja `model.lab = None` si el nivel no tiene una ley por ecuación. pub(crate) fn ensure_lab(model: &mut Model) { let id = model.sel[0]; let is_ec = matches!( model.project.ley(id).map(|l| &l.kind), Some(LeyKind::Ecuacion { campos, .. }) if !campos.is_empty() ); if !is_ec { model.lab = None; return; } let need = match &model.lab { Some(lab) => lab.ley_id != id || lab.gen != model.lab_gen, None => true, }; if !need { return; } let (campos, program) = match &model.project.ley(id).unwrap().kind { k @ LeyKind::Ecuacion { campos, .. } => (campos.clone(), k.compile_ecuacion().unwrap()), _ => unreachable!("is_ec ya lo garantiza"), }; let vis = model.lab.as_ref().map(|x| x.vis).unwrap_or(0); model.lab = Some(LawLab::build(id, model.lab_gen, &campos, program, vis)); } /// (Re)construye el grafo de nodos a partir de las fórmulas de la ley seleccionada. pub(crate) fn rebuild_eq_graph(model: &mut Model) { let built = sel_ley(model).and_then(|l| match &l.kind { LeyKind::Ecuacion { fuentes, .. } => { l.kind.ecuacion_symbols().map(|sym| EqGraph::from_fuentes(fuentes, &sym)) } _ => None, }); model.eq_graph = built; model.eq_graph_gen = model.lab_gen; } /// Recompila el grafo a fórmulas y las escribe en la ley. Bump de `lab_gen` (para que /// el laboratorio recompile) marcando el grafo como en‑sync (no se reconstruye solo). pub(crate) fn sync_graph_to_fuentes(model: &mut Model) { let Some(sym) = sel_ley(model).and_then(|l| l.kind.ecuacion_symbols()) else { return; }; let n = sym.campos.len(); let fuentes = match &model.eq_graph { Some(g) => g.to_fuentes(n, &sym), None => return, }; if let Some(l) = sel_ley_mut(model) { if let LeyKind::Ecuacion { fuentes: f, .. } = &mut l.kind { *f = fuentes; } } model.lab_gen += 1; model.eq_graph_gen = model.lab_gen; } /// Siembra un motor de campo: cada campo a su `init` + una mancha central (para /// disparar difusión/reacción). La mancha = 60 % del rango, en un cuadro central. pub(crate) fn seed_engine(engine: &mut FieldEngine) { let dim = engine.dim(); let defs: Vec<(f32, f32)> = engine.fields().iter().map(|d| (d.min, d.max)).collect(); let (cx, cz) = (dim[0] / 2, dim[2] / 2); let r = (dim[0] / 8).max(2); for (f, (mn, mx)) in defs.iter().enumerate() { let blob = mn + 0.6 * (mx - mn); for z in cz.saturating_sub(r)..(cz + r).min(dim[2]) { for x in cx.saturating_sub(r)..(cx + r).min(dim[0]) { engine.set(f as u16, x, 0, z, blob); } } } } // ============================================================================= // Niveles de composición (rail izquierdo) // =============================================================================