-- Smoothstep for a gentle ramp (0..1 -> 0..1)
function CubeLifeRenderer:_ease01(t)
    t = (t < 0) and 0 or ((t > 1) and 1 or t)
    return t * t * (3 - 2 * t)
end

-- Mix any color toward black; amt is 0..1 of *how much* to darken
function CubeLifeRenderer:_mixToBlack(c, amt)
    if amt <= 0 then return c end
    if amt > 1  then amt = 1 end
    -- keep alpha unchanged
    return color(c.r * (1 - amt), c.g * (1 - amt), c.b * (1 - amt), c.a)
end

-- Replace your _colorForCell with this version
function CubeLifeRenderer:_colorForCell(cell)
    if cell.alive ~= 1 then return self.deadC end
    local fn  = self._palette or ageToColor
    local col = fn(cell.age or 0)
    
    -- Darken gently as the static neighborhood ages toward decay
    local T = self.life and self.life.decayTurns or -1
    if T and T >= 0 then
        local ticks = cell.staticTicks or 0
        if ticks > 0 then
            -- fraction of the way to decay
            local frac = ticks / math.max(1, T)
            -- smooth it so it starts subtle and ramps late
            frac = self:_ease01(frac)
            -- cap the darkening so it never looks fully black while alive
            -- (e.g., at most 55% of the base color removed)
            local maxDarken = 0.55
            local amt = maxDarken * frac
            col = self:_mixToBlack(col, amt)
        end
    end
    
    return col
end