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; }