Solution (source code)

= Solution

Keep a buffered first match and a flag recording whether the initial prefix still needs to be skipped:

``
class DropUntil[T] private (
    p: T => Boolean,
    iter: Iter[T],
    private var cached: Option[T],
    private var seeking: Boolean) extends Iter[T] {

  def this(p: T => Boolean, iter: Iter[T]) =
    this(p, iter, None, true)

  private def prepare(): Unit = {
    if (seeking) {
      while (cached.isEmpty && iter.hasNext) {
        val x = iter.next()
        if (p(x)) cached = Some(x)
      }
      seeking = false
    }
  }

  def hasNext: Boolean = {
    prepare()
    cached.nonEmpty || iter.hasNext
  }

  def next(): T = {
    prepare()
    cached match {
      case Some(x) => cached = None; x
      case None =>
        if (!iter.hasNext) throw new NoSuchElementException("DropUntil.next")
        iter.next()
    }
  }

  def copy(): Iter[T] = {
    prepare()
    new DropUntil[T](p, iter.copy(), cached, false)
  }
}
``

While seeking, `prepare` drops exactly the rejected prefix and caches the first accepted value, including a possible null payload as `Some(null)`. Afterwards the abstract remainder is the cache, if any, followed by the underlying remainder; later values are returned even when they fail `p`. Repeated `hasNext` calls do not lose the cached value.

The <iterator snapshot> copies the underlying cursor and the cached value, then marks the initial search complete. It does not reconstruct a fresh two-argument `DropUntil`, which could incorrectly discard later values. Resolving the initial search before copying also avoids having two snapshots independently rerun a privately stateful predicate. The wrapper owns consumption of the supplied source cursor. If that source is infinite and never produces an accepted value, seeking cannot finish. This is a <prefix-dropping iterator>, rather than a repeatedly <demand-driven filtering iterator>.