Stabilize WebGL flip reveal handoff

This commit is contained in:
2026-06-10 15:10:57 +02:00
parent 97eab216b7
commit ef358c5cfd
6 changed files with 186 additions and 41 deletions
+36 -2
View File
@@ -108,10 +108,10 @@ class BookPlaybackTimelineModule extends BaseModule {
await this.timeStage('activate', segment, () => this.activatePreparedSegment(segment, sentence));
sentence.webglRevealController = () => this.startRevealForSegment(segment);
const visualPromise = this.waitForVisualCompletion(segment);
const playbackPromise = this.timeStage('playback', segment, () => {
return this.playbackCoordinator?.play?.(sentence) || Promise.resolve();
});
const visualPromise = this.waitForVisualCompletion(segment);
await Promise.all([playbackPromise, visualPromise]);
this.recordDiagnostic('segment-play:end', segment);
@@ -172,8 +172,14 @@ class BookPlaybackTimelineModule extends BaseModule {
requiresRightFlip: revealSides.includes('right') && this.requiresRightPageFlipAfterReveal(previewSpread),
preparedTexturePlan: texturePlan,
preparedAt: performance.now(),
revealStartedAt: null,
revealStartedPromise: null,
resolveRevealStarted: null,
status: 'prepared'
};
segment.revealStartedPromise = new Promise(resolve => {
segment.resolveRevealStarted = resolve;
});
this.applyTexturePlan(texturePlan, segment, 'prepare');
await this.timeStage('texture-prewarm', segment, () => this.prewarmSegmentTextures(segment));
@@ -303,6 +309,11 @@ class BookPlaybackTimelineModule extends BaseModule {
throw new Error('BookPlaybackTimeline: WebGL book lab cannot start prepared reveals explicitly');
}
window.BookLabDebug.startRevealForBlock(segment.blockId);
segment.revealStartedAt = performance.now();
if (typeof segment.resolveRevealStarted === 'function') {
segment.resolveRevealStarted(segment.revealStartedAt);
segment.resolveRevealStarted = null;
}
this.markBenchmark('reveal-start', segment);
this.recordDiagnostic('reveal-started', segment);
return true;
@@ -337,7 +348,7 @@ class BookPlaybackTimelineModule extends BaseModule {
this.recordDiagnostic('visual-completion:no-right-flip-wait', segment);
return;
}
const committed = await this.timeStage('wait-right-reveal-commit', segment, () => this.waitForRevealCommit(segment));
const committed = await this.timeStage('wait-right-reveal-commit', segment, () => this.waitForPlannedRightReveal(segment));
if (!committed || this.isChoiceAwaitingPlayer()) return;
await this.timeStage('right-page-flip', segment, () => this.requestPageFlip(1, {
reason: 'timeline-right-page-filled',
@@ -346,6 +357,29 @@ class BookPlaybackTimelineModule extends BaseModule {
}));
}
async waitForPlannedRightReveal(segment = {}) {
const startedAt = Number(segment.revealStartedAt)
|| await (segment.revealStartedPromise || Promise.resolve(performance.now()));
const duration = this.getRightRevealDurationMs(segment);
const elapsed = Math.max(0, performance.now() - Number(startedAt || performance.now()));
const remaining = Math.max(0, duration - elapsed);
const planned = new Promise(resolve => {
setTimeout(() => resolve(true), remaining);
});
return Promise.race([
planned,
this.waitForRevealCommit(segment)
]);
}
getRightRevealDurationMs(segment = {}) {
const duration = Number(segment.activeTexturePlan?.reveal?.right?.durationMs
?? segment.preparedTexturePlan?.reveal?.right?.durationMs
?? 0);
if (Number.isFinite(duration) && duration > 0) return duration;
return Math.max(1, Number(segment.sentence?.animation?.totalDuration || 1));
}
waitForRevealCommit(segment = {}) {
const blockId = String(segment.blockId ?? '');
if (!blockId) return Promise.resolve(false);