diff --git a/.gitignore b/.gitignore index aa58bd0..307ee05 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ ~$Plan.docx -D:\ARAS\refactor\zoomsketch-issue \ No newline at end of file +D:\ARAS\refactor\zoomsketch-issue +steps/Console_logs/Log_After_Updateframe_moved.log +steps/Console_logs/127.0.0.1-1763094792904.log +steps/Console_logs/ diff --git a/steps/src/main.js b/steps/src/main.js index bc46e58..ff1a792 100644 --- a/steps/src/main.js +++ b/steps/src/main.js @@ -346,6 +346,11 @@ function finalizeSetup(_parsedJsonData) { appState.speedGraphInstance.setData(appState.vizData, videoPlayer.duration); appState.speedGraphInstance.redraw(); } + // --- START: FIX for Initial Overlay Visibility --- + // Manually update overlays on initial load so they are visible before playback starts. + updatePersistentOverlays(videoPlayer.currentTime); + updateDebugOverlay(videoPlayer.currentTime); + // --- END: FIX for Initial Overlay Visibility --- // Update SNR inputs now that data is loaded if (appState.vizData) { diff --git a/steps/src/p5/radarSketch.js b/steps/src/p5/radarSketch.js index 050346d..e33605c 100644 --- a/steps/src/p5/radarSketch.js +++ b/steps/src/p5/radarSketch.js @@ -130,7 +130,9 @@ export const radarSketch = function (p) { }; p.draw = function () { - if (debugFlags.drawing) console.log("draw_DEBUG: radarSketch.draw() called."); + if (debugFlags.drawing) { + console.log(`[${performance.now().toFixed(3)}] draw_DEBUG: radarSketch.draw() called.`); + } // --- START: FPS Calculation & Display --- const currentTime = p.millis(); diff --git a/steps/src/sync.js b/steps/src/sync.js index 6213567..d80589c 100644 --- a/steps/src/sync.js +++ b/steps/src/sync.js @@ -137,6 +137,12 @@ export function updateFrame(frame, forceVideoSeek = false) { // --- END: Centralized Explorer Update --- const endTime = performance.now(); appState.lastFrameRenderTime = endTime - startTime; // <-- End timer and update state + + // --- START: FIX for Overlay Visibility During Scrubbing --- + // Update overlays here to ensure they refresh when scrubbing while paused. + updatePersistentOverlays(videoPlayer.currentTime); + updateDebugOverlay(videoPlayer.currentTime); + // --- END: FIX for Overlay Visibility During Scrubbing --- } // --- [END] MOVED FROM DOM.JS --- @@ -154,7 +160,9 @@ export function stopPlayback() { * Its ONLY job is to update appState.currentFrame. It does NO drawing. */ export function videoFrameCallback(now, metadata) { - if (debugFlags.sync) console.log("vfc_DEBUG: videoFrameCallback running."); + if (debugFlags.sync) { + console.log(`[${performance.now().toFixed(3)}] vfc_DEBUG: videoFrameCallback running.`); + } if (!appState.isPlaying || videoPlayer.paused || !appState.vizData) { return; @@ -170,7 +178,7 @@ export function videoFrameCallback(now, metadata) { // 3. Update the application state. This is the ONLY state this function changes. if (frameIndex !== appState.currentFrame) { appState.currentFrame = frameIndex; - updateFrame(appState.currentFrame); // <-- MOVE UI UPDATE CALL HERE + // This is the ONLY state this function should change. All UI updates are in animationLoop. } // Re-register the callback for the next frame to create a loop @@ -182,12 +190,12 @@ export function videoFrameCallback(now, metadata) { * Its ONLY job is to draw the current state. It does NO data calculation. */ export function animationLoop() { - if (debugFlags.sync) console.log("anim_DEBUG: animationLoop running."); + if (debugFlags.sync) { + console.log(`[${performance.now().toFixed(3)}] anim_DEBUG: animationLoop running.`); + } - // Update debug overlay information - updatePersistentOverlays(videoPlayer.currentTime); - // updatePersistentOverlays(); // This is a duplicate call and can be removed - updateDebugOverlay(videoPlayer.currentTime); + // The render loop is responsible for ALL UI updates, ensuring perfect sync. + updateFrame(appState.currentFrame); // --- START: Centralized Redraw Logic --- // Explicitly redraw all active sketches in sync with the animation frame. @@ -201,22 +209,86 @@ export function animationLoop() { } } +let timelineDebounceTimer; export function handleTimelineInput(event) { if (!appState.vizData) return; + // 1. If playing, pause playback to allow scrubbing. if (appState.isPlaying) { pausePlayback(); appState.isPlaying = false; playPauseBtn.textContent = "Play"; } + // 2. Get the target frame from the slider. const frame = parseInt(event.target.value, 10); - updateFrame(frame, true); // Seek the video to the new frame - // Manually trigger redraws since the animation loop is not running + + // 3. Update UI immediately for responsiveness, but WITHOUT forcing a video seek. + updateFrame(frame, false); if (appState.p5_instance) appState.p5_instance.redraw(); if (appState.speedGraphInstance) appState.speedGraphInstance.redraw(); + + // 4. Use a debouncer to perform the expensive video seek after the user stops dragging. + clearTimeout(timelineDebounceTimer); + timelineDebounceTimer = setTimeout(() => { + updateFrame(appState.currentFrame, true); // Perform final, precise video seek. + }, 300); // 300ms delay after last input event. +} + +let lastScrollTime = 0; +let scrollSpeed = 0; +let seekDebounceTimer; + +function handleTimelineWheel(event) { + if (!appState.vizData) return; + event.preventDefault(); // Prevent default page scroll + + // 1. Pause playback if the user starts scrubbing. + if (appState.isPlaying) { + pausePlayback(); + appState.isPlaying = false; + playPauseBtn.textContent = "Play"; + } + + // 2. Calculate scroll speed to create a dynamic seek amount. + const now = performance.now(); + const timeDelta = now - (lastScrollTime || now); + lastScrollTime = now; + scrollSpeed = timeDelta > 0 ? 1000 / timeDelta : scrollSpeed; + + // 3. Map scroll speed to an acceleration curve. + // The sensitivity value (e.g., 4) can be adjusted for more/less acceleration. + const speedMultiplier = 1 + Math.floor(scrollSpeed / 4); + const seekAmount = Math.max(1, speedMultiplier); // Ensure we always move at least 1 frame. + + // 4. Calculate the new frame index. + const direction = Math.sign(event.deltaY); + // FIX: Invert the direction. Scrolling down (positive deltaY) should advance the frame. + let newFrame = appState.currentFrame + direction * seekAmount; + + // 5. Clamp the new frame to the valid range. + const totalFrames = appState.vizData.radarFrames.length - 1; + newFrame = Math.max(0, Math.min(newFrame, totalFrames)); + + // 6. Update the UI immediately for responsive feedback, but WITHOUT forcing a video seek. + // This makes the slider feel fast without causing video stutter. + updateFrame(newFrame, false); + // --- START: Immediate Redraw for Responsiveness --- + // Manually trigger redraws here so the radar visualization updates as the user scrolls. + if (appState.p5_instance) appState.p5_instance.redraw(); + if (appState.speedGraphInstance) appState.speedGraphInstance.redraw(); + // --- END: Immediate Redraw for Responsiveness --- + + // 7. Use a debouncer for the expensive video seek. This will only run once + // after the user has finished scrolling, ensuring a final, precise sync. + clearTimeout(seekDebounceTimer); + seekDebounceTimer = setTimeout(() => { + // Perform the final, expensive video seek. + updateFrame(appState.currentFrame, true); + }, 300); // 300ms delay after the last scroll event. } export function initSyncUIHandlers() { timelineSlider.addEventListener("input", handleTimelineInput); + timelineSlider.addEventListener("wheel", handleTimelineWheel, { passive: false }); }