sclass G22CriticalPixelSearch is Steppable {
  // input
  settable IG22MasksHolder<?> masksHolder;
  settable bool keepAllEntries;
  
  // internal
  int w, h;
  ItIt<Pt> pixelIterator;
  
  // output
  new Best<Pt> bestPixel;
  Entry bestEntry;
  Pt lastPixelLookedAt;
  Entry lastEntry;
  Entry[] pixelEntries;
  float[] pixelScores;
  
  *() {}
  *(IG22MasksHolder *masksHolder) {}
  
  class Entry {
    Pt pixel;
    // child mask holders for pixel dark or pixel bright
    //IG22MasksHolder darkChild, brightChild;
    L<IG22Mask> darkMasks, brightMasks;
    gettable IG22MasksHolder darkHolder;
    gettable IG22MasksHolder brightHolder;
    
    toString {
      ret commaCombine(
        "Split at " + pixel,
        "dark=" + darkHolder.renderCountAndCertainty(),
        "bright=" + brightHolder.renderCountAndCertainty());
    }
  }
  
  public bool step() {
    if (w == 0) {
      if (!masksHolder.canSplit()) false;
      
      meta-for w also as h { w = masksHolder.maskSize().w(); }
      pixelEntries = new Entry[w*h];
      pixelScores = new float[w*h];
      arrayfill(pixelScores, -1);
      pixelIterator = new WeightlessShuffledIterator<Pt>(pixelsInImage(w, h));
    }
    
    if (!pixelIterator.hasNext()) false;
    lastPixelLookedAt = pixelIterator.next();
    testPixel(lastPixelLookedAt);
    true;
  }
  
  int idx(Pt p) { ret p.y*w+p.x; }
  
  void testPixel(Pt p) {
    double score;
    Entry e = null;
    
    // Find out if pixel is the same in all masks
    float ghostBrightness = masksHolder.ghost().getFloatPixel(p);
    if (ghostBrightness == 0 || ghostBrightness == 1)
      // Pixel is determined, not usable for splitting
      score = -2;
    else {
      // Pixel is not determined - let's make the 2 subsets
      
      e = new Entry;
      e.pixel = p;
      lastEntry = e;
      if (keepAllEntries)
        pixelEntries[idx(p)] = e;
      /*Pair<L<? extends IG22Mask>>*/var pair = filterAntiFilter(masksHolder.masks(),
        mask -> !mask.image().getBoolPixel(p));
      e.darkMasks = (L) pair.a;
      e.brightMasks = (L) pair.b;
      e.darkHolder = new G22HashedMasks(e.darkMasks);
      e.brightHolder = new G22HashedMasks(e.brightMasks);

      score = scoreEntry(e);
    }
    
    pixelScores[idx(p)] = (float) score;
    if (score > 0 && bestPixel.put(p, score))
      bestEntry = e;
  }
  
  swappable double scoreEntry(Entry e) {
    double c1 = e.darkHolder().certainty();
    double c2 = e.brightHolder().certainty();
    // ret avg(c1, c2);
    ret min(c1, c2);
  }
  
  double bestScore() {
    ret bestPixel.score();
  }
  
  BWImage scoreImage() {
    double min = masksHolder.certainty();
    double max = bestPixel.score();
    ret bwImageFromFunction(w, h,
      (x, y) -> transformToZeroToOne(pixelScores[y*w+x], max, min));
  }
  
  G22PixelSplitMasks splitMask() {
  }
}