For a paragraph that overflows onto the next spread, the continuation page was redrawn
synchronously after the flip (drawSpread on the main thread), so the next page stayed
blank for ~2.7s and then the carried-over lines popped in already ~24% revealed instead
of animating from the start.
Move that work off the critical path: during lookahead, prepare and cache the
continuation spread's reveal plan using the not-yet-committed preview spreads (so per-line
timing is computed across both spreads), then reuse it after the flip instead of redrawing.
- pagination: expose the preview spread layout on the returned preview spread so the owner
can detect the continuation spread (race-free; each call owns its preparedSpreads).
- renderer: revealSpreadSourceOverride lets region collection use preview spreads during
lookahead; prepareContinuationRevealPlan draws+caches the continuation plan (publishEvent
off); takeContinuationRevealPlan reuses it, re-stamped as an activate-phase publish.
- timeline: prepare the continuation plan during background (non-immediate) prepares;
revealContinuationSpread reuses it, falling back to the redraw when none was prepared.
Verified live on a spanning block: continuation now appears ~0.25s after the flip (was
~2.7s) at ve~3471 = the right line's duration, i.e. it animates from the start (no pop-in),
runs to ~full over the TTS, no fast-forward, no continuation-missing problems.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The previous commit changed per-line reveal-duration distribution from ink-area to
word-count. That dropped a deliberate precision decision (area gives sub-line
granularity) and, verified live on a spanning paragraph, it was what made the
continuation page fail to animate. Restore area-weighting for the per-line split.
The word-share scaling of the *total* duration for partial (spanning) blocks and the
timeline-module timing snapshot/restore are kept — they only preserve existing
word-timings, they do not change the area-based per-line distribution.
Verified: on a real spanning block the right line reveals over its area share (~3.3s),
the page flips, and the continuation animates progressively across the next spread
over the full TTS (no fast-forward, no reveal-all-at-once).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A paragraph that overflows the right page onto the next spread revealed its single
right-page line over the entire TTS, then timed out (timeline-reveal-commit-timeout)
and only flipped after the whole narration. Two root causes:
- At activate the reused lookahead segment played a sentence instance whose animation
word-timings were lost (wordTimings=[], totalDuration=0), so reveal timing fell back
to an area estimate spanning the full TTS. Snapshot the timings at prepare and restore
them at activate.
- Reveal duration was distributed by ink area, but just-paginated continuation lines
have ~0 area, so the one right-page line received the whole duration. Distribute by
word count (reliable) with area as fallback.
Now the right page reveals only its word share (~2.7s for a 6/55-word line), commits,
and flips while TTS continues; the continuation animates on the next spread. Also
rewrote the right-reveal wait to a single timer + commit/fast-forward listeners with
cleanup, removing the stray timeline-reveal-commit-timeout.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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 <noreply@anthropic.com>
- Navigation now operates on spread indices, not the non-contiguous page-position
scheme that mapped a forward step onto the same spread (so forward stalled and
triggered a no-op multi-flip). Forward/back move one spread; start/end and the
slider use spread indices. The page readout shows the odd page of the visible
pair (2*spread+1) or 0 at the title spread.
- Flipping forward could show the source page with its last word still masked: a
stale reveal mask left on the flip surface by a previous playback flip was not
cleared when the (finished) source page had no active reveal. Reset the flip
surface reveal shader in that case so the full page shows during the turn.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Reveal timing is now word-proportional per page: when a block's reveal only
covers part of the block (the continuation spread is not paginated at reveal
time), the page reveals only its share of the TTS, offset by the words before
it. The right page no longer absorbs the whole TTS before flipping; it flips at
normal pace and the continuation resumes on the next spread while TTS plays. No
effect when the regions already cover the whole block (unified plan / one page).
- Page flip start now shows the target spread's same-side page beneath the lifting
page (revealed as it turns away) instead of a blank that pops in after the flip.
Deferred (pending-reveal) sides stay blank so the masked reveal still lands via
activate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Establish book-playback-timeline as the sole playback owner driving the
scene through formal webgl-book:* events (not the BookLabDebug surface),
with a single reveal clock in the scene render loop and webgl-page-cache as
the only texture cache. Remove the legacy dual playback path and the
ownsPageFlipCommit gating.
Fixes:
- Flip page detached/folded at the spine: restore the raw page-cap line for
flip geometry (matches the prototype/pre-regression), removing
normalizeFlipLineToVisiblePage which moved the pivot off the spine arc.
- Flip textures: distance-based UVs (no horizontal compression),
direction-aware face material (source on the camera-facing side), source
meta derived from the visible spread (manual flips), prewarm shape fix.
- Reveal: flash removed on the static page and the flip back surface;
spanning blocks rebuild the reveal plan at activate and continue the
reveal on the next spread after the fill flip.
- Cache staleness is contentVersion-primary; nav clamps to spreadCount.
Docs updated to describe the intended single-owner architecture. Regression
checks updated to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>