From d7b3824e1ee6a2ee6459d5dfa5ede48b31797e4c Mon Sep 17 00:00:00 2001 From: Mikkeli Date: Wed, 30 Sep 2026 01:29:13 +0900 Subject: [PATCH] fix(274): end the fade-out where the black hold starts FADE_OUT_POST holds full black over the last stretch of the video, but the ramp still aimed to finish at lastFrameS - FADE_OUT_S, so it completed inside the hold and the two overlapped. The ramp now runs from lastFrameS - FADE_OUT_POST - FADE_OUT_S to lastFrameS - FADE_OUT_POST, giving FADE_OUT_S of fade followed by FADE_OUT_POST of black. Verified: with 0.4 / 0.3 / 0.2 / 0.4 the picture is clear from 0.70 s to 618.67 s, and the rendered file is pure black for the first and last 13 frames at 30 fps (25 at 60). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PLt25nCVMsoroKALYUBu6u --- src/song-274/fade.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/song-274/fade.ts b/src/song-274/fade.ts index e1423d5..e9d14e1 100644 --- a/src/song-274/fade.ts +++ b/src/song-274/fade.ts @@ -5,6 +5,8 @@ /** Seconds of complete black at start */ export const FADE_IN_PRE = 0.4; +/** Seconds of complete black at end */ +export const FADE_OUT_POST = 0.4; /** Seconds from black to the picture at the start. 0 disables the fade-in. */ export const FADE_IN_S = 0.3; /** Seconds from the picture to black at the end. 0 disables the fade-out. */ @@ -19,6 +21,8 @@ const clamp01 = (v: number) => Math.min(1, Math.max(0, v)); */ export const blackOpacity = (t: number, lastFrameS: number): number => { const fadeIn = (t < FADE_IN_PRE) ? 1 : (FADE_IN_S > 0 ? 1 - clamp01((t - FADE_IN_PRE) / FADE_IN_S) : 0); - const fadeOut = FADE_OUT_S > 0 ? clamp01((t - (lastFrameS - FADE_OUT_S)) / FADE_OUT_S) : 0; + // Black from here to the last frame; the fade-out ramp ends exactly here. + const blackFromS = lastFrameS - FADE_OUT_POST; + const fadeOut = (t >= blackFromS) ? 1 : (FADE_OUT_S > 0 ? clamp01((t - (blackFromS - FADE_OUT_S)) / FADE_OUT_S) : 0); return Math.max(fadeIn, fadeOut); };