From d92ce07bb3b99f6efbf08e9786502a708fb8ef8c Mon Sep 17 00:00:00 2001 From: "R.Eugenio" Date: Sat, 3 Jan 2026 14:30:11 +0100 Subject: [PATCH] fix(animation): ColorTransition epsilon check ANTES del lerp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El check anterior estaba DESPUÉS del lerpU8, causando loop infinito: - lerpU8 trunca incrementos <1, current no cambia - dr = |current - target| sigue siendo > 1 (ej: 10) - return true → pide más frames - Repeat forever Nuevo algoritmo: 1. Calcular diferencia PRIMERO 2. Si diferencia <= 2 en todos los canales → snap directo 3. Solo hacer lerp si diferencia > 2 Umbral aumentado de 1 a 2 para mayor margen de seguridad. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/render/animation.zig | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/render/animation.zig b/src/render/animation.zig index dd5205b..ddc9c5f 100644 --- a/src/render/animation.zig +++ b/src/render/animation.zig @@ -719,13 +719,22 @@ pub const ColorTransition = struct { return true; } - // Already at target? - if (self.current.r == target.r and - self.current.g == target.g and - self.current.b == target.b and - self.current.a == target.a) - { - return false; + // EPSILON CHECK PRIMERO: Si estamos "suficientemente cerca", snap directo + // Esto evita el problema de lerpU8 truncando incrementos <1 + const dr = @abs(@as(i16, self.current.r) - @as(i16, target.r)); + const dg = @abs(@as(i16, self.current.g) - @as(i16, target.g)); + const db = @abs(@as(i16, self.current.b) - @as(i16, target.b)); + const da = @abs(@as(i16, self.current.a) - @as(i16, target.a)); + + // Umbral pequeño (<=2) para garantizar convergencia sin saltos visibles + if (dr <= 2 and dg <= 2 and db <= 2 and da <= 2) { + if (self.current.r != target.r or self.current.g != target.g or + self.current.b != target.b or self.current.a != target.a) + { + self.current = target; // Snap final + return true; // Un último frame para el snap + } + return false; // Ya en target, animación terminada } // Calculate interpolation factor (chase towards target) @@ -739,19 +748,6 @@ pub const ColorTransition = struct { .a = lerpU8(self.current.a, target.a, t), }; - // EPSILON FIX: Forzar convergencia cuando estamos "suficientemente cerca" - // Sin esto, lerpU8 trunca incrementos <1 y la animación nunca termina. - // Umbral de 1 es imperceptible al ojo pero garantiza convergencia. - const dr = @abs(@as(i16, self.current.r) - @as(i16, target.r)); - const dg = @abs(@as(i16, self.current.g) - @as(i16, target.g)); - const db = @abs(@as(i16, self.current.b) - @as(i16, target.b)); - const da = @abs(@as(i16, self.current.a) - @as(i16, target.a)); - - if (dr <= 1 and dg <= 1 and db <= 1 and da <= 1) { - self.current = target; // Snap al target - return false; // Animación terminada - } - return true; }