synapse-link / src /App.jsx
dixiebone13-a11y
Synapse Link HEBB-9 - Hebbian learning neural sim
4ad4378
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 (
<div className={`min-h-screen p-4 md:p-6 font-mono selection:bg-[#00ff9f] selection:text-black transition-colors duration-500 ${accuracy < 25 ? 'bg-[#200] text-[#ff0055]' : 'bg-[#020202] text-[#00ff9f]'}`}>
{/* Scanline overlay */}
<div className="fixed inset-0 pointer-events-none z-50 opacity-[0.05] bg-[linear-gradient(rgba(18,16,16,0)_50%,rgba(0,0,0,0.25)_50%),linear-gradient(90deg,rgba(255,0,0,0.06),rgba(0,255,0,0.02),rgba(0,0,255,0.06))] bg-[length:100%_2px,3px_100%]" />
<div className="max-w-7xl mx-auto space-y-6">
{/* Header */}
<header className="flex flex-col md:flex-row items-start md:items-center justify-between p-6 bg-[#0a0a0a] border-2 border-[#00ff9f]/20 rounded-sm relative overflow-hidden">
<div className="z-10 flex items-center gap-5">
<div className={`p-3 rounded-none shadow-lg transition-colors ${accuracy < 25 ? 'bg-[#ff0055] shadow-[#ff0055]' : 'bg-[#00ff9f] shadow-[#00ff9f]'}`}>
<BrainCircuit size={32} className="text-black" />
</div>
<div>
<h1 className={`text-4xl font-black uppercase tracking-tighter ${glitch || accuracy < 25 ? 'animate-pulse' : ''}`}>
Synapse_Link <span className="text-white text-2xl font-light opacity-50">HEBB-9</span>
</h1>
<div className="flex items-center gap-3 text-[10px] mt-1 font-bold opacity-60 uppercase">
<span className="flex items-center gap-1"><Dna size={12} /> PLASTICITY: ACTIVE</span>
<span className={`flex items-center gap-1 ${accuracy < 40 ? 'text-[#ff0055] animate-bounce' : ''}`}>
<ShieldAlert size={12} /> {accuracy < 25 ? 'CRITICAL DECOHERENCE' : 'SIGNAL STABLE'}
</span>
</div>
</div>
</div>
<div className="mt-4 md:mt-0 flex flex-wrap gap-3">
<button
onClick={() => setIsDrifting(d => !d)}
className={`px-6 py-3 font-black uppercase tracking-widest text-xs transition-all flex items-center gap-2 ${isDrifting ? 'bg-white text-black' : 'border border-[#00ff9f] text-[#00ff9f]'}`}
>
<Waves size={16} /> {isDrifting ? 'Stop Drift' : 'Enable Drift'}
</button>
<button onClick={generatePattern} className="bg-[#00ff9f] text-black px-6 py-3 font-black uppercase tracking-widest text-xs hover:bg-white transition-all shadow-md flex items-center gap-2">
<RefreshCw size={16} /> Sync Source
</button>
<button onClick={clearGrids} className="border border-[#ff0055] text-[#ff0055] px-6 py-3 font-black uppercase tracking-widest text-xs hover:bg-[#ff0055] hover:text-black transition-all flex items-center gap-2">
<Trash2 size={16} /> Purge
</button>
</div>
</header>
{/* Waveform + Fidelity */}
<div className="grid lg:grid-cols-4 gap-6">
<div className="lg:col-span-3 bg-[#0a0a0a] border border-[#00ff9f]/20 p-4 relative h-32" ref={containerRef}>
<div className="absolute top-2 left-2 text-[8px] opacity-40 uppercase font-black">Bio_Feedback_Wave</div>
<canvas ref={waveCanvasRef} className="w-full h-full" />
</div>
<div className={`bg-[#0a0a0a] border border-[#00ff9f]/20 p-4 flex flex-col justify-center items-center ${accuracy < 25 ? 'border-[#ff0055]' : ''}`}>
<div className="text-[10px] uppercase opacity-40 font-black mb-1">Engram Fidelity</div>
<div className={`text-4xl font-black italic tracking-tighter ${accuracy < 60 ? 'text-[#ff0055]' : 'text-white'}`}>
{accuracy}.0
</div>
<div className="w-full bg-[#111] h-1 mt-2">
<div className="bg-[#00ff9f] h-full transition-all" style={{ width: `${accuracy}%` }} />
</div>
</div>
</div>
{/* Grids */}
<div className="grid lg:grid-cols-2 gap-8">
{/* Signal Input */}
<div className="bg-[#0a0a0a] border-l-4 border-[#00ff9f] p-6 relative">
<div className="flex items-center justify-between mb-6">
<h3 className="font-black uppercase tracking-widest text-white flex items-center gap-2">
<Zap size={18} className="text-[#00ff9f]" /> Stimulus Source
</h3>
<div className="text-[10px] opacity-40 font-bold">MANUAL_OVERRIDE_ENABLED</div>
</div>
<div className="grid grid-cols-8 gap-2 aspect-square bg-black/40 p-3 border border-[#00ff9f]/10" role="grid" aria-label="Stimulus grid">
{signalGrid.map((active, i) => (
<GridCell key={i} active={active} variant="signal" weight={0} mismatch={false} onClick={() => toggleBlock(i)} />
))}
</div>
</div>
{/* Memory Output */}
<div className="bg-[#0a0a0a] border-r-4 border-[#ff0055] p-6 relative">
<div className="flex items-center justify-between mb-6">
<h3 className="font-black uppercase tracking-widest text-white flex items-center gap-2">
<BrainCircuit size={18} className="text-[#ff0055]" /> Synaptic Cache
</h3>
<div className="flex items-center gap-2 text-[8px] font-bold">
<div className="w-2 h-2 bg-white rounded-full animate-ping" />
LEARNING_ACTIVE
</div>
</div>
<div className="grid grid-cols-8 gap-2 aspect-square bg-black/40 p-3 border border-[#ff0055]/10" role="grid" aria-label="Synaptic memory grid">
{memoryGrid.map((active, i) => (
<GridCell key={i} active={active} variant="memory" weight={weights[i]} mismatch={signalGrid[i] !== active} />
))}
</div>
</div>
</div>
{/* Sliders */}
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-4">
<div className="bg-[#0a0a0a] p-4 border border-[#00ff9f]/20">
<label className="flex justify-between text-[10px] font-black uppercase mb-3">
<span>SNR_Amp</span><span>{strength}%</span>
</label>
<input type="range" min="0" max="100" value={strength} onChange={sliderHandler(setStrength, strengthRef)} className="w-full accent-[#00ff9f]" />
</div>
<div className="bg-[#0a0a0a] p-4 border border-[#00ff9f]/20">
<label className="flex justify-between text-[10px] font-black uppercase mb-3">
<span>Refresh_Hz</span><span>{frequency}%</span>
</label>
<input type="range" min="1" max="100" value={frequency} onChange={sliderHandler(setFrequency, frequencyRef)} className="w-full accent-[#00ff9f]" />
</div>
<div className="bg-[#0a0a0a] p-4 border border-[#ff0055]/20">
<label className="flex justify-between text-[10px] font-black uppercase mb-3 text-[#ff0055]">
<span>Persistence</span><span>{persistence}%</span>
</label>
<input type="range" min="0" max="100" value={persistence} onChange={sliderHandler(setPersistence, persistenceRef)} className="w-full accent-[#ff0055]" />
</div>
<div className="bg-[#0a0a0a] p-4 border border-[#ff0055]/20">
<label className="flex justify-between text-[10px] font-black uppercase mb-3 text-[#ff0055]">
<span>Cross_Talk</span><span>{crossTalk}%</span>
</label>
<input type="range" min="0" max="100" value={crossTalk} onChange={sliderHandler(setCrossTalk, crossTalkRef)} className="w-full accent-[#ff0055]" />
</div>
<div className="bg-[#111] p-4 border border-white/20">
<label className="flex justify-between text-[10px] font-black uppercase mb-3 text-white">
<span>Plasticity</span><span>{plasticity}%</span>
</label>
<input type="range" min="0" max="100" value={plasticity} onChange={sliderHandler(setPlasticity, plasticityRef)} className="w-full accent-white" />
</div>
</div>
{/* Telemetry Graph */}
<div className="bg-[#0a0a0a] border border-[#00ff9f]/10 p-4 h-20 flex items-end gap-1 relative overflow-hidden" aria-label="Fidelity history">
<div className="absolute inset-0 bg-gradient-to-t from-[#ff0055]/10 to-transparent pointer-events-none" />
{history.map((val, i) => (
<div key={i} className={`flex-1 transition-all ${val < 50 ? 'bg-[#ff0055]' : 'bg-[#00ff9f]/40'}`} style={{ height: `${val}%` }} />
))}
</div>
{/* Footer */}
<footer className="p-4 bg-[#0a0a0a] border border-white/5 text-[9px] uppercase tracking-tighter opacity-60 grid grid-cols-2 gap-4">
<div className="flex gap-2"><Binary size={12} /> DATA_PAGING: STOCHASTIC_GRADIENT_ENABLED</div>
<div className="text-right">SYNAPTIC_WEIGHTS_NORMALIZED // HEBBIAN_COEFFICIENT: {(plasticity / 100).toFixed(2)}</div>
</footer>
</div>
</div>
);
};
export default App;