import React, { useState, useEffect, useCallback, useRef } from 'react'; import { Zap, Trash2, RefreshCw, Waves, ShieldAlert, Binary, BrainCircuit, Dna } from 'lucide-react'; import GridCell from './components/GridCell'; const GRID_SIZE = 8; const CELL_COUNT = GRID_SIZE * GRID_SIZE; const MAX_HISTORY = 50; // Pre-allocate empty arrays to avoid recreating on every clear const EMPTY_BOOLS = Object.freeze(Array(CELL_COUNT).fill(false)); const EMPTY_WEIGHTS = Object.freeze(Array(CELL_COUNT).fill(0)); const FULL_HISTORY = Object.freeze(Array(MAX_HISTORY).fill(100)); const App = () => { // ── Render state (drives UI) ── const [signalGrid, setSignalGrid] = useState([...EMPTY_BOOLS]); const [memoryGrid, setMemoryGrid] = useState([...EMPTY_BOOLS]); const [weights, setWeights] = useState([...EMPTY_WEIGHTS]); const [accuracy, setAccuracy] = useState(100); const [history, setHistory] = useState([...FULL_HISTORY]); const [glitch, setGlitch] = useState(false); const [isDrifting, setIsDrifting] = useState(false); // Slider state (UI display) const [strength, setStrength] = useState(85); const [frequency, setFrequency] = useState(50); const [persistence, setPersistence] = useState(30); const [crossTalk, setCrossTalk] = useState(20); const [plasticity, setPlasticity] = useState(40); // ── Refs (simulation reads from these — no dependency churn) ── const signalRef = useRef(signalGrid); const memoryRef = useRef(memoryGrid); const weightsRef = useRef(weights); const strengthRef = useRef(strength); const frequencyRef = useRef(frequency); const persistenceRef = useRef(persistence); const crossTalkRef = useRef(crossTalk); const plasticityRef = useRef(plasticity); const isDriftingRef = useRef(isDrifting); const simTimerRef = useRef(null); const driftTimerRef = useRef(null); const waveCanvasRef = useRef(null); const waveFrameRef = useRef(null); const waveFrameCount = useRef(0); const accuracyRef = useRef(100); const containerRef = useRef(null); // Keep refs in sync with state useEffect(() => { signalRef.current = signalGrid; }, [signalGrid]); useEffect(() => { strengthRef.current = strength; }, [strength]); useEffect(() => { frequencyRef.current = frequency; }, [frequency]); useEffect(() => { persistenceRef.current = persistence; }, [persistence]); useEffect(() => { crossTalkRef.current = crossTalk; }, [crossTalk]); useEffect(() => { plasticityRef.current = plasticity; }, [plasticity]); useEffect(() => { isDriftingRef.current = isDrifting; }, [isDrifting]); // ── Actions ── const generatePattern = useCallback(() => { const newGrid = Array.from({ length: CELL_COUNT }, () => Math.random() > 0.8); setSignalGrid(newGrid); signalRef.current = newGrid; setGlitch(true); setTimeout(() => setGlitch(false), 150); }, []); const clearGrids = useCallback(() => { const s = [...EMPTY_BOOLS]; const m = [...EMPTY_BOOLS]; const w = [...EMPTY_WEIGHTS]; setSignalGrid(s); setMemoryGrid(m); setWeights(w); setHistory([...FULL_HISTORY]); signalRef.current = s; memoryRef.current = m; weightsRef.current = w; accuracyRef.current = 100; }, []); const toggleBlock = useCallback((index) => { setSignalGrid(prev => { const next = [...prev]; next[index] = !next[index]; signalRef.current = next; return next; }); }, []); // ── Signal Drift ── useEffect(() => { const runDrift = () => { if (!isDriftingRef.current) return; setSignalGrid(prev => { const next = [...prev]; const last = next.pop(); next.unshift(last); signalRef.current = next; return next; }); }; // Restart drift interval when frequency or drift toggle changes clearInterval(driftTimerRef.current); if (isDrifting) { driftTimerRef.current = setInterval(runDrift, 2000 - frequency * 15); } return () => clearInterval(driftTimerRef.current); }, [isDrifting, frequency]); // ── Main Simulation Loop (runs once on mount) ── useEffect(() => { const tick = () => { const signal = signalRef.current; const prevMemory = memoryRef.current; const prevWeights = weightsRef.current; const str = strengthRef.current; const freq = frequencyRef.current; const pers = persistenceRef.current; const ct = crossTalkRef.current; const plast = plasticityRef.current; const baseSnrThreshold = (100 - str) / 100; // Build next memory const nextGrid = new Array(CELL_COUNT); for (let i = 0; i < CELL_COUNT; i++) { const noise = Math.random(); const shouldUpdate = Math.random() < freq / 100; if (!shouldUpdate) { nextGrid[i] = prevMemory[i]; continue; } const localWeight = prevWeights[i] * (plast / 100); const effectiveThreshold = Math.max(0.01, baseSnrThreshold - localWeight); if (signal[i]) { nextGrid[i] = noise > effectiveThreshold; } else if (prevMemory[i] && Math.random() < pers / 100) { nextGrid[i] = true; } else { nextGrid[i] = noise < effectiveThreshold / 20; } } // Spatial cross-talk let finalGrid = nextGrid; if (ct > 0) { finalGrid = new Array(CELL_COUNT); for (let i = 0; i < CELL_COUNT; i++) { if (nextGrid[i]) { finalGrid[i] = true; continue; } const row = (i / GRID_SIZE) | 0; const col = i % GRID_SIZE; let activeNeighbors = 0; for (let r = -1; r <= 1; r++) { for (let c = -1; c <= 1; c++) { if (r === 0 && c === 0) continue; const nr = row + r, nc = col + c; if (nr >= 0 && nr < GRID_SIZE && nc >= 0 && nc < GRID_SIZE) { if (nextGrid[nr * GRID_SIZE + nc]) activeNeighbors++; } } } finalGrid[i] = Math.random() < activeNeighbors * (ct / 1000); } } // Hebbian weight update const nextWeights = new Array(CELL_COUNT); for (let i = 0; i < CELL_COUNT; i++) { if (signal[i] && finalGrid[i]) { nextWeights[i] = Math.min(1, prevWeights[i] + 0.02); } else if (!signal[i] && !finalGrid[i]) { nextWeights[i] = prevWeights[i]; } else { nextWeights[i] = Math.max(0, prevWeights[i] - 0.01); } } // Accuracy let matches = 0; for (let i = 0; i < CELL_COUNT; i++) { if (signal[i] === finalGrid[i]) matches++; } const acc = Math.round((matches / CELL_COUNT) * 100); // Commit to refs memoryRef.current = finalGrid; weightsRef.current = nextWeights; accuracyRef.current = acc; // Batch state updates setMemoryGrid(finalGrid); setWeights(nextWeights); setAccuracy(acc); setHistory(prev => { const next = [...prev]; next.shift(); next.push(acc); return next; }); }; // Adaptive tick rate — re-read frequency ref each cycle const scheduleNext = () => { const intervalTime = Math.max(16, 1000 - frequencyRef.current * 9.8); simTimerRef.current = setTimeout(() => { tick(); scheduleNext(); }, intervalTime); }; scheduleNext(); return () => clearTimeout(simTimerRef.current); }, []); // mount-only — reads everything from refs // ── Waveform Canvas (ResizeObserver + proper rAF cleanup) ── useEffect(() => { const canvas = waveCanvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); const container = canvas.parentElement; // Sync canvas resolution to container const resizeCanvas = () => { const rect = container.getBoundingClientRect(); const dpr = window.devicePixelRatio || 1; canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; ctx.scale(dpr, dpr); canvas.style.width = rect.width + 'px'; canvas.style.height = rect.height + 'px'; }; const ro = new ResizeObserver(resizeCanvas); ro.observe(container); resizeCanvas(); const animate = () => { waveFrameCount.current++; const frame = waveFrameCount.current; const w = canvas.width / (window.devicePixelRatio || 1); const h = canvas.height / (window.devicePixelRatio || 1); ctx.clearRect(0, 0, w, h); ctx.beginPath(); ctx.strokeStyle = accuracyRef.current < 50 ? '#ff0055' : '#00ff9f'; ctx.lineWidth = 2; const freq = frequencyRef.current; const str = strengthRef.current; const freqMod = freq / 20; const ampMod = str / 4; const noiseMod = (100 - str) / 5; for (let x = 0; x < w; x++) { const y = h / 2 + Math.sin(x * 0.05 * freqMod + frame * 0.1) * ampMod + Math.sin(x * 0.1 + frame * 0.2) * noiseMod; if (x === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.stroke(); waveFrameRef.current = requestAnimationFrame(animate); }; waveFrameRef.current = requestAnimationFrame(animate); return () => { cancelAnimationFrame(waveFrameRef.current); ro.disconnect(); }; }, []); // ── Slider helpers ── const sliderHandler = useCallback((setter, ref) => (e) => { const v = parseInt(e.target.value); setter(v); ref.current = v; }, []); return (