Create src/lib.rs
Browse files- rust-lattice/src/lib.rs +354 -0
rust-lattice/src/lib.rs
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use wasm_bindgen::prelude::*;
|
| 2 |
+
use rand::{Rng, RngCore, SeedableRng, rngs::StdRng};
|
| 3 |
+
use std::f32::consts::PI;
|
| 4 |
+
|
| 5 |
+
const Q: i32 = 8380417; // Modulus: 2^23 - 2^13 + 1
|
| 6 |
+
const N: usize = 1024; // Polynomial degree
|
| 7 |
+
const ROOT_OF_UNITY: i64 = 1753; // 2n-th root of unity modulo q
|
| 8 |
+
const INV_N: i64 = 8372225; // n^-1 mod q
|
| 9 |
+
|
| 10 |
+
#[wasm_bindgen]
|
| 11 |
+
pub struct SecureRNG {
|
| 12 |
+
rng: StdRng,
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
#[wasm_bindgen]
|
| 16 |
+
impl SecureRNG {
|
| 17 |
+
#[wasm_bindgen(constructor)]
|
| 18 |
+
pub fn new() -> Self {
|
| 19 |
+
Self {
|
| 20 |
+
rng: StdRng::from_entropy(),
|
| 21 |
+
}
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
pub fn next_float(&mut self) -> f32 {
|
| 25 |
+
self.rng.gen::<f32>()
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
pub fn next_uint32(&mut self) -> u32 {
|
| 29 |
+
self.rng.next_u32()
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
pub fn gaussian(&mut self, sigma: f32) -> i32 {
|
| 33 |
+
let u: f32 = self.rng.gen_range(0.0001..1.0);
|
| 34 |
+
let v: f32 = self.rng.gen_range(0.0001..1.0);
|
| 35 |
+
|
| 36 |
+
((-2.0 * u.ln()).sqrt() * (2.0 * PI * v).cos() * sigma).round() as i32
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
pub fn cbd(&mut self, eta: i32) -> i32 {
|
| 40 |
+
let mut res = 0;
|
| 41 |
+
for _ in 0..eta {
|
| 42 |
+
let t1 = (self.rng.next_u32() & 1) as i32;
|
| 43 |
+
let t2 = (self.rng.next_u32() & 1) as i32;
|
| 44 |
+
res += t1 - t2;
|
| 45 |
+
}
|
| 46 |
+
res
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
pub struct Cell {
|
| 51 |
+
pub gx: f32,
|
| 52 |
+
pub gy: f32,
|
| 53 |
+
pub size_mult: f32,
|
| 54 |
+
pub stretch_x: f32,
|
| 55 |
+
pub stretch_y: f32,
|
| 56 |
+
pub jitter_x: f32,
|
| 57 |
+
pub jitter_y: f32,
|
| 58 |
+
pub drift_phase: f32,
|
| 59 |
+
pub drift_speed: f32,
|
| 60 |
+
pub alpha: f32,
|
| 61 |
+
pub rotation: f32,
|
| 62 |
+
pub shape_offsets: Vec<f32>,
|
| 63 |
+
pub hue_shift: f32,
|
| 64 |
+
pub layer: u8,
|
| 65 |
+
pub edge_style: f32,
|
| 66 |
+
pub eye_warp: f32,
|
| 67 |
+
pub mouth_warp: f32,
|
| 68 |
+
pub asymmetry: f32,
|
| 69 |
+
pub phase_lag: f32,
|
| 70 |
+
pub texture_shift: f32,
|
| 71 |
+
pub lwe_index: usize,
|
| 72 |
+
pub persistent_offset_x: f32,
|
| 73 |
+
pub persistent_offset_y: f32,
|
| 74 |
+
pub gaze_mismatch: f32,
|
| 75 |
+
pub stutter_frequency: f32,
|
| 76 |
+
pub tissue_inertia: f32,
|
| 77 |
+
pub blink_lag: f32,
|
| 78 |
+
pub is_neural_frozen: bool,
|
| 79 |
+
pub freeze_duration: f32,
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
#[wasm_bindgen]
|
| 83 |
+
pub struct LatticeEngine {
|
| 84 |
+
cells: Vec<Cell>,
|
| 85 |
+
b_field: Vec<f32>,
|
| 86 |
+
target_b_field: Vec<f32>,
|
| 87 |
+
color_b_field: Vec<f32>,
|
| 88 |
+
target_color_b_field: Vec<f32>,
|
| 89 |
+
hidden_manifold: Vec<f32>,
|
| 90 |
+
latent_secret: Vec<i32>,
|
| 91 |
+
drift_field_a: Vec<i32>,
|
| 92 |
+
ntt_psi: Vec<i32>,
|
| 93 |
+
ntt_psi_inv: Vec<i32>,
|
| 94 |
+
rng: SecureRNG,
|
| 95 |
+
frame_count: usize,
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
#[wasm_bindgen]
|
| 99 |
+
impl LatticeEngine {
|
| 100 |
+
#[wasm_bindgen(constructor)]
|
| 101 |
+
pub fn new() -> Self {
|
| 102 |
+
let mut rng = SecureRNG::new();
|
| 103 |
+
let mut hidden_manifold = vec![0.0; N];
|
| 104 |
+
let mut latent_secret = vec![0; N];
|
| 105 |
+
for i in 0..N {
|
| 106 |
+
hidden_manifold[i] = rng.next_float() - 0.5;
|
| 107 |
+
latent_secret[i] = rng.gaussian(2.0);
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
let mut ntt_psi = vec![0; N];
|
| 111 |
+
let mut ntt_psi_inv = vec![0; N];
|
| 112 |
+
let psi = Self::mod_pow(ROOT_OF_UNITY as i64, 1);
|
| 113 |
+
let psi_inv = Self::mod_pow(psi, (Q - 2) as i64);
|
| 114 |
+
|
| 115 |
+
for i in 0..N {
|
| 116 |
+
ntt_psi[i] = Self::mod_pow(psi, Self::bit_reverse(i, 10) as i64) as i32;
|
| 117 |
+
ntt_psi_inv[i] = Self::mod_pow(psi_inv, Self::bit_reverse(i, 10) as i64) as i32;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
Self {
|
| 121 |
+
cells: Vec::new(),
|
| 122 |
+
b_field: vec![0.0; N],
|
| 123 |
+
target_b_field: vec![0.0; N],
|
| 124 |
+
color_b_field: vec![0.0; N],
|
| 125 |
+
target_color_b_field: vec![0.0; N],
|
| 126 |
+
hidden_manifold,
|
| 127 |
+
latent_secret,
|
| 128 |
+
drift_field_a: vec![0; N],
|
| 129 |
+
ntt_psi,
|
| 130 |
+
ntt_psi_inv,
|
| 131 |
+
rng,
|
| 132 |
+
frame_count: 0,
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
fn mod_pow(base: i64, exp: i64) -> i64 {
|
| 137 |
+
let mut res = 1i64;
|
| 138 |
+
let mut b = base % Q as i64;
|
| 139 |
+
let mut e = exp;
|
| 140 |
+
while e > 0 {
|
| 141 |
+
if e % 2 == 1 {
|
| 142 |
+
res = (res * b) % Q as i64;
|
| 143 |
+
}
|
| 144 |
+
b = (b * b) % Q as i64;
|
| 145 |
+
e /= 2;
|
| 146 |
+
}
|
| 147 |
+
res
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
fn bit_reverse(mut x: usize, bits: usize) -> usize {
|
| 151 |
+
let mut res = 0;
|
| 152 |
+
for _ in 0..bits {
|
| 153 |
+
res = (res << 1) | (x & 1);
|
| 154 |
+
x >>= 1;
|
| 155 |
+
}
|
| 156 |
+
res
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
pub fn init_lattice(&mut self, dim: usize, rlwe_n: usize) {
|
| 160 |
+
self.cells.clear();
|
| 161 |
+
for gy in 0..dim {
|
| 162 |
+
for gx in 0..dim {
|
| 163 |
+
let rand_val = self.rng.next_float();
|
| 164 |
+
let mut size_mult = 0.5 + self.rng.next_float() * 0.3;
|
| 165 |
+
let mut layer = 0;
|
| 166 |
+
let edge_style = self.rng.next_float();
|
| 167 |
+
|
| 168 |
+
if rand_val > 0.5 && rand_val <= 0.85 {
|
| 169 |
+
size_mult = 0.9 + self.rng.next_float() * 0.4;
|
| 170 |
+
layer = 1;
|
| 171 |
+
} else if rand_val > 0.85 {
|
| 172 |
+
size_mult = 1.3 + self.rng.next_float() * 0.5;
|
| 173 |
+
layer = 2;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
let shape_count = 4 + (self.rng.next_float() * 3.0) as usize;
|
| 177 |
+
let mut shape_offsets = Vec::with_capacity(shape_count);
|
| 178 |
+
for _ in 0..shape_count {
|
| 179 |
+
shape_offsets.push(0.6 + self.rng.next_float() * 0.8);
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
self.cells.push(Cell {
|
| 183 |
+
gx: gx as f32,
|
| 184 |
+
gy: gy as f32,
|
| 185 |
+
size_mult,
|
| 186 |
+
stretch_x: 0.6 + self.rng.next_float() * 0.8,
|
| 187 |
+
stretch_y: 0.6 + self.rng.next_float() * 0.8,
|
| 188 |
+
jitter_x: (self.rng.next_float() - 0.5) * 1.5,
|
| 189 |
+
jitter_y: (self.rng.next_float() - 0.5) * 1.5,
|
| 190 |
+
drift_phase: self.rng.next_float() * PI * 2.0,
|
| 191 |
+
drift_speed: 0.05 + self.rng.next_float() * 0.15,
|
| 192 |
+
alpha: 0.5 + self.rng.next_float() * 0.45,
|
| 193 |
+
rotation: self.rng.next_float() * PI * 2.0,
|
| 194 |
+
shape_offsets,
|
| 195 |
+
hue_shift: (self.rng.next_float() - 0.5) * 16.0,
|
| 196 |
+
layer,
|
| 197 |
+
edge_style,
|
| 198 |
+
eye_warp: (self.rng.next_float() - 0.5) * 0.8,
|
| 199 |
+
mouth_warp: (self.rng.next_float() - 0.5) * 0.6,
|
| 200 |
+
asymmetry: (self.rng.next_float() - 0.5) * 0.4,
|
| 201 |
+
phase_lag: self.rng.next_float() * PI * 2.0,
|
| 202 |
+
texture_shift: (self.rng.next_float() - 0.5) * 8.0,
|
| 203 |
+
lwe_index: (self.rng.next_float() * rlwe_n as f32) as usize,
|
| 204 |
+
persistent_offset_x: (self.rng.next_float() - 0.5) * 4.0,
|
| 205 |
+
persistent_offset_y: (self.rng.next_float() - 0.5) * 4.0,
|
| 206 |
+
gaze_mismatch: 0.9 + self.rng.next_float() * 0.2,
|
| 207 |
+
stutter_frequency: 0.015 + self.rng.next_float() * 0.05,
|
| 208 |
+
tissue_inertia: 0.8 + self.rng.next_float() * 0.6,
|
| 209 |
+
blink_lag: self.rng.next_float() * 120.0,
|
| 210 |
+
is_neural_frozen: false,
|
| 211 |
+
freeze_duration: 0.0,
|
| 212 |
+
});
|
| 213 |
+
}
|
| 214 |
+
}
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
fn run_forward_ntt(a: &mut [i32], psi: &[i32]) {
|
| 218 |
+
let mut k = 1;
|
| 219 |
+
for len in (1..N).rev().step_by(1) {
|
| 220 |
+
// Simplified for brevity, in a real implementation we'd use the full Cooley-Tukey
|
| 221 |
+
// But I'll mirror the TS implementation exactly.
|
| 222 |
+
}
|
| 223 |
+
// Mirroring the TS logic exactly is safer for "exact same behavior"
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
// Since I want to be 100% accurate, I'll copy the logic I just refined in TS.
|
| 227 |
+
pub fn rotate_field(&mut self, error_width: i32) {
|
| 228 |
+
let mut b_final_warp = vec![0i32; N];
|
| 229 |
+
let mut b_final_color = vec![0i32; N];
|
| 230 |
+
|
| 231 |
+
for i in 0..N {
|
| 232 |
+
self.drift_field_a[i] = (self.rng.next_uint32() % Q as u32) as i32;
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
self.poly_multiply_ntt(&self.drift_field_a, &self.latent_secret, &mut b_final_warp);
|
| 236 |
+
|
| 237 |
+
let mut a2 = vec![0i32; N];
|
| 238 |
+
for i in 0..N {
|
| 239 |
+
a2[i] = (self.rng.next_uint32() % Q as u32) as i32;
|
| 240 |
+
}
|
| 241 |
+
self.poly_multiply_ntt(&a2, &self.latent_secret, &mut b_final_color);
|
| 242 |
+
|
| 243 |
+
for i in 0..N {
|
| 244 |
+
let error_w = self.rng.cbd(error_width);
|
| 245 |
+
let val_w = (b_final_warp[i] + error_w + Q) % Q;
|
| 246 |
+
let shell_w = (val_w as f32 / Q as f32) - 0.5;
|
| 247 |
+
self.target_b_field[i] = shell_w * 0.95 + self.hidden_manifold[i] * 0.05;
|
| 248 |
+
|
| 249 |
+
let error_c = self.rng.cbd(error_width);
|
| 250 |
+
let val_c = (b_final_color[i] + error_c + Q) % Q;
|
| 251 |
+
let shell_c = (val_c as f32 / Q as f32) - 0.5;
|
| 252 |
+
self.target_color_b_field[i] = shell_c * 0.95 + self.hidden_manifold[i] * 0.05;
|
| 253 |
+
}
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
fn poly_multiply_ntt(&self, a: &[i32], s: &[i32], res: &mut [i32]) {
|
| 257 |
+
let mut a_ntt = a.to_vec();
|
| 258 |
+
let mut s_ntt = s.to_vec();
|
| 259 |
+
self.forward_ntt(&mut a_ntt);
|
| 260 |
+
self.forward_ntt(&mut s_ntt);
|
| 261 |
+
for i in 0..N {
|
| 262 |
+
res[i] = ((a_ntt[i] as i64 * s_ntt[i] as i64) % Q as i64) as i32;
|
| 263 |
+
}
|
| 264 |
+
self.inverse_ntt(res);
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
fn forward_ntt(&self, a: &mut [i32]) {
|
| 268 |
+
let mut k = 1;
|
| 269 |
+
let mut len = N / 2;
|
| 270 |
+
while len >= 1 {
|
| 271 |
+
let mut start = 0;
|
| 272 |
+
while start < N {
|
| 273 |
+
let zeta = self.ntt_psi[k] as i64;
|
| 274 |
+
k += 1;
|
| 275 |
+
for j in start..start + len {
|
| 276 |
+
let t = (zeta * a[j + len] as i64) % Q as i64;
|
| 277 |
+
a[j + len] = ((a[j] as i64 - t + Q as i64) % Q as i64) as i32;
|
| 278 |
+
a[j] = ((a[j] as i64 + t) % Q as i64) as i32;
|
| 279 |
+
}
|
| 280 |
+
start += 2 * len;
|
| 281 |
+
}
|
| 282 |
+
len /= 2;
|
| 283 |
+
}
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
fn inverse_ntt(&self, a: &mut [i32]) {
|
| 287 |
+
let mut k = N - 1;
|
| 288 |
+
let mut len = 1;
|
| 289 |
+
while len < N {
|
| 290 |
+
let mut start = 0;
|
| 291 |
+
while start < N {
|
| 292 |
+
let zeta = self.ntt_psi_inv[k] as i64;
|
| 293 |
+
k -= 1;
|
| 294 |
+
for j in start..start + len {
|
| 295 |
+
let u = a[j] as i64;
|
| 296 |
+
let v = (a[j + len] as i64 * zeta) % Q as i64;
|
| 297 |
+
a[j] = ((u + v) % Q as i64) as i32;
|
| 298 |
+
a[j + len] = ((u - v + Q as i64) % Q as i64) as i32;
|
| 299 |
+
}
|
| 300 |
+
start += 2 * len;
|
| 301 |
+
}
|
| 302 |
+
len *= 2;
|
| 303 |
+
}
|
| 304 |
+
for i in 0..N {
|
| 305 |
+
a[i] = ((a[i] as i64 * INV_N) % Q as i64) as i32;
|
| 306 |
+
}
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
pub fn update(&mut self, alpha_smooth: f32) {
|
| 310 |
+
for i in 0..N {
|
| 311 |
+
let d_w = self.target_b_field[i] - self.b_field[i];
|
| 312 |
+
let d_c = self.target_color_b_field[i] - self.color_b_field[i];
|
| 313 |
+
self.b_field[i] += d_w.tanh() * alpha_smooth;
|
| 314 |
+
self.color_b_field[i] += d_c.tanh() * alpha_smooth;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
// Neural Stutter and Freezing logic in Rust
|
| 318 |
+
for cell in &mut self.cells {
|
| 319 |
+
if cell.is_neural_frozen {
|
| 320 |
+
cell.freeze_duration -= 16.6;
|
| 321 |
+
if cell.freeze_duration <= 0.0 {
|
| 322 |
+
cell.is_neural_frozen = false;
|
| 323 |
+
}
|
| 324 |
+
} else if self.rng.next_float() < 0.0005 {
|
| 325 |
+
cell.is_neural_frozen = true;
|
| 326 |
+
cell.freeze_duration = 100.0 + self.rng.next_float() * 300.0;
|
| 327 |
+
}
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
self.frame_count += 1;
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
pub fn get_cells_raw(&self) -> Vec<f32> {
|
| 334 |
+
let mut data = Vec::with_capacity(self.cells.len() * 10);
|
| 335 |
+
for cell in &self.cells {
|
| 336 |
+
data.push(cell.gx);
|
| 337 |
+
data.push(cell.gy);
|
| 338 |
+
data.push(cell.lwe_index as f32);
|
| 339 |
+
data.push(if cell.is_neural_frozen { 1.0 } else { 0.0 });
|
| 340 |
+
data.push(cell.stutter_frequency);
|
| 341 |
+
data.push(cell.tissue_inertia);
|
| 342 |
+
data.push(cell.blink_lag);
|
| 343 |
+
data.push(cell.persistent_offset_x);
|
| 344 |
+
data.push(cell.persistent_offset_y);
|
| 345 |
+
data.push(cell.gaze_mismatch);
|
| 346 |
+
}
|
| 347 |
+
data
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
pub fn get_stats(&self) -> String {
|
| 351 |
+
format!("{{ \"frame\": {}, \"active_cells\": {}, \"engine\": \"Rust/Wasm\" }}",
|
| 352 |
+
self.frame_count, self.cells.len())
|
| 353 |
+
}
|
| 354 |
+
}
|