diff --git a/js/simulation.js b/js/simulation.js index 463a5af..abaeffb 100644 --- a/js/simulation.js +++ b/js/simulation.js @@ -1,20 +1,31 @@ -// Clock simulation engine +// Clock simulation engine — uses real timestamps for consistent timing import { state } from './state.js'; import { evaluateAll } from './gates.js'; import { forceRecordSample } from './waveform.js'; -export function simTick() { - // Toggle all CLOCK gates - state.gates.forEach(g => { - if (g.type === 'CLOCK') { - g.value = g.value ? 0 : 1; +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(); } - }); - evaluateAll(); - // Force record even if evaluateAll didn't detect change - if (state.recording && state.waveformVisible) { - forceRecordSample(); } + + animFrameId = requestAnimationFrame(simLoop); } export function startSim() { @@ -29,18 +40,18 @@ export function startSim() { state.waveformVisible = true; document.getElementById('waveform-panel').classList.add('visible'); document.getElementById('sim-btn').classList.add('active'); - // Trigger resize via event so renderer picks it up window.dispatchEvent(new Event('resize')); } - state.simInterval = setInterval(simTick, state.simSpeed); + lastTickTime = performance.now(); + animFrameId = requestAnimationFrame(simLoop); updateSimUI(); } export function stopSim() { state.simRunning = false; - if (state.simInterval) clearInterval(state.simInterval); - state.simInterval = null; + if (animFrameId) cancelAnimationFrame(animFrameId); + animFrameId = null; updateSimUI(); } @@ -57,10 +68,8 @@ export function updateSimUI() { } export function adjustSpeed(delta) { + const wasRunning = state.simRunning; state.simSpeed = Math.max(50, Math.min(2000, state.simSpeed + delta)); - if (state.simRunning) { - clearInterval(state.simInterval); - state.simInterval = setInterval(simTick, state.simSpeed); - } + // No need to restart — the loop reads simSpeed dynamically updateSimUI(); }