void PickCandidate(ref PackCandidate pc)
            {
                var box = this.input[this._toPack[pc.packIndex]];

                for (
                    pc.skylineIndex = 0;
                    pc.skylineIndex < this.skylines.Count;
                    ++pc.skylineIndex
                    )
                {
                    var skyline = this.skylines[pc.skylineIndex];
                    if (box.h + skyline.h > bin.h)
                    {
                        continue;
                    }
                    // if (box.h + skyline.h - this.lowestSky > this.spreadFactor) {
                    //     continue;
                    // }
                    pc.spreadSatisfied = (box.h + skyline.h - this.lowestSky) <= this.spreadFactor;
                    pc.h = skyline.h;

                    if (skyline.rFit >= box.w)
                    {
                        pc.crossingCount = 1;
                        pc.fitNum        = 0;
                        if (box.w == skyline.w)
                        {
                            pc.fitNum += 1;
                        }
                        if (box.h + skyline.h == bin.h)
                        {
                            pc.fitNum += 1;
                        }
                        if (pc.skylineIndex > 0 && box.h == (this.skylines[pc.skylineIndex - 1].h - skyline.h))
                        {
                            pc.fitNum += 1;
                        }

                        pc.localWaste = 0;
                        for (
                            var widthLeft = box.w - skyline.w;
                            widthLeft > 0;
                            pc.crossingCount++
                            )
                        {
                            var nsi = pc.crossingCount + pc.skylineIndex;
                            UnityEngine.Debug.Assert(nsi < this.skylines.Count);
                            var ns = this.skylines[nsi];
                            UnityEngine.Debug.Assert(skyline.h > ns.h);
                            pc.localWaste += (ns.w) * (skyline.h - ns.h);
                            widthLeft     -= ns.w;
                        }

                        var mh = pc.h + box.h;
                        // left
                        for (int li = pc.skylineIndex - 1, lw = 0, ml = 0; li >= 0; li--)
                        {
                            var left = skylines[li];
                            if (left.h > mh)
                            {
                                if (ml < this.hSpread)
                                {
                                    // if (true) {
                                    pc.localWaste += lw;
                                }
                                break;
                            }
                            ml += left.w;
                            lw += (mh - left.h) * left.w;
                        }


                        { // right
                            var crossIdx = pc.crossingCount + pc.skylineIndex - 1;
                            var cross    = this.skylines[crossIdx];
                            var mr       = (cross.w + cross.x - skyline.x - box.w);
                            UnityEngine.Debug.Assert(mr >= 0);
                            var rw = (mh - cross.h) * mr;
                            for (int ri = crossIdx + 1; ri < this.skylines.Count; ++ri)
                            {
                                var right = skylines[ri];
                                if (right.h > mh)
                                {
                                    if (mr < this.hSpread)
                                    {
                                        pc.localWaste += rw;
                                    }
                                    break;
                                }
                                mr += right.w;
                                rw += (mh - right.h) * right.w;
                            }
                        }

                        // upper
                        if (bin.h - mh < this.minHeight)
                        {
                            pc.localWaste += (bin.h - mh) * box.w;
                        }
                        packCandidates.Add(pc);
                    }
                    if (skyline.lFit >= box.w)
                    {
                        pc.crossingCount = -1;
                        pc.fitNum        = 0;
                        if (box.w == skyline.w)
                        {
                            pc.fitNum += 1;
                        }
                        if (box.h + skyline.h == bin.h)
                        {
                            pc.fitNum += 1;
                        }
                        if (pc.skylineIndex < (this.skylines.Count - 1) && box.h == (this.skylines[pc.skylineIndex + 1].h - skyline.h))
                        {
                            pc.fitNum += 1;
                        }

                        pc.localWaste = 0;
                        var widthLeft = box.w - skyline.w;

                        while (widthLeft > 0)
                        {
                            var nsi = pc.crossingCount + pc.skylineIndex;
                            UnityEngine.Debug.Assert(nsi >= 0);
                            var ns = this.skylines[nsi];
                            UnityEngine.Debug.Assert(skyline.h > ns.h);
                            pc.localWaste += (ns.w) * (skyline.h - ns.h);
                            widthLeft     -= ns.w;
                            pc.crossingCount--;
                        }

                        var mh = pc.h + box.h;
                        // right
                        for (int ri = pc.skylineIndex + 1, rw = 0, mr = 0; ri < skylines.Count; ++ri)
                        {
                            var right = skylines[ri];
                            if (right.h > mh)
                            {
                                if (mr < this.hSpread)
                                {
                                    pc.localWaste += rw;
                                }
                                break;
                            }
                            mr += right.w;
                            rw += (mh - right.h) * right.w;
                        }

                        {// left
                            var crossIdx = pc.crossingCount + pc.skylineIndex + 1;
                            var cross    = this.skylines[crossIdx];
                            var ml       = (skyline.x + skyline.w - box.w - cross.x);
                            UnityEngine.Debug.Assert(ml >= 0);
                            var lw = (mh - cross.h) * ml;
                            for (int li = crossIdx - 1; li >= 0; --li)
                            {
                                var left = skylines[li];
                                if (left.h > mh)
                                {
                                    if (ml < this.hSpread)
                                    {
                                        pc.localWaste += lw;
                                    }
                                    break;
                                }
                                ml += left.w;
                                lw += (mh - left.h) * left.w;
                            }
                        }
                        // upper
                        if (bin.h - mh < this.minHeight)
                        {
                            pc.localWaste += (bin.h - mh) * box.w;
                        }
                        packCandidates.Add(pc);
                    }
                }
            }
            void UpdateSkylines(PackCandidate candidate, Box box)
            {
                UnityEngine.Debug.Assert(candidate.skylineIndex >= 0);
                UnityEngine.Debug.Assert(candidate.skylineIndex < this.skylines.Count);
                this.backBuffer.EnsureCap(this.skylines.Capacity);
                this.backBuffer.Clear();
                if (candidate.crossingCount > 0)
                {
                    for (int i = 0; i < candidate.skylineIndex - 1; ++i)
                    {
                        this.backBuffer.Add(this.skylines[i]);
                    }
                    var skyline = this.skylines[candidate.skylineIndex];
                    var oldX    = skyline.x;
                    skyline.h += box.h;
                    skyline.w  = box.w;
                    if (candidate.skylineIndex - 1 >= 0)
                    {
                        if (this.skylines[candidate.skylineIndex - 1].h != skyline.h)
                        {
                            this.backBuffer.Add(this.skylines[candidate.skylineIndex - 1]);
                        }
                        else
                        {
                            skyline.x  = this.skylines[candidate.skylineIndex - 1].x;
                            skyline.w += oldX - skyline.x;
                        }
                    }

                    if (this.backBuffer.Count > 0)
                    {
                        var last = this.backBuffer[this.backBuffer.Count - 1];
                    }
                    var crossedIndex = candidate.skylineIndex + candidate.crossingCount - 1;
                    var crossedLast  = this.skylines[crossedIndex];
                    var crossedEnd   = crossedLast.x + crossedLast.w;
                    var newEnd       = oldX + box.w;
                    if (crossedEnd != newEnd)
                    {
                        UnityEngine.Debug.Assert(crossedEnd > newEnd);
                        UnityEngine.Debug.Assert(crossedLast.h < skyline.h);

                        this.backBuffer.Add(skyline);
                        crossedLast.w = crossedLast.w - (newEnd - crossedLast.x);
                        crossedLast.x = newEnd;

                        this.backBuffer.Add(crossedLast);
                        if (crossedIndex + 1 < this.skylines.Count)
                        {
                            this.backBuffer.Add(this.skylines[crossedIndex + 1]);
                        }
                    }
                    else
                    {
                        if (crossedIndex + 1 < this.skylines.Count)
                        {
                            var next = this.skylines[crossedIndex + 1];
                            if (next.h == skyline.h)
                            {
                                skyline.w += next.w;
                                this.backBuffer.Add(skyline);
                            }
                            else
                            {
                                this.backBuffer.Add(skyline);
                                this.backBuffer.Add(next);
                            }
                        }
                        else
                        {
                            this.backBuffer.Add(skyline);
                        }
                    }
                    for (int i = crossedIndex + 2; i < this.skylines.Count; ++i)
                    {
                        this.backBuffer.Add(this.skylines[i]);
                    }
                }
                else
                {
                    UnityEngine.Debug.Assert(candidate.crossingCount < 0);
                    var oldIdx = candidate.skylineIndex + candidate.crossingCount; // + 1 - 1
                    for (int i = 0; i < oldIdx; ++i)
                    {
                        this.backBuffer.Add(this.skylines[i]);
                    }

                    var skyline = this.skylines[candidate.skylineIndex];
                    var oldEnd  = skyline.x + skyline.w;
                    skyline.h += box.h;
                    skyline.w  = box.w;
                    skyline.x  = oldEnd - skyline.w;
                    if (oldIdx >= 0)
                    {
                        var old = this.skylines[oldIdx];
                        if (skyline.x == old.x + old.w && old.h == skyline.h)
                        {
                            skyline.x  = old.x;
                            skyline.w += old.w;
                        }
                        else
                        {
                            this.backBuffer.Add(old);
                        }
                    }
                    var crossedIdx = oldIdx + 1;
                    var crossed    = this.skylines[crossedIdx];
                    if (skyline.x != crossed.x)
                    {
                        crossed.w = skyline.x - crossed.x;
                        this.backBuffer.Add(crossed);
                    }
                    var nextIdx = candidate.skylineIndex + 1;
                    if (nextIdx < this.skylines.Count)
                    {
                        var next = this.skylines[nextIdx];
                        if (next.h == skyline.h)
                        {
                            skyline.w += next.w;
                            this.backBuffer.Add(skyline);
                        }
                        else
                        {
                            this.backBuffer.Add(skyline);
                            this.backBuffer.Add(next);
                        }
                    }
                    else
                    {
                        this.backBuffer.Add(skyline);
                    }

                    for (int i = nextIdx + 1; i < this.skylines.Count; ++i)
                    {
                        this.backBuffer.Add(this.skylines[i]);
                    }
                }

                // min gap promotion
                this.skylines.Clear();
                for (int i = 0; i < this.backBuffer.Count; ++i)
                {
                    var line = this.backBuffer[i];
                    if (line.w >= this.packDump)
                    {
                        this.skylines.Add(line);
                        continue;
                    }

                    int lh;
                    if (this.skylines.Count > 0)
                    {
                        lh = this.skylines[this.skylines.Count - 1].h;
                    }
                    else
                    {
                        lh = bin.h;
                    }
                    int rh;
                    if (i + 1 < this.backBuffer.Count)
                    {
                        rh = this.backBuffer[i + 1].h;
                    }
                    else
                    {
                        rh = bin.h;
                    }

                    if (lh < line.h || rh < line.h)
                    {
                        this.skylines.Add(line);
                        continue;
                    }

                    if (lh < rh && this.skylines.Count > 0)
                    {
                        this.skylines.Get(this.skylines.Count - 1).w += line.w;
                        continue;
                    }

                    if (i + 1 < this.backBuffer.Count)
                    {
                        this.backBuffer.Get(i + 1).w += line.w;
                        this.backBuffer.Get(i + 1).x  = line.x;
                        continue;
                    }
                    else
                    {
                        this.skylines.Add(line); // or should we just dump it
                    }
                }

                // var tmp = this.skylines;
                // this.skylines = this.backBuffer;
                // this.backBuffer = tmp;

                // fix lfit/rfit.
                // TODO: optimize this shouldn't have been an O(n^2) op
                this.lowestSky  = bin.h + 1;
                this.highestSky = -1;
                for (int i = 0; i < this.skylines.Count; ++i)
                {
                    var h = this.skylines.Get(i).h;
                    if (h > this.highestSky)
                    {
                        this.highestSky = h;
                    }
                    if (h < this.lowestSky)
                    {
                        this.lowestSky = h;
                    }
                    this.skylines.Get(i).rFit = this.skylines.Get(i).lFit = this.skylines.Get(i).w;
                    for (int j = i - 1; j >= 0; --j)
                    {
                        if (this.skylines.Get(j).h > this.skylines.Get(i).h)
                        {
                            break;
                        }
                        this.skylines.Get(i).lFit += this.skylines.Get(j).w;
                    }
                    for (int j = i + 1; j < this.skylines.Count; ++j)
                    {
                        if (this.skylines.Get(j).h > this.skylines.Get(i).h)
                        {
                            break;
                        }
                        this.skylines.Get(i).rFit += this.skylines.Get(j).w;
                    }
                }
            }