From 6bd1f453620dc9c37df97e84d7ba92e920501403 Mon Sep 17 00:00:00 2001 From: Georg Tomitsch Date: Thu, 18 Jun 2026 16:40:31 +0200 Subject: [PATCH] Reset per-game reveal state on new game so reveal animates over cached content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Starting a new game reuses block ids (1,2,3...). The reveal clock's per-block start times (activeRevealBlockStarts in the lab) and the renderer's animation/ revealed sets are keyed by block id and were never cleared on a client reset, so a new game over already-cached content inherited the previous run's start times. beginPageReveal then computed a huge elapsed and the shader treated the reveal as already complete — showing everything at once instead of animating. resetClientPlaybackAndDisplay (run on new game and restore) now emits story:client-reset; the lab clears activeRevealBlockStarts/pending reveal state, the texture renderer clears active animations and revealed-block ids, and the timeline invalidates prepared segments. So each game starts with a clean reveal clock. Co-Authored-By: Claude Opus 4.8 --- public/js/book-playback-timeline-module.js | 4 ++++ public/js/book-texture-renderer-module.js | 2 ++ public/js/game-loop-module.js | 9 ++++++++- public/js/webgl-book-lab.js | 9 +++++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/public/js/book-playback-timeline-module.js b/public/js/book-playback-timeline-module.js index 889ecb5..edf2985 100644 --- a/public/js/book-playback-timeline-module.js +++ b/public/js/book-playback-timeline-module.js @@ -99,6 +99,10 @@ class BookPlaybackTimelineModule extends BaseModule { }); this.addEventListener(document, 'webgl-book:page-count-changed', this.invalidatePreparedSegments); this.addEventListener(document, 'story:history-restoring', this.invalidatePreparedSegments); + this.addEventListener(document, 'story:client-reset', () => { + this.invalidatePreparedSegments(); + this.activeSegment = null; + }); window.BookPlaybackTimeline = this; this.reportProgress(100, 'Book playback timeline ready'); return true; diff --git a/public/js/book-texture-renderer-module.js b/public/js/book-texture-renderer-module.js index 0535700..44b2a90 100644 --- a/public/js/book-texture-renderer-module.js +++ b/public/js/book-texture-renderer-module.js @@ -118,6 +118,7 @@ class BookTextureRendererModule extends BaseModule { }); this.addEventListener(document, 'story:manual-scroll', this.fastForwardAnimations); this.addEventListener(document, 'story:history-restoring', this.stopAnimations); + this.addEventListener(document, 'story:client-reset', this.stopAnimations); this.currentSpread = this.pagination?.getCurrentSpread?.() || { index: 0, left: [], right: [], pageMeta: { left: null, right: null } }; this.drawSpread(this.currentSpread); this.reportProgress(100, 'Book texture renderer ready'); @@ -1028,6 +1029,7 @@ class BookTextureRendererModule extends BaseModule { stopAnimations() { this.activeAnimations.clear(); + this.revealedBlockIds.clear(); this.drawSpread(this.currentSpread || this.pagination?.getCurrentSpread?.()); } diff --git a/public/js/game-loop-module.js b/public/js/game-loop-module.js index ed954c6..f2dfac9 100644 --- a/public/js/game-loop-module.js +++ b/public/js/game-loop-module.js @@ -657,8 +657,15 @@ class GameLoopModule extends BaseModule { if (inputHandler && typeof inputHandler.clearHistory === 'function') { inputHandler.clearHistory(); } + + // Signal a client reset so transient, block-id-keyed reveal/animation state is + // cleared. Without this, a new game that reuses block ids over already-cached + // content keeps the previous run's reveal start times and skips the animation. + document.dispatchEvent(new CustomEvent('story:client-reset', { + detail: { reason: 'client-reset' } + })); } - + } // Create the singleton instance diff --git a/public/js/webgl-book-lab.js b/public/js/webgl-book-lab.js index efbc676..ae2b53f 100644 --- a/public/js/webgl-book-lab.js +++ b/public/js/webgl-book-lab.js @@ -695,6 +695,15 @@ document.addEventListener('webgl-book:request-page-flip', (event) => { document.addEventListener('webgl-book:page-cache-problem', (event) => { pageTextureStore?.recordProblem?.(event.detail || {}); }); +// New game / history restore: drop block-id-keyed reveal state so a reused block id does +// not inherit the previous run's reveal start time (which would skip the animation). +document.addEventListener('story:client-reset', () => { + activeRevealBlockStarts.clear(); + pendingRevealStartBlockIds.clear(); + pageRevealFreezeAt = null; + clearPageReveal('left', 'client-reset'); + clearPageReveal('right', 'client-reset'); +}); // Pagination spread updates only carry state. The playback owner decides when the // visible spread changes (via flips). The scene jumps directly only for non-playback // commits such as history restore. See docs/webgl-3d-ui-spec.md "Single ownership".