Profiling the per-paragraph playback stutter showed the JS heap sawtoothing (37<->71MB) with
0.4-2.2s long tasks once per block — GC pauses from large (24-48MB) per-block canvas/ImageBitmap
allocations, not pagination (buildPages was ~29ms). These pauses freeze the flip/reveal
animation, which is also why the title flip looked un-animated.
- The reveal "base" layer is the plain paper background, identical for every page of a side.
The worker now sends its bitmap once per side+size; the renderer caches the canvas and reuses
it for all reveals, removing a large per-block bitmap+canvas allocation.
- WEBGL_BOOK_PREFETCH_LOOKAHEAD 2 -> 1 so only the next block's page render is prepared, instead
of letting multiple large rasterizations overlap.
Verified live: per-paragraph long tasks roughly halved (10 -> 5 over the same window) and worst
case 2159ms -> 1431ms. Residual ~1.4s stall remains from the per-block page bitmap + prepared-
page snapshot clone + texture upload; further reduction needs reworking those to reuse buffers.
Suite 181.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Builds on the worker migration with prepare-burst pacing and a title-flip fix:
- New game from mid-game left the book on the previous game's spread, so the first block's
source and target spread matched and the title->content page turn was skipped. story:client-reset
now returns the book to the title spread (spread 0) so the first block flips 0->1 and animates.
Verified: requiresSpreadTransition src=0 tgt=1, page-flip-started/near-end fire.
- The lookahead burst-prepared many blocks at once, spiking allocation/GC into multi-second
main-thread stalls. WebGL book prepares are now serialized through a chain and capped to a
small lookahead window (TTS audio prefetch still spans the full window); future lookahead is
also deferred until the current sentence has entered the display pipeline, keeping it off the
first flip/reveal critical path. Worst game-start stall ~6s -> ~3.4s.
- Page flips now drive the scene through the sceneControl prewarm/startPreparedPageFlip API
(awaited) instead of an event, and the scene awaits the async initial spread draw.
Suite 177. Remaining: a per-block prepare stall (~1.6-3.4s for large blocks at game start)
that profiling has not yet attributed to a single function (likely GC from prepare-path
allocation) — needs a DevTools performance capture for exact attribution.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>