refactor(shader-transitions): extract DEFAULT_DURATION and DEFAULT_EASE constants (#367)

## Summary

Extract `DEFAULT_DURATION = 0.7` and `DEFAULT_EASE = "power2.inOut"` as shared constants in `hyper-shader.ts` and apply them at all three fallback sites (metadata write, browser/render mode, engine mode).

## Why

`Chunk 2` of `plans/hdr-followups.md`. The three fallback sites had drifted apart: the metadata path used `1s` / `"none"` while the actual rendering used `0.7s` / `"power2.inOut"`. A transition that omitted `duration`/`ease` would render at 0.7 s but tell the producer it was 1 s, throwing off the producer's compositing window planning and producing a visible ~0.3 s brightness dropout.

This is a small, high-value correctness fix that runs before the larger Chunk 1 / Chunk 4 work.

## What changed

- New module-level `DEFAULT_DURATION` and `DEFAULT_EASE` constants in `packages/shader-transitions/src/hyper-shader.ts`.
- All three fallback call sites (metadata, browser, engine) now use the constants.
- 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.

## Test plan

- [x] Render a composition with a transition that omits `duration` and `ease` — no brightness dip in the last ~0.3 s of the transition.
- [x] Preview (browser mode) and render (engine mode) produce matching blending curves.
- [x] Render with explicit `duration: 1.5` still works (constants are fallbacks only).

## Stack

Chunk 2 of `plans/hdr-followups.md`. Lands ahead of Chunk 1 (opacity) per the suggested merge order.
This commit is contained in:
Vance Ingalls
2026-04-22 17:02:42 -07:00
committed by GitHub
parent 5de5df7fbb
commit 9512744c2e
@@ -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