Enforce explicit WebGL book playback timeline
This commit is contained in:
@@ -28,6 +28,10 @@ class BookPlaybackTimelineModule extends BaseModule {
|
|||||||
'ensureAnimationTimings',
|
'ensureAnimationTimings',
|
||||||
'createPreparedSegment',
|
'createPreparedSegment',
|
||||||
'createRevealDetail',
|
'createRevealDetail',
|
||||||
|
'applyTexturePlan',
|
||||||
|
'startRevealForSegment',
|
||||||
|
'assertSegmentReady',
|
||||||
|
'collectRequiredPageMetas',
|
||||||
'requiresSpreadTransition',
|
'requiresSpreadTransition',
|
||||||
'requiresRightPageFlipAfterReveal',
|
'requiresRightPageFlipAfterReveal',
|
||||||
'getBlockRevealSides',
|
'getBlockRevealSides',
|
||||||
@@ -103,6 +107,7 @@ class BookPlaybackTimelineModule extends BaseModule {
|
|||||||
|
|
||||||
await this.timeStage('activate', segment, () => this.activatePreparedSegment(segment, sentence));
|
await this.timeStage('activate', segment, () => this.activatePreparedSegment(segment, sentence));
|
||||||
|
|
||||||
|
sentence.webglRevealController = () => this.startRevealForSegment(segment);
|
||||||
const visualPromise = this.waitForVisualCompletion(segment);
|
const visualPromise = this.waitForVisualCompletion(segment);
|
||||||
const playbackPromise = this.timeStage('playback', segment, () => {
|
const playbackPromise = this.timeStage('playback', segment, () => {
|
||||||
return this.playbackCoordinator?.play?.(sentence) || Promise.resolve();
|
return this.playbackCoordinator?.play?.(sentence) || Promise.resolve();
|
||||||
@@ -146,7 +151,10 @@ class BookPlaybackTimelineModule extends BaseModule {
|
|||||||
if (!previewSpread) return null;
|
if (!previewSpread) return null;
|
||||||
|
|
||||||
const revealDetail = this.createRevealDetail(sentence, previewSpread, 'prepare');
|
const revealDetail = this.createRevealDetail(sentence, previewSpread, 'prepare');
|
||||||
this.textureRenderer.prepareRevealBlock(revealDetail, { phase: 'prepare' });
|
const texturePlan = this.textureRenderer.prepareRevealBlock(revealDetail, {
|
||||||
|
phase: 'prepare',
|
||||||
|
publishEvent: false
|
||||||
|
});
|
||||||
|
|
||||||
const targetSpreadIndex = Math.max(0, Number(previewSpread.index || 0));
|
const targetSpreadIndex = Math.max(0, Number(previewSpread.index || 0));
|
||||||
const currentSpreadIndex = this.getVisibleSpreadIndex();
|
const currentSpreadIndex = this.getVisibleSpreadIndex();
|
||||||
@@ -162,11 +170,14 @@ class BookPlaybackTimelineModule extends BaseModule {
|
|||||||
revealSides,
|
revealSides,
|
||||||
requiresPreFlip: targetSpreadIndex > currentSpreadIndex,
|
requiresPreFlip: targetSpreadIndex > currentSpreadIndex,
|
||||||
requiresRightFlip: revealSides.includes('right') && this.requiresRightPageFlipAfterReveal(previewSpread),
|
requiresRightFlip: revealSides.includes('right') && this.requiresRightPageFlipAfterReveal(previewSpread),
|
||||||
|
preparedTexturePlan: texturePlan,
|
||||||
preparedAt: performance.now(),
|
preparedAt: performance.now(),
|
||||||
status: 'prepared'
|
status: 'prepared'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.applyTexturePlan(texturePlan, segment, 'prepare');
|
||||||
await this.timeStage('texture-prewarm', segment, () => this.prewarmSegmentTextures(segment));
|
await this.timeStage('texture-prewarm', segment, () => this.prewarmSegmentTextures(segment));
|
||||||
|
await this.assertSegmentReady(segment, 'prepare');
|
||||||
if (options.immediate !== true) {
|
if (options.immediate !== true) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 0));
|
await new Promise(resolve => setTimeout(resolve, 0));
|
||||||
}
|
}
|
||||||
@@ -185,7 +196,12 @@ class BookPlaybackTimelineModule extends BaseModule {
|
|||||||
&& this.requiresRightPageFlipAfterReveal(segment.activeSpread || segment.previewSpread);
|
&& this.requiresRightPageFlipAfterReveal(segment.activeSpread || segment.previewSpread);
|
||||||
|
|
||||||
const revealDetail = this.createRevealDetail(sentence, segment.activeSpread || segment.previewSpread, 'activate');
|
const revealDetail = this.createRevealDetail(sentence, segment.activeSpread || segment.previewSpread, 'activate');
|
||||||
this.textureRenderer.prepareRevealBlock(revealDetail);
|
const texturePlan = this.textureRenderer.prepareRevealBlock(revealDetail, {
|
||||||
|
publishEvent: false
|
||||||
|
});
|
||||||
|
segment.activeTexturePlan = texturePlan;
|
||||||
|
this.applyTexturePlan(texturePlan, segment, 'activate');
|
||||||
|
await this.assertSegmentReady(segment, 'activate');
|
||||||
segment.status = 'activated';
|
segment.status = 'activated';
|
||||||
this.recordDiagnostic('segment-activate:end', segment);
|
this.recordDiagnostic('segment-activate:end', segment);
|
||||||
return segment.activeSpread;
|
return segment.activeSpread;
|
||||||
@@ -210,6 +226,38 @@ class BookPlaybackTimelineModule extends BaseModule {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyTexturePlan(texturePlan = null, segment = {}, phase = 'activate') {
|
||||||
|
if (!texturePlan) {
|
||||||
|
throw new Error(`BookPlaybackTimeline: Missing texture plan for block ${segment.blockId ?? 'unknown'} during ${phase}`);
|
||||||
|
}
|
||||||
|
if (typeof window.BookLabDebug?.applyPageTextureRecords !== 'function') {
|
||||||
|
throw new Error('BookPlaybackTimeline: WebGL book lab cannot apply prepared texture plans');
|
||||||
|
}
|
||||||
|
window.BookLabDebug.applyPageTextureRecords({
|
||||||
|
...texturePlan,
|
||||||
|
phase: phase === 'prepare' ? 'prepare' : 'activate'
|
||||||
|
});
|
||||||
|
this.recordDiagnostic(`texture-plan-applied:${phase}`, segment);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
startRevealForSegment(segment = {}) {
|
||||||
|
if (!segment?.blockId) return false;
|
||||||
|
const revealStart = this.textureRenderer?.startPreparedRevealAnimation?.(segment.blockId, {
|
||||||
|
publishEvent: false
|
||||||
|
});
|
||||||
|
if (!revealStart) {
|
||||||
|
throw new Error(`BookPlaybackTimeline: Prepared reveal animation is missing for block ${segment.blockId}`);
|
||||||
|
}
|
||||||
|
if (typeof window.BookLabDebug?.startRevealForBlock !== 'function') {
|
||||||
|
throw new Error('BookPlaybackTimeline: WebGL book lab cannot start prepared reveals explicitly');
|
||||||
|
}
|
||||||
|
window.BookLabDebug.startRevealForBlock(segment.blockId);
|
||||||
|
this.markBenchmark('reveal-start', segment);
|
||||||
|
this.recordDiagnostic('reveal-started', segment);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
requiresSpreadTransition(segment = {}) {
|
requiresSpreadTransition(segment = {}) {
|
||||||
return Math.max(0, Number(segment.targetSpreadIndex || 0)) > this.getVisibleSpreadIndex();
|
return Math.max(0, Number(segment.targetSpreadIndex || 0)) > this.getVisibleSpreadIndex();
|
||||||
}
|
}
|
||||||
@@ -288,15 +336,20 @@ class BookPlaybackTimelineModule extends BaseModule {
|
|||||||
getPageMetaForIndex: this.getPageMetaForIndex,
|
getPageMetaForIndex: this.getPageMetaForIndex,
|
||||||
recordMiss: true
|
recordMiss: true
|
||||||
});
|
});
|
||||||
|
await this.assertSegmentReady({
|
||||||
|
blockId: options.blockId ?? null,
|
||||||
|
targetSpreadIndex: options.targetSpread,
|
||||||
|
revealSides: []
|
||||||
|
}, 'flip');
|
||||||
const wait = this.waitForPageFlipFinished(options.targetSpread);
|
const wait = this.waitForPageFlipFinished(options.targetSpread);
|
||||||
document.dispatchEvent(new CustomEvent('webgl-book:request-page-flip', {
|
if (typeof window.BookLabDebug?.requestPageFlip !== 'function') {
|
||||||
detail: {
|
throw new Error('BookPlaybackTimeline: WebGL book lab cannot execute prepared flip plans');
|
||||||
direction,
|
}
|
||||||
force: options.force === true,
|
window.BookLabDebug.requestPageFlip(direction, {
|
||||||
reason: options.reason || 'timeline',
|
force: options.force === true,
|
||||||
targetSpread: options.targetSpread
|
reason: options.reason || 'timeline',
|
||||||
}
|
targetSpread: options.targetSpread
|
||||||
}));
|
});
|
||||||
return wait;
|
return wait;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,6 +369,53 @@ class BookPlaybackTimelineModule extends BaseModule {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
collectRequiredPageMetas(segment = {}) {
|
||||||
|
const spreads = new Set();
|
||||||
|
const currentSpread = this.getVisibleSpreadIndex();
|
||||||
|
const targetSpread = Number.isFinite(Number(segment.targetSpreadIndex))
|
||||||
|
? Math.max(0, Math.round(Number(segment.targetSpreadIndex)))
|
||||||
|
: currentSpread;
|
||||||
|
spreads.add(0);
|
||||||
|
spreads.add(currentSpread);
|
||||||
|
spreads.add(Math.max(0, currentSpread - 1));
|
||||||
|
spreads.add(currentSpread + 1);
|
||||||
|
spreads.add(targetSpread);
|
||||||
|
if (segment.requiresRightFlip) spreads.add(targetSpread + 1);
|
||||||
|
return Array.from(spreads)
|
||||||
|
.filter(spread => spread >= 0)
|
||||||
|
.flatMap(spread => [
|
||||||
|
this.getPageMetaForIndex(spread * 2),
|
||||||
|
this.getPageMetaForIndex(spread * 2 + 1)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async assertSegmentReady(segment = {}, phase = 'play') {
|
||||||
|
if (!this.pageCache || typeof this.pageCache.ensurePageTexture !== 'function') {
|
||||||
|
throw new Error('BookPlaybackTimeline: Page texture cache is not available');
|
||||||
|
}
|
||||||
|
const metas = this.collectRequiredPageMetas(segment);
|
||||||
|
const missing = [];
|
||||||
|
await Promise.all(metas.map(async (meta) => {
|
||||||
|
const texture = await this.pageCache.ensurePageTexture(meta, {
|
||||||
|
recordMiss: true
|
||||||
|
});
|
||||||
|
if (!texture) missing.push(meta);
|
||||||
|
}));
|
||||||
|
if (missing.length > 0) {
|
||||||
|
this.pageCache.recordProblem?.({
|
||||||
|
type: 'timeline-cache-readiness-failed',
|
||||||
|
phase,
|
||||||
|
blockId: segment.blockId ?? null,
|
||||||
|
missingPages: missing.map(meta => meta.pageIndex ?? null)
|
||||||
|
});
|
||||||
|
throw new Error(`BookPlaybackTimeline: Cache readiness failed during ${phase} for pages ${missing.map(meta => meta.pageIndex).join(', ')}`);
|
||||||
|
}
|
||||||
|
segment.cacheReady = true;
|
||||||
|
segment.cacheReadyPhase = phase;
|
||||||
|
this.recordDiagnostic(`cache-ready:${phase}`, segment);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
getPageMetaForIndex(pageIndex = 0) {
|
getPageMetaForIndex(pageIndex = 0) {
|
||||||
const index = Math.max(0, Math.round(Number(pageIndex || 0)));
|
const index = Math.max(0, Math.round(Number(pageIndex || 0)));
|
||||||
const spreadIndex = Math.floor(index / 2);
|
const spreadIndex = Math.floor(index / 2);
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ class BookTextureRendererModule extends BaseModule {
|
|||||||
'prepareRevealBlock',
|
'prepareRevealBlock',
|
||||||
'preloadAdditionalRevealSpreads',
|
'preloadAdditionalRevealSpreads',
|
||||||
'spreadContainsBlock',
|
'spreadContainsBlock',
|
||||||
'hasPreparedRevealBlock',
|
|
||||||
'createAnimationState',
|
'createAnimationState',
|
||||||
'getDrawPhase',
|
'getDrawPhase',
|
||||||
'publishPreparedReveal',
|
'publishPreparedReveal',
|
||||||
@@ -141,12 +140,6 @@ class BookTextureRendererModule extends BaseModule {
|
|||||||
}
|
}
|
||||||
this.drawSpread(this.currentSpread);
|
this.drawSpread(this.currentSpread);
|
||||||
});
|
});
|
||||||
this.addEventListener(document, 'book-texture:reveal-block', (event) => {
|
|
||||||
this.startRevealAnimation(event.detail || {});
|
|
||||||
});
|
|
||||||
this.addEventListener(document, 'book-texture:prepare-reveal-block', (event) => {
|
|
||||||
this.prepareRevealBlock(event.detail || {});
|
|
||||||
});
|
|
||||||
this.addEventListener(document, 'book-texture:fast-forward', this.fastForwardAnimations);
|
this.addEventListener(document, 'book-texture:fast-forward', this.fastForwardAnimations);
|
||||||
this.addEventListener(document, 'ui:command', (event) => {
|
this.addEventListener(document, 'ui:command', (event) => {
|
||||||
if (event.detail?.type === 'continue') this.fastForwardAnimations();
|
if (event.detail?.type === 'continue') this.fastForwardAnimations();
|
||||||
@@ -910,13 +903,17 @@ class BookTextureRendererModule extends BaseModule {
|
|||||||
const cached = this.pageCache.takePreparedRevealPlan(id);
|
const cached = this.pageCache.takePreparedRevealPlan(id);
|
||||||
this.activeAnimations.set(id, this.createAnimationState(blockId, wordTimings, detail));
|
this.activeAnimations.set(id, this.createAnimationState(blockId, wordTimings, detail));
|
||||||
this.pendingRevealBlockIds.delete(id);
|
this.pendingRevealBlockIds.delete(id);
|
||||||
this.publishPreparedReveal(cached);
|
this.publishPreparedReveal(cached, options);
|
||||||
this.markPipelineTiming('prepareRevealBlock:end', {
|
this.markPipelineTiming('prepareRevealBlock:end', {
|
||||||
blockId: id,
|
blockId: id,
|
||||||
wordTimingCount: wordTimings.length,
|
wordTimingCount: wordTimings.length,
|
||||||
reusedPreparedCanvas: true
|
reusedPreparedCanvas: true
|
||||||
});
|
});
|
||||||
return;
|
return {
|
||||||
|
...cached,
|
||||||
|
phase: 'activate',
|
||||||
|
preparedFromCache: true
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeAnimations.set(id, this.createAnimationState(blockId, wordTimings, detail));
|
this.activeAnimations.set(id, this.createAnimationState(blockId, wordTimings, detail));
|
||||||
@@ -924,7 +921,10 @@ class BookTextureRendererModule extends BaseModule {
|
|||||||
this.revealPublishBlockIds = new Set([id]);
|
this.revealPublishBlockIds = new Set([id]);
|
||||||
const spread = detail.spread || this.currentSpread || this.pagination?.getCurrentSpread?.();
|
const spread = detail.spread || this.currentSpread || this.pagination?.getCurrentSpread?.();
|
||||||
const sides = ['left', 'right'];
|
const sides = ['left', 'right'];
|
||||||
const published = this.drawSpread(spread, sides, { phase });
|
const published = this.drawSpread(spread, sides, {
|
||||||
|
phase,
|
||||||
|
publishEvent: options.publishEvent !== false
|
||||||
|
});
|
||||||
if (phase !== 'prepare') this.preloadAdditionalRevealSpreads(id, spread);
|
if (phase !== 'prepare') this.preloadAdditionalRevealSpreads(id, spread);
|
||||||
if (phase === 'prepare' && published) {
|
if (phase === 'prepare' && published) {
|
||||||
this.pageCache?.rememberPreparedRevealPlan?.(id, {
|
this.pageCache?.rememberPreparedRevealPlan?.(id, {
|
||||||
@@ -939,6 +939,12 @@ class BookTextureRendererModule extends BaseModule {
|
|||||||
wordTimingCount: wordTimings.length,
|
wordTimingCount: wordTimings.length,
|
||||||
phase
|
phase
|
||||||
});
|
});
|
||||||
|
return published ? {
|
||||||
|
...published,
|
||||||
|
blockId,
|
||||||
|
wordTimings,
|
||||||
|
totalDuration: detail.totalDuration || 0
|
||||||
|
} : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
preloadAdditionalRevealSpreads(blockId, primarySpread = null) {
|
preloadAdditionalRevealSpreads(blockId, primarySpread = null) {
|
||||||
@@ -960,32 +966,29 @@ class BookTextureRendererModule extends BaseModule {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPreparedRevealBlock(blockId) {
|
publishPreparedReveal(prepared, options = {}) {
|
||||||
const id = String(blockId ?? '');
|
if (!prepared) return null;
|
||||||
return Boolean(id && this.pageCache?.hasPreparedRevealPlan?.(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
publishPreparedReveal(prepared) {
|
|
||||||
if (!prepared) return;
|
|
||||||
this.markPipelineTiming('publishPreparedReveal', {
|
this.markPipelineTiming('publishPreparedReveal', {
|
||||||
blockId: prepared.blockId,
|
blockId: prepared.blockId,
|
||||||
sides: prepared.sides || [],
|
sides: prepared.sides || [],
|
||||||
hasReveal: Boolean(prepared.reveal && Object.keys(prepared.reveal).length)
|
hasReveal: Boolean(prepared.reveal && Object.keys(prepared.reveal).length)
|
||||||
});
|
});
|
||||||
document.dispatchEvent(new CustomEvent('webgl-book:page-texture-records', {
|
const detail = {
|
||||||
detail: {
|
metrics: prepared.metrics,
|
||||||
metrics: prepared.metrics,
|
hitMaps: prepared.hitMaps || this.hitMaps,
|
||||||
hitMaps: prepared.hitMaps || this.hitMaps,
|
records: prepared.records || this.buildPageTextureRecords(prepared.sides || ['left', 'right'], prepared),
|
||||||
records: prepared.records || this.buildPageTextureRecords(prepared.sides || ['left', 'right'], prepared),
|
reveal: prepared.reveal || {},
|
||||||
reveal: prepared.reveal || {},
|
pageMeta: prepared.pageMeta || {},
|
||||||
pageMeta: prepared.pageMeta || {},
|
phase: 'activate',
|
||||||
phase: 'activate',
|
preparedFromCache: true
|
||||||
preparedFromCache: true
|
};
|
||||||
}
|
if (options.publishEvent !== false) {
|
||||||
}));
|
document.dispatchEvent(new CustomEvent('webgl-book:page-texture-records', { detail }));
|
||||||
|
}
|
||||||
|
return detail;
|
||||||
}
|
}
|
||||||
|
|
||||||
startPreparedRevealAnimation(blockId) {
|
startPreparedRevealAnimation(blockId, options = {}) {
|
||||||
const id = String(blockId ?? '');
|
const id = String(blockId ?? '');
|
||||||
const animation = this.activeAnimations.get(id);
|
const animation = this.activeAnimations.get(id);
|
||||||
if (!animation) return false;
|
if (!animation) return false;
|
||||||
@@ -996,13 +999,18 @@ class BookTextureRendererModule extends BaseModule {
|
|||||||
animation.startedAt = performance.now();
|
animation.startedAt = performance.now();
|
||||||
animation.prepared = false;
|
animation.prepared = false;
|
||||||
animation.completed = false;
|
animation.completed = false;
|
||||||
document.dispatchEvent(new CustomEvent('webgl-book:page-reveal-start', {
|
if (options.publishEvent !== false) {
|
||||||
detail: {
|
document.dispatchEvent(new CustomEvent('webgl-book:page-reveal-start', {
|
||||||
blockId: animation.blockId
|
detail: {
|
||||||
}
|
blockId: animation.blockId
|
||||||
}));
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
this.requestAnimationFrame();
|
this.requestAnimationFrame();
|
||||||
return true;
|
return {
|
||||||
|
blockId: animation.blockId,
|
||||||
|
wordTimingCount: Array.isArray(animation.wordTimings) ? animation.wordTimings.length : 0
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fastForwardAnimations() {
|
fastForwardAnimations() {
|
||||||
@@ -1153,9 +1161,11 @@ class BookTextureRendererModule extends BaseModule {
|
|||||||
regionCounts,
|
regionCounts,
|
||||||
phase
|
phase
|
||||||
});
|
});
|
||||||
document.dispatchEvent(new CustomEvent('webgl-book:page-texture-records', {
|
if (options.publishEvent !== false) {
|
||||||
detail
|
document.dispatchEvent(new CustomEvent('webgl-book:page-texture-records', {
|
||||||
}));
|
detail
|
||||||
|
}));
|
||||||
|
}
|
||||||
return detail;
|
return detail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -252,16 +252,6 @@ class PlaybackCoordinatorModule extends BaseModule {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
document.dispatchEvent(new CustomEvent('book-texture:reveal-block', {
|
|
||||||
detail: {
|
|
||||||
id: sentence.id,
|
|
||||||
blockId: sentence.blockId ?? sentence.metadata?.blockId ?? null,
|
|
||||||
wordTimings,
|
|
||||||
cueTimings,
|
|
||||||
totalDuration: sentence.animation.totalDuration || 0
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const totalDuration = wordTimings.length > 0
|
const totalDuration = wordTimings.length > 0
|
||||||
? Math.max(...wordTimings.map(timing => timing.delay + timing.duration))
|
? Math.max(...wordTimings.map(timing => timing.delay + timing.duration))
|
||||||
@@ -332,15 +322,16 @@ class PlaybackCoordinatorModule extends BaseModule {
|
|||||||
cueTimings = [];
|
cueTimings = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
document.dispatchEvent(new CustomEvent('book-texture:reveal-block', {
|
if (typeof sentence.webglRevealController !== 'function') {
|
||||||
detail: {
|
throw new Error('PlaybackCoordinator: WebGL playback requires a prepared timeline reveal controller');
|
||||||
id: sentence.id,
|
}
|
||||||
blockId: sentence.blockId ?? sentence.metadata?.blockId ?? null,
|
sentence.webglRevealController({
|
||||||
wordTimings,
|
id: sentence.id,
|
||||||
cueTimings,
|
blockId: sentence.blockId ?? sentence.metadata?.blockId ?? null,
|
||||||
totalDuration: sentence.animation?.totalDuration || 0
|
wordTimings,
|
||||||
}
|
cueTimings,
|
||||||
}));
|
totalDuration: sentence.animation?.totalDuration || 0
|
||||||
|
});
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const totalDuration = wordTimings.length > 0
|
const totalDuration = wordTimings.length > 0
|
||||||
|
|||||||
@@ -899,39 +899,9 @@ class SentenceQueueModule extends BaseModule {
|
|||||||
const blockId = sentence.blockId ?? sentence.metadata?.blockId ?? null;
|
const blockId = sentence.blockId ?? sentence.metadata?.blockId ?? null;
|
||||||
if (blockId == null) return null;
|
if (blockId == null) return null;
|
||||||
const bookPlaybackTimeline = this.getModule('book-playback-timeline');
|
const bookPlaybackTimeline = this.getModule('book-playback-timeline');
|
||||||
if (bookPlaybackTimeline && typeof bookPlaybackTimeline.prepareSentence === 'function') {
|
if (!bookPlaybackTimeline || typeof bookPlaybackTimeline.prepareSentence !== 'function') {
|
||||||
if (!options.immediate) {
|
throw new Error('SentenceQueue: 3D book presentation requires the book playback timeline');
|
||||||
await new Promise(resolve => {
|
|
||||||
const scheduler = window.requestIdleCallback || ((callback) => window.setTimeout(callback, 1));
|
|
||||||
scheduler(() => resolve(), { timeout: 80 });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (options.queueGeneration !== undefined && options.queueGeneration !== this.queueGeneration) return null;
|
|
||||||
const segment = await bookPlaybackTimeline.prepareSentence(sentence, {
|
|
||||||
immediate: options.immediate === true
|
|
||||||
});
|
|
||||||
if (!segment) return null;
|
|
||||||
sentence.webglBookPresentation = {
|
|
||||||
prepared: true,
|
|
||||||
blockId,
|
|
||||||
spread: segment.previewSpread || segment.activeSpread || null,
|
|
||||||
timelineSegment: segment
|
|
||||||
};
|
|
||||||
return sentence.webglBookPresentation.spread;
|
|
||||||
}
|
}
|
||||||
const bookPagination = this.getModule('book-pagination');
|
|
||||||
const bookTextureRenderer = this.getModule('book-texture-renderer');
|
|
||||||
if (!bookPagination || !bookTextureRenderer) return null;
|
|
||||||
|
|
||||||
if (this.isWebGLBookPresentationPrepared(sentence)) {
|
|
||||||
return sentence.webglBookPresentation?.spread || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Array.isArray(sentence.animation?.wordTimings) || sentence.animation.wordTimings.length === 0) {
|
|
||||||
const words = String(sentence.layoutText || sentence.text || '').match(/\S+/g) || [];
|
|
||||||
sentence.animation = this.calculateAnimationTiming(words, sentence.tts?.duration || 0, sentence.cueMarkers || []);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!options.immediate) {
|
if (!options.immediate) {
|
||||||
await new Promise(resolve => {
|
await new Promise(resolve => {
|
||||||
const scheduler = window.requestIdleCallback || ((callback) => window.setTimeout(callback, 1));
|
const scheduler = window.requestIdleCallback || ((callback) => window.setTimeout(callback, 1));
|
||||||
@@ -939,42 +909,25 @@ class SentenceQueueModule extends BaseModule {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (options.queueGeneration !== undefined && options.queueGeneration !== this.queueGeneration) return null;
|
if (options.queueGeneration !== undefined && options.queueGeneration !== this.queueGeneration) return null;
|
||||||
|
const segment = await bookPlaybackTimeline.prepareSentence(sentence, {
|
||||||
const spread = typeof bookPagination.preparePendingBlock === 'function'
|
immediate: options.immediate === true
|
||||||
? await bookPagination.preparePendingBlock(sentence, {
|
});
|
||||||
activate: false,
|
if (!segment) return null;
|
||||||
publish: false,
|
sentence.webglBookPresentation = {
|
||||||
includeUnrenderedHistory: true
|
prepared: true,
|
||||||
})
|
blockId,
|
||||||
: null;
|
spread: segment.previewSpread || segment.activeSpread || null,
|
||||||
if (!spread) return null;
|
timelineSegment: segment
|
||||||
if (options.queueGeneration !== undefined && options.queueGeneration !== this.queueGeneration) return null;
|
};
|
||||||
|
return sentence.webglBookPresentation.spread;
|
||||||
if (typeof bookTextureRenderer.prepareRevealBlock === 'function') {
|
|
||||||
bookTextureRenderer.prepareRevealBlock({
|
|
||||||
id: sentence.id,
|
|
||||||
blockId,
|
|
||||||
wordTimings: sentence.animation?.wordTimings || [],
|
|
||||||
cueTimings: sentence.animation?.cueTimings || [],
|
|
||||||
totalDuration: sentence.animation?.totalDuration || 0,
|
|
||||||
spread,
|
|
||||||
phase: 'prepare'
|
|
||||||
}, { phase: 'prepare' });
|
|
||||||
sentence.webglBookPresentation = {
|
|
||||||
prepared: true,
|
|
||||||
blockId,
|
|
||||||
spread
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return spread;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isWebGLBookPresentationPrepared(sentence) {
|
isWebGLBookPresentationPrepared(sentence) {
|
||||||
const blockId = sentence?.blockId ?? sentence?.metadata?.blockId ?? null;
|
const blockId = sentence?.blockId ?? sentence?.metadata?.blockId ?? null;
|
||||||
if (blockId == null) return false;
|
if (blockId == null) return false;
|
||||||
if (sentence?.webglBookPresentation?.prepared === true) return true;
|
if (sentence?.webglBookPresentation?.prepared === true) return true;
|
||||||
const bookTextureRenderer = this.getModule('book-texture-renderer');
|
const bookPlaybackTimeline = this.getModule('book-playback-timeline');
|
||||||
return Boolean(bookTextureRenderer?.hasPreparedRevealBlock?.(blockId));
|
return Boolean(bookPlaybackTimeline?.preparedSegments?.has?.(`${sentence.gameId || sentence.metadata?.gameId || 'game'}:${blockId}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
isCurrentQueueItem(item, queueGeneration = this.queueGeneration) {
|
isCurrentQueueItem(item, queueGeneration = this.queueGeneration) {
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ class UIDisplayHandlerModule extends BaseModule {
|
|||||||
'isWebGLMode',
|
'isWebGLMode',
|
||||||
'playWebGLBookSentence',
|
'playWebGLBookSentence',
|
||||||
'prepareWebGLBookReveal',
|
'prepareWebGLBookReveal',
|
||||||
'waitForWebGLPageFlip',
|
|
||||||
'renderStoryBlock',
|
'renderStoryBlock',
|
||||||
'prepareRenderableBlock',
|
'prepareRenderableBlock',
|
||||||
'prepareTextRenderable',
|
'prepareTextRenderable',
|
||||||
@@ -1070,47 +1069,6 @@ class UIDisplayHandlerModule extends BaseModule {
|
|||||||
return timeline.prepareSentence(sentence, { immediate: true });
|
return timeline.prepareSentence(sentence, { immediate: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForWebGLPageFlip(detail = {}) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
let resolved = false;
|
|
||||||
const cleanup = () => {
|
|
||||||
window.clearTimeout(timeout);
|
|
||||||
document.removeEventListener('webgl-book:page-flip-started', onStarted);
|
|
||||||
document.removeEventListener('webgl-book:page-flip-finished', onFinished);
|
|
||||||
};
|
|
||||||
const finish = (result) => {
|
|
||||||
if (resolved) return;
|
|
||||||
resolved = true;
|
|
||||||
cleanup();
|
|
||||||
resolve(result);
|
|
||||||
};
|
|
||||||
const requestedTargetSpread = Number.isFinite(Number(detail.targetSpread))
|
|
||||||
? Math.max(0, Math.round(Number(detail.targetSpread)))
|
|
||||||
: null;
|
|
||||||
const matchesTarget = (eventDetail = {}) => requestedTargetSpread == null
|
|
||||||
|| Math.max(0, Math.round(Number(eventDetail.targetSpread || 0))) === requestedTargetSpread;
|
|
||||||
const onStarted = (event) => {
|
|
||||||
if (!matchesTarget(event.detail || {})) return;
|
|
||||||
document.documentElement.dataset.webglLastStartedPageFlip = JSON.stringify(event.detail || {});
|
|
||||||
};
|
|
||||||
const onFinished = (event) => {
|
|
||||||
if (!matchesTarget(event.detail || {})) return;
|
|
||||||
finish(true);
|
|
||||||
};
|
|
||||||
const timeout = window.setTimeout(() => finish(false), 2400);
|
|
||||||
document.addEventListener('webgl-book:page-flip-started', onStarted);
|
|
||||||
document.addEventListener('webgl-book:page-flip-finished', onFinished);
|
|
||||||
document.dispatchEvent(new CustomEvent('webgl-book:request-page-flip', {
|
|
||||||
detail: {
|
|
||||||
direction: Math.sign(Number(detail.direction || 1)) || 1,
|
|
||||||
reason: detail.reason || 'pending-block-overflow',
|
|
||||||
force: true,
|
|
||||||
targetSpread: requestedTargetSpread
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async rerenderStory() {
|
async rerenderStory() {
|
||||||
if (!this.paragraphContainer || this.renderedItems.length === 0) return;
|
if (!this.paragraphContainer || this.renderedItems.length === 0) return;
|
||||||
console.log('UIDisplayHandler: Re-typesetting story after page resize');
|
console.log('UIDisplayHandler: Re-typesetting story after page resize');
|
||||||
|
|||||||
+11
-11
@@ -586,6 +586,17 @@ window.BookLabDebug = {
|
|||||||
window.BookTextureRenderer?.publishSpread?.();
|
window.BookTextureRenderer?.publishSpread?.();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
applyPageTextureRecords(detail = {}) {
|
||||||
|
handlePageTextureRecords({ detail });
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
startRevealForBlock(blockId) {
|
||||||
|
startPageRevealForBlock(blockId);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
requestPageFlip(direction = 1, options = {}) {
|
||||||
|
return startPageFlip(direction, options);
|
||||||
|
},
|
||||||
getRevealDebugState() {
|
getRevealDebugState() {
|
||||||
return getRevealDebugState();
|
return getRevealDebugState();
|
||||||
},
|
},
|
||||||
@@ -686,17 +697,6 @@ document.addEventListener('webgl-book:page-reserve-directive', (event) => {
|
|||||||
: Math.round(value);
|
: Math.round(value);
|
||||||
setPageReserve(nextReserve);
|
setPageReserve(nextReserve);
|
||||||
});
|
});
|
||||||
document.addEventListener('webgl-book:request-page-flip', (event) => {
|
|
||||||
const detail = event.detail || {};
|
|
||||||
const direction = Math.sign(Number(detail.direction || 1)) || 1;
|
|
||||||
const targetSpread = Number.isFinite(Number(detail.targetSpread))
|
|
||||||
? Math.max(0, Math.round(Number(detail.targetSpread)))
|
|
||||||
: null;
|
|
||||||
startPageFlip(direction, {
|
|
||||||
force: detail.force === true,
|
|
||||||
targetSpread
|
|
||||||
});
|
|
||||||
});
|
|
||||||
document.addEventListener('ui:command', (event) => {
|
document.addEventListener('ui:command', (event) => {
|
||||||
if (event.detail?.type === 'continue' && pendingRightPageFlip) {
|
if (event.detail?.type === 'continue' && pendingRightPageFlip) {
|
||||||
tryStartPendingRightPageFlip('continue', { force: true });
|
tryStartPendingRightPageFlip('continue', { force: true });
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ const checks = [
|
|||||||
['sentence queue keeps current 3D page prep immediate while future lookahead yields cooperatively', /if \(!options\.immediate\) \{[\s\S]*requestIdleCallback[\s\S]*timeout: 80/.test(sentenceQueueSource) && /prefetchAhead\(maxLookahead = 6/.test(sentenceQueueSource)],
|
['sentence queue keeps current 3D page prep immediate while future lookahead yields cooperatively', /if \(!options\.immediate\) \{[\s\S]*requestIdleCallback[\s\S]*timeout: 80/.test(sentenceQueueSource) && /prefetchAhead\(maxLookahead = 6/.test(sentenceQueueSource)],
|
||||||
['pagination can prepare future spreads without activating visible spread', /preparePendingBlock\(block = \{\}, options = \{\}\)/.test(bookPaginationSource) && /options\.activate !== false/.test(bookPaginationSource) && /includeUnrenderedHistory/.test(bookPaginationSource)],
|
['pagination can prepare future spreads without activating visible spread', /preparePendingBlock\(block = \{\}, options = \{\}\)/.test(bookPaginationSource) && /options\.activate !== false/.test(bookPaginationSource) && /includeUnrenderedHistory/.test(bookPaginationSource)],
|
||||||
['pagination preserves active inline style tags for texture lines', /getActiveStyleTags/.test(bookPaginationSource) && /activeStyleTags/.test(bookPaginationSource) && /updateStyleTagStack/.test(bookPaginationSource)],
|
['pagination preserves active inline style tags for texture lines', /getActiveStyleTags/.test(bookPaginationSource) && /activeStyleTags/.test(bookPaginationSource) && /updateStyleTagStack/.test(bookPaginationSource)],
|
||||||
['texture renderer stores prepared reveal plans in the shared texture store', !/preparedRevealCache/.test(textureRendererSource) && /rememberPreparedRevealPlan/.test(webglPageCacheSource) && /takePreparedRevealPlan/.test(textureRendererSource) && /publishPreparedReveal/.test(textureRendererSource) && /hasPreparedRevealBlock/.test(textureRendererSource)],
|
['texture renderer stores prepared reveal plans in the shared texture store', !/preparedRevealCache/.test(textureRendererSource) && /rememberPreparedRevealPlan/.test(webglPageCacheSource) && /takePreparedRevealPlan/.test(textureRendererSource) && /publishPreparedReveal/.test(textureRendererSource) && !/hasPreparedRevealBlock/.test(textureRendererSource)],
|
||||||
['webgl page cache is loaded through module infrastructure', /webgl-page-cache-module\.js/.test(loaderSource) && /super\('webgl-page-cache'/.test(webglPageCacheSource) && /reportProgress\(20, 'Opening WebGL page texture cache'\)/.test(webglPageCacheSource)],
|
['webgl page cache is loaded through module infrastructure', /webgl-page-cache-module\.js/.test(loaderSource) && /super\('webgl-page-cache'/.test(webglPageCacheSource) && /reportProgress\(20, 'Opening WebGL page texture cache'\)/.test(webglPageCacheSource)],
|
||||||
['webgl page cache uses an isolated browser database without upgrading tts history state', /this\.dbName = 'webglPageTextureCacheDB'/.test(webglPageCacheSource) && /this\.dbVersion = 1/.test(webglPageCacheSource) && /this\.dbVersion = 3/.test(ttsFactorySource) && /this\.dbVersion = 3/.test(storyHistorySource) && !/webglPageTextureStore/.test(ttsFactorySource) && !/webglPageTextureStore/.test(storyHistorySource)],
|
['webgl page cache uses an isolated browser database without upgrading tts history state', /this\.dbName = 'webglPageTextureCacheDB'/.test(webglPageCacheSource) && /this\.dbVersion = 1/.test(webglPageCacheSource) && /this\.dbVersion = 3/.test(ttsFactorySource) && /this\.dbVersion = 3/.test(storyHistorySource) && !/webglPageTextureStore/.test(ttsFactorySource) && !/webglPageTextureStore/.test(storyHistorySource)],
|
||||||
['texture renderer hands completed page canvases to the single texture store without owning write queues', /webgl-page-cache/.test(textureRendererSource) && /cachePublishedPages/.test(textureRendererSource) && /storePageCanvas\(pageMeta, canvas, \{ persist: true, resident: true \}\)/.test(textureRendererSource) && !/schedulePageCacheWrite/.test(textureRendererSource) && !/pendingPageCacheWrites/.test(textureRendererSource)],
|
['texture renderer hands completed page canvases to the single texture store without owning write queues', /webgl-page-cache/.test(textureRendererSource) && /cachePublishedPages/.test(textureRendererSource) && /storePageCanvas\(pageMeta, canvas, \{ persist: true, resident: true \}\)/.test(textureRendererSource) && !/schedulePageCacheWrite/.test(textureRendererSource) && !/pendingPageCacheWrites/.test(textureRendererSource)],
|
||||||
@@ -159,7 +159,7 @@ const checks = [
|
|||||||
['webgl debug test hook awaits the same async page flip path', /startPageFlipForTest\(direction, options = \{\}\) \{[\s\S]*return startPageFlip\(direction, options\)/.test(source)],
|
['webgl debug test hook awaits the same async page flip path', /startPageFlipForTest\(direction, options = \{\}\) \{[\s\S]*return startPageFlip\(direction, options\)/.test(source)],
|
||||||
['webgl debug test hook can deterministically finish an active page flip', /advancePageFlipForTest\(elapsedMs = normalFlipDuration \+ 16\)/.test(source) && /updateActiveFlips\(targetNow\)/.test(source)],
|
['webgl debug test hook can deterministically finish an active page flip', /advancePageFlipForTest\(elapsedMs = normalFlipDuration \+ 16\)/.test(source) && /updateActiveFlips\(targetNow\)/.test(source)],
|
||||||
['sentence queue skips duplicate current-item 3D book presentation when reveal is cached', /isWebGLBookPresentationPrepared/.test(sentenceQueueSource) && /if \(!this\.isWebGLBookPresentationPrepared\(sentence\)\) \{\s*await this\.prefetchWebGLBookPresentation/.test(sentenceQueueSource) && /sentence\.webglBookPresentation = \{\s*prepared: true/.test(sentenceQueueSource)],
|
['sentence queue skips duplicate current-item 3D book presentation when reveal is cached', /isWebGLBookPresentationPrepared/.test(sentenceQueueSource) && /if \(!this\.isWebGLBookPresentationPrepared\(sentence\)\) \{\s*await this\.prefetchWebGLBookPresentation/.test(sentenceQueueSource) && /sentence\.webglBookPresentation = \{\s*prepared: true/.test(sentenceQueueSource)],
|
||||||
['3D overflow reveal waits for timeline-owned page flip before activating future spread', /requiresSpreadTransition\(segment\)/.test(bookPlaybackTimelineSource) && /this\.requestPageFlip\(1, \{[\s\S]*targetSpread: segment\.targetSpreadIndex/.test(bookPlaybackTimelineSource) && /this\.activatePreparedSegment\(segment, sentence\)/.test(bookPlaybackTimelineSource) && /webgl-book:request-page-flip/.test(bookPlaybackTimelineSource) && /const targetSpread = Number\.isFinite\(Number\(detail\.targetSpread\)\)/.test(source) && /startPageFlip\(direction, \{[\s\S]*targetSpread/.test(source)],
|
['3D overflow reveal waits for an explicit timeline flip plan before activating future spread', /requiresSpreadTransition\(segment\)/.test(bookPlaybackTimelineSource) && /this\.requestPageFlip\(1, \{[\s\S]*targetSpread: segment\.targetSpreadIndex/.test(bookPlaybackTimelineSource) && /this\.activatePreparedSegment\(segment, sentence\)/.test(bookPlaybackTimelineSource) && /BookLabDebug\?\.requestPageFlip/.test(bookPlaybackTimelineSource) && /requestPageFlip\(direction = 1, options = \{\}\) \{[\s\S]*return startPageFlip\(direction, options\)/.test(source)],
|
||||||
['texture renderer paints inline bold and italic styles', /getInlineStyleState/.test(textureRendererSource) && /updateInlineStyleState/.test(textureRendererSource) && /getCanvasFont/.test(textureRendererSource) && /segment\?\.style/.test(textureRendererSource)],
|
['texture renderer paints inline bold and italic styles', /getInlineStyleState/.test(textureRendererSource) && /updateInlineStyleState/.test(textureRendererSource) && /getCanvasFont/.test(textureRendererSource) && /segment\?\.style/.test(textureRendererSource)],
|
||||||
['webgl lab can preload page textures without swapping visible page material through texture store', /preparePageTexture\(side = 'left'/.test(webglPageCacheSource) && /takePreparedPageTexture\(side = 'left'/.test(webglPageCacheSource) && /renderer\.initTexture\(texture\)/.test(webglPageCacheSource) && /takePreparedPageTexture/.test(source) && !/const preparedPageTextures/.test(source)],
|
['webgl lab can preload page textures without swapping visible page material through texture store', /preparePageTexture\(side = 'left'/.test(webglPageCacheSource) && /takePreparedPageTexture\(side = 'left'/.test(webglPageCacheSource) && /renderer\.initTexture\(texture\)/.test(webglPageCacheSource) && /takePreparedPageTexture/.test(source) && !/const preparedPageTextures/.test(source)],
|
||||||
['webgl page text textures avoid mipmap generation', /function configurePageCanvasTexture/.test(source) && /texture\.minFilter = THREE\.LinearFilter/.test(source) && /texture\.generateMipmaps = false/.test(source)],
|
['webgl page text textures avoid mipmap generation', /function configurePageCanvasTexture/.test(source) && /texture\.minFilter = THREE\.LinearFilter/.test(source) && /texture\.generateMipmaps = false/.test(source)],
|
||||||
@@ -211,11 +211,13 @@ const checks = [
|
|||||||
['webgl reveal clock follows absolute playback time and continues across page flips', /activeRevealBlockStarts/.test(source) && /state\.visualElapsedMs = Math\.max\(0, now - state\.startedAt\)/.test(source) && !/Math\.min\(revealFrameDeltaMs, targetFrameDurationMs\)/.test(source) && /prewarmFlipTextures\(1, targetSpread\)/.test(source)],
|
['webgl reveal clock follows absolute playback time and continues across page flips', /activeRevealBlockStarts/.test(source) && /state\.visualElapsedMs = Math\.max\(0, now - state\.startedAt\)/.test(source) && !/Math\.min\(revealFrameDeltaMs, targetFrameDurationMs\)/.test(source) && /prewarmFlipTextures\(1, targetSpread\)/.test(source)],
|
||||||
['texture renderer preloads every spread touched by an active reveal block', /preloadAdditionalRevealSpreads/.test(textureRendererSource) && /spreadContainsBlock/.test(textureRendererSource) && /this\.drawSpread\(spread, \['left', 'right'\], \{ phase: 'prepare' \}\)/.test(textureRendererSource) && /this\.activeAnimations\.has\(id\)/.test(textureRendererSource)],
|
['texture renderer preloads every spread touched by an active reveal block', /preloadAdditionalRevealSpreads/.test(textureRendererSource) && /spreadContainsBlock/.test(textureRendererSource) && /this\.drawSpread\(spread, \['left', 'right'\], \{ phase: 'prepare' \}\)/.test(textureRendererSource) && /this\.activeAnimations\.has\(id\)/.test(textureRendererSource)],
|
||||||
['webgl visible spread state ignores future prepared publishes before flip', /spreadUpdate:deferred-future-unrendered/.test(source) && /incomingSpreadIndex > Math\.max\(0, Number\(bookPaginationState\.spreadIndex/.test(source) && /this\.drawSpread\(this\.currentSpread, \['left', 'right'\], \{ phase: 'prepare' \}\)/.test(textureRendererSource)],
|
['webgl visible spread state ignores future prepared publishes before flip', /spreadUpdate:deferred-future-unrendered/.test(source) && /incomingSpreadIndex > Math\.max\(0, Number\(bookPaginationState\.spreadIndex/.test(source) && /this\.drawSpread\(this\.currentSpread, \['left', 'right'\], \{ phase: 'prepare' \}\)/.test(textureRendererSource)],
|
||||||
['3D overflow reveal preloads target spread before forced page flip', /createRevealDetail\(sentence, previewSpread, 'prepare'\)/.test(bookPlaybackTimelineSource) && /this\.textureRenderer\.prepareRevealBlock\(revealDetail, \{ phase: 'prepare' \}\)/.test(bookPlaybackTimelineSource) && /this\.prewarmSegmentTextures\(segment\)/.test(bookPlaybackTimelineSource) && /reason: 'timeline-preplay-spread-transition'/.test(bookPlaybackTimelineSource)],
|
['3D overflow reveal preloads target spread before forced page flip', /createRevealDetail\(sentence, previewSpread, 'prepare'\)/.test(bookPlaybackTimelineSource) && /this\.textureRenderer\.prepareRevealBlock\(revealDetail, \{[\s\S]*phase: 'prepare'[\s\S]*publishEvent: false/.test(bookPlaybackTimelineSource) && /this\.prewarmSegmentTextures\(segment\)/.test(bookPlaybackTimelineSource) && /this\.assertSegmentReady\(segment, 'prepare'\)/.test(bookPlaybackTimelineSource) && /reason: 'timeline-preplay-spread-transition'/.test(bookPlaybackTimelineSource)],
|
||||||
['book playback timeline is loaded through module infrastructure', /book-playback-timeline-module\.js/.test(loaderSource) && /super\('book-playback-timeline'/.test(bookPlaybackTimelineSource) && /reportProgress\(100, 'Book playback timeline ready'\)/.test(bookPlaybackTimelineSource)],
|
['book playback timeline is loaded through module infrastructure', /book-playback-timeline-module\.js/.test(loaderSource) && /super\('book-playback-timeline'/.test(bookPlaybackTimelineSource) && /reportProgress\(100, 'Book playback timeline ready'\)/.test(bookPlaybackTimelineSource)],
|
||||||
['3D display playback is owned by book playback timeline', /book-playback-timeline/.test(uiDisplayHandlerSource) && /playWebGLBookSentence/.test(uiDisplayHandlerSource) && /timeline\.playSentence\(sentence\)/.test(uiDisplayHandlerSource) && /if \(useWebGLBookReveal\) \{[\s\S]*await this\.playWebGLBookSentence\(sentence\);[\s\S]*this\.markBlockRendered\(sentence\.blockId\);[\s\S]*return null;/.test(uiDisplayHandlerSource)],
|
['3D display playback is owned by book playback timeline', /book-playback-timeline/.test(uiDisplayHandlerSource) && /playWebGLBookSentence/.test(uiDisplayHandlerSource) && /timeline\.playSentence\(sentence\)/.test(uiDisplayHandlerSource) && /if \(useWebGLBookReveal\) \{[\s\S]*await this\.playWebGLBookSentence\(sentence\);[\s\S]*this\.markBlockRendered\(sentence\.blockId\);[\s\S]*return null;/.test(uiDisplayHandlerSource)],
|
||||||
['sentence queue lookahead prepares 3D book timeline segments', /book-playback-timeline/.test(sentenceQueueSource) && /bookPlaybackTimeline\.prepareSentence\(sentence/.test(sentenceQueueSource) && /timelineSegment: segment/.test(sentenceQueueSource)],
|
['sentence queue lookahead prepares 3D book timeline segments', /book-playback-timeline/.test(sentenceQueueSource) && /bookPlaybackTimeline\.prepareSentence\(sentence/.test(sentenceQueueSource) && /timelineSegment: segment/.test(sentenceQueueSource)],
|
||||||
['book playback timeline prewarms texture window before prepared playback and flips', /prewarmSegmentTextures/.test(bookPlaybackTimelineSource) && /pageCache\.prewarmNavigationWindow/.test(bookPlaybackTimelineSource) && /this\.prewarmSegmentTextures\(segment\)/.test(bookPlaybackTimelineSource) && /await this\.pageCache\?\.prewarmNavigationWindow/.test(bookPlaybackTimelineSource)],
|
['book playback timeline prewarms texture window before prepared playback and flips', /prewarmSegmentTextures/.test(bookPlaybackTimelineSource) && /pageCache\.prewarmNavigationWindow/.test(bookPlaybackTimelineSource) && /this\.prewarmSegmentTextures\(segment\)/.test(bookPlaybackTimelineSource) && /await this\.pageCache\?\.prewarmNavigationWindow/.test(bookPlaybackTimelineSource)],
|
||||||
|
['book playback timeline enforces resident page textures before prepared playback', /assertSegmentReady/.test(bookPlaybackTimelineSource) && /collectRequiredPageMetas/.test(bookPlaybackTimelineSource) && /this\.pageCache\.ensurePageTexture\(meta/.test(bookPlaybackTimelineSource) && /timeline-cache-readiness-failed/.test(bookPlaybackTimelineSource)],
|
||||||
|
['3D reveal start is controlled by the prepared timeline instead of texture events', /sentence\.webglRevealController = \(\) => this\.startRevealForSegment\(segment\)/.test(bookPlaybackTimelineSource) && /startPreparedRevealAnimation\?\.\(segment\.blockId, \{[\s\S]*publishEvent: false/.test(bookPlaybackTimelineSource) && /PlaybackCoordinator: WebGL playback requires a prepared timeline reveal controller/.test(playbackCoordinatorSource) && !/document\.dispatchEvent\(new CustomEvent\('book-texture:reveal-block'/.test(methodBody(playbackCoordinatorSource, 'scheduleWebGLReveal'))],
|
||||||
['webgl lab delegates right-page reveal commits to timeline owner', /BookPlaybackTimeline\?\.ownsPageFlipCommit === true/.test(source) && /handleRevealCommittedForPageFlip/.test(source)],
|
['webgl lab delegates right-page reveal commits to timeline owner', /BookPlaybackTimeline\?\.ownsPageFlipCommit === true/.test(source) && /handleRevealCommittedForPageFlip/.test(source)],
|
||||||
['webgl reveal clock explicitly freezes during physical flips', /pageRevealFreezeAt/.test(source) && /state\.startedAt \+= frozenMs/.test(source) && /activeRevealBlockStarts\.set\(blockId, Number\(value\) \+ frozenMs\)/.test(source)],
|
['webgl reveal clock explicitly freezes during physical flips', /pageRevealFreezeAt/.test(source) && /state\.startedAt \+= frozenMs/.test(source) && /activeRevealBlockStarts\.set\(blockId, Number\(value\) \+ frozenMs\)/.test(source)],
|
||||||
['book playback timeline waits for right reveal only when current block is on right page', /getBlockRevealSides/.test(bookPlaybackTimelineSource) && /revealSides\.includes\('right'\) && this\.requiresRightPageFlipAfterReveal/.test(bookPlaybackTimelineSource) && /visual-completion:no-right-flip-wait/.test(bookPlaybackTimelineSource)],
|
['book playback timeline waits for right reveal only when current block is on right page', /getBlockRevealSides/.test(bookPlaybackTimelineSource) && /revealSides\.includes\('right'\) && this\.requiresRightPageFlipAfterReveal/.test(bookPlaybackTimelineSource) && /visual-completion:no-right-flip-wait/.test(bookPlaybackTimelineSource)],
|
||||||
|
|||||||
Reference in New Issue
Block a user