Rework WebGL book playback to single ownership and fix flip/reveal pipeline

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>
This commit is contained in:
2026-06-17 15:29:50 +02:00
parent c19ebe3089
commit 8bb18fa201
9 changed files with 439 additions and 438 deletions
+72 -22
View File
@@ -52,40 +52,90 @@ This section records the current state after the procedural book integration wor
- The custom book material shader includes a small local indirect/bounce-light term for book surfaces. This is not emissive; it is multiplied by material albedo so paper, leather, and head/end-band colors remain distinct while shadowed side faces stay readable.
- Temporary screenshots and generated debug images are not product assets unless explicitly promoted.
## Current Page-Flow Architecture
## Page-Flow Architecture
The 3D book pipeline is module-owned. No page content should be generated by ad hoc scene code.
The 3D book is the primary display. A secondary 2D DOM view may be driven from the same content,
but it is never the source of truth and must not own playback decisions. No page content is
generated by ad hoc scene code.
- `ui-display-handler` owns the live 3D prepare -> optional page flip -> activate sequence for story text.
- `sentence-queue` may prepare future page presentations during lookahead using the same pagination and texture renderer modules.
- `book-pagination` owns page/spread construction, page metadata, widows/orphans/hyphenation decisions, image placement decisions, and explicit blank/title/body page records.
- `book-texture-renderer` owns drawing final page canvases, reveal-region coordinates, reveal timing metadata, and publishing explicit `webgl-book:page-texture-records`.
- `webgl-page-cache` owns persistent page canvases, memory canvases, prepared reveal plans, prepared GPU textures, resident VRAM textures, blank page texture, and visible texture bindings.
- `webgl-book-lab` owns the Three.js scene, materials, geometry, pointer projection, page flip meshes, and consuming page texture records. It must not become a second page cache.
### Single ownership
Problem states must be surfaced instead of hidden:
There is exactly one playback owner: `book-playback-timeline`. It owns the complete content
lifecycle for story text and is the only module allowed to sequence it:
```
prepare (pagination + textures + prewarm)
-> activate (make the target spread the visible spread)
-> reveal (animate the new block's text in)
-> flip (turn the page when a spread boundary is crossed)
```
- `ui-display-handler` and `sentence-queue` are clients of the owner. They call
`book-playback-timeline.playSentence` (live) and `prepareSentence` (lookahead). They contain no
flip, reveal, or spread-transition logic of their own.
- `book-pagination` owns page/spread construction, page metadata, widows/orphans/hyphenation,
image placement, and explicit blank/title/body page records. It does not decide playback timing.
- `book-texture-renderer` owns drawing final page canvases and computing reveal-region coordinates
and per-region timing metadata. It is a pure renderer: it does **not** run a playback clock, does
**not** decide when reveals start/finish, and does **not** trigger flips. Reactive redraws keyed
off pagination events are forbidden; the owner asks it to draw.
- `webgl-page-cache` is the single texture/canvas cache: persistent canvases, memory canvases,
prepared reveal plans, prepared GPU textures, resident VRAM textures, the blank texture, and
visible texture bindings. No other module may keep a parallel cache.
- `webgl-book-scene` (implemented in `webgl-book-lab.js`) owns the Three.js scene, materials,
geometry, pointer projection, page-flip meshes, the single reveal clock, and consuming page
texture records. It must not become a second page cache and must not decide playback order.
### Single reveal clock
Reveal timing has exactly one authority: the scene render loop. It advances reveal progress,
freezes it during a physical flip, and emits `webgl-book:reveal-committed` when a side's reveal
finishes. No module runs a second `setTimeout`/`requestAnimationFrame` reveal clock in parallel.
The texture renderer supplies region timings; it does not measure their elapsed time.
### Owner-to-scene command channel
The owner drives the scene through the registered scene command interface obtained from the module
registry (`webgl-book-scene`), or through the formal `webgl-book:*` events listed below. The owner
must never reach into `window.BookLabDebug` — that object is a debug/inspection surface only and is
not part of any production control path. Production code must not throw because a debug global is
missing.
### Problem states are surfaced, never hidden
- A missing persistent page canvas during prewarm is a `db-cache-miss`.
- A missing source or required back texture before a page flip is a `flip-source-texture-missing` or `flip-back-texture-missing`.
- These problem states must appear in `webglPageCacheProblems` and must not be silently fixed by borrowing unrelated visible stack textures.
- A missing source or required back texture before a page flip is a `flip-source-texture-missing`
or `flip-back-texture-missing`.
- These appear in `webglPageCacheProblems` and must not be silently fixed by borrowing unrelated
visible stack textures. Surfacing a problem must not abort live reading: a transient miss degrades
(blank/last-good texture) and is logged, rather than throwing out of the playback path.
## Event Surface
The preferred 3D book content path is direct module calls through `ui-display-handler` for live playback and `sentence-queue` for lookahead preparation.
The owner controls the scene through the scene command interface (preferred for direct,
ordered calls) and through these formal events. Each event has one producer and stable meaning:
The following events remain formal integration events and must keep their meaning stable while they exist:
- `webgl-book:page-texture-records` (owner/renderer -> scene): publishes explicit page texture
records for a spread, carrying `phase` (`prepare`|`activate`) and per-side `visibility`.
- `webgl-book:page-reveal-start` (owner -> scene): starts the scene reveal clock for a block.
- `webgl-book:page-reveal-fast-forward` (owner -> scene): accelerates reveal timing without
replacing the page pipeline.
- `webgl-book:reveal-committed` (scene -> owner): a page-side reveal completed. The owner — not the
scene — decides whether a flip follows.
- `webgl-book:request-page-flip` (owner -> scene): requests a physical page flip.
- `webgl-book:page-flip-started`, `webgl-book:page-flip-near-end`, `webgl-book:page-flip-finished`
(scene -> owner): the physical flip lifecycle, each carrying the resolved `targetSpread`.
- `webgl-book:page-texture-records` publishes explicit page texture records from `book-texture-renderer` to the WebGL scene.
- `webgl-book:page-reveal-start` starts shader reveal timing for the prepared block.
- `webgl-book:page-reveal-fast-forward` accelerates reveal timing without replacing the page pipeline.
- `webgl-book:reveal-committed` reports that a page-side reveal completed; if `pageFlipAfterReveal` is true, the WebGL scene may arm a page flip.
- `webgl-book:request-page-flip` requests a physical page flip through the WebGL scene.
- `webgl-book:page-flip-started`, `webgl-book:page-flip-near-end`, and `webgl-book:page-flip-finished` describe the physical flip lifecycle.
The scene reacts to these events; it does not originate flip decisions from `reveal-committed`.
Deprecated or forbidden event contracts:
Deprecated or forbidden contracts:
- `webgl-book:page-canvases` is obsolete. New code must use `webgl-book:page-texture-records`.
- `preloadOnly` and `allowFutureUnrendered` are obsolete boolean flags. New code must use explicit `phase` and `visibility` values.
- `webgl-book:page-canvases` is obsolete; use `webgl-book:page-texture-records`.
- `preloadOnly` and `allowFutureUnrendered` boolean flags are obsolete; use explicit `phase` and
`visibility` values.
- The legacy `ownsPageFlipCommit` toggle and the `book-pagination:spread-updated`-driven reveal/flip
path in `book-texture-renderer` and the scene are removed. There is no second playback path to
gate, so no gating flag exists.
## Non-Negotiable Workflow Rules