⎇ main
crates/modules/dominium/dominium-core/src/lemmings.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
feat(dominium): dominium-core — núcleo del simulador de campo medio
1//! Los Agentes Vectoriales — Lemmings en Structure-of-Arrays.
2//!
3//! Sin objetos ni punteros por agente: vectores paralelos indexados por
4//! un `usize` continuo. Datos crudos alineados en caché.
5
6use serde::{Deserialize, Serialize};
7
8/// Índices de las cuatro componentes de `vector_psi`.
9pub const PSI_ORDEN: usize = 0;
10pub const PSI_MIEDO: usize = 1;
11pub const PSI_CURIOSIDAD: usize = 2;
12pub const PSI_CORRUPTIBILIDAD: usize = 3;
13
14/// Población de Lemmings en SoA. Todos los vectores tienen el mismo largo.
15#[derive(Debug, Clone, Default, Serialize, Deserialize)]
16pub struct Lemmings {
17 pub pos_x: Vec<f32>,
18 pub pos_y: Vec<f32>,
19 /// Contador incremental de ticks de vida.
20 pub edad: Vec<u32>,
21 /// Escalar de salud; si llega a 0 el agente muere.
22 pub energia: Vec<f32>,
23 /// Tensores de sesgo interno `[Orden, Miedo, Curiosidad, Corruptibilidad]`.
24 pub vector_psi: Vec<[f32; 4]>,
25 /// Byte discriminador de la máquina de estados (0-5).
26 pub accion: Vec<u8>,
27}
28
29impl Lemmings {
30 pub fn new() -> Self {
31 Self::default()
32 }
33
34 pub fn len(&self) -> usize {
35 self.pos_x.len()
36 }
37
38 pub fn is_empty(&self) -> bool {
39 self.pos_x.is_empty()
40 }
41
42 /// Instancia un Lemming nuevo (edad 0). Devuelve su índice.
43 pub fn spawn(&mut self, x: f32, y: f32, energia: f32, psi: [f32; 4]) -> usize {
44 let i = self.len();
45 self.pos_x.push(x);
46 self.pos_y.push(y);
47 self.edad.push(0);
48 self.energia.push(energia);
49 self.vector_psi.push(psi);
50 self.accion.push(0);
51 i
52 }
53
54 /// Elimina el Lemming `i` por `swap_remove` — O(1), no preserva el
55 /// orden (el último ocupa el hueco).
56 pub fn remove(&mut self, i: usize) {
57 self.pos_x.swap_remove(i);
58 self.pos_y.swap_remove(i);
59 self.edad.swap_remove(i);
60 self.energia.swap_remove(i);
61 self.vector_psi.swap_remove(i);
62 self.accion.swap_remove(i);
63 }
64
65 /// Distancia euclidiana al cuadrado entre dos Lemmings (sin `sqrt` —
66 /// suficiente para comparar cercanía y bit-exacto).
67 pub fn dist2(&self, a: usize, b: usize) -> f32 {
68 let dx = self.pos_x[a] - self.pos_x[b];
69 let dy = self.pos_y[a] - self.pos_y[b];
70 dx * dx + dy * dy
71 }
72
73 /// Índice del Lemming vivo más cercano a `i` (distinto de `i`), o
74 /// `None` si es el único. Determinista: ante empate gana el menor
75 /// índice.
76 pub fn nearest(&self, i: usize) -> Option<usize> {
77 let mut best: Option<(usize, f32)> = None;
78 for j in 0..self.len() {
79 if j == i {
80 continue;
81 }
82 let d = self.dist2(i, j);
83 if best.map(|(_, bd)| d < bd).unwrap_or(true) {
84 best = Some((j, d));
85 }
86 }
87 best.map(|(j, _)| j)
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn spawn_and_remove() {
97 let mut l = Lemmings::new();
98 let a = l.spawn(1.0, 1.0, 10.0, [0.0; 4]);
99 let _b = l.spawn(2.0, 2.0, 20.0, [0.0; 4]);
100 assert_eq!((a, l.len()), (0, 2));
101 l.remove(a);
102 assert_eq!(l.len(), 1);
103 // swap_remove: el agente "b" ocupa el índice 0.
104 assert_eq!(l.energia[0], 20.0);
105 }
106
107 #[test]
108 fn nearest_picks_closest_and_breaks_ties_by_index() {
109 let mut l = Lemmings::new();
110 l.spawn(0.0, 0.0, 1.0, [0.0; 4]); // 0
111 l.spawn(10.0, 0.0, 1.0, [0.0; 4]); // 1 — lejos
112 l.spawn(1.0, 0.0, 1.0, [0.0; 4]); // 2 — cerca de 0
113 assert_eq!(l.nearest(0), Some(2));
114 // Único agente → None.
115 let mut solo = Lemmings::new();
116 solo.spawn(0.0, 0.0, 1.0, [0.0; 4]);
117 assert_eq!(solo.nearest(0), None);
118 }
119}