From ce815c2b407082a12b7d31acf18d7ee0c3c7175e Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Wed, 22 Apr 2026 16:25:30 -0700 Subject: [PATCH 1/2] force From d0957605d33010fce1cff0a219bc6af6ec3e1e7f Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Tue, 21 Apr 2026 03:32:25 -0700 Subject: [PATCH 2/2] refactor(shader-transitions): extract DEFAULT_DURATION and DEFAULT_EASE constants The three fallback sites in hyper-shader.ts (metadata write, browser/render mode, and engine mode) had drifted apart: the metadata path used 1s/'none' while the actual rendering used 0.7s/'power2.inOut'. This meant a transition without an explicit duration/ease would render at 0.7s but tell the engine it was 1s, throwing off the producer's compositing window planning. Extract DEFAULT_DURATION (0.7) and DEFAULT_EASE ('power2.inOut') as module-level constants and use them at all three sites so a missing duration/ease produces identical behavior in preview, the engine's deterministic seek path, and the metadata the producer reads. The explicit `ease: 'none'` on the timeline-length anchor tweens elsewhere in the file is intentional (those are linear interpolators driving the shader's progress uniform) and is left unchanged. --- .../shader-transitions/src/hyper-shader.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/shader-transitions/src/hyper-shader.ts b/packages/shader-transitions/src/hyper-shader.ts index c393d4c04..0561d4e57 100644 --- a/packages/shader-transitions/src/hyper-shader.ts +++ b/packages/shader-transitions/src/hyper-shader.ts @@ -61,6 +61,14 @@ interface TransState { progress: number; } +// Defaults for transition duration/ease. Used by every fallback site in this +// file — meta-write, browser/render mode, and engine mode — so a transition +// without explicit `duration`/`ease` plays the same length and curve in +// preview, the engine's deterministic seek path, and the metadata the +// producer reads to plan compositing. +const DEFAULT_DURATION = 0.7; +const DEFAULT_EASE = "power2.inOut"; + function parseHex(hex: string): [number, number, number] { const h = hex.replace("#", ""); if (h.length < 6) return [0.5, 0.5, 0.5]; @@ -130,9 +138,9 @@ export function init(config: HyperShaderConfig): GsapTimeline { if (hfWin.__hf) { hfWin.__hf.transitions = transitions.map((t: TransitionConfig, i: number) => ({ time: t.time, - duration: t.duration ?? 1, + duration: t.duration ?? DEFAULT_DURATION, shader: t.shader, - ease: t.ease ?? "none", + ease: t.ease ?? DEFAULT_EASE, fromScene: scenes[i] ?? "", toScene: scenes[i + 1] ?? "", })); @@ -238,8 +246,8 @@ export function init(config: HyperShaderConfig): GsapTimeline { const prog = programs.get(t.shader); if (!prog) continue; - const dur = t.duration ?? 0.7; - const ease = t.ease ?? "power2.inOut"; + const dur = t.duration ?? DEFAULT_DURATION; + const ease = t.ease ?? DEFAULT_EASE; const T = t.time; // Pause timeline during async capture to prevent the progress tween @@ -361,7 +369,7 @@ function initEngineMode( const toId = scenes[i + 1]; if (!fromId || !toId) continue; - const dur = t.duration ?? 0.7; + const dur = t.duration ?? DEFAULT_DURATION; const T = t.time; // During the transition both scenes need to be visible so the engine