// Clock simulation engine — uses real timestamps for consistent timing import { state } from './state.js'; import { evaluateAll } from './gates.js'; import { forceRecordSample } from './waveform.js'; let lastTickTime = 0; let animFrameId = null; function simLoop(now) { if (!state.simRunning) return; // Fire ticks that are due based on real elapsed time while (now - lastTickTime >= state.simSpeed) { lastTickTime += state.simSpeed; // Toggle all CLOCK gates state.gates.forEach(g => { if (g.type === 'CLOCK') { g.value = g.value ? 0 : 1; } }); evaluateAll(); if (state.recording && state.waveformVisible) { forceRecordSample(); } } animFrameId = requestAnimationFrame(simLoop); } export function startSim() { if (state.simRunning) return; const hasClocks = state.gates.some(g => g.type === 'CLOCK'); if (!hasClocks) return; state.simRunning = true; // Auto-open waveform panel if (!state.waveformVisible) { state.waveformVisible = true; document.getElementById('waveform-panel').classList.add('visible'); document.getElementById('sim-btn').classList.add('active'); window.dispatchEvent(new Event('resize')); } lastTickTime = performance.now(); animFrameId = requestAnimationFrame(simLoop); updateSimUI(); } export function stopSim() { state.simRunning = false; if (animFrameId) cancelAnimationFrame(animFrameId); animFrameId = null; updateSimUI(); } export function updateSimUI() { const btn = document.getElementById('sim-run-btn'); if (state.simRunning) { btn.textContent = '⏹ Stop'; btn.classList.add('active'); } else { btn.textContent = '▶ Run'; btn.classList.remove('active'); } document.getElementById('sim-speed-label').textContent = `${state.simSpeed}ms`; } export function adjustSpeed(delta) { const wasRunning = state.simRunning; state.simSpeed = Math.max(50, Math.min(2000, state.simSpeed + delta)); // No need to restart — the loop reads simSpeed dynamically updateSimUI(); }