public static unsafe bool Hit(this BvhNode n, Ray r, float tMin, float tMax,
#if FULL_DIAGNOSTICS
                                      ref AccumulateJob.Diagnostics diagnostics,
#endif
                                      out HitRecord rec)
        {
            rec = default;

            if (!n.Bounds.Hit(r, tMin, tMax))
            {
                return(false);
            }

#if FULL_DIAGNOSTICS
            diagnostics.BoundsHitCount++;
#endif

            if (n.IsLeaf)
            {
#if FULL_DIAGNOSTICS
                diagnostics.CandidateCount++;
#endif
                return(n.Content.Hit(r, tMin, tMax, out rec));
            }

            bool hitLeft  = n.Left->Hit(r, tMin, tMax, out HitRecord leftRecord);
            bool hitRight = n.Right->Hit(r, tMin, tMax, out HitRecord rightRecord);

            if (!hitLeft && !hitRight)
            {
                return(false);
            }

            if (hitLeft && hitRight)
            {
                rec = leftRecord.Distance < rightRecord.Distance ? leftRecord : rightRecord;
                return(true);
            }

            if (hitLeft)
            {
                rec = leftRecord;
                return(true);
            }

            rec = rightRecord;
            return(true);
        }
        public static unsafe bool Hit(this BvhNode n, NativeArray <Entity> entities, Ray r, float tMin, float tMax,
                                      ref Random rng, AccumulateJob.WorkingArea wa,
#if FULL_DIAGNOSTICS
                                      ref Diagnostics diagnostics,
#endif
                                      out HitRecord rec)
        {
            int       candidateCount = 0, nodeStackHeight = 1;
            BvhNode **nodeStackTail = wa.Nodes;
            Entity *  candidateListTail = wa.Entities - 1, candidateListHead = wa.Entities;
            float3    rayInvDirection = rcp(r.Direction);

            *nodeStackTail = &n;

            while (nodeStackHeight > 0)
            {
                BvhNode *nodePtr = *nodeStackTail--;
                nodeStackHeight--;

                if (!nodePtr->Bounds.Hit(r.Origin, rayInvDirection, tMin, tMax))
                {
                    continue;
                }
#if FULL_DIAGNOSTICS
                diagnostics.BoundsHitCount++;
#endif
                if (nodePtr->IsLeaf)
                {
                    *++candidateListTail = entities[nodePtr->EntityId];
                    candidateCount++;
                }
                else
                {
                    *++nodeStackTail = nodePtr->Left;
                    *++nodeStackTail = nodePtr->Right;
                    nodeStackHeight += 2;
                }
            }
#if FULL_DIAGNOSTICS
            diagnostics.CandidateCount = candidateCount;
#endif
            if (candidateCount == 0)
            {
                rec = default;
                return(false);
            }

#if BVH_SIMD
            // TODO: this is fully broken
            // skip SIMD codepath if there's only one
            if (candidateCount == 1)
            {
                return(candidateListHead->Hit(r, tMin, tMax, out rec));
            }

            var simdSpheresHead = (Sphere4 *)wa.Vectors;
            int simdBlockCount  = (int)ceil(candidateCount / 4.0f);

            float4 a = dot(r.Direction, r.Direction);
            int4   curId = int4(0, 1, 2, 3), hitId = -1;
            float4 hitT = tMax;

            Sphere4 *blockCursor     = simdSpheresHead;
            Entity * candidateCursor = candidateListHead;
            int      candidateIndex  = 0;
            for (int i = 0; i < simdBlockCount; i++)
            {
                for (int j = 0; j < 4; j++)
                {
                    if (candidateIndex < candidateCount)
                    {
                        Sphere *sphereData = candidateCursor->AsSphere;
                        float3  center     = sphereData->Center(r.Time);
                        blockCursor->CenterX[j]       = center.x;
                        blockCursor->CenterY[j]       = center.y;
                        blockCursor->CenterZ[j]       = center.z;
                        blockCursor->SquaredRadius[j] = sphereData->SquaredRadius;
                        ++candidateCursor;
                        ++candidateIndex;
                    }
                    else
                    {
                        blockCursor->CenterX[j]       = float.MaxValue;
                        blockCursor->CenterY[j]       = float.MaxValue;
                        blockCursor->CenterZ[j]       = float.MaxValue;
                        blockCursor->SquaredRadius[j] = 0;
                    }
                }

                float4 ocX = r.Origin.x - blockCursor->CenterX,
                       ocY = r.Origin.y - blockCursor->CenterY,
                       ocZ = r.Origin.z - blockCursor->CenterZ;

                float4 b            = ocX * r.Direction.x + ocY * r.Direction.y + ocZ * r.Direction.z;
                float4 c            = ocX * ocX + ocY * ocY + ocZ * ocZ - blockCursor->SquaredRadius;
                float4 discriminant = b * b - a * c;

                bool4 discriminantTest = discriminant > 0;

                if (any(discriminantTest))
                {
                    float4 sqrtDiscriminant = sqrt(discriminant);

                    float4 t0 = (-b - sqrtDiscriminant) / a;
                    float4 t1 = (-b + sqrtDiscriminant) / a;

                    float4 t    = select(t1, t0, t0 > tMin);
                    bool4  mask = discriminantTest & t > tMin & t < hitT;

                    hitId = select(hitId, curId, mask);
                    hitT  = select(hitT, t, mask);
                }

                curId += 4;
                ++blockCursor;
            }

            if (all(hitId == -1))
            {
                rec = default;
                return(false);
            }

            float   minDistance   = cmin(hitT);
            int     laneMask      = bitmask(hitT == minDistance);
            int     firstLane     = tzcnt(laneMask);
            int     closestId     = hitId[firstLane];
            Sphere *closestSphere = candidateListHead[closestId].AsSphere;
            float3  point         = r.GetPoint(minDistance);

            rec = new HitRecord(minDistance, point,
                                (point - closestSphere->Center(r.Time)) / closestSphere->Radius,
                                closestSphere->Material);

            return(true);
#else
            // iterative candidate tests (non-SIMD)
            bool anyHit = candidateListHead->Hit(r, tMin, tMax, ref rng, out rec);
            for (int i = 1; i < candidateCount; i++)
            {
                bool thisHit = candidateListHead[i].Hit(r, tMin, tMax, ref rng, out HitRecord thisRec);
                if (thisHit && (!anyHit || thisRec.Distance < rec.Distance))
                {
                    anyHit = true;
                    rec    = thisRec;
                }
            }
            if (anyHit)
            {
                return(true);
            }

            rec = default;
            return(false);
#endif
        }