#include <string.h>

#include "coduo_x87emu.h" /* defines EMULATE_X87; x87 shim when it is 1 */
#include "coduo_native_x87.h"
#include "../animation/dobj_private.h"
#include "../core_math/core_math_private.h"
#include "cm_world_sector_private.h"

#define SV_TRACE_NOT_CAPSULE 0

enum {
    SV_SOLID_CONTENTS_MASK = 0x02000001,
    SV_SOLID_COMPONENT_MAX = 255,
    SV_SOLID_BMODEL = 0x00ffffff,
    SV_FLAG_CAPSULE = 0x00000200,
    SV_LINK_LEAF_CAP = 128,
    SV_DOBJ_LINK_BOUNDS_SERVER_MASK = 0x00000006,
    SV_DOBJ_LINK_BOUNDS_DEFAULT_CLIP = 0x00000002,
    SV_DOBJ_LINK_BOUNDS_MODEL_PARTS = 0x00000004,
    SV_ENTITYNUM_WORLD = 1022,
    SV_ENTITYNUM_NONE = 1023
};

static const vec3_t sv_zeroVector = {0.0f, 0.0f, 0.0f};

/* NOT_FROM_ORIGINAL_SOURCE: factored solid component clamp. */
static int32_t coduomp_sv_clamp_solid_component(int32_t value)
{
    if (value < 1) {
        return 1;
    }

    if (value > SV_SOLID_COMPONENT_MAX) {
        return SV_SOLID_COMPONENT_MAX;
    }

    return value;
}

/* NOT_FROM_ORIGINAL_SOURCE: named trace-result copy helper. */
static void coduomp_sv_copy_trace_result(trace_t *dest,
                                         const trace_t *source)
{
    memcpy(dest, source, sizeof(*dest));
}

/* NOT_FROM_ORIGINAL_SOURCE: factored point-trace bounds predicate. */
static qboolean coduomp_sv_trace_bounds_are_point(const vec3_t mins,
                                                  const vec3_t maxs)
{
    /*
     * Stock accumulates one flat 80-bit chain -- fld maxs0; fsub mins0;
     * fadd maxs1; fsub mins1; fadd maxs2; fsub mins2 (VA 0x809b0f5 and
     * 0x809b56d) -- not per-axis parenthesised differences.
     */
    return (maxs[0] - mins[0] + maxs[1] - mins[1] + maxs[2] - mins[2]) ==
           0.0f;
}

/* NOT_FROM_ORIGINAL_SOURCE: factored trace-owner sentinel conversion. */
static int32_t coduomp_sv_trace_owner_or_minus_one(
    int32_t entityNum)
{
    if (entityNum == SV_ENTITYNUM_NONE) {
        return -1;
    }

    int32_t ownerNum = SV_GentityNum(entityNum)->traceOwnerNum;
    if (ownerNum == SV_ENTITYNUM_NONE) {
        return -1;
    }

    return ownerNum;
}

/* NOT_FROM_ORIGINAL_SOURCE: factored trace entity skip predicate. */
static qboolean coduomp_sv_should_skip_trace_entity(
    int32_t entityNum,
    const sharedEntity_t *entity,
    int32_t passEntityNum,
    int32_t passOwnerNum)
{
    return passEntityNum != SV_ENTITYNUM_NONE &&
           (entityNum == passEntityNum ||
            entity->traceOwnerNum == passEntityNum ||
            entity->traceOwnerNum == passOwnerNum);
}

/* NOT_FROM_ORIGINAL_SOURCE: factored bmodel/current-angle selector. */
static const float *coduomp_sv_trace_angles_for_entity(
    const sharedEntity_t *entity)
{
    if (entity->bmodel == 0) {
        return sv_zeroVector;
    }

    return entity->currentAngles;
}

/* NOT_FROM_ORIGINAL_SOURCE: factored swept trace bounds setup. */
static void coduomp_sv_init_swept_trace_work_bounds(
    cmClipMoveWork_t *work,
    const vec3_t start,
    const vec3_t mins,
    const vec3_t maxs,
    const vec3_t end)
{
    for (int32_t axis = 0; axis < 3; axis++) {
        /*
         * Stock rounds the raw difference/sum to a float slot before
         * halving (fstp pairs at VA 0x809b27c/0x809b2bc and
         * 0x809b384/0x809b3b8), so keep the two-stage rounding.
         */
        float halfSize = maxs[axis] - mins[axis];
        float offset = maxs[axis] + mins[axis];

        halfSize *= 0.5f;
        offset *= 0.5f;
        work->mins[axis] = halfSize * -1.0f;
        work->maxs[axis] = halfSize;
        work->expandedHalfSize[axis] = halfSize + 1.0f;
        work->start[axis] = start[axis] + offset;
        work->end[axis] = end[axis] + offset;
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: factored swept sight-trace bounds setup. */
static void coduomp_sv_init_swept_sight_work_bounds(
    cmClipSightTraceWork_t *work, const vec3_t start,
    const vec3_t mins, const vec3_t maxs, const vec3_t end)
{
    for (int32_t axis = 0; axis < 3; axis++) {
        /*
         * Stock rounds the raw difference/sum to a float slot before
         * halving (fstp pairs at VA 0x809b702/0x809b742 and
         * 0x809b80a/0x809b83e), so keep the two-stage rounding.
         */
        float halfSize = maxs[axis] - mins[axis];
        float offset = maxs[axis] + mins[axis];

        halfSize *= 0.5f;
        offset *= 0.5f;
        work->mins[axis] = halfSize * -1.0f;
        work->maxs[axis] = halfSize;
        work->expandedHalfSize[axis] = halfSize + 1.0f;
        work->start[axis] = start[axis] + offset;
        work->end[axis] = end[axis] + offset;
    }
}

int32_t SV_ClipHandleForEntity(const sharedEntity_t *entity)
{
    if (entity->bmodel != 0) {
        return CM_InlineModel(entity->entityState.itemIndex);
    }

    return CM_TempBoxModel(entity->mins, entity->maxs, entity->contents,
                           (entity->svFlags & SV_FLAG_CAPSULE) != 0);
}

void SV_SnapVector(vec3_t vector)
{
    for (int32_t axis = 0; axis < 3; axis++) {
        int32_t rounded = Q_rint(vector[axis]);
        /* Stock subtracts the fild-exact integer (VA 0x8099da7); no
         * float narrowing of the operand. */
        float delta = rounded - vector[axis];

        if (delta * delta < 0.0000010000001f) {
            vector[axis] = (float)rounded;
        }
    }
}

void SV_SetBrushModel(sharedEntity_t *gentity)
{
    int32_t model = CM_InlineModel(gentity->entityState.itemIndex);

    CM_ModelBounds(model, gentity->mins, gentity->maxs);
    gentity->bmodel = 1;
    gentity->contents = -1;
    SV_LinkEntity(gentity);
}

qboolean SV_EntityContact(const vec3_t mins, const vec3_t maxs,
                          sharedEntity_t *gentity, qboolean capsule)
{
    trace_t trace;
    int32_t clipHandle = SV_ClipHandleForEntity(gentity);

    CM_TransformedBoxTraceExternal(&trace, sv_zeroVector, sv_zeroVector, mins,
                                   maxs, clipHandle, -1,
                                   gentity->currentOrigin,
                                   gentity->currentAngles, capsule);

    return trace.startsolid;
}

void SV_UnlinkEntity(sharedEntity_t *gentity)
{
    svEntity_t *svEntity = SV_SvEntityForGentity(gentity);

    gentity->linked = 0;
    SV_UnlinkEntityFromWorldSector(svEntity);
}

void SV_LinkEntity(sharedEntity_t *gentity)
{
    svEntity_t *svEntity = SV_SvEntityForGentity(gentity);

    if (gentity->bmodel == 0) {
        if ((gentity->contents & SV_SOLID_CONTENTS_MASK) == 0) {
            gentity->entityState.solid = 0;
        } else {
#if defined(__x86_64__)
            coduo_x87_truncation_control_t conversionControl;
            int32_t x = coduomp_sv_clamp_solid_component(
                CODUO_X87_TRUNCATE_I32_FIRST(
                    &conversionControl, (long double)gentity->maxs[0]));
#else
            int32_t x =
                coduomp_sv_clamp_solid_component((int32_t)gentity->maxs[0]);
#endif
            static const float lowerSolidBias = 1.0f;
            static const float upperSolidBias = 32.0f;
#if EMULATE_X87
            /* 0x08099e72..0x08099ef6: each +/- expression remains live in
             * x87 until truncating fistp. maxs[0] is a direct stored-float
             * conversion and needs no adaptation. */
            int32_t zd = coduomp_sv_clamp_solid_component(
                x87f_store_i32_trunc(x87f_sub(
                    x87f_load_f32(lowerSolidBias),
                    x87f_load_f32(gentity->mins[2]))));
            int32_t zu = coduomp_sv_clamp_solid_component(
                x87f_store_i32_trunc(x87f_add(
                    x87f_load_f32(gentity->maxs[2]),
                    x87f_load_f32(upperSolidBias))));
#elif defined(__x86_64__)
            int32_t zd = coduomp_sv_clamp_solid_component(
                CODUO_X87_TRUNCATE_I32_NEXT(
                    &conversionControl,
                    (long double)lowerSolidBias -
                    (long double)gentity->mins[2]));
            int32_t zu = coduomp_sv_clamp_solid_component(
                CODUO_X87_TRUNCATE_I32_NEXT(
                    &conversionControl,
                    (long double)gentity->maxs[2] +
                    (long double)upperSolidBias));
#else
            int32_t zd =
                coduomp_sv_clamp_solid_component(
                    (int32_t)(lowerSolidBias - gentity->mins[2]));
            int32_t zu =
                coduomp_sv_clamp_solid_component(
                    (int32_t)(gentity->maxs[2] + upperSolidBias));
#endif

            gentity->entityState.solid = x | (zd << 8) | (zu << 16);
        }
    } else {
        gentity->entityState.solid = SV_SOLID_BMODEL;
    }

    SV_SnapVector(gentity->currentAngles);

    if (gentity->bmodel == 0 ||
        (gentity->currentAngles[0] == 0.0f &&
         gentity->currentAngles[1] == 0.0f &&
         gentity->currentAngles[2] == 0.0f)) {
        for (int32_t axis = 0; axis < 3; axis++) {
            gentity->absMin[axis] =
                gentity->currentOrigin[axis] + gentity->mins[axis];
            gentity->absMax[axis] =
                gentity->currentOrigin[axis] + gentity->maxs[axis];
        }
    } else {
        float radius = RadiusFromBounds(gentity->mins, gentity->maxs);

        for (int32_t axis = 0; axis < 3; axis++) {
            gentity->absMin[axis] = gentity->currentOrigin[axis] - radius;
            gentity->absMax[axis] = gentity->currentOrigin[axis] + radius;
        }
    }

    for (int32_t axis = 0; axis < 3; axis++) {
        gentity->absMin[axis] -= 1.0f;
        gentity->absMax[axis] += 1.0f;
    }

    svEntity->numClusters = 0;
    svEntity->lastCluster = 0;
    svEntity->areaNum = -1;
    svEntity->areaNum2 = -1;

    int32_t lastLeaf = 0;
    int32_t leafs[SV_LINK_LEAF_CAP];
    int32_t leafCount =
        CM_BoxLeafnums(gentity->absMin, gentity->absMax, leafs,
                       SV_LINK_LEAF_CAP, &lastLeaf);

    if (leafCount == 0) {
        SV_UnlinkEntityFromWorldSector(svEntity);
        return;
    }

    for (int32_t leafIndex = 0; leafIndex < leafCount; leafIndex++) {
        int32_t area = CM_LeafArea(leafs[leafIndex]);

        if (area == -1) {
            continue;
        }

        if (svEntity->areaNum == -1 || svEntity->areaNum == area) {
            svEntity->areaNum = area;
        } else {
            if (svEntity->areaNum2 != -1 && svEntity->areaNum2 != area &&
                sv.state == CODUO_SS_LOADING) {
                Com_DPrintf("Object %i touching 3 areas at %f %f %f\n",
                            gentity->entityState.s_number,
                            (double)gentity->absMin[0],
                            (double)gentity->absMin[1],
                            (double)gentity->absMin[2]);
            }

            svEntity->areaNum2 = area;
        }
    }

    svEntity->numClusters = 0;
    int32_t leafIndex;
    for (leafIndex = 0; leafIndex < leafCount; leafIndex++) {
        int32_t cluster =
            CM_LeafCluster(leafs[leafIndex]);

        if (cluster == -1) {
            continue;
        }

        svEntity->clusterNums[svEntity->numClusters] = cluster;
        svEntity->numClusters++;
        if (svEntity->numClusters == CODUO_HOST_ENTITY_LINK_CLUSTER_NUM_COUNT) {
            break;
        }
    }

    if (leafIndex != leafCount) {
        svEntity->lastCluster = CM_LeafCluster(lastLeaf);
    }

    gentity->linked = 1;
    if (gentity->contents == 0) {
        SV_UnlinkEntityFromWorldSector(svEntity);
        return;
    }

    DObj *dobj =
        DObjGetForEntity(gentity->entityState.s_number);
    if (dobj == NULL ||
        (gentity->svFlags & SV_DOBJ_LINK_BOUNDS_SERVER_MASK) == 0) {
        SV_LinkEntityToWorldSector(svEntity, gentity->absMin, gentity->absMax);
        return;
    }

    vec3_t linkMins;
    vec3_t linkMaxs;
    if ((gentity->svFlags & SV_DOBJ_LINK_BOUNDS_DEFAULT_CLIP) == 0) {
        DObjGetBounds(dobj, linkMins, linkMaxs);
        linkMins[0] += gentity->currentOrigin[0];
        linkMins[1] += gentity->currentOrigin[1];
        linkMaxs[0] += gentity->currentOrigin[0];
        linkMaxs[1] += gentity->currentOrigin[1];
    } else {
        linkMins[0] =
            gentity->currentOrigin[0] + sv_defaultEntityClipMins[0];
        linkMins[1] =
            gentity->currentOrigin[1] + sv_defaultEntityClipMins[1];
        linkMaxs[0] =
            gentity->currentOrigin[0] + sv_defaultEntityClipMaxs[0];
        linkMaxs[1] =
            gentity->currentOrigin[1] + sv_defaultEntityClipMaxs[1];
    }

    SV_LinkEntityToWorldSector(svEntity, linkMins, linkMaxs);
}

void SV_ClipMoveToEntity(cmClipMoveWork_t *traceWork,
                         svEntity_t *svEntity)
{
    /* 0x0809a59c..0x0809a5bd derives the identity from the host-link slot;
     * it does not trust the game entity's s.number field. */
    int32_t entityNum = (int32_t)(svEntity - &sv_entityLinks[0]);
    sharedEntity_t *gentity = SV_GentityNum(entityNum);

    if ((gentity->contents & traceWork->contentsMask) == 0) {
        return;
    }

    if (coduomp_sv_should_skip_trace_entity(
            entityNum, gentity, traceWork->passEntityNum,
            traceWork->passOwnerNum)) {
        return;
    }

    trace_t trace;
    trace.fraction = traceWork->bestTrace.fraction;
    CM_TransformedBoxTrace(&trace, traceWork->start, traceWork->end,
                           traceWork->mins, traceWork->maxs,
                           SV_ClipHandleForEntity(gentity),
                           traceWork->contentsMask, gentity->currentOrigin,
                           coduomp_sv_trace_angles_for_entity(gentity),
                           traceWork->capsule);

    if (traceWork->bestTrace.fraction <= trace.fraction) {
        traceWork->bestTrace.allsolid |= trace.allsolid;
        traceWork->bestTrace.startsolid |= trace.startsolid;
        return;
    }

    trace.allsolid |= traceWork->bestTrace.allsolid;
    trace.startsolid |= traceWork->bestTrace.startsolid;
    trace.entityNum = gentity->entityState.s_number;
    coduomp_sv_copy_trace_result(&traceWork->bestTrace, &trace);
}

void SV_SightTraceThroughEntity(cmPointTraceWork_t *sightTraceWork,
                                svEntity_t *svEntity)
{
    /* 0x0809a735..0x0809a756 derives the identity from the host-link slot;
     * DObj, VM and skip operations all retain this value. */
    int32_t entityNum = (int32_t)(svEntity - &sv_entityLinks[0]);
    sharedEntity_t *gentity = SV_GentityNum(entityNum);
    trace_t trace;

    if ((gentity->contents & sightTraceWork->contentsMask) == 0) {
        return;
    }

    if (coduomp_sv_should_skip_trace_entity(
            entityNum, gentity, sightTraceWork->passEntityNum,
            sightTraceWork->passOwnerNum)) {
        return;
    }

    if (sightTraceWork->useDObj != 0) {
        DObj *dobj = DObjGetForEntity(entityNum);

        if (dobj != NULL &&
            (gentity->svFlags & SV_DOBJ_LINK_BOUNDS_SERVER_MASK) != 0) {
            vec3_t dobjMins;
            vec3_t dobjMaxs;

            if ((gentity->svFlags & SV_DOBJ_LINK_BOUNDS_MODEL_PARTS) != 0) {
                if (!DObjHasContents(dobj, sightTraceWork->contentsMask)) {
                    return;
                }

                DObjGetBounds(dobj, dobjMins, dobjMaxs);
            } else {
                dobjMins[0] = sv_defaultEntityClipMins[0];
                dobjMins[1] = sv_defaultEntityClipMins[1];
                dobjMins[2] = sv_defaultEntityClipMins[2];
                dobjMaxs[0] = sv_defaultEntityClipMaxs[0];
                dobjMaxs[1] = sv_defaultEntityClipMaxs[1];
                dobjMaxs[2] = sv_defaultEntityClipMaxs[2];
            }

            for (int32_t axis = 0; axis < 3; ++axis) {
                dobjMins[axis] += gentity->currentOrigin[axis];
                dobjMaxs[axis] += gentity->currentOrigin[axis];
            }

            if (CM_TraceLineSkipsBox(sightTraceWork->start,
                                     sightTraceWork->end,
                                     dobjMins,
                                     dobjMaxs,
                                     sightTraceWork->bestTrace.fraction)) {
                return;
            }

            VM_Call(sv_gameVM, CODUO_GAME_DOBJ_CALC_POSE, entityNum);

            coduo_compact_affine_matrix43_t entityAxis;
            vec3_t localStart;
            vec3_t localEnd;
            coduo_dobj_trace_result_t dobjTrace;

            AnglesToAxis(gentity->currentAngles, &entityAxis);
            /*
             * Original VA 0x809a9ae..0x809aa02 leaves currentOrigin in the
             * compact matrix origin slots (+0x24..+0x2c) before the inverse
             * transform calls at 0x809a9cd/0x809a9ef.
             */
            entityAxis.origin[0] = gentity->currentOrigin[0];
            entityAxis.origin[1] = gentity->currentOrigin[1];
            entityAxis.origin[2] = gentity->currentOrigin[2];
            MatrixInverseTransformVector43(sightTraceWork->start, &entityAxis,
                                           localStart);
            MatrixInverseTransformVector43(sightTraceWork->end, &entityAxis,
                                           localEnd);

            dobjTrace.fraction = sightTraceWork->bestTrace.fraction;
            if ((gentity->svFlags & SV_DOBJ_LINK_BOUNDS_MODEL_PARTS) != 0) {
                DObjTraceModelParts(dobj, localStart, localEnd,
                                    sightTraceWork->contentsMask, &dobjTrace);
            } else {
                DObjTraceParts(dobj, localStart, localEnd,
                               sightTraceWork->dobjTracePartState,
                               &dobjTrace);
            }

            if (sightTraceWork->bestTrace.fraction <= dobjTrace.fraction) {
                sightTraceWork->bestTrace.allsolid |= dobjTrace.allsolid;
                sightTraceWork->bestTrace.startsolid |= dobjTrace.startsolid;
                return;
            }

            trace.fraction = dobjTrace.fraction;
            trace.surfaceFlags = dobjTrace.surfaceFlags;
            trace.contents = gentity->contents;
            trace.material = 0;
            trace.entityNum = entityNum;
            trace.partName = dobjTrace.hitPartNameHandle;
            trace.partGroup = dobjTrace.hitPartStateIndex;
            /*
             * ORIGINAL_BINARY_BUG: see
             * original-bugs/coduomp-point-dobj-trace-uninitialized-allsolid.md.
             *
             * Objdump for VA 0x809aab4..0x809ac31 copies compact DObj
             * startsolid (+0x18) into trace.startsolid, leaves the local
             * trace.allsolid byte unwritten, then ORs the previous best trace
             * allsolid into trace.allsolid. It does not copy compact DObj
             * allsolid (+0x19) on the closer-hit path.
             */
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wuninitialized"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wuninitialized"
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
            trace.allsolid |= sightTraceWork->bestTrace.allsolid;
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
            trace.startsolid = dobjTrace.startsolid;
            MatrixTransformVector(dobjTrace.normal, entityAxis.axis, trace.normal);

            trace.endpos[0] =
                sightTraceWork->start[0] +
                (sightTraceWork->end[0] - sightTraceWork->start[0]) *
                    trace.fraction;
            trace.endpos[1] =
                sightTraceWork->start[1] +
                (sightTraceWork->end[1] - sightTraceWork->start[1]) *
                    trace.fraction;
            trace.endpos[2] =
                sightTraceWork->start[2] +
                (sightTraceWork->end[2] - sightTraceWork->start[2]) *
                    trace.fraction;

            trace.startsolid |= sightTraceWork->bestTrace.startsolid;
            coduomp_sv_copy_trace_result(&sightTraceWork->bestTrace, &trace);
            return;
        }
    }

    trace.fraction = sightTraceWork->bestTrace.fraction;
    CM_TransformedBoxTrace(&trace, sightTraceWork->start, sightTraceWork->end,
                           sv_zeroVector, sv_zeroVector,
                           SV_ClipHandleForEntity(gentity),
                           sightTraceWork->contentsMask,
                           gentity->currentOrigin,
                           coduomp_sv_trace_angles_for_entity(gentity),
                           SV_TRACE_NOT_CAPSULE);

    if (sightTraceWork->bestTrace.fraction <= trace.fraction) {
        sightTraceWork->bestTrace.allsolid |= trace.allsolid;
        sightTraceWork->bestTrace.startsolid |= trace.startsolid;
        return;
    }

    trace.allsolid |= sightTraceWork->bestTrace.allsolid;
    trace.startsolid |= sightTraceWork->bestTrace.startsolid;
    trace.entityNum = entityNum;
    trace.material = 0;
    trace.contents = gentity->contents;
    coduomp_sv_copy_trace_result(&sightTraceWork->bestTrace, &trace);
}

int32_t
SV_SweptSightTraceThroughEntity(
    cmClipSightTraceWork_t *sightTraceWork,
    svEntity_t *svEntity)
{
    int32_t entityNum = (int32_t)(svEntity - &sv_entityLinks[0]);
    sharedEntity_t *gentity = SV_GentityNum(entityNum);

    if ((gentity->contents & sightTraceWork->contentsMask) == 0) {
        return 0;
    }

    if (coduomp_sv_should_skip_trace_entity(
            entityNum, gentity, sightTraceWork->passEntityNum,
            sightTraceWork->passEntityOwnerNum)) {
        return 0;
    }

    if (coduomp_sv_should_skip_trace_entity(
            entityNum, gentity, sightTraceWork->passOwnerNum,
            sightTraceWork->passOwnerOwnerNum)) {
        return 0;
    }

    qboolean hit = CM_TransformedBoxSightTrace(
        0, sightTraceWork->start, sightTraceWork->end, sightTraceWork->mins,
        sightTraceWork->maxs, SV_ClipHandleForEntity(gentity),
        sightTraceWork->contentsMask, gentity->currentOrigin,
        coduomp_sv_trace_angles_for_entity(gentity),
        sightTraceWork->capsule);

    return hit == 0 ? 0 : -1;
}

int32_t
SV_PointSightTraceThroughEntity(
    cmPointSightTraceWork_t *sightTraceWork,
    svEntity_t *svEntity)
{
    int32_t entityNum = (int32_t)(svEntity - &sv_entityLinks[0]);
    sharedEntity_t *gentity = SV_GentityNum(entityNum);

    if ((gentity->contents & sightTraceWork->contentsMask) == 0) {
        return 0;
    }

    if (coduomp_sv_should_skip_trace_entity(
            entityNum, gentity, sightTraceWork->passEntityNum,
            sightTraceWork->passEntityOwnerNum)) {
        return 0;
    }

    if (coduomp_sv_should_skip_trace_entity(
            entityNum, gentity, sightTraceWork->passOwnerNum,
            sightTraceWork->passOwnerOwnerNum)) {
        return 0;
    }

    qboolean hit = CM_TransformedBoxSightTrace(
        0, sightTraceWork->start, sightTraceWork->end, sv_zeroVector,
        sv_zeroVector, SV_ClipHandleForEntity(gentity),
        sightTraceWork->contentsMask, gentity->currentOrigin,
        coduomp_sv_trace_angles_for_entity(gentity), 0);

    return hit == 0 ? 0 : -1;
}

void SV_Trace(trace_t *trace, const vec3_t start,
              const vec3_t mins, const vec3_t maxs, const vec3_t end,
              int32_t passEntityNum,
              int32_t contentMask, qboolean capsule,
              qboolean useDObj,
              const uint8_t *dobjTracePartState,
              qboolean locational)
{
    const float *traceMins = mins != NULL ? mins : sv_zeroVector;
    const float *traceMaxs = maxs != NULL ? maxs : sv_zeroVector;
    trace_t worldTrace;

    memset(&worldTrace, 0, sizeof(worldTrace));
    CM_BoxTrace(&worldTrace, start, end, traceMins, traceMaxs, 0, contentMask,
                capsule);
    worldTrace.entityNum =
        worldTrace.fraction == 1.0f ? SV_ENTITYNUM_NONE
                                    : SV_ENTITYNUM_WORLD;

    if (worldTrace.fraction == 0.0f) {
        coduomp_sv_copy_trace_result(trace, &worldTrace);
        return;
    }

    if (locational != 0) {
        CM_PointTraceStaticModels(&worldTrace, start, end, contentMask);
        if (worldTrace.fraction == 0.0f) {
            coduomp_sv_copy_trace_result(trace, &worldTrace);
            return;
        }
    }

    if (coduomp_sv_trace_bounds_are_point(traceMins, traceMaxs)) {
        cmPointTraceWork_t sightTraceWork;

        sightTraceWork.start[0] = start[0];
        sightTraceWork.start[1] = start[1];
        sightTraceWork.start[2] = start[2];
        sightTraceWork.end[0] = end[0];
        sightTraceWork.end[1] = end[1];
        sightTraceWork.end[2] = end[2];
        coduomp_sv_copy_trace_result(&sightTraceWork.bestTrace, &worldTrace);
        sightTraceWork.passEntityNum = passEntityNum;
        sightTraceWork.passOwnerNum =
            coduomp_sv_trace_owner_or_minus_one(passEntityNum);
        sightTraceWork.contentsMask = contentMask;
        sightTraceWork.useDObj = useDObj;
        sightTraceWork.dobjTracePartState = dobjTracePartState;

        SV_SightTraceEntities(&sightTraceWork);
        coduomp_sv_copy_trace_result(trace, &sightTraceWork.bestTrace);
        return;
    }

    cmClipMoveWork_t traceWork;
    coduomp_sv_init_swept_trace_work_bounds(
        &traceWork, start, traceMins, traceMaxs, end);
    coduomp_sv_copy_trace_result(&traceWork.bestTrace, &worldTrace);
    traceWork.passEntityNum = passEntityNum;
    traceWork.passOwnerNum =
        coduomp_sv_trace_owner_or_minus_one(passEntityNum);
    traceWork.contentsMask = contentMask;
    traceWork.capsule = capsule;

    SV_TraceEntities(&traceWork);

    if (traceWork.bestTrace.fraction < worldTrace.fraction) {
        /*
         * Stock spills end-start to float slots before the lerp (fstp at
         * VA 0x809b461/0x809b474/0x809b487), so round the deltas first.
         */
        vec3_t delta;

        delta[0] = end[0] - start[0];
        delta[1] = end[1] - start[1];
        delta[2] = end[2] - start[2];
        traceWork.bestTrace.endpos[0] =
            start[0] + delta[0] * traceWork.bestTrace.fraction;
        traceWork.bestTrace.endpos[1] =
            start[1] + delta[1] * traceWork.bestTrace.fraction;
        traceWork.bestTrace.endpos[2] =
            start[2] + delta[2] * traceWork.bestTrace.fraction;
    }

    coduomp_sv_copy_trace_result(trace, &traceWork.bestTrace);
}

void SV_SightTrace(int32_t *trace,
                   const vec3_t start, const vec3_t mins, const vec3_t maxs,
                   const vec3_t end, int32_t passEntityNum,
                   int32_t passOwnerNum,
                   int32_t contentMask, qboolean capsuleMode)
{
    const float *traceMins = mins != NULL ? mins : sv_zeroVector;
    const float *traceMaxs = maxs != NULL ? maxs : sv_zeroVector;

    *trace = CM_BoxSightTrace(*trace, start, end, traceMins, traceMaxs, 0,
                              contentMask, capsuleMode);
    if (*trace != 0) {
        return;
    }

    if (coduomp_sv_trace_bounds_are_point(traceMins, traceMaxs)) {
        cmPointSightTraceWork_t sightTraceWork;

        sightTraceWork.start[0] = start[0];
        sightTraceWork.start[1] = start[1];
        sightTraceWork.start[2] = start[2];
        sightTraceWork.end[0] = end[0];
        sightTraceWork.end[1] = end[1];
        sightTraceWork.end[2] = end[2];
        sightTraceWork.passEntityNum = passEntityNum;
        sightTraceWork.passOwnerNum = passOwnerNum;
        sightTraceWork.passEntityOwnerNum =
            coduomp_sv_trace_owner_or_minus_one(passEntityNum);
        sightTraceWork.passOwnerOwnerNum =
            coduomp_sv_trace_owner_or_minus_one(passOwnerNum);
        sightTraceWork.contentsMask = contentMask;

        *trace = SV_PointSightTraceEntities(&sightTraceWork);
        return;
    }

    cmClipSightTraceWork_t sightTraceWork;
    coduomp_sv_init_swept_sight_work_bounds(
        &sightTraceWork, start, traceMins, traceMaxs, end);
    sightTraceWork.passEntityNum = passEntityNum;
    sightTraceWork.passOwnerNum = passOwnerNum;
    sightTraceWork.passEntityOwnerNum =
        coduomp_sv_trace_owner_or_minus_one(passEntityNum);
    sightTraceWork.passOwnerOwnerNum =
        coduomp_sv_trace_owner_or_minus_one(passOwnerNum);
    sightTraceWork.contentsMask = contentMask;
    sightTraceWork.capsule = capsuleMode;

    *trace = SV_SweptSightTraceEntities(&sightTraceWork);
}

int32_t
SV_SightTraceToEntity(const vec3_t start, const vec3_t mins,
                      const vec3_t maxs, const vec3_t end,
                      int32_t entityNum,
                      int32_t contentMask, qboolean capsule)
{
    sharedEntity_t *gentity = SV_GentityNum(entityNum);

    if ((gentity->contents & contentMask) == 0) {
        return 0;
    }

    vec3_t traceAbsMin;
    vec3_t traceAbsMax;
    for (int32_t axis = 0; axis < 3; axis++) {
        if (start[axis] < end[axis]) {
            traceAbsMin[axis] = start[axis] + mins[axis] - 1.0f;
            traceAbsMax[axis] = end[axis] + maxs[axis] + 1.0f;
        } else {
            traceAbsMin[axis] = end[axis] + mins[axis] - 1.0f;
            traceAbsMax[axis] = start[axis] + maxs[axis] + 1.0f;
        }
    }

    if (traceAbsMax[0] < gentity->absMin[0] ||
        traceAbsMax[1] < gentity->absMin[1] ||
        traceAbsMax[2] < gentity->absMin[2] ||
        gentity->absMax[0] < traceAbsMin[0] ||
        gentity->absMax[1] < traceAbsMin[1] ||
        gentity->absMax[2] < traceAbsMin[2]) {
        return 0;
    }

    qboolean hit = CM_TransformedBoxSightTrace(
        0, start, end, mins, maxs, SV_ClipHandleForEntity(gentity),
        contentMask, gentity->currentOrigin,
        coduomp_sv_trace_angles_for_entity(gentity), capsule);

    return hit == 0 ? 0 : -1;
}
