Both are happening at the same time in both examples.
Unquoting is the process of substituting a Tree somewhere into the structure of another Tree (like interpolating). In this example, ints isn't exactly a Tree, but there exists a Liftable[List[T]] that allows us to unquote a List[T] into a Tree, as if it were a Tree (ie. the Liftable tells the compiler how to transform the literal List[Int] here into a Tree so that it may be substituted).
To quote the documentation:
Unquote splicing is a way to unquote a variable number of elements.
Here, the variable number of elements would be the elements in the List we want to unquote. If we did q"f($ints)", then we would simply be unquoting ints as a single argument of f. But perhaps we want to apply repeated parameters to f instead. For that we use unquote splicing.
q"f(..$ints) // Using `..` means we get f(1, 2, 3) instead of f(List(1, 2, 3))
Again, the documentation says it best, really:
Dots near unquotee annotate degree of flattening and are also called splicing rank. ..$ expects argument to be an Iterable[Tree] and ...$ expects Iterable[Iterable[Tree]].
So lifting allows us to unquote a List[T] into the tree f(x) as if it were an Iterable[Tree], and unquote splicing allows us to unquote the variable number of elements the List[T] contained as multiple arguments for f.
Here are the different relevant combinations:
val listTree = q"scala.collection.immutable.List(1, 2, 3)"
val treeList = List(q"1", q"2", q"3")
val literalList = List(1, 2, 3)
scala> q"f($listTree)" // plain unquoting from another Tree
res6: reflect.runtime.universe.Tree = f(scala.collection.immutable.List(1, 2, 3))
scala> q"f($literalList)" // unquoting from lifting
res7: reflect.runtime.universe.Tree = f(scala.collection.immutable.List(1, 2, 3))
scala> q"f(..$treeList)" // plain unquote splicing
res8: reflect.runtime.universe.Tree = f(1, 2, 3)
scala> q"f(..$literalList)" // unquote splicing and lifting
res9: reflect.runtime.universe.Tree = f(1, 2, 3)